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

via flang-commits flang-commits at lists.llvm.org
Wed Apr 22 22:12:29 PDT 2026


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

This patch implements OpenMP 5.0 metadirective (static selection among
when clauses), nothing from OpenMP 5.1, and otherwise from OpenMP 5.2.

When all context selectors can be evaluated statically (vendor,
device kind, ISA, construct traits, constant user conditions),
the best-matching variant is selected during flang lowering by taking
advantage of llvm context selector infrastructure.

Dynamic (run-time) user conditions will be handled by a follow-up patch.

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

Assisted with Copilot and GPT 5.4.


>From 649c923457cc04241f35f147010624bc76bb11ad Mon Sep 17 00:00:00 2001
From: "Chi-Chun, Chen" <chichun.chen at hpe.com>
Date: Wed, 8 Apr 2026 17:14:31 -0500
Subject: [PATCH] [flang][OpenMP] Support lowering of metadirective (part 1)

This patch implements OpenMP 5.0 metadirective (static selection among
when clauses), nothing from OpenMP 5.1, and otherwise from OpenMP 5.2.

When all context selectors can be evaluated statically (vendor,
device kind, ISA, construct traits, constant user conditions),
the best-matching variant is selected during flang lowering by taking
advantage of llvm context selector infrastructure.

Dynamic (run-time) user conditions will be handled by a follow-up patch.

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

Assisted with Copilot and GPT 5.4.
---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 205 +++++++-
 flang/lib/Lower/OpenMP/Utils.cpp              | 207 ++++++++
 flang/lib/Lower/OpenMP/Utils.h                |  13 +
 .../OpenMP/Todo/metadirective-dynamic.f90     |  10 +
 .../Lower/OpenMP/Todo/metadirective-exec.f90  |   9 -
 .../Lower/OpenMP/Todo/metadirective-spec.f90  |   9 -
 .../Lower/OpenMP/metadirective-construct.f90  | 459 ++++++++++++++++++
 .../metadirective-device-arch-codegen.f90     |  38 ++
 .../Lower/OpenMP/metadirective-device-isa.f90 | 235 +++++++++
 .../OpenMP/metadirective-device-kind.f90      | 108 +++++
 .../OpenMP/metadirective-implementation.f90   | 224 +++++++++
 .../test/Lower/OpenMP/metadirective-loop.f90  | 165 +++++++
 .../Lower/OpenMP/metadirective-static.f90     |  34 ++
 .../OpenMP/metadirective-target-device.f90    |  52 ++
 14 files changed, 1749 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
 delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-spec.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-construct.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-device-arch-codegen.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-loop.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-static.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-target-device.f90

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index bfdf77340c78e..da787b2d93fcb 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4364,11 +4364,214 @@ 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)
+      : 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;
+  // nullptr:
+  //  1. implicit `nothing` variant.
+  //  2. no explicit otherwise/default clause is present.
+  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 if there's 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, 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());
+    }
+  }
+
+  // For loop-associated metadirective variants, the associated DO construct is
+  // a sibling evaluation in the PFT rather than a nested child. Splice that
+  // sibling DO evaluation into the metadirective evaluation's nested list so
+  // loop lowering can find it via getNestedDoConstruct().
+  auto spliceAssociatedDoEval = [](lower::pft::Evaluation &eval) {
+    if (eval.hasNestedEvaluations())
+      return;
+    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::find_if(std::next(metaIt), parentList->end(),
+                               [](lower::pft::Evaluation &e) {
+                                 return e.getIf<parser::DoConstruct>();
+                               });
+    if (loopIt != parentList->end())
+      eval.evaluationList->splice(eval.evaluationList->end(), *parentList,
+                                  loopIt);
+  };
+
+  // Lower a single resolved candidate. Splice the associated sibling DO
+  // evaluation for loop-associated variants.
+  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;
+        }))
+      spliceAssociatedDoEval(eval);
+
+    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 b7d1cb8b419fd..3e0179068429f 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1117,6 +1117,213 @@ mlir::Value genIteratorCoordinate(Fortran::lower::AbstractConverter &converter,
                                   /*typeparams=*/mlir::ValueRange{});
 }
 
