Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 136 additions & 6 deletions flang/lib/Semantics/resolve-directives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ template <typename T> class DirectiveAttributeVisitor {
dataSharingAttributeObjects_.clear();
}
bool HasDataSharingAttributeObject(const Symbol &);
std::tuple<const parser::Name *, const parser::ScalarExpr *,
const parser::ScalarExpr *, const parser::ScalarExpr *>
GetLoopBounds(const parser::DoConstruct &);
const parser::Name *GetLoopIndex(const parser::DoConstruct &);
const parser::DoConstruct *GetDoConstructIf(
const parser::ExecutionPartConstruct &);
Expand Down Expand Up @@ -933,6 +936,13 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
privateDataSharingAttributeObjects_.clear();
}

/// Check that loops in the loop nest are perfectly nested, as well that lower
/// bound, upper bound, and step expressions do not use the iv
/// of a surrounding loop of the associated loops nest.
/// We do not support non-perfectly nested loops not non-rectangular loops yet
/// (both introduced in OpenMP 5.0)
void CheckPerfectNestAndRectangularLoop(const parser::OpenMPLoopConstruct &x);

// Predetermined DSA rules
void PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
const parser::OpenMPLoopConstruct &);
Expand Down Expand Up @@ -1009,23 +1019,31 @@ bool DirectiveAttributeVisitor<T>::HasDataSharingAttributeObject(
}

template <typename T>
const parser::Name *DirectiveAttributeVisitor<T>::GetLoopIndex(
const parser::DoConstruct &x) {
std::tuple<const parser::Name *, const parser::ScalarExpr *,
const parser::ScalarExpr *, const parser::ScalarExpr *>
DirectiveAttributeVisitor<T>::GetLoopBounds(const parser::DoConstruct &x) {
using Bounds = parser::LoopControl::Bounds;
if (x.GetLoopControl()) {
if (const Bounds * b{std::get_if<Bounds>(&x.GetLoopControl()->u)}) {
return &b->name.thing;
} else {
return nullptr;
auto &&step = b->step;
return {&b->name.thing, &b->lower, &b->upper,
step.has_value() ? &step.value() : nullptr};
}
} else {
context_
.Say(std::get<parser::Statement<parser::NonLabelDoStmt>>(x.t).source,
"Loop control is not present in the DO LOOP"_err_en_US)
.Attach(GetContext().directiveSource,
"associated with the enclosing LOOP construct"_en_US);
return nullptr;
}
return {nullptr, nullptr, nullptr, nullptr};
}

template <typename T>
const parser::Name *DirectiveAttributeVisitor<T>::GetLoopIndex(
const parser::DoConstruct &x) {
auto &&[iv, lb, ub, step] = GetLoopBounds(x);
return iv;
}

template <typename T>
Expand Down Expand Up @@ -1957,6 +1975,10 @@ bool OmpAttributeVisitor::Pre(const parser::OpenMPLoopConstruct &x) {
}
}
}

// Must be done before iv privatization
CheckPerfectNestAndRectangularLoop(x);

PrivatizeAssociatedLoopIndexAndCheckLoopLevel(x);
ordCollapseLevel = GetNumAffectedLoopsFromLoopConstruct(x) + 1;
return true;
Expand Down Expand Up @@ -2152,6 +2174,114 @@ void OmpAttributeVisitor::CollectNumAffectedLoopsFromClauses(
}
}

void OmpAttributeVisitor::CheckPerfectNestAndRectangularLoop(
const parser::OpenMPLoopConstruct &x) {
auto &dirContext = GetContext();
std::int64_t dirDepth{dirContext.associatedLoopLevel};
if (dirDepth <= 0)
return;

auto checkExprHasSymbols = [&](llvm::SmallVector<Symbol *> &ivs,
const parser::ScalarExpr *bound) {
if (ivs.empty())
return;
auto boundExpr{semantics::AnalyzeExpr(context_, *bound)};
if (!boundExpr)
return;
semantics::UnorderedSymbolSet boundSyms =
evaluate::CollectSymbols(*boundExpr);
if (boundSyms.empty())
return;
for (Symbol *iv : ivs) {
if (boundSyms.count(*iv) != 0) {
// TODO: Point to occurence of iv in boundExpr, directiveSource as a
// note
context_.Say(dirContext.directiveSource,
"Trip count must be computable and invariant"_err_en_US);
}
}
};

// Skip over loop transformation directives
const parser::OpenMPLoopConstruct *innerMostLoop = &x;
const parser::NestedConstruct *innerMostNest = nullptr;
while (auto &optLoopCons{
std::get<std::optional<parser::NestedConstruct>>(innerMostLoop->t)}) {
innerMostNest = &(optLoopCons.value());
if (const auto *innerLoop{
std::get_if<common::Indirection<parser::OpenMPLoopConstruct>>(
innerMostNest)}) {
innerMostLoop = &(innerLoop->value());
} else {
break;
}
}

if (!innerMostNest)
return;
const auto &outer{std::get_if<parser::DoConstruct>(innerMostNest)};
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);
}

