[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:52:02 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-fir-hlfir
Author: khaki3
<details>
<summary>Changes</summary>
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`.
---
Full diff: https://github.com/llvm/llvm-project/pull/202373.diff
2 Files Affected:
- (modified) flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp (+17-7)
- (modified) flang/test/HLFIR/separate-allocatable-assign.fir (+32)
``````````diff
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp b/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp
index 0160ff7d75f76..b4391f9069d48 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/SeparateAllocatableAssign.cpp
@@ -115,14 +115,24 @@ 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.
+ // 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();
+ 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..53ae8594a1d98 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. 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>>>
``````````
</details>
https://github.com/llvm/llvm-project/pull/202373
More information about the flang-commits
mailing list