[flang-commits] [flang] [flang] Version loops over POINTER arrays that are not dummy arguments (PR #233394)
via flang-commits
flang-commits at lists.llvm.org
Mon Oct 19 03:12:47 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-fir-hlfir
Author: Ritanya-B-Bharadwaj
<details>
<summary>Changes</summary>
LoopVersioning only versioned loops over dummy arguments, so loops over local, component and module POINTER arrays kept their unknown stride and did not vectorize.
Run flang-licm right before LoopVersioning so that loop-invariant descriptor loads are hoisted out of the loop, and let LoopVersioning also consider POINTER array descriptors that are not function arguments.
Fixes #<!-- -->233372
Assisted-by: GitHub Copilot
---
Full diff: https://github.com/llvm/llvm-project/pull/233394.diff
4 Files Affected:
- (modified) flang/include/flang/Optimizer/Transforms/Passes.td (+4-3)
- (modified) flang/lib/Optimizer/Passes/Pipelines.cpp (+6-1)
- (modified) flang/lib/Optimizer/Transforms/LoopVersioning.cpp (+20-7)
- (added) flang/test/Transforms/loop-versioning-pointers.f90 (+134)
``````````diff
diff --git a/flang/include/flang/Optimizer/Transforms/Passes.td b/flang/include/flang/Optimizer/Transforms/Passes.td
index 07d3296a17be96..166615fea3709e 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.td
+++ b/flang/include/flang/Optimizer/Transforms/Passes.td
@@ -468,9 +468,10 @@ def LoopVersioning : Pass<"loop-versioning", "mlir::func::FuncOp"> {
let summary = "Loop Versioning";
let description = [{
Loop Versioning pass adds a check and two variants of a loop when the input
- array is an assumed shape array, to optimize for the (often common) case where
- an array has element sized stride. The element sizes stride allows some
- loops to be vectorized as well as other loop optimizations.
+ array is an assumed shape or POINTER array, to optimize for the (often
+ common) case where an array has element sized stride. The element sizes
+ stride allows some loops to be vectorized as well as other loop
+ optimizations.
}];
let dependentDialects = [ "fir::FIROpsDialect", "mlir::DLTIDialect" ];
let options = [
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index 953104d0625b76..49a0a73ec75b22 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -197,8 +197,13 @@ void createDefaultFIRPreCFGOptimizerPassPipeline(
pm.addPass(fir::createConstantArgumentGlobalisationOpt());
}
- if (pc.LoopVersioning)
+ if (pc.LoopVersioning) {
+ // Hoist loop-invariant descriptor loads (e.g. of POINTER arrays) first,
+ // so that LoopVersioning can version loops on the strides of such arrays.
+ if (!disableFirLICM && pc.OptLevel != llvm::OptimizationLevel::O0)
+ pm.addPass(fir::createLoopInvariantCodeMotion());
pm.addPass(fir::createLoopVersioning());
+ }
pm.addPass(mlir::createCSEPass());
diff --git a/flang/lib/Optimizer/Transforms/LoopVersioning.cpp b/flang/lib/Optimizer/Transforms/LoopVersioning.cpp
index 57607f3e69b04a..8bf7ae6519e604 100644
--- a/flang/lib/Optimizer/Transforms/LoopVersioning.cpp
+++ b/flang/lib/Optimizer/Transforms/LoopVersioning.cpp
@@ -8,8 +8,11 @@
//===----------------------------------------------------------------------===//
/// \file
-/// This pass looks for loops iterating over assumed-shape arrays, that can
-/// be optimized by "guessing" that the stride is element-sized.
+/// This pass looks for loops iterating over assumed-shape or POINTER arrays,
+/// that can be optimized by "guessing" that the stride is element-sized.
+/// POINTER array descriptors may come from dummy arguments, local variables,
+/// derived type components, module variables, etc. Their descriptors must be
+/// defined outside the loop, e.g. hoisted by LICM.
///
/// This is done by creating two versions of the same loop: one which assumes
/// that the elements are contiguous (stride == size of element), and one that
@@ -155,6 +158,11 @@ static fir::SequenceType getAsSequenceType(mlir::Value v) {
return mlir::dyn_cast<fir::SequenceType>(argTy);
}
+/// Return true iff \p v is a POINTER array descriptor or the address of one.
+static bool isPointerArray(mlir::Value v) {
+ return fir::isPointerType(v.getType()) && getAsSequenceType(v);
+}
+
/// Return the rank and the element size (in bytes) of the given
/// value \p v. If it is not an array or the element type is not
/// supported, then return <0, 0>. Only trivial data types
@@ -441,11 +449,8 @@ void LoopVersioningPass::runOnOperation() {
argsOfInterest.push_back({arg, typeSize, rank, {}});
}
- if (argsOfInterest.empty()) {
- LLVM_DEBUG(llvm::dbgs()
- << "No suitable arguments.\n=== End " DEBUG_TYPE " ===\n");
- return;
- }
+ // POINTER arrays that are not dummy arguments are found while walking
+ // the loops below, so do not give up if there are no suitable arguments.
// A list of all loops in the function in post-order.
mlir::SmallVector<fir::DoLoopOp> originalLoops;
@@ -470,6 +475,14 @@ void LoopVersioningPass::runOnOperation() {
return;
mlir::Value operand = op->getOperand(0);
mlir::Value source = normaliseVal(operand);
+ // POINTER arrays are not known to be contiguous either. Their
+ // descriptors may come from local variables, derived type components,
+ // module variables, etc. Dummy arguments were collected above, so that
+ // the OPTIONAL check applies to them.
+ if (!mlir::isa<mlir::BlockArgument>(source) && isPointerArray(source) &&
+ llvm::none_of(argsOfInterest,
+ [&](const ArgInfo &a) { return a.arg == source; }))
+ argsOfInterest.push_back({source, 0, 0, {}});
for (auto a : argsOfInterest) {
if (a.arg == source) {
// Use the access descriptor, not its originating block argument.
diff --git a/flang/test/Transforms/loop-versioning-pointers.f90 b/flang/test/Transforms/loop-versioning-pointers.f90
new file mode 100644
index 00000000000000..d3b3564166b7e0
--- /dev/null
+++ b/flang/test/Transforms/loop-versioning-pointers.f90
@@ -0,0 +1,134 @@
+! RUN: %flang_fc1 -emit-fir -O3 %s -o - | \
+! RUN: fir-opt --verify-each --flang-licm --loop-versioning | \
+! RUN: FileCheck %s --enable-var-scope
+! RUN: %flang -S -O3 -mmlir --mlir-disable-threading \
+! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
+! RUN: %s -o /dev/null 2>&1 | FileCheck %s --enable-var-scope
+! Verify that loops over POINTER arrays that are not dummy arguments (local
+! variables, derived type components and module variables) are versioned for
+! unit stride once LICM has hoisted their descriptor loads out of the loop.
+! Descriptors that may change inside the loop must not be versioned.
+
+module loop_versioning_pointers
+ implicit none
+ type :: holder
+ real(8), pointer :: v(:) => null()
+ end type
+ real(8), pointer :: gp(:) => null()
+ interface
+ subroutine ext()
+ end subroutine
+ end interface
+contains
+ subroutine local_ptr(a, x, n)
+ real(8), target, intent(inout) :: a(:,:)
+ real(8), target, intent(in) :: x(:,:)
+ integer, intent(in) :: n
+ real(8), pointer :: p(:), q(:)
+ integer :: i, j
+ do j = 1, size(a, 2)
+ p => a(:, j)
+ q => x(:, j)
+ do i = 1, n
+ p(i) = p(i) + 2.0d0 * q(i)
+ end do
+ end do
+ end subroutine
+! CHECK-LABEL: func.func @_QMloop_versioning_pointersPlocal_ptr(
+! CHECK: arith.cmpi eq, %{{.*}}#2, %c8{{.*}} : index
+! CHECK: arith.cmpi eq, %{{.*}}#2, %c8{{.*}} : index
+! CHECK: fir.if
+! CHECK: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf64>>, index) -> !fir.ref<f64>
+! CHECK: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf64>>, index) -> !fir.ref<f64>
+! CHECK: } else {
+! CHECK: fir.array_coor {{.*}} : (!fir.box<!fir.ptr<!fir.array<?xf64>>>, !fir.shift<1>, i64) -> !fir.ref<f64>
+
+ subroutine component_ptr(h, g, n)
+ type(holder), intent(inout) :: h
+ type(holder), intent(in) :: g
+ integer, intent(in) :: n
+ integer :: i
+ do i = 1, n
+ h%v(i) = h%v(i) + 2.0d0 * g%v(i)
+ end do
+ end subroutine
+! CHECK-LABEL: func.func @_QMloop_versioning_pointersPcomponent_ptr(
+! CHECK: arith.cmpi eq, %{{.*}}#2, %c8{{.*}} : index
+! CHECK: arith.cmpi eq, %{{.*}}#2, %c8{{.*}} : index
+! CHECK: fir.if
+! CHECK: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf64>>, index) -> !fir.ref<f64>
+! CHECK: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf64>>, index) -> !fir.ref<f64>
+! CHECK: } else {
+! CHECK: fir.array_coor {{.*}} : (!fir.box<!fir.ptr<!fir.array<?xf64>>>, !fir.shift<1>, i64) -> !fir.ref<f64>
+
+ subroutine module_ptr(n, s)
+ integer, intent(in) :: n
+ real(8), intent(out) :: s
+ integer :: i
+ s = 0.0d0
+ do i = 1, n
+ s = s + gp(i)
+ end do
+ end subroutine
+! CHECK-LABEL: func.func @_QMloop_versioning_pointersPmodule_ptr(
+! CHECK: arith.cmpi eq, %{{.*}}#2, %c8{{.*}} : index
+! CHECK: fir.if
+! CHECK: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xf64>>, index) -> !fir.ref<f64>
+! CHECK: } else {
+! CHECK: fir.array_coor {{.*}} : (!fir.box<!fir.ptr<!fir.array<?xf64>>>, !fir.shift<1>, i64) -> !fir.ref<f64>
+
+ subroutine local_cmplx_ptr(res, k, f, n)
+ complex(8), target, intent(inout) :: res(:,:)
+ real(8), target, intent(in) :: k(:,:)
+ real(8), intent(in) :: f
+ integer, intent(in) :: n
+ complex(8), pointer :: r(:)
+ real(8), pointer :: kx(:)
+ integer :: i
+ r => res(:, 1)
+ kx => k(:, 1)
+ do i = 1, n
+ r(i) = r(i) + cmplx(f, kx(i), kind=8)
+ end do
+ end subroutine
+! CHECK-LABEL: func.func @_QMloop_versioning_pointersPlocal_cmplx_ptr(
+! CHECK-DAG: arith.cmpi eq, %{{.*}}#2, %c16{{.*}} : index
+! CHECK-DAG: arith.cmpi eq, %{{.*}}#2, %c8{{.*}} : index
+! CHECK: fir.if
+! CHECK: fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xcomplex<f64>>>, index) -> !fir.ref<complex<f64>>
+! CHECK: } else {
+! CHECK: fir.array_coor {{.*}} : (!fir.box<!fir.ptr<!fir.array<?xcomplex<f64>>>>, !fir.shift<1>, i64) -> !fir.ref<complex<f64>>
+
+ ! The pointer is re-associated inside the loop.
+ subroutine reassociated_in_loop(a, n, s)
+ real(8), target, intent(in) :: a(:,:)
+ integer, intent(in) :: n
+ real(8), intent(out) :: s
+ real(8), pointer :: p(:)
+ integer :: i
+ s = 0.0d0
+ p => a(:, 1)
+ do i = 1, n
+ s = s + p(i)
+ p => a(:, 2)
+ end do
+ end subroutine
+! CHECK-LABEL: func.func @_QMloop_versioning_pointersPreassociated_in_loop(
+! CHECK-NOT: fir.if
+! CHECK: return
+
+ ! The call may re-associate the module pointer.
+ subroutine module_ptr_with_call(n, s)
+ integer, intent(in) :: n
+ real(8), intent(out) :: s
+ integer :: i
+ s = 0.0d0
+ do i = 1, n
+ s = s + gp(i)
+ call ext()
+ end do
+ end subroutine
+! CHECK-LABEL: func.func @_QMloop_versioning_pointersPmodule_ptr_with_call(
+! CHECK-NOT: fir.if
+! CHECK: return
+end module
``````````
</details>
https://github.com/llvm/llvm-project/pull/233394
More information about the flang-commits
mailing list