[flang-commits] [flang] [flang][PFT-to-MLIR] Do not wrap the DO of an OpenMP loop construct (PR #216280)

Tom Eccles via flang-commits flang-commits at lists.llvm.org
Fri Aug 14 09:23:59 PDT 2026


================
@@ -2497,74 +2498,125 @@ hasIncomingBranch(const Fortran::lower::pft::Evaluation &construct) {
   return walk(funit->evaluationList);
 }
 
-/// True if \p eval is a DoConstruct driven directly into an enclosing acc.loop
-/// by the OpenACCLoopConstruct / OpenACCCombinedConstruct lowering — the
-/// immediate body DO, or one of the N collapsed iterator DOs reached by
-/// walking down from the body DO under a `collapse(N)` clause.
-static bool isAccLoopBody(const Fortran::lower::pft::Evaluation &eval) {
+/// The DoConstructs enclosing (and including) a DO evaluation; index k holds
+/// the one at depth k, so index 0 is the evaluation's own DO.
+using DoConstructChain = llvm::SmallVector<const parser::DoConstruct *, 4>;
+
+/// Value of \p intExpr, or INT64_MAX if it isn't a compile-time constant.
+static int64_t
+constantValueOrMax(const parser::ScalarIntConstantExpr &intExpr) {
+  if (const auto *expr = semantics::GetExpr(intExpr))
+    if (auto v = evaluate::ToInt64(*expr))
+      return *v;
+  return std::numeric_limits<int64_t>::max();
+}
+
+/// Fill \p chain with the DoConstruct at each depth above (and including)
+/// \p eval, and return the innermost enclosing evaluation that is not a
+/// DoConstruct — the one a directive would be attached to. Returns null if
+/// \p eval is not a DoConstruct or has no such enclosing evaluation.
+static const Fortran::lower::pft::Evaluation *
+collectEnclosingDoChain(const Fortran::lower::pft::Evaluation &eval,
+                        DoConstructChain &chain) {
   const auto *doConstruct = eval.getIf<parser::DoConstruct>();
   if (!doConstruct)
-    return false;
-  // N from `collapse(N)`, or 1 if no clause. eval at depth d from the body
-  // (d == 0 means eval IS the body) is a collapsed iterator iff d < N. If the
-  // Collapse value isn't a compile-time constant, be conservative and treat
-  // every DO in the chain as collapsed (INT64_MAX) — wrapping is opt-in and
-  // a false "is collapsed" is safer than a false "is not".
-  auto collapseN = [](const parser::AccClauseList &cl) -> int64_t {
-    for (const parser::AccClause &c : cl.v)
-      if (const auto *cc = std::get_if<parser::AccClause::Collapse>(&c.u)) {
-        const auto &intExpr = std::get<parser::ScalarIntConstantExpr>(cc->v.t);
-        if (const auto *expr = semantics::GetExpr(intExpr))
-          if (auto v = evaluate::ToInt64(*expr))
-            return *v;
-        return std::numeric_limits<int64_t>::max();
-      }
-    return 1;
-  };
-
-  // candidates[k] is the DoConstruct at depth k above (and including) eval.
-  llvm::SmallVector<const parser::DoConstruct *, 4> candidates{doConstruct};
+    return nullptr;
+  chain.push_back(doConstruct);
 
   for (const Fortran::lower::pft::Evaluation *p = eval.parentConstruct; p;
        p = p->parentConstruct) {
     if (const auto *d = p->getIf<parser::DoConstruct>()) {
-      candidates.push_back(d);
+      chain.push_back(d);
       continue;
     }
+    return p;
+  }
+  return nullptr;
+}
 
-    if (const auto *acc = p->getIf<parser::OpenACCConstruct>()) {
-      const parser::DoConstruct *body = nullptr;
-      int64_t n = 1;
-      if (const auto *loop =
-              std::get_if<parser::OpenACCLoopConstruct>(&acc->u)) {
-        if (const auto &b =
-                std::get<std::optional<parser::DoConstruct>>(loop->t))
-          body = &b.value();
-        n = collapseN(std::get<parser::AccClauseList>(std::get<0>(loop->t).t));
-      } else if (const auto *comb =
-                     std::get_if<parser::OpenACCCombinedConstruct>(&acc->u)) {
-        if (const auto &b =
-                std::get<std::optional<parser::DoConstruct>>(comb->t))
-          body = &b.value();
-        n = collapseN(std::get<parser::AccClauseList>(std::get<0>(comb->t).t));
-      }
+/// True if the DO at depth 0 of \p chain is one of the \p n loops a directive
+/// associates with itself, given that the directive's body DO is \p body.
+///
+/// \p body is the outermost candidate, so the evaluation sits at depth
+/// `index of body in chain` below it and is associated iff that depth < \p n.
+/// A \p body outside \p chain is not an error: OpenMPLoopConstruct's body is
+/// found by searching the construct's block (looking through a BLOCK
+/// construct), so it can name a DO that is not on this ancestor chain.
+static bool isAssociatedLoop(const DoConstructChain &chain,
+                             const parser::DoConstruct *body, int64_t n) {
+  if (!body)
+    return false;
+  auto it = llvm::find(chain, body);
+  if (it == chain.end())
+    return false;
+  return std::distance(chain.begin(), it) < n;
+}
 
-      if (body) {
-        // body is at index `candidates.size()-1` (the outermost candidate);
-        // eval at depth (candidates.size()-1) from body. Collapsed iff < N.
-        auto it = llvm::find(candidates, body);
-        if (it != candidates.end()) {
-          int64_t depth = std::distance(candidates.begin(), it);
-          if (depth < n)
-            return true;
-        }
-      }
-    }
+/// True if \p eval is a DoConstruct attached to an enclosing OpenACC loop.
+static bool isAccLoopBody(const Fortran::lower::pft::Evaluation &eval) {
+  DoConstructChain chain;
+  const Fortran::lower::pft::Evaluation *p =
+      collectEnclosingDoChain(eval, chain);
+  if (!p)
+    return false;
+
+  const auto *acc = p->getIf<parser::OpenACCConstruct>();
+  if (!acc)
+    return false;
 
-    break;
+  // N from `collapse(N)`, or 1 if no clause.
+  auto collapseValue = [](const parser::AccClauseList &cl) -> int64_t {
+    for (const parser::AccClause &c : cl.v)
+      if (const auto *cc = std::get_if<parser::AccClause::Collapse>(&c.u))
+        return constantValueOrMax(
+            std::get<parser::ScalarIntConstantExpr>(cc->v.t));
+    return 1;
+  };
+
+  const parser::DoConstruct *body = nullptr;
+  int64_t n = 1;
+  if (const auto *loop = std::get_if<parser::OpenACCLoopConstruct>(&acc->u)) {
+    if (const auto &b = std::get<std::optional<parser::DoConstruct>>(loop->t))
+      body = &b.value();
+    n = collapseValue(std::get<parser::AccClauseList>(std::get<0>(loop->t).t));
+  } else if (const auto *comb =
+                 std::get_if<parser::OpenACCCombinedConstruct>(&acc->u)) {
+    if (const auto &b = std::get<std::optional<parser::DoConstruct>>(comb->t))
+      body = &b.value();
+    n = collapseValue(std::get<parser::AccClauseList>(std::get<0>(comb->t).t));
   }
 
-  return false;
+  return isAssociatedLoop(chain, body, n);
+}
+
+/// True if \p eval is a DoConstruct attached to an enclosing OpenMP loop.
+static bool isOmpLoopBody(const Fortran::lower::pft::Evaluation &eval) {
+  DoConstructChain chain;
+  const Fortran::lower::pft::Evaluation *p =
+      collectEnclosingDoChain(eval, chain);
+  if (!p)
+    return false;
+
+  const auto *omp = p->getIf<parser::OpenMPConstruct>();
+  if (!omp)
+    return false;
+
+  const auto *loop = std::get_if<parser::OpenMPLoopConstruct>(&omp->u);
+  if (!loop)
+    return false;
+
+  // Both `collapse(N)` and `ordered(N)` associate N loops with the directive,
+  // so the associated loop count is the larger of the two.
+  int64_t n = 1;
+  for (const parser::OmpClause &c : loop->BeginDir().Clauses().v) {
+    if (const auto *cc = std::get_if<parser::OmpClause::Collapse>(&c.u))
+      n = std::max(n, constantValueOrMax(cc->v));
+    else if (const auto *oc = std::get_if<parser::OmpClause::Ordered>(&c.u))
----------------
tblah wrote:

What about `tile`? This is a transforming directive not a clause but it could still influence the affected loops.

There is already an implementation of figuring out the affected loop depth in OpenMP semantics. See `GetAffectedNestDepthWithReason`. I think the same logic should be re-used here because it would be too easy to forget to add a new loop transformation here.

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


More information about the flang-commits mailing list