[flang-commits] [flang] [flang][OpenMP] Support ALLOCATABLE list items in LINEAR (PR #228827)
Aditya Trivedi via flang-commits
flang-commits at lists.llvm.org
Sun Oct 11 06:11:15 PDT 2026
https://github.com/adit4443ya updated https://github.com/llvm/llvm-project/pull/228827
>From 2a79d447bf7e6cdc07564bd3b8c55c5e474c337b Mon Sep 17 00:00:00 2001
From: Aditya Trivedi <adit4443ya at gmail.com>
Date: Thu, 1 Oct 2026 18:19:10 +0530
Subject: [PATCH] [flang][OpenMP] Support ALLOCATABLE list items in LINEAR
Privatize allocatable linear items with omp.private, copying the linear
value into the private copy at the start of each iteration and back at
the end.
Fixes #182401
---
flang/lib/Lower/OpenMP/ClauseProcessor.cpp | 9 +-
.../lib/Lower/OpenMP/DataSharingProcessor.cpp | 6 +
flang/lib/Lower/OpenMP/OpenMP.cpp | 67 ++++++++++-
.../test/Lower/OpenMP/linear-allocatable.f90 | 111 ++++++++++++++++++
4 files changed, 191 insertions(+), 2 deletions(-)
create mode 100644 flang/test/Lower/OpenMP/linear-allocatable.f90
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index e41223b5b16c0..0b5b3e73010b3 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -1804,7 +1804,14 @@ bool ClauseProcessor::processLinear(mlir::omp::LinearClauseOps &result,
for (const omp::Object &object : objects) {
semantics::Symbol *sym = object.sym();
- const mlir::Value variable = converter.getSymbolAddress(*sym);
+ mlir::Value variable = converter.getSymbolAddress(*sym);
+ // Pass an allocatable's data address so it matches linear_var_types.
+ if (!isDeclareSimd && semantics::IsAllocatable(sym->GetUltimate())) {
+ fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
+ mlir::Location loc = converter.getCurrentLocation();
+ mlir::Value box = fir::LoadOp::create(firOpBuilder, loc, variable);
+ variable = fir::BoxAddrOp::create(firOpBuilder, loc, box);
+ }
result.linearVars.push_back(variable);
mlir::Type ty = converter.genType(*sym);
typeAttrs.push_back(mlir::TypeAttr::get(ty));
diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index 8d0d191058cfb..db30f113f019c 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -341,6 +341,12 @@ void DataSharingProcessor::collectSymbolsForPrivatization() {
} else {
collectOmpObjectListSymbol(objects, explicitlyPrivatizedSymbols);
}
+ } else if (const auto *linearClause =
+ std::get_if<omp::clause::Linear>(&clause.u)) {
+ // Allocatable linear items need a real private copy.
+ for (const omp::Object &object : std::get<ObjectList>(linearClause->t))
+ if (semantics::IsAllocatable(object.sym()->GetUltimate()))
+ allPrivatizedSymbols.insert(object.sym());
}
}
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 0b20eb1c16dc1..212804e8a13c6 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -2901,6 +2901,46 @@ genFlushOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
operandRange);
}
+/// Pair each privatized allocatable LINEAR item with its linear operand.
+static llvm::SmallVector<std::pair<const semantics::Symbol *, mlir::Value>>
+getLinearAllocatables(
+ llvm::ArrayRef<std::pair<mlir::omp::BlockArgOpenMPOpInterface,
+ const ObjectEntryBlockArgs &>>
+ wrapperArgs) {
+ llvm::SmallVector<mlir::Value> linearVars;
+ for (auto [argGeneratingOp, blockArgs] : wrapperArgs) {
+ mlir::Operation *wrapperOp = argGeneratingOp.getOperation();
+ if (auto simdOp = mlir::dyn_cast<mlir::omp::SimdOp>(wrapperOp))
+ llvm::append_range(linearVars, simdOp.getLinearVars());
+ else if (auto wsloopOp = mlir::dyn_cast<mlir::omp::WsloopOp>(wrapperOp))
+ llvm::append_range(linearVars, wsloopOp.getLinearVars());
+ }
+
+ llvm::SmallVector<std::pair<const semantics::Symbol *, mlir::Value>> result;
+ if (linearVars.empty())
+ return result;
+
+ for (auto [argGeneratingOp, blockArgs] : wrapperArgs) {
+ for (auto [object, privVar] :
+ llvm::zip_equal(blockArgs.priv.objects, blockArgs.priv.vars)) {
+ const semantics::Symbol *sym = object.sym();
+ if (!sym->test(semantics::Symbol::Flag::OmpLinear) ||
+ !semantics::IsAllocatable(sym->GetUltimate()))
+ continue;
+ // The linear operand is box_addr(load(original descriptor)).
+ auto *linearVar = llvm::find_if(linearVars, [&](mlir::Value var) {
+ auto boxAddr = var.getDefiningOp<fir::BoxAddrOp>();
+ auto load =
+ boxAddr ? boxAddr.getVal().getDefiningOp<fir::LoadOp>() : nullptr;
+ return load && load.getMemref() == privVar;
+ });
+ if (linearVar != linearVars.end())
+ result.emplace_back(sym, *linearVar);
+ }
+ }
+ return result;
+}
+
static mlir::omp::LoopNestOp genLoopNestOp(
lower::AbstractConverter &converter, lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval,
@@ -2912,8 +2952,18 @@ static mlir::omp::LoopNestOp genLoopNestOp(
wrapperArgs,
llvm::omp::Directive directive, DataSharingProcessor &dsp,
llvm::function_ref<void(mlir::Operation *)> loopPostIvCb = nullptr) {
+ fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
+ // Copy the linear value into the private allocatable at iteration start.
+ llvm::SmallVector<std::pair<mlir::Value, mlir::Value>> linearCopies;
auto ivCallback = [&](mlir::Operation *op) {
genLoopVars(op, converter, loc, iv, wrapperArgs);
+ for (auto [sym, linearVar] : getLinearAllocatables(wrapperArgs)) {
+ mlir::Value privAddr = converter.getSymbolAddress(*sym);
+ mlir::Value val = fir::LoadOp::create(firOpBuilder, loc, linearVar);
+ hlfir::AssignOp::create(firOpBuilder, loc, val, privAddr,
+ /*realloc=*/true);
+ linearCopies.emplace_back(privAddr, linearVar);
+ }
if (loopPostIvCb)
loopPostIvCb(op);
return llvm::SmallVector<const semantics::Symbol *>(iv);
@@ -2922,7 +2972,7 @@ static mlir::omp::LoopNestOp genLoopNestOp(
uint64_t nestValue = getCollapseValue(item->clauses);
nestValue = nestValue < iv.size() ? iv.size() : nestValue;
auto *nestedEval = getCollapsedLoopEval(eval, nestValue);
- return genOpWithBody<mlir::omp::LoopNestOp>(
+ auto loopNestOp = genOpWithBody<mlir::omp::LoopNestOp>(
OpWithBodyGenInfo(converter, symTable, semaCtx, loc, *nestedEval,
directive)
.setClauses(&item->clauses)
@@ -2930,6 +2980,21 @@ static mlir::omp::LoopNestOp genLoopNestOp(
.setGenRegionEntryCb(ivCallback)
.setCollapseInfo(nestValue, eval),
queue, item, clauseOps);
+
+ // Copy the private value back to the linear value before the yield.
+ if (!linearCopies.empty()) {
+ mlir::OpBuilder::InsertionGuard guard(firOpBuilder);
+ for (mlir::Block &block : loopNestOp.getRegion())
+ if (auto yield = mlir::dyn_cast<mlir::omp::YieldOp>(&block.back()))
+ firOpBuilder.setInsertionPoint(yield);
+ for (auto [privAddr, linearVar] : linearCopies) {
+ hlfir::Entity priv = hlfir::derefPointersAndAllocatables(
+ loc, firOpBuilder, hlfir::Entity{privAddr});
+ mlir::Value val = hlfir::loadTrivialScalar(loc, firOpBuilder, priv);
+ fir::StoreOp::create(firOpBuilder, loc, val, linearVar);
+ }
+ }
+ return loopNestOp;
}
static mlir::omp::LoopOp
diff --git a/flang/test/Lower/OpenMP/linear-allocatable.f90 b/flang/test/Lower/OpenMP/linear-allocatable.f90
new file mode 100644
index 0000000000000..6020562c5408f
--- /dev/null
+++ b/flang/test/Lower/OpenMP/linear-allocatable.f90
@@ -0,0 +1,111 @@
+! LINEAR on an allocatable: privatized via omp.private; the linear value is
+! copied into the private copy at the start of each iteration and back at the
+! end.
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -o - %s 2>&1 | FileCheck %s
+! RUN: bbc -emit-hlfir -fopenmp -o - %s 2>&1 | FileCheck %s
+
+! CHECK: omp.private {type = private} @[[PRIV_I32:.*simd_linear_allocatableEa_private_box_heap_i32]] : !fir.box<!fir.heap<i32>> init {
+! CHECK: fir.allocmem i32
+! CHECK: } dealloc {
+! CHECK: fir.freemem
+
+! CHECK-LABEL: func.func @_QPsimd_linear_allocatable
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} uniq_name("_QFsimd_linear_allocatableEa") fortran_attrs<allocatable>
+! CHECK: %[[BOX:.*]] = fir.load %[[A]]#0 : !fir.ref<!fir.box<!fir.heap<i32>>>
+! CHECK: %[[ADDR:.*]] = fir.box_addr %[[BOX]] : (!fir.box<!fir.heap<i32>>) -> !fir.heap<i32>
+! CHECK: omp.simd linear(%[[ADDR]] : !fir.heap<i32> = %{{.*}} : i32
+! CHECK-SAME: linear_var_types([i32
+! CHECK-SAME: private(@[[PRIV_I32]] %[[A]]#0 -> %[[ARG:.*]] : !fir.ref<!fir.box<!fir.heap<i32>>>)
+! CHECK: omp.loop_nest
+! CHECK: %[[PRIV_A:.*]]:2 = hlfir.declare %[[ARG]] uniq_name("_QFsimd_linear_allocatableEa") fortran_attrs<allocatable>
+! CHECK: %[[LIN:.*]] = fir.load %[[ADDR]] : !fir.heap<i32>
+! CHECK: hlfir.assign %[[LIN]] to %[[PRIV_A]]#0 realloc : i32, !fir.ref<!fir.box<!fir.heap<i32>>>
+! CHECK: hlfir.assign %{{.*}} to %[[PRIV_A]]#0 realloc : i32, !fir.ref<!fir.box<!fir.heap<i32>>>
+! CHECK: %[[PBOX:.*]] = fir.load %[[PRIV_A]]#0 : !fir.ref<!fir.box<!fir.heap<i32>>>
+! CHECK: %[[PADDR:.*]] = fir.box_addr %[[PBOX]] : (!fir.box<!fir.heap<i32>>) -> !fir.heap<i32>
+! CHECK: %[[PVAL:.*]] = fir.load %[[PADDR]] : !fir.heap<i32>
+! CHECK: fir.store %[[PVAL]] to %[[ADDR]] : !fir.heap<i32>
+! CHECK-NEXT: omp.yield
+subroutine simd_linear_allocatable()
+ integer, allocatable :: a
+ integer :: i
+ allocate(a)
+ a = 0
+ !$omp simd linear(a)
+ do i = 1, 2
+ a = 2
+ end do
+ !$omp end simd
+end subroutine
+
+! CHECK-LABEL: func.func @_QPdo_linear_allocatable
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} uniq_name("_QFdo_linear_allocatableEa") fortran_attrs<allocatable>
+! CHECK: omp.parallel
+! CHECK: %[[BOX:.*]] = fir.load %[[A]]#0 : !fir.ref<!fir.box<!fir.heap<i32>>>
+! CHECK: %[[ADDR:.*]] = fir.box_addr %[[BOX]] : (!fir.box<!fir.heap<i32>>) -> !fir.heap<i32>
+! CHECK: omp.wsloop linear(%[[ADDR]] : !fir.heap<i32> = %{{.*}} : i32
+! CHECK-SAME: private(@{{.*}}do_linear_allocatableEa_private_box_heap_i32 %[[A]]#0 -> %[[ARG:[^,]*]],
+! CHECK: omp.loop_nest
+! CHECK: %[[PRIV_A:.*]]:2 = hlfir.declare %[[ARG]] uniq_name("_QFdo_linear_allocatableEa") fortran_attrs<allocatable>
+! CHECK: %[[LIN:.*]] = fir.load %[[ADDR]] : !fir.heap<i32>
+! CHECK: hlfir.assign %[[LIN]] to %[[PRIV_A]]#0 realloc
+! CHECK: fir.store %{{.*}} to %[[ADDR]] : !fir.heap<i32>
+! CHECK-NEXT: omp.yield
+subroutine do_linear_allocatable()
+ integer, allocatable :: a
+ integer :: i
+ allocate(a)
+ a = 0
+ !$omp parallel do linear(a:2)
+ do i = 1, 10
+ a = a + 2
+ end do
+ !$omp end parallel do
+end subroutine
+
+! Composite: the linear operand moves to omp.wsloop, the private copy is on
+! omp.simd.
+! CHECK-LABEL: func.func @_QPdo_simd_linear_allocatable
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} uniq_name("_QFdo_simd_linear_allocatableEa") fortran_attrs<allocatable>
+! CHECK: %[[ADDR:.*]] = fir.box_addr %{{.*}} : (!fir.box<!fir.heap<i64>>) -> !fir.heap<i64>
+! CHECK: omp.wsloop linear(%[[ADDR]] : !fir.heap<i64> = %{{.*}} : i64
+! CHECK: omp.simd
+! CHECK-SAME: private(@{{.*}}do_simd_linear_allocatableEa_private_box_heap_i64 %[[A]]#0 -> %[[ARG:.*]] : !fir.ref<!fir.box<!fir.heap<i64>>>)
+! CHECK: omp.loop_nest
+! CHECK: %[[PRIV_A:.*]]:2 = hlfir.declare %[[ARG]]
+! CHECK: %[[LIN:.*]] = fir.load %[[ADDR]] : !fir.heap<i64>
+! CHECK: hlfir.assign %[[LIN]] to %[[PRIV_A]]#0 realloc : i64
+! CHECK: fir.store %{{.*}} to %[[ADDR]] : !fir.heap<i64>
+! CHECK-NEXT: omp.yield
+subroutine do_simd_linear_allocatable()
+ integer(8), allocatable :: a
+ integer :: i
+ allocate(a)
+ a = 0
+ !$omp do simd linear(a)
+ do i = 1, 10
+ a = a + 1
+ end do
+ !$omp end do simd
+end subroutine
+
+! The original descriptor is used after the loop.
+! CHECK-LABEL: func.func @_QPoriginal_used_after_loop
+! CHECK: %[[A:.*]]:2 = hlfir.declare %{{.*}} uniq_name("_QForiginal_used_after_loopEa") fortran_attrs<allocatable>
+! CHECK: omp.simd linear
+! CHECK: omp.yield
+! CHECK: }
+! CHECK: hlfir.assign %{{.*}} to %[[A]]#0 realloc : i32, !fir.ref<!fir.box<!fir.heap<i32>>>
+subroutine original_used_after_loop()
+ integer, allocatable :: a
+ integer :: i
+ allocate(a)
+ a = 0
+ !$omp simd linear(a)
+ do i = 1, 2
+ a = 2
+ end do
+ !$omp end simd
+ a = 5
+end subroutine
More information about the flang-commits
mailing list