[llvm-branch-commits] [flang] [flang][OpenMP] Introduce `semantics::omp::LoopControl` (PR #190647)

Krzysztof Parzyszek via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Apr 6 11:54:15 PDT 2026


https://github.com/kparzysz created https://github.com/llvm/llvm-project/pull/190647

This structure will contain the symbol for the iteration variable, the lower and upper bounds, and the increment if present in the form of evaluate::Expr. A source construct (such as DO CONCURRENT) may have several iteration variables, each with its own bounds and increment, represented by a list of LoopControl structures.

For loop-transformation constructs that produce additional loops the current code returns an empty list of LoopControls, but it may be extended in the future to represent the yet-nonexistent loops for more accurate semantic analysis.

The code introduced in this PR is not executed yet, but will be used in an upcoming PR.

Issue: https://github.com/llvm/llvm-project/issues/185287

>From f0f7311f5139126b87060b0deb002a8de5345188 Mon Sep 17 00:00:00 2001
From: Krzysztof Parzyszek <Krzysztof.Parzyszek at amd.com>
Date: Tue, 24 Mar 2026 12:11:40 -0500
Subject: [PATCH] [flang][OpenMP] Introduce `semantics::omp::LoopControl`

This structure will contain the symbol for the iteration variable,
the lower and upper bounds, and the increment if present in the
form of evaluate::Expr. A source construct (such as DO CONCURRENT)
may have several iteration variables, each with its own bounds
and increment, represented by a list of LoopControl structures.

For loop-transformation constructs that produce additional loops
the current code returns an empty list of LoopControls, but it
may be extended in the future to represent the yet-nonexistent
loops for more accurate semantic analysis.

The code introduced in this PR is not executed yet, but will be
used in an upcoming PR.

Issue: https://github.com/llvm/llvm-project/issues/185287
---
 flang/include/flang/Semantics/openmp-utils.h | 23 ++++++++
 flang/lib/Semantics/check-omp-loop.cpp       |  1 -
 flang/lib/Semantics/check-omp-structure.h    |  4 ++
 flang/lib/Semantics/openmp-utils.cpp         | 62 ++++++++++++++++++--
 4 files changed, 84 insertions(+), 6 deletions(-)

diff --git a/flang/include/flang/Semantics/openmp-utils.h b/flang/include/flang/Semantics/openmp-utils.h
index 3084750bf73de..f6aebaf8ffdfc 100644
--- a/flang/include/flang/Semantics/openmp-utils.h
+++ b/flang/include/flang/Semantics/openmp-utils.h
@@ -24,6 +24,7 @@
 
 #include "llvm/ADT/ArrayRef.h"
 
+#include <memory>
 #include <optional>
 #include <string>
 #include <tuple>
@@ -124,6 +125,7 @@ std::optional<bool> IsContiguous(
 std::vector<SomeExpr> GetTopLevelDesignators(const SomeExpr &expr);
 const SomeExpr *HasStorageOverlap(
     const SomeExpr &base, llvm::ArrayRef<SomeExpr> exprs);
+
 bool IsAssignment(const parser::ActionStmt *x);
 bool IsPointerAssignment(const evaluate::Assignment &x);
 
@@ -132,6 +134,21 @@ MaybeExpr MakeEvaluateExpr(const parser::OmpStylizedInstance &inp);
 bool IsLoopTransforming(llvm::omp::Directive dir);
 bool IsFullUnroll(const parser::OmpDirectiveSpecification &spec);
 
+struct LoopControl {
+  LoopControl(LoopControl &&x) = default;
+  LoopControl(const LoopControl &x) = default;
+  LoopControl(const parser::LoopControl::Bounds &x);
+  LoopControl(const parser::ConcurrentControl &x);
+
+  const Symbol *iv{nullptr};
+  WithSource<MaybeExpr> lbound, ubound, step;
+
+private:
+  static WithSource<MaybeExpr> fromParserExpr(const parser::Expr &x);
+};
+
+std::vector<LoopControl> GetLoopControls(const parser::DoConstruct &x);
+
 /// A representation of a "because" message.
 struct Reason {
   Reason() = default;
@@ -222,6 +239,12 @@ struct LoopSequence {
   WithReason<bool> isWellFormedSequence() const;
   WithReason<bool> isWellFormedNest() const;
 
+  std::vector<LoopControl> getLoopControls() const;
+  // Check if this loop's bounds are invariant in each of the `outer`
+  // constructs.
+  WithReason<bool> isRectangular(
+      const std::vector<const LoopSequence *> &outer) const;
+
 private:
   using Construct = ExecutionPartIterator::Construct;
 
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 5bbf7bcd627ec..48d0684320e1c 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -267,7 +267,6 @@ void OmpStructureChecker::CheckNestedConstruct(
   }
 
   LoopSequence sequence(body, version, true);
-
   auto assoc{llvm::omp::getDirectiveAssociation(dir)};
   auto needRange{GetAffectedLoopRangeWithReason(beginSpec, version)};
   auto haveLength{sequence.length()};
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index dc84c9d9ae9d8..a27b16561d3f7 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -49,6 +49,10 @@ static const OmpDirectiveSet noWaitClauseNotAllowedSet{
 namespace Fortran::semantics {
 struct AnalyzedCondStmt;
 
+namespace omp {
+struct LoopSequence;
+}
+
 // Mapping from 'Symbol' to 'Source' to keep track of the variables
 // used in multiple clauses
 using SymbolSourceMap = std::multimap<const Symbol *, parser::CharBlock>;
diff --git a/flang/lib/Semantics/openmp-utils.cpp b/flang/lib/Semantics/openmp-utils.cpp
index 1d9de49e6cd8e..bdde253580c26 100644
--- a/flang/lib/Semantics/openmp-utils.cpp
+++ b/flang/lib/Semantics/openmp-utils.cpp
@@ -33,9 +33,14 @@
 
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
 
+#include <array>
 #include <cinttypes>
+#include <list>
+#include <memory>
 #include <optional>
+#include <set>
 #include <string>
 #include <tuple>
 #include <type_traits>
@@ -592,6 +597,44 @@ static bool IsTransformableLoop(const parser::ExecutionPartConstruct &epc) {
   return false;
 }
 
+LoopControl::LoopControl(const parser::LoopControl::Bounds &x) {
+  iv = x.Name().thing.symbol;
+  lbound = fromParserExpr(parser::UnwrapRef<parser::Expr>(x.Lower()));
+  ubound = fromParserExpr(parser::UnwrapRef<parser::Expr>(x.Upper()));
+  if (auto &inc{x.Step()}) {
+    step = fromParserExpr(parser::UnwrapRef<parser::Expr>(*inc));
+  }
+}
+
+LoopControl::LoopControl(const parser::ConcurrentControl &x) {
+  auto &[name, lower, upper, inc]{x.t};
+  iv = name.symbol;
+  lbound = fromParserExpr(parser::UnwrapRef<parser::Expr>(lower));
+  ubound = fromParserExpr(parser::UnwrapRef<parser::Expr>(upper));
+  if (inc) {
+    step = fromParserExpr(parser::UnwrapRef<parser::Expr>(inc));
+  }
+}
+
+WithSource<MaybeExpr> LoopControl::fromParserExpr(const parser::Expr &x) {
+  return WithSource<MaybeExpr>(GetEvaluateExpr(x), x.source);
+}
+
+std::vector<LoopControl> GetLoopControls(const parser::DoConstruct &x) {
+  std::vector<LoopControl> controls;
+  if (x.IsDoNormal()) {
+    const parser::LoopControl &control{*x.GetLoopControl()};
+    controls.emplace_back(std::get<parser::LoopControl::Bounds>(control.u));
+  } else if (x.IsDoConcurrent()) {
+    const parser::LoopControl &control{*x.GetLoopControl()};
+    auto &header{parser::UnwrapRef<parser::ConcurrentHeader>(control)};
+    for (auto &cc : std::get<std::list<parser::ConcurrentControl>>(header.t)) {
+      controls.emplace_back(cc);
+    }
+  }
+  return controls;
+}
+
 static const auto MsgNotValidAffectedLoop{
     "%s is not a valid affected loop"_because_en_US};
 static const auto MsgClauseAbsentAssume{
@@ -935,8 +978,6 @@ WithReason<std::pair<int64_t, int64_t>> GetAffectedLoopRangeWithReason(
       if (!first || !count || *first <= 0 || *count <= 0) {
         return {};
       }
-      std::string name{
-          GetUpperName(llvm::omp::Clause::OMPC_looprange, version)};
       Reason reason;
       reason.Say(clause->source,
           "%s clause was specified with a count of %" PRId64
@@ -1098,6 +1139,17 @@ void LoopSequence::createChildrenFromRange(
   }
 }
 
+std::vector<LoopControl> LoopSequence::getLoopControls() const {
+  if (!entry_->owner) {
+    return {};
+  }
+
+  if (auto *loop{parser::Unwrap<parser::DoConstruct>(*entry_->owner)}) {
+    return GetLoopControls(*loop);
+  }
+  return {};
+}
+
 void LoopSequence::precalculate() {
   // Calculate length before depths.
   length_ = calculateLength();
@@ -1210,8 +1262,8 @@ static Reason WhyNotWellFormed(
 
 LoopSequence::Depth LoopSequence::calculateDepths() const {
   // Get the length of the nested sequence. The invalidIC_ and opaqueIC_
-  // members do not count canonical loop nests, but there can only be one
-  // for depth to make sense.
+  // members do not include sibling canonical loop nests, but there can
+  // only be one for depth to make sense.
   WithReason<int64_t> nestedLength{getNestedLength()};
   // Get the depths of the code nested in this sequence (e.g. contained in
   // entry_), and use it as the basis for the depths of entry_->owner.
@@ -1303,7 +1355,7 @@ LoopSequence::Depth LoopSequence::calculateDepths() const {
           static_cast<int64_t>(num) + perfDepth};
     }
     // The SIZES clause is mandatory, if it's missing the result is unknown.
-    return {};
+    return Depth{};
   case llvm::omp::Directive::OMPD_unroll:
     if (isFullUnroll) {
       Reason reason;



More information about the llvm-branch-commits mailing list