[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


================
@@ -6078,3 +6129,105 @@ void Fortran::lower::materializeOpenMPDeclareMappers(
     }
   }
 }
+
+// Walk scopes and materialize omp.declare_reduction ops for user-defined
+// operator reductions imported from modules. If scope is null, start from the
+// global scope. Mirrors materializeOpenMPDeclareMappers: gating on mod-file
+// module scopes restricts this to separate compilation (a same-file module's op
+// is emitted by the primary translation pass).
+void Fortran::lower::materializeOpenMPDeclareReductions(
+    Fortran::lower::AbstractConverter &converter,
+    semantics::SemanticsContext &semaCtx, const semantics::Scope *scope) {
+  const semantics::Scope &root = scope ? *scope : semaCtx.globalScope();
+
+  // Recurse into child scopes first (modules, submodules, etc.).
+  for (const semantics::Scope &child : root.children())
+    materializeOpenMPDeclareReductions(converter, semaCtx, &child);
+
+  // Only consider module scopes to avoid duplicating local constructs.
+  if (!root.IsModule())
+    return;
+
+  // Only materialize for modules coming from mod files to avoid duplicates.
+  if (!root.symbol() || !root.symbol()->test(semantics::Symbol::Flag::ModFile))
+    return;
+
+  // Scan symbols in this module scope for UserReductionDetails.
+  for (auto &it : root) {
+    const semantics::Symbol &sym = *it.second;
+    const semantics::Symbol &ultimate = sym.GetUltimate();
+    const auto *userDetails =
+        ultimate.detailsIf<semantics::UserReductionDetails>();
+    if (!userDetails)
+      continue;
+    // Skip without calling the impl: a consumer that merely uses this module
+    // must compile even when the reduction is out of the clause-side resolver's
+    // reach. Eagerly lowering a private-ultimate reduction would trip the
+    // impl's
+    // TODO and abort an unused import; a program that references it still gets
+    // the clause-side TODO. The impl's typeNameList loop materializes multiple
+    // declarations and types (one op per type); the per-declaration skips below
+    // guard the remaining unsupported shapes (combiner-in-clause and
+    // unsupported element types).
+    if (ultimate.attrs().test(semantics::Attr::PRIVATE))
+      continue;
+    for (const auto *decl : userDetails->GetDeclList()) {
+      if (const auto *reductionDecl =
+              std::get_if<parser::OmpDeclareReductionDirective>(&decl->u)) {
+        // An unused import must not abort the consumer, so skip a shape the
+        // impl cannot lower (a program that references it still gets the
+        // clause-side
+        // TODO), completing the mandatory skip above (which covers only
+        // private).
+        const auto &specifier =
+            DEREF(parser::omp::GetFirstArgument<parser::OmpReductionSpecifier>(
+                reductionDecl->v));
+        // The combiner-in-clause form (declare reduction(id:type)
+        // combiner(...), OpenMP 6.0) is not yet lowered even in the same-file
+        // path; computeReductionType and the impl both read the combiner from
+        // the specifier.
+        if (!std::get<std::optional<parser::OmpCombinerExpression>>(specifier.t)
+                 .has_value())
+          continue;
+        // A reduction type lowering does not yet support: mirror the impl's
+        // getReductionType check with the shared predicate. A single
+        // declaration may list several types (Form A), each with its own
+        // combiner instance; the impl emits one op per type in a single call,
+        // so skip the whole declaration if any listed type is unsupported
+        // (materializing only a subset is not expressible through the impl's
+        // all-types loop).
+        const parser::OmpCombinerExpression &combinerExpr =
+            std::get<std::optional<parser::OmpCombinerExpression>>(specifier.t)
+                .value();
+        bool anyUnsupportedType = false;
+        for (const parser::OmpStylizedInstance &combinerInst : combinerExpr.v) {
+          if (!isSimpleReductionType(
+                  computeReductionType(converter, combinerInst))) {
+            anyUnsupportedType = true;
----------------
tblah wrote:

Why not just generate the TODO? I'm worried that having this new interface that doesn't display the TODO is error-prone

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


More information about the flang-commits mailing list