[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:09 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,
----------------
MattPD wrote:
The merged metadirective loop checks validate canonical form and depth, but the ordinary OpenMP iteration-variable checks do not seem to apply to the selectable loop variants, and lowering marks the IV DSA without them. A non-integer IV currently reaches the misleading `unstructured associated DO` TODO. A THREADPRIVATE IV is accepted and lowered, producing both `omp.threadprivate` and an `omp.wsloop private` for the same variable. An ordinary `!$omp do` rejects both (`... must be of integer type` and `... cannot be THREADPRIVATE`). Could the ordinary iteration-variable checks be reused for each selectable loop variant, with negative tests for the non-integer and THREADPRIVATE cases?
https://github.com/llvm/llvm-project/pull/210810
More information about the flang-commits
mailing list