[flang-commits] [flang] [flang][OpenMP] Lower DO and SIMD variants in metadirectives (PR #210810)

via flang-commits flang-commits at lists.llvm.org
Wed Jul 29 21:49:46 PDT 2026


================
@@ -5924,125 +6180,99 @@ static void genMetadirective(lower::AbstractConverter &converter,
       case semantics::omp::UnsupportedSelectorFeature::None:
         break;
       }
+    }
+  }
 
-      llvm::omp::VariantMatchInfo rawVMI;
-      std::optional<semantics::omp::DynamicUserCondition> dynamicCond =
-          semantics::omp::MakeVariantMatchInfo(rawVMI, ctxSel, semaCtx);
-
-      if (dynamicCond) {
-        constexpr llvm::omp::TraitProperty dynamicConditionTrait =
-            llvm::omp::TraitProperty::user_condition_unknown;
-        constexpr llvm::omp::TraitProperty matchAnyTrait =
-            llvm::omp::TraitProperty::implementation_extension_match_any;
-        constexpr llvm::omp::TraitProperty matchNoneTrait =
-            llvm::omp::TraitProperty::implementation_extension_match_none;
-
-        // Static applicability must only use traits known at lowering time.
-        // For example, in
-        //   when(implementation={vendor(llvm)},
-        //        user={condition(score(5): flag)}: barrier)
-        // vendor(llvm) can be checked now, but flag cannot. Drop the
-        // runtime-only user_condition_unknown for applicability, while keeping
-        // score(5) so ranking can still honor the user-condition selector.
-        llvm::omp::VariantMatchInfo staticVMI = rawVMI;
-        std::optional<llvm::APInt> conditionScore;
-        auto scoreIt = staticVMI.ScoreMap.find(dynamicConditionTrait);
-        if (scoreIt != staticVMI.ScoreMap.end()) {
-          conditionScore = scoreIt->second;
-          staticVMI.ScoreMap.erase(scoreIt);
-        }
-        staticVMI.RequiredTraits.reset(unsigned(dynamicConditionTrait));
-        llvm::APInt *conditionScorePtr =
-            conditionScore ? &*conditionScore : nullptr;
-
-        bool hasMatchAny = rawVMI.RequiredTraits.test(unsigned(matchAnyTrait));
-        bool hasMatchNone =
-            rawVMI.RequiredTraits.test(unsigned(matchNoneTrait));
-        bool isStaticVMIApplicable =
-            llvm::omp::isVariantApplicableInContext(staticVMI, ompCtx);
-        // If staticVMI does not match, only match_any can still apply. Check
-        // conditionTrueVMI because the runtime condition may satisfy match_any.
-        if (!isStaticVMIApplicable) {
-          if (!hasMatchAny || staticVMI.RequiredTraits.test(
-                                  unsigned(llvm::omp::TraitProperty::invalid)))
-            continue;
-
-          llvm::omp::VariantMatchInfo conditionTrueVMI = staticVMI;
-          conditionTrueVMI.addTrait(
-              llvm::omp::TraitProperty::user_condition_true, "<condition>",
-              conditionScorePtr);
-          if (!llvm::omp::isVariantApplicableInContext(conditionTrueVMI,
-                                                       ompCtx))
-            continue;
-        }
+  std::optional<semantics::omp::MetadirectiveCandidateSet> candidateSet =
+      semantics::omp::BuildMetadirectiveCandidateSet(clauseList, semaCtx,
+                                                     ompCtx);
+  assert(candidateSet && "unsupported selector reached candidate planning");
+  auto &candidates = candidateSet->candidates;
+  const parser::OmpDirectiveSpecification *fallback = candidateSet->fallback;
 
-        auto addConditionTraitForRanking =
-            [&](llvm::omp::VariantMatchInfo &rankingVMI) {
-              rankingVMI.addTrait(
-                  hasMatchNone ? dynamicConditionTrait
-                               : llvm::omp::TraitProperty::user_condition_true,
-                  "<condition>", conditionScorePtr);
-            };
-
-        if (hasMatchAny && isStaticVMIApplicable) {
-          // A statically matched match_any selector needs two candidates: a
-          // guarded candidate with the user condition and score, and an
-          // unguarded candidate with only the statically matched traits. If the
-          // when clause omits its directive, only add the unguarded candidate.
-          if (isExplicit) {
-            llvm::omp::VariantMatchInfo conditionTrueVMI = staticVMI;
-            addConditionTraitForRanking(conditionTrueVMI);
-            candidates.emplace_back(spec, conditionTrueVMI, isExplicit,
-                                    dynamicCond);
-          }
-          candidates.emplace_back(spec, staticVMI, isExplicit);
-          continue;
-        }
+  llvm::SmallVector<unsigned, 4> allCandidateIndices;
+  allCandidateIndices.reserve(candidates.size());
+  for (unsigned idx = 0, end = candidates.size(); idx < end; ++idx)
+    allCandidateIndices.push_back(idx);
 
-        llvm::omp::VariantMatchInfo rankingVMI = staticVMI;
-        // An omitted directive is implicit nothing, so do not let the runtime
-        // condition raise its rank. Explicit `nothing` is still a variant.
-        if (!isExplicit && hasMatchAny && !isStaticVMIApplicable)
-          rankingVMI = llvm::omp::VariantMatchInfo();
-        else if (isExplicit)
-          addConditionTraitForRanking(rankingVMI);
-        candidates.emplace_back(spec, rankingVMI, isExplicit, dynamicCond,
-                                /*conditionShouldBeTrue=*/!hasMatchNone);
-        continue;
-      }
+  llvm::SmallVector<const parser::OmpDirectiveSpecification *, 4>
+      reachableVariantSpecs = semantics::omp::GetReachableMetadirectiveVariants(
+          *candidateSet, ompCtx, semaCtx);
 
