[flang-commits] [flang] [flang][OpenMP] Fix component-level initializer in declare reduction (PR #195751)

via flang-commits flang-commits at lists.llvm.org
Fri May 8 09:10:50 PDT 2026


https://github.com/MattPD updated https://github.com/llvm/llvm-project/pull/195751

>From 5b58755cb0b9537fba7df19955bf2f26fe9d9d4e Mon Sep 17 00:00:00 2001
From: "Matt P. Dziubinski" <matt-p.dziubinski at hpe.com>
Date: Sat, 2 May 2026 12:31:13 -0500
Subject: [PATCH] [flang][OpenMP] Fix component-level initializer in declare
 reduction

When a declare reduction initializer uses a component assignment such as
`initializer(omp_priv%member = 0)`, the lowering would store the scalar
RHS value (i32) directly to the whole derived-type reference, causing a
FIR verification error:
  "fir.store op store value type must match memory reference type"

The root cause is that MakeEvaluateExpr extracts only the RHS expression
from the AssignmentStmt, discarding the LHS component information. The
lowering callback then returns this scalar value which gets stored to the
wrong type.

Fix this by mirroring the approach already used for combiner expressions:
pass the parser-level OmpStylizedInstance to processInitializer so the
callback can access the typed assignment. When the init value type does
not match the reduction variable type (indicating a component-level
assignment), use the assignment LHS to lower a proper hlfir.designate +
hlfir.assign to the correct component.

Fixes #184927 (with-initializer part; the without-initializer case
remains unsupported).

Assisted-by: Claude Opus 4.6.
---
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    | 55 +++++++++++++++--
 flang/lib/Lower/OpenMP/ClauseProcessor.h      |  3 +-
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 20 ++++++-
 .../OpenMP/declare-reduction-finalizer.f90    |  3 +-
 ...eclare-reduction-initializer-component.f90 | 41 +++++++++++++
 ...e-reduction-initializer-defined-assign.f90 | 60 +++++++++++++++++++
 ...declare-reduction-initializer-rhs-call.f90 | 52 ++++++++++++++++
 7 files changed, 225 insertions(+), 9 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/declare-reduction-initializer-component.f90
 create mode 100644 flang/test/Lower/OpenMP/declare-reduction-initializer-defined-assign.f90
 create mode 100644 flang/test/Lower/OpenMP/declare-reduction-initializer-rhs-call.f90

diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 1c39e90a922cf..5f5b4fe77f701 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -523,12 +523,26 @@ bool ClauseProcessor::processInclusive(
 }
 
 bool ClauseProcessor::processInitializer(
-    lower::SymMap &symMap,
-    ReductionProcessor::GenInitValueCBTy &genInitValueCB) const {
+    lower::SymMap &symMap, ReductionProcessor::GenInitValueCBTy &genInitValueCB,
+    const parser::OmpStylizedInstance *parserInitInstance) const {
   if (auto *clause = findUniqueClause<omp::clause::Initializer>()) {
-    genInitValueCB = [&, clause](fir::FirOpBuilder &builder, mlir::Location loc,
-                                 mlir::Type type, mlir::Value moldArg,
-                                 mlir::Value privArg) {
+    // Extract the typed assignment from the parser-level instance, if
+    // the initializer is an assignment statement (as opposed to a call).
+    const evaluate::Assignment *assign = nullptr;
+    if (parserInitInstance) {
+      const auto &instance = std::get<parser::OmpStylizedInstance::Instance>(
+          parserInitInstance->t);
+      if (const auto *assignStmt =
+              std::get_if<parser::AssignmentStmt>(&instance.u)) {
+        if (auto *wrapper = assignStmt->typedAssignment.get())
+          if (wrapper->v)
+            assign = &*wrapper->v;
+      }
+    }
+    genInitValueCB = [&, clause, assign](fir::FirOpBuilder &builder,
+                                         mlir::Location loc, mlir::Type type,
+                                         mlir::Value moldArg,
+                                         mlir::Value privArg) {
       lower::SymMapScope scope(symMap);
       mlir::Value ompPrivVar;
       const StylizedInstance &inst = clause->v.front();
@@ -592,6 +606,37 @@ bool ClauseProcessor::processInitializer(
                 return privVal;
               },
               [&](const auto &expr) -> mlir::Value {
+                // For by-ref reductions with a typed assignment, lower
+                // the full assignment (both LHS and RHS) directly.
+                // This handles both whole-variable (omp_priv = val) and
+                // component-level (omp_priv%member = val) initializers.
+                // Mirror the combiner pattern: dispatch on assign->u to
+                // handle both intrinsic and user-defined assignment.
+                if (privArg && assign) {
+                  lower::StatementContext assignCtx;
+                  hlfir::Entity rhs = lower::convertExprToHLFIR(
+                      loc, converter, assign->rhs, symMap, assignCtx);
+                  rhs = hlfir::loadTrivialScalar(loc, builder, rhs);
+                  hlfir::Entity lhs = lower::convertExprToHLFIR(
+                      loc, converter, assign->lhs, symMap, assignCtx);
+                  common::visit(
+                      common::visitors{
+                          [&](const evaluate::Assignment::Intrinsic &) {
+                            hlfir::AssignOp::create(builder, loc, rhs, lhs);
+                          },
+                          [&](const evaluate::ProcedureRef &procRef) {
+                            lower::convertUserDefinedAssignmentToHLFIR(
+                                loc, converter, procRef, lhs, rhs, symMap);
+                          },
+                          [&](const auto &) {
+                            llvm_unreachable("Unexpected assignment type in "
+                                             "reduction initializer");
+                          },
+                      },
+                      assign->u);
+                  assignCtx.finalizeAndPop();
+                  return mlir::Value{};
+                }
                 mlir::Value exprResult = fir::getBase(convertExprToValue(
                     loc, converter, initExpr, symMap, stmtCtx));
                 if (auto refType = llvm::dyn_cast<fir::ReferenceType>(
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index acf1068efb987..e138b4df30b71 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.h
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.h
@@ -97,7 +97,8 @@ class ClauseProcessor {
                         mlir::omp::InclusiveClauseOps &result) const;
   bool processInitializer(
       lower::SymMap &symMap,
-      ReductionProcessor::GenInitValueCBTy &genInitValueCB) const;
+      ReductionProcessor::GenInitValueCBTy &genInitValueCB,
+      const parser::OmpStylizedInstance *parserInitInstance = nullptr) const;
   bool processMergeable(mlir::omp::MergeableClauseOps &result) const;
   bool processNogroup(mlir::omp::NogroupClauseOps &result) const;
   bool processNotinbranch(mlir::omp::NotinbranchClauseOps &result) const;
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 88d28cf94b045..0ffc7bdae85b9 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4215,6 +4215,18 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
   assert(combinerExpr && "Expecting combiner expression");
   auto parserInstIt = combinerExpr->v.begin();
 
+  // Get the parser-level initializer expression (if present) so we can
+  // pass each parser::OmpStylizedInstance to processInitializer.
+  const parser::OmpInitializerExpression *initExpr = nullptr;
+  for (const auto &clause : construct.v.Clauses().v) {
+    initExpr = parser::omp::GetInitializerExpr(clause);
+    if (initExpr)
+      break;
+  }
+  auto parserInitInstIt =
+      initExpr ? initExpr->v.begin()
+               : std::list<parser::OmpStylizedInstance>::const_iterator{};
+
   for (const auto &typeSpec : typeNameList.v) {
     (void)typeSpec; // Currently unused
 
@@ -4257,7 +4269,13 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                                  parserInst);
     ReductionProcessor::GenInitValueCBTy genInitValueCB;
     ClauseProcessor cp(converter, semaCtx, clauses);
-    cp.processInitializer(symTable, genInitValueCB);
+    const parser::OmpStylizedInstance *parserInitInst = nullptr;
+    if (initExpr) {
+      assert(parserInitInstIt != initExpr->v.end() &&
+             "Mismatched initializer instance count");
+      parserInitInst = &*parserInitInstIt++;
+    }
+    cp.processInitializer(symTable, genInitValueCB, parserInitInst);
     mlir::Type redType =
         isByRef
             ? static_cast<mlir::Type>(fir::ReferenceType::get(reductionType))
diff --git a/flang/test/Lower/OpenMP/declare-reduction-finalizer.f90 b/flang/test/Lower/OpenMP/declare-reduction-finalizer.f90
index 2ec34c446e793..22a653179ce2d 100644
--- a/flang/test/Lower/OpenMP/declare-reduction-finalizer.f90
+++ b/flang/test/Lower/OpenMP/declare-reduction-finalizer.f90
@@ -39,8 +39,7 @@ end module m1
 ! CHECK:          %[[PRIV_DECL:.*]]:2 = hlfir.declare %[[INIT_ARG1]] {uniq_name = "omp_priv"}
 ! CHECK:          %[[INIT_ADDR:.*]] = fir.address_of(@_QQro._QMm1Tt.0)
 ! CHECK:          %[[INIT_DECL:.*]]:2 = hlfir.declare %[[INIT_ADDR]]
-! CHECK:          %[[INIT_VAL:.*]] = fir.load %[[INIT_DECL]]#0
-! CHECK:          fir.store %[[INIT_VAL]] to %[[INIT_ARG1]]
+! CHECK:          hlfir.assign %[[INIT_DECL]]#0 to %[[PRIV_DECL]]#0
 ! CHECK:          omp.yield(%[[INIT_ARG1]] :
 !
 ! -- combiner region
diff --git a/flang/test/Lower/OpenMP/declare-reduction-initializer-component.f90 b/flang/test/Lower/OpenMP/declare-reduction-initializer-component.f90
new file mode 100644
index 0000000000000..b42fa610d17e0
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-initializer-component.f90
@@ -0,0 +1,41 @@
+! Test component-level initializer in declare reduction for derived types.
+! Verifies that `initializer(omp_priv%member = 0)` correctly lowers to
+! a component designate + assign (hlfir.designate + hlfir.assign), rather
+! than storing the scalar directly to the whole derived-type reference.
+!
+! This is a regression test for https://github.com/llvm/llvm-project/issues/184927
+
+!RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
+
+subroutine test_component_init()
+  implicit none
+  type :: t
+     integer :: member
+  end type t
+  integer :: i
+  !$omp declare reduction(add_member : t : &
+  !$omp&   omp_out%member = omp_out%member + omp_in%member) &
+  !$omp&   initializer(omp_priv%member = 0)
+  type(t) :: x
+  x%member = 0
+  !$omp parallel do reduction(add_member : x) num_threads(2)
+  do i = 1, 10
+     x%member = x%member + 1
+  end do
+  !$omp end parallel do
+end subroutine
+
+!CHECK: omp.declare_reduction @add_member : !fir.ref<!fir.type<_QFtest_component_initTt{member:i32}>>
+!CHECK-SAME: alloc {
+!CHECK:   %[[ALLOCA:.*]] = fir.alloca !fir.type<_QFtest_component_initTt{member:i32}>
+!CHECK:   omp.yield(%[[ALLOCA]] : !fir.ref<!fir.type<_QFtest_component_initTt{member:i32}>>)
+!CHECK: } init {
+!CHECK: ^bb0(%[[INIT_ARG0:.*]]: !fir.ref<!fir.type<_QFtest_component_initTt{member:i32}>>,
+!CHECK-SAME: %[[INIT_ARG1:.*]]: !fir.ref<!fir.type<_QFtest_component_initTt{member:i32}>>):
+!CHECK:   %[[OMP_ORIG:.*]]:2 = hlfir.declare %[[INIT_ARG0]] {uniq_name = "omp_orig"}
+!CHECK:   %[[OMP_PRIV:.*]]:2 = hlfir.declare %[[INIT_ARG1]] {uniq_name = "omp_priv"}
+!CHECK:   %[[ZERO:.*]] = arith.constant 0 : i32
+!CHECK:   %[[MEMBER:.*]] = hlfir.designate %[[OMP_PRIV]]#0{"member"} : (!fir.ref<!fir.type<_QFtest_component_initTt{member:i32}>>) -> !fir.ref<i32>
+!CHECK:   hlfir.assign %[[ZERO]] to %[[MEMBER]] : i32, !fir.ref<i32>
+!CHECK:   omp.yield(%[[INIT_ARG1]] : !fir.ref<!fir.type<_QFtest_component_initTt{member:i32}>>)
+!CHECK: } combiner {
diff --git a/flang/test/Lower/OpenMP/declare-reduction-initializer-defined-assign.f90 b/flang/test/Lower/OpenMP/declare-reduction-initializer-defined-assign.f90
new file mode 100644
index 0000000000000..bdf48626fd2b3
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-initializer-defined-assign.f90
@@ -0,0 +1,60 @@
+! Test user-defined assignment in declare reduction initializer.
+! Verifies that `initializer(omp_priv = t(1))` correctly dispatches to the
+! user-defined `assignment(=)` subroutine, not intrinsic assignment.
+!
+! This is a regression test for https://github.com/llvm/llvm-project/issues/184927
+
+!RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
+
+module m_defined_assign
+  implicit none
+  type :: t
+     integer :: val = -999
+  end type t
+
+  interface assignment(=)
+     module procedure custom_assign
+  end interface
+
+contains
+  subroutine custom_assign(lhs, rhs)
+    type(t), intent(out) :: lhs
+    type(t), intent(in)  :: rhs
+    lhs%val = rhs%val * 10
+  end subroutine
+end module
+
+subroutine test_defined_assign_init()
+  use m_defined_assign
+  implicit none
+  integer :: i
+  type(t) :: x
+
+  !$omp declare reduction(add_t : t : omp_out%val = omp_out%val + omp_in%val) &
+  !$omp&   initializer(omp_priv = t(1))
+
+  x = t(0)
+  !$omp parallel do reduction(add_t : x) num_threads(2)
+  do i = 1, 2
+     x%val = x%val + 1
+  end do
+  !$omp end parallel do
+end subroutine
+
+!CHECK: omp.declare_reduction @add_t :
+!CHECK-SAME: alloc {
+!CHECK:   %[[ALLOCA:.*]] = fir.alloca
+!CHECK:   omp.yield(%[[ALLOCA]] :
+!CHECK: } init {
+!CHECK: ^bb0(%[[INIT_ARG0:.*]]: !fir.ref<!fir.type<_QMm_defined_assignTt{val:i32}>>,
+!CHECK-SAME: %[[INIT_ARG1:.*]]: !fir.ref<!fir.type<_QMm_defined_assignTt{val:i32}>>):
+!CHECK:   %[[OMP_ORIG:.*]]:2 = hlfir.declare %[[INIT_ARG0]] {uniq_name = "omp_orig"}
+!CHECK:   %[[OMP_PRIV:.*]]:2 = hlfir.declare %[[INIT_ARG1]] {uniq_name = "omp_priv"}
+!CHECK:   %[[INIT_ADDR:.*]] = fir.address_of(@_QQro._QMm_defined_assignTt.0)
+!CHECK:   %[[INIT_DECL:.*]]:2 = hlfir.declare %[[INIT_ADDR]]
+!CHECK:   %[[AS_EXPR:.*]] = hlfir.as_expr %[[INIT_DECL]]#0
+!CHECK:   %[[ASSOC:.*]]:3 = hlfir.associate %[[AS_EXPR]] {adapt.valuebyref}
+!CHECK:   fir.call @_QMm_defined_assignPcustom_assign(%[[OMP_PRIV]]#0, %[[ASSOC]]#0)
+!CHECK:   hlfir.end_associate %[[ASSOC]]#1, %[[ASSOC]]#2
+!CHECK:   omp.yield(%[[INIT_ARG1]] :
+!CHECK: } combiner {
diff --git a/flang/test/Lower/OpenMP/declare-reduction-initializer-rhs-call.f90 b/flang/test/Lower/OpenMP/declare-reduction-initializer-rhs-call.f90
new file mode 100644
index 0000000000000..7d409b27464e2
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-initializer-rhs-call.f90
@@ -0,0 +1,52 @@
+! Test that a function call on the RHS of a component-level initializer in
+! declare reduction is correctly lowered through the assignment path (not
+! the ProcedureRef/subroutine path). Verifies that the init region contains
+! a call to the function, followed by a component designate and assign.
+!
+! This is a regression test for https://github.com/llvm/llvm-project/issues/184927
+
+!RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
+
+module m
+  implicit none
+  type :: t
+     integer :: member
+  end type t
+contains
+  function init_val() result(res)
+    integer :: res
+    res = 42
+  end function
+end module
+
+subroutine test_rhs_call()
+  use m
+  implicit none
+  integer :: i
+  type(t) :: x
+
+  !$omp declare reduction(add_t : t : omp_out%member = omp_out%member + omp_in%member) &
+  !$omp&   initializer(omp_priv%member = init_val())
+
+  x%member = 0
+  !$omp parallel do reduction(add_t : x) num_threads(2)
+  do i = 1, 2
+     x%member = x%member + 1
+  end do
+  !$omp end parallel do
+end subroutine
+
+!CHECK: omp.declare_reduction @add_t :
+!CHECK-SAME: alloc {
+!CHECK:   %[[ALLOCA:.*]] = fir.alloca !fir.type<_QMmTt{member:i32}>
+!CHECK:   omp.yield(%[[ALLOCA]] :
+!CHECK: } init {
+!CHECK: ^bb0(%[[INIT_ARG0:.*]]: !fir.ref<!fir.type<_QMmTt{member:i32}>>,
+!CHECK-SAME: %[[INIT_ARG1:.*]]: !fir.ref<!fir.type<_QMmTt{member:i32}>>):
+!CHECK:   %[[OMP_ORIG:.*]]:2 = hlfir.declare %[[INIT_ARG0]] {uniq_name = "omp_orig"}
+!CHECK:   %[[OMP_PRIV:.*]]:2 = hlfir.declare %[[INIT_ARG1]] {uniq_name = "omp_priv"}
+!CHECK:   %[[CALL:.*]] = fir.call @_QMmPinit_val() {{.*}} : () -> i32
+!CHECK:   %[[MEMBER:.*]] = hlfir.designate %[[OMP_PRIV]]#0{"member"} : (!fir.ref<!fir.type<_QMmTt{member:i32}>>) -> !fir.ref<i32>
+!CHECK:   hlfir.assign %[[CALL]] to %[[MEMBER]] : i32, !fir.ref<i32>
+!CHECK:   omp.yield(%[[INIT_ARG1]] : !fir.ref<!fir.type<_QMmTt{member:i32}>>)
+!CHECK: } combiner {



More information about the flang-commits mailing list