-
Notifications
You must be signed in to change notification settings - Fork 14.5k
[Flang][OpenMP] Add Semantics support for Nested OpenMPLoopConstructs #145917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Flang][OpenMP] Add Semantics support for Nested OpenMPLoopConstructs #145917
Conversation
@llvm/pr-subscribers-flang-semantics @llvm/pr-subscribers-flang-parser Author: Jack Styles (Stylie777) ChangesIn OpenMP Version 5.1, the tile and unroll directives were added. When using these directives, it is possible to nest them within other OpenMP Loop Constructs. This patch enables the semantics to allow for this behaviour on these specific directives. Any nested loops will be stored within the initial Loop Construct until reaching the DoConstruct itself. Relevant tests have been added, and previous behaviour has been retained with no changes. See also, #110008 Patch is 26.86 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/145917.diff 11 Files Affected:
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 61f97b855b0e5..7aa9250234978 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -5025,7 +5025,7 @@ struct OpenMPLoopConstruct {
TUPLE_CLASS_BOILERPLATE(OpenMPLoopConstruct);
OpenMPLoopConstruct(OmpBeginLoopDirective &&a)
: t({std::move(a), std::nullopt, std::nullopt}) {}
- std::tuple<OmpBeginLoopDirective, std::optional<DoConstruct>,
+ std::tuple<OmpBeginLoopDirective, std::optional<std::variant<DoConstruct, common::Indirection<OpenMPLoopConstruct>>>,
std::optional<OmpEndLoopDirective>>
t;
};
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 3e865a1ee7185..e81ce5e3dd22f 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -4107,6 +4107,12 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
mlir::Location currentLocation =
converter.genLocation(beginLoopDirective.source);
+ auto &optLoopCons = std::get<1>(loopConstruct.t);
+ if(optLoopCons.has_value())
+ if(auto *ompNestedLoopCons{std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(&*optLoopCons)}) {
+ genOMP(converter, symTable, semaCtx, eval, ompNestedLoopCons->value());
+ }
+
llvm::omp::Directive directive =
std::get<parser::OmpLoopDirective>(beginLoopDirective.t).v;
const parser::CharBlock &source =
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index ed0f227fd5b98..276be878dd44e 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2926,7 +2926,7 @@ class UnparseVisitor {
Walk(std::get<OmpBeginLoopDirective>(x.t));
Put("\n");
EndOpenMP();
- Walk(std::get<std::optional<DoConstruct>>(x.t));
+ Walk(std::get<std::optional<std::variant<DoConstruct, common::Indirection<parser::OpenMPLoopConstruct>>>>(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 5164f1dc6faab..d6827435173d3 100644
--- a/flang/lib/Semantics/canonicalize-omp.cpp
+++ b/flang/lib/Semantics/canonicalize-omp.cpp
@@ -8,6 +8,7 @@
#include "canonicalize-omp.h"
#include "flang/Parser/parse-tree-visitor.h"
+#include "flang/Parser/parse-tree.h"
// After Loop Canonicalization, rewrite OpenMP parse tree to make OpenMP
// Constructs more structured which provide explicit scopes for later
@@ -106,6 +107,12 @@ class CanonicalizationOfOmp {
return nullptr;
}
+ void missingDoConstruct(parser::OmpLoopDirective &dir) {
+ messages_.Say(dir.source,
+ "A DO loop must follow the %s directive"_err_en_US,
+ parser::ToUpperCaseLetters(dir.source.ToString()));
+ }
+
void RewriteOpenMPLoopConstruct(parser::OpenMPLoopConstruct &x,
parser::Block &block, parser::Block::iterator it) {
// Check the sequence of DoConstruct and OmpEndLoopDirective
@@ -135,31 +142,62 @@ class CanonicalizationOfOmp {
if (auto *doCons{GetConstructIf<parser::DoConstruct>(*nextIt)}) {
if (doCons->GetLoopControl()) {
// move DoConstruct
- std::get<std::optional<parser::DoConstruct>>(x.t) =
+ std::get<std::optional<std::variant<parser::DoConstruct, common::Indirection<parser::OpenMPLoopConstruct>>>>(x.t) =
std::move(*doCons);
nextIt = block.erase(nextIt);
// try to match OmpEndLoopDirective
- if (nextIt != block.end()) {
- if (auto *endDir{
- GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
- std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
- std::move(*endDir);
- block.erase(nextIt);
- }
+ if (auto *endDir{
+ GetConstructIf<parser::OmpEndLoopDirective>(*nextIt)}) {
+ std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
+ std::move(*endDir);
+ nextIt = block.erase(nextIt);
}
} else {
messages_.Say(dir.source,
"DO loop after the %s directive must have loop control"_err_en_US,
parser::ToUpperCaseLetters(dir.source.ToString()));
}
+ } 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
+ auto &beginDirective =
+ std::get<parser::OmpBeginLoopDirective>(ompLoopCons->t);
+ auto &beginLoopDirective =
+ std::get<parser::OmpLoopDirective>(beginDirective.t);
+ // 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 &endLoopDirective = std::get<parser::OmpLoopDirective>(endDir->t);
+ if(endLoopDirective.v == dir.v) {
+ std::get<std::optional<parser::OmpEndLoopDirective>>(x.t) =
+ std::move(*endDir);
+ endIt = block.erase(endIt);
+ continue;
+ }
+ }
+ ++endIt;
+ }
+ if ((beginLoopDirective.v == llvm::omp::Directive::OMPD_unroll ||
+ beginLoopDirective.v == llvm::omp::Directive::OMPD_tile)) {
+ RewriteOpenMPLoopConstruct(*ompLoopCons, block, nextIt);
+ auto &ompLoop = std::get<std::optional<std::variant<parser::DoConstruct, common::Indirection<parser::OpenMPLoopConstruct>>>>(x.t);
+ ompLoop = std::optional<std::variant<parser::DoConstruct, common::Indirection<parser::OpenMPLoopConstruct>>>{
+ std::variant<parser::DoConstruct, common::Indirection<parser::OpenMPLoopConstruct>>{
+ common::Indirection{std::move(*ompLoopCons)}}};
+ nextIt = block.erase(nextIt);
+ }
} else {
- messages_.Say(dir.source,
- "A DO loop must follow the %s directive"_err_en_US,
- parser::ToUpperCaseLetters(dir.source.ToString()));
+ missingDoConstruct(dir);
}
// If we get here, we either found a loop, or issued an error message.
return;
}
+ if (nextIt == block.end()) {
+ missingDoConstruct(dir);
+ }
}
void RewriteOmpAllocations(parser::ExecutionPart &body) {
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 36d4bcb5d99fa..4e71641ae3858 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -761,10 +761,13 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
}
SetLoopInfo(x);
- if (const auto &doConstruct{
- std::get<std::optional<parser::DoConstruct>>(x.t)}) {
- const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
- CheckNoBranching(doBlock, beginDir.v, beginDir.source);
+ auto &optLoopCons = std::get<1>(x.t);
+ if(optLoopCons.has_value()) {
+ if (const auto &doConstruct{
+ std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ const auto &doBlock{std::get<parser::Block>(doConstruct->t)};
+ CheckNoBranching(doBlock, beginDir.v, beginDir.source);
+ }
}
CheckLoopItrVariableIsInt(x);
CheckAssociatedLoopConstraints(x);
@@ -778,6 +781,12 @@ void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
(beginDir.v == llvm::omp::Directive::OMPD_distribute_simd)) {
CheckDistLinear(x);
}
+ if (beginDir.v == llvm::omp::Directive::OMPD_tile) {
+ const auto &clauses{std::get<parser::OmpClauseList>(beginLoopDir.t)};
+ for (auto &clause : clauses.v) {
+
+ }
+ }
}
const parser::Name OmpStructureChecker::GetLoopIndex(
const parser::DoConstruct *x) {
@@ -785,12 +794,15 @@ const parser::Name OmpStructureChecker::GetLoopIndex(
return std::get<Bounds>(x->GetLoopControl()->u).name.thing;
}
void OmpStructureChecker::SetLoopInfo(const parser::OpenMPLoopConstruct &x) {
- if (const auto &loopConstruct{
- std::get<std::optional<parser::DoConstruct>>(x.t)}) {
- const parser::DoConstruct *loop{&*loopConstruct};
- if (loop && loop->IsDoNormal()) {
- const parser::Name &itrVal{GetLoopIndex(loop)};
- SetLoopIv(itrVal.symbol);
+ auto &optLoopCons = std::get<1>(x.t);
+ if (optLoopCons.has_value()) {
+ if (const auto &loopConstruct{
+ std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ const parser::DoConstruct *loop{&*loopConstruct};
+ if (loop && loop->IsDoNormal()) {
+ const parser::Name &itrVal{GetLoopIndex(loop)};
+ SetLoopIv(itrVal.symbol);
+ }
}
}
}
@@ -856,8 +868,10 @@ void OmpStructureChecker::CheckIteratorModifier(const parser::OmpIterator &x) {
void OmpStructureChecker::CheckLoopItrVariableIsInt(
const parser::OpenMPLoopConstruct &x) {
- if (const auto &loopConstruct{
- std::get<std::optional<parser::DoConstruct>>(x.t)}) {
+ auto &optLoopCons = std::get<1>(x.t);
+ if (optLoopCons.has_value()) {
+ if (const auto &loopConstruct{
+ std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
if (loop->IsDoNormal()) {
@@ -877,6 +891,7 @@ void OmpStructureChecker::CheckLoopItrVariableIsInt(
const auto it{block.begin()};
loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
: nullptr;
+ }
}
}
}
@@ -1076,8 +1091,10 @@ void OmpStructureChecker::CheckDistLinear(
// Match the loop index variables with the collected symbols from linear
// clauses.
+ auto &optLoopCons = std::get<1>(x.t);
+ if (optLoopCons.has_value()) {
if (const auto &loopConstruct{
- std::get<std::optional<parser::DoConstruct>>(x.t)}) {
+ std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
for (const parser::DoConstruct *loop{&*loopConstruct}; loop;) {
if (loop->IsDoNormal()) {
const parser::Name &itrVal{GetLoopIndex(loop)};
@@ -1095,6 +1112,7 @@ void OmpStructureChecker::CheckDistLinear(
const auto it{block.begin()};
loop = it != block.end() ? parser::Unwrap<parser::DoConstruct>(*it)
: nullptr;
+ }
}
}
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index 885c02e6ec74b..36bea4fbe7ae5 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -1796,10 +1796,13 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
SetContextAssociatedLoopLevel(GetAssociatedLoopLevelFromClauses(clauseList));
if (beginDir.v == llvm::omp::Directive::OMPD_do) {
- if (const auto &doConstruct{
- std::get<std::optional<parser::DoConstruct>>(x.t)}) {
- if (doConstruct.value().IsDoWhile()) {
- return true;
+ auto &optLoopCons = std::get<1>(x.t);
+ if (optLoopCons.has_value()) {
+ if (const auto &doConstruct{
+ std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ if (doConstruct->IsDoWhile()) {
+ return true;
+ }
}
}
}
@@ -1962,48 +1965,64 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
bool hasCollapseClause{
clause ? (clause->Id() == llvm::omp::OMPC_collapse) : false};
- const auto &outer{std::get<std::optional<parser::DoConstruct>>(x.t)};
- if (outer.has_value()) {
- 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);
+ auto &optLoopCons = std::get<1>(x.t);
+ if (optLoopCons.has_value()) {
+ if (const auto &outer{std::get_if<parser::DoConstruct>(&*optLoopCons)}) {
+ 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);
}
- } 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 if (const auto &loop{std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(&*optLoopCons)}) {
+ auto &beginDirective =
+ std::get<parser::OmpBeginLoopDirective>(loop->value().t);
+ auto &beginLoopDirective =
+ std::get<parser::OmpLoopDirective>(beginDirective.t);
+ if ((beginLoopDirective.v != llvm::omp::Directive::OMPD_unroll &&
+ beginLoopDirective.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()));
+ } else {
+ PrivatizeAssociatedLoopIndexAndCheckLoopLevel(loop->value());
}
+ } 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()));
}
}
+
void OmpAttributeVisitor::CheckAssocLoopLevel(
std::int64_t level, const parser::OmpClause *clause) {
if (clause && level != 0) {
diff --git a/flang/test/Lower/OpenMP/nested-loop-transformation-construct01.f90 b/flang/test/Lower/OpenMP/nested-loop-transformation-construct01.f90
new file mode 100644
index 0000000000000..b24e2d62ac2aa
--- /dev/null
+++ b/flang/test/Lower/OpenMP/nested-loop-transformation-construct01.f90
@@ -0,0 +1,20 @@
+! Test to ensure TODO message is emitted for tile OpenMP 5.1 Directives when they are nested.
+
+!RUN: not %flang -fopenmp -fopenmp-version=51 %s 2<&1 | FileCheck %s
+
+subroutine loop_transformation_construct
+ implicit none
+ integer :: I = 10
+ integer :: x
+ integer :: y(I)
+
+ !$omp do
+ !$omp tile
+ do i = 1, I
+ y(i) = y(i) * 5
+ end do
+ !$omp end tile
+ !$omp end do
+end subroutine
+
+!CHECK: not yet implemented: Unhandled loop directive (tile)
diff --git a/flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90 b/flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90
new file mode 100644
index 0000000000000..bbb16a6a699e9
--- /dev/null
+++ b/flang/test/Lower/OpenMP/nested-loop-transformation-construct02.f90
@@ -0,0 +1,20 @@
+! Test to ensure TODO message is emitted for unroll OpenMP 5.1 Directives when they are nested.
+
+!RUN: not %flang -fopenmp -fopenmp-version=51 %s 2<&1 | FileCheck %s
+
+program loop_transformation_construct
+ implicit none
+ integer, parameter :: I = 10
+ integer :: x
+ integer :: y(I)
+
+ !$omp do
+ !$omp unroll
+ do x = 1, I
+ y(x) = y(x) * 5
+ end do
+ !$omp end unroll
+ !$omp end do
+end program loop_transformation_construct
+
+!CHECK: not yet implemented: Unhandled loop directive (unroll)
diff --git a/flang/test/Parser/OpenMP/loop-transformation-construct01.f90 b/flang/test/Parser/OpenMP/loop-transformation-construct01.f90
new file mode 100644
index 0000000000000..baffc2f6e2f1e
--- /dev/null
+++ b/flang/test/Parser/OpenMP/loop-transformation-construct01.f90
@@ -0,0 +1,74 @@
+! Test the Parse Tree to ensure the OpenMP Loop Transformation Constructs nest correctly with 1 nested loop.
+
+! 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 :: x
+ integer :: y(I)
+
+ !$omp do
+ !$omp unroll
+ do i = 1, I
+ y(i) = y(i) * 5
+ end do
+ !$omp end unroll
+ !$omp end do
+end subroutine
+
+!CHECK-PARSE: | ExecutionPart -> Block
+!CHECK-PARSE-NEXT: | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | OmpLoopDirective -> llvm::omp::Directive = do
+!CHECK-PARSE-NEXT: | | | | OmpClauseList ->
+!CHECK-PARSE-NEXT: | | | OpenMPLoopConstruct
+!CHECK-PARSE-NEXT: | | | | OmpBeginLoopDirective
+!CHECK-PARSE-NEXT: | | | | | OmpLoopDirective -> llvm::omp::Directive = unroll
+!CHECK-PARSE-NEXT: | | | | | OmpClauseList ->
+!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 -> AssignmentStmt = 'y(int(i,kind=8))=5_4*y(int(i,kind=8))'
+!CHECK-PARSE-NEXT: | | | | | | | Variable = 'y(int(i,kind=8))'
+!CHECK-PARSE-NEXT: | | | | | | | | Designator -> DataRef -> ArrayElement
+!CHECK-PARSE-NEXT: | | | | | | | | | DataRef -> Name = 'y'
+!CHECK-PARSE-NEXT: | | | | | | | | | SectionSubscript -> Integer -> Expr = 'i'
+!CHECK-PARSE-NEXT: |...
[truncated]
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
I have a question about nested loop constructs and checking the legality of combining them. For example doing full unrolling and then tiling would be illegal since if a loop is unrolled there is nothing to tile. Another example that I ran into is collapse and tiling, since collapse assumes independent loops, but only the outer loops after tiling are independent. At some point in the front-end there has to be a checker that makes sure that the nested loop constructs are legal and can report meaningful error messages if they are not. Do you have any ideas how this can be done? Maybe a separate pass would be required that somehow tracks certain properties about the resulting loops that are created from a transform? |
if ((beginLoopDirective.v == llvm::omp::Directive::OMPD_unroll || | ||
beginLoopDirective.v == llvm::omp::Directive::OMPD_tile)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too many parentheses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
flang/lib/Lower/OpenMP/OpenMP.cpp
Outdated
@@ -4107,6 +4107,14 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, | |||
mlir::Location currentLocation = | |||
converter.genLocation(beginLoopDirective.source); | |||
|
|||
auto &optLoopCons = std::get<1>(loopConstruct.t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please spell out the type in <>.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
std::optional< | ||
std::variant<DoConstruct, common::Indirection<OpenMPLoopConstruct>>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can add a type alias for this, like
using NestedConstruct = std::variant<...>;
std::tuple<OmpBeginLoopDirective, std::optional<NestedConstruct>, ...
The full thing is spelled out in several places, it would make the code more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
std::get<std::optional<parser::DoConstruct>>(x.t)}) { | ||
const auto &doBlock{std::get<parser::Block>(doConstruct->t)}; | ||
CheckNoBranching(doBlock, beginDir.v, beginDir.source); | ||
auto &optLoopCons = std::get<1>(x.t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, please put the type name in <>. It's longer, but it makes it a bit easier to read, especially since you have auto
in the declaration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
if (loop && loop->IsDoNormal()) { | ||
const parser::Name &itrVal{GetLoopIndex(loop)}; | ||
SetLoopIv(itrVal.symbol); | ||
auto &optLoopCons = std::get<1>(x.t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DOne
@@ -856,27 +862,30 @@ void OmpStructureChecker::CheckIteratorModifier(const parser::OmpIterator &x) { | |||
|
|||
void OmpStructureChecker::CheckLoopItrVariableIsInt( | |||
const parser::OpenMPLoopConstruct &x) { | |||
if (const auto &loopConstruct{ | |||
std::get<std::optional<parser::DoConstruct>>(x.t)}) { | |||
auto &optLoopCons = std::get<1>(x.t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
In OpenMP Version 5.1, the tile and unroll directives were added. When using these directives, it is possible to nest them within other OpenMP Loop Constructs. This patch enables the semantics to allow for this behaviour on these specific directives. Any nested loops will be stored within the initial Loop Construct until reaching the DoConstruct itself. Relevant tests have been added, and previous behaviour has been retained with no changes. See also, llvm#110008
e075bfb
to
fec9503
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the review comments - these have now been addressed
std::optional< | ||
std::variant<DoConstruct, common::Indirection<OpenMPLoopConstruct>>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
flang/lib/Lower/OpenMP/OpenMP.cpp
Outdated
@@ -4107,6 +4107,14 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, | |||
mlir::Location currentLocation = | |||
converter.genLocation(beginLoopDirective.source); | |||
|
|||
auto &optLoopCons = std::get<1>(loopConstruct.t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if ((beginLoopDirective.v == llvm::omp::Directive::OMPD_unroll || | ||
beginLoopDirective.v == llvm::omp::Directive::OMPD_tile)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
std::get<std::optional<parser::DoConstruct>>(x.t)}) { | ||
const auto &doBlock{std::get<parser::Block>(doConstruct->t)}; | ||
CheckNoBranching(doBlock, beginDir.v, beginDir.source); | ||
auto &optLoopCons = std::get<1>(x.t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
if (loop && loop->IsDoNormal()) { | ||
const parser::Name &itrVal{GetLoopIndex(loop)}; | ||
SetLoopIv(itrVal.symbol); | ||
auto &optLoopCons = std::get<1>(x.t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DOne
@@ -856,27 +862,30 @@ void OmpStructureChecker::CheckIteratorModifier(const parser::OmpIterator &x) { | |||
|
|||
void OmpStructureChecker::CheckLoopItrVariableIsInt( | |||
const parser::OpenMPLoopConstruct &x) { | |||
if (const auto &loopConstruct{ | |||
std::get<std::optional<parser::DoConstruct>>(x.t)}) { | |||
auto &optLoopCons = std::get<1>(x.t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@jsjodin Regarding your comment. This is not something I had considered. I could look at adding a check here? If we have an With the second example you have given, I am not sure if this can be dealt with here and that might require a new pass to check this. I will take a look and see whats possible in the scope I suspect the first point is possible, the second may need some level of processing in the lowering. Admittedly my experience with Flang is limited, having only begun working on this part of the LLVM Project 4 weeks ago, so if others have other ideas please bring them forward. |
I think having the checking done in a separate pass makes sense and the same module could also provide utility functions that could be used for codegen. The problem I had to handle was the combination of collapse and tile and calculate how many loops that need considered to be combined into a single loop nest op. That happened in a couple of places in the code. Another issue is restrictions on what these transforms can do in the OpenMPIRBuilder. What is legal or not may vary over time, so having a central place where this is encoded would be useful. Another question is if the checking should implemented in LLVM similar to how directive and clause handling is done? The same problem has to be solved for Clang and maybe MLIR depending on other possible front-ends. Edit: I think for now if there is a simple check that can be added in your PR you might want to do that, but if it is complicated it should probably be done in a separate PR. |
@jsjodin I think for other cases where it is more involved, a new pass/PR is more sensible. I don't think solving this in the backend is a bad idea, because then as you pointed out it would deal with MLIR/Clang (and any other frontend that uses llvm). I suspect the new pass could be implemented there and it may remove the need for some of the checks we are doing currently in the frontend of clang. Either way, it's a subject for another PR. |
During review, it was noted that tiling after unrolling should not be possible, so a semantics check is now implemented for this. This also refactors the error messages to be lambda functions where an error message can be called from multiple places.
I don't know of a way to generate proper diagnostic messages after semantics. Obviously we can print to stderr and stop the compiler but we loose all of the usual formatting and printing of source code in standard error messages. I think this needs to be done in flang semantics if at all possible (possibly using utilities shared with other places). |
@@ -125,6 +126,16 @@ class CanonicalizationOfOmp { | |||
parser::Block::iterator nextIt; | |||
auto &beginDir{std::get<parser::OmpBeginLoopDirective>(x.t)}; | |||
auto &dir{std::get<parser::OmpLoopDirective>(beginDir.t)}; | |||
auto missingDoConstruct = [](auto &dir, auto &messages_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameters shouldn't have a trailing underscore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
"A DO loop must follow the %s directive"_err_en_US, | ||
parser::ToUpperCaseLetters(dir.source.ToString())); | ||
}; | ||
auto tileUnrollError = [](auto &dir, auto &messages_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
flang/lib/Lower/OpenMP/OpenMP.cpp
Outdated
@@ -4231,6 +4231,15 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, | |||
mlir::Location currentLocation = | |||
converter.genLocation(beginLoopDirective.source); | |||
|
|||
auto &optLoopCons = | |||
std::get<std::optional<parser::NestedConstruct>>(loopConstruct.t); | |||
if (optLoopCons.has_value()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add braces here. This is the common convention even for single statements when the statement contains its own sub-statements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if ((beginLoopDirective.v != llvm::omp::Directive::OMPD_unroll && | ||
beginLoopDirective.v != llvm::omp::Directive::OMPD_tile)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra parentheses here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed - apologies did not spot this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @kparzysz
flang/lib/Lower/OpenMP/OpenMP.cpp
Outdated
@@ -4231,6 +4231,15 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable, | |||
mlir::Location currentLocation = | |||
converter.genLocation(beginLoopDirective.source); | |||
|
|||
auto &optLoopCons = | |||
std::get<std::optional<parser::NestedConstruct>>(loopConstruct.t); | |||
if (optLoopCons.has_value()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -125,6 +126,16 @@ class CanonicalizationOfOmp { | |||
parser::Block::iterator nextIt; | |||
auto &beginDir{std::get<parser::OmpBeginLoopDirective>(x.t)}; | |||
auto &dir{std::get<parser::OmpLoopDirective>(beginDir.t)}; | |||
auto missingDoConstruct = [](auto &dir, auto &messages_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
"A DO loop must follow the %s directive"_err_en_US, | ||
parser::ToUpperCaseLetters(dir.source.ToString())); | ||
}; | ||
auto tileUnrollError = [](auto &dir, auto &messages_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if ((beginLoopDirective.v != llvm::omp::Directive::OMPD_unroll && | ||
beginLoopDirective.v != llvm::omp::Directive::OMPD_tile)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed - apologies did not spot this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
…llvm#145917) In OpenMP Version 5.1, the tile and unroll directives were added. When using these directives, it is possible to nest them within other OpenMP Loop Constructs. This patch enables the semantics to allow for this behaviour on these specific directives. Any nested loops will be stored within the initial Loop Construct until reaching the DoConstruct itself. Relevant tests have been added, and previous behaviour has been retained with no changes. See also, llvm#110008
…llvm#145917) In OpenMP Version 5.1, the tile and unroll directives were added. When using these directives, it is possible to nest them within other OpenMP Loop Constructs. This patch enables the semantics to allow for this behaviour on these specific directives. Any nested loops will be stored within the initial Loop Construct until reaching the DoConstruct itself. Relevant tests have been added, and previous behaviour has been retained with no changes. See also, llvm#110008
seeing random 5 out of 30 compilations fail "flang" "-fc1" "-triple" "x86_64-unknown-linux-gnu" "-emit-llvm-bc" "-mrelocation-model" "static" "-ffast-math" "-target-cpu" "x86-64" "-floop-interchange" "-vectorize-loops" "-vectorize-slp" "-fversion-loops-for-stride" "-fopenmp" "-fopenmp-targets=amdgcn-amd-amdhsa" "-mframe-pointer=none" "-O3" "-x" "f95" "update_halo_kernel-gfx90a-8cde79" #4 0x000014937e341abe Fortran::semantics::CanonicalizationOfOmp::RewriteOpenMPLoopConstruct(Fortran::parser::OpenMPLoopConstruct&, std |
Hi @ronlieb, EDIT: Using the CMD Line options you provided I cannot reproduce this crash using the test files I created for developing this feature. A Reproducer would be very handy in this case to try and locate any potential issues. |
hi @Stylie777 did you try running the compilation 30 times ? its very intermittent for me |
@ronlieb This is done with a build based on Are you seeing this error on a pure LLVM/Flang build or with other changes applied? |
This line sets |
Command:
|
Thanks @kparzysz I will take a look. |
Apologies @kparzysz @ronlieb I am so far unable to reproduce this issue. I ran the reproducer with the command provided 120 times and it has not crashed. I agree that where it sets
Is there any other changes you have applied ontop of mine that could be causing this? |
Add I'm pretty sure that all other cases where nextIt changes go back to the beginning of the loop where nextIt != block.end() is checked. In this one case I wrapped the following "if" in "if (nextIt != block.end())" and that fixed the problem. |
Ok, I will post a fix up or review and tag you both. Thanks all. |
I also hammered the tests on AArch64 Ubuntu Linux and never found a failure but it would fail within a few runs on Windows on Arm. So there is some unknown factor, perhaps memory allocation if the list has to reallocate as a result of the erase. |
On Windows on Arm, the following hack passes all tests and hammering the specific tests produced no failures:
|
I tested it on X86 Ubuntu and found the same. I don't have access to Windows on Arm but that may explain why I could not reproduce the issue. |
As reported in llvm#145917 and llvm#147309, there are situation's where flang may crash. This is because `nextIt` in `RewriteOpenMPLoopConstruct` gets re-assigned when an iterator is erased from the block. If this is missed, Flang may attempt to access a location in memory that is not accessable and cause a compiler crash. This adds protection where the crash can occur, and a test with a reproducer that can trigger the crash. Fixes llvm#147309
Fix is up for review. Apologies everyone for the disruption and thanks for the collaboration. |
As reported in #145917 and #147309, there are situation's where flang may crash. This is because `nextIt` in `RewriteOpenMPLoopConstruct` gets re-assigned when an iterator is erased from the block. If this is missed, Flang may attempt to access a location in memory that is not accessable and cause a compiler crash. This adds protection where the crash can occur, and a test with a reproducer that can trigger the crash. Fixes #147309
In OpenMP Version 5.1, the tile and unroll directives were added. When using these directives, it is possible to nest them within other OpenMP Loop Constructs. This patch enables the semantics to allow for this behaviour on these specific directives. Any nested loops will be stored within the initial Loop Construct until reaching the DoConstruct itself.
Relevant tests have been added, and previous behaviour has been retained with no changes.
See also, #110008