[flang-commits] [flang] [flang][OpenMP] Support lowering of metadirective (part 1) (PR #193664)

via flang-commits flang-commits at lists.llvm.org
Fri May 1 12:38:18 PDT 2026


https://github.com/chichunchen updated https://github.com/llvm/llvm-project/pull/193664

>From b636ef7ef0ee954e89d2974d2c6a9ea98fe0ce83 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Mon, 27 Apr 2026 01:33:53 -0500
Subject: [PATCH 1/5] [flang][OpenMP] Support lowering of metadirective (part
 1)

This patch implements following feature in metadirective:
- implementation={vendor(...)}
- device={kind(...), isa(...), arch(...)}
- user={condition(<constant-expr>)}
- construct={parallel, target, teams}
- default, nothing, and otherwise clause

Dynamic user conditions, target_device, and loop-associated
variants are deferred to follow-up patches.

This patch is part of the feature work for #188820.

Assisted with copilot and GPT-5.4
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 187 ++++++++++++++-
 flang/lib/Lower/OpenMP/Utils.cpp              | 210 +++++++++++++++++
 flang/lib/Lower/OpenMP/Utils.h                |  14 ++
 .../OpenMP/Todo/metadirective-dynamic.f90     |  10 +
 .../Lower/OpenMP/Todo/metadirective-exec.f90  |   9 -
 .../Lower/OpenMP/Todo/metadirective-loop.f90  |  12 +
 .../Lower/OpenMP/Todo/metadirective-spec.f90  |   9 -
 .../Todo/metadirective-target-device.f90      |  10 +
 .../Lower/OpenMP/metadirective-construct.f90  |  30 +++
 .../Lower/OpenMP/metadirective-device-isa.f90 | 213 ++++++++++++++++++
 .../OpenMP/metadirective-device-kind.f90      |  21 ++
 .../OpenMP/metadirective-implementation.f90   | 121 ++++++++++
 .../test/Lower/OpenMP/metadirective-user.f90  |  33 +++
 13 files changed, 860 insertions(+), 19 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90
 delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-exec.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
 delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-spec.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-target-device.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-construct.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-device-isa.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-device-kind.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-implementation.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-user.f90

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 6859f5b291342..a2aa6ac75cbc2 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4399,11 +4399,196 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
   // support the case of threadprivate variable declared in module.
 }
 
+namespace {
+struct TargetOMPContext final : public llvm::omp::OMPContext {
+  TargetOMPContext(mlir::ModuleOp module,
+                   llvm::ArrayRef<llvm::omp::TraitProperty> constructTraits)
+      // DeviceNum is set to -1 (unknown) because the
+      // target_device={device_num()} selector is not yet supported. OMPContext
+      // uses DeviceNum > -1 to activate target_device traits from the offload
+      // triple; without a concrete device number those traits are left
+      // inactive.
+      : OMPContext(isDeviceCompilation(module), fir::getTargetTriple(module),
+                   getOffloadTargetTriple(module),
+                   /*DeviceNum=*/-1),
+        targetFeatures(fir::getTargetFeatures(module)) {
+    for (llvm::omp::TraitProperty trait : constructTraits)
+      addTrait(trait);
+  }
+
+  bool matchesISATrait(llvm::StringRef rawString) const override {
+    if (!targetFeatures || targetFeatures.nullOrEmpty())
+      return false;
+    return targetFeatures.contains(("+" + rawString).str());
+  }
+
+private:
+  static bool isDeviceCompilation(mlir::ModuleOp module) {
+    return llvm::cast<mlir::omp::OffloadModuleInterface>(*module.getOperation())
+        .getIsTargetDevice();
+  }
+
+  static llvm::Triple getOffloadTargetTriple(mlir::ModuleOp module) {
+    auto offloadMod =
+        llvm::cast<mlir::omp::OffloadModuleInterface>(*module.getOperation());
+    auto targetTriples = offloadMod.getTargetTriples();
+
+    if (!targetTriples.empty())
+      if (auto tripleAttr =
+              llvm::dyn_cast<mlir::StringAttr>(targetTriples.front()))
+        return llvm::Triple(tripleAttr.getValue());
+
+    return llvm::Triple();
+  }
+
+  mlir::LLVM::TargetFeaturesAttr targetFeatures;
+};
+} // namespace
+
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    semantics::SemanticsContext &semaCtx,
                    lower::pft::Evaluation &eval,
                    const parser::OmpMetadirectiveDirective &meta) {
-  TODO(converter.getCurrentLocation(), "METADIRECTIVE");
+  const parser::OmpClauseList &clauseList = meta.v.Clauses();
+  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+
+  llvm::SmallVector<llvm::omp::TraitProperty, 8> constructTraits;
+  for (auto *parentEval = eval.parentConstruct; parentEval;
+       parentEval = parentEval->parentConstruct) {
+    const auto *ompConstruct = parentEval->getIf<parser::OpenMPConstruct>();
+    if (!ompConstruct)
+      continue;
+    llvm::omp::Directive dir =
+        parser::omp::GetOmpDirectiveName(*ompConstruct).v;
+    // Record compound directives inside-out so the final reverse yields the
+    // outermost-to-innermost order.
+    if (llvm::omp::allSimdSet.test(dir))
+      constructTraits.push_back(llvm::omp::TraitProperty::construct_simd_simd);
+    if (llvm::omp::allDoSet.test(dir))
+      constructTraits.push_back(llvm::omp::TraitProperty::construct_for_for);
+    if (llvm::omp::allParallelSet.test(dir))
+      constructTraits.push_back(
+          llvm::omp::TraitProperty::construct_parallel_parallel);
+    if (llvm::omp::allTeamsSet.test(dir))
+      constructTraits.push_back(
+          llvm::omp::TraitProperty::construct_teams_teams);
+    if (llvm::omp::allTargetSet.test(dir))
+      constructTraits.push_back(
+          llvm::omp::TraitProperty::construct_target_target);
+  }
+  std::reverse(constructTraits.begin(), constructTraits.end());
+  TargetOMPContext ompCtx(builder.getModule(), constructTraits);
+
+  llvm::SmallVector<const parser::OmpDirectiveSpecification *> candidates;
+  llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> vmis;
+  // A null directive specification represents either the implicit `nothing`
+  // variant or the absence of an explicit otherwise/default clause.
+  const parser::OmpDirectiveSpecification *fallback = nullptr;
+
+  auto getContextSelector = [](const parser::OmpClause::When &whenClause)
+      -> const parser::modifier::OmpContextSelector * {
+    const auto &modifiers = std::get<0>(whenClause.v.t);
+    if (!modifiers)
+      return nullptr;
+    for (const auto &mod : *modifiers) {
+      if (const auto *ctxSel =
+              std::get_if<parser::modifier::OmpContextSelector>(&mod.u))
+        return ctxSel;
+    }
+    return nullptr;
+  };
+
+  auto getDirectiveVariant = [](const parser::OmpClause::When &whenClause)
+      -> const parser::OmpDirectiveSpecification * {
+    const auto &opt = std::get<1>(whenClause.v.t);
+    if (!opt || opt->value().DirId() == llvm::omp::Directive::OMPD_nothing)
+      return nullptr;
+    return &opt->value();
+  };
+
+  // Return the directive spec pointer, or nullptr for "nothing".
+  auto getFallbackVariant = [](const parser::OmpDirectiveSpecification &spec)
+      -> const parser::OmpDirectiveSpecification * {
+    if (spec.DirId() == llvm::omp::Directive::OMPD_nothing)
+      return nullptr;
+    return &spec;
+  };
+
+  for (const auto &clause : clauseList.v) {
+    if (const auto *whenClause =
+            std::get_if<parser::OmpClause::When>(&clause.u)) {
+      const auto *ctxSel = getContextSelector(*whenClause);
+      const auto *variant = getDirectiveVariant(*whenClause);
+
+      // Always match when there is no context selector.
+      if (!ctxSel) {
+        candidates.push_back(variant);
+        vmis.emplace_back();
+        continue;
+      }
+
+      llvm::omp::VariantMatchInfo vmi;
+      const parser::ScalarExpr *dynCondExpr = nullptr;
+      makeVariantMatchInfo(vmi, *ctxSel, semaCtx,
+                           converter.genLocation(clause.source), dynCondExpr);
+
+      if (dynCondExpr)
+        TODO(converter.genLocation(clause.source),
+             "dynamic user condition in METADIRECTIVE");
+
+      if (!llvm::omp::isVariantApplicableInContext(vmi, ompCtx))
+        continue;
+
+      candidates.push_back(variant);
+      vmis.push_back(vmi);
+    } 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 *defaultClause =
+                   std::get_if<parser::OmpClause::Default>(&clause.u)) {
+      if (const auto *dirSpecPtr = std::get_if<
+              common::Indirection<parser::OmpDirectiveSpecification>>(
+              &defaultClause->v.u))
+        fallback = getFallbackVariant(dirSpecPtr->value());
+    }
+  }
+
+  // Lower a single resolved candidate.
+  auto genVariant = [&](const parser::OmpDirectiveSpecification *spec) {
+    if (!spec) {
+      genNestedEvaluations(converter, eval);
+      return;
+    }
+    List<Clause> variantClauses = makeClauses(spec->Clauses(), semaCtx);
+    mlir::Location variantLoc = converter.genLocation(spec->source);
+    ConstructQueue queue{
+        buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
+                            eval, spec->source, spec->DirId(), variantClauses)};
+
+    if (llvm::any_of(queue, [](const auto &item) {
+          return llvm::omp::getDirectiveAssociation(item.id) ==
+                 llvm::omp::Association::LoopNest;
+        })) {
+      TODO(variantLoc, "loop-associated METADIRECTIVE variant");
+    }
+
+    genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
+                   queue.begin());
+  };
+
+  const parser::OmpDirectiveSpecification *selected = fallback;
+  if (candidates.size() == 1) {
+    selected = candidates.front();
+  } else if (!candidates.empty()) {
+    int bestIdx = llvm::omp::getBestVariantMatchForContext(vmis, ompCtx);
+    if (bestIdx >= 0) {
+      assert(static_cast<size_t>(bestIdx) < candidates.size() &&
+             "best variant index out of range");
+      selected = candidates[bestIdx];
+    }
+  }
+  genVariant(selected);
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index d65663dbebc53..3c906859c6976 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1121,6 +1121,216 @@ mlir::Value genIteratorCoordinate(Fortran::lower::AbstractConverter &converter,
                                   /*typeparams=*/mlir::ValueRange{});
 }
 
