[flang-commits] [flang] [flang][DRAFT] Fix sequence association for PARAMETER array elements (PR #187348)
Eugene Epshteyn via flang-commits
flang-commits at lists.llvm.org
Tue Mar 24 06:55:56 PDT 2026
https://github.com/eugeneepshteyn updated https://github.com/llvm/llvm-project/pull/187348
>From 73ea8fbd79524734e079ef21628aed76e914234a Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Wed, 18 Mar 2026 14:26:35 -0400
Subject: [PATCH 1/3] [flang] Fix sequence association for PARAMETER array
elements
Fortran 2023 15.5.2.5p14 allows passing a scalar array element a(i) to an
explicit-shape dummy array via sequence association: the callee may access
elements a(i), a(i+1), ... through the dummy argument. This was working
for regular arrays, but was broken for constant (PARAMETER) arrays.
The fix involved teaching semantics not to constant-fold elements of
constant arrays and changing lowering not to create temps for
sequence association cases.
---
flang/lib/Lower/ConvertCall.cpp | 20 +++++-
flang/lib/Semantics/expression.cpp | 24 ++++++-
.../Lower/HLFIR/parameter-array-seq-assoc.f90 | 69 +++++++++++++++++++
3 files changed, 111 insertions(+), 2 deletions(-)
create mode 100644 flang/test/Lower/HLFIR/parameter-array-seq-assoc.f90
diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index d72f74b440c53..0ae4fb8f31596 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -1500,6 +1500,24 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
// is contiguous according to the dummy type.
if (mustSetDynamicTypeToDummyType)
entity = genSetDynamicTypeToDummyType(entity);
+ const bool isParamObject = isParameterObjectOrSubObject(entity);
+ // For sequence association of a named constant array element with an
+ // array dummy argument, the element address points into contiguous
+ // parameter array storage. Per per F'2023 15.5.2.5p14, we should pass
+ // this address directly, instead of creating a scalar temp copy.
+ // Only apply this to array element designators (hlfir.designate with
+ // subscripts), not to scalar parameter variables.
+ const bool dummyIsArrayRef =
+ mlir::isa<fir::BoxCharType>(dummyType) ||
+ mlir::isa<fir::SequenceType>(fir::unwrapRefType(dummyType));
+ // Character substring designators produce !fir.boxchar<1> entities and
+ // must not be mistaken for array elements; only reference-typed entities
+ // are array element designators valid for sequence association.
+ const bool haveDesignator =
+ entity.getDefiningOp<hlfir::DesignateOp>() != nullptr;
+ const bool skipParamCopyForSeqAssoc =
+ isParamObject && entity.getRank() == 0 && dummyIsArrayRef &&
+ haveDesignator && fir::isa_ref_type(entity.getType());
if (arg.hasValueAttribute() ||
// Constant expressions might be lowered as variables with
// 'parameter' attribute. Even though the constant expressions
@@ -1507,7 +1525,7 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
// possible, we have to create a temporary copies when we pass
// them down the call stack because of potential compiler
// generated writes in copy-out.
- isParameterObjectOrSubObject(entity)) {
+ (isParamObject && !skipParamCopyForSeqAssoc)) {
// Make a copy in a temporary.
auto copy = hlfir::AsExprOp::create(builder, loc, entity);
mlir::Type storageType = entity.getType();
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 457c5a3594f6d..73989c38d0b81 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -5184,7 +5184,29 @@ MaybeExpr ArgumentAnalyzer::AnalyzeExprOrWholeAssumedSizeArray(
}
}
auto restorer{context_.AllowNullPointer()};
- return context_.Analyze(expr);
+ MaybeExpr result{context_.Analyze(expr)};
+ // If the expression is a subscripted named constant array (PARAMETER array
+ // element), preserve the Designator<ArrayRef> form rather than returning the
+ // folded scalar constant. Sequence association requires a designator with
+ // an array element subscript (F'2023 15.5.2.5p14); constant folding of
+ // PARAMETER array elements loses this structure.
+ // Note: Analyze(expr) may have converted a FunctionReference to a Designator
+ // in the parse tree (via CheckFuncRefToArrayElement), so check for
+ // ArrayElement only after Analyze(expr) has run.
+ if (isProcedureCall_ && result && result->Rank() == 0) {
+ if (const auto *ae{parser::Unwrap<parser::ArrayElement>(expr)}) {
+ const auto &baseName{parser::GetLastName(ae->Base())};
+ if (baseName.symbol) {
+ const auto &sym{baseName.symbol->GetUltimate()};
+ if (semantics::IsNamedConstant(sym) && sym.Rank() > 0) {
+ // Re-analyze the ArrayElement directly; this path does not apply
+ // the outer Fold, so the result is a Designator<ArrayRef>.
+ return context_.Analyze(*ae);
+ }
+ }
+ }
+ }
+ return result;
}
bool ArgumentAnalyzer::AreConformable() const {
diff --git a/flang/test/Lower/HLFIR/parameter-array-seq-assoc.f90 b/flang/test/Lower/HLFIR/parameter-array-seq-assoc.f90
new file mode 100644
index 0000000000000..a778dd52c5e38
--- /dev/null
+++ b/flang/test/Lower/HLFIR/parameter-array-seq-assoc.f90
@@ -0,0 +1,69 @@
+! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s
+
+! Test sequence association of PARAMETER array elements (F'2023 15.5.2.5p14).
+! When a PARAMETER array element a(i) is passed to an explicit-shape dummy
+! array, the element address must be passed directly so the callee can access
+! subsequent elements via sequence association. The element must NOT be copied
+! into a scalar temporary first.
+
+! CHECK-LABEL: func.func @_QQmain()
+
+integer, parameter :: n = 5
+
+integer, parameter :: a(n) = [1, 2, 3, 4, 5]
+! Integer PARAMETER array declared with parameter attribute.
+! CHECK: %[[A:.*]]:2 = hlfir.declare {{.*}} {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QFECa"} : (!fir.ref<!fir.array<5xi32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<5xi32>>, !fir.ref<!fir.array<5xi32>>)
+
+character, parameter :: ch(n) = ['a', 'b', 'c', 'd', 'e']
+! Character PARAMETER array declared with parameter attribute.
+! CHECK: %[[CH:.*]]:2 = hlfir.declare {{.*}} typeparams {{.*}} {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QFECch"} : (!fir.ref<!fir.array<5x!fir.char<1>>>, !fir.shape<1>, index) -> (!fir.ref<!fir.array<5x!fir.char<1>>>, !fir.ref<!fir.array<5x!fir.char<1>>>)
+
+call passNint(a(1))
+! Element address is obtained via hlfir.designate (subscript 1) and converted
+! to an array ref for sequence association. No scalar copy (hlfir.as_expr).
+! CHECK-NOT: hlfir.as_expr
+! CHECK: %[[ELEM1:.*]] = hlfir.designate %[[A]]#0 (%{{.*}}) : (!fir.ref<!fir.array<5xi32>>, i64) -> !fir.ref<i32>
+! CHECK: %[[ARR1:.*]] = fir.convert %[[ELEM1]] : (!fir.ref<i32>) -> !fir.ref<!fir.array<5xi32>>
+! CHECK: fir.call @_QFPpassnint(%[[ARR1]])
+
+call passN2int(a(1 + 2))
+! Element 3 address passed for sequence association with a 3-element dummy array.
+! CHECK-NOT: hlfir.as_expr
+! CHECK: %[[ELEM3:.*]] = hlfir.designate %[[A]]#0 (%{{.*}}) : (!fir.ref<!fir.array<5xi32>>, i64) -> !fir.ref<i32>
+! CHECK: %[[ARR3:.*]] = fir.convert %[[ELEM3]] : (!fir.ref<i32>) -> !fir.ref<!fir.array<3xi32>>
+! CHECK: fir.call @_QFPpassn2int(%[[ARR3]])
+
+call passNchar(ch(1))
+! Character element address is obtained via hlfir.designate and wrapped in a
+! boxchar for the character dummy argument. No scalar copy.
+! CHECK-NOT: hlfir.as_expr
+! CHECK: %[[CELEM1:.*]] = hlfir.designate %[[CH]]#0 (%{{.*}}) typeparams %{{.*}} : (!fir.ref<!fir.array<5x!fir.char<1>>>, i64, index) -> !fir.ref<!fir.char<1>>
+! CHECK: %[[BOX1:.*]] = fir.emboxchar %[[CELEM1]], %{{.*}} : (!fir.ref<!fir.char<1>>, index) -> !fir.boxchar<1>
+! CHECK: fir.call @_QFPpassnchar(%[[BOX1]])
+
+call passN2char(ch(1 + 2))
+! Character element 3 address passed for sequence association.
+! CHECK-NOT: hlfir.as_expr
+! CHECK: %[[CELEM3:.*]] = hlfir.designate %[[CH]]#0 (%{{.*}}) typeparams %{{.*}} : (!fir.ref<!fir.array<5x!fir.char<1>>>, i64, index) -> !fir.ref<!fir.char<1>>
+! CHECK: %[[BOX3:.*]] = fir.emboxchar %[[CELEM3]], %{{.*}} : (!fir.ref<!fir.char<1>>, index) -> !fir.boxchar<1>
+! CHECK: fir.call @_QFPpassn2char(%[[BOX3]])
+
+contains
+
+subroutine passNint(b)
+ integer, intent(in) :: b(n)
+end subroutine
+
+subroutine passN2int(b)
+ integer, intent(in) :: b(n - 2)
+end subroutine
+
+subroutine passNchar(b)
+ character, intent(in) :: b(n)
+end subroutine
+
+subroutine passN2char(b)
+ character, intent(in) :: b(n - 2)
+end subroutine
+
+end
>From 652667a538d16599b828b7304aaaa504e733de09 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Thu, 19 Mar 2026 08:44:28 -0400
Subject: [PATCH 2/3] Use parser::GetFirstName(), as suggested by the reviewer
---
flang/lib/Semantics/expression.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 73989c38d0b81..76ad23097adc1 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -5195,7 +5195,7 @@ MaybeExpr ArgumentAnalyzer::AnalyzeExprOrWholeAssumedSizeArray(
// ArrayElement only after Analyze(expr) has run.
if (isProcedureCall_ && result && result->Rank() == 0) {
if (const auto *ae{parser::Unwrap<parser::ArrayElement>(expr)}) {
- const auto &baseName{parser::GetLastName(ae->Base())};
+ const auto &baseName{parser::GetFirstName(ae->Base())};
if (baseName.symbol) {
const auto &sym{baseName.symbol->GetUltimate()};
if (semantics::IsNamedConstant(sym) && sym.Rank() > 0) {
>From 521e6e83c84a590c92ab514c92a406f0ca849a22 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Tue, 24 Mar 2026 09:50:06 -0400
Subject: [PATCH 3/3] Support arrays that are part of constant derived types
---
flang/lib/Lower/ConvertCall.cpp | 15 ++++++++----
flang/lib/Semantics/expression.cpp | 5 +++-
.../Lower/HLFIR/parameter-array-seq-assoc.f90 | 23 +++++++++++++++++++
3 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 8dc8d46b3f310..f918405165fe4 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -1502,9 +1502,11 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
entity = genSetDynamicTypeToDummyType(entity);
const bool isParamObject = isParameterObjectOrSubObject(entity);
// For sequence association of a named constant array element with an
- // array dummy argument, the element address points into contiguous
- // parameter array storage. Per per F'2023 15.5.2.5p14, we should pass
- // this address directly, instead of creating a scalar temp copy.
+ // INTENT(IN) array dummy argument, the element address points into
+ // contiguous parameter array storage. Per F'2023 15.5.2.5p14, we pass
+ // this address directly instead of creating a scalar temp copy.
+ // The INTENT(IN) restriction ensures the callee cannot write through the
+ // dummy and corrupt the named-constant storage.
// Only apply this to array element designators (hlfir.designate with
// subscripts), not to scalar parameter variables.
const bool dummyIsArrayRef =
@@ -1515,9 +1517,14 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
// are array element designators valid for sequence association.
const bool haveDesignator =
entity.getDefiningOp<hlfir::DesignateOp>() != nullptr;
+ // Only skip the copy when the dummy is INTENT(IN): if the callee could
+ // modify the dummy (INTENT(INOUT/OUT) or no declared intent), writing
+ // through the dummy would corrupt the named-constant storage.
+ const bool dummyIsIntentIn = !arg.mayBeModifiedByCall();
const bool skipParamCopyForSeqAssoc =
isParamObject && entity.getRank() == 0 && dummyIsArrayRef &&
- haveDesignator && fir::isa_ref_type(entity.getType());
+ haveDesignator && fir::isa_ref_type(entity.getType()) &&
+ dummyIsIntentIn;
if (arg.hasValueAttribute() ||
// Constant expressions might be lowered as variables with
// 'parameter' attribute. Even though the constant expressions
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 76ad23097adc1..8e828793f3a72 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -5198,9 +5198,12 @@ MaybeExpr ArgumentAnalyzer::AnalyzeExprOrWholeAssumedSizeArray(
const auto &baseName{parser::GetFirstName(ae->Base())};
if (baseName.symbol) {
const auto &sym{baseName.symbol->GetUltimate()};
- if (semantics::IsNamedConstant(sym) && sym.Rank() > 0) {
+ if (semantics::IsNamedConstant(sym)) {
// Re-analyze the ArrayElement directly; this path does not apply
// the outer Fold, so the result is a Designator<ArrayRef>.
+ // This also handles the case where sym is a scalar derived-type
+ // named constant and the subscripted component (e.g. pt%arr(1))
+ // is an array member -- sym.Rank() would be 0 for the base object.
return context_.Analyze(*ae);
}
}
diff --git a/flang/test/Lower/HLFIR/parameter-array-seq-assoc.f90 b/flang/test/Lower/HLFIR/parameter-array-seq-assoc.f90
index a778dd52c5e38..7409d8fca674b 100644
--- a/flang/test/Lower/HLFIR/parameter-array-seq-assoc.f90
+++ b/flang/test/Lower/HLFIR/parameter-array-seq-assoc.f90
@@ -18,6 +18,14 @@
! Character PARAMETER array declared with parameter attribute.
! CHECK: %[[CH:.*]]:2 = hlfir.declare {{.*}} typeparams {{.*}} {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QFECch"} : (!fir.ref<!fir.array<5x!fir.char<1>>>, !fir.shape<1>, index) -> (!fir.ref<!fir.array<5x!fir.char<1>>>, !fir.ref<!fir.array<5x!fir.char<1>>>)
+type :: mytype
+ integer :: arr(n)
+end type
+
+type(mytype), parameter :: pt = mytype([1, 2, 3, 4, 5])
+! PARAMETER of derived type with an integer array component.
+! CHECK: %[[PT:.*]]:2 = hlfir.declare {{.*}} {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QFECpt"} : (!fir.ref<!fir.type<_QFTmytype{{.*}}>>) -> (!fir.ref<!fir.type<_QFTmytype{{.*}}>>, !fir.ref<!fir.type<_QFTmytype{{.*}}>>)
+
call passNint(a(1))
! Element address is obtained via hlfir.designate (subscript 1) and converted
! to an array ref for sequence association. No scalar copy (hlfir.as_expr).
@@ -48,6 +56,21 @@
! CHECK: %[[BOX3:.*]] = fir.emboxchar %[[CELEM3]], %{{.*}} : (!fir.ref<!fir.char<1>>, index) -> !fir.boxchar<1>
! CHECK: fir.call @_QFPpassn2char(%[[BOX3]])
+call passNint(pt%arr(1))
+! Component arr(1) of PARAMETER derived type: element address reused for sequence
+! association. No scalar copy.
+! CHECK-NOT: hlfir.as_expr
+! CHECK: %[[PELEM1:.*]] = hlfir.designate %[[PT]]#0{"arr"} <%{{.*}}> (%{{.*}}) : (!fir.ref<!fir.type<_QFTmytype{{.*}}>>, !fir.shape<1>, index) -> !fir.ref<i32>
+! CHECK: %[[PARR1:.*]] = fir.convert %[[PELEM1]] : (!fir.ref<i32>) -> !fir.ref<!fir.array<5xi32>>
+! CHECK: fir.call @_QFPpassnint(%[[PARR1]])
+
+call passN2int(pt%arr(1 + 2))
+! Component arr(3) element address passed for sequence association with a 3-element dummy.
+! CHECK-NOT: hlfir.as_expr
+! CHECK: %[[PELEM3:.*]] = hlfir.designate %[[PT]]#0{"arr"} <%{{.*}}> (%{{.*}}) : (!fir.ref<!fir.type<_QFTmytype{{.*}}>>, !fir.shape<1>, index) -> !fir.ref<i32>
+! CHECK: %[[PARR3:.*]] = fir.convert %[[PELEM3]] : (!fir.ref<i32>) -> !fir.ref<!fir.array<3xi32>>
+! CHECK: fir.call @_QFPpassn2int(%[[PARR3]])
+
contains
subroutine passNint(b)
More information about the flang-commits
mailing list