[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 10:49:12 PDT 2026
https://github.com/MattPD updated https://github.com/llvm/llvm-project/pull/195751
>From 8eb38be9c39120c4e684106528af6c09ee26d31a 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 and lower the full assignment
(both LHS and RHS), correctly handling component designators, function
calls on the RHS, and user-defined assignment.
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