[flang-commits] [flang] 504fc83 - [flang][OpenMP] Fix user-defined reduction shadowing an intrinsic name (#205893)
via flang-commits
flang-commits at lists.llvm.org
Thu Jul 2 10:02:35 PDT 2026
Author: Carlos Seo
Date: 2026-07-02T14:02:30-03:00
New Revision: 504fc83820b4fa5cf2edd86a5f4661d4dd310933
URL: https://github.com/llvm/llvm-project/commit/504fc83820b4fa5cf2edd86a5f4661d4dd310933
DIFF: https://github.com/llvm/llvm-project/commit/504fc83820b4fa5cf2edd86a5f4661d4dd310933.diff
LOG: [flang][OpenMP] Fix user-defined reduction shadowing an intrinsic name (#205893)
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.
This is handled the same way as for 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
Added:
flang/test/Lower/OpenMP/declare-reduction-logical-init.f90
flang/test/Lower/OpenMP/declare-reduction-shadows-intrinsic-other-type.f90
flang/test/Lower/OpenMP/declare-reduction-shadows-intrinsic.f90
Modified:
flang/lib/Lower/OpenMP/ClauseProcessor.cpp
flang/lib/Lower/Support/ReductionProcessor.cpp
Removed:
################################################################################
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 13b5be88751e6..ee88a24f97263 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -657,6 +657,16 @@ bool ClauseProcessor::processInitializer(
exprResult.getType()))
if (ompPrivVar.getType() == refType)
exprResult = fir::LoadOp::create(builder, loc, exprResult);
+ // The initializer expression may have a
diff erent 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