[flang-commits] [flang] [flang][HLFIR] Preserve RHS lower bounds in SeparateAllocatableAssign (PR #202373)
via flang-commits
flang-commits at lists.llvm.org
Mon Jun 8 08:51:10 PDT 2026
https://github.com/khaki3 created https://github.com/llvm/llvm-project/pull/202373
Example:
```fortran
type t
integer, allocatable :: a(:)
end type
integer, pointer :: p(:)
allocate(p(-5:-3))
x = t(p) ! allocatable component <- source, lbound -5
print *, lbound(x%a, 1) ! should be -5
```
In this code, the structure constructor assigns the allocatable component from a source with a non-default lower bound, so `lbound(x%a,1)` must be `-5`. Lowering wraps the source in an `hlfir.expr` and emits `hlfir.assign ... realloc`. `SeparateAllocatableAssign` split the realloc but assumed an `hlfir.expr` RHS always has lower bound `1`, reallocating the component with `1` instead of `-5`.
Fix: read the reallocation lower bounds from the entity that owns the RHS descriptor — the variable RHS itself, or the variable wrapped by `hlfir.as_expr`. Only truly synthetic exprs (elemental, array constructor) keep lower bound `1`.
>From 5523054454932e96bd743d58dbac31b993a78802 Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Mon, 8 Jun 2026 02:10:16 -0700
Subject: [PATCH 1/2] [flang][HLFIR] Preserve RHS lower bounds in
SeparateAllocatableAssign
When SeparateAllocatableAssign splits an `hlfir.assign ... realloc`, the
lower bounds of the (re-)allocated LHS must come from the RHS descriptor.
The pass assumed an hlfir.expr RHS always has lower bound 1, which corrupts
structure-constructor assigns such as `t = x(f())`, where the allocatable
component must take the source's bounds (F2018 7.5.10): the component was
reallocated with lower bound 1 instead of LBOUND(source).
Take the lower bounds from the entity that actually carries them: the
variable for a variable RHS, or the variable wrapped by hlfir.as_expr for an
hlfir.expr RHS (e.g. an allocatable/pointer function result used in a
structure constructor). Synthetic expressions (elemental, array constructor)
still default to lower bound 1.
---
.../Transforms/SeparateAllocatableAssign.cpp | 33 +++++++++++++++----
.../HLFIR/separate-allocatable-assign.fir | 32 ++++++++++++++++++
2 files changed, 58 insertions(+), 7 deletions(-)
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp b/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp
index 0160ff7d75f76..afc7acc4a56ce 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp
@@ -115,14 +115,33 @@ class SeparateAllocatableAssignConversion
llvm::SmallVector<mlir::Value> rhsExtents =
hlfir::getIndexExtents(loc, builder, rhsShape);
- // F2018 10.2.1.3: when the LHS is (re-)allocated, its lower bounds
- // come from LBOUND(rhs). For variable RHS, extract the actual lower
- // bounds from the entity; for hlfir.expr RHS, LBOUND is always 1.
+ // When the LHS is (re-)allocated, its lower bounds come from the RHS
+ // descriptor, which Fortran lowering has already populated with the
+ // semantically-correct bounds:
+ // - F2018 10.2.1.3 for intrinsic assignment: LBOUND(rhs);
+ // - F2018 7.5.10 for a structure constructor: an allocatable source keeps
+ // its bounds, other sources reset the lower bound to 1.
+ // Find the entity that carries those bounds:
+ // - a variable RHS is itself the descriptor;
+ // - an hlfir.expr produced by hlfir.as_expr from a variable (e.g. an
+ // allocatable function result used in a structure constructor) keeps
+ // the bounds in the wrapped variable's descriptor;
+ // - any other hlfir.expr (elemental, array constructor, ...) has no
+ // underlying descriptor, so its lower bounds are 1.
+ mlir::Value lboundSource;
+ if (!mlir::isa<hlfir::ExprType>(rhs.getType()))
+ lboundSource = assign.getRhs();
+ else if (auto asExpr = assign.getRhs().getDefiningOp<hlfir::AsExprOp>())
+ lboundSource = asExpr.getVar();
+
llvm::SmallVector<mlir::Value> rhsLbounds;
- if (!mlir::isa<hlfir::ExprType>(rhs.getType())) {
- auto bounds = hlfir::genBounds(loc, builder, rhs);
- for (auto &[lb, ub] : bounds)
- rhsLbounds.push_back(lb);
+ if (lboundSource) {
+ hlfir::Entity boundsEntity{lboundSource};
+ if (boundsEntity.isArray()) {
+ auto bounds = hlfir::genBounds(loc, builder, boundsEntity);
+ for (auto &[lb, ub] : bounds)
+ rhsLbounds.push_back(lb);
+ }
}
fir::MutableBoxValue mutableBox(lhs.getFirBase(), /*lenParameters=*/{},
diff --git a/flang/test/HLFIR/separate-allocatable-assign.fir b/flang/test/HLFIR/separate-allocatable-assign.fir
index 97c664c38a94f..fb8ae9512ac63 100644
--- a/flang/test/HLFIR/separate-allocatable-assign.fir
+++ b/flang/test/HLFIR/separate-allocatable-assign.fir
@@ -179,3 +179,35 @@ func.func @test_lower_bounds(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32
// Lower bound 10 should appear in the embox/store of the new allocation.
// CHECK: %[[BOX:.*]] = fir.load %{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
// CHECK: hlfir.assign %{{.*}} to %[[BOX]] : !fir.box<!fir.array<3xi32>>, !fir.box<!fir.heap<!fir.array<?xi32>>>
+
+// Test: lower bounds from an hlfir.expr RHS that wraps a variable (via
+// hlfir.as_expr) must be preserved during reallocation. This is the
+// structure-constructor case `t = x(f())` where `f` is an allocatable function
+// result with a non-default lower bound: the allocatable component must take
+// the source's bounds (F2018 7.5.10). The pass must read the lower bound from
+// the wrapped variable's descriptor instead of assuming 1.
+func.func @test_expr_lower_bounds(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, %arg1: !fir.ref<!fir.array<3xi32>>) {
+ %c10 = arith.constant 10 : index
+ %c3 = arith.constant 3 : index
+ %true = arith.constant true
+ %shapeshift = fir.shape_shift %c10, %c3 : (index, index) -> !fir.shapeshift<1>
+
+ %dest:2 = hlfir.declare %arg0 {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFEdest"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>) -> (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+ %source:2 = hlfir.declare %arg1(%shapeshift) {uniq_name = "_QFEsource"} : (!fir.ref<!fir.array<3xi32>>, !fir.shapeshift<1>) -> (!fir.box<!fir.array<3xi32>>, !fir.ref<!fir.array<3xi32>>)
+ %expr = hlfir.as_expr %source#0 move %true : (!fir.box<!fir.array<3xi32>>, i1) -> !hlfir.expr<3xi32>
+
+ hlfir.assign %expr to %dest#0 realloc temporary_lhs : !hlfir.expr<3xi32>, !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+ hlfir.destroy %expr : !hlfir.expr<3xi32>
+ return
+}
+
+// CHECK-LABEL: func.func @test_expr_lower_bounds
+// The realloc should be separated with the wrapped variable's lower bound 10.
+// CHECK-NOT: hlfir.assign{{.*}}realloc
+// CHECK: %[[C10:.*]] = arith.constant 10 : index
+// CHECK: fir.if
+// CHECK: fir.allocmem
+// Lower bound 10 (not 1) must appear in the embox/store of the new allocation.
+// CHECK: fir.shape_shift %[[C10]]
+// CHECK: %[[BOX:.*]] = fir.load %{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+// CHECK: hlfir.assign %{{.*}} to %[[BOX]] temporary_lhs : !hlfir.expr<3xi32>, !fir.box<!fir.heap<!fir.array<?xi32>>>
>From 35501ad2d3279195950687d9706117ff812a56eb Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Mon, 8 Jun 2026 02:54:06 -0700
Subject: [PATCH 2/2] [flang][HLFIR] Shorten lower-bound comments in
SeparateAllocatableAssign
---
.../Transforms/SeparateAllocatableAssign.cpp | 17 ++++-------------
.../test/HLFIR/separate-allocatable-assign.fir | 4 ++--
2 files changed, 6 insertions(+), 15 deletions(-)
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp b/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp
index afc7acc4a56ce..b4391f9069d48 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp
@@ -115,19 +115,10 @@ class SeparateAllocatableAssignConversion
llvm::SmallVector<mlir::Value> rhsExtents =
hlfir::getIndexExtents(loc, builder, rhsShape);
- // When the LHS is (re-)allocated, its lower bounds come from the RHS
- // descriptor, which Fortran lowering has already populated with the
- // semantically-correct bounds:
- // - F2018 10.2.1.3 for intrinsic assignment: LBOUND(rhs);
- // - F2018 7.5.10 for a structure constructor: an allocatable source keeps
- // its bounds, other sources reset the lower bound to 1.
- // Find the entity that carries those bounds:
- // - a variable RHS is itself the descriptor;
- // - an hlfir.expr produced by hlfir.as_expr from a variable (e.g. an
- // allocatable function result used in a structure constructor) keeps
- // the bounds in the wrapped variable's descriptor;
- // - any other hlfir.expr (elemental, array constructor, ...) has no
- // underlying descriptor, so its lower bounds are 1.
+ // The re-allocated LHS takes the RHS lower bounds, which lowering has
+ // already put in the RHS descriptor. Read them from the entity that owns
+ // that descriptor: a variable RHS itself, or the variable wrapped by
+ // hlfir.as_expr. Other hlfir.exprs have no descriptor, so lower bound is 1.
mlir::Value lboundSource;
if (!mlir::isa<hlfir::ExprType>(rhs.getType()))
lboundSource = assign.getRhs();
diff --git a/flang/test/HLFIR/separate-allocatable-assign.fir b/flang/test/HLFIR/separate-allocatable-assign.fir
index fb8ae9512ac63..53ae8594a1d98 100644
--- a/flang/test/HLFIR/separate-allocatable-assign.fir
+++ b/flang/test/HLFIR/separate-allocatable-assign.fir
@@ -184,8 +184,8 @@ func.func @test_lower_bounds(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32
// hlfir.as_expr) must be preserved during reallocation. This is the
// structure-constructor case `t = x(f())` where `f` is an allocatable function
// result with a non-default lower bound: the allocatable component must take
-// the source's bounds (F2018 7.5.10). The pass must read the lower bound from
-// the wrapped variable's descriptor instead of assuming 1.
+// the source's bounds. The pass must read the lower bound from the wrapped
+// variable's descriptor instead of assuming 1.
func.func @test_expr_lower_bounds(%arg0: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, %arg1: !fir.ref<!fir.array<3xi32>>) {
%c10 = arith.constant 10 : index
%c3 = arith.constant 3 : index
More information about the flang-commits
mailing list