+llvm::omp::TraitSet mapTraitSet(parser::OmpTraitSetSelectorName::Value name) {
+  switch (name) {
+  case parser::OmpTraitSetSelectorName::Value::Construct:
+    return llvm::omp::TraitSet::construct;
+  case parser::OmpTraitSetSelectorName::Value::Device:
+    return llvm::omp::TraitSet::device;
+  case parser::OmpTraitSetSelectorName::Value::Implementation:
+    return llvm::omp::TraitSet::implementation;
+  case parser::OmpTraitSetSelectorName::Value::User:
+    return llvm::omp::TraitSet::user;
+  case parser::OmpTraitSetSelectorName::Value::Target_Device:
+    return llvm::omp::TraitSet::target_device;
+  }
+  llvm_unreachable("unknown trait set");
+}
+
+llvm::omp::TraitSelector
+mapTraitSelector(const parser::OmpTraitSelectorName &name,
+                 llvm::omp::TraitSet set) {
+  if (const auto *val =
+          std::get_if<parser::OmpTraitSelectorName::Value>(&name.u)) {
+    switch (*val) {
+    case parser::OmpTraitSelectorName::Value::Kind:
+      if (set == llvm::omp::TraitSet::target_device)
+        return llvm::omp::TraitSelector::target_device_kind;
+      return llvm::omp::TraitSelector::device_kind;
+    case parser::OmpTraitSelectorName::Value::Arch:
+      if (set == llvm::omp::TraitSet::target_device)
+        return llvm::omp::TraitSelector::target_device_arch;
+      return llvm::omp::TraitSelector::device_arch;
+    case parser::OmpTraitSelectorName::Value::Isa:
+      if (set == llvm::omp::TraitSet::target_device)
+        return llvm::omp::TraitSelector::target_device_isa;
+      return llvm::omp::TraitSelector::device_isa;
+    case parser::OmpTraitSelectorName::Value::Vendor:
+      return llvm::omp::TraitSelector::implementation_vendor;
+    case parser::OmpTraitSelectorName::Value::Extension:
+      return llvm::omp::TraitSelector::implementation_extension;
+    case parser::OmpTraitSelectorName::Value::Condition:
+      return llvm::omp::TraitSelector::user_condition;
+    case parser::OmpTraitSelectorName::Value::Atomic_Default_Mem_Order:
+    case parser::OmpTraitSelectorName::Value::Requires:
+    case parser::OmpTraitSelectorName::Value::Simd:
+    case parser::OmpTraitSelectorName::Value::Device_Num:
+    case parser::OmpTraitSelectorName::Value::Uid:
+      break;
+    }
+  }
+  // Construct traits, extension strings, and remaining selectors use
+  // string-based lookup.
+  return llvm::omp::getOpenMPContextTraitSelectorKind(name.ToString(), set);
+}
+
+/// Collect trait property names (vendor, kind, arch, isa, etc.) into a VMI.
+static void processTraitProperties(
+    llvm::omp::VariantMatchInfo &vmi, llvm::omp::TraitSet set,
+    llvm::omp::TraitSelector selector,
+    const std::optional<parser::OmpTraitSelector::Properties> &props,
+    llvm::APInt *scorePtr) {
+  if (!props)
+    return;
+
+  for (const auto &prop :
+       std::get<std::list<parser::OmpTraitProperty>>(props->t)) {
+    const auto *name = std::get_if<parser::OmpTraitPropertyName>(&prop.u);
+    if (!name)
+      continue;
+
+    llvm::omp::TraitProperty propKind =
+        llvm::omp::getOpenMPContextTraitPropertyKind(set, selector, name->v);
+    if (propKind != llvm::omp::TraitProperty::invalid) {
+      vmi.addTrait(set, propKind, name->v, scorePtr);
+      continue;
+    }
+
+    // Treat unknown properties as ISA-like raw strings.
+    vmi.addTrait(set, llvm::omp::TraitProperty::device_isa___ANY, name->v,
+                 scorePtr);
+  }
+}
+
+/// Try to constant-fold a user condition expression to a boolean.
+static std::optional<bool>
+evaluateUserCondition(semantics::SemanticsContext &semaCtx,
+                      const parser::ScalarExpr &scalarExpr) {
+  const auto *typedExpr = semantics::GetExpr(semaCtx, scalarExpr);
+  if (!typedExpr)
+    return std::nullopt;
+
+  auto foldedExpr = Fortran::evaluate::Fold(semaCtx.foldingContext(),
+                                            Fortran::common::Clone(*typedExpr));
+  if (auto constVal = Fortran::evaluate::ToInt64(foldedExpr))
+    return *constVal != 0;
+
+  if (auto logicalVal = Fortran::evaluate::GetScalarConstantValue<
+          Fortran::evaluate::LogicalResult>(foldedExpr))
+    return logicalVal->IsTrue();
+
+  return std::nullopt;
+}
+
+/// Process user={condition(...)} trait properties. Constant conditions are
+/// resolved to user_condition_true/false. Non-constant conditions are marked
+/// as user_condition_unknown and the expression pointer is returned via
+/// \p dynamicCondExpr.
+static void processUserConditionTrait(
+    llvm::omp::VariantMatchInfo &vmi,
+    const std::optional<parser::OmpTraitSelector::Properties> &props,
+    semantics::SemanticsContext &semaCtx,
+    const parser::ScalarExpr *&dynamicCondExpr) {
+  if (!props)
+    return;
+
+  for (const auto &prop :
+       std::get<std::list<parser::OmpTraitProperty>>(props->t)) {
+    const auto *scalarExpr = std::get_if<parser::ScalarExpr>(&prop.u);
+    if (!scalarExpr)
+      continue;
+
+    if (auto constValue = evaluateUserCondition(semaCtx, *scalarExpr)) {
+      vmi.addTrait(*constValue ? llvm::omp::TraitProperty::user_condition_true
+                               : llvm::omp::TraitProperty::user_condition_false,
+                   "<condition>");
+      continue;
+    }
+
+    dynamicCondExpr = scalarExpr;
+    vmi.addTrait(llvm::omp::TraitProperty::user_condition_unknown,
+                 "<condition>");
+  }
+}
+
+/// Extract the optional score value from trait properties.
+static llvm::APInt *
+getTraitScore(const std::optional<parser::OmpTraitSelector::Properties> &props,
+              semantics::SemanticsContext &semaCtx,
+              std::optional<llvm::APInt> &scoreStorage) {
+  if (!props)
+    return nullptr;
+
+  const auto &optScore =
+      std::get<std::optional<parser::OmpTraitScore>>(props->t);
+  if (!optScore)
+    return nullptr;
+
+  const auto *typedExpr = semantics::GetExpr(semaCtx, optScore->v);
+  if (!typedExpr)
+    return nullptr;
+
+  auto constVal = Fortran::evaluate::ToInt64(*typedExpr);
+  if (!constVal)
+    return nullptr;
+
+  scoreStorage = llvm::APInt(64, *constVal);
+  return &*scoreStorage;
+}
+
+/// Populate a VariantMatchInfo from context selector.
+/// For user conditions, attempts constant folding. Non-constant conditions
+/// are recorded as user_condition_unknown and the expression pointer is
+/// returned via \p dynamicCondExpr for later use in fir.if lowering.
+void makeVariantMatchInfo(llvm::omp::VariantMatchInfo &vmi,
+                          const parser::modifier::OmpContextSelector &ctxSel,
+                          semantics::SemanticsContext &semaCtx,
+                          mlir::Location loc,
+                          const parser::ScalarExpr *&dynamicCondExpr) {
+  dynamicCondExpr = nullptr;
+
+  for (const auto &traitSet : ctxSel.v) {
+    using TSSName = parser::OmpTraitSetSelectorName;
+    auto setName = std::get<TSSName>(traitSet.t).v;
+    llvm::omp::TraitSet set = mapTraitSet(setName);
+
+    for (const auto &trait :
+         std::get<std::list<parser::OmpTraitSelector>>(traitSet.t)) {
+      const auto &selectorName =
+          std::get<parser::OmpTraitSelectorName>(trait.t);
+      llvm::omp::TraitSelector selector = mapTraitSelector(selectorName, set);
+      const auto &props =
+          std::get<std::optional<parser::OmpTraitSelector::Properties>>(
+              trait.t);
+
+      // device_num requires runtime target device queries not yet supported.
+      if (const auto *val =
+              std::get_if<parser::OmpTraitSelectorName::Value>(&selectorName.u);
+          val && *val == parser::OmpTraitSelectorName::Value::Device_Num)
+        TODO(loc, "target_device={device_num()} selector in METADIRECTIVE");
+
+      if (selector == llvm::omp::TraitSelector::user_condition) {
+        processUserConditionTrait(vmi, props, semaCtx, dynamicCondExpr);
+        continue;
+      }
+
+      std::optional<llvm::APInt> score;
+      llvm::APInt *scorePtr = getTraitScore(props, semaCtx, score);
+
+      processTraitProperties(vmi, set, selector, props, scorePtr);
+
+      if (props || set != llvm::omp::TraitSet::construct)
+        continue;
+
+      // Construct traits with no properties: the selector is the property.
+      llvm::omp::TraitProperty propKind =
+          llvm::omp::getOpenMPContextTraitPropertyForSelector(selector);
+      if (propKind != llvm::omp::TraitProperty::invalid)
+        vmi.addTrait(set, propKind, selectorName.ToString(), scorePtr);
+    }
+  }
+}
+
 } // namespace omp
 } // namespace lower
 } // namespace Fortran
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 587a078c33ed8..eb784cc7911a5 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -14,6 +14,7 @@
 #include "mlir/Dialect/OpenMP/OpenMPDialect.h"
 #include "mlir/IR/Location.h"
 #include "mlir/IR/Value.h"
+#include "llvm/Frontend/OpenMP/OMPContext.h"
 #include "llvm/Support/CommandLine.h"
 #include <cstdint>
 #include <optional>
@@ -229,6 +230,19 @@ std::optional<llvm::SmallVector<mlir::Value>> getIteratorElementIndices(
     Fortran::lower::AbstractConverter &converter, const omp::Object &object,
     Fortran::lower::StatementContext &stmtCtx, mlir::Location loc);
 
