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

via flang-commits flang-commits at lists.llvm.org
Sun Apr 26 23:52:56 PDT 2026


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

>From 48fc5a021d6795857068b797cd6fa26f69191e99 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] [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 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             | 181 +++++++++++++++-
 flang/lib/Lower/OpenMP/Utils.cpp              | 204 ++++++++++++++++++
 flang/lib/Lower/OpenMP/Utils.h                |  13 ++
 .../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 -
 .../Lower/OpenMP/metadirective-construct.f90  |  30 +++
 .../Lower/OpenMP/metadirective-device-isa.f90 | 162 ++++++++++++++
 .../OpenMP/metadirective-device-kind.f90      |  21 ++
 .../OpenMP/metadirective-implementation.f90   | 121 +++++++++++
 .../Lower/OpenMP/metadirective-static.f90     |  33 +++
 12 files changed, 786 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/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-static.f90

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index bfdf77340c78e..c6d06379e98ea 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4364,11 +4364,190 @@ 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;
+  // 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, 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 b7d1cb8b419fd..8008d4775b3be 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -31,6 +31,7 @@
 #include <flang/Semantics/tools.h>
 #include <flang/Semantics/type.h>
 #include <flang/Utils/OpenMP.h>
+#include <llvm/ADT/APInt.h>
 #include <llvm/ADT/STLExtras.h>
 #include <llvm/ADT/SmallPtrSet.h>
 #include <llvm/ADT/StringRef.h>
@@ -1117,6 +1118,209 @@ 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,
+                          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-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/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..ec301f9991883
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-device-isa.f90
@@ -0,0 +1,162 @@
+! 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
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-static.f90 b/flang/test/Lower/OpenMP/metadirective-static.f90
new file mode 100644
index 0000000000000..5165268cf7cc2
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-static.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



More information about the flang-commits mailing list