[flang-commits] [flang] [flang][OpenMP] Lower multi-type user-defined declare reduction (PR #207494)

Tom Eccles via flang-commits flang-commits at lists.llvm.org
Mon Jul 13 03:55:15 PDT 2026


================
@@ -819,52 +825,87 @@ bool ReductionProcessor::processReductionArguments(
                                  omp::clause::ReductionOperatorList>) {
       const Fortran::lower::omp::clause::ReductionOperator &redOperator =
           redOperatorList.front();
+      // Name user-defined reduction ops from the canonical element type, not
+      // the raw lowered variable type. An allocatable/pointer variable lowers
+      // to a boxed reference (!fir.ref<!fir.box<!fir.heap<!fir.char<1>>>>), but
+      // the directive names its op from the declared element type
+      // (!fir.char<1>); getReductionName unwraps only one reference level, so
+      // without stripping the ref/box/heap/pointer wrappers the by-ref suffix
+      // diverges and a valid allocatable-character reduction is wrongly
+      // rejected. Keep any array fir::SequenceType. Only naming and the type
+      // check use namingType; redType stays intact for op binding.
+      mlir::Type namingType =
+          fir::unwrapPassByRefType(fir::unwrapRefType(redType));
+      // An allocatable/pointer reduction of a trivial element type is a case
+      // the directive does not materialize: it emits the scalar by-value op,
+      // and binding the boxed by-ref operand to it is invalid IR. In the
+      // default mode the by-ref name suffix diverges (boxed clause by-ref,
+      // scalar op by-value), the lookup misses, and it is a clean TODO. Under
+      // -mmlir
+      // --force-byref-reduction both sides are forced by-ref, the names match,
+      // and the element-only type check (i32 == i32) would bind the box to the
+      // scalar op. Guard on the inherent triviality of the element type, not
+      // doReductionByRef (which the flag forces), so it is a clean TODO in
+      // every mode; boxed character and derived reductions (genuinely by-ref)
+      // still bind. Deferred to flang PR #186765.
+      const bool isBoxedTrivialReduction =
+          mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(redType)) &&
+          fir::isa_trivial(namingType);
       if (const auto &redDefinedOp =
               std::get_if<omp::clause::DefinedOperator>(&redOperator.u)) {
         if (const auto *definedOpName =
                 std::get_if<omp::clause::DefinedOperator::DefinedOpName>(
                     &redDefinedOp->u)) {
           // User-defined operator reduction (e.g. reduction(.myop.:x)). Resolve
-          // the use-site operator to its reduction symbol, which semantics
-          // names "op<spelling>" (MangleDefinedOperator in resolve-names), in
-          // the current scope, then reference the omp.declare_reduction op the
-          // directive materialized for it. Only a locally-declared,
-          // single-declaration, single-type reduction whose type the variable
-          // supports is handled here; anything else (imported, renamed, merged,
-          // or multiple declarations/types) is a clean TODO rather than a crash
-          // or a wrong binding.
+          // the use-site operator to the source reduction symbol as the OpenMP
+          // semantic checks do (following USE association, operator renames,
+          // private visibility, and merged generics), then reference the
+          // omp.declare_reduction op named from that symbol's scoped (name,
+          // owner). Reductions in different source modules have distinct owner
+          // scopes, so a same-spelling/same-type operator reduction imported
+          // from two modules yields two distinct ops, each clause binding its
+          // own combiner instead of colliding onto one. The variable's type
+          // selects the matching per-type op name (getScopedUserReductionName
+          // appends the type), so a multiple-declaration/multiple-type operator
+          // is handled (one op per type). An operator with no reduction for the
+          // variable's type, or a variable with no type, is a clean TODO rather
+          // than a crash or a wrong binding.
           const semantics::Symbol *opSym = definedOpName->v.sym();
-          std::string mangledName = "op" + opSym->name().ToString();
-          const semantics::Symbol *redSym =
-              converter.getCurrentScope().FindSymbol(
-                  parser::CharBlock{mangledName});
+          const semantics::DeclTypeSpec *varType =
+              reductionSymbols[idx]->GetUltimate().GetType();
+          // The variable's type disambiguates an operator that carries
+          // reductions for several types (e.g. a generic merged from multiple
+          // single-type modules): the resolver returns only a reduction that
+          // supports varType.
+          const semantics::Symbol *resolvedSym =
+              semantics::omp::FindOperatorUserReductionSymbol(*opSym, varType);
+          // resolvedSym is the found symbol, which may be a USE-associated
+          // wrapper; take its ultimate before reading the reduction details.
           const semantics::Symbol *ultimate =
-              redSym ? &redSym->GetUltimate() : nullptr;
+              resolvedSym ? &resolvedSym->GetUltimate() : nullptr;
           const semantics::UserReductionDetails *userDetails =
               ultimate ? ultimate->detailsIf<semantics::UserReductionDetails>()
                        : nullptr;
-          const semantics::DeclTypeSpec *varType =
-              reductionSymbols[idx]->GetUltimate().GetType();
-          if (!redSym || ultimate != redSym || !userDetails ||
-              userDetails->GetDeclList().size() != 1 ||
-              userDetails->GetTypeList().size() != 1 || !varType ||
-              !userDetails->SupportsType(*varType)) {
+          if (!varType || !resolvedSym || !userDetails) {
             TODO(currentLocation,
                  "OpenMP user-defined operator reduction is not yet supported "
-                 "for imported, renamed, or multiple-declaration/type "
-                 "reductions");
+                 "when the variable has no type or no matching reduction is "
+                 "visible for its type");
----------------
tblah wrote:

I guess this wasn't introduced by your patch but I wonder whether this is a genuine TODO or just a bug in semantic checking? Maybe it is a case for `fir::emitFatalError`?

https://github.com/llvm/llvm-project/pull/207494


More information about the flang-commits mailing list