+llvm::omp::TraitSet
+mapTraitSet(parser::OmpTraitSetSelectorName::Value flangSet);
+
+llvm::omp::TraitSelector
+mapTraitSelector(const parser::OmpTraitSelectorName &name,
+                 llvm::omp::TraitSet set);
+
+void makeVariantMatchInfo(llvm::omp::VariantMatchInfo &vmi,
+                          const parser::modifier::OmpContextSelector &ctxSel,
+                          semantics::SemanticsContext &semaCtx,
+                          mlir::Location loc,
+                          const parser::ScalarExpr *&dynamicCondExpr);
+
 } // namespace omp
 } // namespace lower
 } // namespace Fortran
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90
new file mode 100644
index 0000000000000..a9a02ece1db4b
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90
@@ -0,0 +1,10 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: dynamic user condition in METADIRECTIVE
+
+subroutine test_dynamic_user_condition(flag)
+  logical, intent(in) :: flag
+  !$omp metadirective &
+  !$omp & when(user={condition(flag)}: taskyield) &
+  !$omp & default(nothing)
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-exec.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-exec.f90
deleted file mode 100644
index 2e160a1896616..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-exec.f90
+++ /dev/null
@@ -1,9 +0,0 @@
-!RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-!RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-!CHECK: not yet implemented: METADIRECTIVE
-subroutine f00
-  continue
-  !Executable
-  !$omp metadirective when(user={condition(.true.)}: nothing)
-end
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
new file mode 100644
index 0000000000000..999a8c0839d15
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
@@ -0,0 +1,12 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: loop-associated METADIRECTIVE variant
+
+subroutine test_loop_variant()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-spec.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-spec.f90
deleted file mode 100644
index a00612a92218a..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-spec.f90
+++ /dev/null
@@ -1,9 +0,0 @@
-!RUN: %not_todo_cmd bbc -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-!RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-!CHECK: not yet implemented: METADIRECTIVE
-subroutine f00
-  !Specification
-  !$omp metadirective when(user={condition(.true.)}: nothing)
-  implicit none
-end
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-target-device.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-target-device.f90
new file mode 100644
index 0000000000000..d5269ea909fbc
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-target-device.f90
@@ -0,0 +1,10 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: target_device={device_num()} selector in METADIRECTIVE
+
+subroutine test_target_device_num()
+  continue
+  !$omp metadirective &
+  !$omp & when(target_device={device_num(0), kind(gpu)}: barrier) &
+  !$omp & default(nothing)
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-construct.f90 b/flang/test/Lower/OpenMP/metadirective-construct.f90
new file mode 100644
index 0000000000000..911f6e77a8a6f
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-construct.f90
@@ -0,0 +1,30 @@
+! Test lowering of OpenMP metadirective with construct selectors.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck %s
+
+! CHECK-LABEL: func.func @_QPtest_construct_parallel()
+! CHECK:         omp.parallel
+! CHECK:           omp.barrier
+! CHECK:           omp.terminator
+! CHECK:         return
+subroutine test_construct_parallel()
+  !$omp parallel
+    !$omp metadirective &
+    !$omp & when(construct={parallel}: barrier) &
+    !$omp & default(nothing)
+  !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_construct_no_match()
+! CHECK:         omp.parallel
+! CHECK-NOT:     omp.barrier
+! CHECK:         omp.taskyield
+! CHECK:         omp.terminator
+! CHECK:         return
+subroutine test_construct_no_match()
+  !$omp parallel
+    !$omp metadirective &
+    !$omp & when(construct={target}: barrier) &
+    !$omp & default(taskyield)
+  !$omp end parallel
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-device-isa.f90 b/flang/test/Lower/OpenMP/metadirective-device-isa.f90
new file mode 100644
index 0000000000000..b03c362c3ab9d
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-device-isa.f90
@@ -0,0 +1,213 @@
+! Test metadirective with device={isa(...)} trait selectors.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -target-feature +neon %s -o - | FileCheck --check-prefix=NEON %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -target-feature +neon -target-feature +sve %s -o - | FileCheck --check-prefix=SVE %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -target-feature +sse %s -o - | FileCheck --check-prefix=SSE %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -target-feature +avx %s -o - | FileCheck --check-prefix=AVX %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck --check-prefix=NONE %s
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -target-feature +neon %s -o - | FileCheck --check-prefix=NEON %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -target-feature +neon -target-feature +sve %s -o - | FileCheck --check-prefix=SVE %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -target-feature +sse %s -o - | FileCheck --check-prefix=SSE %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -target-feature +avx %s -o - | FileCheck --check-prefix=AVX %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | FileCheck --check-prefix=NONE %s
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -target-feature +neon %s -o - | FileCheck --check-prefix=NEON %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -target-feature +neon -target-feature +sve %s -o - | FileCheck --check-prefix=SVE %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -target-feature +sse %s -o - | FileCheck --check-prefix=SSE %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -target-feature +avx %s -o - | FileCheck --check-prefix=AVX %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 %s -o - | FileCheck --check-prefix=NONE %s
+
+! NEON-LABEL: func.func @_QPtest_isa_neon()
+! NEON:         omp.barrier
+! SVE-LABEL: func.func @_QPtest_isa_neon()
+! SVE:         omp.barrier
+! SSE-LABEL: func.func @_QPtest_isa_neon()
+! SSE-NOT:     omp.barrier
+! SSE:         return
+! AVX-LABEL: func.func @_QPtest_isa_neon()
+! AVX-NOT:     omp.barrier
+! AVX:         return
+! NONE-LABEL: func.func @_QPtest_isa_neon()
+! NONE-NOT:     omp.barrier
+! NONE:         return
+subroutine test_isa_neon()
+  !$omp metadirective &
+  !$omp & when(device={isa("neon")}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! NEON-LABEL: func.func @_QPtest_isa_sve()
+! NEON-NOT:     omp.barrier
+! NEON:         return
+! SVE-LABEL: func.func @_QPtest_isa_sve()
+! SVE:         omp.barrier
+! SSE-LABEL: func.func @_QPtest_isa_sve()
+! SSE-NOT:     omp.barrier
+! SSE:         return
+! AVX-LABEL: func.func @_QPtest_isa_sve()
+! AVX-NOT:     omp.barrier
+! AVX:         return
+! NONE-LABEL: func.func @_QPtest_isa_sve()
+! NONE-NOT:     omp.barrier
+! NONE:         return
+subroutine test_isa_sve()
+  !$omp metadirective &
+  !$omp & when(device={isa("sve")}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! NEON-LABEL: func.func @_QPtest_isa_sse()
+! NEON-NOT:     omp.barrier
+! NEON:         return
+! SVE-LABEL: func.func @_QPtest_isa_sse()
+! SVE-NOT:     omp.barrier
+! SVE:         return
+! SSE-LABEL: func.func @_QPtest_isa_sse()
+! SSE:         omp.barrier
+! AVX-LABEL: func.func @_QPtest_isa_sse()
+! AVX-NOT:     omp.barrier
+! AVX:         return
+! NONE-LABEL: func.func @_QPtest_isa_sse()
+! NONE-NOT:     omp.barrier
+! NONE:         return
+subroutine test_isa_sse()
+  !$omp metadirective &
+  !$omp & when(device={isa("sse")}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! NEON-LABEL: func.func @_QPtest_isa_avx()
+! NEON-NOT:     omp.barrier
+! NEON:         return
+! SVE-LABEL: func.func @_QPtest_isa_avx()
+! SVE-NOT:     omp.barrier
+! SVE:         return
+! SSE-LABEL: func.func @_QPtest_isa_avx()
+! SSE-NOT:     omp.barrier
+! SSE:         return
+! AVX-LABEL: func.func @_QPtest_isa_avx()
+! AVX:         omp.barrier
+! NONE-LABEL: func.func @_QPtest_isa_avx()
+! NONE-NOT:     omp.barrier
+! NONE:         return
+subroutine test_isa_avx()
+  !$omp metadirective &
+  !$omp & when(device={isa("avx")}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! NEON-LABEL: func.func @_QPtest_isa_multi_when()
+! NEON:         omp.barrier
+! NEON-NOT:     omp.taskwait
+! SVE-LABEL: func.func @_QPtest_isa_multi_when()
+! SVE:         omp.barrier
+! SVE-NOT:     omp.taskwait
+! SSE-LABEL: func.func @_QPtest_isa_multi_when()
+! SSE-NOT:     omp.barrier
+! SSE:         omp.taskwait
+! AVX-LABEL: func.func @_QPtest_isa_multi_when()
+! AVX-NOT:     omp.barrier
+! AVX-NOT:     omp.taskwait
+! AVX:         return
+! NONE-LABEL: func.func @_QPtest_isa_multi_when()
+! NONE-NOT:     omp.barrier
+! NONE-NOT:     omp.taskwait
+! NONE:         return
+subroutine test_isa_multi_when()
+  !$omp metadirective &
+  !$omp & when(device={isa("neon")}: barrier) &
+  !$omp & when(device={isa("sse")}: taskwait) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! NEON-LABEL: func.func @_QPtest_isa_no_match_default()
+! NEON:         omp.barrier
+! SVE-LABEL: func.func @_QPtest_isa_no_match_default()
+! SVE:         omp.barrier
+! SSE-LABEL: func.func @_QPtest_isa_no_match_default()
+! SSE:         omp.barrier
+! AVX-LABEL: func.func @_QPtest_isa_no_match_default()
+! AVX:         omp.barrier
+! NONE-LABEL: func.func @_QPtest_isa_no_match_default()
+! NONE:         omp.barrier
+subroutine test_isa_no_match_default()
+  !$omp metadirective &
+  !$omp & when(device={isa("sve2")}: taskwait) &
+#ifdef OMP_52
+  !$omp & otherwise(barrier)
+#else
+  !$omp & default(barrier)
+#endif
+end subroutine
+
+! Test device={arch()} selector. Arch properties without a known TraitProperty
+! mapping are matched against target features, like ISA traits.
+
+! NEON-LABEL: func.func @_QPtest_arch_neon()
+! NEON:         omp.barrier
+! SVE-LABEL: func.func @_QPtest_arch_neon()
+! SVE:         omp.barrier
+! SSE-LABEL: func.func @_QPtest_arch_neon()
+! SSE-NOT:     omp.barrier
+! SSE:         return
+! AVX-LABEL: func.func @_QPtest_arch_neon()
+! AVX-NOT:     omp.barrier
+! AVX:         return
+! NONE-LABEL: func.func @_QPtest_arch_neon()
+! NONE-NOT:     omp.barrier
+! NONE:         return
+subroutine test_arch_neon()
+  !$omp metadirective &
+  !$omp & when(device={arch("neon")}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! NEON-LABEL: func.func @_QPtest_arch_no_match()
+! NEON-NOT:     omp.barrier
+! NEON:         omp.taskwait
+! SVE-LABEL: func.func @_QPtest_arch_no_match()
+! SVE-NOT:     omp.barrier
+! SVE:         omp.taskwait
+! SSE-LABEL: func.func @_QPtest_arch_no_match()
+! SSE-NOT:     omp.barrier
+! SSE:         omp.taskwait
+! AVX-LABEL: func.func @_QPtest_arch_no_match()
+! AVX-NOT:     omp.barrier
+! AVX:         omp.taskwait
+! NONE-LABEL: func.func @_QPtest_arch_no_match()
+! NONE-NOT:     omp.barrier
+! NONE:         omp.taskwait
+subroutine test_arch_no_match()
+  !$omp metadirective &
+  !$omp & when(device={arch("unknown_arch")}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(taskwait)
+#else
+  !$omp & default(taskwait)
+#endif
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-device-kind.f90 b/flang/test/Lower/OpenMP/metadirective-device-kind.f90
new file mode 100644
index 0000000000000..f4f5aca61b3db
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-device-kind.f90
@@ -0,0 +1,21 @@
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck %s
+
+! CHECK-LABEL: func.func @_QPtest_device_kind_host()
+! CHECK:         omp.taskyield
+! CHECK:         return
+subroutine test_device_kind_host()
+  !$omp metadirective &
+  !$omp & when(device={kind(host)}: taskyield) &
+  !$omp & default(nothing)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_multiple_when_second_match()
+! CHECK-NOT:     omp.taskwait
+! CHECK:         omp.taskyield
+! CHECK:         return
+subroutine test_multiple_when_second_match()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor("amd")}: taskwait) &
+  !$omp & when(device={kind(host)}: taskyield) &
+  !$omp & default(nothing)
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-implementation.f90 b/flang/test/Lower/OpenMP/metadirective-implementation.f90
new file mode 100644
index 0000000000000..9e071f67c2e67
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-implementation.f90
@@ -0,0 +1,121 @@
+! Test lowering of OpenMP metadirective with implementation selectors.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 %s -o - | FileCheck %s
+
+! CHECK-LABEL: func.func @_QPtest_vendor_llvm()
+! CHECK:         omp.taskwait
+! CHECK:         return
+subroutine test_vendor_llvm()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: taskwait) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_vendor_no_match()
+! CHECK-NOT:     omp.taskwait
+! CHECK:         return
+subroutine test_vendor_no_match()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor("unknown")}: taskwait) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_standalone_barrier_match()
+! CHECK:         omp.barrier
+! CHECK:         return
+subroutine test_standalone_barrier_match()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_standalone_barrier_fallback()
+! CHECK:         omp.barrier
+! CHECK:         return
+subroutine test_standalone_barrier_fallback()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor("cray")}: nothing) &
+#ifdef OMP_52
+  !$omp & otherwise(barrier)
+#else
+  !$omp & default(barrier)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_nothing_variant()
+! CHECK-NOT:     omp.taskwait
+! CHECK:         return
+subroutine test_nothing_variant()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: nothing) &
+#ifdef OMP_52
+  !$omp & otherwise(taskwait)
+#else
+  !$omp & default(taskwait)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_default_fallback()
+! CHECK:         omp.taskwait
+! CHECK:         return
+subroutine test_default_fallback()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor("unknown")}: nothing) &
+#ifdef OMP_52
+  !$omp & otherwise(taskwait)
+#else
+  !$omp & default(taskwait)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_no_default()
+! CHECK-NOT:     omp.taskyield
+! CHECK:         return
+subroutine test_no_default()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor("gnu")}: taskyield)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_multiple_when_first_match()
+! CHECK:         omp.taskwait
+! CHECK-NOT:     omp.taskyield
+! CHECK:         return
+subroutine test_multiple_when_first_match()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: taskwait) &
+  !$omp & when(user={condition(.false.)}: taskyield) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_multiple_when_fallback()
+! CHECK-NOT:     omp.taskyield
+! CHECK:         omp.taskwait
+! CHECK:         return
+subroutine test_multiple_when_fallback()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor("nvidia")}: taskyield) &
+  !$omp & when(user={condition(.false.)}: taskyield) &
+#ifdef OMP_52
+  !$omp & otherwise(taskwait)
+#else
+  !$omp & default(taskwait)
+#endif
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
new file mode 100644
index 0000000000000..5165268cf7cc2
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -0,0 +1,33 @@
+! Test lowering of OpenMP metadirective with constant-folded user selectors.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 %s -o - | FileCheck %s
+
+! CHECK-LABEL: func.func @_QPtest_condition_true()
+! CHECK:         omp.taskyield
+! CHECK-NOT:     fir.if
+! CHECK:         return
+subroutine test_condition_true()
+  !$omp metadirective &
+  !$omp & when(user={condition(.true.)}: taskyield) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_condition_false()
+! CHECK-NOT:     omp.taskwait
+! CHECK-NOT:     fir.if
+! CHECK:         return
+subroutine test_condition_false()
+  !$omp metadirective &
+  !$omp & when(user={condition(.false.)}: taskwait) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine

