[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