[flang-commits] [flang] [Flang][OpenMP] Add semantic support for Loop Sequences and OpenMP loop fuse (PR #161213)
Ferran Toda via flang-commits
flang-commits at lists.llvm.org
Mon Oct 20 07:51:40 PDT 2025
https://github.com/NouTimbaler updated https://github.com/llvm/llvm-project/pull/161213
>From c761405274c57d65a86c5a2e0d48c3729bacaaf0 Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Mon, 29 Sep 2025 14:33:49 +0000
Subject: [PATCH 1/4] Loop sequences and loop fuse semantics
---
flang/include/flang/Parser/parse-tree.h | 4 +-
.../flang/Semantics/openmp-directive-sets.h | 7 +
flang/lib/Lower/OpenMP/OpenMP.cpp | 9 +-
flang/lib/Lower/OpenMP/Utils.cpp | 8 +-
flang/lib/Parser/openmp-parsers.cpp | 1 +
flang/lib/Parser/unparse.cpp | 3 +-
flang/lib/Semantics/canonicalize-omp.cpp | 92 ++++++----
flang/lib/Semantics/check-omp-loop.cpp | 30 ++--
flang/lib/Semantics/resolve-directives.cpp | 157 +++++++++---------
flang/lib/Semantics/rewrite-parse-tree.cpp | 44 +++--
flang/test/Parser/OpenMP/fuse.f90 | 28 ++++
.../loop-transformation-construct04.f90 | 78 +++++++++
.../loop-transformation-construct05.f90 | 86 ++++++++++
.../loop-transformation-construct01.f90 | 4 +-
.../loop-transformation-construct02.f90 | 95 +++++++++++
15 files changed, 489 insertions(+), 157 deletions(-)
create mode 100644 flang/test/Parser/OpenMP/fuse.f90
create mode 100644 flang/test/Parser/OpenMP/loop-transformation-construct04.f90
create mode 100644 flang/test/Parser/OpenMP/loop-transformation-construct05.f90
create mode 100644 flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 325ca9b4a227b..67bcbbd923375 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -5177,7 +5177,7 @@ using NestedConstruct =
struct OpenMPLoopConstruct {
TUPLE_CLASS_BOILERPLATE(OpenMPLoopConstruct);
OpenMPLoopConstruct(OmpBeginLoopDirective &&a)
- : t({std::move(a), std::nullopt, std::nullopt}) {}
+ : t({std::move(a), std::list<NestedConstruct>(), std::nullopt}) {}
const OmpBeginLoopDirective &BeginDir() const {
return std::get<OmpBeginLoopDirective>(t);
@@ -5185,7 +5185,7 @@ struct OpenMPLoopConstruct {
const std::optional<OmpEndLoopDirective> &EndDir() const {
return std::get<std::optional<OmpEndLoopDirective>>(t);
}
- std::tuple<OmpBeginLoopDirective, std::optional<NestedConstruct>,
+ std::tuple<OmpBeginLoopDirective, std::list<NestedConstruct>,
std::optional<OmpEndLoopDirective>>
t;
};
diff --git a/flang/include/flang/Semantics/openmp-directive-sets.h b/flang/include/flang/Semantics/openmp-directive-sets.h
index 01e8481e05721..609a7be700c28 100644
--- a/flang/include/flang/Semantics/openmp-directive-sets.h
+++ b/flang/include/flang/Semantics/openmp-directive-sets.h
@@ -275,10 +275,17 @@ static const OmpDirectiveSet loopConstructSet{
Directive::OMPD_teams_distribute_parallel_do_simd,
Directive::OMPD_teams_distribute_simd,
Directive::OMPD_teams_loop,
+ Directive::OMPD_fuse,
Directive::OMPD_tile,
Directive::OMPD_unroll,
};
+static const OmpDirectiveSet loopTransformationSet{
+ Directive::OMPD_tile,
+ Directive::OMPD_unroll,
+ Directive::OMPD_fuse,
+};
+
static const OmpDirectiveSet nonPartialVarSet{
Directive::OMPD_allocate,
Directive::OMPD_allocators,
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 1cb3335abbd06..c3598f6846822 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3360,6 +3360,7 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
newOp = genTeamsOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue,
item);
break;
+ case llvm::omp::Directive::OMPD_fuse:
case llvm::omp::Directive::OMPD_tile: {
unsigned version = semaCtx.langOptions().OpenMPVersion;
if (!semaCtx.langOptions().OpenMPSimd)
@@ -3814,12 +3815,12 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
mlir::Location currentLocation = converter.genLocation(beginSpec.source);
- auto &optLoopCons =
- std::get<std::optional<parser::NestedConstruct>>(loopConstruct.t);
- if (optLoopCons.has_value()) {
+ auto &loopConsList =
+ std::get<std::list<parser::NestedConstruct>>(loopConstruct.t);
+ for (auto &loopCons : loopConsList) {
if (auto *ompNestedLoopCons{
std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
- &*optLoopCons)}) {
+ &loopCons)}) {
llvm::omp::Directive nestedDirective =
parser::omp::GetOmpDirectiveName(*ompNestedLoopCons).v;
switch (nestedDirective) {
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 83b7ccb1ce0ee..30e90b2aed50b 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -607,13 +607,13 @@ static void processTileSizesFromOpenMPConstruct(
if (!ompCons)
return;
if (auto *ompLoop{std::get_if<parser::OpenMPLoopConstruct>(&ompCons->u)}) {
- const auto &nestedOptional =
- std::get<std::optional<parser::NestedConstruct>>(ompLoop->t);
- assert(nestedOptional.has_value() &&
+ const auto &loopConsList =
+ std::get<std::list<parser::NestedConstruct>>(ompLoop->t);
+ assert(loopConsList.size() == 1 &&
"Expected a DoConstruct or OpenMPLoopConstruct");
const auto *innerConstruct =
std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
- &(nestedOptional.value()));
+ &(loopConsList.front()));
if (innerConstruct) {
const auto &innerLoopDirective = innerConstruct->value();
const parser::OmpDirectiveSpecification &innerBeginSpec =
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index 9507021057476..0082f1180a60d 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -2019,6 +2019,7 @@ static constexpr DirectiveSet GetLoopDirectives() {
unsigned(Directive::OMPD_teams_distribute_parallel_do_simd),
unsigned(Directive::OMPD_teams_distribute_simd),
unsigned(Directive::OMPD_teams_loop),
+ unsigned(Directive::OMPD_fuse),
unsigned(Directive::OMPD_tile),
unsigned(Directive::OMPD_unroll),
};
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 0511f5bdf7478..7d4b9f903b1d1 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2720,8 +2720,7 @@ class UnparseVisitor {
}
void Unparse(const OpenMPLoopConstruct &x) {
Walk(std::get<OmpBeginLoopDirective>(x.t));
- Walk(std::get<std::optional<std::variant<DoConstruct,
- common::Indirection<parser::OpenMPLoopConstruct>>>>(x.t));
+ Walk(std::get<std::list<parser::NestedConstruct>>(x.t));
Walk(std::get<std::optional<OmpEndLoopDirective>>(x.t));
}
void Unparse(const BasedPointer &x) {
diff --git a/flang/lib/Semantics/canonicalize-omp.cpp b/flang/lib/Semantics/canonicalize-omp.cpp
index c884658bf464a..74b3fa978a53c 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -10,6 +10,7 @@
#include "flang/Parser/parse-tree-visitor.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Semantics/semantics.h"
+#include "flang/Semantics/openmp-directive-sets.h"
// After Loop Canonicalization, rewrite OpenMP parse tree to make OpenMP
// Constructs more structured which provide explicit scopes for later
@@ -137,30 +138,42 @@ class CanonicalizationOfOmp {
"A DO loop must follow the %s directive"_err_en_US,
parser::ToUpperCaseLetters(dirName.source.ToString()));
};
- auto tileUnrollError = [](const parser::OmpDirectiveName &dirName,
+ auto transformUnrollError = [](const parser::OmpDirectiveName &dirName,
parser::Messages &messages) {
messages.Say(dirName.source,
- "If a loop construct has been fully unrolled, it cannot then be tiled"_err_en_US,
+ "If a loop construct has been fully unrolled, it cannot then be further transformed"_err_en_US,
parser::ToUpperCaseLetters(dirName.source.ToString()));
};
+ auto missingEndFuse = [](auto &dir, auto &messages) {
+ messages.Say(dir.source,
+ "The %s construct requires the END FUSE directive"_err_en_US,
+ parser::ToUpperCaseLetters(dir.source.ToString()));
+ };
+
+ bool endFuseNeeded = beginName.v == llvm::omp::Directive::OMPD_fuse;
nextIt = it;
- while (++nextIt != block.end()) {
+ nextIt++;
+ while (nextIt != block.end()) {
// Ignore compiler directives.
- if (GetConstructIf<parser::CompilerDirective>(*nextIt))
+ if (GetConstructIf<parser::CompilerDirective>(*nextIt)) {
+ nextIt++;
continue;
+ }
if (auto *doCons{GetConstructIf<parser::DoConstruct>(*nextIt)}) {
if (doCons->GetLoopControl()) {
// move DoConstruct
- std::get<std::optional<std::variant<parser::DoConstruct,
- common::Indirection<parser::OpenMPLoopConstruct>>>>(x.t) =
- std::move(*doCons);
+ std::get<std::list<parser::NestedConstruct>>(x.t).push_back(
+ std::move(*doCons));
nextIt = block.erase(nextIt);
// try to match OmpEndLoopDirective
if (nextIt != block.end()) {
if (auto *endDir{
GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
+ auto &endDirName = endDir->DirName();
+ if (endDirName.v == llvm::omp::Directive::OMPD_fuse)
+ endFuseNeeded = false;
std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
std::move(*endDir);
nextIt = block.erase(nextIt);
@@ -170,6 +183,7 @@ class CanonicalizationOfOmp {
messages_.Say(beginName.source,
"DO loop after the %s directive must have loop control"_err_en_US,
parser::ToUpperCaseLetters(beginName.source.ToString()));
+ endFuseNeeded = false;
}
} else if (auto *ompLoopCons{
GetOmpIf<parser::OpenMPLoopConstruct>(*nextIt)}) {
@@ -177,10 +191,29 @@ class CanonicalizationOfOmp {
// OpenMP Loop Construct and the DO loop itself
auto &nestedBeginDirective = ompLoopCons->BeginDir();
auto &nestedBeginName = nestedBeginDirective.DirName();
- if ((nestedBeginName.v == llvm::omp::Directive::OMPD_unroll ||
- nestedBeginName.v == llvm::omp::Directive::OMPD_tile) &&
- !(nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
- beginName.v == llvm::omp::Directive::OMPD_tile)) {
+ if (llvm::omp::loopTransformationSet.test(nestedBeginName.v)) {
+ if (nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
+ llvm::omp::loopTransformationSet.test(beginName.v)) {
+ // if a loop has been unrolled, the user can not then tile that loop
+ // as it has been unrolled
+ const parser::OmpClauseList &unrollClauseList{
+ nestedBeginDirective.Clauses()};
+ if (unrollClauseList.v.empty()) {
+ // if the clause list is empty for an unroll construct, we assume
+ // the loop is being fully unrolled
+ transformUnrollError(beginName, messages_);
+ endFuseNeeded = false;
+ } else {
+ // parse the clauses for the unroll directive to find the full
+ // clause
+ for (auto &clause : unrollClauseList.v) {
+ if (clause.Id() == llvm::omp::OMPC_full) {
+ transformUnrollError(beginName, messages_);
+ endFuseNeeded = false;
+ }
+ }
+ }
+ }
// iterate through the remaining block items to find the end directive
// for the unroll/tile directive.
parser::Block::iterator endIt;
@@ -190,6 +223,8 @@ class CanonicalizationOfOmp {
GetConstructIf<parser::OmpEndLoopDirective>(*endIt)}) {
auto &endDirName = endDir->DirName();
if (endDirName.v == beginName.v) {
+ if (endDirName.v == llvm::omp::Directive::OMPD_fuse)
+ endFuseNeeded = false;
std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
std::move(*endDir);
endIt = block.erase(endIt);
@@ -199,43 +234,30 @@ class CanonicalizationOfOmp {
++endIt;
}
RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
- auto &ompLoop = std::get<std::optional<parser::NestedConstruct>>(x.t);
- ompLoop =
- std::optional<parser::NestedConstruct>{parser::NestedConstruct{
- common::Indirection{std::move(*ompLoopCons)}}};
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ loopConsList.push_back(parser::NestedConstruct{
+ common::Indirection{std::move(*ompLoopCons)}});
nextIt = block.erase(nextIt);
- } else if (nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
- beginName.v == llvm::omp::Directive::OMPD_tile) {
- // if a loop has been unrolled, the user can not then tile that loop
- // as it has been unrolled
- const parser::OmpClauseList &unrollClauseList{
- nestedBeginDirective.Clauses()};
- if (unrollClauseList.v.empty()) {
- // if the clause list is empty for an unroll construct, we assume
- // the loop is being fully unrolled
- tileUnrollError(beginName, messages_);
- } else {
- // parse the clauses for the unroll directive to find the full
- // clause
- for (auto &clause : unrollClauseList.v) {
- if (clause.Id() == llvm::omp::OMPC_full) {
- tileUnrollError(beginName, messages_);
- }
- }
- }
} else {
messages_.Say(nestedBeginName.source,
"Only Loop Transformation Constructs or Loop Nests can be nested within Loop Constructs"_err_en_US,
parser::ToUpperCaseLetters(nestedBeginName.source.ToString()));
+ endFuseNeeded = false;
}
} else {
missingDoConstruct(beginName, messages_);
+ endFuseNeeded = false;
}
+ if (endFuseNeeded)
+ continue;
// If we get here, we either found a loop, or issued an error message.
return;
}
if (nextIt == block.end()) {
- missingDoConstruct(beginName, messages_);
+ if (endFuseNeeded)
+ missingEndFuse(beginName, messages_);
+ else
+ missingDoConstruct(beginName, messages_);
}
}
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index c9d0495850b6e..7b95f4b6c31bf 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -286,10 +286,9 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
}
SetLoopInfo(x);
- auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
- if (optLoopCons.has_value()) {
- if (const auto &doConstruct{
- std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ for (auto &loopCons : loopConsList) {
+ if (const auto &doConstruct{std::get_if<parser::DoConstruct>(&loopCons)}) {
const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
CheckNoBranching(doBlock, beginName.v, beginName.source);
}
@@ -315,10 +314,10 @@ const parser::Name OmpStructureChecker::GetLoopIndex(
}
void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
- auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
- if (optLoopCons.has_value()) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ if (loopConsList.size() == 1) {
if (const auto &loopConstruct{
- std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ std::get_if<parser::DoConstruct>(&loopConsList.front())}) {
const parser::DoConstruct *loop{&*loopConstruct};
if (loop && loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
@@ -330,10 +329,10 @@ void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
void OmpStructureChecker::CheckLoopItrVariableIsInt(
const parser::OpenMPLoopConstruct &x) {
- auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
- if (optLoopCons.has_value()) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ for (auto &loopCons : loopConsList) {
if (const auto &loopConstruct{
- std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ std::get_if<parser::DoConstruct>(&loopCons)}) {
for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
if (loop->IsDoNormal()) {
@@ -418,10 +417,11 @@ void OmpStructureChecker::CheckDistLinear(
// Match the loop index variables with the collected symbols from linear
// clauses.
- auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
- if (optLoopCons.has_value()) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ for (auto &loopCons : loopConsList) {
+ std::int64_t collapseVal_ = collapseVal;
if (const auto &loopConstruct{
- std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ std::get_if<parser::DoConstruct>(&loopCons)}) {
for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
if (loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
@@ -429,8 +429,8 @@ void OmpStructureChecker::CheckDistLinear(
// Remove the symbol from the collected set
indexVars.erase(&itrVal.symbol->GetUltimate());
}
- collapseVal--;
- if (collapseVal == 0) {
+ collapseVal_--;
+ if (collapseVal_ == 0) {
break;
}
}
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 270642acb3e9b..a7314a702563c 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -474,7 +474,10 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
void Post(const parser::OpenMPSimpleStandaloneConstruct &) { PopContext(); }
bool Pre(const parser::OpenMPLoopConstruct &);
- void Post(const parser::OpenMPLoopConstruct &) { PopContext(); }
+ void Post(const parser::OpenMPLoopConstruct &) {
+ ordCollapseLevel++;
+ PopContext();
+ }
void Post(const parser::OmpBeginLoopDirective &) {
GetContext().withinConstruct = true;
}
@@ -1961,6 +1964,7 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
case llvm::omp::Directive::OMPD_teams_distribute_parallel_do_simd:
case llvm::omp::Directive::OMPD_teams_distribute_simd:
case llvm::omp::Directive::OMPD_teams_loop:
+ case llvm::omp::Directive::OMPD_fuse:
case llvm::omp::Directive::OMPD_tile:
case llvm::omp::Directive::OMPD_unroll:
PushContext(beginName.source, beginName.v);
@@ -1980,10 +1984,10 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
SetContextAssociatedLoopLevel(GetNumAffectedLoopsFromLoopConstruct(x));
if (beginName.v == llvm::omp::Directive::OMPD_do) {
- auto &optLoopCons = std::get<std::optional<parser::NestedConstruct>>(x.t);
- if (optLoopCons.has_value()) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ if (loopConsList.size() == 1) { //a OMPD_do cant be a loop sequence
if (const auto &doConstruct{
- std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ std::get_if<parser::DoConstruct>(&loopConsList.front())}) {
if (doConstruct->IsDoWhile()) {
return true;
}
@@ -2140,17 +2144,18 @@ void OmpAttributeVisitor::CollectNumAffectedLoopsFromInnerLoopContruct(
llvm::SmallVector<std::int64_t> &levels,
llvm::SmallVector<const parser::OmpClause *> &clauses) {
- const auto &nestedOptional =
- std::get<std::optional<parser::NestedConstruct>>(x.t);
- assert(nestedOptional.has_value() &&
+ const auto &nestedList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ assert(nestedList.size() >= 1 &&
"Expected a DoConstruct or OpenMPLoopConstruct");
- const auto *innerConstruct =
- std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
- &(nestedOptional.value()));
+ for (auto &nest : nestedList) {
+ const auto *innerConstruct =
+ std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
+ &nest);
- if (innerConstruct) {
- CollectNumAffectedLoopsFromLoopConstruct(
- innerConstruct->value(), levels, clauses);
+ if (innerConstruct) {
+ CollectNumAffectedLoopsFromLoopConstruct(
+ innerConstruct->value(), levels, clauses);
+ }
}
}
@@ -2214,76 +2219,78 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
const parser::OmpClause *clause{GetAssociatedClause()};
bool hasCollapseClause{
clause ? (clause->Id() == llvm::omp::OMPC_collapse) : false};
- const parser::OpenMPLoopConstruct *innerMostLoop = &x;
- const parser::NestedConstruct *innerMostNest = nullptr;
- while (auto &optLoopCons{
- std::get<std::optional<parser::NestedConstruct>>(innerMostLoop->t)}) {
- innerMostNest = &(optLoopCons.value());
- if (const auto *innerLoop{
- std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
- innerMostNest)}) {
- innerMostLoop = &(innerLoop->value());
- } else
- break;
- }
- if (innerMostNest) {
- if (const auto &outer{std::get_if<parser::DoConstruct>(innerMostNest)}) {
- for (const parser::DoConstruct *loop{&*outer}; loop && level > 0;
- --level) {
- if (loop->IsDoConcurrent()) {
- // DO CONCURRENT is explicitly allowed for the LOOP construct so long
- // as there isn't a COLLAPSE clause
- if (isLoopConstruct) {
- if (hasCollapseClause) {
- // hasCollapseClause implies clause != nullptr
- context_.Say(clause->source,
- "DO CONCURRENT loops cannot be used with the COLLAPSE clause."_err_en_US);
- }
- } else {
- auto &stmt =
+ const parser::OpenMPLoopConstruct *innerMostLoop = &x;
+ auto &loopConsList =
+ std::get<std::list<parser::NestedConstruct>>(innerMostLoop->t);
+ for (auto &loopCons : loopConsList) {
+ std::int64_t level_{level};
+ const parser::NestedConstruct *innerMostNest = nullptr;
+ if (const auto &innerloop{std::get_if<parser::DoConstruct>(&loopCons)}) {
+ innerMostNest = &loopCons;
+ } else if (const auto *innerLoop{
+ std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
+ &loopCons)}) {
+ PrivatizeAssociatedLoopIndexAndCheckLoopLevel(innerLoop->value());
+ }
+
+ if (innerMostNest) {
+ if (const auto &outer{std::get_if<parser::DoConstruct>(innerMostNest)}) {
+ for (const parser::DoConstruct *loop{&*outer}; loop && level_ > 0;
+ --level_) {
+ if (loop->IsDoConcurrent()) {
+ // DO CONCURRENT is explicitly allowed for the LOOP construct so long
+ // as there isn't a COLLAPSE clause
+ if (isLoopConstruct) {
+ if (hasCollapseClause) {
+ // hasCollapseClause implies clause != nullptr
+ context_.Say(clause->source,
+ "DO CONCURRENT loops cannot be used with the COLLAPSE clause."_err_en_US);
+ }
+ } else {
+ auto &stmt =
std::get<parser::Statement<parser::NonLabelDoStmt>>(loop->t);
- context_.Say(stmt.source,
- "DO CONCURRENT loops cannot form part of a loop nest."_err_en_US);
- }
- }
- // go through all the nested do-loops and resolve index variables
- const parser::Name *iv{GetLoopIndex(*loop)};
- if (iv) {
- if (auto *symbol{ResolveOmp(*iv, ivDSA, currScope())}) {
- SetSymbolDSA(*symbol, {Symbol::Flag::OmpPreDetermined, ivDSA});
- iv->symbol = symbol; // adjust the symbol within region
- AddToContextObjectWithDSA(*symbol, ivDSA);
+ context_.Say(stmt.source,
+ "DO CONCURRENT loops cannot form part of a loop nest."_err_en_US);
+ }
}
+ // go through all the nested do-loops and resolve index variables
+ const parser::Name *iv{GetLoopIndex(*loop)};
+ if (iv) {
+ if (auto *symbol{ResolveOmp(*iv, ivDSA, currScope())}) {
+ SetSymbolDSA(*symbol, {Symbol::Flag::OmpPreDetermined, ivDSA});
+ iv->symbol = symbol; // adjust the symbol within region
+ AddToContextObjectWithDSA(*symbol, ivDSA);
+ }
- const auto &block{std::get<parser::Block>(loop->t)};
- const auto it{block.begin()};
- loop = it != block.end() ? GetDoConstructIf(*it) : nullptr;
+ const auto &block{std::get<parser::Block>(loop->t)};
+ const auto it{block.begin()};
+ loop = it != block.end() ? GetDoConstructIf(*it) : nullptr;
+ }
}
- }
- CheckAssocLoopLevel(level, GetAssociatedClause());
- } else if (const auto *loop{std::get_if<
- common::Indirection<parser::OpenMPLoopConstruct>>(
- innerMostNest)}) {
- const parser::OmpDirectiveSpecification &beginSpec{
+ CheckAssocLoopLevel(level_, GetAssociatedClause());
+ } else if (const auto *loop{std::get_if<
+ common::Indirection<parser::OpenMPLoopConstruct>>(
+ innerMostNest)}) {
+ const parser::OmpDirectiveSpecification &beginSpec{
loop->value().BeginDir()};
- const parser::OmpDirectiveName &beginName{beginSpec.DirName()};
- if (beginName.v != llvm::omp::Directive::OMPD_unroll &&
- beginName.v != llvm::omp::Directive::OMPD_tile) {
- context_.Say(GetContext().directiveSource,
- "Only UNROLL or TILE constructs are allowed between an OpenMP Loop Construct and a DO construct"_err_en_US,
- parser::ToUpperCaseLetters(llvm::omp::getOpenMPDirectiveName(
- GetContext().directive, version)
- .str()));
+ const parser::OmpDirectiveName &beginName{beginSpec.DirName()};
+ if (!llvm::omp::loopTransformationSet.test(beginName.v)) {
+ context_.Say(GetContext().directiveSource,
+ "Only Loop Transformation Constructs are allowed between an OpenMP Loop Construct and a DO construct"_err_en_US,
+ parser::ToUpperCaseLetters(llvm::omp::getOpenMPDirectiveName(
+ GetContext().directive, version)
+ .str()));
+ } else {
+ PrivatizeAssociatedLoopIndexAndCheckLoopLevel(loop->value());
+ }
} else {
- PrivatizeAssociatedLoopIndexAndCheckLoopLevel(loop->value());
- }
- } else {
- context_.Say(GetContext().directiveSource,
- "A DO loop must follow the %s directive"_err_en_US,
- parser::ToUpperCaseLetters(
+ context_.Say(GetContext().directiveSource,
+ "A DO loop must follow the %s directive"_err_en_US,
+ parser::ToUpperCaseLetters(
llvm::omp::getOpenMPDirectiveName(GetContext().directive, version)
- .str()));
+ .str()));
+ }
}
}
}
diff --git a/flang/lib/Semantics/rewrite-parse-tree.cpp b/flang/lib/Semantics/rewrite-parse-tree.cpp
index 5b7dab309eda7..391099d5a866b 100644
--- a/flang/lib/Semantics/rewrite-parse-tree.cpp
+++ b/flang/lib/Semantics/rewrite-parse-tree.cpp
@@ -195,20 +195,26 @@ void RewriteMutator::OpenMPSimdOnly(
++it;
continue;
}
- auto &nest =
- std::get<std::optional<parser::NestedConstruct>>(ompLoop->t);
-
- if (auto *doConstruct =
- std::get_if<parser::DoConstruct>(&nest.value())) {
- auto &loopBody = std::get<parser::Block>(doConstruct->t);
- // We can only remove some constructs from a loop when it's _not_ a
- // OpenMP simd loop
- OpenMPSimdOnly(loopBody, /*isNonSimdLoopBody=*/true);
- auto newDoConstruct = std::move(*doConstruct);
- auto newLoop = parser::ExecutionPartConstruct{
- parser::ExecutableConstruct{std::move(newDoConstruct)}};
+ auto &nestList =
+ std::get<std::list<parser::NestedConstruct>>(ompLoop->t);
+
+ std::list<parser::ExecutionPartConstruct> doList;
+ for (auto &nest : nestList) {
+ if (auto *doConstruct = std::get_if<parser::DoConstruct>(&nest)) {
+ auto &loopBody = std::get<parser::Block>(doConstruct->t);
+ // We can only remove some constructs from a loop when it's _not_
+ // a OpenMP simd loop
+ OpenMPSimdOnly(loopBody, /*isNonSimdLoopBody=*/true);
+ auto newDoConstruct = std::move(*doConstruct);
+ auto newLoop = parser::ExecutionPartConstruct{
+ parser::ExecutableConstruct{std::move(newDoConstruct)}};
+ doList.insert(doList.end(), std::move(newLoop));
+ }
+ }
+ if (!doList.empty()) {
it = block.erase(it);
- block.insert(it, std::move(newLoop));
+ for (auto &newLoop : doList)
+ block.insert(it, std::move(newLoop));
continue;
}
} else if (auto *ompCon{std::get_if<parser::OpenMPSectionsConstruct>(
@@ -386,13 +392,15 @@ bool RewriteMutator::Pre(parser::OpenMPLoopConstruct &ompLoop) {
// If we're looking at a non-simd OpenMP loop, we need to explicitly
// call OpenMPSimdOnly on the nested loop block while indicating where
// the block comes from.
- auto &nest = std::get<std::optional<parser::NestedConstruct>>(ompLoop.t);
- if (!nest.has_value()) {
+ auto &nestList = std::get<std::list<parser::NestedConstruct>>(ompLoop.t);
+ if (nestList.empty()) {
return true;
}
- if (auto *doConstruct = std::get_if<parser::DoConstruct>(&*nest)) {
- auto &innerBlock = std::get<parser::Block>(doConstruct->t);
- OpenMPSimdOnly(innerBlock, /*isNonSimdLoopBody=*/true);
+ for (auto &nest : nestList) {
+ if (auto *doConstruct = std::get_if<parser::DoConstruct>(&nest)) {
+ auto &innerBlock = std::get<parser::Block>(doConstruct->t);
+ OpenMPSimdOnly(innerBlock, /*isNonSimdLoopBody=*/true);
+ }
}
}
return true;
diff --git a/flang/test/Parser/OpenMP/fuse.f90 b/flang/test/Parser/OpenMP/fuse.f90
new file mode 100644
index 0000000000000..98ce0e33797b5
--- /dev/null
+++ b/flang/test/Parser/OpenMP/fuse.f90
@@ -0,0 +1,28 @@
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp %s | FileCheck --ignore-case %s
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp %s | FileCheck --check-prefix="PARSE-TREE" %s
+
+subroutine openmp_fuse(x)
+
+ integer, intent(inout)::x
+
+!CHECK: !$omp fuse
+!$omp fuse
+!CHECK: do
+ do x = 1, 100
+ call F1()
+!CHECK: end do
+ end do
+!CHECK: do
+ do x = 1, 100
+ call F1()
+!CHECK: end do
+ end do
+!CHECK: !$omp end fuse
+!$omp end fuse
+
+!PARSE-TREE: OpenMPConstruct -> OpenMPLoopConstruct
+!PARSE-TREE: OmpBeginLoopDirective
+!PARSE-TREE: OmpDirectiveName -> llvm::omp::Directive = fuse
+
+END subroutine openmp_fuse
+
diff --git a/flang/test/Parser/OpenMP/loop-transformation-construct04.f90 b/flang/test/Parser/OpenMP/loop-transformation-construct04.f90
new file mode 100644
index 0000000000000..65a5f2143e6aa
--- /dev/null
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct04.f90
@@ -0,0 +1,78 @@
+! Test the Parse Tree to ensure the OpenMP Loop Transformation Construct Fuse constructs a correct sequence.
+
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-PARSE
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-UNPARSE
+
+subroutine loop_transformation_construct
+ implicit none
+ integer :: I = 10
+ integer :: j
+
+ !$omp do
+ !$omp fuse
+ do i = 1, I
+ continue
+ end do
+ do j = 1, I
+ continue
+ end do
+ !$omp end fuse
+ !$omp end do
+end subroutine
+
+!CHECK-PARSE: | ExecutionPart -> Block
+!CHECK-PARSE-NEXT: | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = do
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Name = 'j'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = do
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+
+!CHECK-UNPARSE: SUBROUTINE loop_transformation_construct
+!CHECK-UNPARSE-NEXT: IMPLICIT NONE
+!CHECK-UNPARSE-NEXT: INTEGER :: i = 10_4
+!CHECK-UNPARSE-NEXT: INTEGER j
+!CHECK-UNPARSE-NEXT: !$OMP DO
+!CHECK-UNPARSE-NEXT: !$OMP FUSE
+!CHECK-UNPARSE-NEXT: DO i=1_4,i
+!CHECK-UNPARSE-NEXT: CONTINUE
+!CHECK-UNPARSE-NEXT: END DO
+!CHECK-UNPARSE-NEXT: DO j=1_4,i
+!CHECK-UNPARSE-NEXT: CONTINUE
+!CHECK-UNPARSE-NEXT: END DO
+!CHECK-UNPARSE-NEXT: !$OMP END FUSE
+!CHECK-UNPARSE-NEXT: !$OMP END DO
diff --git a/flang/test/Parser/OpenMP/loop-transformation-construct05.f90 b/flang/test/Parser/OpenMP/loop-transformation-construct05.f90
new file mode 100644
index 0000000000000..6b6d64d53ca23
--- /dev/null
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct05.f90
@@ -0,0 +1,86 @@
+! Test the Parse Tree to ensure the OpenMP Loop Transformation Construct Fuse constructs a correct sequence
+! and can correctly combine with loop nests
+
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-PARSE
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-UNPARSE
+
+subroutine loop_transformation_construct
+ implicit none
+ integer :: I = 10
+ integer :: j
+
+ !$omp do
+ !$omp fuse
+ do i = 1, I
+ continue
+ end do
+ !$omp tile
+ do j = 1, I
+ continue
+ end do
+ !$omp end fuse
+ !$omp end do
+end subroutine
+
+!CHECK-PARSE: | ExecutionPart -> Block
+!CHECK-PARSE-NEXT: | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = do
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | | OmpDirectiveName -> llvm::omp::Directive = tile
+!CHECK-PARSE-NEXT: | | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | | Scalar -> Name = 'j'
+!CHECK-PARSE-NEXT: | | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = do
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+
+!CHECK-UNPARSE: SUBROUTINE loop_transformation_construct
+!CHECK-UNPARSE-NEXT: IMPLICIT NONE
+!CHECK-UNPARSE-NEXT: INTEGER :: i = 10_4
+!CHECK-UNPARSE-NEXT: INTEGER j
+!CHECK-UNPARSE-NEXT: !$OMP DO
+!CHECK-UNPARSE-NEXT: !$OMP FUSE
+!CHECK-UNPARSE-NEXT: DO i=1_4,i
+!CHECK-UNPARSE-NEXT: CONTINUE
+!CHECK-UNPARSE-NEXT: END DO
+!CHECK-UNPARSE-NEXT: !$OMP TILE
+!CHECK-UNPARSE-NEXT: DO j=1_4,i
+!CHECK-UNPARSE-NEXT: CONTINUE
+!CHECK-UNPARSE-NEXT: END DO
+!CHECK-UNPARSE-NEXT: !$OMP END FUSE
+!CHECK-UNPARSE-NEXT: !$OMP END DO
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
index f718efc32aabf..927831a06d5fa 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct01.f90
@@ -62,7 +62,7 @@ subroutine loop_transformation_construct4
integer :: v(i)
!$omp do
- !ERROR: If a loop construct has been fully unrolled, it cannot then be tiled
+ !ERROR: If a loop construct has been fully unrolled, it cannot then be further transformed
!$omp tile
!$omp unroll full
do x = 1, i
@@ -77,7 +77,7 @@ subroutine loop_transformation_construct5
integer :: v(i)
!$omp do
- !ERROR: If a loop construct has been fully unrolled, it cannot then be tiled
+ !ERROR: If a loop construct has been fully unrolled, it cannot then be further transformed
!$omp tile
!$omp unroll
do x = 1, i
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
new file mode 100644
index 0000000000000..74fbab89a8cf4
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -0,0 +1,95 @@
+! Testing the Semantics of loop sequences combined with
+! nested Loop Transformation Constructs
+
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+subroutine loop_transformation_construct1
+ implicit none
+
+ !$omp do
+ !ERROR: The FUSE construct requires the END FUSE directive
+ !$omp fuse
+end subroutine
+
+subroutine loop_transformation_construct2
+ implicit none
+
+ !$omp do
+ !ERROR: A DO loop must follow the FUSE directive
+ !$omp fuse
+ !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
+ !$omp end fuse
+end subroutine
+
+subroutine loop_transformation_construct3
+ implicit none
+ integer :: i = 5
+ integer :: y
+ integer :: v(i)
+
+ !$omp do
+ !$omp fuse
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ !$omp end fuse
+ !$omp end do
+ !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
+ !$omp end fuse
+end subroutine
+
+subroutine loop_transformation_construct4
+ implicit none
+ integer :: i = 5
+ integer :: y
+ integer :: v(i)
+
+ !$omp do
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ !ERROR: A DO loop must follow the FUSE directive
+ !$omp fuse
+ !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
+ !$omp end fuse
+end subroutine
+
+subroutine loop_transformation_construct5
+ implicit none
+ integer :: i = 5
+ integer :: y
+ integer :: v(i)
+
+ !$omp do
+ !ERROR: If a loop construct has been fully unrolled, it cannot then be further transformed
+ !$omp fuse
+ !$omp unroll full
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ !$omp end fuse
+end subroutine
+
+subroutine loop_transformation_construct6
+ implicit none
+ integer :: i = 5
+ integer :: y
+ integer :: v(i)
+
+ !$omp do
+ !$omp fuse
+ !$omp unroll partial(2)
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ !$omp end fuse
+end subroutine
>From de8f50c3b50e02793bc06820d1bbf1fa4ae84520 Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Mon, 29 Sep 2025 14:35:44 +0000
Subject: [PATCH 2/4] looprange clause semantics
---
flang/lib/Lower/OpenMP/Clauses.cpp | 2 +-
flang/lib/Semantics/check-omp-loop.cpp | 29 ++++++++++++++
flang/lib/Semantics/check-omp-structure.cpp | 8 ++--
flang/lib/Semantics/check-omp-structure.h | 1 +
flang/test/Parser/OpenMP/fuse-looprange.f90 | 38 +++++++++++++++++++
.../OpenMP/loop-transformation-clauses01.f90 | 31 +++++++++++++++
6 files changed, 105 insertions(+), 4 deletions(-)
create mode 100644 flang/test/Parser/OpenMP/fuse-looprange.f90
create mode 100644 flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index fac37a372caaf..ce516ce10815b 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1038,7 +1038,7 @@ Link make(const parser::OmpClause::Link &inp,
LoopRange make(const parser::OmpClause::Looprange &inp,
semantics::SemanticsContext &semaCtx) {
- llvm_unreachable("Unimplemented: looprange");
+ TODO_NOLOC("looprange clause");
}
Map make(const parser::OmpClause::Map &inp,
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 7b95f4b6c31bf..70c41b5442828 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -305,6 +305,9 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
beginName.v == llvm::omp::Directive::OMPD_distribute_simd) {
CheckDistLinear(x);
}
+ if (beginName.v == llvm::omp::Directive::OMPD_fuse) {
+ CheckLooprangeBounds(x);
+ }
}
const parser::Name OmpStructureChecker::GetLoopIndex(
@@ -453,6 +456,32 @@ void OmpStructureChecker::CheckDistLinear(
}
}
+void OmpStructureChecker::CheckLooprangeBounds(
+ const parser::OpenMPLoopConstruct &x) {
+ const parser::OmpClauseList &clauseList = x.BeginDir().Clauses();
+ if (!clauseList.v.empty()) {
+ for (auto &clause : clauseList.v) {
+ if (const auto *lrClause{
+ std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
+ if (const auto first{GetIntValue(std::get<0>((lrClause->v).t))}) {
+ if (const auto count{GetIntValue(std::get<1>((lrClause->v).t))}) {
+ auto &loopConsList =
+ std::get<std::list<parser::NestedConstruct>>(x.t);
+ if (loopConsList.size() < (unsigned)(*first + *count - 1)) {
+ context_.Say(clause.source,
+ "The loop range indicated in the %s clause"
+ " must not be out of the bounds of the Loop Sequence"
+ " following the construct."_err_en_US,
+ parser::ToUpperCaseLetters(clause.source.ToString()));
+ }
+ }
+ }
+ return;
+ }
+ }
+ }
+}
+
void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) {
const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()};
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index db030bbe1f023..7980ec886604d 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -3107,9 +3107,11 @@ CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Safelen, OMPC_safelen)
CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Simdlen, OMPC_simdlen)
void OmpStructureChecker::Enter(const parser::OmpClause::Looprange &x) {
- context_.Say(GetContext().clauseSource,
- "LOOPRANGE clause is not implemented yet"_err_en_US,
- ContextDirectiveAsFortran());
+ CheckAllowedClause(llvm::omp::Clause::OMPC_looprange);
+ auto &first = std::get<0>(x.v.t);
+ auto &count = std::get<1>(x.v.t);
+ RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, count);
+ RequiresConstantPositiveParameter(llvm::omp::Clause::OMPC_looprange, first);
}
// Restrictions specific to each clause are implemented apart from the
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 193784555a887..3705b95c15b48 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -297,6 +297,7 @@ class OmpStructureChecker
void CheckAtomicWrite(const parser::OpenMPAtomicConstruct &x);
void CheckAtomicUpdate(const parser::OpenMPAtomicConstruct &x);
+ void CheckLooprangeBounds(const parser::OpenMPLoopConstruct &x);
void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
void CheckSIMDNest(const parser::OpenMPConstruct &x);
void CheckTargetNest(const parser::OpenMPConstruct &x);
diff --git a/flang/test/Parser/OpenMP/fuse-looprange.f90 b/flang/test/Parser/OpenMP/fuse-looprange.f90
new file mode 100644
index 0000000000000..75ec15fddd65f
--- /dev/null
+++ b/flang/test/Parser/OpenMP/fuse-looprange.f90
@@ -0,0 +1,38 @@
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=60 %s | FileCheck --ignore-case %s
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=60 %s | FileCheck --check-prefix="PARSE-TREE" %s
+
+subroutine openmp_fuse(x)
+
+ integer, intent(inout)::x
+
+!CHECK: !$omp fuse looprange
+!$omp fuse looprange(1,2)
+!CHECK: do
+ do x = 1, 100
+ call F1()
+!CHECK: end do
+ end do
+!CHECK: do
+ do x = 1, 100
+ call F1()
+!CHECK: end do
+ end do
+!CHECK: do
+ do x = 1, 100
+ call F1()
+!CHECK: end do
+ end do
+!CHECK: !$omp end fuse
+!$omp end fuse
+
+!PARSE-TREE: OpenMPConstruct -> OpenMPLoopConstruct
+!PARSE-TREE: OmpBeginLoopDirective
+!PARSE-TREE: OmpDirectiveName -> llvm::omp::Directive = fuse
+!PARSE-TREE: OmpClauseList -> OmpClause -> Looprange -> OmpLoopRangeClause
+!PARSE-TREE: Scalar -> Integer -> Constant -> Expr = '1_4'
+!PARSE-TREE: LiteralConstant -> IntLiteralConstant = '1'
+!PARSE-TREE: Scalar -> Integer -> Constant -> Expr = '2_4'
+!PARSE-TREE: LiteralConstant -> IntLiteralConstant = '2'
+
+END subroutine openmp_fuse
+
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
new file mode 100644
index 0000000000000..07d4bb9886df5
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
@@ -0,0 +1,31 @@
+! Testing the Semantics of clauses on loop transformation directives
+
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+
+subroutine loop_transformation_construct1
+ implicit none
+ integer, parameter:: i = 5
+ integer :: x
+ integer :: v(i)
+
+ !$omp fuse looprange(1,2)
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ !$omp end fuse
+
+ !ERROR: The loop range indicated in the LOOPRANGE(5,2) clause must not be out of the bounds of the Loop Sequence following the construct.
+ !$omp fuse looprange(5,2)
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ !$omp end fuse
+end subroutine
+
>From ead87b90de9cbf9aba1f441356725c003ca1dbb5 Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Tue, 7 Oct 2025 12:59:42 +0000
Subject: [PATCH 3/4] Address reviews, add more tests and fix an issue
---
flang/lib/Lower/OpenMP/Clauses.cpp | 2 +-
flang/lib/Lower/OpenMP/OpenMP.cpp | 7 +-
flang/lib/Semantics/canonicalize-omp.cpp | 66 +++++++------
flang/lib/Semantics/check-omp-loop.cpp | 44 ++++++++-
flang/lib/Semantics/check-omp-structure.h | 1 +
flang/lib/Semantics/resolve-directives.cpp | 20 ++--
flang/test/Parser/OpenMP/fail-looprange.f90 | 11 +++
.../Parser/OpenMP/{fuse.f90 => fuse01.f90} | 0
flang/test/Parser/OpenMP/fuse02.f90 | 95 +++++++++++++++++++
.../OpenMP/loop-transformation-clauses01.f90 | 24 ++++-
.../loop-transformation-construct02.f90 | 4 +-
.../loop-transformation-construct03.f90 | 39 ++++++++
.../loop-transformation-construct04.f90 | 47 +++++++++
13 files changed, 307 insertions(+), 53 deletions(-)
create mode 100644 flang/test/Parser/OpenMP/fail-looprange.f90
rename flang/test/Parser/OpenMP/{fuse.f90 => fuse01.f90} (100%)
create mode 100644 flang/test/Parser/OpenMP/fuse02.f90
create mode 100644 flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
create mode 100644 flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
diff --git a/flang/lib/Lower/OpenMP/Clauses.cpp b/flang/lib/Lower/OpenMP/Clauses.cpp
index ce516ce10815b..fac37a372caaf 100644
--- a/flang/lib/Lower/OpenMP/Clauses.cpp
+++ b/flang/lib/Lower/OpenMP/Clauses.cpp
@@ -1038,7 +1038,7 @@ Link make(const parser::OmpClause::Link &inp,
LoopRange make(const parser::OmpClause::Looprange &inp,
semantics::SemanticsContext &semaCtx) {
- TODO_NOLOC("looprange clause");
+ llvm_unreachable("Unimplemented: looprange");
}
Map make(const parser::OmpClause::Map &inp,
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index c3598f6846822..fce5053da9ddc 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3360,7 +3360,12 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
newOp = genTeamsOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue,
item);
break;
- case llvm::omp::Directive::OMPD_fuse:
+ case llvm::omp::Directive::OMPD_fuse: {
+ unsigned version = semaCtx.langOptions().OpenMPVersion;
+ TODO(loc, "Unhandled loop directive (" +
+ llvm::omp::getOpenMPDirectiveName(dir, version) + ")");
+ break;
+ }
case llvm::omp::Directive::OMPD_tile: {
unsigned version = semaCtx.langOptions().OpenMPVersion;
if (!semaCtx.langOptions().OpenMPSimd)
diff --git a/flang/lib/Semantics/canonicalize-omp.cpp b/flang/lib/Semantics/canonicalize-omp.cpp
index 74b3fa978a53c..fa2408efdb715 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -172,81 +172,85 @@ class CanonicalizationOfOmp {
if (auto *endDir{
GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
auto &endDirName = endDir->DirName();
- if (endDirName.v == llvm::omp::Directive::OMPD_fuse)
- endFuseNeeded = false;
- std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
- std::move(*endDir);
- nextIt = block.erase(nextIt);
+ if (endDirName.v != llvm::omp::Directive::OMPD_fuse) {
+ std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
+ std::move(*endDir);
+ nextIt = block.erase(nextIt);
+ }
}
}
} else {
messages_.Say(beginName.source,
"DO loop after the %s directive must have loop control"_err_en_US,
parser::ToUpperCaseLetters(beginName.source.ToString()));
- endFuseNeeded = false;
}
} else if (auto *ompLoopCons{
GetOmpIf<parser::OpenMPLoopConstruct>(*nextIt)}) {
- // We should allow UNROLL and TILE constructs to be inserted between an
- // OpenMP Loop Construct and the DO loop itself
+ // We should allow loop transformation constructs to be inserted between
+ // an OpenMP Loop Construct and the DO loop itself
auto &nestedBeginDirective = ompLoopCons->BeginDir();
auto &nestedBeginName = nestedBeginDirective.DirName();
if (llvm::omp::loopTransformationSet.test(nestedBeginName.v)) {
if (nestedBeginName.v == llvm::omp::Directive::OMPD_unroll &&
llvm::omp::loopTransformationSet.test(beginName.v)) {
- // if a loop has been unrolled, the user can not then tile that loop
- // as it has been unrolled
+ // if a loop has been unrolled, the user can not then transform that
+ // loop as it has been unrolled
const parser::OmpClauseList &unrollClauseList{
nestedBeginDirective.Clauses()};
if (unrollClauseList.v.empty()) {
// if the clause list is empty for an unroll construct, we assume
// the loop is being fully unrolled
transformUnrollError(beginName, messages_);
- endFuseNeeded = false;
} else {
// parse the clauses for the unroll directive to find the full
// clause
for (auto &clause : unrollClauseList.v) {
if (clause.Id() == llvm::omp::OMPC_full) {
transformUnrollError(beginName, messages_);
- endFuseNeeded = false;
}
}
}
}
- // iterate through the remaining block items to find the end directive
- // for the unroll/tile directive.
- parser::Block::iterator endIt;
- endIt = nextIt;
- while (endIt != block.end()) {
+ RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
+ auto &loopConsList =
+ std::get<std::list<parser::NestedConstruct>>(x.t);
+ loopConsList.push_back(parser::NestedConstruct{
+ common::Indirection{std::move(*ompLoopCons)}});
+ nextIt = block.erase(nextIt);
+ // check the following block item to find the end directive
+ // for the loop transform directive.
+ if (nextIt != block.end()) {
if (auto *endDir{
- GetConstructIf<parser::OmpEndLoopDirective>(*endIt)}) {
+ GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
auto &endDirName = endDir->DirName();
- if (endDirName.v == beginName.v) {
- if (endDirName.v == llvm::omp::Directive::OMPD_fuse)
- endFuseNeeded = false;
+ if (endDirName.v == beginName.v &&
+ endDirName.v != llvm::omp::Directive::OMPD_fuse) {
std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
std::move(*endDir);
- endIt = block.erase(endIt);
- continue;
+ nextIt = block.erase(nextIt);
}
}
- ++endIt;
}
- RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
- auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
- loopConsList.push_back(parser::NestedConstruct{
- common::Indirection{std::move(*ompLoopCons)}});
- nextIt = block.erase(nextIt);
} else {
messages_.Say(nestedBeginName.source,
"Only Loop Transformation Constructs or Loop Nests can be nested within Loop Constructs"_err_en_US,
parser::ToUpperCaseLetters(nestedBeginName.source.ToString()));
- endFuseNeeded = false;
}
} else {
missingDoConstruct(beginName, messages_);
- endFuseNeeded = false;
+ }
+
+ if (endFuseNeeded && nextIt != block.end()) {
+ if (auto *endDir{
+ GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
+ auto &endDirName = endDir->DirName();
+ if (endDirName.v == llvm::omp::Directive::OMPD_fuse) {
+ endFuseNeeded = false;
+ std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
+ std::move(*endDir);
+ nextIt = block.erase(nextIt);
+ }
+ }
}
if (endFuseNeeded)
continue;
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 70c41b5442828..a3c5b98692dba 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -307,7 +307,8 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
}
if (beginName.v == llvm::omp::Directive::OMPD_fuse) {
CheckLooprangeBounds(x);
- }
+ } else
+ CheckNestedFuse(x);
}
const parser::Name OmpStructureChecker::GetLoopIndex(
@@ -422,7 +423,7 @@ void OmpStructureChecker::CheckDistLinear(
// clauses.
auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
for (auto &loopCons : loopConsList) {
- std::int64_t collapseVal_ = collapseVal;
+ std::int64_t curCollapseVal{collapseVal};
if (const auto &loopConstruct{
std::get_if<parser::DoConstruct>(&loopCons)}) {
for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
@@ -432,8 +433,8 @@ void OmpStructureChecker::CheckDistLinear(
// Remove the symbol from the collected set
indexVars.erase(&itrVal.symbol->GetUltimate());
}
- collapseVal_--;
- if (collapseVal_ == 0) {
+ curCollapseVal--;
+ if (curCollapseVal == 0) {
break;
}
}
@@ -467,7 +468,8 @@ void OmpStructureChecker::CheckLooprangeBounds(
if (const auto count{GetIntValue(std::get<1>((lrClause->v).t))}) {
auto &loopConsList =
std::get<std::list<parser::NestedConstruct>>(x.t);
- if (loopConsList.size() < (unsigned)(*first + *count - 1)) {
+ if (*first > 0 && *count > 0 &&
+ loopConsList.size() < (unsigned)(*first + *count - 1)) {
context_.Say(clause.source,
"The loop range indicated in the %s clause"
" must not be out of the bounds of the Loop Sequence"
@@ -482,6 +484,38 @@ void OmpStructureChecker::CheckLooprangeBounds(
}
}
+void OmpStructureChecker::CheckNestedFuse(
+ const parser::OpenMPLoopConstruct &x) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
+ for (auto &loopCons : loopConsList) {
+ if (const auto &ompConstruct{
+ std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
+ &loopCons)}) {
+ const parser::OmpClauseList &clauseList =
+ ompConstruct->value().BeginDir().Clauses();
+ if (!clauseList.v.empty()) {
+ for (auto &clause : clauseList.v) {
+ if (const auto *lrClause{
+ std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
+ if (const auto count{GetIntValue(std::get<1>((lrClause->v).t))}) {
+ auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(
+ ompConstruct->value().t);
+ if (loopConsList.size() > (unsigned)(*count)) {
+ context_.Say(x.BeginDir().DirName().source,
+ "The loop sequence following the %s construct"
+ " must be fully fused first."_err_en_US,
+ parser::ToUpperCaseLetters(
+ x.BeginDir().DirName().source.ToString()));
+ }
+ }
+ return;
+ }
+ }
+ }
+ }
+ }
+}
+
void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) {
const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()};
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 3705b95c15b48..2aea16f0aa2ed 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -298,6 +298,7 @@ class OmpStructureChecker
void CheckAtomicUpdate(const parser::OpenMPAtomicConstruct &x);
void CheckLooprangeBounds(const parser::OpenMPLoopConstruct &x);
+ void CheckNestedFuse(const parser::OpenMPLoopConstruct &x);
void CheckDistLinear(const parser::OpenMPLoopConstruct &x);
void CheckSIMDNest(const parser::OpenMPConstruct &x);
void CheckTargetNest(const parser::OpenMPConstruct &x);
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index a7314a702563c..0e05d6185ef21 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -1985,12 +1985,12 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
if (beginName.v == llvm::omp::Directive::OMPD_do) {
auto &loopConsList = std::get<std::list<parser::NestedConstruct>>(x.t);
- if (loopConsList.size() == 1) { //a OMPD_do cant be a loop sequence
- if (const auto &doConstruct{
- std::get_if<parser::DoConstruct>(&loopConsList.front())}) {
- if (doConstruct->IsDoWhile()) {
- return true;
- }
+ assert(loopConsList.size() == 1 &&
+ "Expected a single DoConstruct or OpenMPLoopConstruct");
+ if (const auto &doConstruct{
+ std::get_if<parser::DoConstruct>(&loopConsList.front())}) {
+ if (doConstruct->IsDoWhile()) {
+ return true;
}
}
}
@@ -2224,7 +2224,7 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
auto &loopConsList =
std::get<std::list<parser::NestedConstruct>>(innerMostLoop->t);
for (auto &loopCons : loopConsList) {
- std::int64_t level_{level};
+ std::int64_t curLevel{level};
const parser::NestedConstruct *innerMostNest = nullptr;
if (const auto &innerloop{std::get_if<parser::DoConstruct>(&loopCons)}) {
innerMostNest = &loopCons;
@@ -2236,8 +2236,8 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
if (innerMostNest) {
if (const auto &outer{std::get_if<parser::DoConstruct>(innerMostNest)}) {
- for (const parser::DoConstruct *loop{&*outer}; loop && level_ > 0;
- --level_) {
+ for (const parser::DoConstruct *loop{&*outer}; loop && curLevel > 0;
+ --curLevel) {
if (loop->IsDoConcurrent()) {
// DO CONCURRENT is explicitly allowed for the LOOP construct so long
// as there isn't a COLLAPSE clause
@@ -2268,7 +2268,7 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
loop = it != block.end() ? GetDoConstructIf(*it) : nullptr;
}
}
- CheckAssocLoopLevel(level_, GetAssociatedClause());
+ CheckAssocLoopLevel(curLevel, GetAssociatedClause());
} else if (const auto *loop{std::get_if<
common::Indirection<parser::OpenMPLoopConstruct>>(
innerMostNest)}) {
diff --git a/flang/test/Parser/OpenMP/fail-looprange.f90 b/flang/test/Parser/OpenMP/fail-looprange.f90
new file mode 100644
index 0000000000000..ebe3480b44f12
--- /dev/null
+++ b/flang/test/Parser/OpenMP/fail-looprange.f90
@@ -0,0 +1,11 @@
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp %s 2>&1 | FileCheck %s
+
+! CHECK: error: expected end of line
+!$omp fuse looprange
+
+! CHECK: error: expected end of line
+!$omp fuse looprange(1)
+
+! CHECK: error: expected end of line
+!$omp fuse looprange(1,2,3)
+end
diff --git a/flang/test/Parser/OpenMP/fuse.f90 b/flang/test/Parser/OpenMP/fuse01.f90
similarity index 100%
rename from flang/test/Parser/OpenMP/fuse.f90
rename to flang/test/Parser/OpenMP/fuse01.f90
diff --git a/flang/test/Parser/OpenMP/fuse02.f90 b/flang/test/Parser/OpenMP/fuse02.f90
new file mode 100644
index 0000000000000..7a46f4fe0f5cc
--- /dev/null
+++ b/flang/test/Parser/OpenMP/fuse02.f90
@@ -0,0 +1,95 @@
+! Test the Parse Tree to ensure the OpenMP Loop Transformation Construct Fuse can be constructed on another Fuse
+
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-PARSE
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=51 %s | FileCheck %s --check-prefix=CHECK-UNPARSE
+
+subroutine fuse_on_fuse
+ implicit none
+ integer :: I = 10
+ integer :: j
+
+ !$omp fuse
+ !$omp fuse
+ do i = 1, I
+ continue
+ end do
+ do j = 1, I
+ continue
+ end do
+ !$omp end fuse
+ do j = 1, I
+ continue
+ end do
+ !$omp end fuse
+end subroutine
+
+!CHECK-PARSE: | ExecutionPart -> Block
+!CHECK-PARSE-NEXT: | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Name = 'j'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | DoConstruct
+!CHECK-PARSE-NEXT: | | | | NonLabelDoStmt
+!CHECK-PARSE-NEXT: | | | | | LoopControl -> LoopBounds
+!CHECK-PARSE-NEXT: | | | | | | Scalar -> Name = 'j'
+!CHECK-PARSE-NEXT: | | | | | | Scalar -> Expr = '1_4'
+!CHECK-PARSE-NEXT: | | | | | | | LiteralConstant -> IntLiteralConstant = '1'
+!CHECK-PARSE-NEXT: | | | | | | Scalar -> Expr = 'i'
+!CHECK-PARSE-NEXT: | | | | | | | Designator -> DataRef -> Name = 'i'
+!CHECK-PARSE-NEXT: | | | | Block
+!CHECK-PARSE-NEXT: | | | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> ContinueStmt
+!CHECK-PARSE-NEXT: | | | | EndDoStmt ->
+!CHECK-PARSE-NEXT: | | | OmpEndLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpDirectiveName -> llvm::omp::Directive = fuse
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | | Flags = None
+
+!CHECK-UNPARSE: SUBROUTINE fuse_on_fuse
+!CHECK-UNPARSE-NEXT: IMPLICIT NONE
+!CHECK-UNPARSE-NEXT: INTEGER :: i = 10_4
+!CHECK-UNPARSE-NEXT: INTEGER j
+!CHECK-UNPARSE-NEXT: !$OMP FUSE
+!CHECK-UNPARSE-NEXT: !$OMP FUSE
+!CHECK-UNPARSE-NEXT: DO i=1_4,i
+!CHECK-UNPARSE-NEXT: CONTINUE
+!CHECK-UNPARSE-NEXT: END DO
+!CHECK-UNPARSE-NEXT: DO j=1_4,i
+!CHECK-UNPARSE-NEXT: CONTINUE
+!CHECK-UNPARSE-NEXT: END DO
+!CHECK-UNPARSE-NEXT: !$OMP END FUSE
+!CHECK-UNPARSE-NEXT: DO j=1_4,i
+!CHECK-UNPARSE-NEXT: CONTINUE
+!CHECK-UNPARSE-NEXT: END DO
+!CHECK-UNPARSE-NEXT: !$OMP END FUSE
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
index 07d4bb9886df5..4e1b9c7f44ca5 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
@@ -9,7 +9,8 @@ subroutine loop_transformation_construct1
integer :: x
integer :: v(i)
- !$omp fuse looprange(1,2)
+ !ERROR: At most one LOOPRANGE clause can appear on the FUSE directive
+ !$omp fuse looprange(1,2) looprange(1,2)
do x = 1, i
v(x) = x * 2
end do
@@ -27,5 +28,24 @@ subroutine loop_transformation_construct1
v(x) = x * 2
end do
!$omp end fuse
-end subroutine
+ !ERROR: The parameter of the LOOPRANGE clause must be a constant positive integer expression
+ !$omp fuse looprange(0,1)
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ !$omp end fuse
+
+ !ERROR: The parameter of the LOOPRANGE clause must be a constant positive integer expression
+ !$omp fuse looprange(1,-1)
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ !$omp end fuse
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
index 74fbab89a8cf4..d82fc3668198d 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -17,7 +17,6 @@ subroutine loop_transformation_construct2
!$omp do
!ERROR: A DO loop must follow the FUSE directive
!$omp fuse
- !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
!$omp end fuse
end subroutine
@@ -53,7 +52,6 @@ subroutine loop_transformation_construct4
end do
!ERROR: A DO loop must follow the FUSE directive
!$omp fuse
- !ERROR: The END FUSE directive must follow the DO loop associated with the loop construct
!$omp end fuse
end subroutine
@@ -83,7 +81,7 @@ subroutine loop_transformation_construct6
integer :: v(i)
!$omp do
- !$omp fuse
+ !$omp fuse looprange(1,1)
!$omp unroll partial(2)
do x = 1, i
v(x) = x(x) * 2
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
new file mode 100644
index 0000000000000..58e977241fd86
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct03.f90
@@ -0,0 +1,39 @@
+! Testing the Semantic failure of forming loop sequences under regular OpenMP directives
+
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+subroutine loop_transformation_construct1
+ implicit none
+ integer :: i = 5
+ integer :: y
+ integer :: v(i)
+
+ ! Only 1 do loop is associated with the OMP DO directive so the END DO directive is unmatched
+ !$omp do
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ !ERROR: The END DO directive must follow the DO loop associated with the loop construct
+ !$omp end do
+end subroutine
+
+subroutine loop_transformation_construct2
+ implicit none
+ integer :: i = 5
+ integer :: y
+ integer :: v(i)
+
+ ! Only 1 do loop is associated with the OMP TILE directive so the END TILE directive is unmatched
+ !$omp tile
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ do x = 1, i
+ v(x) = x(x) * 2
+ end do
+ !ERROR: The END TILE directive must follow the DO loop associated with the loop construct
+ !$omp end tile
+end subroutine
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90 b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
new file mode 100644
index 0000000000000..589241ecfb6f8
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct04.f90
@@ -0,0 +1,47 @@
+! Testing the Semantic failure of forming loop sequences under regular OpenMP directives
+
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+subroutine loop_transformation_construct3
+ implicit none
+ integer, parameter :: i = 5
+ integer :: x
+ integer :: v(i)
+
+ !ERROR: The loop sequence following the DO construct must be fully fused first.
+ !$omp do
+ !$omp fuse looprange(1,2)
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ !$omp end fuse
+ !$omp end do
+end subroutine
+
+subroutine loop_transformation_construct4
+ implicit none
+ integer, parameter :: i = 5
+ integer :: x
+ integer :: v(i)
+
+ !ERROR: The loop sequence following the TILE construct must be fully fused first.
+ !$omp tile
+ !$omp fuse looprange(1,2)
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ !$omp end fuse
+ !$omp end tile
+end subroutine
>From bc6d6519a7a4df698f69c06f6e1ce4ca1e00b2b3 Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Mon, 20 Oct 2025 14:51:26 +0000
Subject: [PATCH 4/4] looprange constant parameter test
---
.../OpenMP/loop-transformation-clauses01.f90 | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
index 4e1b9c7f44ca5..9ca0e8cfc9af1 100644
--- a/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
+++ b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
@@ -7,6 +7,7 @@ subroutine loop_transformation_construct1
implicit none
integer, parameter:: i = 5
integer :: x
+ integer :: a
integer :: v(i)
!ERROR: At most one LOOPRANGE clause can appear on the FUSE directive
@@ -48,4 +49,18 @@ subroutine loop_transformation_construct1
v(x) = x * 2
end do
!$omp end fuse
+
+ !ERROR: Must be a constant value
+ !$omp fuse looprange(a,2)
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ !$omp end fuse
+
+ !ERROR: Must be a constant value
+ !$omp fuse looprange(1,a)
+ do x = 1, i
+ v(x) = x * 2
+ end do
+ !$omp end fuse
end subroutine
More information about the flang-commits
mailing list