[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 &region,
+                      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