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

via flang-commits flang-commits at lists.llvm.org
Mon Jun 1 19:55:01 PDT 2026


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

>From 95948e1ed0accf10e9d07c0e99152d66e24b33cd 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 01/13] [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 7cb7e379eb503..414f37b8ff7d0 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4586,11 +4586,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 fff06bc51e3d6..a6835856a324e 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1130,6 +1130,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 7022597a233ca..819b99ca9a466 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 429bbe63f090bcc228cc6da77728609dcd3b2e56 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 02/13] 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 414f37b8ff7d0..0534672b3ba80 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4632,11 +4632,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;
@@ -4778,6 +4778,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,
@@ -4920,8 +4927,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 e123d30b078062a724f5572797b402c29a15c3a0 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 03/13] 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 a6835856a324e..527d8e63d2650 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1205,9 +1205,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);
+    }
   }
 }
 
@@ -1312,11 +1321,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 be13726bd97a09e815849f9816cfe459496cbf70 Mon Sep 17 00:00:00 2001
From: chichunchen <chichunchen844 at gmail.com>
Date: Wed, 29 Apr 2026 01:18:32 -0500
Subject: [PATCH 04/13] 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 0534672b3ba80..1c2e5cfdc4310 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4630,6 +4630,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,
@@ -4666,8 +4681,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;
@@ -4685,12 +4699,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".
@@ -4705,12 +4721,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;
       }
 
@@ -4726,8 +4742,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)
@@ -4766,13 +4781,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 5823414cab52b342d0de0c78ef3ce6258b62c623 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 05/13] 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 1c2e5cfdc4310..01b2dd2ad176c 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4631,11 +4631,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)
@@ -4699,11 +4694,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};
@@ -4721,12 +4718,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;
       }
 
@@ -4742,7 +4739,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)

>From 94069edbc51d4325f9677902003cedf5d5af51a3 Mon Sep 17 00:00:00 2001
From: chichunchen <chichunchen844 at gmail.com>
Date: Tue, 5 May 2026 23:41:50 -0500
Subject: [PATCH 06/13] Fix for construct

---
 flang/lib/Lower/OpenMP/OpenMP.cpp | 56 ++++++++++++++++++++++---------
 1 file changed, 41 insertions(+), 15 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 01b2dd2ad176c..c886c5a4880f6 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4642,6 +4642,41 @@ struct MetadirectiveCandidate {
 };
 } // namespace
 
+static void appendConstructTraits(
+    llvm::omp::Directive dir,
+    llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits) {
+  // Record compound directives inside-out so callers can reverse the final
+  // sequence into the outermost-to-innermost order required by OMPContext.
+  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);
+}
+
+static void appendConstructTraits(
+    mlir::Operation *op,
+    llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits) {
+  if (mlir::isa<mlir::omp::SimdOp>(op))
+    constructTraits.push_back(llvm::omp::TraitProperty::construct_simd_simd);
+  if (mlir::isa<mlir::omp::WsloopOp>(op))
+    constructTraits.push_back(llvm::omp::TraitProperty::construct_for_for);
+  if (mlir::isa<mlir::omp::ParallelOp>(op))
+    constructTraits.push_back(
+        llvm::omp::TraitProperty::construct_parallel_parallel);
+  if (mlir::isa<mlir::omp::TeamsOp>(op))
+    constructTraits.push_back(llvm::omp::TraitProperty::construct_teams_teams);
+  if (mlir::isa<mlir::omp::TargetOp>(op))
+    constructTraits.push_back(
+        llvm::omp::TraitProperty::construct_target_target);
+}
 static void genMetadirective(lower::AbstractConverter &converter,
                              lower::SymMap &symTable,
                              semantics::SemanticsContext &semaCtx,
@@ -4657,21 +4692,12 @@ static void genMetadirective(lower::AbstractConverter &converter,
       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);
+    appendConstructTraits(dir, constructTraits);
+  }
+  if (constructTraits.empty()) {
+    for (mlir::Operation *op = builder.getInsertionBlock()->getParentOp(); op;
+         op = op->getParentOp())
+      appendConstructTraits(op, constructTraits);
   }
   std::reverse(constructTraits.begin(), constructTraits.end());
   TargetOMPContext ompCtx(builder.getModule(), constructTraits);

