[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


================
@@ -2521,6 +2543,263 @@ std::optional<DynamicUserCondition> MakeVariantMatchInfo(
   return dynamicCond;
 }
 
+std::optional<MetadirectiveCandidateSet> BuildMetadirectiveCandidateSet(
+    const parser::OmpClauseList &clauses, SemanticsContext &context,
+    const OmpVariantMatchContext &matchContext) {
+  MetadirectiveCandidateSet result;
+
+  auto getContextSelector = [](const parser::OmpClause::When &whenClause)
+      -> const parser::modifier::OmpContextSelector * {
+    const auto &modifiers{std::get<0>(whenClause.v.t)};
+    if (!modifiers || modifiers->size() != 1) {
+      return nullptr;
+    }
+    return std::get_if<parser::modifier::OmpContextSelector>(
+        &modifiers->front().u);
+  };
+
+  auto getDirectiveVariant = [](const parser::OmpClause::When &whenClause)
+      -> std::pair<const parser::OmpDirectiveSpecification *, bool> {
+    const auto &optionalSpec{std::get<1>(whenClause.v.t)};
+    if (!optionalSpec) {
+      return {nullptr, false};
+    }
+    if (optionalSpec->value().DirId() == llvm::omp::Directive::OMPD_nothing) {
+      return {nullptr, true};
+    }
+    return {&optionalSpec->value(), true};
+  };
+
+  auto getFallbackVariant = [](const parser::OmpDirectiveSpecification &spec) {
+    return spec.DirId() == llvm::omp::Directive::OMPD_nothing ? nullptr : &spec;
+  };
+
+  for (const parser::OmpClause &clause : clauses.v) {
+    if (const auto *whenClause{
+            std::get_if<parser::OmpClause::When>(&clause.u)}) {
+      const auto *ctxSel{getContextSelector(*whenClause)};
+      if (!ctxSel ||
+          FindUnsupportedSelectorFeature(*ctxSel, context) !=
+              UnsupportedSelectorFeature::None) {
+        return std::nullopt;
+      }
+
+      auto [spec, isExplicit]{getDirectiveVariant(*whenClause)};
+      llvm::omp::VariantMatchInfo rawVMI;
+      std::optional<DynamicUserCondition> dynamicCondition{
+          MakeVariantMatchInfo(rawVMI, *ctxSel, context)};
+      if (llvm::any_of(
+              rawVMI.ConstructTraits, [](llvm::omp::TraitProperty property) {
+                return llvm::omp::getOpenMPContextTraitSetForProperty(
+                           property) != llvm::omp::TraitSet::construct;
+              })) {
+        return std::nullopt;
+      }
+
+      if (dynamicCondition) {
+        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 uses only traits known at compile time. Keep
+        // the condition's score so a true runtime condition is still ranked
+        // correctly.
+        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, matchContext)};
+        // Only match_any can remain applicable when the static traits do not
+        // match, because a true runtime condition may satisfy the selector.
+        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, matchContext)) {
+            continue;
+          }
+        }
+
+        auto addConditionTraitForRanking =
+            [&](llvm::omp::VariantMatchInfo &rankingVMI) {
+              rankingVMI.addTrait(hasMatchNone
+                      ? dynamicConditionTrait
+                      : llvm::omp::TraitProperty::user_condition_true,
+                  "<condition>", conditionScorePtr);
+            };
+
+        if (hasMatchAny && isStaticVMIApplicable) {
+          // Represent both outcomes: a guarded candidate with the condition's
+          // score and an unguarded candidate with only the static traits.
+          llvm::omp::VariantMatchInfo conditionTrueVMI{staticVMI};
+          addConditionTraitForRanking(conditionTrueVMI);
+          result.candidates.push_back({spec, std::move(conditionTrueVMI),
+              isExplicit, dynamicCondition});
+          result.candidates.push_back({spec, std::move(staticVMI), isExplicit});
+          continue;
+        }
+
+        llvm::omp::VariantMatchInfo rankingVMI{staticVMI};
+        addConditionTraitForRanking(rankingVMI);
+        result.candidates.push_back({spec, std::move(rankingVMI), isExplicit,
+            dynamicCondition, /*conditionShouldBeTrue=*/!hasMatchNone});
+        continue;
+      }
+
+      if (!llvm::omp::isVariantApplicableInContext(rawVMI, matchContext)) {
+        continue;
+      }
+      result.candidates.push_back({spec, std::move(rawVMI), isExplicit});
+    } else if (const auto *otherwiseClause{
+                   std::get_if<parser::OmpClause::Otherwise>(&clause.u)}) {
+      if (otherwiseClause->v && otherwiseClause->v->v) {
+        result.fallback = getFallbackVariant(otherwiseClause->v->v->value());
+      }
+    } else if (const auto *defaultVariantClause{
+                   std::get_if<parser::OmpClause::DefaultVariant>(&clause.u)}) {
+      result.fallback = getFallbackVariant(defaultVariantClause->v.v.value());
+    }
+  }
+  return result;
+}
+
+std::optional<unsigned> SelectBestMetadirectiveCandidate(
+    llvm::ArrayRef<unsigned> candidateIndices,
+    llvm::ArrayRef<MetadirectiveCandidate> candidates,
+    const OmpVariantMatchContext &matchContext) {
+  if (candidateIndices.empty()) {
+    return std::nullopt;
+  }
+  if (candidateIndices.size() == 1) {
+    return candidateIndices.front();
+  }
+
+  // The context scorer preserves input order for ties. Explicit replacements
+  // take precedence over an omitted directive's implicit NOTHING.
+  llvm::SmallVector<unsigned, 4> candidateOrder;
+  candidateOrder.reserve(candidateIndices.size());
+  for (unsigned index : candidateIndices) {
+    if (candidates[index].isExplicit) {
+      candidateOrder.push_back(index);
+    }
+  }
+  for (unsigned index : candidateIndices) {
+    if (!candidates[index].isExplicit) {
+      candidateOrder.push_back(index);
+    }
+  }
+
+  llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs;
+  orderedVMIs.reserve(candidateOrder.size());
+  for (unsigned index : candidateOrder) {
+    orderedVMIs.push_back(candidates[index].vmi);
+  }
+
+  int bestIndex{
+      llvm::omp::getBestVariantMatchForContext(orderedVMIs, matchContext)};
+  if (bestIndex < 0) {
+    return std::nullopt;
+  }
+  CHECK(static_cast<std::size_t>(bestIndex) < candidateOrder.size());
+  return candidateOrder[bestIndex];
+}
+
+llvm::SmallVector<unsigned, 4> GetMetadirectiveElsePathCandidates(
+    unsigned selectedIndex, llvm::ArrayRef<unsigned> candidateIndices,
+    llvm::ArrayRef<MetadirectiveCandidate> candidates,
+    SemanticsContext &context) {
+  CHECK(selectedIndex < candidates.size());
+  const MetadirectiveCandidate &selected{candidates[selectedIndex]};
+  CHECK(selected.dynamicCondition);
+  const SomeExpr *selectedExpr{
+      GetExpr(context, *selected.dynamicCondition->expr)};
+
+  llvm::SmallVector<unsigned, 4> result;
+  result.reserve(candidateIndices.size());
+  for (unsigned index : candidateIndices) {
+    if (index == selectedIndex) {
+      continue;
+    }
+
+    const MetadirectiveCandidate &candidate{candidates[index]};
+    bool hasSameFailedCondition{candidate.dynamicCondition &&
+        candidate.conditionShouldBeTrue == selected.conditionShouldBeTrue};
+    if (hasSameFailedCondition) {
+      const SomeExpr *candidateExpr{
+          GetExpr(context, *candidate.dynamicCondition->expr)};
+      hasSameFailedCondition =
+          selectedExpr && candidateExpr && *selectedExpr == *candidateExpr;
----------------
MattPD wrote:

This equality test is the whole reachability boundary, and it is imprecise in both directions. Both cases are one-line edits to `test_unreachable_same_runtime_condition`.

Too wide: replacing both `flag` guards with a call to an impure `flip()` that returns false, then true.

```
%11 = fir.call @_QPflip() fastmath<contract> : () -> !fir.logical<4>
fir.if %12 {
  omp.barrier
} else {
}
fir.do_loop %arg2 = %c1_i32 to %13 step %c1_i32_0 : i32 {
```

Two guards in the source produce one call. The pruning treated the second guard as the same condition as the first, so it neither re-evaluated it nor kept its arm. The empty `else` is where the `do` arm would have gone, and the loop below it runs serially on the path where the worksharing arm was selectable.

Pruning is safe only when both occurrences of the condition are guaranteed to produce the same value. `evaluate::FindImpureCall`, in `Evaluate/tools.h`, rejects the impure case, though it does not by itself establish that stronger property.

Too narrow: writing the second guard as `(flag)` instead of `flag`. That emits the `omp.wsloop` the same test's `CHECK-NOT` forbids, which is the dead loop the test exists to prevent. Changing `i` to a real in that mutation turns the dead loop into `The DO loop iteration variable must be of integer type`, so the same imprecision also rejects valid code. `flag .or. flag` against `flag` behaves the same way. The header comment at `openmp-utils.h:300-302` says the pruning covers "an equivalent condition", and the test pins only the identical spelling, so nothing documents where the match stops.

The two directions want a single predicate: prune only on conditions guaranteed to repeat their value, and compare those after normalization. Widening the match on its own would make the impure case worse. Would you narrow and widen together in the same change?

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


More information about the flang-commits mailing list