[flang-commits] [flang] [flang][OpenMP] Fix user-defined reduction shadowing an intrinsic name (PR #205893)
Carlos Seo via flang-commits
flang-commits at lists.llvm.org
Tue Jun 30 18:03:03 PDT 2026
https://github.com/ceseo updated https://github.com/llvm/llvm-project/pull/205893
>From d110d2bc4443b195e0d4de8c24a6b447bdbf32e5 Mon Sep 17 00:00:00 2001
From: Carlos Seo <carlos.seo at linaro.org>
Date: Wed, 24 Jun 2026 21:38:38 -0300
Subject: [PATCH] [flang][OpenMP] Fix user-defined reduction shadowing an
intrinsic name
A user-defined reduction whose identifier is one of the intrinsic reduction
names (max, min, iand, ior, ieor) was mishandled during lowering, causing a
crash. This was due to two different problems:
1. Intrinsic shadowing. A reduction(max:var) clause resolves its identifier
to the intrinsic procedure rather than to the user's declaration, so
lowering applied the built-in reduction and ignored the declared one. For
integer variables this silently used the wrong combiner/initializer; for a
LOGICAL variable it crashed, because the built-in MAX/MIN initializer calls
getIntOrFloatBitWidth() on a fir.logical type.
Per OpenMP 6.0 7.6.14, a user-defined reduction has the same visibility and
accessibility as a variable declared at the same location, so a visible
declaration must take precedence over a same-named intrinsic. When the
identifier names an intrinsic reduction, first look for a user-defined
reduction declared in the current scope (semantics names it "op<name>" via
MangleSpecialFunctions); if one is visible and supports the variable's
type, bind the clause to the omp.declare_reduction op materialized for it.
The user declaration shadows the intrinsic only for the types it is
declared for: if the variable's type is not covered, the implicit intrinsic
reduction still applies, so lowering falls back to it, just like in
user-defined operator reductions.
TODO: imported, renamed, or multiple-declaration/type reductions are
currently not implemented.
2. LOGICAL initializer type. A user-defined reduction on a LOGICAL variable
with a logical-literal initializer (e.g. omp_priv = .false.) lowered the
literal to an i1 and yielded it from the init region, but the reduction
type is !fir.logical<4>. The omp.declare_reduction verifier then rejected
the op ("expects initializer region to yield a value of the reduction
type"). Convert the initializer result to the reduction type when they are
convertible trivial types, so the init region yields the reduction type.
Fixes #188880
---
flang/lib/Lower/OpenMP/ClauseProcessor.cpp | 10 ++++
.../lib/Lower/Support/ReductionProcessor.cpp | 50 +++++++++++++++++++
.../OpenMP/declare-reduction-logical-init.f90 | 26 ++++++++++
...reduction-shadows-intrinsic-other-type.f90 | 24 +++++++++
.../declare-reduction-shadows-intrinsic.f90 | 30 +++++++++++
5 files changed, 140 insertions(+)
create mode 100644 flang/test/Lower/OpenMP/declare-reduction-logical-init.f90
create mode 100644 flang/test/Lower/OpenMP/declare-reduction-shadows-intrinsic-other-type.f90
create mode 100644 flang/test/Lower/OpenMP/declare-reduction-shadows-intrinsic.f90
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index f1ccb64e3dfb3..38817aff2e899 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -661,6 +661,16 @@ bool ClauseProcessor::processInitializer(
exprResult.getType()))
if (ompPrivVar.getType() == refType)
exprResult = fir::LoadOp::create(builder, loc, exprResult);
+ // The initializer expression may have a different but
+ // convertible scalar type than the reduction. For example a
+ // LOGICAL initializer (e.g. omp_priv = .false.) lowers to an
+ // i1 while the reduction type is !fir.logical<4>. Convert so
+ // the init region yields the reduction type, as the
+ // omp.declare_reduction verifier requires.
+ if (exprResult.getType() != type &&
+ fir::isa_trivial(exprResult.getType()) &&
+ fir::isa_trivial(type))
+ exprResult = builder.createConvert(loc, type, exprResult);
return exprResult;
}},
initExpr.u);
diff --git a/flang/lib/Lower/Support/ReductionProcessor.cpp b/flang/lib/Lower/Support/ReductionProcessor.cpp
index d57aa48fd82b8..e1b4684bd8871 100644
--- a/flang/lib/Lower/Support/ReductionProcessor.cpp
+++ b/flang/lib/Lower/Support/ReductionProcessor.cpp
@@ -922,6 +922,56 @@ bool ReductionProcessor::processReductionArguments(
++idx;
continue;
}
+ // A user-defined reduction may shadow a built-in intrinsic reduction
+ // of the same name (max/min/iand/ior/ieor). Per the OpenMP spec, such
+ // reduction has the same visibility as a variable declared at the same
+ // location, so a visible declaration takes precedence over the
+ // intrinsic. Semantics names it "op<name>" (MangleSpecialFunctions in
+ // resolve-names). If one is visible in the current scope and supports
+ // the variable's type, bind to the omp.declare_reduction op the
+ // directive materialized for it instead of generating the intrinsic.
+ semantics::Symbol *sym = reductionIntrinsic->v.sym();
+ std::string mangledName = "op." + getRealName(sym).ToString();
+ if (const semantics::Symbol *redSym =
+ converter.getCurrentScope().FindSymbol(
+ parser::CharBlock{mangledName})) {
+ const semantics::Symbol &ultimate = redSym->GetUltimate();
+ const semantics::UserReductionDetails *userDetails =
+ ultimate.detailsIf<semantics::UserReductionDetails>();
+ const semantics::DeclTypeSpec *varType =
+ reductionSymbols[idx]->GetUltimate().GetType();
+ // A user-defined reduction shadows the intrinsic only for the types
+ // it is declared for. If it does not cover this variable's type, the
+ // user has not redefined the reduction for that type and the
+ // implicit intrinsic reduction still applies, so fall through to it.
+ if (userDetails && varType && userDetails->SupportsType(*varType)) {
+ // The user declaration takes precedence over the intrinsic for this
+ // type. Only a locally-declared, single-declaration, single-type
+ // reduction is currently supported.
+ if (&ultimate != redSym || userDetails->GetDeclList().size() != 1 ||
+ userDetails->GetTypeList().size() != 1) {
+ TODO(currentLocation,
+ "OpenMP user-defined reduction shadowing an intrinsic "
+ "reduction is not yet supported for imported, renamed, or "
+ "multiple-declaration/type reductions.");
+ }
+ std::string opName = ReductionProcessor::getScopedUserReductionName(
+ converter, ultimate);
+ mlir::ModuleOp module = builder.getModule();
+ auto existingDecl = module.lookupSymbol<OpType>(opName);
+ if (!existingDecl || fir::unwrapRefType(existingDecl.getType()) !=
+ fir::unwrapRefType(redType)) {
+ TODO(currentLocation,
+ "OpenMP user-defined reduction declaration was not "
+ "materialized for this type");
+ }
+ reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get(
+ builder.getContext(), existingDecl.getSymName()));
+ ++idx;
+ continue;
+ }
+ }
+
redId = getReductionType(*reductionIntrinsic);
reductionName =
getReductionName(getRealName(*reductionIntrinsic).ToString(),
diff --git a/flang/test/Lower/OpenMP/declare-reduction-logical-init.f90 b/flang/test/Lower/OpenMP/declare-reduction-logical-init.f90
new file mode 100644
index 0000000000000..c8c7bdaa48f13
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-logical-init.f90
@@ -0,0 +1,26 @@
+!RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
+
+! Test lowering of a user-defined reduction on a LOGICAL variable whose
+! initializer is a logical literal (omp_priv = .false.). The literal lowers to
+! an i1, but the reduction type is !fir.logical<4>; the init region must yield
+! the reduction type.
+
+subroutine test_logical(r)
+ logical :: r
+ integer :: i
+!$omp declare reduction(my_and:logical:omp_out=omp_in.and.omp_out) initializer(omp_priv=.false.)
+!$omp parallel do reduction(my_and:r)
+ do i=1,2
+ end do
+end subroutine
+
+! CHECK: omp.declare_reduction @[[RED:_QQFtest_logicalmy_and]] : !fir.logical<4> init {
+! CHECK: %[[FALSE:.*]] = arith.constant false
+! CHECK: %[[CONV:.*]] = fir.convert %[[FALSE]] : (i1) -> !fir.logical<4>
+! CHECK: omp.yield(%[[CONV]] : !fir.logical<4>)
+! CHECK: } combiner {
+! CHECK: fir.logical_and
+! CHECK: omp.yield(%{{.*}} : !fir.logical<4>)
+
+! CHECK-LABEL: func.func @_QPtest_logical
+! CHECK: omp.wsloop {{.*}}reduction(@[[RED]] %{{.*}} -> %{{.*}} : !fir.ref<!fir.logical<4>>)
diff --git a/flang/test/Lower/OpenMP/declare-reduction-shadows-intrinsic-other-type.f90 b/flang/test/Lower/OpenMP/declare-reduction-shadows-intrinsic-other-type.f90
new file mode 100644
index 0000000000000..bdd0429693e4b
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-shadows-intrinsic-other-type.f90
@@ -0,0 +1,24 @@
+!RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
+
+! A user-defined reduction shadows a same-named intrinsic reduction only for
+! the types it is declared for. Here "max" is declared for INTEGER, but the
+! reduction clause uses a REAL variable, for which no user-defined reduction
+! exists. The clause must therefore bind to the implicit intrinsic real max,
+! not to the (integer) user-defined reduction.
+
+subroutine test(rr, a)
+ real :: rr, a(10)
+ integer :: i
+!$omp declare reduction(max:integer:omp_out=omp_out+omp_in) initializer(omp_priv=0)
+ rr = 0.0
+!$omp parallel do reduction(max:rr)
+ do i=1,10
+ rr = max(rr, a(i))
+ end do
+end subroutine
+
+! The intrinsic real max reduction is used for the real variable.
+! CHECK: omp.declare_reduction @[[MAXF:max_f32]] : f32 init {
+
+! CHECK-LABEL: func.func @_QPtest
+! CHECK: omp.wsloop {{.*}}reduction(@[[MAXF]] %{{.*}} -> %{{.*}} : !fir.ref<f32>)
diff --git a/flang/test/Lower/OpenMP/declare-reduction-shadows-intrinsic.f90 b/flang/test/Lower/OpenMP/declare-reduction-shadows-intrinsic.f90
new file mode 100644
index 0000000000000..212909c242222
--- /dev/null
+++ b/flang/test/Lower/OpenMP/declare-reduction-shadows-intrinsic.f90
@@ -0,0 +1,30 @@
+!RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
+
+! Test that a user-defined reduction whose identifier is the same as an
+! intrinsic reduction (max/min/iand/ior/ieor) shadows the intrinsic: the
+! reduction clause must bind to the user-declared reduction, not to the
+! built-in one.
+
+subroutine test_max(x)
+ integer :: x(10), r, i
+!$omp declare reduction(max:integer:omp_out=omp_out+omp_in) initializer(omp_priv=0)
+ r = 0
+!$omp parallel do reduction(max:r)
+ do i=1,10
+ r = r + x(i)
+ end do
+end subroutine
+
+! The user-defined reduction must be materialized and its combiner must be the
+! user's addition (not the intrinsic max's select/compare). No intrinsic
+! @max_reduction / @max_i32 op should be generated.
+
+! CHECK-NOT: omp.declare_reduction @max
+! CHECK: omp.declare_reduction @[[RED:_QQFtest_maxop.max]] : i32 init {
+! CHECK: combiner {
+! CHECK: arith.addi
+! CHECK: omp.yield
+
+! CHECK-LABEL: func.func @_QPtest_max
+! CHECK: omp.wsloop {{.*}}reduction(@[[RED]] %{{.*}} -> %{{.*}} : !fir.ref<i32>)
+! CHECK-NOT: omp.declare_reduction @max
More information about the flang-commits
mailing list