>From 9e4dbb22fa71dbbf15d5526388abc10d733eb03c Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 26 May 2026 13:08:36 -0500
Subject: [PATCH 07/13] Preserve metadirective user condition scores

The user-condition path returned before getTraitScore was called, so a
score on condition(...) was silently ignored during variant selection.

Extract the score before dispatching user-condition handling, pass it to
the condition traits, and add a test where a scored true condition wins
over an unscored candidate.
---
 flang/lib/Lower/OpenMP/Utils.cpp               | 15 ++++++++-------
 flang/test/Lower/OpenMP/metadirective-user.f90 | 15 +++++++++++++++
 2 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 527d8e63d2650..bd74e332fd1bb 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1248,7 +1248,7 @@ static void processUserConditionTrait(
     llvm::omp::VariantMatchInfo &vmi,
     const std::optional<parser::OmpTraitSelector::Properties> &props,
     semantics::SemanticsContext &semaCtx,
-    const parser::ScalarExpr *&dynamicCondExpr) {
+    const parser::ScalarExpr *&dynamicCondExpr, llvm::APInt *scorePtr) {
   if (!props)
     return;
 
@@ -1261,13 +1261,13 @@ static void processUserConditionTrait(
     if (auto constValue = evaluateUserCondition(semaCtx, *scalarExpr)) {
       vmi.addTrait(*constValue ? llvm::omp::TraitProperty::user_condition_true
                                : llvm::omp::TraitProperty::user_condition_false,
-                   "<condition>");
+                   "<condition>", scorePtr);
       continue;
     }
 
     dynamicCondExpr = scalarExpr;
     vmi.addTrait(llvm::omp::TraitProperty::user_condition_unknown,
-                 "<condition>");
+                 "<condition>", scorePtr);
   }
 }
 
@@ -1326,14 +1326,15 @@ void makeVariantMatchInfo(llvm::omp::VariantMatchInfo &vmi,
       if (set == llvm::omp::TraitSet::target_device)
         TODO(loc, "target_device selector in METADIRECTIVE");
 
+      std::optional<llvm::APInt> score;
+      llvm::APInt *scorePtr = getTraitScore(props, semaCtx, score);
+
       if (selector == llvm::omp::TraitSelector::user_condition) {
-        processUserConditionTrait(vmi, props, semaCtx, dynamicCondExpr);
+        processUserConditionTrait(vmi, props, semaCtx, dynamicCondExpr,
+                                  scorePtr);
         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)
diff --git a/flang/test/Lower/OpenMP/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
index 623443b2c6c3c..54b3dbfc20eae 100644
--- a/flang/test/Lower/OpenMP/metadirective-user.f90
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -32,6 +32,21 @@ subroutine test_condition_false()
 #endif
 end subroutine
 
+! CHECK-LABEL: func.func @_QPtest_condition_score()
+! CHECK-NOT:     omp.taskyield
+! CHECK:         omp.taskwait
+! CHECK:         return
+subroutine test_condition_score()
+  !$omp metadirective &
+  !$omp & when(user={condition(.true.)}: taskyield) &
+  !$omp & when(user={condition(score(2): .true.)}: taskwait) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
 ! CHECK-LABEL: func.func @_QPtest_begin_condition_true()
 ! CHECK:         omp.parallel
 ! CHECK:           omp.terminator

>From c4cd2f810a5aeba392f9991f63ba205308c05bb7 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 26 May 2026 13:24:14 -0500
Subject: [PATCH 08/13] Use selected variants in metadirective construct
 context

An enclosing selected begin/end metadirective variant can introduce
a construct that is not represented by the PFT parent chain. For example,
an inner metadirective inside target and an outer-selected parallel must
be able to match construct={target, parallel}.

Collect construct traits from already-lowered enclosing OpenMP operations,
which represent both ordinary enclosing constructs and constructs introduced
by selected variants.
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 41 ++++---------------
 .../Lower/OpenMP/metadirective-construct.f90  | 19 +++++++++
 2 files changed, 27 insertions(+), 33 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index c886c5a4880f6..cb9602f5ec0f7 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4642,25 +4642,6 @@ struct MetadirectiveCandidate {
 };
 } // namespace
 
