[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