-      if (!llvm::omp::isVariantApplicableInContext(rawVMI, ompCtx))
-        continue;
+  bool hasLoopAssociatedCandidate =
+      llvm::any_of(reachableVariantSpecs, [](const auto *spec) {
+        return spec && hasDirectiveAssociation(
+                           spec->DirId(), llvm::omp::Association::LoopNest);
+      });
+  SplicedAssociatedEvaluations splicedAssociatedEvaluations;
+  lower::pft::Evaluation *associatedLoopEval = nullptr;
+  llvm::scope_exit restoreEvaluationOwnership([&]() {
+    if (eval.hasNestedEvaluations())
+      splicedAssociatedEvaluations.restore(eval.getNestedEvaluations());
+  });
+  if (hasLoopAssociatedCandidate) {
+    if (lower::pft::Evaluation *loopEval =
+            spliceAssociatedDoEval(eval, &splicedAssociatedEvaluations)) {
+      associatedLoopEval = loopEval;
+      if (lower::pft::FunctionLikeUnit *owningProc =
+              eval.getOwningProcedure()) {
+        if (owningProc->getEntryEval() &&
+            splicedAssociatedEvaluations.getEntryBlock()) {
+          // Alternate ENTRY lowering starts with a branch. Emit selection in
+          // the detached associated block, which is either that branch's
+          // destination or unreachable for an ENTRY after the metadirective.
+          builder.setInsertionPointToStart(
+              splicedAssociatedEvaluations.getEntryBlock());
+        }
+      }
 
-      candidates.emplace_back(spec, rawVMI, isExplicit);
-    } else if (const auto *otherwiseClause =
-                   std::get_if<parser::OmpClause::Otherwise>(&clause.u)) {
-      if (otherwiseClause->v && otherwiseClause->v->v)
-        fallback = getFallbackVariant(otherwiseClause->v->v->value());
-    } else if (const auto *defaultVariantClause =
-                   std::get_if<parser::OmpClause::DefaultVariant>(&clause.u)) {
-      const auto &dirSpec = defaultVariantClause->v.v;
-      fallback = getFallbackVariant(dirSpec.value());
+      auto &nested = eval.getNestedEvaluations();
+      auto loopIt =
+          llvm::find_if(nested, [loopEval](lower::pft::Evaluation &e) {
+            return &e == loopEval;
+          });
+      assert(loopIt != nested.end() && "associated loop not nested");
+
+      // Attach compiler directives to the loop before any selected variant
+      // lowers it. Variant bodies skip them below to avoid processing them a
+      // second time.
+      for (auto it = nested.begin(); it != loopIt; ++it)
+        if (it->getIf<parser::CompilerDirective>())
+          converter.genEval(*it);
     }
   }
 
+  auto genMetadirectiveBody = [&]() {
+    for (lower::pft::Evaluation &nested : eval.getNestedEvaluations())
+      if (!hasLoopAssociatedCandidate ||
+          !nested.getIf<parser::CompilerDirective>())
+        converter.genEval(nested);
+  };
+
   // Lower a single resolved candidate.
   auto genVariant = [&](const parser::OmpDirectiveSpecification *spec) {
     if (!spec) {
-      genNestedEvaluations(converter, eval);
+      genMetadirectiveBody();
       return;
     }
-    List<Clause> variantClauses = makeClauses(spec->Clauses(), semaCtx);
     mlir::Location variantLoc = converter.genLocation(spec->source);
+    List<Clause> variantClauses = makeClauses(spec->Clauses(), semaCtx);
     ConstructQueue queue{
         buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
                             eval, spec->source, spec->DirId(), variantClauses)};
+    unsigned ompVersion{semaCtx.langOptions().OpenMPVersion};
+
+    // Eager privatization relies on variant-local host associations that name
+    // resolution cannot create for a metadirective replacement.
+    if (!enableDelayedPrivatization &&
+        hasEagerPrivatizationClause(queue, ompVersion))
----------------
MattPD wrote:

`firstprivate` needs the variant-local host association in delayed mode as well, so the `!enableDelayedPrivatization` condition leaves the default configuration aborting. This is `Todo/metadirective-block-eager-privatization.f90` with `private(x)` changed to `firstprivate(x)`:

```fortran
subroutine fp(x)
  integer :: x
  !$omp begin metadirective &
  !$omp & when(implementation={vendor(llvm)}: parallel firstprivate(x)) &
  !$omp & otherwise(nothing)
  x = x + 1
  !$omp end metadirective
end subroutine
```

Compiled with `%flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52` and no `-mmlir` flag, it aborts:

```
Bridge.cpp:1065: Assertion `sym.has<HostAssocDetails>() && "No host-association found"' failed.
```

Both new tests force eager privatization, so the suite stays green while the default configuration aborts: `Todo/metadirective-block-eager-privatization.f90` and `Todo/metadirective-block-eager-default-private.f90`.

A `copyin` clause aborts in both modes, at `ClauseProcessor.cpp:1175`, and this predicate does not reach it. It enumerates `Default` plus `isPrivatizingClause`, rather than the property the lowering depends on.

Would checking whether a clause needs a variant-local host association cover `firstprivate` and `copyin` together? A second RUN line without the `-mmlir` flag would then pin the default configuration.

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


More information about the flang-commits mailing list