>From db3ab6113a3e0dc57f714c1fb61a5bb079357eb6 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 28 Apr 2026 18:50:17 -0500
Subject: [PATCH 2/5] Support begin/end metadirective

---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 21 +++--
 .../Lower/OpenMP/metadirective-construct.f90  | 35 ++++++++
 .../Lower/OpenMP/metadirective-device-isa.f90 | 55 ++++++++++++
 .../OpenMP/metadirective-device-kind.f90      | 41 +++++++++
 .../OpenMP/metadirective-implementation.f90   | 89 +++++++++++++++++++
 .../test/Lower/OpenMP/metadirective-user.f90  | 36 ++++++++
 6 files changed, 270 insertions(+), 7 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index a2aa6ac75cbc2..05ac2534a7b4b 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4445,11 +4445,11 @@ struct TargetOMPContext final : public llvm::omp::OMPContext {
 };
 } // namespace
 
-static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
-                   semantics::SemanticsContext &semaCtx,
-                   lower::pft::Evaluation &eval,
-                   const parser::OmpMetadirectiveDirective &meta) {
-  const parser::OmpClauseList &clauseList = meta.v.Clauses();
+static void genMetadirective(lower::AbstractConverter &converter,
+                             lower::SymMap &symTable,
+                             semantics::SemanticsContext &semaCtx,
+                             lower::pft::Evaluation &eval,
+                             const parser::OmpClauseList &clauseList) {
   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
 
   llvm::SmallVector<llvm::omp::TraitProperty, 8> constructTraits;
@@ -4591,6 +4591,13 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
   genVariant(selected);
 }
 
+static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
+                   semantics::SemanticsContext &semaCtx,
+                   lower::pft::Evaluation &eval,
+                   const parser::OmpMetadirectiveDirective &meta) {
+  genMetadirective(converter, symTable, semaCtx, eval, meta.v.Clauses());
+}
+
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    semantics::SemanticsContext &semaCtx,
                    lower::pft::Evaluation &eval,
@@ -4733,8 +4740,8 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    semantics::SemanticsContext &semaCtx,
                    lower::pft::Evaluation &eval,
                    const parser::OmpDelimitedMetadirectiveDirective &meta) {
-  TODO(converter.getCurrentLocation(),
-       "OpenMP BEGIN/END METADIRECTIVE lowering");
+  genMetadirective(converter, symTable, semaCtx, eval,
+                   meta.BeginDir().Clauses());
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/test/Lower/OpenMP/metadirective-construct.f90 b/flang/test/Lower/OpenMP/metadirective-construct.f90
index 911f6e77a8a6f..2246119201c0c 100644
--- a/flang/test/Lower/OpenMP/metadirective-construct.f90
+++ b/flang/test/Lower/OpenMP/metadirective-construct.f90
@@ -28,3 +28,38 @@ subroutine test_construct_no_match()
     !$omp & default(taskyield)
   !$omp end parallel
 end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_construct_parallel()
+! CHECK:         omp.parallel
+! CHECK:           omp.parallel
+! CHECK:             omp.terminator
+! CHECK:           omp.terminator
+! CHECK:         return
+subroutine test_begin_construct_parallel()
+  integer :: x
+  x = 0
+  !$omp parallel
+    !$omp begin metadirective &
+    !$omp & when(construct={parallel}: parallel) &
+    !$omp & default(nothing)
+    x = 1
+    !$omp end metadirective
+  !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_construct_no_match()
+! CHECK:         omp.parallel
+! CHECK-NOT:     omp.task
+! CHECK:         omp.terminator
+! CHECK:         return
+subroutine test_begin_construct_no_match()
+  integer :: x
+  x = 0
+  !$omp parallel
+    !$omp begin metadirective &
+    !$omp & when(construct={target}: task) &
+    !$omp & default(nothing)
+    x = 1
+    !$omp end metadirective
+  !$omp end parallel
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-device-isa.f90 b/flang/test/Lower/OpenMP/metadirective-device-isa.f90
index b03c362c3ab9d..8f06e3379c835 100644
--- a/flang/test/Lower/OpenMP/metadirective-device-isa.f90
+++ b/flang/test/Lower/OpenMP/metadirective-device-isa.f90
@@ -211,3 +211,58 @@ subroutine test_arch_no_match()
   !$omp & default(taskwait)
 #endif
 end subroutine
+
+! NEON-LABEL: func.func @_QPtest_begin_isa_neon()
+! NEON:         omp.parallel
+! SVE-LABEL: func.func @_QPtest_begin_isa_neon()
+! SVE:         omp.parallel
+! SSE-LABEL: func.func @_QPtest_begin_isa_neon()
+! SSE-NOT:     omp.parallel
+! SSE:         return
+! AVX-LABEL: func.func @_QPtest_begin_isa_neon()
+! AVX-NOT:     omp.parallel
+! AVX:         return
+! NONE-LABEL: func.func @_QPtest_begin_isa_neon()
+! NONE-NOT:     omp.parallel
+! NONE:         return
+subroutine test_begin_isa_neon()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(device={isa("neon")}: parallel) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! NEON-LABEL: func.func @_QPtest_begin_isa_sse()
+! NEON-NOT:     omp.parallel
+! NEON:         return
+! SVE-LABEL: func.func @_QPtest_begin_isa_sse()
+! SVE-NOT:     omp.parallel
+! SVE:         return
+! SSE-LABEL: func.func @_QPtest_begin_isa_sse()
+! SSE:         omp.parallel
+! AVX-LABEL: func.func @_QPtest_begin_isa_sse()
+! AVX-NOT:     omp.parallel
+! AVX:         return
+! NONE-LABEL: func.func @_QPtest_begin_isa_sse()
+! NONE-NOT:     omp.parallel
+! NONE:         return
+subroutine test_begin_isa_sse()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(device={isa("sse")}: parallel) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-device-kind.f90 b/flang/test/Lower/OpenMP/metadirective-device-kind.f90
index f4f5aca61b3db..4c1b4e45818a7 100644
--- a/flang/test/Lower/OpenMP/metadirective-device-kind.f90
+++ b/flang/test/Lower/OpenMP/metadirective-device-kind.f90
@@ -19,3 +19,44 @@ subroutine test_multiple_when_second_match()
   !$omp & when(device={kind(host)}: taskyield) &
   !$omp & default(nothing)
 end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_device_kind_host()
+! CHECK:         omp.parallel
+! CHECK:         return
+subroutine test_begin_device_kind_host()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(device={kind(host)}: parallel) &
+  !$omp & default(nothing)
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_device_kind_nohost()
+! CHECK-NOT:     omp.parallel
+! CHECK:         return
+subroutine test_begin_device_kind_nohost()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(device={kind(nohost)}: parallel) &
+  !$omp & default(nothing)
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_multiple_when()
+! CHECK:         omp.parallel
+! CHECK-NOT:     omp.task
+! CHECK:         return
+subroutine test_begin_multiple_when()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor("amd")}: task) &
+  !$omp & when(device={kind(host)}: parallel) &
+  !$omp & default(nothing)
+  x = 1
+  !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-implementation.f90 b/flang/test/Lower/OpenMP/metadirective-implementation.f90
index 9e071f67c2e67..aa031192d8789 100644
--- a/flang/test/Lower/OpenMP/metadirective-implementation.f90
+++ b/flang/test/Lower/OpenMP/metadirective-implementation.f90
@@ -119,3 +119,92 @@ subroutine test_multiple_when_fallback()
   !$omp & default(taskwait)
 #endif
 end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_vendor_llvm()
