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
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
29 changes: 29 additions & 0 deletions mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,35 @@ class OpenMP_NumTeamsClauseSkip<

def OpenMP_NumTeamsClause : OpenMP_NumTeamsClauseSkip<>;

//===----------------------------------------------------------------------===//
// V5.1: [10.1.2] `sizes` clause
//===----------------------------------------------------------------------===//

class OpenMP_SizesClauseSkip<
bit traits = false, bit arguments = false, bit assemblyFormat = false,
bit description = false, bit extraClassDeclaration = false
> : OpenMP_Clause<traits, arguments, assemblyFormat, description,
extraClassDeclaration> {
let arguments = (ins
Variadic<IntLikeType>:$sizes
);

let optAssemblyFormat = [{
`sizes` `(` $sizes `:` type($sizes) `)`
}];

let description = [{
The `sizes` clauses defines the size of a grid over a multi-dimensional
logical iteration space. This grid is used for loop transformations such as
`tile` and `strip`. The size per dimension can be a variable, but only
values that are not at least 2 make sense. It is not specified what happens
when smaller values are used, but should still result in a loop nest that
executes each logical iteration once.
}];
}

def OpenMP_SizesClause : OpenMP_SizesClauseSkip<>;

//===----------------------------------------------------------------------===//
// V5.2: [10.1.2] `num_threads` clause
//===----------------------------------------------------------------------===//
Expand Down
69 changes: 67 additions & 2 deletions mlir/include/mlir/Dialect/OpenMP/OpenMPOpBase.td
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,44 @@ def OpenMP_MapBoundsType : OpenMP_Type<"MapBounds", "map_bounds_ty"> {
let summary = "Type for representing omp map clause bounds information";
}

//===---------------------------------------------------------------------===//
// OpenMP Canonical Loop Info Type
//===---------------------------------------------------------------------===//

def CanonicalLoopInfoType : OpenMP_Type<"CanonicalLoopInfo", "cli"> {
let summary = "Type for representing a reference to a canonical loop";
let description = [{
A variable of type CanonicalLoopInfo refers to an OpenMP-compatible
canonical loop in the same function. Values of this type are not
available at runtime and therefore cannot be used by the program itself,
i.e. an opaque type. It is similar to the transform dialect's
`!transform.interface` type, but instead of implementing an interface
for each transformation, the OpenMP dialect itself defines possible
operations on this type.

A value of type CanonicalLoopInfoType (in the following: CLI) value can be

1. created by omp.new_cli.
2. passed to omp.canonical_loop to associate the loop to that CLI. A CLI
can only be associated once.
3. passed to an omp loop transformation operation that modifies the loop
associated with the CLI. The CLI is the "applyee" and the operation is
the consumer. A CLI can only be consumed once.
4. passed to an omp loop transformation operation to associate the cli with
a result of that transformation. The CLI is the "generatee" and the
operation is the generator.

A CLI cannot

1. be returned from a function.
2. be passed to operations that are not specifically designed to take a
CanonicalLoopInfoType, including AnyType.

A CLI directly corresponds to an object of
OpenMPIRBuilder's CanonicalLoopInfo struct when lowering to LLVM-IR.
}];
}

//===----------------------------------------------------------------------===//
// Base classes for OpenMP dialect operations.
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -211,8 +249,35 @@ class OpenMP_Op<string mnemonic, list<Trait> traits = [],
// Doesn't actually create a C++ base class (only defines default values for
// tablegen classes that derive from this). Use LoopTransformationInterface
// instead for common operations.
class OpenMPTransform_Op<string mnemonic, list<Trait> traits = []> :
OpenMP_Op<mnemonic, !listconcat([DeclareOpInterfaceMethods<LoopTransformationInterface>], traits) > {
class OpenMPTransform_Op<string mnemonic,
list<Trait> traits = [],
list<OpenMP_Clause> clauses = []> :
OpenMP_Op<mnemonic,
traits = !listconcat([DeclareOpInterfaceMethods<LoopTransformationInterface>], traits),
clauses = clauses> {
}

// Base clause for loop transformations using the standard syntax.
//
// omp.opname ($generatees) <- ($applyees) clause(...) clause(...) ... <attr-dicr>
// omp.opname ($applyees) clause(...) clause(...) ... <attr-dict>
//
// $generatees is optional and is assumed to be empty if omitted
class OpenMPTransformBase_Op<string mnemonic,
list<Trait> traits = [],
list<OpenMP_Clause> clauses = []> :
OpenMPTransform_Op<mnemonic,
traits = !listconcat(traits, [AttrSizedOperandSegments]),
clauses = clauses> {

let arguments = !con(
(ins Variadic<CanonicalLoopInfoType>:$generatees,
Variadic<CanonicalLoopInfoType>:$applyees
), clausesArgs);

let assemblyFormat = [{ custom<LoopTransformClis>($generatees, $applyees) }]
# clausesAssemblyFormat
# [{ attr-dict }];
}

#endif // OPENMP_OP_BASE
Loading