[flang-commits] [flang] [flang][OpenMP] Support lowering of metadirective (part 3) (PR #208081)

via flang-commits flang-commits at lists.llvm.org
Tue Jul 7 12:51:34 PDT 2026


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

Enable lowering of metadirectives that resolve to a loop-associated
variant such as `do`, `simd`, `parallel do`, and `do simd`. The
associated DO construct is made available to the selected variant so the
existing OpenMP loop lowering can process it. A standalone metadirective
uses the following sibling DO, and a begin/end metadirective uses the
first substantive evaluation nested in its block.

For example, when the `vendor(llvm)` selector matches, the `parallel do`
variant is lowered together with the DO loop that follows it:

```fortran
!$omp metadirective when(implementation={vendor(llvm)}: parallel do)
do i = 1, n
  y(i) = a*x(i) + y(i)
end do
```

```mlir
omp.parallel {
  omp.wsloop {
    omp.loop_nest (%iv) = (%lb) to (%ub) ... { ... }
  }
}
```

Because semantic analysis cannot know which variant is selected,
induction-variable data-sharing attributes are marked during lowering and
scoped to the selected variant, so DSA state does not leak between
alternatives. A variant that opens a parallel, task, or teams region also
privatizes the indices of its nested sequential loops. `collapse(n)` and
`ordered(n)` use the normal affected-loop-depth calculation, and SIMD
variants are recognized from their leaf constructs.

Dynamic selection preserves the associated DO evaluation in every arm, so
a runtime `condition(flag)` that may pick a loop-associated variant or a
standalone fallback does not drop the loop body from the fallback arm.

Lowering assumes the associated loop nest is a canonical DO nest that is
deep enough for any `collapse(n)` or `ordered(n)`, and asserts these
invariants (for example in markMetadirectiveLoopIVs) rather than
re-diagnosing them. The companion metadirective loop-nest semantic checks
reject a DO WHILE, a missing or non-canonical loop, and an oversized
`collapse`/`ordered`, so a malformed nest does not reach lowering in a
well-formed program.

Part of the feature work for #188820. The metadirective loop-nest
semantic checks are a companion patch
([#207088](https://github.com/llvm/llvm-project/pull/207088)).

Assisted with Copilot and GPT-5.


>From 136b8b574ffb72b779c11837557e62845e3e897f Mon Sep 17 00:00:00 2001
From: "Chi Chun, Chen" <chichun.chen at hpe.com>
Date: Mon, 29 Jun 2026 14:49:44 -0500
Subject: [PATCH] [flang][OpenMP] Support lowering of metadirective (part 3)

Enable lowering of metadirectives that resolve to a loop-associated
variant such as `do`, `simd`, `parallel do`, and `do simd`. The
associated DO construct is made available to the selected variant so the
existing OpenMP loop lowering can process it. A standalone metadirective
uses the following sibling DO, and a begin/end metadirective uses the
first substantive evaluation nested in its block.

For example, when the `vendor(llvm)` selector matches, the `parallel do`
variant is lowered together with the DO loop that follows it:

```fortran
!$omp metadirective when(implementation={vendor(llvm)}: parallel do)
do i = 1, n
  y(i) = a*x(i) + y(i)
end do
```

```mlir
omp.parallel {
  omp.wsloop {
    omp.loop_nest (%iv) = (%lb) to (%ub) ... { ... }
  }
}
```

Because semantic analysis cannot know which variant is selected,
induction-variable data-sharing attributes are marked during lowering and
scoped to the selected variant, so DSA state does not leak between
alternatives. A variant that opens a parallel, task, or teams region also
privatizes the indices of its nested sequential loops. `collapse(n)` and
`ordered(n)` use the normal affected-loop-depth calculation, and SIMD
variants are recognized from their leaf constructs.

Dynamic selection preserves the associated DO evaluation in every arm, so
a runtime `condition(flag)` that may pick a loop-associated variant or a
standalone fallback does not drop the loop body from the fallback arm.

Lowering assumes the associated loop nest is a canonical DO nest that is
deep enough for any `collapse(n)` or `ordered(n)`, and asserts these
invariants (for example in markMetadirectiveLoopIVs) rather than
re-diagnosing them. The companion metadirective loop-nest semantic checks
reject a DO WHILE, a missing or non-canonical loop, and an oversized
`collapse`/`ordered`, so a malformed nest does not reach lowering in a
well-formed program.

Part of the feature work for #188820. The metadirective loop-nest
semantic checks are a companion patch
([#207088](https://github.com/llvm/llvm-project/pull/207088)).

Assisted with Copilot and GPT-5.
---
 .../lib/Lower/OpenMP/DataSharingProcessor.cpp |  90 +++-
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 251 ++++++++-
 flang/lib/Lower/OpenMP/Utils.cpp              |  32 +-
 flang/lib/Lower/OpenMP/Utils.h                |   6 +
 .../Lower/OpenMP/Todo/metadirective-loop.f90  |  12 -
 .../OpenMP/Todo/metadirective-module-loop.f90 |   7 +
 .../OpenMP/Todo/metadirective-target-loop.f90 |  15 +
 .../test/Lower/OpenMP/metadirective-loop.f90  | 476 ++++++++++++++++++
 8 files changed, 863 insertions(+), 26 deletions(-)
 delete mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-module-loop.f90
 create mode 100644 flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
 create mode 100644 flang/test/Lower/OpenMP/metadirective-loop.f90

diff --git a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
index c97d054e1210b..31f832ca29095 100644
--- a/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/DataSharingProcessor.cpp
@@ -41,8 +41,46 @@ bool DataSharingProcessor::OMPConstructSymbolVisitor::isSymbolDefineBy(
     const semantics::Symbol *symbol, lower::pft::Evaluation &eval) const {
   return eval.visit(common::visitors{
       [&](const parser::OpenMPConstruct &functionParserNode) {
+        if (symDefMap.count(symbol) &&
+            symDefMap.at(symbol) == ConstructPtr(&functionParserNode))
+          return true;
+
+        // For metadirectives on standalone or delimited constructs, the
+        // construct itself does not introduce privatization. As a result,
+        // symbols from nested (spliced or block-owned) DO evaluations may be
+        // associated with a null `OpenMPConstruct` pointer. This is expected
+        // and should be accepted.
+        bool isMetadirective =
+            std::holds_alternative<parser::OmpDelimitedMetadirectiveDirective>(
+                functionParserNode.u);
+        if (const auto *standalone =
+                std::get_if<parser::OpenMPStandaloneConstruct>(
+                    &functionParserNode.u)) {
+          isMetadirective =
+              isMetadirective ||
+              std::holds_alternative<parser::OmpMetadirectiveDirective>(
+                  standalone->u);
+        }
+        if (isMetadirective)
+          return symDefMap.count(symbol) &&
+                 symDefMap.at(symbol) ==
+                     ConstructPtr(
+                         static_cast<const parser::OpenMPConstruct *>(nullptr));
+        return false;
+      },
+      [&](const parser::OpenMPDeclarativeConstruct &functionParserNode) {
+        // Metadirective variants are resolved during lowering, so the eval
+        // is an `OpenMPDeclarativeConstruct` rather than an `OpenMPConstruct`.
+        // The visitor does not track declarative constructs, so symbols
+        // referenced at the top level of the eval (e.g. loop IVs from a
+        // spliced DO) are mapped to a null `OpenMPConstruct` pointer.
+        if (!std::holds_alternative<parser::OmpMetadirectiveDirective>(
+                functionParserNode.u))
+          return false;
         return symDefMap.count(symbol) &&
-               symDefMap.at(symbol) == ConstructPtr(&functionParserNode);
+               symDefMap.at(symbol) ==
+                   ConstructPtr(
+                       static_cast<const parser::OpenMPConstruct *>(nullptr));
       },
       [](const auto &functionParserNode) { return false; }});
 }
@@ -77,6 +115,14 @@ DataSharingProcessor::DataSharingProcessor(
   eval.visit([&](const auto &functionParserNode) {
     parser::Walk(functionParserNode, visitor);
   });
+  // For metadirective evaluations, the associated DO loop is spliced into the
+  // evaluation tree but is not part of the metadirective's parse tree. Walk
+  // nested evaluations' parse trees so the visitor can track their symbols
+  // (e.g. loop iteration variables).
+  if (isMetadirectiveEval(eval) && eval.hasNestedEvaluations()) {
+    for (auto &nestedEval : eval.getNestedEvaluations())
+      nestedEval.visit([&](const auto &node) { parser::Walk(node, visitor); });
+  }
 }
 
 DataSharingProcessor::DataSharingProcessor(lower::AbstractConverter &converter,
@@ -220,8 +266,27 @@ void DataSharingProcessor::copyFirstPrivateSymbol(
 
 void DataSharingProcessor::copyLastPrivateSymbol(
     const semantics::Symbol *sym, mlir::OpBuilder::InsertPoint *lastPrivIP) {
-  if (sym->test(semantics::Symbol::Flag::OmpLastPrivate))
+  if (!sym->test(semantics::Symbol::Flag::OmpLastPrivate))
+    return;
+
+  if (sym->has<semantics::HostAssocDetails>()) {
     converter.copyHostAssociateVar(*sym, lastPrivIP, /*hostIsSource=*/false);
+    return;
+  }
+
+  // Metadirective loop IVs can be marked lastprivate during lowering, after
+  // semantic host-association symbols would normally be created. Copy from the
+  // private binding back to the one-level-up binding directly.
+  mlir::OpBuilder::InsertionGuard guard(firOpBuilder);
+  if (lastPrivIP)
+    firOpBuilder.restoreInsertionPoint(*lastPrivIP);
+  lower::SymbolBox hostBox = converter.lookupOneLevelUpSymbol(*sym);
+  lower::SymbolBox privBox = converter.shallowLookupSymbol(*sym);
+  assert(hostBox && privBox &&
+         "expected symbol bindings for lastprivate loop IV");
+  if (hostBox.getAddr() != privBox.getAddr())
+    converter.copyVar(converter.getCurrentLocation(), hostBox.getAddr(),
+                      privBox.getAddr(), fir::FortranVariableFlagsEnum::None);
 }
 
 void DataSharingProcessor::collectOmpObjectListSymbol(
@@ -542,6 +607,19 @@ void DataSharingProcessor::collectPrivatizedSymbols(
   llvm::SetVector<const semantics::Scope *> clauseScopes;
   (void)collectScopes(semaCtx, eval, clauseScopes);
 
+  // For metadirective evaluations, the source range only covers the directive
+  // clauses, not the spliced DO loop. The scope found from that narrow range
+  // may not include parent scopes where the loop IV is declared (e.g. the
+  // function scope when the metadirective is inside a target region). Walk up
+  // the scope chain to include all ancestor scopes.
+  if (isMetadirectiveEval(eval) && !clauseScopes.empty()) {
+    const semantics::Scope *scope = *clauseScopes.begin();
+    while (scope->kind() != semantics::Scope::Kind::Global) {
+      clauseScopes.insert(scope);
+      scope = &scope->parent();
+    }
+  }
+
   for (const auto *sym : allSymbols) {
     if (semantics::omp::IsPrivatizable(*sym) &&
         !symbolsInNestedRegions.contains(sym) &&
@@ -571,6 +649,14 @@ void DataSharingProcessor::collectSymbols(
                              /*collectSymbols=*/true,
                              /*collectHostAssociatedSymbols=*/true);
 
+  // Collect symbols from spliced nested evaluations for metadirectives.
+  if (isMetadirectiveEval(eval) && eval.hasNestedEvaluations()) {
+    for (auto &nestedEval : eval.getNestedEvaluations())
+      converter.collectSymbolSet(nestedEval, allSymbols, flag,
+                                 /*collectSymbols=*/true,
+                                 /*collectHostAssociatedSymbols=*/true);
+  }
+
   llvm::SetVector<const semantics::Symbol *> symbolsInNestedRegions;
   collectSymbolsInNestedRegions(eval, flag, symbolsInNestedRegions);
 
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index bfb63c36991be..5264e5b9a2790 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -43,6 +43,7 @@
 #include "flang/Parser/parse-tree.h"
 #include "flang/Parser/tools.h"
 #include "flang/Semantics/openmp-directive-sets.h"
+#include "flang/Semantics/openmp-dsa.h"
 #include "flang/Semantics/openmp-utils.h"
 #include "flang/Semantics/tools.h"
 #include "flang/Support/Flags.h"
@@ -5088,6 +5089,202 @@ struct MetadirectiveCandidate {
 };
 } // namespace
 
+/// A loop-associated metadirective is lowered like a real loop construct, but
+/// the PFT leaves its associated loop nest as the following sibling instead of
+/// nesting it underneath. Splice that sibling into the metadirective's own
+/// nested evaluations so the shared loop-lowering path can find it. Return
+/// nullptr if no associated DO loop follows.
+static lower::pft::Evaluation *
+spliceAssociatedDoEval(lower::pft::Evaluation &eval) {
+  if (eval.hasNestedEvaluations()) {
+    auto nestedIt =
+        llvm::find_if(eval.getNestedEvaluations(), [](auto &nested) {
+          return !nested.isEndStmt() &&
+                 !nested.template getIf<parser::CompilerDirective>();
+        });
+    if (nestedIt != eval.getNestedEvaluations().end())
+      return nestedIt->getIf<parser::DoConstruct>() ? &*nestedIt : nullptr;
+    return nullptr;
+  }
+
+  // A metadirective in a specification part (e.g. at module scope) has no
+  // parent construct and no owning procedure, so there is no sibling list.
+  lower::pft::FunctionLikeUnit *owningProc = eval.getOwningProcedure();
+  if (!eval.parentConstruct && !owningProc)
+    return nullptr;
+  auto *parentList = eval.parentConstruct
+                         ? eval.parentConstruct->evaluationList.get()
+                         : &owningProc->evaluationList;
+  auto metaIt = llvm::find_if(
+      *parentList, [&](lower::pft::Evaluation &e) { return &e == &eval; });
+  assert(metaIt != parentList->end() &&
+         "metadirective eval not found in parent list");
+
+  auto loopIt = std::next(metaIt);
+  while (loopIt != parentList->end() &&
+         (loopIt->isEndStmt() || loopIt->getIf<parser::CompilerDirective>()))
+    ++loopIt;
+
+  if (loopIt == parentList->end() || !loopIt->getIf<parser::DoConstruct>())
+    return nullptr;
+
+  eval.evaluationList->splice(eval.evaluationList->end(), *parentList, loopIt);
+  return &eval.getNestedEvaluations().back();
+}
+
+static bool hasLoopAssociatedDirective(const ConstructQueue &queue) {
+  return llvm::any_of(queue, [](const auto &item) {
+    return llvm::omp::getDirectiveAssociation(item.id) ==
+           llvm::omp::Association::LoopNest;
+  });
+}
+
+static bool consumesAssociatedEvaluation(const ConstructQueue &queue) {
+  return llvm::any_of(queue, [](const auto &item) {
+    return llvm::omp::getDirectiveAssociation(item.id) !=
+           llvm::omp::Association::None;
+  });
+}
+
+/// Clear all existing DSA flags on \p sym, then set PreDetermined + \p dsa.
+static void applySymbolDSA(semantics::Symbol &sym,
+                           semantics::Symbol::Flag dsa) {
+  using Symbol = semantics::Symbol;
+  semantics::SetSymbolDSA(sym,
+                          Symbol::Flags{Symbol::Flag::OmpPreDetermined, dsa});
+}
+
+class SymbolDSAGuard {
+public:
+  ~SymbolDSAGuard() {
+    for (auto &[sym, flags] : llvm::reverse(savedFlags))
+      sym->flags() = flags;
+  }
+
+  void setSymbolDSA(semantics::Symbol &sym, semantics::Symbol::Flag dsa) {
+    if (!llvm::any_of(savedFlags,
+                      [&](const auto &entry) { return entry.first == &sym; }))
+      savedFlags.emplace_back(&sym, sym.flags());
+    applySymbolDSA(sym, dsa);
+  }
+
+private:
+  llvm::SmallVector<std::pair<semantics::Symbol *, semantics::Symbol::Flags>, 4>
+      savedFlags;
+};
+
+/// Mark the iteration variable of every sequential DO loop nested in a
+/// metadirective variant's generated region as private, mirroring
+/// `OmpAttributeVisitor::ResolveSeqLoopIndexInParallelOrTaskConstruct`, which
+/// cannot run because the variant is only resolved during lowering.
+static void markSequentialLoopIVs(
+    lower::pft::Evaluation &eval, SymbolDSAGuard &dsaGuard,
+    const llvm::SmallPtrSetImpl<const semantics::Symbol *> &clauseSyms,
+    unsigned version) {
+  using Symbol = semantics::Symbol;
+  if (!eval.hasNestedEvaluations())
+    return;
+  for (lower::pft::Evaluation &nested : eval.getNestedEvaluations()) {
+    // A nested construct with its own data environment (parallel, task, teams,
+    // or target) owns the loops it contains, so do not descend into it. A
+    // nested unresolved metadirective is not special-cased: privatizing its
+    // sibling loop index here too is benign for a loop index.
+    if (const auto *ompConstruct = nested.getIf<parser::OpenMPConstruct>()) {
+      llvm::omp::Directive dir =
+          parser::omp::GetOmpDirectiveName(*ompConstruct).v;
+      if (llvm::omp::allParallelSet.test(dir) ||
+          llvm::omp::taskGeneratingSet.test(dir) ||
+          llvm::omp::allTargetSet.test(dir) ||
+          (version >= 52 && llvm::omp::allTeamsSet.test(dir)))
+        continue;
+    }
+    // Skip an index that already has a DSA: predetermined (the variant's own
+    // associated loops and nested loop-construct indices) or clause-named.
+    if (nested.getIf<parser::DoConstruct>())
+      if (Symbol *sym = getIterationVariableSymbol(nested))
+        if (!sym->test(Symbol::Flag::OmpPreDetermined) &&
+            !clauseSyms.contains(sym))
+          dsaGuard.setSymbolDSA(*sym, Symbol::Flag::OmpPrivate);
+    markSequentialLoopIVs(nested, dsaGuard, clauseSyms, version);
+  }
+}
+
+/// Mark loop induction variable data-sharing attributes for a
+/// metadirective-selected loop variant. Semantic analysis cannot mark these
+/// because the variant is resolved at lowering time. Return false if the
+/// associated loop nest is shallower than the variant's COLLAPSE/ORDERED
+/// requires, leaving the diagnostic to the caller.
+static bool
+markMetadirectiveLoopIVs(semantics::SemanticsContext &semaCtx,
+                         const parser::OmpDirectiveSpecification &spec,
+                         lower::pft::Evaluation &loopEval,
+                         SymbolDSAGuard &dsaGuard) {
+  using Symbol = semantics::Symbol;
+
+  auto [depth, _] = semantics::omp::GetAffectedNestDepthWithReason(
+      spec, semaCtx.langOptions().OpenMPVersion, &semaCtx);
+  if (!depth || !depth.value || *depth.value <= 0)
+    return true;
+
+  int64_t affectedDepth = *depth.value;
+  bool isSimdVariant =
+      llvm::is_contained(llvm::omp::getLeafConstructsOrSelf(spec.DirId()),
+                         llvm::omp::Directive::OMPD_simd);
+  Symbol::Flag ivDSA;
+  if (!isSimdVariant)
+    ivDSA = Symbol::Flag::OmpPrivate;
+  else if (affectedDepth == 1 && semaCtx.langOptions().OpenMPVersion < 60)
+    ivDSA = Symbol::Flag::OmpLinear;
+  else
+    ivDSA = Symbol::Flag::OmpLastPrivate;
+
+  lower::pft::Evaluation *doEval = &loopEval;
+  for (int64_t level = 0; level < affectedDepth; ++level) {
+    // A nest shallower than COLLAPSE/ORDERED requires is diagnosed during
+    // semantic analysis in check-omp-variant. Guard against it here too,
+    // returning false so the caller handles it instead of asserting in
+    // `getNestedDoConstruct`.
+    const parser::DoConstruct *doConstruct =
+        doEval ? doEval->getIf<parser::DoConstruct>() : nullptr;
+    if (!doConstruct)
+      return false;
+    // The affected loop must be a canonical DO loop (or a DO CONCURRENT, which
+    // lowering rejects further down). A DO WHILE or a loop without loop control
+    // is rejected by the metadirective loop-nest semantic checks
+    // (check-omp-variant.cpp), so it never reaches lowering in a well-formed
+    // program.
+    assert((doConstruct->IsDoNormal() || doConstruct->IsDoConcurrent()) &&
+           "metadirective affected loop must be a canonical DO loop");
+    if (semantics::Symbol *sym = getIterationVariableSymbol(*doEval))
+      dsaGuard.setSymbolDSA(*sym, ivDSA);
+    if (level + 1 < affectedDepth)
+      doEval = tryGetNestedDoConstruct(*doEval);
+  }
+
+  // A variant that generates a parallel, task, or (>= 5.2) teams region also
+  // privatizes the index of every nested sequential loop. `doEval` is the
+  // innermost associated loop, so its body holds those loops.
+  bool variantGeneratesParallelRegion =
+      llvm::omp::allParallelSet.test(spec.DirId()) ||
+      llvm::omp::taskGeneratingSet.test(spec.DirId()) ||
+      (semaCtx.langOptions().OpenMPVersion >= 52 &&
+       llvm::omp::allTeamsSet.test(spec.DirId()));
+  if (variantGeneratesParallelRegion) {
+    // Indices named in the variant's own clauses already have an explicit DSA;
+    // exclude them so the marking below does not override it.
+    llvm::SmallPtrSet<const semantics::Symbol *, 4> clauseSyms;
+    for (const parser::OmpClause &clause : spec.Clauses().v)
+      if (const parser::OmpObjectList *objects =
+              parser::omp::GetOmpObjectList(clause))
+        for (const parser::OmpObject &object : objects->v)
+          if (const semantics::Symbol *sym = makeObject(object, semaCtx).sym())
+            clauseSyms.insert(sym);
+    markSequentialLoopIVs(*doEval, dsaGuard, clauseSyms,
+                          semaCtx.langOptions().OpenMPVersion);
+  }
+  return true;
+}
+
 static void genMetadirective(lower::AbstractConverter &converter,
                              lower::SymMap &symTable,
                              semantics::SemanticsContext &semaCtx,
@@ -5261,23 +5458,41 @@ static void genMetadirective(lower::AbstractConverter &converter,
     }
   }
 
+  auto makeVariantQueue = [&](const parser::OmpDirectiveSpecification &spec) {
+    List<Clause> variantClauses = makeClauses(spec.Clauses(), semaCtx);
+    return ConstructQueue{
+        buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
+                            eval, spec.source, spec.DirId(), variantClauses)};
+  };
+
+  bool hasLoopAssociatedCandidate = false;
+  for (const MetadirectiveCandidate &candidate : candidates) {
+    if (candidate.spec &&
+        hasLoopAssociatedDirective(makeVariantQueue(*candidate.spec))) {
+      hasLoopAssociatedCandidate = true;
+      break;
+    }
+  }
+  if (!hasLoopAssociatedCandidate && fallback)
+    hasLoopAssociatedCandidate =
+        hasLoopAssociatedDirective(makeVariantQueue(*fallback));
+  if (hasLoopAssociatedCandidate)
+    (void)spliceAssociatedDoEval(eval);
+
   // 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)};
+    ConstructQueue queue = makeVariantQueue(*spec);
 
     if (llvm::any_of(queue, [](const auto &item) {
-          return llvm::omp::getDirectiveAssociation(item.id) ==
-                 llvm::omp::Association::LoopNest;
+          return llvm::omp::allTargetSet.test(item.id);
         })) {
-      TODO(variantLoc, "loop-associated METADIRECTIVE variant");
+      TODO(variantLoc,
+           "TARGET construct selected by METADIRECTIVE (host-eval)");
     }
 
     if (llvm::any_of(queue, [](const auto &item) {
@@ -5289,8 +5504,25 @@ static void genMetadirective(lower::AbstractConverter &converter,
       TODO(variantLoc, "declarative METADIRECTIVE variant");
     }
 
+    bool hasLoopAssociation = hasLoopAssociatedDirective(queue);
+    if (hasLoopAssociation) {
+      lower::pft::Evaluation *loopEval = spliceAssociatedDoEval(eval);
+      if (!loopEval)
+        TODO(variantLoc, "loop-associated METADIRECTIVE without associated DO");
+      SymbolDSAGuard dsaGuard;
+      if (!markMetadirectiveLoopIVs(semaCtx, *spec, *loopEval, dsaGuard))
+        TODO(variantLoc, "METADIRECTIVE variant with COLLAPSE or ORDERED "
+                         "requires a deeper perfectly-nested loop nest than "
+                         "is present");
+      genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
+                     queue.begin());
+      return;
+    }
+
     genOMPDispatch(converter, symTable, semaCtx, eval, variantLoc, queue,
                    queue.begin());
+    if (!consumesAssociatedEvaluation(queue) && eval.hasNestedEvaluations())
+      genNestedEvaluations(converter, eval);
   };
 
   auto selectBestCandidate =
@@ -5806,7 +6038,10 @@ void Fortran::lower::genOpenMPDeclarativeConstruct(
     semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval,
     const parser::OpenMPDeclarativeConstruct &omp) {
   genOMP(converter, symTable, semaCtx, eval, omp);
-  genNestedEvaluations(converter, eval);
+  // Metadirective lowering selects a variant and consumes its associated
+  // evaluations itself.
+  if (!isMetadirectiveEval(eval))
+    genNestedEvaluations(converter, eval);
 }
 
 void Fortran::lower::genOpenMPSymbolProperties(
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index e0d843ef4eca5..699a7c675d4a0 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -699,7 +699,7 @@ static void processTileSizesFromOpenMPConstruct(
   }
 }
 
-pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval) {
+pft::Evaluation *tryGetNestedDoConstruct(pft::Evaluation &eval) {
   for (pft::Evaluation &nested : eval.getNestedEvaluations()) {
     // In an OpenMPConstruct there can be compiler directives:
     // 1 <<OpenMPConstruct>>
@@ -719,10 +719,34 @@ pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval) {
     // Loop transformations can introduce nested OpenMP
     // constructs between the directive and the actual do-loop nest.
     if (nested.getIf<parser::OpenMPConstruct>())
-      return getNestedDoConstruct(nested);
-    assert(false && "Unexpected construct in the nested evaluations");
+      return tryGetNestedDoConstruct(nested);
+    // Anything else means the perfectly-nested do-loop nest ends here.
+    return nullptr;
   }
-  llvm_unreachable("Expected do loop to be in the nested evaluations");
+  return nullptr;
+}
+
+pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval) {
+  pft::Evaluation *doEval = tryGetNestedDoConstruct(eval);
+  assert(doEval && "Expected do loop to be in the nested evaluations");
+  return doEval;
+}
+
+/// Return true if \p eval holds a metadirective.
+bool isMetadirectiveEval(lower::pft::Evaluation &eval) {
+  if (const auto *decl = eval.getIf<parser::OpenMPDeclarativeConstruct>())
+    return std::holds_alternative<parser::OmpMetadirectiveDirective>(decl->u);
+  if (const auto *ompConstruct = eval.getIf<parser::OpenMPConstruct>()) {
+    if (std::holds_alternative<parser::OmpDelimitedMetadirectiveDirective>(
+            ompConstruct->u))
+      return true;
+    if (const auto *standalone =
+            std::get_if<parser::OpenMPStandaloneConstruct>(&ompConstruct->u)) {
+      return std::holds_alternative<parser::OmpMetadirectiveDirective>(
+          standalone->u);
+    }
+  }
+  return false;
 }
 
 /// Populates the sizes vector with values if the given OpenMPConstruct
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 94f85c43f7033..09ba77ba95055 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -170,6 +170,10 @@ void genObjectList(const ObjectList &objects,
 void lastprivateModifierNotSupported(const omp::clause::Lastprivate &lastp,
                                      mlir::Location loc);
 
+/// Return the perfectly-nested DO construct directly contained in \p eval, or
+/// null when there is none (e.g. the loop nest is shallower than required).
+pft::Evaluation *tryGetNestedDoConstruct(pft::Evaluation &eval);
+
 pft::Evaluation *getNestedDoConstruct(pft::Evaluation &eval);
 
 int64_t collectLoopRelatedInfo(
@@ -274,6 +278,8 @@ semantics::omp::OmpVariantMatchContext makeVariantMatchContext(
     mlir::ModuleOp module,
     llvm::ArrayRef<llvm::omp::TraitProperty> constructTraits);
 
+bool isMetadirectiveEval(lower::pft::Evaluation &eval);
+
 } // namespace omp
 } // namespace lower
 } // namespace Fortran
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
deleted file mode 100644
index 999a8c0839d15..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/metadirective-loop.f90
+++ /dev/null
@@ -1,12 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=50 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: loop-associated METADIRECTIVE variant
-
-subroutine test_loop_variant()
-  integer :: i
-  !$omp metadirective &
-  !$omp & when(implementation={vendor(llvm)}: parallel do) &
-  !$omp & default(nothing)
-  do i = 1, 100
-  end do
-end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-module-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-module-loop.f90
new file mode 100644
index 0000000000000..be08d91a95c01
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-module-loop.f90
@@ -0,0 +1,7 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: loop-associated METADIRECTIVE without associated DO
+
+module m
+  !$omp metadirective when(implementation={vendor(llvm)}: do) default(nothing)
+end module
diff --git a/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90 b/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
new file mode 100644
index 0000000000000..f271cbeffb21f
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/metadirective-target-loop.f90
@@ -0,0 +1,15 @@
+! Test that a metadirective variant resolving to a target construct
+! correctly reports a TODO (host-eval support needed).
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
+
+! CHECK: not yet implemented: TARGET construct selected by METADIRECTIVE (host-eval)
+
+subroutine test_target_loop()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: target teams distribute parallel do) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
diff --git a/flang/test/Lower/OpenMP/metadirective-loop.f90 b/flang/test/Lower/OpenMP/metadirective-loop.f90
new file mode 100644
index 0000000000000..559949d2ef7bf
--- /dev/null
+++ b/flang/test/Lower/OpenMP/metadirective-loop.f90
@@ -0,0 +1,476 @@
+! Test lowering of OpenMP metadirective with loop-associated variants.
+
+! RUN: %flang_fc1 -fopenmp -emit-hlfir -fopenmp-version=51 %s -o - | FileCheck %s
+
+!===----------------------------------------------------------------------===!
+! Basic loop-associated variants via static selection
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do()
+! CHECK:         omp.parallel {
+! CHECK:           omp.wsloop
+! CHECK:             omp.loop_nest
+! CHECK:             omp.yield
+! CHECK:           omp.terminator
+! CHECK:         return
+subroutine test_parallel_do()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_do()
+! CHECK-NOT:     omp.parallel
+! CHECK:         omp.wsloop
+! CHECK:           omp.loop_nest
+! CHECK:           omp.yield
+! CHECK:         return
+subroutine test_do()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_simd()
+! CHECK:         %[[I:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "_QFtest_simdEi"}
+! CHECK-NOT:     omp.wsloop
+! CHECK:         omp.simd linear(%[[I]]#0
+! CHECK:           omp.loop_nest
+! CHECK:           omp.yield
+! CHECK-NOT:     fir.do_loop
+! CHECK:         fir.load %[[I]]#0
+! CHECK:         return
+subroutine test_simd()
+  integer :: i, i_after
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: simd) &
+  !$omp & default(nothing)
+  do i = 1, 100
+    i_after = i
+  end do
+  i_after = i
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_do_simd()
+! CHECK-NOT:     omp.parallel
+! CHECK:         omp.wsloop
+! CHECK:           omp.simd
+! CHECK:             omp.loop_nest
+! CHECK:             omp.yield
+! CHECK:         return
+subroutine test_do_simd()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do simd) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_begin_do()
+! CHECK-NOT:     omp.parallel
+! CHECK:         omp.wsloop
+! CHECK:           omp.loop_nest
+! CHECK:           omp.yield
+! CHECK:         return
+subroutine test_begin_do()
+  integer :: i
+  !$omp begin metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+  !$omp end metadirective
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Static mismatch falls through to standalone fallback
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_loop_static_mismatch()
+! CHECK-NOT:     omp.wsloop
+! CHECK-NOT:     omp.loop_nest
+! CHECK:         omp.barrier
+! CHECK:         return
+subroutine test_loop_static_mismatch()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor("unknown")}: parallel do) &
+  !$omp & default(barrier)
+  do i = 1, 100
+  end do
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Dynamic user condition with loop-associated variant
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_loop(
+! CHECK-SAME:    %[[ARG0:.*]]: !fir.ref<!fir.logical<4>>
+! CHECK:         %[[DECL:.*]]:2 = hlfir.declare %[[ARG0]]
+! CHECK:         %[[LOAD:.*]] = fir.load %[[DECL]]#0
+! CHECK:         %[[COND:.*]] = fir.convert %[[LOAD]] : (!fir.logical<4>) -> i1
+! CHECK:         fir.if %[[COND]] {
+! CHECK:           omp.parallel {
+! CHECK:             omp.wsloop
+! CHECK:               omp.loop_nest
+! CHECK:         } else {
+! CHECK:           omp.simd
+! CHECK:             omp.loop_nest
+! CHECK:         }
+! CHECK:         return
+subroutine test_dynamic_loop(flag)
+  logical, intent(in) :: flag
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(user={condition(flag)}: parallel do) &
+  !$omp & default(simd)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_loop_standalone_fallback(
+! CHECK:         fir.if {{.*}} {
+! CHECK:           omp.parallel {
+! CHECK:             omp.wsloop
+! CHECK:               omp.loop_nest
+! CHECK:         } else {
+! CHECK:           omp.barrier
+! CHECK:           fir.do_loop
+! CHECK:         }
+! CHECK:         return
+subroutine test_dynamic_loop_standalone_fallback(flag, a)
+  logical, intent(in) :: flag
+  integer :: i, a
+  a = 0
+  !$omp metadirective &
+  !$omp & when(user={condition(flag)}: parallel do) &
+  !$omp & default(barrier)
+  do i = 1, 100
+    a = a + i
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_dynamic_loop_dsa_isolation(
+! CHECK:         fir.if {{.*}} {
+! CHECK:           omp.simd {{.*}}private(@{{[^,]*}}Ei_private_i32
+! CHECK-SAME:      @_QFtest_dynamic_loop_dsa_isolationEj_private_i32
+! CHECK:             omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2)
+! CHECK:         } else {
+! CHECK:           omp.simd linear({{[^)]*}}) {
+! CHECK:             omp.loop_nest ({{.*}}) : i32
+! CHECK:         }
+! CHECK:         return
+subroutine test_dynamic_loop_dsa_isolation(flag, n, sink)
+  logical, intent(in) :: flag
+  integer :: n, sink
+  integer :: i, j
+  sink = 0
+  !$omp metadirective &
+  !$omp & when(user={condition(flag)}: simd collapse(2)) &
+  !$omp & default(simd)
+  do i = 1, n
+    do j = 1, n
+      sink = sink + i + j
+    end do
+  end do
+  sink = sink + j
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_ordered_depth()
+! CHECK:         omp.parallel {
+! CHECK:           omp.wsloop {{.*}}private(@{{[^,]*}}Ei_private_i32
+! CHECK-SAME:      @_QFtest_ordered_depthEj_private_i32
+! CHECK:             omp.loop_nest ({{.*}}) : i32
+! CHECK:         return
+subroutine test_ordered_depth()
+  integer :: i, j
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do ordered(2)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+    do j = 1, 100
+    end do
+  end do
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Loop-associated variants with clauses
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_schedule()
+! CHECK:         omp.wsloop schedule(static)
+! CHECK:           omp.loop_nest
+! CHECK:         return
+subroutine test_schedule()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do schedule(static)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_reduction()
+! CHECK:         omp.wsloop {{.*}} reduction(@add_reduction_i32
+! CHECK:           omp.loop_nest
+! CHECK:         return
+subroutine test_reduction()
+  integer :: i, s
+  s = 0
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do reduction(+:s)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+    s = s + i
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_private()
+! CHECK:         omp.wsloop private(
+! CHECK:           omp.loop_nest
+! CHECK:         return
+subroutine test_private()
+  integer :: i, x
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do private(x)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+    x = i
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_collapse()
+! CHECK:         omp.wsloop
+! CHECK:           omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2)
+! CHECK:         return
+subroutine test_collapse()
+  integer :: i, j
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do collapse(2)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+    do j = 1, 100
+    end do
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_safelen()
+! CHECK:         omp.simd {{.*}}safelen(4)
+! CHECK:           omp.loop_nest
+! CHECK:         return
+subroutine test_safelen()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: simd safelen(4)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_num_threads()
+! CHECK:         omp.parallel num_threads({{.*}}) {
+! CHECK:           omp.wsloop
+! CHECK:             omp.loop_nest
+! CHECK:         return
+subroutine test_num_threads()
+  integer :: i
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do num_threads(4)) &
+  !$omp & default(nothing)
+  do i = 1, 100
+  end do
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Lastprivate copy-back for metadirective-selected loop induction variables
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_simd_collapse_lastprivate(
+! CHECK:         %[[I_ORIG:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_simd_collapse_lastprivateEi"}
+! CHECK:         %[[J_ORIG:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = "_QFtest_simd_collapse_lastprivateEj"}
+! CHECK:         omp.simd {{.*}}private({{.*}}_QFtest_simd_collapse_lastprivateEi_private_i32{{.*}}_QFtest_simd_collapse_lastprivateEj_private_i32{{.*}}) {
+! CHECK:           omp.loop_nest ({{.*}}, {{.*}}) : i32 {{.*}} collapse(2) {
+! CHECK:             fir.if
+! CHECK:               hlfir.assign %{{.*}} to %[[I_ORIG]]#0
+! CHECK:               hlfir.assign %{{.*}} to %[[J_ORIG]]#0
+! CHECK:             omp.yield
+subroutine test_simd_collapse_lastprivate(n, sink)
+  integer :: n, sink
+  integer :: i, j
+  sink = 0
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: simd collapse(2)) &
+  !$omp & default(nothing)
+  do i = 1, n
+    do j = 1, n
+      sink = sink + i + j
+    end do
+  end do
+  sink = sink + j
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Metadirective nested inside a BLOCK construct
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_block_nested_parallel_do(
+! CHECK:         omp.parallel {
+! CHECK:           omp.wsloop private(@_QFtest_block_nested_parallel_doEi_private_i32 {{.*}}) {
+! CHECK:             omp.loop_nest
+subroutine test_block_nested_parallel_do(n, a)
+  integer :: n
+  integer :: a(n)
+  integer :: i
+  block
+    !$omp metadirective &
+    !$omp & when(implementation={vendor(llvm)}: parallel do) &
+    !$omp & default(nothing)
+    do i = 1, n
+      a(i) = i
+    end do
+  end block
+end subroutine
+
+!===----------------------------------------------------------------------===!
+! Inner sequential loop induction-variable privatization
+!===----------------------------------------------------------------------===!
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_seq_inner(
+! CHECK:         omp.parallel {
+! CHECK:           omp.wsloop private({{.*}}_QFtest_parallel_do_seq_innerEi_private_i32{{.*}}_QFtest_parallel_do_seq_innerEk_private_i32{{.*}}) {
+! CHECK:             omp.loop_nest
+subroutine test_parallel_do_seq_inner(n, a)
+  integer :: n
+  integer :: a(n, n)
+  integer :: i, k
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do) &
+  !$omp & default(nothing)
+  do i = 1, n
+    do k = 1, n
+      a(k, i) = k
+    end do
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_do_seq_inner_shared(
+! CHECK:         omp.wsloop private(
+! CHECK-NOT:       _QFtest_do_seq_inner_sharedEk_private
+! CHECK:           omp.loop_nest
+subroutine test_do_seq_inner_shared(n, a)
+  integer :: n
+  integer :: a(n, n)
+  integer :: i, k
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: do) &
+  !$omp & default(nothing)
+  do i = 1, n
+    do k = 1, n
+      a(k, i) = k
+    end do
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_critical_inner(
+! CHECK:         omp.parallel {
+! CHECK:           omp.wsloop private({{.*}}_QFtest_parallel_do_critical_innerEi_private_i32{{.*}}_QFtest_parallel_do_critical_innerEk_private_i32{{.*}}) {
+! CHECK:             omp.loop_nest
+! CHECK:               omp.critical
+subroutine test_parallel_do_critical_inner(n, a)
+  integer :: n
+  integer :: a(n)
+  integer :: i, k
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do) &
+  !$omp & default(nothing)
+  do i = 1, n
+    !$omp critical
+    do k = 1, n
+      a(k) = k
+    end do
+    !$omp end critical
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_inner_shared(
+! CHECK:         omp.wsloop private(
+! CHECK:           omp.loop_nest
+! CHECK:             omp.parallel {
+! CHECK-NOT:           _QFtest_parallel_do_inner_sharedEk_private
+subroutine test_parallel_do_inner_shared(n, a)
+  integer :: n
+  integer :: a(n)
+  integer :: i, k
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do) &
+  !$omp & default(nothing)
+  do i = 1, n
+    !$omp parallel shared(k)
+    do k = 1, n
+      a(k) = k
+    end do
+    !$omp end parallel
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_reduction_iv(
+! CHECK:         omp.wsloop private(@_QFtest_parallel_do_reduction_ivEi_private_i32 {{[^,]*}}) reduction(@add_reduction_i32 {{.*}}) {
+subroutine test_parallel_do_reduction_iv(n, a)
+  integer :: n
+  integer :: a(n)
+  integer :: i, k
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do reduction(+:k)) &
+  !$omp & default(nothing)
+  do i = 1, n
+    do k = 1, n
+      a(k) = k
+    end do
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_enclosing_shared(
+! CHECK:         omp.wsloop private({{.*}}_QFtest_parallel_do_enclosing_sharedEi_private_i32{{.*}}_QFtest_parallel_do_enclosing_sharedEk_private_i32{{.*}}) {
+! CHECK:           omp.loop_nest
+subroutine test_parallel_do_enclosing_shared(n, a)
+  integer :: n
+  integer :: a(n)
+  integer :: i, k
+  !$omp parallel shared(k)
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do) &
+  !$omp & default(nothing)
+  do i = 1, n
+    do k = 1, n
+      a(k) = k
+    end do
+  end do
+  !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_parallel_do_target_inner(
+! CHECK:         omp.wsloop private(@_QFtest_parallel_do_target_innerEi_private_i32 {{[^,]*}}) {
+! CHECK:           omp.target
+! CHECK-NOT:         _QFtest_parallel_do_target_innerEk_private_i32
+subroutine test_parallel_do_target_inner(n, a)
+  integer :: n
+  integer :: a(n)
+  integer :: i, k
+  !$omp metadirective &
+  !$omp & when(implementation={vendor(llvm)}: parallel do) &
+  !$omp & default(nothing)
+  do i = 1, n
+    !$omp target map(tofrom: a)
+    do k = 1, n
+      a(k) = k
+    end do
+    !$omp end target
+  end do
+end subroutine



More information about the flang-commits mailing list