+! CHECK:         omp.parallel
+! CHECK:         return
+subroutine test_begin_vendor_llvm()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_vendor_no_match()
+! CHECK-NOT:     omp.parallel
+! CHECK:         return
+subroutine test_begin_vendor_no_match()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor("unknown")}: parallel) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_nothing_variant()
+! CHECK-NOT:     omp.parallel
+! CHECK:         return
+subroutine test_begin_nothing_variant()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor(llvm)}: nothing) &
+#ifdef OMP_52
+  !$omp & otherwise(parallel)
+#else
+  !$omp & default(parallel)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_multiple_when_first_match()
+! CHECK:         omp.parallel
+! CHECK-NOT:     omp.task
+! CHECK:         return
+subroutine test_begin_multiple_when_first_match()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel) &
+  !$omp & when(user={condition(.false.)}: task) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_multiple_when_fallback()
+! CHECK-NOT:     omp.task
+! CHECK:         omp.parallel
+! CHECK:         return
+subroutine test_begin_multiple_when_fallback()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor("nvidia")}: task) &
+  !$omp & when(user={condition(.false.)}: task) &
+#ifdef OMP_52
+  !$omp & otherwise(parallel)
+#else
+  !$omp & default(parallel)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
index 5165268cf7cc2..5f89a0a7f32cd 100644
--- a/flang/test/Lower/OpenMP/metadirective-user.f90
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -31,3 +31,39 @@ subroutine test_condition_false()
   !$omp & default(nothing)
 #endif
 end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_condition_true()
+! CHECK:         omp.parallel
+! CHECK-NOT:     fir.if
+! CHECK:         return
+subroutine test_begin_condition_true()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(user={condition(.true.)}: parallel) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_condition_false()
+! CHECK-NOT:     omp.parallel
+! CHECK-NOT:     fir.if
+! CHECK:         return
+subroutine test_begin_condition_false()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(user={condition(.false.)}: parallel) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine

>From e5513e9f29debc7cb35a82db99d1e54ad81bbd34 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 29 Apr 2026 00:14:23 -0500
Subject: [PATCH 3/5] Fix trait-property mapping and improve metadirective
 tests

- In processTraitProperties, restrict the device_isa___ANY fallback to
  only isa selectors. Unknown properties under arch, kind, or vendor
  now produce an invalid trait so the variant does not match. Previously,
  device={arch("neon")} would incorrectly match via ISA target-feature
  checking.
- Add metadirective-nothing tests for OpenMP version >= 5.1.
- Add explicit -triple flags to ISA tests so AArch64 features run
  under an aarch64 triple and x86 features under an x86_64 triple.
- Split device={arch()} tests into metadirective-device-arch.f90
- Add omp.terminator checks for begin/end metadirective match cases.
- Remove begin-metadirective.f90 TODO test (now supported).

Assisted with copilot
---
 flang/lib/Lower/OpenMP/Utils.cpp              |  24 ++--
 .../Lower/OpenMP/Todo/begin-metadirective.f90 |  12 --
 .../Todo/metadirective-target-device.f90      |   9 +-
 .../Lower/OpenMP/metadirective-construct.f90  |   6 +-
 .../OpenMP/metadirective-device-arch.f90      | 103 ++++++++++++++++++
 .../Lower/OpenMP/metadirective-device-isa.f90 |  91 ++++------------
 .../OpenMP/metadirective-device-kind.f90      |  11 +-
 .../OpenMP/metadirective-implementation.f90   |  34 ++----
 .../Lower/OpenMP/metadirective-nothing.f90    |  62 +++++++++++
 .../test/Lower/OpenMP/metadirective-user.f90  |  11 +-
 10 files changed, 236 insertions(+), 127 deletions(-)
 delete mode 100644 flang/test/Lower/OpenMP/Todo/begin-metadirective.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-device-arch.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-nothing.f90

diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 3c906859c6976..03f1a2045b2a8 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1196,9 +1196,18 @@ static void processTraitProperties(
       continue;
     }
 
-    // Treat unknown properties as ISA-like raw strings.
-    vmi.addTrait(set, llvm::omp::TraitProperty::device_isa___ANY, name->v,
-                 scorePtr);
+    if (selector == llvm::omp::TraitSelector::device_isa) {
+      vmi.addTrait(set, llvm::omp::TraitProperty::device_isa___ANY, name->v,
+                   scorePtr);
+    } else if (selector == llvm::omp::TraitSelector::target_device_isa) {
+      vmi.addTrait(set, llvm::omp::TraitProperty::target_device_isa___ANY,
+                   name->v, scorePtr);
+    } else {
+      // For non-ISA selectors (arch, kind, vendor, etc.), unknown properties
+      // mean the variant cannot match. Add an invalid trait to ensure it is
+      // not selected.
+      vmi.addTrait(llvm::omp::TraitProperty::invalid, name->v, scorePtr);
+    }
   }
 }
 
