Skip to content

Commit 4165254

Browse files
committed
[flang][OpenMP] Semantic checks for DOACROSS clause
Keep track of loop constructs and OpenMP loop constructs that have been entered. Use the information to validate the variables in the SINK loop iteration vector.
1 parent c4a850e commit 4165254

File tree

8 files changed

+230
-30
lines changed

8 files changed

+230
-30
lines changed

flang/lib/Lower/OpenMP/Clauses.cpp

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -574,20 +574,17 @@ Defaultmap make(const parser::OmpClause::Defaultmap &inp,
574574
/*VariableCategory=*/maybeApply(convert2, t1)}};
575575
}
576576

577-
Depend make(const parser::OmpClause::Depend &inp,
578-
semantics::SemanticsContext &semaCtx) {
579-
// inp.v -> parser::OmpDependClause
580-
using wrapped = parser::OmpDependClause;
581-
using Variant = decltype(Depend::u);
577+
Doacross makeDoacross(const parser::OmpDoacross &doa,
578+
semantics::SemanticsContext &semaCtx) {
582579
// Iteration is the equivalent of parser::OmpIteration
583580
using Iteration = Doacross::Vector::value_type; // LoopIterationT
584581

585-
auto visitSource = [&](const parser::OmpDoacross::Source &) -> Variant {
582+
auto visitSource = [&](const parser::OmpDoacross::Source &) {
586583
return Doacross{{/*DependenceType=*/Doacross::DependenceType::Source,
587584
/*Vector=*/{}}};
588585
};
589586

590-
auto visitSink = [&](const parser::OmpDoacross::Sink &s) -> Variant {
587+
auto visitSink = [&](const parser::OmpDoacross::Sink &s) {
591588
using IterOffset = parser::OmpIterationOffset;
592589
auto convert2 = [&](const parser::OmpIteration &v) {
593590
auto &t0 = std::get<parser::Name>(v.t);
@@ -605,6 +602,15 @@ Depend make(const parser::OmpClause::Depend &inp,
605602
/*Vector=*/makeList(s.v.v, convert2)}};
606603
};
607604

605+
return common::visit(common::visitors{visitSink, visitSource}, doa.u);
606+
}
607+
608+
Depend make(const parser::OmpClause::Depend &inp,
609+
semantics::SemanticsContext &semaCtx) {
610+
// inp.v -> parser::OmpDependClause
611+
using wrapped = parser::OmpDependClause;
612+
using Variant = decltype(Depend::u);
613+
608614
auto visitTaskDep = [&](const wrapped::TaskDep &s) -> Variant {
609615
auto &t0 = std::get<std::optional<parser::OmpIteratorModifier>>(s.t);
610616
auto &t1 = std::get<parser::OmpTaskDependenceType>(s.t);
@@ -617,11 +623,11 @@ Depend make(const parser::OmpClause::Depend &inp,
617623
/*LocatorList=*/makeObjects(t2, semaCtx)}};
618624
};
619625

620-
return Depend{Fortran::common::visit( //
626+
return Depend{common::visit( //
621627
common::visitors{
622628
// Doacross
623629
[&](const parser::OmpDoacross &s) -> Variant {
624-
return common::visit(common::visitors{visitSink, visitSource}, s.u);
630+
return makeDoacross(s, semaCtx);
625631
},
626632
// Depend::TaskDep
627633
visitTaskDep,
@@ -692,8 +698,8 @@ DistSchedule make(const parser::OmpClause::DistSchedule &inp,
692698

693699
Doacross make(const parser::OmpClause::Doacross &inp,
694700
semantics::SemanticsContext &semaCtx) {
695-
// inp -> empty
696-
llvm_unreachable("Empty: doacross");
701+
// inp.v -> OmpDoacrossClause
702+
return makeDoacross(inp.v.v, semaCtx);
697703
}
698704

699705
// DynamicAllocators: empty

flang/lib/Semantics/check-omp-structure.cpp

Lines changed: 132 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ void OmpStructureChecker::Leave(const parser::OpenMPConstruct &) {
541541
}
542542

543543
void OmpStructureChecker::Enter(const parser::OpenMPLoopConstruct &x) {
544+
loopStack_.push_back(&x);
544545
const auto &beginLoopDir{std::get<parser::OmpBeginLoopDirective>(x.t)};
545546
const auto &beginDir{std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
546547

@@ -933,11 +934,19 @@ void OmpStructureChecker::CheckDistLinear(
933934
}
934935
}
935936

936-
void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &) {
937+
void OmpStructureChecker::Leave(const parser::OpenMPLoopConstruct &x) {
937938
if (llvm::omp::allSimdSet.test(GetContext().directive)) {
938939
ExitDirectiveNest(SIMDNest);
939940
}
940941
dirContext_.pop_back();
942+
943+
assert(!loopStack_.empty() && "Expecting non-empty loop stack");
944+
const LoopConstruct &top = loopStack_.back();
945+
#ifndef NDEBUG
946+
auto *loopc = std::get_if<const parser::OpenMPLoopConstruct *>(&top);
947+
assert(loopc != nullptr && *loopc == &x && "Mismatched loop constructs");
948+
#endif
949+
loopStack_.pop_back();
941950
}
942951

943952
void OmpStructureChecker::Enter(const parser::OmpEndLoopDirective &x) {
@@ -1068,8 +1077,7 @@ void OmpStructureChecker::Leave(const parser::OpenMPBlockConstruct &) {
10681077
void OmpStructureChecker::ChecksOnOrderedAsBlock() {
10691078
if (FindClause(llvm::omp::Clause::OMPC_depend)) {
10701079
context_.Say(GetContext().clauseSource,
1071-
"DEPEND(*) clauses are not allowed when ORDERED construct is a block"
1072-
" construct with an ORDERED region"_err_en_US);
1080+
"DEPEND clauses are not allowed when ORDERED construct is a block construct with an ORDERED region"_err_en_US);
10731081
return;
10741082
}
10751083

@@ -1618,7 +1626,8 @@ void OmpStructureChecker::CheckBarrierNesting(
16181626
void OmpStructureChecker::ChecksOnOrderedAsStandalone() {
16191627
if (FindClause(llvm::omp::Clause::OMPC_threads) ||
16201628
FindClause(llvm::omp::Clause::OMPC_simd)) {
1621-
context_.Say(GetContext().clauseSource,
1629+
context_.Say(
1630+
GetContext().clauseSource,
16221631
"THREADS, SIMD clauses are not allowed when ORDERED construct is a "
16231632
"standalone construct with no ORDERED region"_err_en_US);
16241633
}
@@ -1645,8 +1654,9 @@ void OmpStructureChecker::ChecksOnOrderedAsStandalone() {
16451654
}
16461655
};
16471656

1648-
auto clauseAll = FindClauses(llvm::omp::Clause::OMPC_depend);
1649-
for (auto itr = clauseAll.first; itr != clauseAll.second; ++itr) {
1657+
// Visit the DEPEND and DOACROSS clauses.
1658+
auto depClauses = FindClauses(llvm::omp::Clause::OMPC_depend);
1659+
for (auto itr = depClauses.first; itr != depClauses.second; ++itr) {
16501660
const auto &dependClause{
16511661
std::get<parser::OmpClause::Depend>(itr->second->u)};
16521662
if (auto *doAcross{std::get_if<parser::OmpDoacross>(&dependClause.v.u)}) {
@@ -1656,6 +1666,11 @@ void OmpStructureChecker::ChecksOnOrderedAsStandalone() {
16561666
"Only SINK or SOURCE dependence types are allowed when ORDERED construct is a standalone construct with no ORDERED region"_err_en_US);
16571667
}
16581668
}
1669+
auto doaClauses = FindClauses(llvm::omp::Clause::OMPC_doacross);
1670+
for (auto itr = doaClauses.first; itr != doaClauses.second; ++itr) {
1671+
auto &doaClause{std::get<parser::OmpClause::Doacross>(itr->second->u)};
1672+
visitDoacross(doaClause.v.v, itr->second->source);
1673+
}
16591674

16601675
bool isNestedInDoOrderedWithPara{false};
16611676
if (CurrentDirectiveIsNested() &&
@@ -1693,13 +1708,18 @@ void OmpStructureChecker::CheckOrderedDependClause(
16931708
}
16941709
}
16951710
};
1696-
auto clauseAll{FindClauses(llvm::omp::Clause::OMPC_depend)};
1697-
for (auto itr = clauseAll.first; itr != clauseAll.second; ++itr) {
1711+
auto depClauses{FindClauses(llvm::omp::Clause::OMPC_depend)};
1712+
for (auto itr = depClauses.first; itr != depClauses.second; ++itr) {
16981713
auto &dependClause{std::get<parser::OmpClause::Depend>(itr->second->u)};
16991714
if (auto *doAcross{std::get_if<parser::OmpDoacross>(&dependClause.v.u)}) {
17001715
visitDoacross(*doAcross, itr->second->source);
17011716
}
17021717
}
1718+
auto doaClauses = FindClauses(llvm::omp::Clause::OMPC_doacross);
1719+
for (auto itr = doaClauses.first; itr != doaClauses.second; ++itr) {
1720+
auto &doaClause{std::get<parser::OmpClause::Doacross>(itr->second->u)};
1721+
visitDoacross(doaClause.v.v, itr->second->source);
1722+
}
17031723
}
17041724

17051725
void OmpStructureChecker::CheckTargetUpdate() {
@@ -2677,7 +2697,6 @@ CHECK_SIMPLE_CLAUSE(Bind, OMPC_bind)
26772697
CHECK_SIMPLE_CLAUSE(Align, OMPC_align)
26782698
CHECK_SIMPLE_CLAUSE(Compare, OMPC_compare)
26792699
CHECK_SIMPLE_CLAUSE(CancellationConstructType, OMPC_cancellation_construct_type)
2680-
CHECK_SIMPLE_CLAUSE(Doacross, OMPC_doacross)
26812700
CHECK_SIMPLE_CLAUSE(OmpxAttribute, OMPC_ompx_attribute)
26822701
CHECK_SIMPLE_CLAUSE(OmpxBare, OMPC_ompx_bare)
26832702
CHECK_SIMPLE_CLAUSE(Fail, OMPC_fail)
@@ -3458,6 +3477,7 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Depend &x) {
34583477
"Unexpected alternative in update clause");
34593478

34603479
if (doaDep) {
3480+
CheckDoacross(*doaDep);
34613481
CheckDependenceType(doaDep->GetDepType());
34623482
} else {
34633483
CheckTaskDependenceType(taskDep->GetTaskDepType());
@@ -3537,6 +3557,93 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Depend &x) {
35373557
}
35383558
}
35393559

3560+
void OmpStructureChecker::Enter(const parser::OmpClause::Doacross &x) {
3561+
CheckAllowedClause(llvm::omp::Clause::OMPC_doacross);
3562+
CheckDoacross(x.v.v);
3563+
}
3564+
3565+
void OmpStructureChecker::CheckDoacross(const parser::OmpDoacross &doa) {
3566+
if (std::holds_alternative<parser::OmpDoacross::Source>(doa.u)) {
3567+
// Nothing to check here.
3568+
return;
3569+
}
3570+
3571+
// Process SINK dependence type. SINK may only appear in an ORDER construct,
3572+
// which references a prior ORDERED(n) clause on a DO or SIMD construct
3573+
// that marks the top of the loop nest.
3574+
3575+
auto &sink{std::get<parser::OmpDoacross::Sink>(doa.u)};
3576+
const std::list<parser::OmpIteration> &vec{sink.v.v};
3577+
3578+
// Check if the variables in the iteration vector are unique.
3579+
struct Less {
3580+
bool operator()(
3581+
const parser::OmpIteration *a, const parser::OmpIteration *b) const {
3582+
auto namea{std::get<parser::Name>(a->t)};
3583+
auto nameb{std::get<parser::Name>(b->t)};
3584+
assert(namea.symbol && nameb.symbol && "Unresolved symbols");
3585+
// The non-determinism of the "<" doesn't matter, we only care about
3586+
// equality, i.e. a == b <=> !(a < b) && !(b < a)
3587+
return reinterpret_cast<uintptr_t>(namea.symbol) <
3588+
reinterpret_cast<uintptr_t>(nameb.symbol);
3589+
}
3590+
};
3591+
if (auto *duplicate{FindDuplicateEntry<parser::OmpIteration, Less>(vec)}) {
3592+
auto name{std::get<parser::Name>(duplicate->t)};
3593+
context_.Say(name.source,
3594+
"Duplicate variable '%s' in the iteration vector"_err_en_US,
3595+
name.ToString());
3596+
}
3597+
3598+
// Check if the variables in the iteration vector are induction variables.
3599+
// Ignore any mismatch between the size of the iteration vector and the
3600+
// number of DO constructs on the stack. This is checked elsewhere.
3601+
3602+
auto GetLoopDirective{[](const parser::OpenMPLoopConstruct &x) {
3603+
auto &begin{std::get<parser::OmpBeginLoopDirective>(x.t)};
3604+
return std::get<parser::OmpLoopDirective>(begin.t).v;
3605+
}};
3606+
auto GetLoopClauses{[](const parser::OpenMPLoopConstruct &x)
3607+
-> const std::list<parser::OmpClause> & {
3608+
auto &begin{std::get<parser::OmpBeginLoopDirective>(x.t)};
3609+
return std::get<parser::OmpClauseList>(begin.t).v;
3610+
}};
3611+
3612+
std::set<const Symbol *> inductionVars;
3613+
for (const LoopConstruct &loop : llvm::reverse(loopStack_)) {
3614+
if (auto *doc{std::get_if<const parser::DoConstruct *>(&loop)}) {
3615+
// Do-construct, collect the induction variable.
3616+
if (auto &control{(*doc)->GetLoopControl()}) {
3617+
if (auto *b{std::get_if<parser::LoopControl::Bounds>(&control->u)}) {
3618+
inductionVars.insert(b->name.thing.symbol);
3619+
}
3620+
}
3621+
} else {
3622+
// Omp-loop-construct, check if it's do/simd with an ORDERED clause.
3623+
auto *loopc{std::get_if<const parser::OpenMPLoopConstruct *>(&loop)};
3624+
assert(loopc && "Expecting OpenMPLoopConstruct");
3625+
llvm::omp::Directive loopDir{GetLoopDirective(**loopc)};
3626+
if (loopDir == llvm::omp::OMPD_do || loopDir == llvm::omp::OMPD_simd) {
3627+
auto IsOrdered{[](const parser::OmpClause &c) {
3628+
return c.Id() == llvm::omp::OMPC_ordered;
3629+
}};
3630+
// If it has ORDERED clause, stop the traversal.
3631+
if (llvm::any_of(GetLoopClauses(**loopc), IsOrdered)) {
3632+
break;
3633+
}
3634+
}
3635+
}
3636+
}
3637+
for (const parser::OmpIteration &iter : vec) {
3638+
auto &name{std::get<parser::Name>(iter.t)};
3639+
if (!inductionVars.count(name.symbol)) {
3640+
context_.Say(name.source,
3641+
"The iteration vector element '%s' is not an induction variable within the ORDERED loop nest"_err_en_US,
3642+
name.ToString());
3643+
}
3644+
}
3645+
}
3646+
35403647
void OmpStructureChecker::CheckCopyingPolymorphicAllocatable(
35413648
SymbolSourceMap &symbols, const llvm::omp::Clause clause) {
35423649
if (context_.ShouldWarn(common::UsageWarning::Portability)) {
@@ -4291,6 +4398,22 @@ void OmpStructureChecker::Enter(
42914398
CheckAllowedRequiresClause(llvm::omp::Clause::OMPC_unified_shared_memory);
42924399
}
42934400

4401+
void OmpStructureChecker::Enter(const parser::DoConstruct &x) {
4402+
Base::Enter(x);
4403+
loopStack_.push_back(&x);
4404+
}
4405+
4406+
void OmpStructureChecker::Leave(const parser::DoConstruct &x) {
4407+
assert(!loopStack_.empty() && "Expecting non-empty loop stack");
4408+
const LoopConstruct &top = loopStack_.back();
4409+
#ifndef NDEBUG
4410+
auto *doc = std::get_if<const parser::DoConstruct *>(&top);
4411+
assert(doc != nullptr && *doc == &x && "Mismatched loop constructs");
4412+
#endif
4413+
loopStack_.pop_back();
4414+
Base::Leave(x);
4415+
}
4416+
42944417
void OmpStructureChecker::CheckAllowedRequiresClause(llvmOmpClause clause) {
42954418
CheckAllowedClause(clause);
42964419

flang/lib/Semantics/check-omp-structure.h

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ class OmpStructureChecker
6060
: public DirectiveStructureChecker<llvm::omp::Directive, llvm::omp::Clause,
6161
parser::OmpClause, llvm::omp::Clause_enumSize> {
6262
public:
63+
using Base = DirectiveStructureChecker<llvm::omp::Directive,
64+
llvm::omp::Clause, parser::OmpClause, llvm::omp::Clause_enumSize>;
65+
6366
OmpStructureChecker(SemanticsContext &context)
6467
: DirectiveStructureChecker(context,
6568
#define GEN_FLANG_DIRECTIVE_CLAUSE_MAP
@@ -131,6 +134,9 @@ class OmpStructureChecker
131134
void Enter(const parser::OmpAtomicCapture &);
132135
void Leave(const parser::OmpAtomic &);
133136

137+
void Enter(const parser::DoConstruct &);
138+
void Leave(const parser::DoConstruct &);
139+
134140
#define GEN_FLANG_CLAUSE_CHECK_ENTER
135141
#include "llvm/Frontend/OpenMP/OMP.inc"
136142

@@ -156,13 +162,19 @@ class OmpStructureChecker
156162
const parser::OmpScheduleModifierType::ModType &);
157163
void CheckAllowedMapTypes(const parser::OmpMapClause::Type &,
158164
const std::list<parser::OmpMapClause::Type> &);
159-
template <typename T> const T *FindDuplicateEntry(const std::list<T> &);
160165
llvm::StringRef getClauseName(llvm::omp::Clause clause) override;
161166
llvm::StringRef getDirectiveName(llvm::omp::Directive directive) override;
162167

168+
template <typename T> struct DefaultLess {
169+
bool operator()(const T *a, const T *b) const { return *a < *b; }
170+
};
171+
template <typename T, typename Less = DefaultLess<T>>
172+
const T *FindDuplicateEntry(const std::list<T> &);
173+
163174
void CheckDependList(const parser::DataRef &);
164175
void CheckDependArraySection(
165176
const common::Indirection<parser::ArrayElement> &, const parser::Name &);
177+
void CheckDoacross(const parser::OmpDoacross &doa);
166178
bool IsDataRefTypeParamInquiry(const parser::DataRef *dataRef);
167179
void CheckIsVarPartOfAnotherVar(const parser::CharBlock &source,
168180
const parser::OmpObjectList &objList, llvm::StringRef clause = "");
@@ -254,20 +266,21 @@ class OmpStructureChecker
254266
int directiveNest_[LastType + 1] = {0};
255267

256268
SymbolSourceMap deferredNonVariables_;
269+
270+
using LoopConstruct = std::variant<const parser::DoConstruct *,
271+
const parser::OpenMPLoopConstruct *>;
272+
std::vector<LoopConstruct> loopStack_;
257273
};
258274

259-
template <typename T>
275+
template <typename T, typename Less>
260276
const T *OmpStructureChecker::FindDuplicateEntry(const std::list<T> &list) {
261277
// Add elements of the list to a set. If the insertion fails, return
262278
// the address of the failing element.
263279

264280
// The objects of type T may not be copyable, so add their addresses
265281
// to the set. The set will need to compare the actual objects, so
266282
// the custom comparator is provided.
267-
struct less {
268-
bool operator()(const T *a, const T *b) const { return *a < *b; }
269-
};
270-
std::set<const T *, less> uniq;
283+
std::set<const T *, Less> uniq;
271284

272285
for (const T &item : list) {
273286
if (!uniq.insert(&item).second) {

flang/lib/Semantics/resolve-directives.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,8 +554,16 @@ class OmpAttributeVisitor : DirectiveAttributeVisitor<llvm::omp::Directive> {
554554
}
555555

556556
void Post(const parser::OmpIteration &x) {
557-
const auto &name{std::get<parser::Name>(x.t)};
558-
ResolveName(&name);
557+
if (const auto &name{std::get<parser::Name>(x.t)}; !name.symbol) {
558+
auto *symbol{currScope().FindSymbol(name.source)};
559+
if (!symbol) {
560+
// OmpIteration must use an existing object. If there isn't one,
561+
// create a fake one and flag an error later.
562+
symbol = &currScope().MakeSymbol(
563+
name.source, Attrs{}, EntityDetails(/*isDummy=*/true));
564+
}
565+
Resolve(name, symbol);
566+
}
559567
}
560568

561569
bool Pre(const parser::OmpClause::UseDevicePtr &x) {

0 commit comments

Comments
 (0)