+/// Map parse-tree OmpTraitSetSelectorName to the corresponding
+/// llvm::omp::TraitSet enum.
+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");
+}
+
+/// Map parse-tree OmpTraitSelectorName to the corresponding
+/// llvm::omp::TraitSelector enum.
+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;
+    }
+
+    // Unknown property, add as ISA-like raw string.
+    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,
+                          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);
+
+      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..6b5c36ffca747 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,18 @@ 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,
+                          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-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/metadirective-construct.f90 b/flang/test/Lower/OpenMP/metadirective-construct.f90
new file mode 100644
index 0000000000000..f5d90e2352ffc
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-construct.f90
@@ -0,0 +1,459 @@
+! Test metadirective with construct trait selectors.
+
+! RUN: split-file %s %t
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 %t/default.f90 -o - | FileCheck --check-prefix=DEFAULT %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %t/default.f90 -o - | FileCheck --check-prefix=DEFAULT %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 %t/otherwise.f90 -o - | FileCheck --check-prefix=OTHERWISE %s
+
+!--- default.f90
+subroutine test_construct_parallel()
+  !$omp parallel
+  !$omp metadirective &
+  !$omp & when(construct={parallel}: taskwait) &
+  !$omp & default(nothing)
+  !$omp end parallel
+end subroutine
+
+subroutine test_construct_single()
+  !$omp parallel
+  !$omp single
+  !$omp metadirective &
+  !$omp & when(construct={single}: taskwait) &
+  !$omp & default(nothing)
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
+subroutine test_construct_no_match()
+  !$omp parallel
+  !$omp metadirective &
+  !$omp & when(construct={target}: taskyield) &
+  !$omp & default(nothing)
+  !$omp end parallel
+end subroutine
+
+subroutine test_construct_parallel_do()
+  integer :: i
+  ! Inside parallel do — construct={parallel do} should match in the loop body.
+  !$omp parallel do
+  do i = 1, 10
+    !$omp metadirective &
+    !$omp & when(construct={parallel do}: taskwait) &
+    !$omp & default(nothing)
+  end do
+  !$omp end parallel do
+end subroutine
+
+subroutine test_construct_target()
+  !$omp target
+  !$omp metadirective &
+  !$omp & when(construct={target}: taskwait) &
+  !$omp & default(nothing)
+  !$omp end target
+end subroutine
+
+subroutine test_construct_target_parallel()
+  !$omp target parallel
+  !$omp metadirective &
+  !$omp & when(construct={target parallel}: taskwait) &
+  !$omp & default(nothing)
+  !$omp end target parallel
+end subroutine
+
+subroutine test_construct_target_parallel_do()
+  integer :: i
+  !$omp target parallel do
+  do i = 1, 10
+    !$omp metadirective &
+    !$omp & when(construct={target parallel do}: taskyield) &
+    !$omp & default(nothing)
+  end do
+  !$omp end target parallel do
+end subroutine
+
+subroutine test_construct_target_teams()
+  !$omp target teams
+  !$omp metadirective &
+  !$omp & when(construct={target teams}: taskwait) &
+  !$omp & default(nothing)
+  !$omp end target teams
+end subroutine
+
+subroutine test_construct_target_teams_distribute()
+  integer :: i
+  !$omp target teams distribute
+  do i = 1, 10
+    !$omp metadirective &
+    !$omp & when(construct={target teams distribute}: taskyield) &
+    !$omp & default(nothing)
+  end do
+  !$omp end target teams distribute
+end subroutine
+
+subroutine test_construct_target_teams_distribute_parallel_do()
+  integer :: i
+  !$omp target teams distribute parallel do
+  do i = 1, 10
+    !$omp metadirective &
+    !$omp & when(construct={target teams distribute parallel do}: taskwait) &
+    !$omp & default(nothing)
+  end do
+  !$omp end target teams distribute parallel do
+end subroutine
+
+subroutine test_construct_multi_region()
+  integer :: i
+  ! The earlier parallel do has ended before the metadirective and must not
+  ! satisfy construct={parallel}. Only the second parallel actively encloses
+  ! the metadirective, so that is the construct that should match.
+  !$omp parallel do
+  do i = 1, 10
+  end do
+  !$omp end parallel do
+  !$omp parallel
+  !$omp metadirective &
+  !$omp & when(construct={parallel}: taskyield) &
+  !$omp & default(nothing)
+  !$omp end parallel
+end subroutine
+
+subroutine test_construct_sections()
+  !$omp parallel sections
+  !$omp section
+  !$omp metadirective &
+  !$omp & when(construct={sections}: taskwait) &
+  !$omp & default(nothing)
+  !$omp end parallel sections
+end subroutine
+
+subroutine test_construct_critical()
+  !$omp critical
+  !$omp metadirective &
+  !$omp & when(construct={critical}: taskyield) &
+  !$omp & default(nothing)
+  !$omp end critical
+end subroutine
+
+subroutine test_construct_not_enclosing()
+  !$omp metadirective &
+  !$omp & when(construct={parallel}: taskwait) &
+  !$omp & default(nothing)
+end subroutine
+
+!--- otherwise.f90
+subroutine test_construct_parallel()
+  !$omp parallel
+  !$omp metadirective &
+  !$omp & when(construct={parallel}: taskwait) &
+  !$omp & otherwise(nothing)
+  !$omp end parallel
+end subroutine
+
+subroutine test_construct_single()
+  !$omp parallel
+  !$omp single
+  !$omp metadirective &
+  !$omp & when(construct={single}: taskwait) &
+  !$omp & otherwise(nothing)
+  !$omp end single
+  !$omp end parallel
+end subroutine
+
+subroutine test_construct_no_match()
+  ! Should lower to nothing
+  !$omp parallel
+  !$omp metadirective &
+  !$omp & when(construct={target}: taskyield) &
+  !$omp & otherwise(nothing)
+  !$omp end parallel
+end subroutine
+
+subroutine test_construct_parallel_do()
+  integer :: i
+  !$omp parallel do
+  do i = 1, 10
+    !$omp metadirective &
+    !$omp & when(construct={parallel do}: taskwait) &
+    !$omp & otherwise(nothing)
+  end do
+  !$omp end parallel do
+end subroutine
+
+subroutine test_construct_target()
+  !$omp target
+  !$omp metadirective &
+  !$omp & when(construct={target}: taskwait) &
+  !$omp & otherwise(nothing)
+  !$omp end target
+end subroutine
+
+subroutine test_construct_target_parallel()
+  !$omp target parallel
+  !$omp metadirective &
+  !$omp & when(construct={target parallel}: taskwait) &
+  !$omp & otherwise(nothing)
+  !$omp end target parallel
+end subroutine
+
+subroutine test_construct_target_parallel_do()
+  integer :: i
+  !$omp target parallel do
+  do i = 1, 10
+    !$omp metadirective &
+    !$omp & when(construct={target parallel do}: taskyield) &
+    !$omp & otherwise(nothing)
+  end do
+  !$omp end target parallel do
+end subroutine
+
+subroutine test_construct_target_teams()
+  !$omp target teams
+  !$omp metadirective &
+  !$omp & when(construct={target teams}: taskwait) &
+  !$omp & otherwise(nothing)
+  !$omp end target teams
+end subroutine
+
+subroutine test_construct_target_teams_distribute()
+  integer :: i
+  !$omp target teams distribute
+  do i = 1, 10
+    !$omp metadirective &
+    !$omp & when(construct={target teams distribute}: taskyield) &
+    !$omp & otherwise(nothing)
+  end do
+  !$omp end target teams distribute
+end subroutine
+
+subroutine test_construct_target_teams_distribute_parallel_do()
+  integer :: i
+  !$omp target teams distribute parallel do
+  do i = 1, 10
+    !$omp metadirective &
+    !$omp & when(construct={target teams distribute parallel do}: taskwait) &
+    !$omp & otherwise(nothing)
+  end do
+  !$omp end target teams distribute parallel do
+end subroutine
+
+subroutine test_construct_sections()
+  !$omp parallel sections
+  !$omp section
+  !$omp metadirective &
+  !$omp & when(construct={sections}: taskwait) &
+  !$omp & otherwise(nothing)
+  !$omp end parallel sections
+end subroutine
+
+subroutine test_construct_critical()
+  !$omp critical
+  !$omp metadirective &
+  !$omp & when(construct={critical}: taskyield) &
+  !$omp & otherwise(nothing)
+  !$omp end critical
+end subroutine
+
+subroutine test_construct_not_enclosing()
+  ! No enclosing parallel, fall through to otherwise(taskwait).
+  !$omp metadirective &
+  !$omp & when(construct={parallel}: nothing) &
+  !$omp & otherwise(taskwait)
+end subroutine
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_parallel()
+! DEFAULT:         omp.parallel {
+! DEFAULT:           omp.taskwait
+! DEFAULT:           omp.terminator
+! DEFAULT:         }
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_single()
+! DEFAULT:         omp.parallel {
+! DEFAULT:           omp.single {
+! DEFAULT:             omp.taskwait
+! DEFAULT:             omp.terminator
+! DEFAULT:           omp.terminator
+! DEFAULT:         }
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_no_match()
+! DEFAULT-NOT:     omp.parallel
+! DEFAULT-NOT:     omp.taskyield
+! DEFAULT:         return
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_parallel_do()
+! DEFAULT:         omp.parallel {
+! DEFAULT:           omp.wsloop
+! DEFAULT:             omp.loop_nest
+! DEFAULT:               omp.taskwait
+! DEFAULT:               omp.yield
+! DEFAULT:           omp.terminator
+! DEFAULT:         }
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_target()
+! DEFAULT:         omp.target {
+! DEFAULT:           omp.taskwait
+! DEFAULT:           omp.terminator
+! DEFAULT:         }
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_target_parallel()
+! DEFAULT:         omp.target {
+! DEFAULT:           omp.parallel {
+! DEFAULT:             omp.taskwait
+! DEFAULT:             omp.terminator
+! DEFAULT:           omp.terminator
+! DEFAULT:         }
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_target_parallel_do()
+! DEFAULT:         omp.target
+! DEFAULT:           omp.parallel {
+! DEFAULT:             omp.wsloop
+! DEFAULT:               omp.loop_nest
+! DEFAULT:                 omp.taskyield
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_target_teams()
+! DEFAULT:         omp.target {
+! DEFAULT:           omp.teams {
+! DEFAULT:             omp.taskwait
+! DEFAULT:             omp.terminator
+! DEFAULT:           omp.terminator
+! DEFAULT:         }
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_target_teams_distribute()
+! DEFAULT:         omp.target
+! DEFAULT:           omp.teams {
+! DEFAULT:             omp.distribute
+! DEFAULT:               omp.loop_nest
+! DEFAULT:                 omp.taskyield
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_target_teams_distribute_parallel_do()
+! DEFAULT:         omp.target
+! DEFAULT:           omp.teams {
+! DEFAULT:             omp.parallel
+! DEFAULT:               omp.distribute
+! DEFAULT:                 omp.wsloop
+! DEFAULT:                   omp.loop_nest
+! DEFAULT:                     omp.taskwait
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_multi_region()
+! DEFAULT:         omp.parallel {
+! DEFAULT:           omp.wsloop
+! DEFAULT:             omp.loop_nest
+! DEFAULT:           omp.terminator
+! DEFAULT:         }
+! DEFAULT:         omp.parallel {
+! DEFAULT:           omp.taskyield
+! DEFAULT:           omp.terminator
+! DEFAULT:         }
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_sections()
+! DEFAULT:         omp.parallel {
+! DEFAULT:           omp.sections {
+! DEFAULT:             omp.section {
+! DEFAULT:               omp.taskwait
+! DEFAULT:               omp.terminator
+! DEFAULT:             omp.terminator
+! DEFAULT:           omp.terminator
+! DEFAULT:         }
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_critical()
+! DEFAULT:         omp.critical {
+! DEFAULT:           omp.taskyield
+! DEFAULT:           omp.terminator
+! DEFAULT:         }
+
+! DEFAULT-LABEL: func.func @_QPtest_construct_not_enclosing()
+! DEFAULT-NOT:     omp.taskwait
+! DEFAULT:         return
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_parallel()
+! OTHERWISE:         omp.parallel {
+! OTHERWISE:           omp.taskwait
+! OTHERWISE:           omp.terminator
+! OTHERWISE:         }
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_single()
+! OTHERWISE:         omp.parallel {
+! OTHERWISE:           omp.single {
+! OTHERWISE:             omp.taskwait
+! OTHERWISE:             omp.terminator
+! OTHERWISE:           omp.terminator
+! OTHERWISE:         }
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_no_match()
+! OTHERWISE-NOT:     omp.parallel
+! OTHERWISE-NOT:     omp.taskyield
+! OTHERWISE:         return
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_parallel_do()
+! OTHERWISE:         omp.parallel {
+! OTHERWISE:           omp.wsloop
+! OTHERWISE:             omp.loop_nest
+! OTHERWISE:               omp.taskwait
+! OTHERWISE:               omp.yield
+! OTHERWISE:           omp.terminator
+! OTHERWISE:         }
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_target()
+! OTHERWISE:         omp.target {
+! OTHERWISE:           omp.taskwait
+! OTHERWISE:           omp.terminator
+! OTHERWISE:         }
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_target_parallel()
+! OTHERWISE:         omp.target {
+! OTHERWISE:           omp.parallel {
+! OTHERWISE:             omp.taskwait
+! OTHERWISE:             omp.terminator
+! OTHERWISE:           omp.terminator
+! OTHERWISE:         }
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_target_parallel_do()
+! OTHERWISE:         omp.target
+! OTHERWISE:           omp.parallel {
+! OTHERWISE:             omp.wsloop
+! OTHERWISE:               omp.loop_nest
+! OTHERWISE:                 omp.taskyield
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_target_teams()
+! OTHERWISE:         omp.target {
+! OTHERWISE:           omp.teams {
+! OTHERWISE:             omp.taskwait
+! OTHERWISE:             omp.terminator
+! OTHERWISE:           omp.terminator
+! OTHERWISE:         }
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_target_teams_distribute()
+! OTHERWISE:         omp.target
+! OTHERWISE:           omp.teams {
+! OTHERWISE:             omp.distribute
+! OTHERWISE:               omp.loop_nest
+! OTHERWISE:                 omp.taskyield
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_target_teams_distribute_parallel_do()
+! OTHERWISE:         omp.target
+! OTHERWISE:           omp.teams {
+! OTHERWISE:             omp.parallel
+! OTHERWISE:               omp.distribute
+! OTHERWISE:                 omp.wsloop
+! OTHERWISE:                   omp.loop_nest
+! OTHERWISE:                     omp.taskwait
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_sections()
+! OTHERWISE:         omp.parallel {
+! OTHERWISE:           omp.sections {
+! OTHERWISE:             omp.section {
+! OTHERWISE:               omp.taskwait
+! OTHERWISE:               omp.terminator
+! OTHERWISE:             omp.terminator
+! OTHERWISE:           omp.terminator
+! OTHERWISE:         }
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_critical()
+! OTHERWISE:         omp.critical {
+! OTHERWISE:           omp.taskyield
+! OTHERWISE:           omp.terminator
+! OTHERWISE:         }
+
+! OTHERWISE-LABEL: func.func @_QPtest_construct_not_enclosing()
+! OTHERWISE:         omp.taskwait
+! OTHERWISE:         return
diff --git a/flang/test/Lower/OpenMP/metadirective-device-arch-codegen.f90 b/flang/test/Lower/OpenMP/metadirective-device-arch-codegen.f90
new file mode 100644
index 0000000000000..bf14b9a23e05d
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-device-arch-codegen.f90
@@ -0,0 +1,38 @@
+! Test lowering of metadirective target_device arch selection inside a target
+! region. This test is inspired by SOLLVE tests: test_metadirective_arch_is_nvidia.F90
+
+! RUN: %flang_fc1 -cpp -DGPU_ARCH=amdgcn -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck --check-prefixes=HOST,COMMON %s
+! RUN: %flang_fc1 -cpp -DGPU_ARCH=spirv64 -fopenmp -emit-hlfir -fopenmp-version=50 %s -o - | FileCheck --check-prefixes=HOST,COMMON %s
+! RUN: %if amdgpu-registered-target %{ %flang_fc1 -cpp -DGPU_ARCH=amdgcn -triple amdgcn-amd-amdhsa -fopenmp -emit-hlfir -fopenmp-version=50 -fopenmp-is-target-device %s -o - | FileCheck --check-prefixes=DEVICE,COMMON %s %}
+! RUN: %if spirv-registered-target %{ %flang_fc1 -cpp -DGPU_ARCH=spirv64 -triple spirv64-intel -fopenmp -emit-hlfir -fopenmp-version=50 -fopenmp-is-target-device %s -o - | FileCheck --check-prefixes=DEVICE,COMMON %s %}
+
+! COMMON-LABEL: func.func @_QPmetadirective_device_arch_codegen()
+
+! HOST:         omp.target
+! HOST-NOT:     omp.teams
+! HOST:         omp.parallel
+! HOST-NOT:     omp.distribute
+! HOST:         omp.wsloop
+! HOST:         omp.loop_nest
+
+! DEVICE:       omp.target
+! DEVICE:       omp.teams
+! DEVICE:       omp.parallel
+! DEVICE:       omp.distribute
+! DEVICE:       omp.wsloop
+! DEVICE:       omp.loop_nest
+subroutine metadirective_device_arch_codegen()
+  integer, parameter :: n = 1024
+  integer :: i
+  integer :: default_device
+  real :: v1(n), v2(n), v3(n)
+
+  !$omp target map(to:v1, v2) map(from:v3) device(default_device)
+    !$omp metadirective &
+    !$omp & when(target_device={arch(GPU_ARCH)}: teams distribute parallel do) &
+    !$omp & default(parallel do)
+    do i = 1, n
+      v3(i) = v1(i) * v2(i)
+    end do
+  !$omp end target
+end subroutine metadirective_device_arch_codegen
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..235d4b168eeaa
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-device-isa.f90
@@ -0,0 +1,235 @@
+! 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
+! RUN: %if amdgpu-registered-target %{ %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=50 -triple amdgcn-amd-amdhsa -target-cpu gfx906 -fopenmp-is-target-device %s -o - | FileCheck --check-prefix=AMDGCN %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_parallel_do()
+! NEON:         omp.parallel
+! NEON:           omp.wsloop
+! SVE-LABEL: func.func @_QPtest_isa_parallel_do()
+! SVE:         omp.parallel
+! SVE:           omp.wsloop
+! SSE-LABEL: func.func @_QPtest_isa_parallel_do()
+! SSE-NOT:     omp.parallel
+! SSE:         omp.wsloop
+! AVX-LABEL: func.func @_QPtest_isa_parallel_do()
+! AVX-NOT:     omp.parallel
+! AVX:         omp.wsloop
+! NONE-LABEL: func.func @_QPtest_isa_parallel_do()
+! NONE-NOT:     omp.parallel
+! NONE:         omp.wsloop
+subroutine test_isa_parallel_do()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(device={isa("neon")}: parallel do) &
+#ifdef OMP_52
+  !$omp & otherwise(do)
+#else
+  !$omp & default(do)
+#endif
+  do i = 1, 10
+  end do
+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
+
+! Below two functions was ported from clang/test/OpenMP/metadirective_device_isa_codegen_amdgcn.cpp
+
+! AMDGCN-LABEL: func.func @_QPtest_amdgcn_device_isa_selected()
+! AMDGCN:         omp.target
+! AMDGCN:           omp.parallel {
+! AMDGCN:             omp.wsloop {
+! AMDGCN:               omp.loop_nest
+
+! AMDGCN-LABEL: func.func @_QPtest_amdgcn_device_isa_not_selected()
+! AMDGCN:         omp.target
+! AMDGCN-NOT:     omp.parallel
+! AMDGCN:           omp.wsloop {
+! AMDGCN:             omp.loop_nest
+subroutine test_amdgcn_device_isa_selected()
+  integer, parameter :: n = 32
+  integer :: i
+  integer :: values(n)
+
+  !$omp target map(tofrom: values)
+    !$omp metadirective &
+    !$omp & when(device={isa("dpp")}: parallel do) &
+    !$omp & default(do)
+    do i = 1, n
+      values(i) = i
+    end do
+  !$omp end target
+end subroutine
+
+subroutine test_amdgcn_device_isa_not_selected()
+  integer, parameter :: n = 32
+  integer :: i
+  integer :: values(n)
+
+  !$omp target map(tofrom: values)
+    !$omp metadirective &
+    !$omp & when(device={isa("sse")}: parallel do) &
+    !$omp & when(device={isa("another-unsupported-gpu-feature")}: parallel do) &
+    !$omp & default(do)
+    do i = 1, n
+      values(i) = i
+    end do
+  !$omp end target
+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..bcfc8cb950a1a
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-device-kind.f90
@@ -0,0 +1,108 @@
+! 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
+
+! CHECK-LABEL: func.func @_QPtest_device_kind_any_parallel_do()
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+subroutine test_device_kind_any_parallel_do()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(device={kind(any)}: parallel do) &
+  !$omp & default(do)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_device_kind_host_cpu_parallel_do_num_threads()
+! CHECK:         omp.parallel num_threads(
+! CHECK:           omp.wsloop
+subroutine test_device_kind_host_cpu_parallel_do_num_threads()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(device={kind(host, cpu)}: parallel do num_threads(4)) &
+  !$omp & default(do)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_device_kind_host_parallel_do()
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+subroutine test_device_kind_host_parallel_do()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(device={kind(host)}: parallel do) &
+  !$omp & default(do)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_device_kind_cpu_second_match()
+! CHECK-NOT:     omp.target
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+subroutine test_device_kind_cpu_second_match()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(device={kind(nohost, gpu)}: nothing) &
+  !$omp & when(device={kind(cpu)}: parallel do) &
+  !$omp & default(do)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_device_kind_any_cpu_parallel_do()
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+subroutine test_device_kind_any_cpu_parallel_do()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(device={kind(any, cpu)}: parallel do) &
+  !$omp & default(do)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_device_kind_any_host_parallel_do()
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+subroutine test_device_kind_any_host_parallel_do()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(device={kind(any, host)}: parallel do) &
+  !$omp & default(do)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_device_kind_gpu_fallback_parallel_do()
+! CHECK-NOT:     omp.target
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+subroutine test_device_kind_gpu_fallback_parallel_do()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(device={kind(gpu)}: target parallel do) &
+  !$omp & default(parallel do)
+  do i = 1, 100
+  end do
+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..084063c76ec81
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-implementation.f90
@@ -0,0 +1,224 @@
+! 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
+
+! Below is ported from clang/test/OpenMP/metadirective_implementation_codegen.c
+
+! CHECK-LABEL: func.func @_QPtest_implementation_vendor_score_device_cpu()
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+subroutine test_implementation_vendor_score_device_cpu()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(score(0): llvm)}, device={kind(cpu)}: parallel do) &
+#ifdef OMP_52
+  !$omp & otherwise(target teams distribute parallel do)
+#else
+  !$omp & default(target teams distribute parallel do)
+#endif
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_implementation_second_when_matches()
+! CHECK-NOT:     omp.target
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+subroutine test_implementation_second_when_matches()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(device={kind(gpu)}: target teams distribute parallel do) &
+  !$omp & when(implementation={vendor(llvm)}: parallel do) &
+#ifdef OMP_52
+  !$omp & otherwise(nothing)
+#else
+  !$omp & default(nothing)
+#endif
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_implementation_default_before_when()
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+subroutine test_implementation_default_before_when()
+  integer :: i
+  !$omp metadirective &
+#ifdef OMP_52
+  !$omp & otherwise(do) &
+#else
+  !$omp & default(do) &
+#endif
+  !$omp & when(implementation={vendor(score(5): llvm)}, device={kind(cpu, host)}: parallel do)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_implementation_extension_match_all()
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+! CHECK-NOT:     omp.simd
+subroutine test_implementation_extension_match_all()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={extension(match_all)}: parallel do) &
+#ifdef OMP_52
+  !$omp & otherwise(do simd)
+#else
+  !$omp & default(do simd)
+#endif
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_implementation_extension_match_any()
+! CHECK-NOT:     omp.parallel
+! CHECK:         omp.wsloop
+! CHECK:           omp.simd
+subroutine test_implementation_extension_match_any()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={extension(match_any)}: parallel do) &
+#ifdef OMP_52
+  !$omp & otherwise(do simd)
+#else
+  !$omp & default(do simd)
+#endif
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_implementation_extension_match_none()
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+! CHECK-NOT:     omp.simd
+subroutine test_implementation_extension_match_none()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={extension(match_none)}: parallel do) &
+#ifdef OMP_52
+  !$omp & otherwise(do simd)
+#else
+  !$omp & default(do simd)
+#endif
+  do i = 1, 100
+  end do
+end subroutine
\ No newline at end of file
diff --git a/flang/test/Lower/OpenMP/metadirective-loop.f90 b/flang/test/Lower/OpenMP/metadirective-loop.f90
new file mode 100644
index 0000000000000..4f679f9d38b0b
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-loop.f90
@@ -0,0 +1,165 @@
+! Test lowering of metadirective with loop-associated directive variants.
+
+! 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 @_QPtest_loop_vendor_match
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+! CHECK:             omp.loop_nest
+! CHECK-NOT:    fir.if
+subroutine test_loop_vendor_match(a, n)
+  real, intent(inout) :: a(:)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp   when(implementation={vendor(llvm)}: parallel do) &
+#ifdef OMP_52
+  !$omp   otherwise(do)
+#else
+  !$omp   default(do)
+#endif
+  do i = 1, n
+    a(i) = a(i) + 1.0
+  end do
+end subroutine
+
+! Vendor does not match, fallback to default(do).
+! CHECK-LABEL: func @_QPtest_loop_vendor_no_match
+! CHECK-NOT:     omp.parallel
+! CHECK:         omp.wsloop
+! CHECK:           omp.loop_nest
+subroutine test_loop_vendor_no_match(b, n)
+  real, intent(inout) :: b(:)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp   when(implementation={vendor("unknown")}: parallel do) &
+#ifdef OMP_52
+  !$omp   otherwise(do)
+#else
+  !$omp   default(do)
+#endif
+  do i = 1, n
+    b(i) = b(i) * 2.0
+  end do
+end subroutine
+
+! Vendor does not match, fallback to parallel do.
+! CHECK-LABEL: func @_QPtest_loop_parallel_do_fallback
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+! CHECK:             omp.loop_nest
+! CHECK-NOT:    omp.simd
+subroutine test_loop_parallel_do_fallback(c, n)
+  real, intent(inout) :: c(:)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp   when(implementation={vendor("unknown")}: do) &
+#ifdef OMP_52
+  !$omp   otherwise(parallel do)
+#else
+  !$omp   default(parallel do)
+#endif
+  do i = 1, n
+    c(i) = c(i) - 1.0
+  end do
+end subroutine
+
+! Vendor matches, select simd directly.
+! CHECK-LABEL: func @_QPtest_loop_simd_match
+! CHECK-NOT:     omp.parallel
+! CHECK-NOT:     omp.wsloop
+! CHECK:         omp.simd
+! CHECK:           omp.loop_nest
+subroutine test_loop_simd_match(d, n)
+  real, intent(inout) :: d(:)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp   when(implementation={vendor(llvm)}: simd) &
+#ifdef OMP_52
+  !$omp   otherwise(do)
+#else
+  !$omp   default(do)
+#endif
+  do i = 1, n
+    d(i) = d(i) + 3.0
+  end do
+end subroutine
+
+! Vendor does not match, fallback to simd.
+! CHECK-LABEL: func @_QPtest_loop_simd_fallback
+! CHECK-NOT:     omp.parallel
+! CHECK-NOT:     omp.wsloop
+! CHECK:         omp.simd
+! CHECK:           omp.loop_nest
+subroutine test_loop_simd_fallback(g, n)
+  real, intent(inout) :: g(:)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp   when(implementation={vendor("gnu")}: do) &
+#ifdef OMP_52
+  !$omp   otherwise(simd)
+#else
+  !$omp   default(simd)
+#endif
+  do i = 1, n
+    g(i) = g(i) + 5.0
+  end do
+end subroutine
+
+! Vendor does not match, fallback to default(do simd).
+! CHECK-LABEL: func @_QPtest_loop_do_simd_fallback
+! CHECK-NOT:     omp.parallel
+! CHECK:         omp.wsloop
+! CHECK:           omp.simd
+! CHECK:             omp.loop_nest
+subroutine test_loop_do_simd_fallback(e, n)
+  real, intent(inout) :: e(:)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp   when(implementation={vendor("nvidia")}: parallel do) &
+#ifdef OMP_52
+  !$omp   otherwise(do simd)
+#else
+  !$omp   default(do simd)
+#endif
+  do i = 1, n
+    e(i) = e(i) - 2.0
+  end do
+end subroutine
+
+! Vendor matches, select the composite parallel do simd.
+! CHECK-LABEL: func @_QPtest_loop_parallel_do_simd_match
+! CHECK:         omp.parallel
+! CHECK:           omp.wsloop
+! CHECK:             omp.simd
+! CHECK:               omp.loop_nest
+subroutine test_loop_parallel_do_simd_match(f, n)
+  real, intent(inout) :: f(:)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp   when(implementation={vendor(llvm)}: parallel do simd) &
+#ifdef OMP_52
+  !$omp   otherwise(do)
+#else
+  !$omp   default(do)
+#endif
+  do i = 1, n
+    f(i) = f(i) * 4.0
+  end do
+end subroutine
+
+! No match and no default, implicit nothing; loop is plain sequential.
+! CHECK-LABEL: func @_QPtest_loop_no_default
+! CHECK-NOT:     omp.parallel
+! CHECK-NOT:     omp.wsloop
+! CHECK:         fir.do_loop
+subroutine test_loop_no_default(h, n)
+  real, intent(inout) :: h(:)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp   when(implementation={vendor("unknown")}: parallel do)
+  do i = 1, n
+    h(i) = h(i) - 1.0
+  end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-static.f90 b/flang/test/Lower/OpenMP/metadirective-static.f90
new file mode 100644
index 0000000000000..113e3d80f3c07
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-static.f90
@@ -0,0 +1,34 @@
+! 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
+
diff --git a/flang/test/Lower/OpenMP/metadirective-target-device.f90 b/flang/test/Lower/OpenMP/metadirective-target-device.f90
new file mode 100644
index 0000000000000..36bce026af515
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-target-device.f90
@@ -0,0 +1,52 @@
+! REQUIRES: amdgpu-registered-target
+
+! Test metadirective with target_device trait selectors.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 %s -o - | FileCheck --check-prefix=HOST %s
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=52 -triple amdgcn-amd-amdhsa -fopenmp-is-target-device %s -o - | FileCheck --check-prefix=AMDGCN %s
+
+! HOST-LABEL: func.func @_QPtest_target_device_kind_gpu()
+! HOST-NOT:     omp.target
+! HOST:         omp.parallel
+! HOST-NOT:     omp.distribute
+! HOST:         omp.wsloop
+! AMDGCN-LABEL: func.func @_QPtest_target_device_kind_gpu()
+! AMDGCN-NOT:     omp.taskwait
+! AMDGCN:         omp.target
+! AMDGCN:         omp.teams
+! AMDGCN:         omp.parallel
+! AMDGCN:         omp.distribute
+! AMDGCN:         omp.wsloop
+subroutine test_target_device_kind_gpu(a, n)
+  real, intent(inout) :: a(:)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp & when(target_device={kind(gpu)}: target teams distribute parallel do) &
+  !$omp & otherwise(parallel do)
+  do i = 1, n
+    a(i) = a(i) + 1.0
+  end do
+end subroutine
+
+! HOST-LABEL: func.func @_QPtest_target_device_arch_amdgcn()
+! HOST-NOT:     omp.target
+! HOST-NOT:     omp.parallel
+! HOST:         omp.taskwait
+! HOST:         fir.do_loop
+! AMDGCN-LABEL: func.func @_QPtest_target_device_arch_amdgcn()
+! AMDGCN-NOT:     omp.taskwait
+! AMDGCN:         omp.target
+! AMDGCN:         omp.teams
+! AMDGCN:         omp.parallel
+! AMDGCN:         omp.distribute
+! AMDGCN:         omp.wsloop
+subroutine test_target_device_arch_amdgcn(a, n)
+  real, intent(inout) :: a(:)
+  integer, intent(in) :: n
+  !$omp metadirective &
+  !$omp & when(target_device={arch(amdgcn)}: target teams distribute parallel do) &
+  !$omp & otherwise(taskwait)
+  do i = 1, n
+    a(i) = a(i) * 2.0
+  end do
+end subroutine
\ No newline at end of file



More information about the flang-commits mailing list