@@ -1303,11 +1312,10 @@ void makeVariantMatchInfo(llvm::omp::VariantMatchInfo &vmi,
           std::get<std::optional<parser::OmpTraitSelector::Properties>>(
               trait.t);
 
-      // device_num requires runtime target device queries not yet supported.
-      if (const auto *val =
-              std::get_if<parser::OmpTraitSelectorName::Value>(&selectorName.u);
-          val && *val == parser::OmpTraitSelectorName::Value::Device_Num)
-        TODO(loc, "target_device={device_num()} selector in METADIRECTIVE");
+      // target_device selectors require runtime target device queries not yet
+      // supported.
+      if (set == llvm::omp::TraitSet::target_device)
+        TODO(loc, "target_device selector in METADIRECTIVE");
 
       if (selector == llvm::omp::TraitSelector::user_condition) {
         processUserConditionTrait(vmi, props, semaCtx, dynamicCondExpr);
diff --git a/flang/test/Lower/OpenMP/Todo/begin-metadirective.f90 b/flang/test/Lower/OpenMP/Todo/begin-metadirective.f90
deleted file mode 100644
index ccc06ebc3007f..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/begin-metadirective.f90
+++ /dev/null
@@ -1,12 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: OpenMP BEGIN/END METADIRECTIVE lowering
-subroutine test_begin_metadirective
-  integer :: x
-  x = 0
-  !$omp begin metadirective &
-  !$omp & when(implementation={vendor(llvm)}: parallel) &
-  !$omp & otherwise(nothing)
-  x = 1
-  !$omp end metadirective
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-target-device.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-target-device.f90
index d5269ea909fbc..78eb7992326c0 100644
--- a/flang/test/Lower/OpenMP/Todo/metadirective-target-device.f90
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-target-device.f90
@@ -1,6 +1,6 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
 
-! CHECK: not yet implemented: target_device={device_num()} selector in METADIRECTIVE
+! CHECK: not yet implemented: target_device selector in METADIRECTIVE
 
 subroutine test_target_device_num()
   continue
@@ -8,3 +8,10 @@ subroutine test_target_device_num()
   !$omp & when(target_device={device_num(0), kind(gpu)}: barrier) &
   !$omp & default(nothing)
 end subroutine
+
+subroutine test_target_device_kind()
+  continue
+  !$omp metadirective &
+  !$omp & when(target_device={kind(host)}: barrier) &
+  !$omp & default(nothing)
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-construct.f90 b/flang/test/Lower/OpenMP/metadirective-construct.f90
index 2246119201c0c..77c7a6e140f14 100644
--- a/flang/test/Lower/OpenMP/metadirective-construct.f90
+++ b/flang/test/Lower/OpenMP/metadirective-construct.f90
@@ -40,8 +40,7 @@ subroutine test_begin_construct_parallel()
   x = 0
   !$omp parallel
     !$omp begin metadirective &
-    !$omp & when(construct={parallel}: parallel) &
-    !$omp & default(nothing)
+    !$omp & when(construct={parallel}: parallel)
     x = 1
     !$omp end metadirective
   !$omp end parallel
@@ -57,8 +56,7 @@ subroutine test_begin_construct_no_match()
   x = 0
   !$omp parallel
     !$omp begin metadirective &
-    !$omp & when(construct={target}: task) &
-    !$omp & default(nothing)
+    !$omp & when(construct={target}: task)
     x = 1
     !$omp end metadirective
   !$omp end parallel
diff --git a/flang/test/Lower/OpenMP/metadirective-device-arch.f90 b/flang/test/Lower/OpenMP/metadirective-device-arch.f90
new file mode 100644
index 0000000000000..bde1aa50b89fc
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-device-arch.f90
@@ -0,0 +1,103 @@
+! Test metadirective with device={arch(...)} trait selectors.
+
+! RUN: %if aarch64-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -triple aarch64-unknown-linux-gnu %s -o - | FileCheck --check-prefix=AARCH64 %s %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -triple x86_64-unknown-linux-gnu %s -o - | FileCheck --check-prefix=X86_64 %s %}
+
+! AARCH64-LABEL: func.func @_QPtest_arch_aarch64()
+! AARCH64:         omp.barrier
+! X86_64-LABEL: func.func @_QPtest_arch_aarch64()
+! X86_64-NOT:     omp.barrier
+! X86_64:         return
+subroutine test_arch_aarch64()
+  !$omp metadirective &
+  !$omp & when(device={arch(aarch64)}: barrier) &
+  !$omp & default(nothing)
+end subroutine
+
+! AARCH64-LABEL: func.func @_QPtest_arch_x86_64()
+! AARCH64-NOT:     omp.barrier
+! AARCH64:         return
+! X86_64-LABEL: func.func @_QPtest_arch_x86_64()
+! X86_64:         omp.barrier
+subroutine test_arch_x86_64()
+  !$omp metadirective &
+  !$omp & when(device={arch(x86_64)}: barrier) &
+  !$omp & default(nothing)
+end subroutine
+
+! AARCH64-LABEL: func.func @_QPtest_arch_unknown()
+! AARCH64-NOT:     omp.barrier
+! AARCH64:         omp.taskwait
+! X86_64-LABEL: func.func @_QPtest_arch_unknown()
+! X86_64-NOT:     omp.barrier
+! X86_64:         omp.taskwait
+subroutine test_arch_unknown()
+  !$omp metadirective &
+  !$omp & when(device={arch("unknown_arch")}: barrier) &
+  !$omp & default(taskwait)
+end subroutine
+
+! AARCH64-LABEL: func.func @_QPtest_begin_arch_aarch64()
+! AARCH64:         omp.parallel
+! AARCH64:           omp.terminator
+! X86_64-LABEL: func.func @_QPtest_begin_arch_aarch64()
+! X86_64-NOT:     omp.parallel
+! X86_64:         return
+subroutine test_begin_arch_aarch64()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(device={arch(aarch64)}: parallel)
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! AARCH64-LABEL: func.func @_QPtest_begin_arch_x86_64()
+! AARCH64-NOT:     omp.parallel
+! AARCH64:         return
+! X86_64-LABEL: func.func @_QPtest_begin_arch_x86_64()
+! X86_64:         omp.parallel
+! X86_64:           omp.terminator
+subroutine test_begin_arch_x86_64()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(device={arch(x86_64)}: parallel)
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! AARCH64-LABEL: func.func @_QPtest_begin_arch_unknown()
+! AARCH64-NOT:     omp.parallel
+! AARCH64-NOT:     omp.task
+! AARCH64:         return
+! X86_64-LABEL: func.func @_QPtest_begin_arch_unknown()
+! X86_64-NOT:     omp.parallel
+! X86_64-NOT:     omp.task
+! X86_64:         return
+subroutine test_begin_arch_unknown()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(device={arch("unknown_arch")}: parallel)
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! AARCH64-LABEL: func.func @_QPtest_begin_arch_multi_when()
+! AARCH64:         omp.parallel
+! AARCH64:           omp.terminator
+! AARCH64-NOT:     omp.task
+! X86_64-LABEL: func.func @_QPtest_begin_arch_multi_when()
+! X86_64-NOT:     omp.parallel
+! X86_64:         omp.task
+! X86_64:           omp.terminator
+subroutine test_begin_arch_multi_when()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(device={arch(aarch64)}: parallel) &
+  !$omp & when(device={arch(x86_64)}: task)
+  x = 1
+  !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-device-isa.f90 b/flang/test/Lower/OpenMP/metadirective-device-isa.f90
index 8f06e3379c835..75eff7da32a3f 100644
--- a/flang/test/Lower/OpenMP/metadirective-device-isa.f90
+++ b/flang/test/Lower/OpenMP/metadirective-device-isa.f90
@@ -1,22 +1,22 @@
 ! Test metadirective with device={isa(...)} trait selectors.
 
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -target-feature +neon %s -o - | FileCheck --check-prefix=NEON %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -target-feature +neon -target-feature +sve %s -o - | FileCheck --check-prefix=SVE %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -target-feature +sse %s -o - | FileCheck --check-prefix=SSE %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -target-feature +avx %s -o - | FileCheck --check-prefix=AVX %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck --check-prefix=NONE %s
+! RUN: %if aarch64-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -triple aarch64-unknown-linux-gnu -target-feature +neon %s -o - | FileCheck --check-prefix=NEON %s %}
+! RUN: %if aarch64-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -triple aarch64-unknown-linux-gnu -target-feature +neon -target-feature +sve %s -o - | FileCheck --check-prefix=SVE %s %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -triple x86_64-unknown-linux-gnu -target-feature +sse %s -o - | FileCheck --check-prefix=SSE %s %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -triple x86_64-unknown-linux-gnu -target-feature +avx %s -o - | FileCheck --check-prefix=AVX %s %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -triple x86_64-unknown-linux-gnu %s -o - | FileCheck --check-prefix=NONE %s %}
 
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -target-feature +neon %s -o - | FileCheck --check-prefix=NEON %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -target-feature +neon -target-feature +sve %s -o - | FileCheck --check-prefix=SVE %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -target-feature +sse %s -o - | FileCheck --check-prefix=SSE %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -target-feature +avx %s -o - | FileCheck --check-prefix=AVX %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | FileCheck --check-prefix=NONE %s
+! RUN: %if aarch64-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -triple aarch64-unknown-linux-gnu -target-feature +neon %s -o - | FileCheck --check-prefix=NEON %s %}
+! RUN: %if aarch64-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -triple aarch64-unknown-linux-gnu -target-feature +neon -target-feature +sve %s -o - | FileCheck --check-prefix=SVE %s %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -triple x86_64-unknown-linux-gnu -target-feature +sse %s -o - | FileCheck --check-prefix=SSE %s %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -triple x86_64-unknown-linux-gnu -target-feature +avx %s -o - | FileCheck --check-prefix=AVX %s %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 -triple x86_64-unknown-linux-gnu %s -o - | FileCheck --check-prefix=NONE %s %}
 
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -target-feature +neon %s -o - | FileCheck --check-prefix=NEON %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -target-feature +neon -target-feature +sve %s -o - | FileCheck --check-prefix=SVE %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -target-feature +sse %s -o - | FileCheck --check-prefix=SSE %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -target-feature +avx %s -o - | FileCheck --check-prefix=AVX %s
-! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 %s -o - | FileCheck --check-prefix=NONE %s
+! RUN: %if aarch64-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -triple aarch64-unknown-linux-gnu -target-feature +neon %s -o - | FileCheck --check-prefix=NEON %s %}
+! RUN: %if aarch64-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -triple aarch64-unknown-linux-gnu -target-feature +neon -target-feature +sve %s -o - | FileCheck --check-prefix=SVE %s %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -triple x86_64-unknown-linux-gnu -target-feature +sse %s -o - | FileCheck --check-prefix=SSE %s %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -triple x86_64-unknown-linux-gnu -target-feature +avx %s -o - | FileCheck --check-prefix=AVX %s %}
+! RUN: %if x86-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 -triple x86_64-unknown-linux-gnu %s -o - | FileCheck --check-prefix=NONE %s %}
 
 ! NEON-LABEL: func.func @_QPtest_isa_neon()
 ! NEON:         omp.barrier
@@ -161,57 +161,6 @@ subroutine test_isa_no_match_default()
 #endif
 end subroutine
 
-! Test device={arch()} selector. Arch properties without a known TraitProperty
-! mapping are matched against target features, like ISA traits.
-
-! NEON-LABEL: func.func @_QPtest_arch_neon()
-! NEON:         omp.barrier
-! SVE-LABEL: func.func @_QPtest_arch_neon()
-! SVE:         omp.barrier
-! SSE-LABEL: func.func @_QPtest_arch_neon()
-! SSE-NOT:     omp.barrier
-! SSE:         return
-! AVX-LABEL: func.func @_QPtest_arch_neon()
-! AVX-NOT:     omp.barrier
-! AVX:         return
-! NONE-LABEL: func.func @_QPtest_arch_neon()
-! NONE-NOT:     omp.barrier
-! NONE:         return
-subroutine test_arch_neon()
-  !$omp metadirective &
-  !$omp & when(device={arch("neon")}: barrier) &
-#ifdef OMP_52
-  !$omp & otherwise(nothing)
-#else
-  !$omp & default(nothing)
-#endif
-end subroutine
-
-! NEON-LABEL: func.func @_QPtest_arch_no_match()
-! NEON-NOT:     omp.barrier
-! NEON:         omp.taskwait
-! SVE-LABEL: func.func @_QPtest_arch_no_match()
-! SVE-NOT:     omp.barrier
-! SVE:         omp.taskwait
-! SSE-LABEL: func.func @_QPtest_arch_no_match()
-! SSE-NOT:     omp.barrier
-! SSE:         omp.taskwait
-! AVX-LABEL: func.func @_QPtest_arch_no_match()
-! AVX-NOT:     omp.barrier
-! AVX:         omp.taskwait
-! NONE-LABEL: func.func @_QPtest_arch_no_match()
-! NONE-NOT:     omp.barrier
-! NONE:         omp.taskwait
-subroutine test_arch_no_match()
-  !$omp metadirective &
-  !$omp & when(device={arch("unknown_arch")}: barrier) &
-#ifdef OMP_52
-  !$omp & otherwise(taskwait)
-#else
-  !$omp & default(taskwait)
-#endif
-end subroutine
-
 ! NEON-LABEL: func.func @_QPtest_begin_isa_neon()
 ! NEON:         omp.parallel
 ! SVE-LABEL: func.func @_QPtest_begin_isa_neon()
