[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
Thu Nov 20 08:19:08 PST 2025
https://github.com/NouTimbaler updated https://github.com/llvm/llvm-project/pull/161213
>From 42a5057c156f51b5476a5f29f44f42169bb50913 Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Thu, 20 Nov 2025 14:32:46 +0000
Subject: [PATCH] Semantics fuse rebase
---
flang/include/flang/Parser/openmp-utils.h | 3 +
.../flang/Semantics/openmp-directive-sets.h | 7 +
flang/lib/Lower/OpenMP/OpenMP.cpp | 41 ++--
flang/lib/Parser/openmp-parsers.cpp | 1 +
flang/lib/Parser/openmp-utils.cpp | 17 ++
flang/lib/Semantics/canonicalize-omp.cpp | 117 ++++++----
flang/lib/Semantics/check-omp-loop.cpp | 139 +++++++++---
flang/lib/Semantics/check-omp-structure.cpp | 8 +-
flang/lib/Semantics/check-omp-structure.h | 2 +
flang/lib/Semantics/resolve-directives.cpp | 210 +++++++++---------
flang/lib/Semantics/rewrite-parse-tree.cpp | 39 ++--
flang/test/Parser/OpenMP/fail-looprange.f90 | 11 +
flang/test/Parser/OpenMP/fuse-looprange.f90 | 38 ++++
flang/test/Parser/OpenMP/fuse01.f90 | 28 +++
flang/test/Parser/OpenMP/fuse02.f90 | 97 ++++++++
.../loop-transformation-construct04.f90 | 80 +++++++
.../loop-transformation-construct05.f90 | 90 ++++++++
.../OpenMP/loop-transformation-clauses01.f90 | 66 ++++++
.../loop-transformation-construct01.f90 | 4 +-
.../loop-transformation-construct02.f90 | 93 ++++++++
.../loop-transformation-construct03.f90 | 39 ++++
.../loop-transformation-construct04.f90 | 47 ++++
flang/test/Semantics/OpenMP/tile02.f90 | 2 +-
23 files changed, 963 insertions(+), 216 deletions(-)
create mode 100644 flang/test/Parser/OpenMP/fail-looprange.f90
create mode 100644 flang/test/Parser/OpenMP/fuse-looprange.f90
create mode 100644 flang/test/Parser/OpenMP/fuse01.f90
create mode 100644 flang/test/Parser/OpenMP/fuse02.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-clauses01.f90
create mode 100644 flang/test/Semantics/OpenMP/loop-transformation-construct02.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/include/flang/Parser/openmp-utils.h b/flang/include/flang/Parser/openmp-utils.h
index 36556f8dd7f4a..7396e57144b90 100644
--- a/flang/include/flang/Parser/openmp-utils.h
+++ b/flang/include/flang/Parser/openmp-utils.h
@@ -123,6 +123,9 @@ template <typename T> OmpDirectiveName GetOmpDirectiveName(const T &x) {
const OpenMPDeclarativeConstruct *GetOmp(const DeclarationConstruct &x);
const OpenMPConstruct *GetOmp(const ExecutionPartConstruct &x);
+const OpenMPLoopConstruct *GetOmpLoop(const ExecutionPartConstruct &x);
+const DoConstruct *GetDoConstruct(const ExecutionPartConstruct &x);
+
const OmpObjectList *GetOmpObjectList(const OmpClause &clause);
template <typename 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 c6487349c4056..b6efa8592c678 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -3507,6 +3507,13 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
case llvm::omp::Directive::OMPD_tile:
genTileOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item);
break;
+ case llvm::omp::Directive::OMPD_fuse: {
+ unsigned version = semaCtx.langOptions().OpenMPVersion;
+ if (!semaCtx.langOptions().OpenMPSimd)
+ TODO(loc, "Unhandled loop directive (" +
+ llvm::omp::getOpenMPDirectiveName(dir, version) + ")");
+ break;
+ }
case llvm::omp::Directive::OMPD_unroll:
genUnrollOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item);
break;
@@ -3962,22 +3969,24 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
mlir::Location currentLocation = converter.genLocation(beginSpec.source);
- if (const parser::OpenMPLoopConstruct *ompNestedLoopCons =
- loopConstruct.GetNestedConstruct()) {
- llvm::omp::Directive nestedDirective =
- parser::omp::GetOmpDirectiveName(*ompNestedLoopCons).v;
- switch (nestedDirective) {
- case llvm::omp::Directive::OMPD_tile:
- // Skip OMPD_tile since the tile sizes will be retrieved when
- // generating the omp.loop_nest op.
- break;
- default: {
- unsigned version = semaCtx.langOptions().OpenMPVersion;
- TODO(currentLocation,
- "Applying a loop-associated on the loop generated by the " +
- llvm::omp::getOpenMPDirectiveName(nestedDirective, version) +
- " construct");
- }
+ for (auto &construct : std::get<parser::Block>(loopConstruct.t)) {
+ if (const parser::OpenMPLoopConstruct *ompNestedLoopCons =
+ parser::omp::GetOmpLoop(construct)) {
+ llvm::omp::Directive nestedDirective =
+ parser::omp::GetOmpDirectiveName(*ompNestedLoopCons).v;
+ switch (nestedDirective) {
+ case llvm::omp::Directive::OMPD_tile:
+ // Skip OMPD_tile since the tile sizes will be retrieved when
+ // generating the omp.loop_nest op.
+ break;
+ default: {
+ unsigned version = semaCtx.langOptions().OpenMPVersion;
+ TODO(currentLocation,
+ "Applying a loop-associated on the loop generated by the " +
+ llvm::omp::getOpenMPDirectiveName(nestedDirective, version) +
+ " construct");
+ }
+ }
}
}
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index e2da60ed19de8..231eea8841d4b 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -2260,6 +2260,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/openmp-utils.cpp b/flang/lib/Parser/openmp-utils.cpp
index 2424828293c73..dfe8dbdd5ac9e 100644
--- a/flang/lib/Parser/openmp-utils.cpp
+++ b/flang/lib/Parser/openmp-utils.cpp
@@ -41,6 +41,23 @@ const OpenMPConstruct *GetOmp(const ExecutionPartConstruct &x) {
return nullptr;
}
+const OpenMPLoopConstruct *GetOmpLoop(const ExecutionPartConstruct &x) {
+ if (auto *construct{GetOmp(x)}) {
+ if (auto *omp{std::get_if<OpenMPLoopConstruct>(&construct->u)}) {
+ return omp;
+ }
+ }
+ return nullptr;
+}
+const DoConstruct *GetDoConstruct(const ExecutionPartConstruct &x) {
+ if (auto *y{std::get_if<ExecutableConstruct>(&x.u)}) {
+ if (auto *z{std::get_if<common::Indirection<DoConstruct>>(&y->u)}) {
+ return &z->value();
+ }
+ }
+ return nullptr;
+}
+
const OmpObjectList *GetOmpObjectList(const OmpClause &clause) {
// Clauses with OmpObjectList as its data member
using MemberObjectListClauses = std::tuple<OmpClause::Copyin,
diff --git a/flang/lib/Semantics/canonicalize-omp.cpp b/flang/lib/Semantics/canonicalize-omp.cpp
index 0cec1969e0978..f7c53d6d8f4c4 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -9,6 +9,7 @@
#include "canonicalize-omp.h"
#include "flang/Parser/parse-tree-visitor.h"
#include "flang/Parser/parse-tree.h"
+#include "flang/Semantics/openmp-directive-sets.h"
#include "flang/Semantics/semantics.h"
// After Loop Canonicalization, rewrite OpenMP parse tree to make OpenMP
@@ -136,20 +137,30 @@ class CanonicalizationOfOmp {
"A DO loop must follow the %s directive"_err_en_US,
parser::ToUpperCaseLetters(dirName.source.ToString()));
};
- auto tileUnrollError = [](const parser::OmpDirectiveName &dirName,
- parser::Messages &messages) {
+ 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;
auto &body{std::get<parser::Block>(x.t)};
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()) {
@@ -160,9 +171,12 @@ class CanonicalizationOfOmp {
if (nextIt != block.end()) {
if (auto *endDir{
GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
- std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
- std::move(*endDir);
- nextIt = block.erase(nextIt);
+ auto &endDirName = endDir->DirName();
+ if (endDirName.v != llvm::omp::Directive::OMPD_fuse) {
+ std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
+ std::move(*endDir);
+ nextIt = block.erase(nextIt);
+ }
}
}
} else {
@@ -172,50 +186,45 @@ class CanonicalizationOfOmp {
}
} 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 ((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)) {
- // 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()) {
- if (auto *endDir{
- GetConstructIf<parser::OmpEndLoopDirective>(*endIt)}) {
- auto &endDirName = endDir->DirName();
- if (endDirName.v == beginName.v) {
- std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
- std::move(*endDir);
- endIt = block.erase(endIt);
- continue;
+ 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 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_);
+ } 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_);
+ }
}
}
- ++endIt;
}
RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
body.push_back(std::move(*nextIt));
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_);
+ // 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>(*nextIt)}) {
+ auto &endDirName = endDir->DirName();
+ if (endDirName.v == beginName.v &&
+ endDirName.v != llvm::omp::Directive::OMPD_fuse) {
+ std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
+ std::move(*endDir);
+ nextIt = block.erase(nextIt);
}
}
}
@@ -227,11 +236,29 @@ class CanonicalizationOfOmp {
} else {
missingDoConstruct(beginName, messages_);
}
+
+ 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;
// 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 3d3596b500880..13581008433a6 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -285,9 +285,11 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
}
SetLoopInfo(x);
- if (const auto *doConstruct{x.GetNestedLoop()}) {
- const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
- CheckNoBranching(doBlock, beginName.v, beginName.source);
+ for (auto &construct : std::get<parser::Block>(x.t)) {
+ if (const auto *doConstruct{parser::omp::GetDoConstruct(construct)}) {
+ const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
+ CheckNoBranching(doBlock, beginName.v, beginName.source);
+ }
}
CheckLoopItrVariableIsInt(x);
CheckAssociatedLoopConstraints(x);
@@ -301,6 +303,11 @@ 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);
+ } else {
+ CheckNestedFuse(x);
+ }
}
const parser::Name OmpStructureChecker::GetLoopIndex(
@@ -320,24 +327,28 @@ void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
void OmpStructureChecker::CheckLoopItrVariableIsInt(
const parser::OpenMPLoopConstruct &x) {
- for (const parser::DoConstruct *loop{x.GetNestedLoop()}; loop;) {
- if (loop->IsDoNormal()) {
- const parser::Name &itrVal{GetLoopIndex(loop)};
- if (itrVal.symbol) {
- const auto *type{itrVal.symbol->GetType()};
- if (!type->IsNumeric(TypeCategory::Integer)) {
- context_.Say(itrVal.source,
- "The DO loop iteration"
- " variable must be of the type integer."_err_en_US,
- itrVal.ToString());
+ for (auto &construct : std::get<parser::Block>(x.t)) {
+ for (const parser::DoConstruct *loop{
+ parser::omp::GetDoConstruct(construct)};
+ loop;) {
+ if (loop->IsDoNormal()) {
+ const parser::Name &itrVal{GetLoopIndex(loop)};
+ if (itrVal.symbol) {
+ const auto *type{itrVal.symbol->GetType()};
+ if (!type->IsNumeric(TypeCategory::Integer)) {
+ context_.Say(itrVal.source,
+ "The DO loop iteration"
+ " variable must be of the type integer."_err_en_US,
+ itrVal.ToString());
+ }
}
}
+ // Get the next DoConstruct if block is not empty.
+ const auto &block{std::get<parser::Block>(loop->t)};
+ const auto it{block.begin()};
+ loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
+ : nullptr;
}
- // Get the next DoConstruct if block is not empty.
- const auto &block{std::get<parser::Block>(loop->t)};
- const auto it{block.begin()};
- loop =
- it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it) : nullptr;
}
}
@@ -401,23 +412,28 @@ void OmpStructureChecker::CheckDistLinear(
// Match the loop index variables with the collected symbols from linear
// clauses.
- for (const parser::DoConstruct *loop{x.GetNestedLoop()}; loop;) {
- if (loop->IsDoNormal()) {
- const parser::Name &itrVal{GetLoopIndex(loop)};
- if (itrVal.symbol) {
- // Remove the symbol from the collected set
- indexVars.erase(&itrVal.symbol->GetUltimate());
- }
- collapseVal--;
- if (collapseVal == 0) {
- break;
+ for (auto &construct : std::get<parser::Block>(x.t)) {
+ std::int64_t curCollapseVal{collapseVal};
+ for (const parser::DoConstruct *loop{
+ parser::omp::GetDoConstruct(construct)};
+ loop;) {
+ if (loop->IsDoNormal()) {
+ const parser::Name &itrVal{GetLoopIndex(loop)};
+ if (itrVal.symbol) {
+ // Remove the symbol from the collected set
+ indexVars.erase(&itrVal.symbol->GetUltimate());
+ }
+ curCollapseVal--;
+ if (curCollapseVal == 0) {
+ break;
+ }
}
+ // Get the next DoConstruct if block is not empty.
+ const auto &block{std::get<parser::Block>(loop->t)};
+ const auto it{block.begin()};
+ loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
+ : nullptr;
}
- // Get the next DoConstruct if block is not empty.
- const auto &block{std::get<parser::Block>(loop->t)};
- const auto it{block.begin()};
- loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
- : nullptr;
}
// Show error for the remaining variables
@@ -430,6 +446,63 @@ void OmpStructureChecker::CheckDistLinear(
}
}
+void OmpStructureChecker::CheckLooprangeBounds(
+ const parser::OpenMPLoopConstruct &x) {
+ const parser::OmpClauseList &clauseList{x.BeginDir().Clauses()};
+ if (clauseList.v.empty()) {
+ return;
+ }
+ for (auto &clause : clauseList.v) {
+ if (const auto *lrClause{
+ std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
+ auto first{GetIntValue(std::get<0>((lrClause->v).t))};
+ auto count{GetIntValue(std::get<1>((lrClause->v).t))};
+ if (!first || !count) {
+ return;
+ }
+ auto &loopConsList{std::get<parser::Block>(x.t)};
+ 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 following the construct."_err_en_US,
+ parser::ToUpperCaseLetters(clause.source.ToString()));
+ }
+ return;
+ }
+ }
+}
+
+void OmpStructureChecker::CheckNestedFuse(
+ const parser::OpenMPLoopConstruct &x) {
+ auto &loopConsList{std::get<parser::Block>(x.t)};
+ assert(loopConsList.size() == 1 && "Not Expecting a loop sequence");
+ const auto *ompConstruct{parser::omp::GetOmpLoop(loopConsList.front())};
+ if (!ompConstruct) {
+ return;
+ }
+ const parser::OmpClauseList &clauseList{ompConstruct->BeginDir().Clauses()};
+ if (clauseList.v.empty()) {
+ return;
+ }
+ for (auto &clause : clauseList.v) {
+ if (const auto *lrClause{
+ std::get_if<parser::OmpClause::Looprange>(&clause.u)}) {
+ auto count{GetIntValue(std::get<1>((lrClause->v).t))};
+ if (!count) {
+ return;
+ }
+ auto &nestedLoopConsList{std::get<parser::Block>(ompConstruct->t)};
+ if (nestedLoopConsList.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.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 37b4404cc598f..63751fd0c8abd 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -3401,9 +3401,11 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Sizes &c) {
}
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 1b84bc5dda471..a4d74398378d2 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -316,6 +316,8 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
void CheckAtomicWrite(const parser::OpenMPAtomicConstruct &x);
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 c4d103613b587..48b23ad077626 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -525,7 +525,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;
}
@@ -2028,6 +2031,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);
@@ -2205,8 +2209,11 @@ void OmpAttributeVisitor::CollectNumAffectedLoopsFromInnerLoopContruct(
const parser::OpenMPLoopConstruct &x,
llvm::SmallVector<std::int64_t> &levels,
llvm::SmallVector<const parser::OmpClause *> &clauses) {
- if (auto *innerConstruct{x.GetNestedConstruct()}) {
- CollectNumAffectedLoopsFromLoopConstruct(*innerConstruct, levels, clauses);
+ for (auto &construct : std::get<parser::Block>(x.t)) {
+ if (auto *innerConstruct{parser::omp::GetOmpLoop(construct)}) {
+ CollectNumAffectedLoopsFromLoopConstruct(
+ *innerConstruct, levels, clauses);
+ }
}
}
@@ -2271,74 +2278,74 @@ void OmpAttributeVisitor::CheckPerfectNestAndRectangularLoop(
// Find the associated region by skipping nested loop-associated constructs
// such as loop transformations
- const parser::OpenMPLoopConstruct *innermostConstruct{&x};
- while (auto *nested{innermostConstruct->GetNestedConstruct()}) {
- innermostConstruct = nested;
- }
-
- const auto *outer{innermostConstruct->GetNestedLoop()};
- if (!outer)
- return;
-
- llvm::SmallVector<Symbol *> ivs;
- int curLevel{0};
- const parser::DoConstruct *loop{outer};
- while (true) {
- auto [iv, lb, ub, step] = GetLoopBounds(*loop);
-
- if (lb)
- checkExprHasSymbols(ivs, lb);
- if (ub)
- checkExprHasSymbols(ivs, ub);
- if (step)
- checkExprHasSymbols(ivs, step);
- if (iv) {
- if (auto *symbol{currScope().FindSymbol(iv->source)})
- ivs.push_back(symbol);
- }
+ for (auto &construct : std::get<parser::Block>(x.t)) {
+ if (const auto *innermostConstruct{parser::omp::GetOmpLoop(construct)}) {
+ CheckPerfectNestAndRectangularLoop(*innermostConstruct);
+ } else if (const auto *doConstruct{
+ parser::omp::GetDoConstruct(construct)}) {
+
+ llvm::SmallVector<Symbol *> ivs;
+ int curLevel{0};
+ const auto *loop{doConstruct};
+ while (true) {
+ auto [iv, lb, ub, step] = GetLoopBounds(*loop);
+
+ if (lb)
+ checkExprHasSymbols(ivs, lb);
+ if (ub)
+ checkExprHasSymbols(ivs, ub);
+ if (step)
+ checkExprHasSymbols(ivs, step);
+ if (iv) {
+ if (auto *symbol{currScope().FindSymbol(iv->source)})
+ ivs.push_back(symbol);
+ }
- // Stop after processing all affected loops
- if (curLevel + 1 >= dirDepth)
- break;
+ // Stop after processing all affected loops
+ if (curLevel + 1 >= dirDepth)
+ break;
- // Recurse into nested loop
- const auto &block{std::get<parser::Block>(loop->t)};
- if (block.empty()) {
- // Insufficient number of nested loops already reported by
- // CheckAssocLoopLevel()
- break;
- }
+ // Recurse into nested loop
+ const auto &block{std::get<parser::Block>(loop->t)};
+ if (block.empty()) {
+ // Insufficient number of nested loops already reported by
+ // CheckAssocLoopLevel()
+ break;
+ }
- loop = GetDoConstructIf(block.front());
- if (!loop) {
- // Insufficient number of nested loops already reported by
- // CheckAssocLoopLevel()
- break;
- }
+ loop = GetDoConstructIf(block.front());
+ if (!loop) {
+ // Insufficient number of nested loops already reported by
+ // CheckAssocLoopLevel()
+ break;
+ }
- auto checkPerfectNest = [&, this]() {
- if (block.empty())
- return;
- auto last = block.end();
- --last;
+ auto checkPerfectNest = [&, this]() {
+ if (block.empty())
+ return;
+ auto last = block.end();
+ --last;
- // A trailing CONTINUE is not considered part of the loop body
- if (parser::Unwrap<parser::ContinueStmt>(*last))
- --last;
+ // A trailing CONTINUE is not considered part of the loop body
+ if (parser::Unwrap<parser::ContinueStmt>(*last))
+ --last;
- // In a perfectly nested loop, the nested loop must be the only statement
- if (last == block.begin())
- return;
+ // In a perfectly nested loop, the nested loop must be the only
+ // statement
+ if (last == block.begin())
+ return;
- // Non-perfectly nested loop
- // TODO: Point to non-DO statement, directiveSource as a note
- context_.Say(dirContext.directiveSource,
- "Canonical loop nest must be perfectly nested."_err_en_US);
- };
+ // Non-perfectly nested loop
+ // TODO: Point to non-DO statement, directiveSource as a note
+ context_.Say(dirContext.directiveSource,
+ "Canonical loop nest must be perfectly nested."_err_en_US);
+ };
- checkPerfectNest();
+ checkPerfectNest();
- ++curLevel;
+ ++curLevel;
+ }
+ }
}
}
@@ -2372,50 +2379,51 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
bool hasCollapseClause{
clause ? (clause->Id() == llvm::omp::OMPC_collapse) : false};
- const parser::OpenMPLoopConstruct *innerMostNest = &x;
- while (auto *nested{innerMostNest->GetNestedConstruct()}) {
- innerMostNest = nested;
- }
-
- if (const auto *outer{innerMostNest->GetNestedLoop()}) {
- 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);
+ for (auto &construct : std::get<parser::Block>(x.t)) {
+ if (const auto *innermostConstruct{parser::omp::GetOmpLoop(construct)}) {
+ PrivatizeAssociatedLoopIndexAndCheckLoopLevel(*innermostConstruct);
+ } else if (const auto *doConstruct{
+ parser::omp::GetDoConstruct(construct)}) {
+ for (const parser::DoConstruct *loop{&*doConstruct}; 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);
}
- } 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);
}
+ // 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 {
+ context_.Say(GetContext().directiveSource,
+ "A DO loop must follow the %s directive"_err_en_US,
+ parser::ToUpperCaseLetters(
+ llvm::omp::getOpenMPDirectiveName(GetContext().directive, version)
+ .str()));
}
- CheckAssocLoopLevel(level, GetAssociatedClause());
- } else {
- context_.Say(GetContext().directiveSource,
- "A DO loop must follow the %s directive"_err_en_US,
- parser::ToUpperCaseLetters(
- llvm::omp::getOpenMPDirectiveName(GetContext().directive, version)
- .str()));
}
}
diff --git a/flang/lib/Semantics/rewrite-parse-tree.cpp b/flang/lib/Semantics/rewrite-parse-tree.cpp
index b5a07680a3377..285eaac1e2c8f 100644
--- a/flang/lib/Semantics/rewrite-parse-tree.cpp
+++ b/flang/lib/Semantics/rewrite-parse-tree.cpp
@@ -9,6 +9,7 @@
#include "rewrite-parse-tree.h"
#include "flang/Common/indirection.h"
+#include "flang/Parser/openmp-utils.h"
#include "flang/Parser/parse-tree-visitor.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Parser/tools.h"
@@ -195,18 +196,24 @@ void RewriteMutator::OpenMPSimdOnly(
++it;
continue;
}
- if (auto *doConstruct =
- const_cast<parser::DoConstruct *>(ompLoop->GetNestedLoop())) {
- 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(const_cast<parser::Block &>(loopBody),
- /*isNonSimdLoopBody=*/true);
-
- auto newLoop = parser::ExecutionPartConstruct{
- parser::ExecutableConstruct{std::move(*doConstruct)}};
+ std::list<parser::ExecutionPartConstruct> doList;
+ for (auto &construct : std::get<parser::Block>(ompLoop->t)) {
+ if (auto *doConstruct = const_cast<parser::DoConstruct *>(
+ parser::omp::GetDoConstruct(construct))) {
+ 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(const_cast<parser::Block &>(loopBody),
+ /*isNonSimdLoopBody=*/true);
+ auto newLoop = parser::ExecutionPartConstruct{
+ parser::ExecutableConstruct{std::move(*doConstruct)}};
+ 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>(
@@ -384,10 +391,12 @@ 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.
- if (auto *doConstruct =
- const_cast<parser::DoConstruct *>(ompLoop.GetNestedLoop())) {
- auto &innerBlock = std::get<parser::Block>(doConstruct->t);
- OpenMPSimdOnly(innerBlock, /*isNonSimdLoopBody=*/true);
+ for (auto &construct : std::get<parser::Block>(ompLoop.t)) {
+ if (auto *doConstruct = parser::omp::GetDoConstruct(construct)) {
+ auto &innerBlock = std::get<parser::Block>(doConstruct->t);
+ OpenMPSimdOnly(const_cast<parser::Block &>(innerBlock),
+ /*isNonSimdLoopBody=*/true);
+ }
}
}
return true;
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-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/Parser/OpenMP/fuse01.f90 b/flang/test/Parser/OpenMP/fuse01.f90
new file mode 100644
index 0000000000000..98ce0e33797b5
--- /dev/null
+++ b/flang/test/Parser/OpenMP/fuse01.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/fuse02.f90 b/flang/test/Parser/OpenMP/fuse02.f90
new file mode 100644
index 0000000000000..cc3de48dd658a
--- /dev/null
+++ b/flang/test/Parser/OpenMP/fuse02.f90
@@ -0,0 +1,97 @@
+! 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: | | | 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: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> 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: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> 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: | | | | ExecutionPartConstruct -> ExecutableConstruct -> 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/Parser/OpenMP/loop-transformation-construct04.f90 b/flang/test/Parser/OpenMP/loop-transformation-construct04.f90
new file mode 100644
index 0000000000000..e37e2bbfe155b
--- /dev/null
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct04.f90
@@ -0,0 +1,80 @@
+! 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: | | | 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: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> 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: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> 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..6d3303841d506
--- /dev/null
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct05.f90
@@ -0,0 +1,90 @@
+! 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 sizes(2)
+ 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: | | | 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: | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> 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: | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | | | | OmpDirectiveName -> llvm::omp::Directive = tile
+!CHECK-PARSE-NEXT: | | | | | | | | OmpClauseList -> OmpClause -> Sizes -> Scalar -> Integer -> Expr = '2_4'
+!CHECK-PARSE-NEXT: | | | | | | | | | LiteralConstant -> IntLiteralConstant = '2'
+!CHECK-PARSE-NEXT: | | | | | | | | Flags = None
+!CHECK-PARSE-NEXT: | | | | | | | Block
+!CHECK-PARSE-NEXT: | | | | | | | | ExecutionPartConstruct -> ExecutableConstruct -> 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-clauses01.f90 b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
new file mode 100644
index 0000000000000..9ca0e8cfc9af1
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/loop-transformation-clauses01.f90
@@ -0,0 +1,66 @@
+! 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 :: a
+ integer :: v(i)
+
+ !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
+ 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
+
+ !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
+
+ !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
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..d82fc3668198d
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/loop-transformation-construct02.f90
@@ -0,0 +1,93 @@
+! 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
+ !$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
+ !$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 looprange(1,1)
+ !$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
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..5e459c7985523
--- /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 sizes(2)
+ 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..2856247329f3b
--- /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 sizes(2)
+ !$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
diff --git a/flang/test/Semantics/OpenMP/tile02.f90 b/flang/test/Semantics/OpenMP/tile02.f90
index 676796375353f..096a0f349932e 100644
--- a/flang/test/Semantics/OpenMP/tile02.f90
+++ b/flang/test/Semantics/OpenMP/tile02.f90
@@ -6,7 +6,7 @@ subroutine on_unroll
implicit none
integer i
- !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 sizes(2)
!$omp unroll
do i = 1, 5
More information about the flang-commits
mailing list