[flang-commits] [flang] [flang][lowering] fix vector subscripts in character elemental procedures (PR #156661)
via flang-commits
flang-commits at lists.llvm.org
Wed Sep 3 06:01:50 PDT 2025
https://github.com/jeanPerier created https://github.com/llvm/llvm-project/pull/156661
Fixes https://github.com/llvm/llvm-project/issues/145151
Character elemental procedures require evaluating the result specification expression outside of the elemental loops when the function result length is not a constant. This is needed so that the array result storage can be allocated before the evaluation if a storage is needed.
Because the result specification expression may depend on inquires about the dummy argument (but not usages of values thanks to F2023 C15121), some representation of the dummy must be created. Since this is an elemental call, this requires providing an element, and not the whole array actual argument (we only care about the properties of such element it does not matter which element is being used).
The previous code was creating the element with a type cast from the base array, but this did not work with vector subscripted arrays where the lowering representation is more complex. This caused a compiler assert to fire.
Switch the code to address the first element instead (which is OK even for zero size arrays since the data is not read).
>From 357f2b6457bf8543c56f488d707bb629f2fa7b84 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Wed, 3 Sep 2025 05:49:57 -0700
Subject: [PATCH] [flang][lowering] fix vector subscripts in character
elemental procedures
---
flang/include/flang/Lower/HlfirIntrinsics.h | 5 ++
flang/lib/Lower/ConvertCall.cpp | 58 ++++++++++++++++++-
.../test/Lower/array-elemental-calls-char.f90 | 50 +++++++++++++++-
3 files changed, 109 insertions(+), 4 deletions(-)
diff --git a/flang/include/flang/Lower/HlfirIntrinsics.h b/flang/include/flang/Lower/HlfirIntrinsics.h
index 088f8bccef4aa..89aad7f462aba 100644
--- a/flang/include/flang/Lower/HlfirIntrinsics.h
+++ b/flang/include/flang/Lower/HlfirIntrinsics.h
@@ -50,9 +50,14 @@ struct PreparedActualArgument {
: actual{actual}, isPresent{isPresent} {}
PreparedActualArgument(hlfir::ElementalAddrOp vectorSubscriptedActual)
: actual{vectorSubscriptedActual}, isPresent{std::nullopt} {}
+
void setElementalIndices(mlir::ValueRange &indices) {
oneBasedElementalIndices = &indices;
}
+ void resetElementalIndices() { oneBasedElementalIndices = nullptr; }
+ bool hasElementalIndices() const {
+ return oneBasedElementalIndices != nullptr;
+ }
/// Get the prepared actual. If this is an array argument in an elemental
/// call, the current element value will be returned.
diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 04dcc9250be61..c6a705a007b6a 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -903,6 +903,15 @@ struct CallContext {
return false;
}
+ int getRankIfElementalProcWithArrayArgs() const {
+ if (procRef.IsElemental())
+ for (const std::optional<Fortran::evaluate::ActualArgument> &arg :
+ procRef.arguments())
+ if (arg && arg->Rank() != 0)
+ return arg->Rank();
+ return 0;
+ }
+
/// Is this a statement function reference?
bool isStatementFunctionCall() const {
if (const Fortran::semantics::Symbol *symbol = procRef.proc().GetSymbol())
@@ -2367,10 +2376,37 @@ class ElementalUserCallBuilder
auto &converter = callContext.converter;
mlir::Type idxTy = builder.getIndexType();
llvm::SmallVector<CallCleanUp> callCleanUps;
+ llvm::SmallVector<mlir::Value> mockIndices;
+ mlir::ValueRange mockIndicesRange;
+
+ // If this is an elemental call, evaluate the specification expressions
+ // using the first elements of dummy arguments. The address of these
+ // elements will not be read thanks to Fortran 2023 C15121 (dummy
+ // arguments can only be inquired about inside the specification for the
+ // result), so no care is needed for the zero size array case.
+ bool mustResetElementalIndices = false;
+ if (int elementalRank = callContext.getRankIfElementalProcWithArrayArgs()) {
+ mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+ mockIndices.assign(elementalRank, one);
+ mockIndicesRange = mockIndices;
+ for (auto &preparedActual : loweredActuals)
+ if (preparedActual) {
+ assert(
+ !preparedActual->hasElementalIndices() &&
+ "result length must be computed before the elemental loop nest");
+ preparedActual->setElementalIndices(mockIndicesRange);
+ }
+ mustResetElementalIndices = true;
+ }
prepareUserCallArguments(loweredActuals, caller, callSiteType, callContext,
callCleanUps);
+ if (mustResetElementalIndices)
+ for (auto &preparedActual : loweredActuals)
+ if (preparedActual)
+ preparedActual->resetElementalIndices();
+
callContext.symMap.pushScope();
// Map prepared argument to dummy symbol to be able to lower spec expr.
@@ -2381,7 +2417,7 @@ class ElementalUserCallBuilder
fir::ExtendedValue exv = Fortran::lower::translateToExtendedValue(
loc, builder, hlfir::Entity{input}, callContext.stmtCtx);
fir::FortranVariableOpInterface variableIface = hlfir::genDeclare(
- loc, builder, exv, "dummy.tmp", fir::FortranVariableFlagsAttr{});
+ loc, builder, exv, "mock.dummy", fir::FortranVariableFlagsAttr{});
callContext.symMap.addVariableDefinition(*sym, variableIface);
}
@@ -2769,11 +2805,24 @@ genIntrinsicRef(const Fortran::evaluate::SpecificIntrinsic *intrinsic,
return genIntrinsicRef(intrinsic, *intrinsicEntry, callContext);
}
+namespace {
+/// Helper to erase temporary ElementalAddrOp created for vector subscripted
+/// arguments of elemental procedures using C++ lifetime scopes.
+struct ElementalAddrEraser {
+ ~ElementalAddrEraser() {
+ for (hlfir::ElementalAddrOp elementalAddr : elementalAddrOps)
+ elementalAddr.erase();
+ }
+ llvm::SmallVector<hlfir::ElementalAddrOp> elementalAddrOps;
+};
+} // namespace
+
/// Main entry point to lower procedure references, regardless of what they are.
static std::optional<hlfir::EntityWithAttributes>
genProcedureRef(CallContext &callContext) {
mlir::Location loc = callContext.loc;
fir::FirOpBuilder &builder = callContext.getBuilder();
+ ElementalAddrEraser elementalAddrEraser;
if (auto *intrinsic = callContext.procRef.proc().GetSpecificIntrinsic())
return genIntrinsicRef(intrinsic, callContext);
// Intercept non BIND(C) module procedure reference that have lowering
@@ -2856,6 +2905,12 @@ genProcedureRef(CallContext &callContext) {
Fortran::lower::convertVectorSubscriptedExprToElementalAddr(
loc, callContext.converter, *expr, callContext.symMap,
callContext.stmtCtx);
+ // ElementalAddrOp bodies will be copied as needed when generating the
+ // elemental calls to generate the operand element addressing and the
+ // operation must be erased afterwards (cannot wait on dead code
+ // elimination because these operations are invalid outside of
+ // hlfir.region_assign contexts).
+ elementalAddrEraser.elementalAddrOps.push_back(elementalAddr);
loweredActuals.emplace_back(
Fortran::lower::PreparedActualArgument{elementalAddr});
continue;
@@ -2905,7 +2960,6 @@ hlfir::Entity Fortran::lower::PreparedActualArgument::getActual(
loc, builder, elementalAddr, *oneBasedElementalIndices, mapper,
/*mustRecursivelyInline=*/alwaysFalse);
assert(elementalAddr.getCleanup().empty() && "no clean-up expected");
- elementalAddr.erase();
return hlfir::Entity{addr};
}
diff --git a/flang/test/Lower/array-elemental-calls-char.f90 b/flang/test/Lower/array-elemental-calls-char.f90
index 4ee1165ae3219..dce5c0ae625a4 100644
--- a/flang/test/Lower/array-elemental-calls-char.f90
+++ b/flang/test/Lower/array-elemental-calls-char.f90
@@ -240,8 +240,9 @@ subroutine foo6(c)
! CHECK: %[[VAL_4:.*]] = arith.constant 10 : index
! CHECK: %[[VAL_5:.*]] = fir.shape %[[VAL_4]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_6:.*]]:2 = hlfir.declare %[[VAL_3]](%[[VAL_5]]) typeparams %[[VAL_2]]#1 dummy_scope %[[VAL_1]] {uniq_name = "_QMchar_elemFfoo6Ec"} : (!fir.ref<!fir.array<10x!fir.char<1,?>>>, !fir.shape<1>, index, !fir.dscope) -> (!fir.box<!fir.array<10x!fir.char<1,?>>>, !fir.ref<!fir.array<10x!fir.char<1,?>>>)
-! CHECK: %[[VAL_7:.*]] = fir.convert %[[VAL_6]]#1 : (!fir.ref<!fir.array<10x!fir.char<1,?>>>) -> !fir.ref<!fir.char<1,?>>
-! CHECK: %[[VAL_8:.*]]:2 = hlfir.declare %[[VAL_7]] typeparams %[[VAL_2]]#1 {uniq_name = "dummy.tmp"} : (!fir.ref<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+! CHECK: %[[VAL_7:.*]] = hlfir.designate %[[VAL_6:.*]]#0 (%c1) typeparams %1#1 : (!fir.box<!fir.array<10x!fir.char<1,?>>>, index, index) -> !fir.boxchar<1>
+! CHECK: %[[VAL_7B:.*]]:2 = fir.unboxchar %[[VAL_7]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+! CHECK: %[[VAL_8:.*]]:2 = hlfir.declare %[[VAL_7B]]#0 typeparams %[[VAL_2]]#1 {uniq_name = "mock.dummy"} : (!fir.ref<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
! CHECK: %[[VAL_9:.*]] = fir.convert %[[VAL_2]]#1 : (index) -> i64
! CHECK: %[[VAL_10:.*]] = fir.convert %[[VAL_9]] : (i64) -> i32
! CHECK: %[[VAL_11:.*]] = fir.convert %[[VAL_10]] : (i32) -> i64
@@ -274,3 +275,48 @@ subroutine foo6(c)
! CHECK: }
end module
+
+subroutine bug_145151(c, vector_subscript)
+ interface
+ elemental function f(c_dummy)
+ character(*), intent(in) :: c_dummy
+ character(len(c_dummy, KIND=8)) :: f
+ end
+ end interface
+ integer(8) :: vector_subscript(100)
+ character(*) :: c(100)
+ c = f(c(vector_subscript))
+end subroutine
+! CHECK-LABEL: func.func @_QPbug_145151(
+! CHECK-SAME: %[[ARG0:.*]]: !fir.boxchar<1>
+! CHECK: %[[VAL_1:.*]]:2 = fir.unboxchar %[[ARG0]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+! CHECK: %[[VAL_2:.*]] = fir.convert %[[VAL_1]]#0 : (!fir.ref<!fir.char<1,?>>) -> !fir.ref<!fir.array<100x!fir.char<1,?>>>
+! CHECK: %[[VAL_3:.*]] = arith.constant 100 : index
+! CHECK: %[[VAL_4:.*]] = fir.shape %[[VAL_3]] : (index) -> !fir.shape<1>
+! CHECK: %[[VAL_5:.*]]:2 = hlfir.declare %{{.*}}"_QFbug_145151Ec"} : (!fir.ref<!fir.array<100x!fir.char<1,?>>>,
+! CHECK: %[[VAL_10:.*]]:2 = hlfir.declare %{{.*}}"_QFbug_145151Evector_subscript"} : (!fir.ref<!fir.array<100xi64>>,
+! CHECK: %[[VAL_11:.*]] = arith.constant 100 : index
+! CHECK: %[[VAL_12:.*]] = fir.shape %[[VAL_11]] : (index) -> !fir.shape<1>
+! CHECK: %[[VAL_13:.*]] = arith.constant 1 : index
+! CHECK: %[[VAL_14:.*]] = hlfir.designate %[[VAL_10]]#0 (%[[VAL_13]]) : (!fir.ref<!fir.array<100xi64>>, index) -> !fir.ref<i64>
+! CHECK: %[[VAL_15:.*]] = fir.load %[[VAL_14]] : !fir.ref<i64>
+! CHECK: %[[VAL_16:.*]] = hlfir.designate %[[VAL_5]]#0 (%[[VAL_15]]) typeparams %[[VAL_1]]#1 : (!fir.box<!fir.array<100x!fir.char<1,?>>>, i64, index) -> !fir.boxchar<1>
+! CHECK: %[[VAL_17:.*]]:2 = fir.unboxchar %[[VAL_16]] : (!fir.boxchar<1>) -> (!fir.ref<!fir.char<1,?>>, index)
+! CHECK: %[[VAL_18:.*]]:2 = hlfir.declare %[[VAL_17]]#0 typeparams %[[VAL_1]]#1 {uniq_name = "mock.dummy"} : (!fir.ref<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+! CHECK: %[[VAL_19:.*]] = fir.convert %[[VAL_1]]#1 : (index) -> i64
+! CHECK: %[[VAL_22:.*]] = fir.convert %[[VAL_19]] : (i64) -> index
+! CHECK: %[[VAL_23:.*]] = arith.constant 0 : index
+! CHECK: %[[VAL_24:.*]] = arith.cmpi sgt, %[[VAL_22]], %[[VAL_23]] : index
+! CHECK: %[[VAL_25:.*]] = arith.select %[[VAL_24]], %[[VAL_22]], %[[VAL_23]] : index
+! CHECK: %[[VAL_26:.*]] = hlfir.elemental %[[VAL_12]] typeparams %[[VAL_25]] unordered : (!fir.shape<1>, index) -> !hlfir.expr<100x!fir.char<1,?>> {
+! CHECK: ^bb0(%[[VAL_27:.*]]: index):
+! CHECK: %[[VAL_28:.*]] = hlfir.designate %[[VAL_10]]#0 (%[[VAL_27]]) : (!fir.ref<!fir.array<100xi64>>, index) -> !fir.ref<i64>
+! CHECK: %[[VAL_29:.*]] = fir.load %[[VAL_28]] : !fir.ref<i64>
+! CHECK: %[[VAL_30:.*]] = hlfir.designate %[[VAL_5]]#0 (%[[VAL_29]]) typeparams %[[VAL_1]]#1 : (!fir.box<!fir.array<100x!fir.char<1,?>>>, i64, index) -> !fir.boxchar<1>
+! CHECK: %[[VAL_41:.*]] = fir.call @_QPf(
+! CHECK: hlfir.yield_element %{{.*}} : !hlfir.expr<!fir.char<1,?>>
+! CHECK: }
+! CHECK: hlfir.assign %[[VAL_26]] to %[[VAL_5]]#0 : !hlfir.expr<100x!fir.char<1,?>>, !fir.box<!fir.array<100x!fir.char<1,?>>>
+! CHECK: hlfir.destroy %[[VAL_26]] : !hlfir.expr<100x!fir.char<1,?>>
+! CHECK: return
+! CHECK: }
More information about the flang-commits
mailing list