@@ -228,12 +177,13 @@ subroutine test_arch_no_match()
 subroutine test_begin_isa_neon()
   integer :: x
   x = 0
+#ifdef OMP_52
   !$omp begin metadirective &
   !$omp & when(device={isa("neon")}: parallel) &
-#ifdef OMP_52
   !$omp & otherwise(nothing)
 #else
-  !$omp & default(nothing)
+  !$omp begin metadirective &
+  !$omp & when(device={isa("neon")}: parallel)
 #endif
   x = 1
   !$omp end metadirective
@@ -256,12 +206,13 @@ subroutine test_begin_isa_neon()
 subroutine test_begin_isa_sse()
   integer :: x
   x = 0
+#ifdef OMP_52
   !$omp begin metadirective &
   !$omp & when(device={isa("sse")}: parallel) &
-#ifdef OMP_52
   !$omp & otherwise(nothing)
 #else
-  !$omp & default(nothing)
+  !$omp begin metadirective &
+  !$omp & when(device={isa("sse")}: parallel)
 #endif
   x = 1
   !$omp end metadirective
diff --git a/flang/test/Lower/OpenMP/metadirective-device-kind.f90 b/flang/test/Lower/OpenMP/metadirective-device-kind.f90
index 4c1b4e45818a7..ae7613d40c5d9 100644
--- a/flang/test/Lower/OpenMP/metadirective-device-kind.f90
+++ b/flang/test/Lower/OpenMP/metadirective-device-kind.f90
@@ -22,13 +22,13 @@ subroutine test_multiple_when_second_match()
 
 ! CHECK-LABEL: func.func @_QPtest_begin_device_kind_host()
 ! CHECK:         omp.parallel
+! CHECK:           omp.terminator
 ! CHECK:         return
 subroutine test_begin_device_kind_host()
   integer :: x
   x = 0
   !$omp begin metadirective &
-  !$omp & when(device={kind(host)}: parallel) &
-  !$omp & default(nothing)
+  !$omp & when(device={kind(host)}: parallel)
   x = 1
   !$omp end metadirective
 end subroutine
@@ -40,14 +40,14 @@ subroutine test_begin_device_kind_nohost()
   integer :: x
   x = 0
   !$omp begin metadirective &
-  !$omp & when(device={kind(nohost)}: parallel) &
-  !$omp & default(nothing)
+  !$omp & when(device={kind(nohost)}: parallel)
   x = 1
   !$omp end metadirective
 end subroutine
 
 ! CHECK-LABEL: func.func @_QPtest_begin_multiple_when()
 ! CHECK:         omp.parallel
+! CHECK:           omp.terminator
 ! CHECK-NOT:     omp.task
 ! CHECK:         return
 subroutine test_begin_multiple_when()
@@ -55,8 +55,7 @@ subroutine test_begin_multiple_when()
   x = 0
   !$omp begin metadirective &
   !$omp & when(implementation={vendor("amd")}: task) &
-  !$omp & when(device={kind(host)}: parallel) &
-  !$omp & default(nothing)
+  !$omp & when(device={kind(host)}: parallel)
   x = 1
   !$omp end metadirective
 end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-implementation.f90 b/flang/test/Lower/OpenMP/metadirective-implementation.f90
index aa031192d8789..7b043ae0b44dd 100644
--- a/flang/test/Lower/OpenMP/metadirective-implementation.f90
+++ b/flang/test/Lower/OpenMP/metadirective-implementation.f90
@@ -122,16 +122,18 @@ subroutine test_multiple_when_fallback()
 
 ! CHECK-LABEL: func.func @_QPtest_begin_vendor_llvm()
 ! CHECK:         omp.parallel
+! CHECK:           omp.terminator
 ! CHECK:         return
 subroutine test_begin_vendor_llvm()
   integer :: x
   x = 0
+#ifdef OMP_52
   !$omp begin metadirective &
   !$omp & when(implementation={vendor(llvm)}: parallel) &
-#ifdef OMP_52
   !$omp & otherwise(nothing)
 #else
-  !$omp & default(nothing)
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel)
 #endif
   x = 1
   !$omp end metadirective
@@ -143,29 +145,13 @@ subroutine test_begin_vendor_llvm()
 subroutine test_begin_vendor_no_match()
   integer :: x
   x = 0
+#ifdef OMP_52
   !$omp begin metadirective &
   !$omp & when(implementation={vendor("unknown")}: parallel) &
-#ifdef OMP_52
   !$omp & otherwise(nothing)
 #else
-  !$omp & default(nothing)
-#endif
-  x = 1
-  !$omp end metadirective
-end subroutine
-
-! CHECK-LABEL: func.func @_QPtest_begin_nothing_variant()
-! CHECK-NOT:     omp.parallel
-! CHECK:         return
-subroutine test_begin_nothing_variant()
-  integer :: x
-  x = 0
   !$omp begin metadirective &
-  !$omp & when(implementation={vendor(llvm)}: nothing) &
-#ifdef OMP_52
-  !$omp & otherwise(parallel)
-#else
-  !$omp & default(parallel)
+  !$omp & when(implementation={vendor("unknown")}: parallel)
 #endif
   x = 1
   !$omp end metadirective
@@ -173,18 +159,21 @@ subroutine test_begin_nothing_variant()
 
 ! CHECK-LABEL: func.func @_QPtest_begin_multiple_when_first_match()
 ! CHECK:         omp.parallel
+! CHECK:           omp.terminator
 ! CHECK-NOT:     omp.task
 ! CHECK:         return
 subroutine test_begin_multiple_when_first_match()
   integer :: x
   x = 0
+#ifdef OMP_52
   !$omp begin metadirective &
   !$omp & when(implementation={vendor(llvm)}: parallel) &
   !$omp & when(user={condition(.false.)}: task) &
-#ifdef OMP_52
   !$omp & otherwise(nothing)
 #else
-  !$omp & default(nothing)
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel) &
+  !$omp & when(user={condition(.false.)}: task)
 #endif
   x = 1
   !$omp end metadirective
@@ -193,6 +182,7 @@ subroutine test_begin_multiple_when_first_match()
 ! CHECK-LABEL: func.func @_QPtest_begin_multiple_when_fallback()
 ! CHECK-NOT:     omp.task
 ! CHECK:         omp.parallel
+! CHECK:           omp.terminator
 ! CHECK:         return
 subroutine test_begin_multiple_when_fallback()
   integer :: x
diff --git a/flang/test/Lower/OpenMP/metadirective-nothing.f90 b/flang/test/Lower/OpenMP/metadirective-nothing.f90
new file mode 100644
index 0000000000000..ab35f2e4c95c6
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-nothing.f90
@@ -0,0 +1,62 @@
+! Test begin metadirective with the nothing directive as a variant.
+! The nothing directive as a begin-metadirective variant requires OpenMP 5.1+,
+! which added it as an exception to the paired-end-directive rule.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | FileCheck %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -cpp -DOMP_52 %s -o - | FileCheck %s
+
+! CHECK-LABEL: func.func @_QPtest_begin_nothing_variant()
+! CHECK-NOT:     omp.parallel
+! CHECK:         return
+subroutine test_begin_nothing_variant()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+#ifdef OMP_52
+  !$omp & when(implementation={vendor(llvm)}: nothing) &
+  !$omp & otherwise(parallel)
+#else
+  !$omp & when(implementation={vendor(llvm)}: nothing) &
+  !$omp & default(parallel)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_nothing_default()
+! CHECK-NOT:     omp.parallel
+! CHECK:         return
+subroutine test_begin_nothing_default()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+#ifdef OMP_52
+  !$omp & when(implementation={vendor("unknown")}: parallel) &
+  !$omp & otherwise(nothing)
+#else
+  !$omp & when(implementation={vendor("unknown")}: parallel) &
+  !$omp & default(nothing)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_nothing_first_match()
+! CHECK-NOT:     omp.parallel
+! CHECK-NOT:     omp.task
+! CHECK:         return
+subroutine test_begin_nothing_first_match()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor(llvm)}: nothing) &
+#ifdef OMP_52
+  !$omp & when(user={condition(.false.)}: task) &
+  !$omp & otherwise(parallel)
+#else
+  !$omp & when(user={condition(.false.)}: task) &
+  !$omp & default(parallel)
+#endif
+  x = 1
+  !$omp end metadirective
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
index 5f89a0a7f32cd..623443b2c6c3c 100644
--- a/flang/test/Lower/OpenMP/metadirective-user.f90
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -34,17 +34,19 @@ subroutine test_condition_false()
 
 ! CHECK-LABEL: func.func @_QPtest_begin_condition_true()
 ! CHECK:         omp.parallel
+! CHECK:           omp.terminator
 ! CHECK-NOT:     fir.if
 ! CHECK:         return
 subroutine test_begin_condition_true()
   integer :: x
   x = 0
+#ifdef OMP_52
   !$omp begin metadirective &
   !$omp & when(user={condition(.true.)}: parallel) &
-#ifdef OMP_52
   !$omp & otherwise(nothing)
 #else
-  !$omp & default(nothing)
+  !$omp begin metadirective &
+  !$omp & when(user={condition(.true.)}: parallel)
 #endif
   x = 1
   !$omp end metadirective
@@ -57,12 +59,13 @@ subroutine test_begin_condition_true()
 subroutine test_begin_condition_false()
   integer :: x
   x = 0
+#ifdef OMP_52
   !$omp begin metadirective &
   !$omp & when(user={condition(.false.)}: parallel) &
-#ifdef OMP_52
   !$omp & otherwise(nothing)
 #else
-  !$omp & default(nothing)
+  !$omp begin metadirective &
+  !$omp & when(user={condition(.false.)}: parallel)
 #endif
   x = 1
   !$omp end metadirective

>From 87979ddbc8149eb042299af36657cc78e667ee57 Mon Sep 17 00:00:00 2001
From: chichunchen <chichunchen844 at gmail.com>
Date: Wed, 29 Apr 2026 01:18:32 -0500
Subject: [PATCH 4/5] Fix metadirective implicit-nothing candidate ordering

Preserve whether a metadirective variant was explicitly
specified so selection can distinguish explicit nothing
from an omitted directive variant. Order explicit candidates
before implicit nothing candidates when invoking the OpenMP
context scorer, matching the metadirective tie-break rule.

Add standalone and begin/end metadirective regression tests
where an implicit nothing candidate appears before an
otherwise-tied explicit directive variant.

