[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:09 PDT 2026
https://github.com/Ritanya-B-Bharadwaj created https://github.com/llvm/llvm-project/pull/233394
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
>From 0cbfb299efbe7e70fc2f8639412ed11995dfc641 Mon Sep 17 00:00:00 2001
From: Ritanya B Bharadwaj <ritanya.b.bharadwaj at gmail.com>
Date: Mon, 19 Oct 2026 04:07:32 -0500
Subject: [PATCH] [flang] Version loops over POINTER arrays that are not dummy
arguments
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
Co-authored-by: Copilot <223556219+Copilot at users.noreply.github.com>
---
.../flang/Optimizer/Transforms/Passes.td | 7 +-
flang/lib/Optimizer/Passes/Pipelines.cpp | 7 +-
.../Optimizer/Transforms/LoopVersioning.cpp | 27 +++-
.../Transforms/loop-versioning-pointers.f90 | 134 ++++++++++++++++++
4 files changed, 164 insertions(+), 11 deletions(-)
create mode 100644 flang/test/Transforms/loop-versioning-pointers.f90
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
More information about the flang-commits
mailing list