[flang-commits] [flang] [Flang][OpenMP] Fix crash and IR errors for user-defined reduction on allocatable variables (PR #186765)
via flang-commits
flang-commits at lists.llvm.org
Tue Jul 21 23:22:42 PDT 2026
https://github.com/Ritanya-B-Bharadwaj updated https://github.com/llvm/llvm-project/pull/186765
>From 2572501bec2697d6912cb4de61dbc9d2a193f007 Mon Sep 17 00:00:00 2001
From: Ritanya-B-Bharadwaj <ritanya.b.bharadwaj at gmail.com>
Date: Wed, 22 Jul 2026 11:47:33 +0530
Subject: [PATCH] Addressing review comments
---
.../lib/Lower/Support/ReductionProcessor.cpp | 194 ++++++++++++++++++
.../declare-reduction-allocatable-trivial.f90 | 33 ---
.../declare-reduction-allocatable-char.f90 | 37 ++++
...declare-reduction-allocatable-combined.f90 | 35 ++++
...eclare-reduction-allocatable-orig-init.f90 | 41 ++++
.../declare-reduction-allocatable-real.f90 | 50 +++++
.../OpenMP/declare-reduction-allocatable.f90 | 125 +++++++++++
7 files changed, 482 insertions(+), 33 deletions(-)
delete mode 100644 flang/test/Lower/OpenMP/Todo/declare-reduction-allocatable-trivial.f90
create mode 100644 flang/test/Lower/OpenMP/declare-reduction-allocatable-char.f90
create mode 100644 flang/test/Lower/OpenMP/declare-reduction-allocatable-combined.f90
create mode 100644 flang/test/Lower/OpenMP/declare-reduction-allocatable-orig-init.f90
create mode 100644 flang/test/Lower/OpenMP/declare-reduction-allocatable-real.f90
create mode 100644 flang/test/Lower/OpenMP/declare-reduction-allocatable.f90
diff --git a/flang/lib/Lower/Support/ReductionProcessor.cpp b/flang/lib/Lower/Support/ReductionProcessor.cpp
index e779e5cc7e4a0..cd107a820b478 100644
--- a/flang/lib/Lower/Support/ReductionProcessor.cpp
+++ b/flang/lib/Lower/Support/ReductionProcessor.cpp
@@ -26,6 +26,7 @@
#include "flang/Optimizer/HLFIR/HLFIROps.h"
#include "flang/Semantics/openmp-utils.h"
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
+#include "mlir/IR/IRMapping.h"
#include "llvm/Support/CommandLine.h"
#include <type_traits>
@@ -734,6 +735,150 @@ bool ReductionProcessor::doReductionByRef(mlir::Value reductionVar) {
return doReductionByRef(reductionVar.getType());
}
+static bool baseInitReadsOrig(mlir::Block &initBlock) {
+ bool reads = false;
+ initBlock.walk([&](hlfir::DeclareOp declOp) {
+ std::optional<llvm::StringRef> name = declOp.getUniqName();
+ if (name && *name == "omp_orig" &&
+ (!declOp.getResult(0).use_empty() || !declOp.getResult(1).use_empty()))
+ reads = true;
+ });
+ return reads;
+}
+
+template <typename OpType>
+static OpType getOrCreateBoxedUserReduction(
+ lower::AbstractConverter &converter, semantics::SemanticsContext *semaCtx,
+ const semantics::Symbol &redSym, mlir::Type redType, mlir::Location loc) {
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ mlir::ModuleOp module = builder.getModule();
+
+ // Boxed op name encodes the full boxed type (..._byref_box_heap_i32),
+ // distinct from the base element op.
+ std::string boxedName = ReductionProcessor::getScopedUserReductionName(
+ converter, redSym, redType, /*isByRef=*/true);
+ if (auto existing = module.lookupSymbol<OpType>(boxedName))
+ return existing;
+
+ // Base op is named from the scalar element type (identical for scalar and
+ // array); materialize on demand for separate compilation.
+ mlir::Type elementType = unwrapSeqOrBoxedType(fir::unwrapRefType(redType));
+ const bool baseByRef = ReductionProcessor::doReductionByRef(elementType);
+ std::string baseName = ReductionProcessor::getScopedUserReductionName(
+ converter, redSym, elementType, baseByRef);
+ auto baseDecl = module.lookupSymbol<OpType>(baseName);
+ if (!baseDecl && semaCtx && redSym.owner().symbol() &&
+ redSym.owner().symbol()->test(semantics::Symbol::Flag::ModFile)) {
+ Fortran::lower::materializeUserReduction(converter, *semaCtx, redSym,
+ baseName, elementType, baseByRef);
+ baseDecl = module.lookupSymbol<OpType>(baseName);
+ }
+ if (!baseDecl)
+ return {};
+
+ // Only a by-value base op can be wrapped (its init/combiner yield scalars).
+ mlir::Region &baseInitRegion = baseDecl.getInitializerRegion();
+ mlir::Region &baseCombinerRegion = baseDecl.getReductionRegion();
+ mlir::Block &baseInitBlock = baseInitRegion.front();
+ if (fir::isa_ref_type(baseInitBlock.getArgument(0).getType()) ||
+ fir::isa_ref_type(baseCombinerRegion.front().getArgument(0).getType()))
+ return {};
+ const bool initReadsOrig = baseInitReadsOrig(baseInitBlock);
+
+ // Clone the region's entry block (up to its terminator) mapping block args to
+ // \p args; return the yielded value.
+ auto cloneBody = [](fir::FirOpBuilder &b, mlir::Region ®ion,
+ mlir::ValueRange args) -> mlir::Value {
+ mlir::Block &block = region.front();
+ mlir::IRMapping mapper;
+ for (auto [blockArg, val] : llvm::zip(block.getArguments(), args))
+ mapper.map(blockArg, val);
+ for (mlir::Operation &op : block.without_terminator())
+ b.clone(op, mapper);
+ mlir::Operation *term = block.getTerminator();
+ if (term && term->getNumOperands() > 0)
+ return mapper.lookupOrDefault(term->getOperand(0));
+ return {};
+ };
+
+ auto genInitValueCB = [&](fir::FirOpBuilder &b, mlir::Location loc,
+ mlir::Type ty, mlir::Value moldArg,
+ mlir::Value /*privArg*/) -> mlir::Value {
+ mlir::Type scalarTy = unwrapSeqOrBoxedType(ty);
+ llvm::SmallVector<mlir::Value> initArgs;
+ if (initReadsOrig) {
+ auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(fir::unwrapRefType(ty));
+ auto seqTy = boxTy ? mlir::dyn_cast_or_null<fir::SequenceType>(
+ fir::unwrapRefType(boxTy.getEleTy()))
+ : nullptr;
+ if (seqTy)
+ TODO(loc, "OpenMP user-defined reduction on an allocatable/pointer "
+ "array whose initializer reads omp_orig");
+ // Scalar: feed the original element to the cloned initializer's omp_orig.
+ mlir::Value box = fir::LoadOp::create(b, loc, moldArg);
+ mlir::Value addr = fir::BoxAddrOp::create(b, loc, box);
+ initArgs.push_back(fir::LoadOp::create(b, loc, addr));
+ } else {
+ // orig-independent: a placeholder feeds the base's dead temporary stores.
+ initArgs.push_back(fir::UndefOp::create(b, loc, scalarTy));
+ }
+ return cloneBody(b, baseInitRegion, initArgs);
+ };
+
+ auto genCombinerCB = [&](fir::FirOpBuilder &b, mlir::Location loc,
+ mlir::Type type, mlir::Value op1, mlir::Value op2,
+ bool /*isByRef*/) {
+ auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(fir::unwrapRefType(type));
+ if (!boxTy) {
+ TODO(loc, "OpenMP user-defined reduction on unsupported boxed type");
+ return;
+ }
+ auto seqTy = mlir::dyn_cast_or_null<fir::SequenceType>(
+ fir::unwrapRefType(boxTy.getEleTy()));
+
+ mlir::Value lhsBox = fir::LoadOp::create(b, loc, op1);
+ mlir::Value rhsBox = fir::LoadOp::create(b, loc, op2);
+
+ if (!seqTy) {
+ // Scalar allocatable/pointer: combine the single element in place.
+ mlir::Value lhsAddr = fir::BoxAddrOp::create(b, loc, lhsBox);
+ mlir::Value rhsAddr = fir::BoxAddrOp::create(b, loc, rhsBox);
+ mlir::Value lhsEle = fir::LoadOp::create(b, loc, lhsAddr);
+ mlir::Value rhsEle = fir::LoadOp::create(b, loc, rhsAddr);
+ if (mlir::Value res = cloneBody(b, baseCombinerRegion, {lhsEle, rhsEle}))
+ fir::StoreOp::create(b, loc, res, lhsAddr);
+ } else {
+ // Array allocatable/pointer: apply the scalar combiner element-wise.
+ fir::ShapeShiftOp shapeShift =
+ getShapeShift(b, loc, lhsBox,
+ /*cannotHaveNonDefaultLowerBounds=*/false,
+ /*useDefaultLowerBounds=*/true);
+ hlfir::LoopNest nest = hlfir::genLoopNest(loc, b, shapeShift.getExtents(),
+ /*isUnordered=*/true);
+ b.setInsertionPointToStart(nest.body);
+ mlir::Type eleTy = seqTy.getEleTy();
+ mlir::Type refTy =
+ fir::ReferenceType::get(eleTy, fir::isa_volatile_type(eleTy));
+ auto lhsEleAddr = fir::ArrayCoorOp::create(
+ b, loc, refTy, lhsBox, shapeShift, /*slice=*/mlir::Value{},
+ nest.oneBasedIndices, /*typeparms=*/mlir::ValueRange{});
+ auto rhsEleAddr = fir::ArrayCoorOp::create(
+ b, loc, refTy, rhsBox, shapeShift, /*slice=*/mlir::Value{},
+ nest.oneBasedIndices, /*typeparms=*/mlir::ValueRange{});
+ mlir::Value lhsEle = fir::LoadOp::create(b, loc, lhsEleAddr);
+ mlir::Value rhsEle = fir::LoadOp::create(b, loc, rhsEleAddr);
+ if (mlir::Value res = cloneBody(b, baseCombinerRegion, {lhsEle, rhsEle}))
+ fir::StoreOp::create(b, loc, res, lhsEleAddr);
+ b.setInsertionPointAfter(nest.outerOp);
+ }
+ genYield<OpType>(b, loc, op1);
+ };
+
+ return ReductionProcessor::createDeclareReductionHelper<OpType>(
+ converter, boxedName, redType, loc, /*isByRef=*/true, genCombinerCB,
+ genInitValueCB, /*sym=*/nullptr);
+}
+
template <typename OpType, typename RedOperatorListTy>
bool ReductionProcessor::processReductionArguments(
mlir::Location currentLocation, lower::AbstractConverter &converter,
@@ -921,6 +1066,14 @@ bool ReductionProcessor::processReductionArguments(
const bool isBoxedTrivialReduction =
mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(redType)) &&
fir::isa_trivial(namingType);
+ // Like isBoxedTrivialReduction but gated on the trivial *element* type,
+ // so array allocatables/pointers (whose namingType is a non-trivial
+ // sequence) are also wrapped around the scalar base op (#186765).
+ mlir::Type boxedElementType =
+ unwrapSeqOrBoxedType(fir::unwrapRefType(redType));
+ const bool isBoxedTrivialElemReduction =
+ mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(redType)) &&
+ fir::isa_trivial(boxedElementType);
if (const auto &redDefinedOp =
std::get_if<omp::clause::DefinedOperator>(&redOperator.u)) {
if (const auto *definedOpName =
@@ -986,6 +1139,16 @@ bool ReductionProcessor::processReductionArguments(
// is the canonical reduction element type (allocatable/pointer
// storage wrappers stripped), matching the type the op was named and
// created from on the directive side.
+ if (isBoxedTrivialElemReduction) {
+ // Trivial-element allocatable/pointer: synthesize the boxed op.
+ if (OpType boxedDecl = getOrCreateBoxedUserReduction<OpType>(
+ converter, semaCtx, *ultimate, redType, currentLocation)) {
+ reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get(
+ builder.getContext(), boxedDecl.getSymName()));
+ ++idx;
+ continue;
+ }
+ }
if (!existingDecl ||
fir::unwrapRefType(existingDecl.getType()) !=
fir::unwrapRefType(namingType) ||
@@ -1058,6 +1221,16 @@ bool ReductionProcessor::processReductionArguments(
converter, *semaCtx, ultimate, opName, namingType, isByRef);
existingDecl = module.lookupSymbol<OpType>(opName);
}
+ if (isBoxedTrivialElemReduction) {
+ // Trivial-element allocatable/pointer: synthesize the boxed op.
+ if (OpType boxedDecl = getOrCreateBoxedUserReduction<OpType>(
+ converter, semaCtx, ultimate, redType, currentLocation)) {
+ reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get(
+ builder.getContext(), boxedDecl.getSymName()));
+ ++idx;
+ continue;
+ }
+ }
if (!existingDecl ||
fir::unwrapRefType(existingDecl.getType()) !=
fir::unwrapRefType(namingType) ||
@@ -1152,6 +1325,17 @@ bool ReductionProcessor::processReductionArguments(
// op was created; better an error here than binding a wrong op.
// - isBoxedTrivialReduction: an allocatable/pointer reduction of a
// trivial element type, deferred to #186765.
+ if (isBoxedTrivialElemReduction) {
+ // Trivial-element allocatable/pointer: synthesize the boxed op.
+ if (OpType boxedDecl = getOrCreateBoxedUserReduction<OpType>(
+ converter, semaCtx, namedUltimate, redType,
+ currentLocation)) {
+ reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get(
+ builder.getContext(), boxedDecl.getSymName()));
+ ++idx;
+ continue;
+ }
+ }
if (!existingDecl ||
fir::unwrapRefType(existingDecl.getType()) !=
fir::unwrapRefType(namingType) ||
@@ -1216,6 +1400,16 @@ bool ReductionProcessor::processReductionArguments(
converter, *semaCtx, ultimate, opName, namingType, isByRef);
existingDecl = module.lookupSymbol<OpType>(opName);
}
+ if (isBoxedTrivialElemReduction) {
+ // Trivial-element allocatable/pointer: synthesize the boxed op.
+ if (OpType boxedDecl = getOrCreateBoxedUserReduction<OpType>(
+ converter, semaCtx, ultimate, redType, currentLocation)) {
+ reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get(
+ builder.getContext(), boxedDecl.getSymName()));
+ ++idx;
+ continue;
+ }
+ }
if (!existingDecl ||
fir::unwrapRefType(existingDecl.getType()) !=
fir::unwrapRefType(namingType) ||
diff --git a/flang/test/Lower/OpenMP/Todo/declare-reduction-allocatable-trivial.f90 b/flang/test/Lower/OpenMP/Todo/declare-reduction-allocatable-trivial.f90
deleted file mode 100644
index c3af4961f7f8a..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/declare-reduction-allocatable-trivial.f90
+++ /dev/null
@@ -1,33 +0,0 @@
-! A user-defined reduction on an allocatable (or pointer) variable of a trivial
-! by-value type (integer/real) is not yet lowered: the directive materializes a
-! by-value op (e.g. @..._i32) from the declared element type, but the reduction
-! operand is a boxed, by-ref allocatable, so the clause looks up a by-ref name
-! (@..._byref_i32) the directive never emitted and reaches a clean TODO rather
-! than binding a by-value op to a boxed operand (which produced invalid IR
-! before). Creating the boxed-type op for such operands is tracked by
-! https://github.com/llvm/llvm-project/pull/186765. Character and derived-type
-! allocatable reductions (by-ref op and by-ref operand) do lower and are covered
-! by declare-reduction-character-allocatable.f90.
-
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: OpenMP user-defined named reduction is not yet lowered for this variable's shape
-
-program p
- integer, allocatable :: a
- real, allocatable :: b
- integer :: i
- !$omp declare reduction(myadd: integer, real : omp_out = omp_out + omp_in) &
- !$omp initializer(omp_priv = 0)
- allocate(a); a = 0
- allocate(b); b = 0.0
- !$omp parallel do reduction(myadd: a)
- do i = 1, 5
- a = a + i
- end do
- !$omp parallel do reduction(myadd: b)
- do i = 1, 5
- b = b + real(i)
- end do
- print *, a, b
-end program
diff --git a/flang/test/Lower/OpenMP/declare-reduction-allocatable-char.f90 b/flang/test/Lower/OpenMP/declare-reduction-allocatable-char.f90
new file mode 100644
index 0000000000000..2452f730e9d2c
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-allocatable-char.f90
@@ -0,0 +1,37 @@
+! Allocatable CHARACTER UDR lowers via the existing by-ref char path (character
+! is not trivial, so it is not wrapped by the boxed-trivial synthesis)
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=45 %s -o - | FileCheck %s
+
+subroutine test_udr_char_allocatable()
+ implicit none
+ integer :: i
+ character(len=3), allocatable :: s
+
+ !$omp declare reduction(cmax : character(len=3) : omp_out = max(omp_out, omp_in)) &
+ !$omp& initializer(omp_priv = ' ')
+
+ allocate(s)
+ s = 'aaa'
+
+ !$omp parallel do reduction(cmax : s)
+ do i = 1, 3
+ s = max(s, 'bbb')
+ end do
+end subroutine
+
+! CHECK-LABEL: omp.declare_reduction @{{.*}}cmax_byref_c8x3 : !fir.ref<!fir.char<1,3>>
+! CHECK-SAME: attributes {byref_element_type = !fir.char<1,3>}
+! CHECK: init {
+! CHECK: ^bb0(%{{.*}}: !fir.ref<!fir.char<1,3>>, %{{.*}}: !fir.ref<!fir.char<1,3>>):
+! CHECK: hlfir.assign
+! CHECK: omp.yield
+! CHECK: } combiner {
+! CHECK: ^bb0(%[[ARG0:.*]]: !fir.ref<!fir.char<1,3>>, %{{.*}}: !fir.ref<!fir.char<1,3>>):
+! CHECK: hlfir.char_extremum max
+! CHECK: fir.store %{{.*}} to %[[ARG0]]
+! CHECK: omp.yield(%[[ARG0]] : !fir.ref<!fir.char<1,3>>)
+
+! The clause binds the character op with the boxed allocatable operand.
+! CHECK-LABEL: func.func @_QPtest_udr_char_allocatable
+! CHECK: omp.wsloop {{.*}} reduction(byref @{{.*}}cmax_byref_c8x3 %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.char<1,3>>>>)
diff --git a/flang/test/Lower/OpenMP/declare-reduction-allocatable-combined.f90 b/flang/test/Lower/OpenMP/declare-reduction-allocatable-combined.f90
new file mode 100644
index 0000000000000..81a2e1829a382
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-allocatable-combined.f90
@@ -0,0 +1,35 @@
+! One combined reduction(foo : a, b, c) clause over mixed-shape allocatables with
+! non-default lower bounds (b(6:9), c(3,8:9)) must lower.
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=45 %s -o - | FileCheck %s
+
+subroutine test_udr_combined()
+ implicit none
+ integer :: i
+ integer, allocatable :: a, b(:), c(:,:)
+
+ !$omp declare reduction (foo : integer : omp_out = omp_out + omp_in) &
+ !$omp & initializer (omp_priv = 0)
+
+ allocate(a, b(6:9), c(3, 8:9))
+ a = 0
+ b = 0
+ c = 0
+
+ !$omp parallel do reduction(foo : a, b, c)
+ do i = 1, 10
+ a = a + i
+ b = b + i
+ c = c + i
+ end do
+end subroutine
+
+! One boxed op per shape is synthesized from the single scalar base op.
+! CHECK-DAG: omp.declare_reduction @{{.*}}foo_byref_box_heap_i32 : !fir.ref<!fir.box<!fir.heap<i32>>>
+! CHECK-DAG: omp.declare_reduction @{{.*}}foo_byref_box_heap_Uxi32 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+! CHECK-DAG: omp.declare_reduction @{{.*}}foo_byref_box_heap_UxUxi32 : !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>
+! CHECK-DAG: omp.declare_reduction @{{.*}}foo_i32 : i32
+
+! The one combined clause binds all three boxed ops in a single wsloop.
+! CHECK-LABEL: func.func @_QPtest_udr_combined
+! CHECK: omp.wsloop {{.*}}reduction(byref @{{.*}}foo_byref_box_heap_i32 {{.*}}byref @{{.*}}foo_byref_box_heap_Uxi32 {{.*}}byref @{{.*}}foo_byref_box_heap_UxUxi32 {{.*}}!fir.ref<!fir.box<!fir.heap<i32>>>, !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>)
diff --git a/flang/test/Lower/OpenMP/declare-reduction-allocatable-orig-init.f90 b/flang/test/Lower/OpenMP/declare-reduction-allocatable-orig-init.f90
new file mode 100644
index 0000000000000..d98ca16f72966
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-allocatable-orig-init.f90
@@ -0,0 +1,41 @@
+! An initializer reading omp_orig must init the allocatable private copy from the
+! original element, not a fabricated zero.
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=45 %s -o - | FileCheck %s
+
+subroutine test_udr_orig_init()
+ implicit none
+ integer :: i
+ integer, allocatable :: a
+
+ !$omp declare reduction (myinit : integer : omp_out = omp_out + omp_in) &
+ !$omp & initializer (omp_priv = omp_orig)
+
+ allocate(a)
+ a = 7
+
+ !$omp parallel do reduction(myinit : a)
+ do i = 1, 4
+ a = a + i
+ end do
+end subroutine
+
+! CHECK-LABEL: omp.declare_reduction @{{.*}}myinit_byref_box_heap_i32 : !fir.ref<!fir.box<!fir.heap<i32>>>
+! CHECK: init {
+! CHECK: ^bb0(%[[MOLD:.*]]: !fir.ref<!fir.box<!fir.heap<i32>>>, %[[ALLOC:.*]]: !fir.ref<!fir.box<!fir.heap<i32>>>):
+! The original element is read (omp_orig), not replaced by a constant zero.
+! CHECK: %[[MBOX:.*]] = fir.load %[[MOLD]]
+! CHECK: %[[MADDR:.*]] = fir.box_addr %[[MBOX]]
+! CHECK: fir.load %[[MADDR]]
+! CHECK: omp.yield
+! CHECK: } combiner {
+! CHECK: fir.box_addr
+! CHECK: arith.addi
+! CHECK: fir.store
+! CHECK: omp.yield
+! CHECK: } cleanup {
+! CHECK: fir.freemem
+! CHECK: omp.yield
+
+! CHECK-LABEL: func.func @_QPtest_udr_orig_init
+! CHECK: omp.wsloop {{.*}} reduction(byref @{{.*}}myinit_byref_box_heap_i32 %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.heap<i32>>>)
diff --git a/flang/test/Lower/OpenMP/declare-reduction-allocatable-real.f90 b/flang/test/Lower/OpenMP/declare-reduction-allocatable-real.f90
new file mode 100644
index 0000000000000..3236e1f62ffd0
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-allocatable-real.f90
@@ -0,0 +1,50 @@
+! real, allocatable UDR with initializer(omp_priv = omp_orig) must lower without
+! the f32 FloatAttr crash the old fabricated-zero fallback caused.
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=45 %s -o - | FileCheck %s
+
+subroutine test_udr_real_allocatable()
+ implicit none
+ integer :: i
+ real, allocatable :: a
+
+ !$omp declare reduction (rmax : real : omp_out = max(omp_out, omp_in)) &
+ !$omp & initializer (omp_priv = omp_orig)
+
+ allocate(a)
+ a = 0.0
+
+ !$omp parallel do reduction(rmax : a)
+ do i = 1, 4
+ a = max(a, real(i))
+ end do
+end subroutine
+
+! CHECK-LABEL: omp.declare_reduction @{{.*}}rmax_byref_box_heap_f32 : !fir.ref<!fir.box<!fir.heap<f32>>>
+! CHECK-SAME: attributes {byref_element_type = f32}
+! CHECK: alloc {
+! CHECK: fir.alloca !fir.box<!fir.heap<f32>>
+! CHECK: omp.yield
+! CHECK: } init {
+! CHECK: ^bb0(%[[MOLD:.*]]: !fir.ref<!fir.box<!fir.heap<f32>>>, %[[ALLOC:.*]]: !fir.ref<!fir.box<!fir.heap<f32>>>):
+! The original f32 element is read (omp_orig), not a fabricated f32 constant.
+! CHECK: %[[MBOX:.*]] = fir.load %[[MOLD]]
+! CHECK: %[[MADDR:.*]] = fir.box_addr %[[MBOX]]
+! CHECK: fir.load %[[MADDR]] : !fir.heap<f32>
+! CHECK: omp.yield
+! CHECK: } combiner {
+! CHECK: ^bb0(%[[ARG0:.*]]: !fir.ref<!fir.box<!fir.heap<f32>>>, %[[ARG1:.*]]: !fir.ref<!fir.box<!fir.heap<f32>>>):
+! CHECK: %[[LHS_BOX:.*]] = fir.load %[[ARG0]]
+! CHECK: %[[RHS_BOX:.*]] = fir.load %[[ARG1]]
+! CHECK: %[[LHS_ADDR:.*]] = fir.box_addr %[[LHS_BOX]]
+! CHECK: %[[RHS_ADDR:.*]] = fir.box_addr %[[RHS_BOX]]
+! CHECK: fir.load %[[LHS_ADDR]]
+! CHECK: fir.load %[[RHS_ADDR]]
+! CHECK: fir.store %{{.*}} to %[[LHS_ADDR]]
+! CHECK: omp.yield(%[[ARG0]] : !fir.ref<!fir.box<!fir.heap<f32>>>)
+! CHECK: } cleanup {
+! CHECK: fir.freemem
+! CHECK: omp.yield
+
+! CHECK-LABEL: func.func @_QPtest_udr_real_allocatable
+! CHECK: omp.wsloop {{.*}} reduction(byref @{{.*}}rmax_byref_box_heap_f32 %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.heap<f32>>>)
diff --git a/flang/test/Lower/OpenMP/declare-reduction-allocatable.f90 b/flang/test/Lower/OpenMP/declare-reduction-allocatable.f90
new file mode 100644
index 0000000000000..89390812ada90
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-allocatable.f90
@@ -0,0 +1,125 @@
+! User-defined reduction on allocatable scalar/1-D/2-D integer variables must
+! lower to boxed by-ref ops.
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=45 %s -o - | FileCheck %s
+
+subroutine test_udr_allocatable()
+ implicit none
+ integer :: i
+ integer, allocatable :: a, b(:), c(:,:)
+
+ !$omp declare reduction (foo : integer : omp_out = omp_out + omp_in) &
+ !$omp & initializer (omp_priv = 0)
+
+ allocate(a, b(4), c(3,2))
+ a = 0
+ b = 0
+ c = 0
+
+ !$omp parallel do reduction(foo : a)
+ do i = 1, 10
+ a = a + i
+ end do
+
+ !$omp parallel do reduction(foo : b)
+ do i = 1, 10
+ b = b + i
+ end do
+
+ !$omp parallel do reduction(foo : c)
+ do i = 1, 10
+ c = c + i
+ end do
+end subroutine
+
+! The 2-D allocatable: boxed by-ref op, iterated combiner, cleanup freemem.
+! CHECK-LABEL: omp.declare_reduction @{{.*}}foo_byref_box_heap_UxUxi32 : !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>
+! CHECK-SAME: attributes {byref_element_type = !fir.array<?x?xi32>}
+! CHECK: alloc {
+! CHECK: fir.alloca !fir.box<!fir.heap<!fir.array<?x?xi32>>>
+! CHECK: omp.yield
+! CHECK: } init {
+! CHECK: arith.constant 0 : i32
+! CHECK: omp.yield
+! CHECK: } combiner {
+! CHECK: ^bb0(%[[ARG0_2D:.*]]: !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>, %[[ARG1_2D:.*]]: !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>):
+! CHECK: %[[LHS_BOX_2D:.*]] = fir.load %[[ARG0_2D]]
+! CHECK: %[[RHS_BOX_2D:.*]] = fir.load %[[ARG1_2D]]
+! CHECK: fir.shape_shift
+! CHECK: fir.do_loop {{.*}} unordered {
+! CHECK: fir.do_loop {{.*}} unordered {
+! CHECK: fir.array_coor %[[LHS_BOX_2D]]
+! CHECK: fir.array_coor %[[RHS_BOX_2D]]
+! CHECK: arith.addi
+! CHECK: fir.store
+! CHECK: }
+! CHECK: }
+! CHECK: omp.yield(%[[ARG0_2D]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>)
+! CHECK: } cleanup {
+! CHECK: fir.freemem
+! CHECK: omp.yield
+
+! The 1-D allocatable.
+! CHECK-LABEL: omp.declare_reduction @{{.*}}foo_byref_box_heap_Uxi32 : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>
+! CHECK-SAME: attributes {byref_element_type = !fir.array<?xi32>}
+! CHECK: alloc {
+! CHECK: fir.alloca !fir.box<!fir.heap<!fir.array<?xi32>>>
+! CHECK: omp.yield
+! CHECK: } init {
+! CHECK: arith.constant 0 : i32
+! CHECK: omp.yield
+! CHECK: } combiner {
+! CHECK: ^bb0(%[[ARG0_1D:.*]]: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>, %[[ARG1_1D:.*]]: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>):
+! CHECK: %[[LHS_BOX_1D:.*]] = fir.load %[[ARG0_1D]]
+! CHECK: %[[RHS_BOX_1D:.*]] = fir.load %[[ARG1_1D]]
+! CHECK: fir.shape_shift
+! CHECK: fir.do_loop {{.*}} unordered {
+! CHECK: fir.array_coor %[[LHS_BOX_1D]]
+! CHECK: fir.array_coor %[[RHS_BOX_1D]]
+! CHECK: arith.addi
+! CHECK: fir.store
+! CHECK: }
+! CHECK: omp.yield(%[[ARG0_1D]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+! CHECK: } cleanup {
+! CHECK: fir.freemem
+! CHECK: omp.yield
+
+! The scalar allocatable.
+! CHECK-LABEL: omp.declare_reduction @{{.*}}foo_byref_box_heap_i32 : !fir.ref<!fir.box<!fir.heap<i32>>>
+! CHECK-SAME: attributes {byref_element_type = i32}
+! CHECK: alloc {
+! CHECK: fir.alloca !fir.box<!fir.heap<i32>>
+! CHECK: omp.yield
+! CHECK: } init {
+! CHECK: arith.constant 0 : i32
+! CHECK: omp.yield
+! CHECK: } combiner {
+! CHECK: ^bb0(%[[ARG0_S:.*]]: !fir.ref<!fir.box<!fir.heap<i32>>>, %[[ARG1_S:.*]]: !fir.ref<!fir.box<!fir.heap<i32>>>):
+! CHECK: %[[LHS_BOX_S:.*]] = fir.load %[[ARG0_S]]
+! CHECK: %[[RHS_BOX_S:.*]] = fir.load %[[ARG1_S]]
+! CHECK: %[[LHS_ADDR_S:.*]] = fir.box_addr %[[LHS_BOX_S]]
+! CHECK: %[[RHS_ADDR_S:.*]] = fir.box_addr %[[RHS_BOX_S]]
+! CHECK: fir.load %[[LHS_ADDR_S]]
+! CHECK: fir.load %[[RHS_ADDR_S]]
+! CHECK: arith.addi
+! CHECK: fir.store %{{.*}} to %[[LHS_ADDR_S]]
+! CHECK: omp.yield(%[[ARG0_S]] : !fir.ref<!fir.box<!fir.heap<i32>>>)
+! CHECK: } cleanup {
+! CHECK: fir.freemem
+! CHECK: omp.yield
+
+! The base element-typed by-value op that the boxed ops wrap.
+! CHECK-LABEL: omp.declare_reduction @{{.*}}foo_i32 : i32
+! CHECK: init {
+! CHECK: %[[C0_BASE:.*]] = arith.constant 0 : i32
+! CHECK: omp.yield(%[[C0_BASE]] : i32)
+! CHECK: } combiner {
+! CHECK: ^bb0(%[[LHS_BASE:.*]]: i32, %[[RHS_BASE:.*]]: i32):
+! CHECK: arith.addi
+! CHECK: omp.yield
+
+! Each loop binds the boxed op matching its variable's shape.
+! CHECK-LABEL: func.func @_QPtest_udr_allocatable
+! CHECK: omp.wsloop {{.*}} reduction(byref @{{.*}}foo_byref_box_heap_i32 %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.heap<i32>>>)
+! CHECK: omp.wsloop {{.*}} reduction(byref @{{.*}}foo_byref_box_heap_Uxi32 %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>)
+! CHECK: omp.wsloop {{.*}} reduction(byref @{{.*}}foo_byref_box_heap_UxUxi32 %{{.*}} -> %{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xi32>>>>)
More information about the flang-commits
mailing list