[flang-commits] [flang] [flang][OpenMP] Lower DO, SIMD, and DO SIMD metadirective variants (PR #218555)

via flang-commits flang-commits at lists.llvm.org
Wed Sep 2 00:41:26 PDT 2026


================
@@ -6845,23 +6857,402 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
 }
 
 namespace {
-struct MetadirectiveCandidate {
-  MetadirectiveCandidate(const parser::OmpDirectiveSpecification *spec,
-                         llvm::omp::VariantMatchInfo vmi, bool isExplicit,
-                         std::optional<semantics::omp::DynamicUserCondition>
-                             dynamicCond = std::nullopt,
-                         bool conditionShouldBeTrue = true)
-      : spec(spec), vmi(vmi), isExplicit(isExplicit), dynamicCond(dynamicCond),
-        conditionShouldBeTrue(conditionShouldBeTrue) {}
-
-  const parser::OmpDirectiveSpecification *spec = nullptr;
-  llvm::omp::VariantMatchInfo vmi;
-  bool isExplicit = false;
-  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
 
+static bool isSupportedMetadirectiveLoopCompilerDirective(
+    const parser::CompilerDirective &directive) {
+  // Only directives known not to emit executable operations may be processed
+  // before runtime selection. New variants remain unsupported until classified.
+  using SupportedDirectives = std::tuple<
+      std::list<parser::CompilerDirective::IgnoreTKR>,
+      parser::CompilerDirective::LoopCount,
+      std::list<parser::CompilerDirective::AssumeAligned>,
+      parser::CompilerDirective::VectorAlways,
+      parser::CompilerDirective::VectorLength,
+      std::list<parser::CompilerDirective::NameValue>,
+      parser::CompilerDirective::Unroll,
+      parser::CompilerDirective::UnrollAndJam,
+      parser::CompilerDirective::Unrecognized,
+      parser::CompilerDirective::NoVector, parser::CompilerDirective::NoUnroll,
+      parser::CompilerDirective::NoUnrollAndJam,
+      parser::CompilerDirective::ForceInline, parser::CompilerDirective::Inline,
+      parser::CompilerDirective::NoInline,
+      parser::CompilerDirective::InlineAlways, parser::CompilerDirective::IVDep,
+      parser::CompilerDirective::Simd>;
+
+  return common::visit(
+      [](const auto &value) {
+        using T = std::decay_t<decltype(value)>;
+        return common::HasMember<T, SupportedDirectives>;
+      },
+      directive.u);
+}
+
+static bool
+isIgnorableMetadirectiveLoopAssociationEval(lower::pft::Evaluation &eval) {
+  const auto *directive = eval.getIf<parser::CompilerDirective>();
+  return eval.isEndStmt() ||
+         (directive &&
+          isSupportedMetadirectiveLoopCompilerDirective(*directive));
+}
+
+static bool
+isUnsupportedMetadirectiveLoopAssociationEval(lower::pft::Evaluation &eval) {
+  if (const auto *directive = eval.getIf<parser::CompilerDirective>())
+    return !isSupportedMetadirectiveLoopCompilerDirective(*directive);
+  return eval.isOtherStmt() ||
+         (eval.isDirective() && !eval.isExecutableDirective());
+}
+
+/// 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,
+    lower::pft::Evaluation **unsupportedInterveningEval = nullptr) {
+  if (unsupportedInterveningEval)
+    *unsupportedInterveningEval = nullptr;
+
+  if (eval.hasNestedEvaluations()) {
+    auto nestedIt =
+        llvm::find_if(eval.getNestedEvaluations(), [](auto &nested) {
+          return !isIgnorableMetadirectiveLoopAssociationEval(nested);
+        });
+    if (nestedIt != eval.getNestedEvaluations().end()) {
+      if (nestedIt->getIf<parser::DoConstruct>())
+        return &*nestedIt;
+      if (unsupportedInterveningEval &&
+          isUnsupportedMetadirectiveLoopAssociationEval(*nestedIt))
+        *unsupportedInterveningEval = &*nestedIt;
+    }
+    return nullptr;
+  }
+
+  // A delimited metadirective owns only its nested evaluations. An empty body
+  // must not capture a following sibling loop as its associated DO.
+  if (const auto *omp = eval.getIf<parser::OpenMPConstruct>();
+      omp && std::holds_alternative<parser::OmpDelimitedMetadirectiveDirective>(
+                 omp->u))
+    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() &&
+         isIgnorableMetadirectiveLoopAssociationEval(*loopIt))
+    ++loopIt;
+
+  if (loopIt == parentList->end())
+    return nullptr;
+  if (!loopIt->getIf<parser::DoConstruct>()) {
+    if (unsupportedInterveningEval &&
+        isUnsupportedMetadirectiveLoopAssociationEval(*loopIt))
+      *unsupportedInterveningEval = &*loopIt;
+    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 hasContentFollowingAssociatedDo(lower::pft::Evaluation &eval,
+                                            lower::pft::Evaluation &loopEval) {
+  auto &nested = eval.getNestedEvaluations();
+  auto loopIt = llvm::find_if(
+      nested, [&](lower::pft::Evaluation &e) { return &e == &loopEval; });
+  assert(loopIt != nested.end() && "associated loop not nested");
+  return llvm::any_of(llvm::make_range(std::next(loopIt), nested.end()),
+                      [](lower::pft::Evaluation &e) {
+                        // PFTBuilder adds a source-less CONTINUE as the exit
+                        // target when an executable directive's region ends
+                        // with a construct.
+                        bool isSyntheticExit =
+                            e.getIf<parser::ContinueStmt>() &&
+                            e.position.empty();
+                        return !e.isEndStmt() && !isSyntheticExit;
+                      });
+}
+
+static bool hasNestedOpenMPConstruct(lower::pft::Evaluation &eval) {
+  if (!eval.hasNestedEvaluations())
+    return false;
+  for (lower::pft::Evaluation &nested : eval.getNestedEvaluations()) {
+    if (nested.getIf<parser::OpenMPConstruct>() ||
+        hasNestedOpenMPConstruct(nested))
+      return true;
+  }
+  return false;
+}
+
+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,
+                                            llvm::omp::Version 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());
+      markedSymbols.push_back(&sym);
+    }
+    using Symbol = semantics::Symbol;
+    semantics::SetSymbolDSA(sym, {Symbol::Flag::OmpPreDetermined, dsa});
+  }
+
+  llvm::ArrayRef<const semantics::Symbol *> getMarkedSymbols() const {
+    return markedSymbols;
+  }
+
+private:
+  llvm::SmallVector<std::pair<semantics::Symbol *, semantics::Symbol::Flags>, 4>
+      savedFlags;
+  llvm::SmallVector<const semantics::Symbol *, 4> markedSymbols;
+};
+
+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.
+  ThreadprivateIV,  // An affected induction variable is THREADPRIVATE.
+};
+
+/// 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().getOpenMPVersion(), &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().getOpenMPVersion() < 60)
----------------
MattPD wrote:

When a SIMD variant affects one loop level, `markMetadirectiveLoopIVs` selects linear IV lowering before OpenMP 6.0. Starting with OpenMP 6.0, the function selects lastprivate IV lowering. Every lowering test in this PR uses OpenMP 5.2 or earlier.

OpenMP 5.2 lowers the SIMD IV with an implicit linear clause. OpenMP 6.0 uses a private recipe, a last-iteration guard, and a host copy-back.

The OpenMP 6.0 output passes verification and matches the lowering of a standalone SIMD directive, so I found no defect in the current implementation.

Could a focused test with `-fopenmp-version=60` check these OpenMP 6.0 properties: use of the private IV in the loop body, the last-iteration guard, the host copy-back, and the absence of an implicit linear clause? Such a test would exercise the version boundary added by this PR.

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


More information about the flang-commits mailing list