[flang-commits] [flang] [flang] Reset lbounds for allocatable function results. (PR #65286)

Slava Zakharin via flang-commits flang-commits at lists.llvm.org
Mon Sep 4 17:18:40 PDT 2023


https://github.com/vzakhari created https://github.com/llvm/llvm-project/pull/65286:

With HLFIR the lbounds for the ALLOCATABLE result are taken from the mutable box created for the result, so the non-default lbounds might be propagated further causing incorrect result, e.g.:
```
program p
  real, allocatable :: p5(:)
  allocate(p5, source=real_init())
  print *, lbound(p5, 1) ! must print 1, but prints 7
contains
  function real_init()
    real, allocatable :: real_init(:)
    allocate(real_init(7:8))
  end function real_init
end program p
```

With FIR lowering the box passed for `source` has explicit lower bound 1 at the call site, but the runtime box initialized by `real_init` call still has lower bound 7. I am not sure if the runtime box initialized b `real_init` will ever be accessed in a debugger via Fortran variable names, but I think that having the right runtime bounds that can be accessible via examining registers/stack might be good in general. So I decided to update the runtime bounds at the point of return.

This change "fixes" the test above for HLFIR.

Reviewed By: jeanPerier

Differential Revision: https://reviews.llvm.org/D156187

>From d461ce0b4b0d007e2be3fee3114e621b61c7fffd Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Mon, 4 Sep 2023 17:09:53 -0700
Subject: [PATCH] [flang] Reset lbounds for allocatable function results.

With HLFIR the lbounds for the ALLOCATABLE result are taken
from the mutable box created for the result, so the non-default
lbounds might be propagated further causing incorrect result, e.g.:
```
program p
  real, allocatable :: p5(:)
  allocate(p5, source=real_init())
  print *, lbound(p5, 1) ! must print 1, but prints 7
contains
  function real_init()
    real, allocatable :: real_init(:)
    allocate(real_init(7:8))
  end function real_init
end program p
```

With FIR lowering the box passed for `source` has explicit lower
bound 1 at the call site, but the runtime box initialized by
`real_init` call still has lower bound 7. I am not sure if
the runtime box initialized b `real_init` will ever be accessed
in a debugger via Fortran variable names, but I think that
having the right runtime bounds that can be accessible via
examining registers/stack might be good in general. So I decided
to update the runtime bounds at the point of return.

This change "fixes" the test above for HLFIR.

Reviewed By: jeanPerier

Differential Revision: https://reviews.llvm.org/D156187
---
 flang/lib/Lower/Bridge.cpp                    | 24 ++++++
 flang/test/Lower/HLFIR/allocatable-return.f90 | 85 +++++++++++++++++++
 flang/test/Lower/allocatable-return.f90       | 77 +++++++++++++++++
 3 files changed, 186 insertions(+)
 create mode 100644 flang/test/Lower/HLFIR/allocatable-return.f90
 create mode 100644 flang/test/Lower/allocatable-return.f90

diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index d5de3679cffca3..5d5ac0f274773e 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -1308,6 +1308,30 @@ class FirConverter : public Fortran::lower::AbstractConverter {
           return fir::factory::CharacterExprHelper{*builder, loc}
               .createEmboxChar(x.getBuffer(), x.getLen());
         },
+        [&](const fir::MutableBoxValue &x) -> mlir::Value {
+          mlir::Value resultRef = resultSymBox.getAddr();
+          mlir::Value load = builder->create<fir::LoadOp>(loc, resultRef);
+          unsigned rank = x.rank();
+          if (x.isAllocatable() && rank > 0) {
+            // ALLOCATABLE array result must have default lower bounds.
+            // At the call site the result box of a function reference
+            // might be considered having default lower bounds, but
+            // the runtime box should probably comply with this assumption
+            // as well. If the result box has proper lbounds in runtime,
+            // this may improve the debugging experience of Fortran apps.
+            // We may consider removing this, if the overhead of setting
+            // default lower bounds is too big.
+            mlir::Value one =
+                builder->createIntegerConstant(loc, builder->getIndexType(), 1);
+            llvm::SmallVector<mlir::Value> lbounds{rank, one};
+            auto shiftTy = fir::ShiftType::get(builder->getContext(), rank);
+            mlir::Value shiftOp =
+                builder->create<fir::ShiftOp>(loc, shiftTy, lbounds);
+            load = builder->create<fir::ReboxOp>(
+                loc, load.getType(), load, shiftOp, /*slice=*/mlir::Value{});
+          }
+          return load;
+        },
         [&](const auto &) -> mlir::Value {
           mlir::Value resultRef = resultSymBox.getAddr();
           mlir::Type resultType = genType(resultSym);
diff --git a/flang/test/Lower/HLFIR/allocatable-return.f90 b/flang/test/Lower/HLFIR/allocatable-return.f90
new file mode 100644
index 00000000000000..d652b2f9f767dc
--- /dev/null
+++ b/flang/test/Lower/HLFIR/allocatable-return.f90
@@ -0,0 +1,85 @@
+! RUN: bbc -emit-hlfir --polymorphic-type -I nowhere %s -o - | FileCheck %s
+
+! Test allocatable return.
+! Allocatable arrays must have default runtime lbounds after the return.
+
+function test_alloc_return_scalar
+  real, allocatable :: test_alloc_return_scalar
+  allocate(test_alloc_return_scalar)
+end function test_alloc_return_scalar
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_scalar() -> !fir.box<!fir.heap<f32>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.box<!fir.heap<f32>> {bindc_name = "test_alloc_return_scalar", uniq_name = "_QFtest_alloc_return_scalarEtest_alloc_return_scalar"}
+! CHECK:           %[[VAL_3:.*]]:2 = hlfir.declare %[[VAL_0]] {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtest_alloc_return_scalarEtest_alloc_return_scalar"} : (!fir.ref<!fir.box<!fir.heap<f32>>>) -> (!fir.ref<!fir.box<!fir.heap<f32>>>, !fir.ref<!fir.box<!fir.heap<f32>>>)
+! CHECK:           %[[VAL_6:.*]] = fir.load %[[VAL_3]]#1 : !fir.ref<!fir.box<!fir.heap<f32>>>
+! CHECK:           return %[[VAL_6]] : !fir.box<!fir.heap<f32>>
+! CHECK:         }
+
+function test_alloc_return_array
+  real, allocatable :: test_alloc_return_array(:)
+  allocate(test_alloc_return_array(7:8))
+end function test_alloc_return_array
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_array() -> !fir.box<!fir.heap<!fir.array<?xf32>>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<?xf32>>> {bindc_name = "test_alloc_return_array", uniq_name = "_QFtest_alloc_return_arrayEtest_alloc_return_array"}
+! CHECK:           %[[VAL_5:.*]]:2 = hlfir.declare %[[VAL_0]] {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtest_alloc_return_arrayEtest_alloc_return_array"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>, !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>)
+! CHECK:           %[[VAL_19:.*]] = fir.load %[[VAL_5]]#1 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+! CHECK:           %[[VAL_20:.*]] = arith.constant 1 : index
+! CHECK:           %[[VAL_21:.*]] = fir.shift %[[VAL_20]] : (index) -> !fir.shift<1>
+! CHECK:           %[[VAL_22:.*]] = fir.rebox %[[VAL_19]](%[[VAL_21]]) : (!fir.box<!fir.heap<!fir.array<?xf32>>>, !fir.shift<1>) -> !fir.box<!fir.heap<!fir.array<?xf32>>>
+! CHECK:           return %[[VAL_22]] : !fir.box<!fir.heap<!fir.array<?xf32>>>
+! CHECK:         }
+
+function test_alloc_return_char_scalar
+  character(3), allocatable :: test_alloc_return_char_scalar
+  allocate(test_alloc_return_char_scalar)
+end function test_alloc_return_char_scalar
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_char_scalar() -> !fir.box<!fir.heap<!fir.char<1,3>>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.box<!fir.heap<!fir.char<1,3>>> {bindc_name = "test_alloc_return_char_scalar", uniq_name = "_QFtest_alloc_return_char_scalarEtest_alloc_return_char_scalar"}
+! CHECK:           %[[VAL_1:.*]] = arith.constant 3 : index
+! CHECK:           %[[VAL_4:.*]]:2 = hlfir.declare %[[VAL_0]] typeparams %[[VAL_1]] {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtest_alloc_return_char_scalarEtest_alloc_return_char_scalar"} : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,3>>>>, index) -> (!fir.ref<!fir.box<!fir.heap<!fir.char<1,3>>>>, !fir.ref<!fir.box<!fir.heap<!fir.char<1,3>>>>)
+! CHECK:           %[[VAL_7:.*]] = fir.load %[[VAL_4]]#1 : !fir.ref<!fir.box<!fir.heap<!fir.char<1,3>>>>
+! CHECK:           return %[[VAL_7]] : !fir.box<!fir.heap<!fir.char<1,3>>>
+! CHECK:         }
+
+function test_alloc_return_char_array
+  character(3), allocatable :: test_alloc_return_char_array(:)
+  allocate(test_alloc_return_char_array(7:8))
+end function test_alloc_return_char_array
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_char_array() -> !fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>> {bindc_name = "test_alloc_return_char_array", uniq_name = "_QFtest_alloc_return_char_arrayEtest_alloc_return_char_array"}
+! CHECK:           %[[VAL_1:.*]] = arith.constant 3 : index
+! CHECK:           %[[VAL_6:.*]]:2 = hlfir.declare %[[VAL_0]] typeparams %[[VAL_1]] {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtest_alloc_return_char_arrayEtest_alloc_return_char_array"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>>, index) -> (!fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>>, !fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>>)
+! CHECK:           %[[VAL_20:.*]] = fir.load %[[VAL_6]]#1 : !fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>>
+! CHECK:           %[[VAL_21:.*]] = arith.constant 1 : index
+! CHECK:           %[[VAL_22:.*]] = fir.shift %[[VAL_21]] : (index) -> !fir.shift<1>
+! CHECK:           %[[VAL_23:.*]] = fir.rebox %[[VAL_20]](%[[VAL_22]]) : (!fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>, !fir.shift<1>) -> !fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>
+! CHECK:           return %[[VAL_23]] : !fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>
+! CHECK:         }
+
+function test_alloc_return_poly_scalar
+  type t
+  end type t
+  class(*), allocatable :: test_alloc_return_poly_scalar
+  allocate(t :: test_alloc_return_poly_scalar)
+end function test_alloc_return_poly_scalar
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_poly_scalar() -> !fir.class<!fir.heap<none>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.class<!fir.heap<none>> {bindc_name = "test_alloc_return_poly_scalar", uniq_name = "_QFtest_alloc_return_poly_scalarEtest_alloc_return_poly_scalar"}
+! CHECK:           %[[VAL_3:.*]]:2 = hlfir.declare %[[VAL_0]] {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtest_alloc_return_poly_scalarEtest_alloc_return_poly_scalar"} : (!fir.ref<!fir.class<!fir.heap<none>>>) -> (!fir.ref<!fir.class<!fir.heap<none>>>, !fir.ref<!fir.class<!fir.heap<none>>>)
+! CHECK:           %[[VAL_17:.*]] = fir.load %[[VAL_3]]#1 : !fir.ref<!fir.class<!fir.heap<none>>>
+! CHECK:           return %[[VAL_17]] : !fir.class<!fir.heap<none>>
+! CHECK:         }
+
+function test_alloc_return_poly_array
+  type t
+  end type t
+  class(*), allocatable :: test_alloc_return_poly_array(:)
+  allocate(t :: test_alloc_return_poly_array(7:8))
+end function test_alloc_return_poly_array
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_poly_array() -> !fir.class<!fir.heap<!fir.array<?xnone>>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.class<!fir.heap<!fir.array<?xnone>>> {bindc_name = "test_alloc_return_poly_array", uniq_name = "_QFtest_alloc_return_poly_arrayEtest_alloc_return_poly_array"}
+! CHECK:           %[[VAL_5:.*]]:2 = hlfir.declare %[[VAL_0]] {fortran_attrs = #fir.var_attrs<allocatable>, uniq_name = "_QFtest_alloc_return_poly_arrayEtest_alloc_return_poly_array"} : (!fir.ref<!fir.class<!fir.heap<!fir.array<?xnone>>>>) -> (!fir.ref<!fir.class<!fir.heap<!fir.array<?xnone>>>>, !fir.ref<!fir.class<!fir.heap<!fir.array<?xnone>>>>)
+! CHECK:           %[[VAL_26:.*]] = fir.load %[[VAL_5]]#1 : !fir.ref<!fir.class<!fir.heap<!fir.array<?xnone>>>>
+! CHECK:           %[[VAL_27:.*]] = arith.constant 1 : index
+! CHECK:           %[[VAL_28:.*]] = fir.shift %[[VAL_27]] : (index) -> !fir.shift<1>
+! CHECK:           %[[VAL_29:.*]] = fir.rebox %[[VAL_26]](%[[VAL_28]]) : (!fir.class<!fir.heap<!fir.array<?xnone>>>, !fir.shift<1>) -> !fir.class<!fir.heap<!fir.array<?xnone>>>
+! CHECK:           return %[[VAL_29]] : !fir.class<!fir.heap<!fir.array<?xnone>>>
+! CHECK:         }
diff --git a/flang/test/Lower/allocatable-return.f90 b/flang/test/Lower/allocatable-return.f90
new file mode 100644
index 00000000000000..c9c0ef37a78d53
--- /dev/null
+++ b/flang/test/Lower/allocatable-return.f90
@@ -0,0 +1,77 @@
+! RUN: bbc -emit-fir --polymorphic-type -I nowhere %s -o - | FileCheck %s
+
+! Test allocatable return.
+! Allocatable arrays must have default runtime lbounds after the return.
+
+function test_alloc_return_scalar
+  real, allocatable :: test_alloc_return_scalar
+  allocate(test_alloc_return_scalar)
+end function test_alloc_return_scalar
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_scalar() -> !fir.box<!fir.heap<f32>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.box<!fir.heap<f32>> {bindc_name = "test_alloc_return_scalar", uniq_name = "_QFtest_alloc_return_scalarEtest_alloc_return_scalar"}
+! CHECK:           %[[VAL_5:.*]] = fir.load %[[VAL_0]] : !fir.ref<!fir.box<!fir.heap<f32>>>
+! CHECK:           return %[[VAL_5]] : !fir.box<!fir.heap<f32>>
+! CHECK:         }
+
+function test_alloc_return_array
+  real, allocatable :: test_alloc_return_array(:)
+  allocate(test_alloc_return_array(7:8))
+end function test_alloc_return_array
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_array() -> !fir.box<!fir.heap<!fir.array<?xf32>>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<?xf32>>> {bindc_name = "test_alloc_return_array", uniq_name = "_QFtest_alloc_return_arrayEtest_alloc_return_array"}
+! CHECK:           %[[VAL_18:.*]] = fir.load %[[VAL_0]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+! CHECK:           %[[VAL_19:.*]] = arith.constant 1 : index
+! CHECK:           %[[VAL_20:.*]] = fir.shift %[[VAL_19]] : (index) -> !fir.shift<1>
+! CHECK:           %[[VAL_21:.*]] = fir.rebox %[[VAL_18]](%[[VAL_20]]) : (!fir.box<!fir.heap<!fir.array<?xf32>>>, !fir.shift<1>) -> !fir.box<!fir.heap<!fir.array<?xf32>>>
+! CHECK:           return %[[VAL_21]] : !fir.box<!fir.heap<!fir.array<?xf32>>>
+! CHECK:         }
+
+function test_alloc_return_char_scalar
+  character(3), allocatable :: test_alloc_return_char_scalar
+  allocate(test_alloc_return_char_scalar)
+end function test_alloc_return_char_scalar
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_char_scalar() -> !fir.box<!fir.heap<!fir.char<1,3>>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.box<!fir.heap<!fir.char<1,3>>> {bindc_name = "test_alloc_return_char_scalar", uniq_name = "_QFtest_alloc_return_char_scalarEtest_alloc_return_char_scalar"}
+! CHECK:           %[[VAL_5:.*]] = fir.load %[[VAL_0]] : !fir.ref<!fir.box<!fir.heap<!fir.char<1,3>>>>
+! CHECK:           return %[[VAL_5]] : !fir.box<!fir.heap<!fir.char<1,3>>>
+! CHECK:         }
+
+function test_alloc_return_char_array
+  character(3), allocatable :: test_alloc_return_char_array(:)
+  allocate(test_alloc_return_char_array(7:8))
+end function test_alloc_return_char_array
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_char_array() -> !fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>> {bindc_name = "test_alloc_return_char_array", uniq_name = "_QFtest_alloc_return_char_arrayEtest_alloc_return_char_array"}
+! CHECK:           %[[VAL_18:.*]] = fir.load %[[VAL_0]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>>
+! CHECK:           %[[VAL_19:.*]] = arith.constant 1 : index
+! CHECK:           %[[VAL_20:.*]] = fir.shift %[[VAL_19]] : (index) -> !fir.shift<1>
+! CHECK:           %[[VAL_21:.*]] = fir.rebox %[[VAL_18]](%[[VAL_20]]) : (!fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>, !fir.shift<1>) -> !fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>
+! CHECK:           return %[[VAL_21]] : !fir.box<!fir.heap<!fir.array<?x!fir.char<1,3>>>>
+! CHECK:         }
+
+function test_alloc_return_poly_scalar
+  type t
+  end type t
+  class(*), allocatable :: test_alloc_return_poly_scalar
+  allocate(t :: test_alloc_return_poly_scalar)
+end function test_alloc_return_poly_scalar
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_poly_scalar() -> !fir.class<!fir.heap<none>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.class<!fir.heap<none>> {bindc_name = "test_alloc_return_poly_scalar", uniq_name = "_QFtest_alloc_return_poly_scalarEtest_alloc_return_poly_scalar"}
+! CHECK:           %[[VAL_16:.*]] = fir.load %[[VAL_0]] : !fir.ref<!fir.class<!fir.heap<none>>>
+! CHECK:           return %[[VAL_16]] : !fir.class<!fir.heap<none>>
+! CHECK:         }
+
+function test_alloc_return_poly_array
+  type t
+  end type t
+  class(*), allocatable :: test_alloc_return_poly_array(:)
+  allocate(t :: test_alloc_return_poly_array(7:8))
+end function test_alloc_return_poly_array
+! CHECK-LABEL:   func.func @_QPtest_alloc_return_poly_array() -> !fir.class<!fir.heap<!fir.array<?xnone>>> {
+! CHECK:           %[[VAL_0:.*]] = fir.alloca !fir.class<!fir.heap<!fir.array<?xnone>>> {bindc_name = "test_alloc_return_poly_array", uniq_name = "_QFtest_alloc_return_poly_arrayEtest_alloc_return_poly_array"}
+! CHECK:           %[[VAL_25:.*]] = fir.load %[[VAL_0]] : !fir.ref<!fir.class<!fir.heap<!fir.array<?xnone>>>>
+! CHECK:           %[[VAL_26:.*]] = arith.constant 1 : index
+! CHECK:           %[[VAL_27:.*]] = fir.shift %[[VAL_26]] : (index) -> !fir.shift<1>
+! CHECK:           %[[VAL_28:.*]] = fir.rebox %[[VAL_25]](%[[VAL_27]]) : (!fir.class<!fir.heap<!fir.array<?xnone>>>, !fir.shift<1>) -> !fir.class<!fir.heap<!fir.array<?xnone>>>
+! CHECK:           return %[[VAL_28]] : !fir.class<!fir.heap<!fir.array<?xnone>>>
+! CHECK:         }



More information about the flang-commits mailing list