Reference:
OpenMP 5.0 [2.3.4] says that if multiple when clauses have
compatible context selectors with the same highest score, and
at least one of them specifies a directive variant, "the first
directive variant specified in the lexical order of those when
clauses" replaces the metadirective.
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 64 ++++++++++++++-----
 .../OpenMP/metadirective-implementation.f90   | 23 +++++++
 2 files changed, 71 insertions(+), 16 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 05ac2534a7b4b..954cb0554f211 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4443,6 +4443,21 @@ struct TargetOMPContext final : public llvm::omp::OMPContext {
 
   mlir::LLVM::TargetFeaturesAttr targetFeatures;
 };
+
+struct MetadirectiveVariant {
+  const parser::OmpDirectiveSpecification *spec = nullptr;
+  bool isExplicit = false;
+};
+
+struct MetadirectiveCandidate {
+  MetadirectiveCandidate(const parser::OmpDirectiveSpecification *spec,
+                         llvm::omp::VariantMatchInfo vmi, bool isExplicit)
+      : spec(spec), vmi(vmi), isExplicit(isExplicit) {}
+
+  const parser::OmpDirectiveSpecification *spec = nullptr;
+  llvm::omp::VariantMatchInfo vmi;
+  bool isExplicit = false;
+};
 } // namespace
 
 static void genMetadirective(lower::AbstractConverter &converter,
@@ -4479,8 +4494,7 @@ static void genMetadirective(lower::AbstractConverter &converter,
   std::reverse(constructTraits.begin(), constructTraits.end());
   TargetOMPContext ompCtx(builder.getModule(), constructTraits);
 
-  llvm::SmallVector<const parser::OmpDirectiveSpecification *> candidates;
-  llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> vmis;
+  llvm::SmallVector<MetadirectiveCandidate, 4> candidates;
   // A null directive specification represents either the implicit `nothing`
   // variant or the absence of an explicit otherwise/default clause.
   const parser::OmpDirectiveSpecification *fallback = nullptr;
@@ -4498,12 +4512,14 @@ static void genMetadirective(lower::AbstractConverter &converter,
     return nullptr;
   };
 
-  auto getDirectiveVariant = [](const parser::OmpClause::When &whenClause)
-      -> const parser::OmpDirectiveSpecification * {
+  auto getDirectiveVariant =
+      [](const parser::OmpClause::When &whenClause) -> MetadirectiveVariant {
     const auto &opt = std::get<1>(whenClause.v.t);
-    if (!opt || opt->value().DirId() == llvm::omp::Directive::OMPD_nothing)
-      return nullptr;
-    return &opt->value();
+    if (!opt)
+      return {};
+    if (opt->value().DirId() == llvm::omp::Directive::OMPD_nothing)
+      return {nullptr, true};
+    return {&opt->value(), true};
   };
 
   // Return the directive spec pointer, or nullptr for "nothing".
@@ -4518,12 +4534,12 @@ static void genMetadirective(lower::AbstractConverter &converter,
     if (const auto *whenClause =
             std::get_if<parser::OmpClause::When>(&clause.u)) {
       const auto *ctxSel = getContextSelector(*whenClause);
-      const auto *variant = getDirectiveVariant(*whenClause);
+      MetadirectiveVariant variant = getDirectiveVariant(*whenClause);
 
       // Always match when there is no context selector.
       if (!ctxSel) {
-        candidates.push_back(variant);
-        vmis.emplace_back();
+        candidates.emplace_back(variant.spec, llvm::omp::VariantMatchInfo(),
+                                variant.isExplicit);
         continue;
       }
 
@@ -4539,8 +4555,7 @@ static void genMetadirective(lower::AbstractConverter &converter,
       if (!llvm::omp::isVariantApplicableInContext(vmi, ompCtx))
         continue;
 
-      candidates.push_back(variant);
-      vmis.push_back(vmi);
+      candidates.emplace_back(variant.spec, vmi, variant.isExplicit);
     } else if (const auto *otherwiseClause =
                    std::get_if<parser::OmpClause::Otherwise>(&clause.u)) {
       if (otherwiseClause->v && otherwiseClause->v->v)
@@ -4579,13 +4594,30 @@ static void genMetadirective(lower::AbstractConverter &converter,
 
   const parser::OmpDirectiveSpecification *selected = fallback;
   if (candidates.size() == 1) {
-    selected = candidates.front();
+    selected = candidates.front().spec;
   } else if (!candidates.empty()) {
-    int bestIdx = llvm::omp::getBestVariantMatchForContext(vmis, ompCtx);
+    // The OpenMP context scorer preserves input order for tied candidates.
+    // Put explicit variants first so they take precedence over implicit
+    // `nothing`, as required by metadirective selection.
+    llvm::SmallVector<unsigned, 4> candidateOrder;
+    candidateOrder.reserve(candidates.size());
+    for (auto [idx, candidate] : llvm::enumerate(candidates))
+      if (candidate.isExplicit)
+        candidateOrder.push_back(idx);
+    for (auto [idx, candidate] : llvm::enumerate(candidates))
+      if (!candidate.isExplicit)
+        candidateOrder.push_back(idx);
+
+    llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs;
+    orderedVMIs.reserve(candidates.size());
+    for (unsigned idx : candidateOrder)
+      orderedVMIs.push_back(candidates[idx].vmi);
+
+    int bestIdx = llvm::omp::getBestVariantMatchForContext(orderedVMIs, ompCtx);
     if (bestIdx >= 0) {
-      assert(static_cast<size_t>(bestIdx) < candidates.size() &&
+      assert(static_cast<size_t>(bestIdx) < candidateOrder.size() &&
              "best variant index out of range");
-      selected = candidates[bestIdx];
+      selected = candidates[candidateOrder[bestIdx]].spec;
     }
   }
   genVariant(selected);
diff --git a/flang/test/Lower/OpenMP/metadirective-implementation.f90 b/flang/test/Lower/OpenMP/metadirective-implementation.f90
index 7b043ae0b44dd..d85a8ecda2466 100644
--- a/flang/test/Lower/OpenMP/metadirective-implementation.f90
+++ b/flang/test/Lower/OpenMP/metadirective-implementation.f90
@@ -120,6 +120,15 @@ subroutine test_multiple_when_fallback()
 #endif
 end subroutine
 
+! CHECK-LABEL: func.func @_QPtest_implicit_nothing_tie_break()
+! CHECK:         omp.barrier
+! CHECK:         return
+subroutine test_implicit_nothing_tie_break()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}:) &
+  !$omp & when(implementation={vendor(llvm)}: barrier)
+end subroutine
+
 ! CHECK-LABEL: func.func @_QPtest_begin_vendor_llvm()
 ! CHECK:         omp.parallel
 ! CHECK:           omp.terminator
@@ -179,6 +188,20 @@ subroutine test_begin_multiple_when_first_match()
   !$omp end metadirective
 end subroutine
 
+! CHECK-LABEL: func.func @_QPtest_begin_implicit_nothing_tie_break()
+! CHECK:         omp.parallel
+! CHECK:           omp.terminator
+! CHECK:         return
+subroutine test_begin_implicit_nothing_tie_break()
+  integer :: x
+  x = 0
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor(llvm)}:) &
+  !$omp & when(implementation={vendor(llvm)}: parallel)
+  x = 1
+  !$omp end metadirective
+end subroutine
+
 ! CHECK-LABEL: func.func @_QPtest_begin_multiple_when_fallback()
 ! CHECK-NOT:     omp.task
 ! CHECK:         omp.parallel

>From dadc748acd6218284a7d573ce26606681e277a97 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Fri, 1 May 2026 14:28:08 -0500
Subject: [PATCH 5/5] Remove redundant MetadirectiveVariant

---
 flang/lib/Lower/OpenMP/OpenMP.cpp | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 954cb0554f211..fe62db07e7f14 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4444,11 +4444,6 @@ struct TargetOMPContext final : public llvm::omp::OMPContext {
   mlir::LLVM::TargetFeaturesAttr targetFeatures;
 };
 
-struct MetadirectiveVariant {
-  const parser::OmpDirectiveSpecification *spec = nullptr;
-  bool isExplicit = false;
-};
-
 struct MetadirectiveCandidate {
   MetadirectiveCandidate(const parser::OmpDirectiveSpecification *spec,
                          llvm::omp::VariantMatchInfo vmi, bool isExplicit)
@@ -4512,11 +4507,13 @@ static void genMetadirective(lower::AbstractConverter &converter,
     return nullptr;
   };
 
-  auto getDirectiveVariant =
-      [](const parser::OmpClause::When &whenClause) -> MetadirectiveVariant {
+  // Extract the directive variant spec from a when clause.
+  // Returns {spec_ptr, isExplicit}. A null spec means "nothing".
+  auto getDirectiveVariant = [](const parser::OmpClause::When &whenClause)
+      -> std::pair<const parser::OmpDirectiveSpecification *, bool> {
     const auto &opt = std::get<1>(whenClause.v.t);
     if (!opt)
-      return {};
+      return {nullptr, false};
     if (opt->value().DirId() == llvm::omp::Directive::OMPD_nothing)
       return {nullptr, true};
     return {&opt->value(), true};
@@ -4534,12 +4531,12 @@ static void genMetadirective(lower::AbstractConverter &converter,
     if (const auto *whenClause =
             std::get_if<parser::OmpClause::When>(&clause.u)) {
       const auto *ctxSel = getContextSelector(*whenClause);
-      MetadirectiveVariant variant = getDirectiveVariant(*whenClause);
+      auto [spec, isExplicit] = getDirectiveVariant(*whenClause);
 
       // Always match when there is no context selector.
       if (!ctxSel) {
-        candidates.emplace_back(variant.spec, llvm::omp::VariantMatchInfo(),
-                                variant.isExplicit);
+        candidates.emplace_back(spec, llvm::omp::VariantMatchInfo(),
+                                isExplicit);
         continue;
       }
 
@@ -4555,7 +4552,7 @@ static void genMetadirective(lower::AbstractConverter &converter,
       if (!llvm::omp::isVariantApplicableInContext(vmi, ompCtx))
         continue;
 
-      candidates.emplace_back(variant.spec, vmi, variant.isExplicit);
+      candidates.emplace_back(spec, vmi, isExplicit);
     } else if (const auto *otherwiseClause =
                    std::get_if<parser::OmpClause::Otherwise>(&clause.u)) {
       if (otherwiseClause->v && otherwiseClause->v->v)



More information about the flang-commits mailing list