// 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;
}

loop = GetDoConstructIf(block.front());
if (!loop) {
// Insufficient number of nested loops already reported by
// CheckAssocLoopLevel()
break;
}

auto checkPerfectNest = [&, this]() {
auto blockSize = block.size();
if (blockSize <= 1)
return;

if (parser::Unwrap<parser::ContinueStmt>(x))
blockSize -= 1;

if (blockSize <= 1)
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);
};

checkPerfectNest();

++curLevel;
}
}

// 2.15.1.1 Data-sharing Attribute Rules - Predetermined
// - The loop iteration variable(s) in the associated do-loop(s) of a do,
// parallel do, taskloop, or distribute construct is (are) private.
Expand Down
28 changes: 28 additions & 0 deletions flang/test/Fir/omp-cli.fir
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// RUN: fir-opt %s | FileCheck %s --enable-var-scope
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this test belongs in the MLIR openmp dialect rather than in flang? It could be written with scf.if instead of fir.if etc.

In theory at least, the MLIR openmp dialect exists upstream of flang and could have other users.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am worried about the dependencies. With FIR we can assume that the omp dialect is available, there are other tests using the omp dialect already. The openmp dialect does not require scf, nor does scf require omp.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated using scf.if

// RUN: fir-opt %s | fir-opt | FileCheck %s --enable-var-scope

// CHECK-LABEL: @omp_canonloop_multiregion(
func.func @omp_canonloop_multiregion(%c : i1) -> () {
%c42_i32 = arith.constant 42: i32
%canonloop1 = omp.new_cli
%canonloop2 = omp.new_cli
%canonloop3 = omp.new_cli
fir.if %c {
// CHECK: omp.canonical_loop(%canonloop_r0) %iv_r0 : i32 in range(%c42_i32) {
omp.canonical_loop(%canonloop1) %iv1 : i32 in range(%c42_i32) {
omp.terminator
}
} else {
// CHECK: omp.canonical_loop(%canonloop_r1_s0) %iv_r1_s0 : i32 in range(%c42_i32) {
omp.canonical_loop(%canonloop2) %iv2 : i32 in range(%c42_i32) {
omp.terminator
}
// CHECK: omp.canonical_loop(%canonloop_r1_s1) %iv_r1_s1 : i32 in range(%c42_i32) {
omp.canonical_loop(%canonloop3) %iv3 : i32 in range(%c42_i32) {
omp.terminator
}
}

// CHECK: fir.unreachable
fir.unreachable
}
1 change: 1 addition & 0 deletions flang/test/Semantics/OpenMP/do08.f90
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ program omp
!$omp end do


!ERROR: Canonical loop nest must be perfectly nested.
!ERROR: The value of the parameter in the COLLAPSE or ORDERED clause must not be larger than the number of nested loops following the construct.
!$omp do collapse(3)
do 60 i=2,200,2
Expand Down
1 change: 1 addition & 0 deletions flang/test/Semantics/OpenMP/do13.f90
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ program omp
!$omp end do


!ERROR: Canonical loop nest must be perfectly nested.
!ERROR: The value of the parameter in the COLLAPSE or ORDERED clause must not be larger than the number of nested loops following the construct.
!$omp do collapse(3)
do 60 i=1,10
Expand Down
73 changes: 73 additions & 0 deletions flang/test/Semantics/OpenMP/do22.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
! RUN: %python %S/../test_errors.py %s %flang -fopenmp
! Check for existence of loop following a DO directive

subroutine do_imperfectly_nested_before
integer i, j

!ERROR: The value of the parameter in the COLLAPSE or ORDERED clause must not be larger than the number of nested loops following the construct.
!$omp do collapse(2)
do i = 1, 10
print *, i
do j = 1, 10
print *, i, j
end do
end do
!$omp end do
end subroutine


subroutine do_imperfectly_nested_behind
integer i, j

!ERROR: Canonical loop nest must be perfectly nested.
!$omp do collapse(2)
do i = 1, 10
do j = 1, 10
print *, i, j
end do
print *, i
end do
!$omp end do
end subroutine


subroutine do_nonrectangular_lb
integer i, j

!ERROR: Trip count must be computable and invariant
!$omp do collapse(2)
do i = 1, 10
do j = i, 10
print *, i, j
end do
end do
!$omp end do
end subroutine


subroutine do_nonrectangular_ub
integer i, j

!ERROR: Trip count must be computable and invariant
!$omp do collapse(2)
do i = 1, 10
do j = 0, i
print *, i, j
end do
end do
!$omp end do
end subroutine


subroutine do_nonrectangular_step
integer i, j

!ERROR: Trip count must be computable and invariant
!$omp do collapse(2)
do i = 1, 10
do j = 1, 10, i
print *, i, j
end do
end do
!$omp end do
end subroutine
Loading
Loading