[flang-commits] [flang] [flang][OpenMP] Lower DO and SIMD variants in metadirectives (PR #210810)

via flang-commits flang-commits at lists.llvm.org
Fri Jul 24 22:55:07 PDT 2026


================
@@ -5853,8 +5855,296 @@ struct MetadirectiveCandidate {
   std::optional<semantics::omp::DynamicUserCondition> dynamicCond;
   bool conditionShouldBeTrue = true;
 };
+
+struct SplicedAssociatedEvaluations {
+  using Iterator = lower::pft::EvaluationList::iterator;
+
+  void record(lower::pft::EvaluationList &parent, Iterator evaluation) {
+    assert((!parentList || parentList == &parent) &&
+           "associated evaluations have different parents");
+    parentList = &parent;
+    evaluations.emplace_back(evaluation, std::next(evaluation));
+  }
+
+  void restore(lower::pft::EvaluationList &nested) {
+    if (evaluations.empty())
+      return;
+    assert(parentList && "missing parent evaluation list");
+    // A saved successor may also have been spliced. Restore in reverse order
+    // so every insertion point is back in the parent list before it is used.
+    for (auto &entry : llvm::reverse(evaluations)) {
+      entry.first->skipNextLowering = true;
+      parentList->splice(entry.second, nested, entry.first);
+    }
+    if (entryEvaluation) {
+      entryEvaluation->isNewBlock = true;
+      entryEvaluation->block = entryBlock;
+    }
+  }
+
+  void suppressEntryBlock(lower::pft::Evaluation &evaluation) {
+    assert(!entryEvaluation && evaluation.isNewBlock && evaluation.block &&
+           "invalid associated entry evaluation");
+    // Do not let either cloned loop arm enter a function-region block. The
+    // metadirective selection will be placed in this block for an active ENTRY.
+    entryEvaluation = &evaluation;
+    entryBlock = evaluation.block;
+    evaluation.isNewBlock = false;
+    evaluation.block = nullptr;
+  }
+
+  mlir::Block *getEntryBlock() const { return entryBlock; }
+
+private:
+  lower::pft::EvaluationList *parentList = nullptr;
+  llvm::SmallVector<std::pair<Iterator, Iterator>, 4> evaluations;
+  lower::pft::Evaluation *entryEvaluation = nullptr;
+  mlir::Block *entryBlock = nullptr;
+};
 } // 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,
