[flang-commits] [flang] users/cchen/flang metadirective loop (PR #195344)

via flang-commits flang-commits at lists.llvm.org
Fri May 1 13:14:13 PDT 2026


https://github.com/chichunchen created https://github.com/llvm/llvm-project/pull/195344

- **[flang][OpenMP] Support lowering of metadirective (part 1)**
- **Support begin/end metadirective**
- **Fix trait-property mapping and improve metadirective tests**
- **Fix metadirective implicit-nothing candidate ordering**
- **Remove redundant MetadirectiveVariant**
- **[flang][OpenMP] Support lowering of metadirective (part 2)**
- **Fix dynamic metadirective candidate selection**
- **[flang][OpenMP] Support loop-associated metadirective variants (part 3)**


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

>From 241c7bb83ca47317c7d10bfac428ec6d266e85c4 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Mon, 27 Apr 2026 02:45:02 -0500
Subject: [PATCH 6/8] [flang][OpenMP] Support lowering of metadirective (part
 2)

Lower non-constant user={condition(expr)} selectors in metadirectives
to a fir.if/else chain.

Only statically applicable when-clauses participate in dynamic
selection. Dynamic conditions are evaluated at runtime in declaration
order, with the best static match, an explicit otherwise/default
clause, or implicit nothing as the final fallback.

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

Assisted with copilot and GPT-5.4
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 108 +++++++---
 .../OpenMP/Todo/metadirective-dynamic.f90     |  10 -
 .../test/Lower/OpenMP/metadirective-user.f90  | 203 +++++++++++++++++-
 3 files changed, 280 insertions(+), 41 deletions(-)
 delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index fe62db07e7f14..b25e5971ea0a3 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4490,6 +4490,11 @@ static void genMetadirective(lower::AbstractConverter &converter,
   TargetOMPContext ompCtx(builder.getModule(), constructTraits);
 
   llvm::SmallVector<MetadirectiveCandidate, 4> candidates;
+  struct DynamicCandidate {
+    const parser::OmpDirectiveSpecification *spec = nullptr;
+    const parser::ScalarExpr *condExpr = nullptr;
+  };
+  llvm::SmallVector<DynamicCandidate> dynamicCandidates;
   // A null directive specification represents either the implicit `nothing`
   // variant or the absence of an explicit otherwise/default clause.
   const parser::OmpDirectiveSpecification *fallback = nullptr;
@@ -4545,9 +4550,16 @@ static void genMetadirective(lower::AbstractConverter &converter,
       makeVariantMatchInfo(vmi, *ctxSel, semaCtx,
                            converter.genLocation(clause.source), dynCondExpr);
 
-      if (dynCondExpr)
-        TODO(converter.genLocation(clause.source),
-             "dynamic user condition in METADIRECTIVE");
+      // Check if this variant has a dynamic user condition.
+      if (dynCondExpr) {
+        // For dynamic candidates, verify that the non-user parts (device,
+        // implementation) still match statically before keeping the candidate.
+        if (!llvm::omp::isVariantApplicableInContext(
+                vmi, ompCtx, /*DeviceOrImplementationSetOnly=*/true))
+          continue;
+        dynamicCandidates.push_back({variant.spec, dynCondExpr});
+        continue;
+      }
 
       if (!llvm::omp::isVariantApplicableInContext(vmi, ompCtx))
         continue;
@@ -4589,35 +4601,71 @@ static void genMetadirective(lower::AbstractConverter &converter,
                    queue.begin());
   };
 
-  const parser::OmpDirectiveSpecification *selected = fallback;
-  if (candidates.size() == 1) {
-    selected = candidates.front().spec;
-  } else if (!candidates.empty()) {
-    // 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) < candidateOrder.size() &&
-             "best variant index out of range");
-      selected = candidates[candidateOrder[bestIdx]].spec;
+  auto selectStaticBest = [&]() -> const parser::OmpDirectiveSpecification * {
+    if (candidates.size() == 1)
+      return candidates.front().spec;
+    if (!candidates.empty()) {
+      // 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) < candidateOrder.size() &&
+               "best variant index out of range");
+        return candidates[candidateOrder[bestIdx]].spec;
+      }
     }
+    return fallback;
+  };
+
+  // If no dynamic candidates, do pure static resolution.
+  if (dynamicCandidates.empty()) {
+    genVariant(selectStaticBest());
+    return;
+  }
+
+  // Dynamic resolution: build a fir.if chain for user conditions.
+  // Candidates are tested in declaration order. The final else branch
+  // falls back to the best static match, an explicit otherwise/default
+  // clause, or an implicit no-op.
+  mlir::Location loc = converter.genLocation(clauseList.source);
+  lower::StatementContext stmtCtx;
+  const parser::OmpDirectiveSpecification *staticFallback = selectStaticBest();
+
+  for (auto [i, cand] : llvm::enumerate(dynamicCandidates)) {
+    assert(cand.condExpr && "dynamic candidate must have condition expr");
+    const auto *typedExpr = semantics::GetExpr(semaCtx, *cand.condExpr);
+    assert(typedExpr && "missing typed expression for user condition");
+    mlir::Value condVal =
+        fir::getBase(converter.genExprValue(*typedExpr, stmtCtx, &loc));
+
+    if (condVal.getType() != builder.getI1Type())
+      condVal = builder.createConvert(loc, builder.getI1Type(), condVal);
+
+    auto ifOp =
+        fir::IfOp::create(builder, loc, condVal, /*withElseRegion=*/true);
+    builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
+    genVariant(cand.spec);
+
+    builder.setInsertionPointToStart(&ifOp.getElseRegion().front());
+    if (i == dynamicCandidates.size() - 1)
+      genVariant(staticFallback);
   }
