[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