-static void appendConstructTraits(
-    llvm::omp::Directive dir,
-    llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits) {
-  // Record compound directives inside-out so callers can reverse the final
-  // sequence into the outermost-to-innermost order required by OMPContext.
-  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);
-}
-
 static void appendConstructTraits(
     mlir::Operation *op,
     llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits) {
@@ -4685,20 +4666,14 @@ static void genMetadirective(lower::AbstractConverter &converter,
   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;
-    appendConstructTraits(dir, constructTraits);
-  }
-  if (constructTraits.empty()) {
-    for (mlir::Operation *op = builder.getInsertionBlock()->getParentOp(); op;
-         op = op->getParentOp())
-      appendConstructTraits(op, constructTraits);
-  }
+  // Collect enclosing OpenMP operations so variants chosen by an outer
+  // metadirective are part of this metadirective's context. For example, an
+  // inner metadirective inside `target` and an outer-selected `parallel` must
+  // be able to match construct={target, parallel}.
+  for (mlir::Operation *op = builder.getInsertionBlock()->getParentOp(); op;
+       op = op->getParentOp())
+    appendConstructTraits(op, constructTraits);
+
   std::reverse(constructTraits.begin(), constructTraits.end());
   TargetOMPContext ompCtx(builder.getModule(), constructTraits);
 
diff --git a/flang/test/Lower/OpenMP/metadirective-construct.f90 b/flang/test/Lower/OpenMP/metadirective-construct.f90
index 77c7a6e140f14..74eacc78143fb 100644
--- a/flang/test/Lower/OpenMP/metadirective-construct.f90
+++ b/flang/test/Lower/OpenMP/metadirective-construct.f90
@@ -61,3 +61,22 @@ subroutine test_begin_construct_no_match()
     !$omp end metadirective
   !$omp end parallel
 end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_construct_selected_parent()
+! CHECK:         omp.target
+! CHECK:           omp.parallel
+! CHECK:             omp.barrier
+! CHECK-NOT:         omp.taskyield
+! CHECK:             omp.terminator
+! CHECK:           omp.terminator
+! CHECK:         return
+subroutine test_begin_construct_selected_parent()
+  !$omp target
+    !$omp begin metadirective &
+    !$omp & when(implementation={vendor(llvm)}: parallel)
+      !$omp metadirective &
+      !$omp & when(construct={target, parallel}: barrier) &
+      !$omp & default(taskyield)
+    !$omp end metadirective
+  !$omp end target
+end subroutine

>From 1f4ea15193304e569add73ad56aff8f652bdecb5 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 26 May 2026 13:37:05 -0500
Subject: [PATCH 09/13] Require a selector when lowering WHEN

This patch ensure we don't have missing selector as unconditional in
lowering since `WHEN` requires a context-selector.

Added negative test to replace the positive test testing against missing
selector.
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 26 ++++++-------------
 .../Semantics/OpenMP/metadirective-common.f90 |  5 ++++
 2 files changed, 13 insertions(+), 18 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index cb9602f5ec0f7..e59be30bc3812 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4682,17 +4682,14 @@ static void genMetadirective(lower::AbstractConverter &converter,
   // variant or the absence of an explicit otherwise/default clause.
   const parser::OmpDirectiveSpecification *fallback = nullptr;
 
+  // Extract the context-selector that controls whether a WHEN variant is
+  // applicable. Modifier validation requires exactly one selector per clause.
   auto getContextSelector = [](const parser::OmpClause::When &whenClause)
-      -> const parser::modifier::OmpContextSelector * {
+      -> 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;
+    assert(modifiers && modifiers->size() == 1 &&
+           "WHEN clause should contain one context-selector");
+    return std::get<parser::modifier::OmpContextSelector>(modifiers->front().u);
   };
 
   // Extract the directive variant spec from a when clause.
@@ -4718,19 +4715,12 @@ static void genMetadirective(lower::AbstractConverter &converter,
   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 &ctxSel = getContextSelector(*whenClause);
       auto [spec, isExplicit] = getDirectiveVariant(*whenClause);
 
-      // Always match when there is no context selector.
-      if (!ctxSel) {
-        candidates.emplace_back(spec, llvm::omp::VariantMatchInfo(),
-                                isExplicit);
-        continue;
-      }
-
       llvm::omp::VariantMatchInfo vmi;
       const parser::ScalarExpr *dynCondExpr = nullptr;
-      makeVariantMatchInfo(vmi, *ctxSel, semaCtx,
+      makeVariantMatchInfo(vmi, ctxSel, semaCtx,
                            converter.genLocation(clause.source), dynCondExpr);
 
       if (dynCondExpr)
diff --git a/flang/test/Semantics/OpenMP/metadirective-common.f90 b/flang/test/Semantics/OpenMP/metadirective-common.f90
index 9225048ece3c1..3d219e9d9495a 100644
--- a/flang/test/Semantics/OpenMP/metadirective-common.f90
+++ b/flang/test/Semantics/OpenMP/metadirective-common.f90
@@ -40,3 +40,8 @@ subroutine f04
   !$omp & when(target_device={device_num("device", "foo"(1))}: nothing)
 end
 
+subroutine f05
+  !$omp metadirective &
+!ERROR: 'context-selector' modifier is required
+  !$omp & when(nothing)
+end

>From 0452b6570b7e9cf15c8ac7236b74817e8aae8be6 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 26 May 2026 14:18:42 -0500
Subject: [PATCH 10/13] Fix device arch negative check coverage

---
 flang/test/Lower/OpenMP/metadirective-device-arch.f90 | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/flang/test/Lower/OpenMP/metadirective-device-arch.f90 b/flang/test/Lower/OpenMP/metadirective-device-arch.f90
index bde1aa50b89fc..ed2d8a687f48a 100644
--- a/flang/test/Lower/OpenMP/metadirective-device-arch.f90
+++ b/flang/test/Lower/OpenMP/metadirective-device-arch.f90
@@ -69,11 +69,9 @@ subroutine test_begin_arch_x86_64()
 
 ! 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
@@ -85,13 +83,17 @@ subroutine test_begin_arch_unknown()
 end subroutine
 
 ! AARCH64-LABEL: func.func @_QPtest_begin_arch_multi_when()
+! AARCH64-NOT:     omp.task
 ! AARCH64:         omp.parallel
 ! AARCH64:           omp.terminator
 ! AARCH64-NOT:     omp.task
+! AARCH64:         return
 ! X86_64-LABEL: func.func @_QPtest_begin_arch_multi_when()
 ! X86_64-NOT:     omp.parallel
 ! X86_64:         omp.task
 ! X86_64:           omp.terminator
+! X86_64-NOT:     omp.parallel
+! X86_64:         return
 subroutine test_begin_arch_multi_when()
   integer :: x
   x = 0

>From f32e814b033852b66ed816df2a73c0e6da449263 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 26 May 2026 19:19:21 -0500
Subject: [PATCH 11/13] Guard unsupported metadirective trait matching

---
 flang/lib/Lower/OpenMP/OpenMP.cpp             |  1 +
 flang/lib/Lower/OpenMP/Utils.cpp              |  9 ++++++---
 ...etadirective-structured-trait-property.f90 | 20 +++++++++++++++++++
 3 files changed, 27 insertions(+), 3 deletions(-)
 create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-structured-trait-property.f90

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index e59be30bc3812..e0beed090c4db 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4658,6 +4658,7 @@ static void appendConstructTraits(
     constructTraits.push_back(
         llvm::omp::TraitProperty::construct_target_target);
 }
+
 static void genMetadirective(lower::AbstractConverter &converter,
                              lower::SymMap &symTable,
                              semantics::SemanticsContext &semaCtx,
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index bd74e332fd1bb..0ef824df8455b 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1188,15 +1188,18 @@ 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) {
+    llvm::APInt *scorePtr, mlir::Location loc) {
   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);
+    // Clause properties and extension properties (e.g. `simdlen(8)` in
+    // `construct={simd(simdlen(8))}`) and `foo(bar)` in
+    // `implementation={my_trait(foo(bar))}` are not matched yet.
     if (!name)
-      continue;
+      TODO(loc, "clause or extension trait matching in METADIRECTIVE");
 
     llvm::omp::TraitProperty propKind =
         llvm::omp::getOpenMPContextTraitPropertyKind(set, selector, name->v);
@@ -1335,7 +1338,7 @@ void makeVariantMatchInfo(llvm::omp::VariantMatchInfo &vmi,
         continue;
       }
 
-      processTraitProperties(vmi, set, selector, props, scorePtr);
+      processTraitProperties(vmi, set, selector, props, scorePtr, loc);
 
       if (props || set != llvm::omp::TraitSet::construct)
         continue;
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-structured-trait-property.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-structured-trait-property.f90
new file mode 100644
index 0000000000000..4459f28839549
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-structured-trait-property.f90
@@ -0,0 +1,20 @@
+! RUN: %not_todo_cmd %flang_fc1 -cpp -DCLAUSE_PROPERTY -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
+! RUN: %not_todo_cmd %flang_fc1 -cpp -DEXTENSION_PROPERTY -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: clause or extension trait matching in METADIRECTIVE
+
+#ifdef CLAUSE_PROPERTY
+subroutine test_construct_simd_clause_property()
+  !$omp metadirective &
+  !$omp & when(construct={simd(simdlen(8))}: barrier) &
+  !$omp & default(nothing)
+end subroutine
+#endif
+
+#ifdef EXTENSION_PROPERTY
+subroutine test_implementation_extension_property()
+  !$omp metadirective &
+  !$omp & when(implementation={my_trait(foo(bar))}: barrier) &
+  !$omp & default(nothing)
+end subroutine
+#endif

>From e814c5aa56dc5cb7815752c90853ca9f1f3295f9 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Mon, 1 Jun 2026 13:36:52 -0500
Subject: [PATCH 12/13] Refine metadirective construct context handling and
 improve comments

Drop construct={simd} context matching because metadirectives
encountered in SIMD regions are rejected by semantics today:

```
subroutine test_construct_simd(n)
  integer :: i, n
  !$omp simd
  do i = 1, n
    !$omp metadirective &
    !$omp & when(construct={simd}: nothing) &
    !$omp & default(taskyield)
  end do
  !$omp end simd
end subroutine
```

Keep construct={for} matching because it only observes an already-lowered
enclosing worksharing-loop context. This is separate from selecting
loop-associated directive variants, which remains deferred. Add a
regression for this case.

Clarify the target-device OMPContext comment and document the trait
mapping helper contracts used by metadirective matching.
---
 flang/lib/Lower/OpenMP/OpenMP.cpp                 | 11 ++++-------
 flang/lib/Lower/OpenMP/Utils.h                    |  7 +++++++
 .../test/Lower/OpenMP/metadirective-construct.f90 | 15 +++++++++++++++
 3 files changed, 26 insertions(+), 7 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index e0beed090c4db..e556c99d8eeda 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4590,11 +4590,10 @@ 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.
+      // Metadirective lowering has no selected target device, so construct the
+      // context with an unknown device number. Target-device selectors are
+      // rejected before matching because OMPContext would otherwise describe
+      // the host device in this mode.
       : OMPContext(isDeviceCompilation(module), fir::getTargetTriple(module),
                    getOffloadTargetTriple(module),
                    /*DeviceNum=*/-1),
@@ -4645,8 +4644,6 @@ struct MetadirectiveCandidate {
 static void appendConstructTraits(
     mlir::Operation *op,
     llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits) {
-  if (mlir::isa<mlir::omp::SimdOp>(op))
-    constructTraits.push_back(llvm::omp::TraitProperty::construct_simd_simd);
   if (mlir::isa<mlir::omp::WsloopOp>(op))
     constructTraits.push_back(llvm::omp::TraitProperty::construct_for_for);
   if (mlir::isa<mlir::omp::ParallelOp>(op))
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 819b99ca9a466..35b2f79faf0d6 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -230,13 +230,20 @@ std::optional<llvm::SmallVector<mlir::Value>> getIteratorElementIndices(
     Fortran::lower::AbstractConverter &converter, const omp::Object &object,
     Fortran::lower::StatementContext &stmtCtx, mlir::Location loc);
 
+/// Map a parsed OpenMP context trait-set selector to its OMPContext kind.
 llvm::omp::TraitSet
 mapTraitSet(parser::OmpTraitSetSelectorName::Value flangSet);
 
+/// Map a parsed OpenMP context trait selector within \p set to its OMPContext
+/// kind.
 llvm::omp::TraitSelector
 mapTraitSelector(const parser::OmpTraitSelectorName &name,
                  llvm::omp::TraitSet set);
 
+/// Populate \p vmi from a parsed OpenMP context selector. Constant user
+/// conditions are folded into user_condition_true/false traits. A non-constant
+/// user condition is recorded as user_condition_unknown and returned through
+/// \p dynamicCondExpr for later lowering as a runtime condition.
 void makeVariantMatchInfo(llvm::omp::VariantMatchInfo &vmi,
                           const parser::modifier::OmpContextSelector &ctxSel,
                           semantics::SemanticsContext &semaCtx,
diff --git a/flang/test/Lower/OpenMP/metadirective-construct.f90 b/flang/test/Lower/OpenMP/metadirective-construct.f90
index 74eacc78143fb..68d4496476fef 100644
--- a/flang/test/Lower/OpenMP/metadirective-construct.f90
+++ b/flang/test/Lower/OpenMP/metadirective-construct.f90
@@ -29,6 +29,21 @@ subroutine test_construct_no_match()
   !$omp end parallel
 end subroutine
 
+! CHECK-LABEL: func.func @_QPtest_construct_for(
+! CHECK:         omp.wsloop
+! CHECK-NOT:     omp.taskyield
+! CHECK:         return
+subroutine test_construct_for(n)
+  integer :: i, n
+  !$omp do
+  do i = 1, n
+    !$omp metadirective &
+    !$omp & when(construct={for}: nothing) &
+    !$omp & default(taskyield)
+  end do
+  !$omp end do
+end subroutine
+
 ! CHECK-LABEL: func.func @_QPtest_begin_construct_parallel()
 ! CHECK:         omp.parallel
 ! CHECK:           omp.parallel

>From 3226701b326418b1e695e586a12f19880d6ce22d Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Mon, 1 Jun 2026 21:49:43 -0500
Subject: [PATCH 13/13] Add todo guard for declarative construct

Metadirective variants are lowered through genOMPDispatch, which
only handles executable constructs. If the generated construct
queue contains a declarative directive, route it to the existing
TODO path instead of dispatching it.
---
 flang/lib/Lower/OpenMP/OpenMP.cpp                      |  9 +++++++++
 .../Lower/OpenMP/Todo/metadirective-declarative.f90    | 10 ++++++++++
 2 files changed, 19 insertions(+)
 create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-declarative.f90

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index e556c99d8eeda..d314a8a6d1fd4 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4761,6 +4761,15 @@ static void genMetadirective(lower::AbstractConverter &converter,
       TODO(variantLoc, "loop-associated METADIRECTIVE variant");
     }
 
+    if (llvm::any_of(queue, [](const auto &item) {
+          return llvm::omp::getDirectiveAssociation(item.id) ==
+                     llvm::omp::Association::Declaration ||
+                 llvm::omp::getDirectiveCategory(item.id) ==
+                     llvm::omp::Category::Declarative;
+        })) {
+      TODO(variantLoc, "declarative METADIRECTIVE variant");
+    }
+
     genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
                    queue.begin());
   };
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-declarative.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-declarative.f90
new file mode 100644
index 0000000000000..95fcaf78c7a6e
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-declarative.f90
@@ -0,0 +1,10 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=52 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: declarative METADIRECTIVE variant
+
+subroutine test_declarative_variant()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: declare target) &
+  !$omp & otherwise(nothing)
+end subroutine
+



More information about the flang-commits mailing list