-  genVariant(selected);
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90
deleted file mode 100644
index a9a02ece1db4b..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-dynamic.f90
+++ /dev/null
@@ -1,10 +0,0 @@
-! 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/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
index 623443b2c6c3c..e17e5dfc4968c 100644
--- a/flang/test/Lower/OpenMP/metadirective-user.f90
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -1,9 +1,13 @@
-! Test lowering of OpenMP metadirective with constant-folded user selectors.
+! Test lowering of OpenMP metadirective with user={condition()} 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
 
+!===----------------------------------------------------------------------===!
+! Static (constant-folded) user conditions
+!===----------------------------------------------------------------------===!
+
 ! CHECK-LABEL: func.func @_QPtest_condition_true()
 ! CHECK:         omp.taskyield
 ! CHECK-NOT:     fir.if
@@ -70,3 +74,200 @@ subroutine test_begin_condition_false()
   x = 1
   !$omp end metadirective
 end subroutine
+
+!===----------------------------------------------------------------------===!
+! Dynamic (runtime) user conditions
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_condition(
+! CHECK-SAME:    %[[ARG0:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK:         %[[DECL:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK:         %[[LOAD:.*]] = fir.load %[[DECL]]#0
+! CHECK:         %[[COND:.*]] = fir.convert %[[LOAD]] : (!fir.logical<4>) -> i1
+! CHECK:         fir.if %[[COND]] {
+! CHECK:           omp.barrier
+! CHECK:         } else {
+! CHECK:         }
+! CHECK:         return
+subroutine test_dynamic_condition(flag)
+  logical, intent(in) :: flag
+  !$omp metadirective &
+  !$omp & when(user={condition(flag)}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_condition_expr(
+! CHECK-SAME:    %[[ARG0:.*]]: !fir.ref<i32>
+! CHECK:         %[[DECL:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK:         %[[LOAD:.*]] = fir.load %[[DECL]]#0
+! CHECK:         %[[C1000:.*]] = arith.constant 1000 : i32
+! CHECK:         %[[CMP:.*]] = arith.cmpi sgt, %[[LOAD]], %[[C1000]] : i32
+! CHECK:         fir.if %[[CMP]] {
+! CHECK:           omp.barrier
+! CHECK:         } else {
+! CHECK:         }
+! CHECK:         return
+subroutine test_dynamic_condition_expr(n)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp & when(user={condition(n > 1000)}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! Both when clauses pass vendor(llvm) statically. The first has a dynamic
+! condition so becomes a runtime branch; the second is fully static and
+! becomes the fallback.
+! CHECK-LABEL: func.func @_QPtest_mixed_static_dynamic(
+! CHECK-SAME:    %[[ARG0:.*]]: !fir.ref<i32>
+! CHECK:         %[[DECL:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK:         %[[LOAD:.*]] = fir.load %[[DECL]]#0
+! CHECK:         %[[C100:.*]] = arith.constant 100 : i32
+! CHECK:         %[[CMP:.*]] = arith.cmpi sgt, %[[LOAD]], %[[C100]] : i32
+! CHECK:         fir.if %[[CMP]] {
+! CHECK:           omp.barrier
+! CHECK:         } else {
+! CHECK:           omp.taskwait
+! CHECK:         }
+! CHECK:         return
+subroutine test_mixed_static_dynamic(n)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}, user={condition(n > 100)}: barrier) &
+  !$omp & when(implementation={vendor(llvm)}: taskwait) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! Dynamic candidate whose static traits don't match is skipped entirely.
+! CHECK-LABEL: func.func @_QPtest_dynamic_static_mismatch(
+! CHECK-NOT:     fir.if
+! CHECK:         omp.taskyield
+! CHECK:         return
+subroutine test_dynamic_static_mismatch(flag)
+  logical, intent(in) :: flag
+  !$omp metadirective &
+  !$omp & when(implementation={vendor("unknown")}, user={condition(flag)}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(taskyield)
+#else
+  !$omp & default(taskyield)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_two_dynamic(
+! CHECK-SAME:    %[[ARG0:[^,]*]]: !fir.ref<!fir.logical<4>>
+! CHECK-SAME:    %[[ARG1:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK:         %[[DECLA:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK:         %[[DECLB:.*]]:2 = hlfir.declare %[[ARG1]]
+! CHECK:         %[[LOADA:.*]] = fir.load %[[DECLA]]#0
+! CHECK:         %[[CONDA:.*]] = fir.convert %[[LOADA]] : (!fir.logical<4>) -> i1
+! CHECK:         fir.if %[[CONDA]] {
+! CHECK:           omp.barrier
+! CHECK:         } else {
+! CHECK:           %[[LOADB:.*]] = fir.load %[[DECLB]]#0
+! CHECK:           %[[CONDB:.*]] = fir.convert %[[LOADB]] : (!fir.logical<4>) -> i1
+! CHECK:           fir.if %[[CONDB]] {
+! CHECK:             omp.taskwait
+! CHECK:           } else {
+! CHECK:             omp.taskyield
+! CHECK:           }
+! CHECK:         }
+! CHECK:         return
+subroutine test_two_dynamic(a, b)
+  logical, intent(in) :: a, b
+  !$omp metadirective &
+  !$omp & when(user={condition(a)}: barrier) &
+  !$omp & when(user={condition(b)}: taskwait) &
+#ifdef OMP_52
+  !$omp & otherwise(taskyield)
+#else
+  !$omp & default(taskyield)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_three_dynamic(
+! CHECK-SAME:    %[[A:[^,]*]]: !fir.ref<!fir.logical<4>>
+! CHECK-SAME:    %[[B:[^,]*]]: !fir.ref<!fir.logical<4>>
+! CHECK-SAME:    %[[C:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK:         %[[DA:.*]]:2 = hlfir.declare %[[A]]
+! CHECK:         %[[DB:.*]]:2 = hlfir.declare %[[B]]
+! CHECK:         %[[DC:.*]]:2 = hlfir.declare %[[C]]
+! CHECK:         %[[LA:.*]] = fir.load %[[DA]]#0
+! CHECK:         %[[CA:.*]] = fir.convert %[[LA]] : (!fir.logical<4>) -> i1
+! CHECK:         fir.if %[[CA]] {
+! CHECK:           omp.barrier
+! CHECK:         } else {
+! CHECK:           %[[LB:.*]] = fir.load %[[DB]]#0
+! CHECK:           %[[CB:.*]] = fir.convert %[[LB]] : (!fir.logical<4>) -> i1
+! CHECK:           fir.if %[[CB]] {
+! CHECK:             omp.taskwait
+! CHECK:           } else {
+! CHECK:             %[[LC:.*]] = fir.load %[[DC]]#0
+! CHECK:             %[[CC:.*]] = fir.convert %[[LC]] : (!fir.logical<4>) -> i1
+! CHECK:             fir.if %[[CC]] {
+! CHECK:               omp.taskyield
+! CHECK:             } else {
+! CHECK:             }
+! CHECK:           }
+! CHECK:         }
+! CHECK:         return
+subroutine test_three_dynamic(a, b, c)
+  logical, intent(in) :: a, b, c
+  !$omp metadirective &
+  !$omp & when(user={condition(a)}: barrier) &
+  !$omp & when(user={condition(b)}: taskwait) &
+  !$omp & when(user={condition(c)}: taskyield) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_multi_dynamic_multi_static(
+! CHECK-SAME:    %[[A:[^,]*]]: !fir.ref<!fir.logical<4>>
+! CHECK-SAME:    %[[B:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK:         %[[DA:.*]]:2 = hlfir.declare %[[A]]
+! CHECK:         %[[DB:.*]]:2 = hlfir.declare %[[B]]
+! CHECK:         %[[LA:.*]] = fir.load %[[DA]]#0
+! CHECK:         %[[CA:.*]] = fir.convert %[[LA]] : (!fir.logical<4>) -> i1
+! CHECK:         fir.if %[[CA]] {
+! CHECK:           omp.barrier
+! CHECK:         } else {
+! CHECK:           %[[LB:.*]] = fir.load %[[DB]]#0
+! CHECK:           %[[CB:.*]] = fir.convert %[[LB]] : (!fir.logical<4>) -> i1
+! CHECK:           fir.if %[[CB]] {
+! CHECK:             omp.taskyield
+! CHECK:           } else {
+! CHECK:             omp.taskwait
+! CHECK:           }
+! CHECK:         }
+! CHECK:         return
+subroutine test_multi_dynamic_multi_static(a, b)
+  logical, intent(in) :: a, b
+  ! dynamic + vendor(llvm) -> kept
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}, user={condition(a)}: barrier) &
+  ! dynamic + vendor("unknown") -> skipped (static mismatch)
+  !$omp & when(implementation={vendor("unknown")}, user={condition(.true.)}: taskwait) &
+  ! dynamic + vendor(llvm) -> kept
+  !$omp & when(implementation={vendor(llvm)}, user={condition(b)}: taskyield) &
+  ! static + vendor(llvm) -> best static fallback
+  !$omp & when(implementation={vendor(llvm)}: taskwait) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine

>From c36eec79da089d45d1ca15bca2f2109b81e005d2 Mon Sep 17 00:00:00 2001
From: chichunchen <chichunchen844 at gmail.com>
Date: Wed, 29 Apr 2026 02:45:38 -0500
Subject: [PATCH 7/8] Fix dynamic metadirective candidate selection

- Use one scored candidate path for static and dynamic metadirective variants.
- Dynamic user conditions are statically filtered and scored using their
  non-user traits, then guarded at runtime with fir.if.
- Keeps construct/device/implementation traits enforced for dynamic
  candidates and lets higher-scored static candidates beat lower-scored dynamic
  candidates.
- Add regressions for construct mismatch, score ordering, and
  implicit-nothing tie-breaking.
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 127 ++++++++++--------
 .../test/Lower/OpenMP/metadirective-user.f90  |  50 +++++++
 2 files changed, 118 insertions(+), 59 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index b25e5971ea0a3..bc7575547311a 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4446,12 +4446,14 @@ struct TargetOMPContext final : public llvm::omp::OMPContext {
 
 struct MetadirectiveCandidate {
   MetadirectiveCandidate(const parser::OmpDirectiveSpecification *spec,
-                         llvm::omp::VariantMatchInfo vmi, bool isExplicit)
-      : spec(spec), vmi(vmi), isExplicit(isExplicit) {}
+                         llvm::omp::VariantMatchInfo vmi, bool isExplicit,
+                         const parser::ScalarExpr *condExpr = nullptr)
+      : spec(spec), vmi(vmi), isExplicit(isExplicit), condExpr(condExpr) {}
 
   const parser::OmpDirectiveSpecification *spec = nullptr;
   llvm::omp::VariantMatchInfo vmi;
   bool isExplicit = false;
+  const parser::ScalarExpr *condExpr = nullptr;
 };
 } // namespace
 
@@ -4490,11 +4492,6 @@ static void genMetadirective(lower::AbstractConverter &converter,
   TargetOMPContext ompCtx(builder.getModule(), constructTraits);
 
   llvm::SmallVector<MetadirectiveCandidate, 4> candidates;
-  struct DynamicCandidate {
-    const parser::OmpDirectiveSpecification *spec = nullptr;
-    const parser::ScalarExpr *condExpr = nullptr;
-  };
-  llvm::SmallVector<DynamicCandidate> dynamicCandidates;
   // A null directive specification represents either the implicit `nothing`
   // variant or the absence of an explicit otherwise/default clause.
   const parser::OmpDirectiveSpecification *fallback = nullptr;
@@ -4552,12 +4549,12 @@ static void genMetadirective(lower::AbstractConverter &converter,
 
       // Check if this variant has a dynamic user condition.
       if (dynCondExpr) {
-        // For dynamic candidates, verify that the non-user parts (device,
-        // implementation) still match statically before keeping the candidate.
-        if (!llvm::omp::isVariantApplicableInContext(
-                vmi, ompCtx, /*DeviceOrImplementationSetOnly=*/true))
+        llvm::omp::VariantMatchInfo dynamicVMI = vmi;
+        dynamicVMI.RequiredTraits.reset(
+            unsigned(llvm::omp::TraitProperty::user_condition_unknown));
+        if (!llvm::omp::isVariantApplicableInContext(dynamicVMI, ompCtx))
           continue;
-        dynamicCandidates.push_back({variant.spec, dynCondExpr});
+        candidates.emplace_back(spec, dynamicVMI, isExplicit, dynCondExpr);
         continue;
       }
 
@@ -4601,58 +4598,67 @@ static void genMetadirective(lower::AbstractConverter &converter,
                    queue.begin());
   };
 
-  auto selectStaticBest = [&]() -> const parser::OmpDirectiveSpecification * {
-    if (candidates.size() == 1)
-      return candidates.front().spec;
-    if (!candidates.empty()) {
-      // 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) < candidateOrder.size() &&
-               "best variant index out of range");
-        return candidates[candidateOrder[bestIdx]].spec;
-      }
+  auto selectBestCandidate =
+      [](llvm::ArrayRef<unsigned> candidateIndices,
+         llvm::ArrayRef<MetadirectiveCandidate> candidates,
+         const TargetOMPContext &ompCtx) -> std::optional<unsigned> {
+    if (candidateIndices.empty())
+      return std::nullopt;
+    if (candidateIndices.size() == 1)
+      return candidateIndices.front();
+
+    // 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(candidateIndices.size());
+    for (unsigned idx : candidateIndices)
+      if (candidates[idx].isExplicit)
+        candidateOrder.push_back(idx);
+    for (unsigned idx : candidateIndices)
+      if (!candidates[idx].isExplicit)
+        candidateOrder.push_back(idx);
+
+    llvm::SmallVector<llvm::omp::VariantMatchInfo, 4> orderedVMIs;
+    orderedVMIs.reserve(candidateOrder.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) < candidateOrder.size() &&
+             "best variant index out of range");
+      return candidateOrder[bestIdx];
     }
-    return fallback;
+    return std::nullopt;
   };
 
-  // If no dynamic candidates, do pure static resolution.
-  if (dynamicCandidates.empty()) {
-    genVariant(selectStaticBest());
-    return;
-  }
+  llvm::SmallVector<unsigned, 4> remainingCandidates;
+  remainingCandidates.reserve(candidates.size());
+  for (unsigned idx = 0, end = candidates.size(); idx < end; ++idx)
+    remainingCandidates.push_back(idx);
 
-  // Dynamic resolution: build a fir.if chain for user conditions.
-  // Candidates are tested in declaration order. The final else branch
-  // falls back to the best static match, an explicit otherwise/default
-  // clause, or an implicit no-op.
   mlir::Location loc = converter.genLocation(clauseList.source);
   lower::StatementContext stmtCtx;
-  const parser::OmpDirectiveSpecification *staticFallback = selectStaticBest();
 
-  for (auto [i, cand] : llvm::enumerate(dynamicCandidates)) {
-    assert(cand.condExpr && "dynamic candidate must have condition expr");
-    const auto *typedExpr = semantics::GetExpr(semaCtx, *cand.condExpr);
-    assert(typedExpr && "missing typed expression for user condition");
+  while (!remainingCandidates.empty()) {
+    std::optional<unsigned> selected =
+        selectBestCandidate(remainingCandidates, candidates, ompCtx);
+    if (!selected) {
+      genVariant(fallback);
+      return;
+    }
+
+    const MetadirectiveCandidate &candidate = candidates[*selected];
+    if (!candidate.condExpr) {
+      genVariant(candidate.spec);
+      return;
+    }
+
+    const auto *condExpr = semantics::GetExpr(semaCtx, *candidate.condExpr);
+    assert(condExpr && "missing expression for user condition");
     mlir::Value condVal =
-        fir::getBase(converter.genExprValue(*typedExpr, stmtCtx, &loc));
+        fir::getBase(converter.genExprValue(*condExpr, stmtCtx, &loc));
 
     if (condVal.getType() != builder.getI1Type())
       condVal = builder.createConvert(loc, builder.getI1Type(), condVal);
@@ -4660,12 +4666,15 @@ static void genMetadirective(lower::AbstractConverter &converter,
     auto ifOp =
         fir::IfOp::create(builder, loc, condVal, /*withElseRegion=*/true);
     builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
-    genVariant(cand.spec);
+    genVariant(candidate.spec);
 
     builder.setInsertionPointToStart(&ifOp.getElseRegion().front());
-    if (i == dynamicCandidates.size() - 1)
-      genVariant(staticFallback);
+    auto *remainingIt = llvm::find(remainingCandidates, *selected);
+    assert(remainingIt != remainingCandidates.end() &&
+           "selected candidate missing from remaining candidates");
+    remainingCandidates.erase(remainingIt);
   }
+  genVariant(fallback);
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/test/Lower/OpenMP/metadirective-user.f90 b/flang/test/Lower/OpenMP/metadirective-user.f90
index e17e5dfc4968c..b0cec034466eb 100644
--- a/flang/test/Lower/OpenMP/metadirective-user.f90
+++ b/flang/test/Lower/OpenMP/metadirective-user.f90
@@ -165,6 +165,56 @@ subroutine test_dynamic_static_mismatch(flag)
 #endif
 end subroutine
 
+! Dynamic candidates must still satisfy non-user static traits. This construct
+! selector does not match outside a parallel construct, so the fallback wins.
+! CHECK-LABEL: func.func @_QPtest_dynamic_construct_mismatch(
+! CHECK-NOT:     fir.if
+! CHECK-NOT:     omp.barrier
+! CHECK:         omp.taskwait
+! CHECK:         return
+subroutine test_dynamic_construct_mismatch(flag)
+  logical, intent(in) :: flag
+  !$omp metadirective &
+  !$omp & when(construct={parallel}, user={condition(flag)}: barrier) &
+#ifdef OMP_52
+  !$omp & otherwise(taskwait)
+#else
+  !$omp & default(taskwait)
+#endif
+end subroutine
+
+! A higher-scored static candidate is selected before a lower-scored dynamic
+! candidate, even when the dynamic condition could be true at runtime.
+! CHECK-LABEL: func.func @_QPtest_dynamic_static_score_order(
+! CHECK-NOT:     fir.if
+! CHECK-NOT:     omp.barrier
+! CHECK:         omp.taskwait
+! CHECK:         return
+subroutine test_dynamic_static_score_order(flag)
+  logical, intent(in) :: flag
+  !$omp metadirective &
+  !$omp & when(user={condition(flag)}: barrier) &
+  !$omp & when(device={kind(host)}: taskwait) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+end subroutine
+
+! The explicit directive variant wins this tie over the earlier implicit
+! nothing candidate.
+! CHECK-LABEL: func.func @_QPtest_dynamic_implicit_nothing_tie_break(
+! CHECK-NOT:     fir.if
+! CHECK:         omp.barrier
+! CHECK:         return
+subroutine test_dynamic_implicit_nothing_tie_break(flag)
+  logical, intent(in) :: flag
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}, user={condition(flag)}:) &
+  !$omp & when(implementation={vendor(llvm)}: barrier)
+end subroutine
+
 ! CHECK-LABEL: func.func @_QPtest_two_dynamic(
 ! CHECK-SAME:    %[[ARG0:[^,]*]]: !fir.ref<!fir.logical<4>>
 ! CHECK-SAME:    %[[ARG1:.*]]: !fir.ref<!fir.logical<4>>

>From 2d44076ac6f165cf11560762a517cfbc2efd8545 Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Tue, 28 Apr 2026 11:54:13 -0500
Subject: [PATCH 8/8] [flang][OpenMP] Support loop-associated metadirective
 variants (part 3)

Enable metadirective lowering for loop-associated variants such as
`do`, `simd`, `parallel do`, and `do simd`.

When a metadirective resolves to a loop-associated directive, the
sibling DO evaluation is spliced into the metadirective's evaluation
list so existing loop lowering finds it. Loop IV data-sharing
attributes are marked at lowering time since semantic analysis cannot
know which variant will be selected.

The DataSharingProcessor is extended to handle spliced evaluations:
the symbol visitor walks nested parse trees, symbol collection
includes nested evaluations, scope resolution walks ancestor scopes,
and lastprivate copy-back handles symbols without HostAssocDetails.

Not yet supported:
- Target constructs selected by metadirective (requires host-eval)
---
 .../lib/Lower/OpenMP/DataSharingProcessor.cpp |  86 +++++++-
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 101 ++++++++-
 flang/lib/Lower/OpenMP/Utils.cpp              |  14 ++
 flang/lib/Lower/OpenMP/Utils.h                |   2 +
 .../Lower/OpenMP/Todo/metadirective-loop.f90  |  12 --
 .../OpenMP/Todo/metadirective-no-loop.f90     |  12 ++
 .../OpenMP/Todo/metadirective-target-loop.f90 |  15 ++
 .../test/Lower/OpenMP/metadirective-loop.f90  | 203 ++++++++++++++++++
 8 files changed, 430 insertions(+), 15 deletions(-)
 delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-no-loop.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-loop.f90

diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index e392497d30de7..4a8c397a96d89 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -41,8 +41,41 @@ bool DataSharingProcessor::OMPConstructSymbolVisitor::isSymbolDefineBy(
     const semantics::Symbol *symbol, lower::pft::Evaluation &eval) const {
   return eval.visit(common::visitors{
       [&](const parser::OpenMPConstruct &functionParserNode) {
+        if (symDefMap.count(symbol) &&
+            symDefMap.at(symbol) == ConstructPtr(&functionParserNode))
+          return true;
+
+        // For metadirectives on standalone constructs, the construct itself
+        // does not introduce privatization. As a result, symbols from nested
+        // (spliced) DO evaluations may be associated with a null
+        // OpenMPConstruct pointer. This is expected and should be accepted.
+        if (const auto *standalone =
+                std::get_if<parser::OpenMPStandaloneConstruct>(
+                    &functionParserNode.u)) {
+          if (std::holds_alternative<parser::OmpMetadirectiveDirective>(
+                  standalone->u)) {
+            return symDefMap.count(symbol) &&
+                   symDefMap.at(symbol) ==
+                       ConstructPtr(
+                           static_cast<const parser::OpenMPConstruct *>(
+                               nullptr));
+          }
+        }
+        return false;
+      },
+      [&](const parser::OpenMPDeclarativeConstruct &functionParserNode) {
+        // Metadirective variants are resolved during lowering, so the eval
+        // is an OpenMPDeclarativeConstruct rather than an OpenMPConstruct.
+        // The visitor does not track declarative constructs, so symbols
+        // referenced at the top level of the eval (e.g. loop IVs from a
+        // spliced DO) are mapped to a null OpenMPConstruct pointer.
+        if (!std::holds_alternative<parser::OmpMetadirectiveDirective>(
+                functionParserNode.u))
+          return false;
         return symDefMap.count(symbol) &&
-               symDefMap.at(symbol) == ConstructPtr(&functionParserNode);
+               symDefMap.at(symbol) ==
+                   ConstructPtr(
+                       static_cast<const parser::OpenMPConstruct *>(nullptr));
       },
       [](const auto &functionParserNode) { return false; }});
 }
@@ -77,6 +110,15 @@ DataSharingProcessor::DataSharingProcessor(
   eval.visit([&](const auto &functionParserNode) {
     parser::Walk(functionParserNode, visitor);
   });
+  // For metadirective evaluations, the associated DO loop is spliced into the
+  // evaluation tree but is not part of the metadirective's parse tree. Walk
+  // nested evaluations' parse trees so the visitor can track their symbols
+  // (e.g. loop iteration variables).
+  if (isMetadirectiveEval(eval) && eval.hasNestedEvaluations()) {
+    for (auto &nestedEval : eval.getNestedEvaluations())
+      nestedEval.visit(
+          [&](const auto &node) { parser::Walk(node, visitor); });
+  }
 }
 
 DataSharingProcessor::DataSharingProcessor(lower::AbstractConverter &converter,
@@ -220,8 +262,27 @@ void DataSharingProcessor::copyFirstPrivateSymbol(
 
 void DataSharingProcessor::copyLastPrivateSymbol(
     const semantics::Symbol *sym, mlir::OpBuilder::InsertPoint *lastPrivIP) {
-  if (sym->test(semantics::Symbol::Flag::OmpLastPrivate))
+  if (!sym->test(semantics::Symbol::Flag::OmpLastPrivate))
+    return;
+
+  if (sym->has<semantics::HostAssocDetails>()) {
     converter.copyHostAssociateVar(*sym, lastPrivIP, /*hostIsSource=*/false);
+    return;
+  }
+
+  // Metadirective loop IVs can be marked lastprivate during lowering, after
+  // semantic host-association symbols would normally be created. Copy from the
+  // private binding back to the one-level-up binding directly.
+  mlir::OpBuilder::InsertionGuard guard(firOpBuilder);
+  if (lastPrivIP)
+    firOpBuilder.restoreInsertionPoint(*lastPrivIP);
+  lower::SymbolBox hostBox = converter.lookupOneLevelUpSymbol(*sym);
+  lower::SymbolBox privBox = converter.shallowLookupSymbol(*sym);
+  assert(hostBox && privBox &&
+         "expected symbol bindings for lastprivate loop IV");
+  if (hostBox.getAddr() != privBox.getAddr())
+    converter.copyVar(converter.getCurrentLocation(), hostBox.getAddr(),
+                      privBox.getAddr(), fir::FortranVariableFlagsEnum::None);
 }
 
 void DataSharingProcessor::collectOmpObjectListSymbol(
@@ -536,6 +597,19 @@ void DataSharingProcessor::collectPrivatizedSymbols(
   llvm::SetVector<const semantics::Scope *> clauseScopes;
   (void)collectScopes(semaCtx, eval, clauseScopes);
 
+  // For metadirective evaluations, the source range only covers the directive
+  // clauses, not the spliced DO loop. The scope found from that narrow range
+  // may not include parent scopes where the loop IV is declared (e.g. the
+  // function scope when the metadirective is inside a target region). Walk up
+  // the scope chain to include all ancestor scopes.
+  if (isMetadirectiveEval(eval) && !clauseScopes.empty()) {
+    const semantics::Scope *scope = *clauseScopes.begin();
+    while (scope->kind() != semantics::Scope::Kind::Global) {
+      clauseScopes.insert(scope);
+      scope = &scope->parent();
+    }
+  }
+
   for (const auto *sym : allSymbols) {
     if (semantics::omp::IsPrivatizable(*sym) &&
         !symbolsInNestedRegions.contains(sym) &&
@@ -565,6 +639,14 @@ void DataSharingProcessor::collectSymbols(
                              /*collectSymbols=*/true,
                              /*collectHostAssociatedSymbols=*/true);
 
+  // Collect symbols from spliced nested evaluations for metadirectives.
+  if (isMetadirectiveEval(eval) && eval.hasNestedEvaluations()) {
+    for (auto &nestedEval : eval.getNestedEvaluations())
+      converter.collectSymbolSet(nestedEval, allSymbols, flag,
+                                 /*collectSymbols=*/true,
+                                 /*collectHostAssociatedSymbols=*/true);
+  }
+
   llvm::SetVector<const semantics::Symbol *> symbolsInNestedRegions;
   collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions);
 
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index bc7575547311a..9ca75caf663b1 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4457,6 +4457,95 @@ struct MetadirectiveCandidate {
 };
 } // namespace
 
+/// If the metadirective evaluation already contains a spliced DO loop (from a
+/// prior call), return it. Otherwise, locate the sibling DO loop that
+/// immediately follows the metadirective in the parent evaluation list, splice
+/// it into the metadirective's own evaluation list, and return a pointer to it.
+/// Returns nullptr if no associated DO loop is found.
+static lower::pft::Evaluation *
+spliceAssociatedDoEval(lower::pft::Evaluation &eval) {
+  if (eval.hasNestedEvaluations()) {
+    lower::pft::Evaluation &nested = eval.getNestedEvaluations().back();
+    return nested.getIf<parser::DoConstruct>() ? &nested : nullptr;
+  }
+
+  auto *parentList = eval.parentConstruct
+                         ? eval.parentConstruct->evaluationList.get()
+                         : &eval.getOwningProcedure()->evaluationList;
+  auto metaIt = llvm::find_if(
+      *parentList, [&](lower::pft::Evaluation &e) { return &e == &eval; });
+  assert(metaIt != parentList->end() &&
+         "metadirective eval not found in parent list");
+
+  auto loopIt = std::next(metaIt);
+  while (loopIt != parentList->end() &&
+         (loopIt->isEndStmt() || loopIt->getIf<parser::CompilerDirective>()))
+    ++loopIt;
+
+  if (loopIt == parentList->end() || !loopIt->getIf<parser::DoConstruct>())
+    return nullptr;
+
+  eval.evaluationList->splice(eval.evaluationList->end(), *parentList, loopIt);
+  return &eval.getNestedEvaluations().back();
+}
+
+/// Clear all existing DSA flags on \p sym, then set PreDetermined + \p dsa.
+static void setSymbolDSA(semantics::Symbol &sym, semantics::Symbol::Flag dsa) {
+  using Symbol = semantics::Symbol;
+  Symbol::Flags dataSharingAttributeFlags{
+      Symbol::Flag::OmpShared,       Symbol::Flag::OmpPrivate,
+      Symbol::Flag::OmpFirstPrivate, Symbol::Flag::OmpLastPrivate,
+      Symbol::Flag::OmpReduction,    Symbol::Flag::OmpLinear};
+  sym.flags() &=
+      ~(dataSharingAttributeFlags |
+        Symbol::Flags{Symbol::Flag::OmpExplicit, Symbol::Flag::OmpImplicit,
+                      Symbol::Flag::OmpPreDetermined});
+  sym.flags() |= Symbol::Flags{Symbol::Flag::OmpPreDetermined, dsa};
+}
+
+/// Extract the induction variable symbol from a DO construct.
+static semantics::Symbol *
+getDoConstructIVSymbol(const parser::DoConstruct &doConstruct) {
+  if (const auto &loopCtrl = doConstruct.GetLoopControl())
+    if (auto *bounds = std::get_if<parser::LoopControl::Bounds>(&loopCtrl->u))
+      return bounds->Name().thing.symbol;
+  return nullptr;
+}
+
+/// Mark loop induction variable data-sharing attributes for a
+/// metadirective-selected loop variant. Semantic analysis cannot mark these
+/// because the variant is resolved at lowering time.
+static void
+markMetadirectiveLoopIVs(semantics::SemanticsContext &semaCtx,
+                         const parser::OmpDirectiveSpecification &spec,
+                         lower::pft::Evaluation &loopEval) {
+  using Symbol = semantics::Symbol;
+
+  auto [depth, _] = semantics::omp::GetAffectedNestDepthWithReason(
+      spec, semaCtx.langOptions().OpenMPVersion, &semaCtx);
+  if (!depth || !depth.value || *depth.value <= 0)
+    return;
+
+  int64_t affectedDepth = *depth.value;
+  Symbol::Flag ivDSA;
+  if (!llvm::omp::allSimdSet.test(spec.DirId()))
+    ivDSA = Symbol::Flag::OmpPrivate;
+  else if (affectedDepth == 1 && semaCtx.langOptions().OpenMPVersion < 60)
+    ivDSA = Symbol::Flag::OmpLinear;
+  else
+    ivDSA = Symbol::Flag::OmpLastPrivate;
+
+  lower::pft::Evaluation *doEval = &loopEval;
+  for (int64_t level = 0; level < affectedDepth; ++level) {
+    auto *doConstruct = doEval->getIf<parser::DoConstruct>();
+    assert(doConstruct && "expected associated DO construct");
+    if (semantics::Symbol *sym = getDoConstructIVSymbol(*doConstruct))
+      setSymbolDSA(*sym, ivDSA);
+    if (level + 1 < affectedDepth)
+      doEval = getNestedDoConstruct(*doEval);
+  }
+}
+
 static void genMetadirective(lower::AbstractConverter &converter,
                              lower::SymMap &symTable,
                              semantics::SemanticsContext &semaCtx,
@@ -4591,7 +4680,17 @@ static void genMetadirective(lower::AbstractConverter &converter,
           return llvm::omp::getDirectiveAssociation(item.id) ==
                  llvm::omp::Association::LoopNest;
         })) {
-      TODO(variantLoc, "loop-associated METADIRECTIVE variant");
+      lower::pft::Evaluation *loopEval = spliceAssociatedDoEval(eval);
+      if (!loopEval)
+        TODO(variantLoc, "loop-associated METADIRECTIVE without associated DO");
+      markMetadirectiveLoopIVs(semaCtx, *spec, *loopEval);
+    }
+
+    if (llvm::any_of(queue, [](const auto &item) {
+          return llvm::omp::allTargetSet.test(item.id);
+        })) {
+      TODO(variantLoc,
+           "TARGET construct selected by METADIRECTIVE (host-eval)");
     }
 
     genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 03f1a2045b2a8..25197c79688fc 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -713,6 +713,20 @@ pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval) {
   llvm_unreachable("Expected do loop to be in the nested evaluations");
 }
 
+/// Return true if \p eval holds a metadirective.
+bool isMetadirectiveEval(lower::pft::Evaluation &eval) {
+  if (const auto *decl = eval.getIf<parser::OpenMPDeclarativeConstruct>())
+    return std::holds_alternative<parser::OmpMetadirectiveDirective>(decl->u);
+  if (const auto *ompConstruct = eval.getIf<parser::OpenMPConstruct>()) {
+    if (const auto *standalone =
+            std::get_if<parser::OpenMPStandaloneConstruct>(&ompConstruct->u)) {
+      return std::holds_alternative<parser::OmpMetadirectiveDirective>(
+          standalone->u);
+    }
+  }
+  return false;
+}
+
 /// Populates the sizes vector with values if the given OpenMPConstruct
 /// contains a loop construct with an inner tiling construct.
 void collectTileSizesFromOpenMPConstruct(
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index eb784cc7911a5..4295d07f70420 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -243,6 +243,8 @@ void makeVariantMatchInfo(llvm::omp::VariantMatchInfo &vmi,
                           mlir::Location loc,
                           const parser::ScalarExpr *&dynamicCondExpr);
 
+bool isMetadirectiveEval(lower::pft::Evaluation &eval);
+
 } // namespace omp
 } // namespace lower
 } // namespace Fortran
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
deleted file mode 100644
index 999a8c0839d15..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
+++ /dev/null
@@ -1,12 +0,0 @@
-! 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-no-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-no-loop.f90
new file mode 100644
index 0000000000000..d10b57c493b6d
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-no-loop.f90
@@ -0,0 +1,12 @@
+! Test that a metadirective resolving to a loop-associated variant
+! without an associated DO loop correctly reports a TODO.
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: loop-associated METADIRECTIVE without associated DO
+
+subroutine test_no_associated_do()
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do) &
+  !$omp & default(nothing)
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
new file mode 100644
index 0000000000000..f271cbeffb21f
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
@@ -0,0 +1,15 @@
+! Test that a metadirective variant resolving to a target construct
+! correctly reports a TODO (host-eval support needed).
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: TARGET construct selected by METADIRECTIVE (host-eval)
+
+subroutine test_target_loop()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: target teams distribute parallel do) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-loop.f90 b/flang/test/Lower/OpenMP/metadirective-loop.f90
new file mode 100644
index 0000000000000..80c826a722496
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-loop.f90
@@ -0,0 +1,203 @@
+! Test lowering of OpenMP metadirective with loop-associated variants.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | FileCheck %s
+
+!===----------------------------------------------------------------------===!
+! Basic loop-associated variants via static selection
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do()
+! CHECK:         omp.parallel {
+! CHECK:           omp.wsloop
+! CHECK:             omp.loop_nest
+! CHECK:             omp.yield
+! CHECK:           omp.terminator
+! CHECK:         return
+subroutine test_parallel_do()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_do()
+! CHECK-NOT:     omp.parallel
+! CHECK:         omp.wsloop
+! CHECK:           omp.loop_nest
+! CHECK:           omp.yield
+! CHECK:         return
+subroutine test_do()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_simd()
+! CHECK-NOT:     omp.wsloop
+! CHECK:         omp.simd
+! CHECK:           omp.loop_nest
+! CHECK:           omp.yield
+! CHECK:         return
+subroutine test_simd()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: simd) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_do_simd()
+! CHECK-NOT:     omp.parallel
+! CHECK:         omp.wsloop
+! CHECK:           omp.simd
+! CHECK:             omp.loop_nest
+! CHECK:             omp.yield
+! CHECK:         return
+subroutine test_do_simd()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do simd) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Static mismatch falls through to standalone fallback
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_loop_static_mismatch()
+! CHECK-NOT:     omp.wsloop
+! CHECK-NOT:     omp.loop_nest
+! CHECK:         omp.barrier
+! CHECK:         return
+subroutine test_loop_static_mismatch()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor("unknown")}: parallel do) &
+  !$omp & default(barrier)
+  do i = 1, 100
+  end do
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Dynamic user condition with loop-associated variant
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_loop(
+! CHECK-SAME:    %[[ARG0:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK:         %[[DECL:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK:         %[[LOAD:.*]] = fir.load %[[DECL]]#0
+! CHECK:         %[[COND:.*]] = fir.convert %[[LOAD]] : (!fir.logical<4>) -> i1
+! CHECK:         fir.if %[[COND]] {
+! CHECK:           omp.parallel {
+! CHECK:             omp.wsloop
+! CHECK:               omp.loop_nest
+! CHECK:         } else {
+! CHECK:           omp.simd
+! CHECK:             omp.loop_nest
+! CHECK:         }
+! CHECK:         return
+subroutine test_dynamic_loop(flag)
+  logical, intent(in) :: flag
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(user={condition(flag)}: parallel do) &
+  !$omp & default(simd)
+  do i = 1, 100
+  end do
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Loop-associated variants with clauses
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_schedule()
+! CHECK:         omp.wsloop schedule(static)
+! CHECK:           omp.loop_nest
+! CHECK:         return
+subroutine test_schedule()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do schedule(static)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_reduction()
+! CHECK:         omp.wsloop {{.*}} reduction(@add_reduction_i32
+! CHECK:           omp.loop_nest
+! CHECK:         return
+subroutine test_reduction()
+  integer :: i, s
+  s = 0
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do reduction(+:s)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+    s = s + i
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_private()
+! CHECK:         omp.wsloop private(
+! CHECK:           omp.loop_nest
+! CHECK:         return
+subroutine test_private()
+  integer :: i, x
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do private(x)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+    x = i
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_collapse()
+! CHECK:         omp.wsloop
+! CHECK:           omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2)
+! CHECK:         return
+subroutine test_collapse()
+  integer :: i, j
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do collapse(2)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+    do j = 1, 100
+    end do
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_safelen()
+! CHECK:         omp.simd {{.*}} safelen(4)
+! CHECK:           omp.loop_nest
+! CHECK:         return
+subroutine test_safelen()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: simd safelen(4)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_num_threads()
+! CHECK:         omp.parallel num_threads({{.*}}) {
+! CHECK:           omp.wsloop
+! CHECK:             omp.loop_nest
+! CHECK:         return
+subroutine test_num_threads()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do num_threads(4)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine



More information about the flang-commits mailing list