[flang-commits] [flang] [flang][Lower] Implement lowering for new expression kind used in explicit-shape-bounds-spec (PR #215403)

via flang-commits flang-commits at lists.llvm.org
Mon Aug 10 14:54:13 PDT 2026


https://github.com/ivanrodriguez3753 updated https://github.com/llvm/llvm-project/pull/215403

>From b05b5f121ae1d2fcd80504ac7330eb5d7d4bf7d4 Mon Sep 17 00:00:00 2001
From: Ivan Rodriguez <ivan.rodriguez at hpe.com>
Date: Mon, 10 Aug 2026 16:53:59 -0500
Subject: [PATCH] [flang][Lower] Implement lowering for new expression kind

As part of rank-1 integer array handling in explicit shape bounds, a new class was introduced to scalarize rank-1 integer array bounds. This commit implements the lowering for such an expression.

Also, change semantic analysis to fold before Lower when possible. This was initially intentional so that we would still get the symbol when exporting to a modfile, but after seeing existing behavior for the equivalent test case using the old syntax for explicit-shape bounds, which is to lose the symbol and fold to a constant, fold when possible and lose the RankOneBoundElement object. Otherwise, we would need to special case ROBEs to fold during Lower.
---
 flang/lib/Evaluate/check-expression.cpp       |   2 +-
 flang/lib/Lower/ConvertExprToHLFIR.cpp        |  18 ++-
 flang/lib/Semantics/resolve-names-utils.cpp   |  14 ++-
 .../Lower/HLFIR/explicit-shape-bounds.f90     | 104 ++++++++++++++++++
 .../modfile-explicit-shape-bounds.f90         |   2 +-
 5 files changed, 132 insertions(+), 8 deletions(-)
 create mode 100644 flang/test/Lower/HLFIR/explicit-shape-bounds.f90

diff --git a/flang/lib/Evaluate/check-expression.cpp b/flang/lib/Evaluate/check-expression.cpp
index 737502a504d61..76fa6df441d1c 100644
--- a/flang/lib/Evaluate/check-expression.cpp
+++ b/flang/lib/Evaluate/check-expression.cpp
@@ -1808,7 +1808,7 @@ class CollectUsedSymbolValuesHelper
     return {}; // doesn't count as a use
   }
   Result operator()(const RankOneBoundElement &x) const {
-    return {}; // unreachable
+    return (*this)(x.base());
   }
 
   template <typename T> Result operator()(const ConditionalExpr<T> &condExpr) {
diff --git a/flang/lib/Lower/ConvertExprToHLFIR.cpp b/flang/lib/Lower/ConvertExprToHLFIR.cpp
index 59ef7143914b2..679fc9820eb94 100644
--- a/flang/lib/Lower/ConvertExprToHLFIR.cpp
+++ b/flang/lib/Lower/ConvertExprToHLFIR.cpp
@@ -1865,7 +1865,23 @@ class HlfirBuilder {
 
   hlfir::EntityWithAttributes
   gen(const Fortran::evaluate::RankOneBoundElement &x) {
-    TODO(getLoc(), "rank-1 bound element lowering");
+    mlir::Location loc = getLoc();
+    auto &builder = getBuilder();
+    using ResTy = Fortran::evaluate::RankOneBoundElement::Result;
+    mlir::Type resultType =
+        getConverter().genType(ResTy::category, ResTy::kind);
+    // Evaluate the rank-1 base expression.
+    Fortran::lower::SomeExpr someExpr{Fortran::evaluate::AsGenericExpr(
+        Fortran::evaluate::Expr<Fortran::evaluate::SomeInteger>{
+            Fortran::common::Clone(x.base())})};
+    hlfir::Entity baseArray{Fortran::lower::convertExprToHLFIR(
+        loc, getConverter(), someExpr, getSymMap(), getStmtCtx())};
+    // Extract element at dimension (1-based index).
+    mlir::Value idx = builder.createIntegerConstant(loc, builder.getIndexType(),
+                                                    x.dimension() + 1);
+    mlir::Value elem = hlfir::loadElementAt(loc, builder, baseArray, {idx});
+    return hlfir::EntityWithAttributes{
+        builder.createConvert(loc, resultType, elem)};
   }
 
   /// Generate a conditional expression as an hlfir.conditional op whose
diff --git a/flang/lib/Semantics/resolve-names-utils.cpp b/flang/lib/Semantics/resolve-names-utils.cpp
index 6580c7e31abdb..5fa912cdf321e 100644
--- a/flang/lib/Semantics/resolve-names-utils.cpp
+++ b/flang/lib/Semantics/resolve-names-utils.cpp
@@ -560,7 +560,9 @@ void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeBoundsSpec &x) {
   }
   // For rank-1 bounds, emit N ShapeSpecs each wrapping a scalar
   // RankOneBoundElement that extracts element [dim] from the rank-1
-  // expression.  This makes all downstream consumers see scalar bounds.
+  // expression, then fold: a constant rank-1 base collapses to a scalar
+  // constant (as if written dims(1),dims(2),...), while a non-constant base
+  // keeps the RankOneBoundElement for lowering and mod-file round-tripping.
   int numDims = static_cast<int>(result->numDims);
   if (numDims == 0) {
     // A zero-size bounds array declares a scalar (rank 0); leave arraySpec_
@@ -580,8 +582,9 @@ void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeBoundsSpec &x) {
     MaybeSubscriptIntExpr ubExpr;
     if (auto &ubOrig = result->ubound.GetExplicit()) {
       if (ubOrig->Rank() > 0) {
-        ubExpr = SubscriptIntExpr{
-            evaluate::RankOneBoundElement{common::Clone(*ubOrig), dim}};
+        ubExpr = evaluate::Fold(context_.foldingContext(),
+            SubscriptIntExpr{
+                evaluate::RankOneBoundElement{common::Clone(*ubOrig), dim}});
       } else {
         ubExpr = common::Clone(*ubOrig);
       }
@@ -591,8 +594,9 @@ void ArraySpecAnalyzer::Analyze(const parser::ExplicitShapeBoundsSpec &x) {
     if (result->lbound) {
       if (auto &lbOrig = result->lbound->GetExplicit()) {
         if (lbOrig->Rank() > 0) {
-          lbExpr = SubscriptIntExpr{
-              evaluate::RankOneBoundElement{common::Clone(*lbOrig), dim}};
+          lbExpr = evaluate::Fold(context_.foldingContext(),
+              SubscriptIntExpr{
+                  evaluate::RankOneBoundElement{common::Clone(*lbOrig), dim}});
         } else {
           lbExpr = common::Clone(*lbOrig);
         }
diff --git a/flang/test/Lower/HLFIR/explicit-shape-bounds.f90 b/flang/test/Lower/HLFIR/explicit-shape-bounds.f90
new file mode 100644
index 0000000000000..c022ec4214668
--- /dev/null
+++ b/flang/test/Lower/HLFIR/explicit-shape-bounds.f90
@@ -0,0 +1,104 @@
+! Test lowering of explicit-shape bounds using rank-1 integer arrays
+! (RankOneBoundElement in the evaluate representation).
+! RUN: bbc -emit-hlfir -o - %s 2>&1 | FileCheck %s
+
+! Test with PARAMETER rank-1 bounds: the constant array is folded so the
+! explicit-shape bounds become compile-time constants (no runtime load).
+module test_param
+contains
+  subroutine test_param_bounds()
+    integer, parameter :: dims(3) = [2, 3, 4]
+    real :: a(dims)
+    a(1,1,1) = 1.0
+  end subroutine
+end module
+! CHECK-LABEL: func.func @_QMtest_paramPtest_param_bounds()
+! CHECK:  %[[C2:.*]] = arith.constant 2 : index
+! CHECK:  %[[C3:.*]] = arith.constant 3 : index
+! CHECK:  %[[C4:.*]] = arith.constant 4 : index
+! CHECK:  fir.alloca !fir.array<2x3x4xf32>
+! CHECK:  fir.shape %[[C2]], %[[C3]], %[[C4]] : (index, index, index) -> !fir.shape<3>
+
+! Test with rank-1 dummy as upper bounds only.
+module test_dummy_upper
+contains
+  subroutine test_dummy_upper_bounds(n)
+    integer, intent(in) :: n(3)
+    real :: a(n)
+    a(1,1,1) = 1.0
+  end subroutine
+end module
+! CHECK-LABEL: func.func @_QMtest_dummy_upperPtest_dummy_upper_bounds(
+! CHECK:  hlfir.elemental {{.*}} -> !hlfir.expr<3xi64>
+! CHECK:  ^bb0(%arg{{.*}}: index):
+! CHECK:    hlfir.designate {{.*}} (%arg{{.*}}) : ({{.*}}, index) -> !fir.ref<i32>
+! CHECK:    fir.load {{.*}} : !fir.ref<i32>
+! CHECK:    fir.convert {{.*}} : (i32) -> i64
+! CHECK:  %[[C1:.*]] = arith.constant 1 : index
+! CHECK:  hlfir.apply {{.*}}, %[[C1]] : (!hlfir.expr<3xi64>, index) -> i64
+! CHECK:  %[[C2:.*]] = arith.constant 2 : index
+! CHECK:  hlfir.apply {{.*}}, %[[C2]] : (!hlfir.expr<3xi64>, index) -> i64
+! CHECK:  %[[C3:.*]] = arith.constant 3 : index
+! CHECK:  hlfir.apply {{.*}}, %[[C3]] : (!hlfir.expr<3xi64>, index) -> i64
+
+! Test with both lower and upper rank-1 bounds.
+module test_dummy_both
+contains
+  subroutine test_dummy_both_bounds(lb, ub)
+    integer, intent(in) :: lb(2), ub(2)
+    real :: a(lb:ub)
+    a(1,1) = 1.0
+  end subroutine
+end module
+! CHECK-LABEL: func.func @_QMtest_dummy_bothPtest_dummy_both_bounds(
+! CHECK:  hlfir.elemental {{.*}} -> !hlfir.expr<2xi64>
+! CHECK:  %[[C1:.*]] = arith.constant 1 : index
+! CHECK:  hlfir.apply {{.*}}, %[[C1]] : (!hlfir.expr<2xi64>, index) -> i64
+! CHECK:  hlfir.elemental {{.*}} -> !hlfir.expr<2xi64>
+! CHECK:  %[[C1_1:.*]] = arith.constant 1 : index
+! CHECK:  hlfir.apply {{.*}}, %[[C1_1]] : (!hlfir.expr<2xi64>, index) -> i64
+! CHECK:  hlfir.elemental {{.*}} -> !hlfir.expr<2xi64>
+! CHECK:  %[[C2:.*]] = arith.constant 2 : index
+! CHECK:  hlfir.apply {{.*}}, %[[C2]] : (!hlfir.expr<2xi64>, index) -> i64
+! CHECK:  hlfir.elemental {{.*}} -> !hlfir.expr<2xi64>
+! CHECK:  %[[C2_1:.*]] = arith.constant 2 : index
+! CHECK:  hlfir.apply {{.*}}, %[[C2_1]] : (!hlfir.expr<2xi64>, index) -> i64
+
+! Test broadcast of scalar lower bound with rank-1 upper bounds.
+module test_broadcast
+contains
+  subroutine test_broadcast_bounds(ub)
+    integer, intent(in) :: ub(2)
+    real :: a(0:ub)
+    a(0,0) = 1.0
+  end subroutine
+end module
+! CHECK-LABEL: func.func @_QMtest_broadcastPtest_broadcast_bounds(
+! CHECK:  hlfir.elemental {{.*}} -> !hlfir.expr<2xi64>
+! CHECK:  %[[U1:.*]] = arith.constant 1 : index
+! CHECK:  hlfir.apply {{.*}}, %[[U1]] : (!hlfir.expr<2xi64>, index) -> i64
+! CHECK:  %[[U2:.*]] = arith.constant 2 : index
+! CHECK:  hlfir.apply {{.*}}, %[[U2]] : (!hlfir.expr<2xi64>, index) -> i64
+! CHECK:  fir.shape_shift {{.*}} : (index, index, index, index) -> !fir.shapeshift<2>
+
+! Test rank-1 dummy bounds of explicit integer kinds: the bound base is always
+! coerced to a 64-bit subscript, so INTEGER(4) is widened (element loaded as i32
+! then converted to i64) while INTEGER(8) is already 64-bit and loaded directly.
+module test_bound_kinds
+contains
+  subroutine test_bound_kinds_bounds(n4, n8)
+    integer(4), intent(in) :: n4(2)
+    integer(8), intent(in) :: n8(2)
+    real :: a4(n4)
+    real :: a8(n8)
+    a4(1,1) = 1.0
+    a8(1,1) = 1.0
+  end subroutine
+end module
+! CHECK-LABEL: func.func @_QMtest_bound_kindsPtest_bound_kinds_bounds(
+! CHECK:  hlfir.elemental {{.*}} -> !hlfir.expr<2xi64>
+! CHECK:    hlfir.designate {{.*}} (%arg{{.*}}) : ({{.*}}, index) -> !fir.ref<i32>
+! CHECK:    fir.load {{.*}} : !fir.ref<i32>
+! CHECK:    fir.convert {{.*}} : (i32) -> i64
+! CHECK:  hlfir.designate {{.*}} : ({{.*}}, index) -> !fir.ref<i64>
+! CHECK:  fir.load {{.*}} : !fir.ref<i64>
diff --git a/flang/test/Semantics/modfile-explicit-shape-bounds.f90 b/flang/test/Semantics/modfile-explicit-shape-bounds.f90
index 11b596012c2c2..7f9ac97200076 100644
--- a/flang/test/Semantics/modfile-explicit-shape-bounds.f90
+++ b/flang/test/Semantics/modfile-explicit-shape-bounds.f90
@@ -11,7 +11,7 @@ module m1
 !Expect: m1.mod
 !module m1
 !integer(4),parameter::dims(1_8:3_8)=[INTEGER(4)::5_4,10_4,15_4]
-!real(4)::a(1_8:[INTEGER(8)::5_8,10_8,15_8])
+!real(4)::a(1_8:5_8,1_8:10_8,1_8:15_8)
 !end
 
 ! Rank-1 dummy as upper bounds



More information about the flang-commits mailing list