+    SplicedAssociatedEvaluations *splicedEvaluations = nullptr) {
+  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 firstAssociatedIt = std::next(metaIt);
+  auto loopIt = firstAssociatedIt;
+  while (loopIt != parentList->end() &&
+         (loopIt->isEndStmt() || loopIt->getIf<parser::CompilerDirective>()))
+    ++loopIt;
+
+  if (loopIt == parentList->end() || !loopIt->getIf<parser::DoConstruct>())
+    return nullptr;
+
+  if (splicedEvaluations) {
+    auto entryIt =
+        llvm::find_if(llvm::make_range(firstAssociatedIt, loopIt),
+                      [](lower::pft::Evaluation &candidate) {
+                        return candidate.isNewBlock && candidate.block;
+                      });
+    if (entryIt != loopIt) {
+      splicedEvaluations->suppressEntryBlock(*entryIt);
+    } else {
+      lower::pft::Evaluation &doStmt = loopIt->getFirstNestedEvaluation();
+      if (doStmt.isNewBlock && doStmt.block)
+        splicedEvaluations->suppressEntryBlock(doStmt);
+    }
+  }
+
+  // Compiler directives between the metadirective and its associated loop
+  // must be processed before the loop is lowered. Move them with the loop so
+  // they are not visited later as siblings of the metadirective.
+  for (auto it = firstAssociatedIt; it != loopIt;) {
+    auto current = it++;
+    if (current->getIf<parser::CompilerDirective>()) {
+      if (splicedEvaluations)
+        splicedEvaluations->record(*parentList, current);
+      eval.evaluationList->splice(eval.evaluationList->end(), *parentList,
+                                  current);
+    }
+  }
+  if (splicedEvaluations)
+    splicedEvaluations->record(*parentList, loopIt);
+  eval.evaluationList->splice(eval.evaluationList->end(), *parentList, loopIt);
+  return &eval.getNestedEvaluations().back();
+}
+
+static bool hasDirectiveAssociation(llvm::omp::Directive directive,
+                                    llvm::omp::Association association) {
+  return llvm::any_of(llvm::omp::getLeafConstructsOrSelf(directive),
+                      [association](llvm::omp::Directive leaf) {
+                        return llvm::omp::getDirectiveAssociation(leaf) ==
+                               association;
+                      });
+}
+
+static bool hasDirectiveAssociation(const ConstructQueue &queue,
+                                    llvm::omp::Association association) {
+  return llvm::any_of(queue, [association](const auto &item) {
+    return llvm::omp::getDirectiveAssociation(item.id) == association;
+  });
+}
+
+static bool isSupportedMetadirectiveLoopQueue(const ConstructQueue &queue) {
+  using llvm::omp::Directive;
+  using lower::omp::matchLeafSequence;
+  return matchLeafSequence(queue.begin(), queue, Directive::OMPD_do) ||
+         matchLeafSequence(queue.begin(), queue, Directive::OMPD_simd) ||
+         matchLeafSequence(queue.begin(), queue, Directive::OMPD_do_simd);
+}
+
+static bool isNestedInOpenMPDataEnvironment(lower::pft::Evaluation &eval,
+                                            mlir::Operation *currentOp) {
+  for (lower::pft::Evaluation *parent = eval.parentConstruct; parent;
+       parent = parent->parentConstruct) {
+    if (const auto *omp = parent->getIf<parser::OpenMPConstruct>()) {
+      llvm::omp::Directive directive = parser::omp::GetOmpDirectiveName(*omp).v;
+      if (semantics::omp::HasDataEnvironment(directive))
+        return true;
+    }
+  }
+
+  // A PFT ancestor can itself be a metadirective, so its source directive does
+  // not reveal the data environment selected during lowering. Check the
+  // already-emitted OpenMP operation ancestry as well.
+  for (mlir::Operation *op = currentOp; op; op = op->getParentOp()) {
+    if (mlir::isa<mlir::omp::DistributeOp, mlir::omp::LoopNestOp,
+                  mlir::omp::ParallelOp, mlir::omp::ScopeOp,
+                  mlir::omp::SectionsOp, mlir::omp::SimdOp, mlir::omp::SingleOp,
+                  mlir::omp::TargetDataOp, mlir::omp::TargetOp,
+                  mlir::omp::TaskgroupOp, mlir::omp::TaskloopContextOp,
+                  mlir::omp::TaskOp, mlir::omp::TeamsOp, mlir::omp::WsloopOp>(
+            op))
+      return true;
+  }
+  return false;
+}
+
+static bool
+hasUnsupportedDataEnvironmentDirective(const ConstructQueue &queue) {
+  return llvm::any_of(queue, [](const auto &item) {
+    return llvm::omp::allParallelSet.test(item.id) ||
+           llvm::omp::taskGeneratingSet.test(item.id) ||
+           llvm::omp::allTeamsSet.test(item.id);
+  });
+}
+
+static bool hasUnsupportedDataSharingClause(const ConstructQueue &queue,
+                                            unsigned version) {
+  return llvm::any_of(queue, [version](const auto &item) {
+    return llvm::any_of(item.clauses, [version](const Clause &ompClause) {
+      return std::holds_alternative<clause::Default>(ompClause.u) ||
+             llvm::omp::isDataSharingAttributeClause(ompClause.id, version);
+    });
+  });
+}
+
+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());
+    using Symbol = semantics::Symbol;
+    semantics::SetSymbolDSA(sym,
+                            Symbol::Flags{Symbol::Flag::OmpPreDetermined, dsa});
+  }
+
+private:
+  llvm::SmallVector<std::pair<semantics::Symbol *, semantics::Symbol::Flags>, 4>
+      savedFlags;
+};
+
+enum class MetadirectiveLoopIVMarking {
+  Marked,           // Induction variables marked (or there was nothing to do).
+  NestTooShallow,   // Fewer DO loops than the variant's COLLAPSE/ORDERED needs.
+  NonCanonicalLoop, // An affected loop is a DO WHILE or has no loop control.
+  IndirectIV,       // An affected induction variable is POINTER or ALLOCATABLE.
+  AssociateIV,      // An affected induction variable is an ASSOCIATE name.
+};
+
+/// 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 a non-`Marked`
+/// result, leaving the diagnostic to the caller, when the associated loop nest
+/// is shallower than the variant's COLLAPSE/ORDERED requires or an affected
+/// loop is not a canonical DO loop or an affected induction variable requires
+/// construct-scoped name resolution that metadirective lowering cannot yet
+/// reproduce.
+static MetadirectiveLoopIVMarking
+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 MetadirectiveLoopIVMarking::Marked;
+
+  int64_t affectedDepth = *depth.value;
+  bool isSimdVariant = llvm::omp::allSimdSet.test(spec.DirId());
+  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 so the
+    // caller handles it instead of descending into a missing loop.
+    const parser::DoConstruct *doConstruct =
+        doEval ? doEval->getIf<parser::DoConstruct>() : nullptr;
+    if (!doConstruct)
+      return MetadirectiveLoopIVMarking::NestTooShallow;
+    // 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 earlier by the merged metadirective loop-nest semantic checks
+    // (check-omp-variant.cpp), so it should not reach lowering. This guard is
+    // defense-in-depth: bail out for the caller to emit a TODO rather than
+    // crash if that invariant is ever violated.
+    if (!doConstruct->IsDoNormal() && !doConstruct->IsDoConcurrent())
+      return MetadirectiveLoopIVMarking::NonCanonicalLoop;
+    if (semantics::Symbol *sym = getIterationVariableSymbol(*doEval)) {
+      // Ordinary OpenMP name resolution creates a construct-scoped symbol for
+      // an ASSOCIATE-name induction variable. Marking the associate name after
+      // name resolution cannot create the private or lastprivate binding that
+      // loop lowering requires.
+      if (sym->GetUltimate().has<semantics::AssocEntityDetails>())
+        return MetadirectiveLoopIVMarking::AssociateIV;
+      // Ordinary OpenMP semantic resolution creates a construct-scoped symbol
+      // for a POINTER or ALLOCATABLE induction variable. A metadirective
+      // variant is selected too late for that name-resolution step, and marking
+      // the descriptor-backed source symbol cannot recreate it.
+      if (semantics::IsAllocatableOrObjectPointer(sym))
+        return MetadirectiveLoopIVMarking::IndirectIV;
+      dsaGuard.setSymbolDSA(*sym, ivDSA);
----------------
MattPD wrote:

`markMetadirectiveLoopIVs` passes the original loop-IV symbol to `dsaGuard.setSymbolDSA`. By design that symbol has no `HostAssocDetails`. On the eager privatization path, `DataSharingProcessor::cloneSymbol` asserts `details && "No host-association found"` and then dereferences the missing details, so a selected `do` or `simd` metadirective compiled with `-mmlir --enable-delayed-privatization=false` aborts, and under NDEBUG this becomes a null dereference. The configuration is reachable, because some constructs still use eager privatization and the lit suite exercises that flag. Could eager privatization handle the IV that `markMetadirectiveLoopIVs` marks, either by synthesizing the host-association binding or by routing the IV through delayed privatization? Failing that, could lowering emit a graceful TODO for this configuration instead of asserting?

Reproducer: `!$omp metadirective when(implementation={vendor(llvm)}: do) otherwise(nothing)` over `do i = 1, n; a(i) = i; end do`, with `-fopenmp -fopenmp-version=52 -mmlir --enable-delayed-privatization=false`.

https://github.com/llvm/llvm-project/pull/210810


More information about the flang-commits mailing list