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

via flang-commits flang-commits at lists.llvm.org
Mon Jul 27 22:07:25 PDT 2026


================
@@ -2521,6 +2543,239 @@ 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.
+          if (isExplicit) {
+            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};
+        // An omitted directive is implicit NOTHING and must not gain rank from
+        // the runtime condition. Explicit NOTHING remains a normal variant.
+        if (!isExplicit && hasMatchAny && !isStaticVMIApplicable) {
+          rankingVMI = llvm::omp::VariantMatchInfo();
+        } else if (isExplicit) {
+          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<const parser::OmpDirectiveSpecification *, 4>
+GetReachableMetadirectiveVariants(const MetadirectiveCandidateSet &candidateSet,
----------------
MattPD wrote:

`GetReachableMetadirectiveVariants` treats each non-constant `condition()` arm as independently selectable, so two arms guarded by the same expression both stay reachable. A lower-ranked loop variant that can never be selected then still contributes its constraints.

The case I hit is `unreachable_ranked_iteration_variable`: with `condition(score(2): .true.)` and `condition(score(1): .true.)` it passes, and changing both `.true.` to a runtime `flag` turns it into `error: The DO loop iteration variable must be of integer type`. The constant-condition case is the behaviour I would expect. The runtime-condition case looks like a false rejection, since `do` is unreachable either way. The same shape also produces the collapse-depth error, the interrupted-association error, and the enclosing-data-environment TODO.

Every runtime-selection test in `metadirective-loop.f90` uses a constant condition, so this path is currently unexercised. Would you prefer to fold identical guards, or to hold back fatal errors for a variant whose selection depends on a runtime condition?

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


More information about the flang-commits mailing list