aboutsummaryrefslogtreecommitdiff
path: root/flang/lib/Semantics/check-omp-structure.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'flang/lib/Semantics/check-omp-structure.cpp')
-rw-r--r--flang/lib/Semantics/check-omp-structure.cpp195
1 files changed, 98 insertions, 97 deletions
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 8264e1d..a9c56c3 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -16,7 +16,6 @@
#include "flang/Common/idioms.h"
#include "flang/Common/indirection.h"
#include "flang/Common/visit.h"
-#include "flang/Evaluate/shape.h"
#include "flang/Evaluate/tools.h"
#include "flang/Evaluate/type.h"
#include "flang/Parser/char-block.h"
@@ -499,7 +498,7 @@ template <typename Checker> struct DirectiveSpellingVisitor {
template <typename... Ts>
static const parser::OmpDirectiveName &GetDirName(
const std::tuple<Ts...> &t) {
- return std::get<parser::OmpDirectiveSpecification>(t).DirName();
+ return std::get<parser::OmpBeginDirective>(t).DirName();
}
bool Pre(const parser::OmpSectionsDirective &x) {
@@ -589,12 +588,14 @@ template <typename Checker> struct DirectiveSpellingVisitor {
checker_(std::get<parser::Verbatim>(x.t).source, Directive::OMPD_requires);
return false;
}
-
- bool Pre(const parser::OmpBlockDirective &x) {
- checker_(x.source, x.v);
+ bool Pre(const parser::OmpBeginDirective &x) {
+ checker_(x.DirName().source, x.DirId());
+ return false;
+ }
+ bool Pre(const parser::OmpEndDirective &x) {
+ checker_(x.DirName().source, x.DirId());
return false;
}
-
bool Pre(const parser::OmpLoopDirective &x) {
checker_(x.source, x.v);
return false;
@@ -727,22 +728,22 @@ void OmpStructureChecker::CheckTargetNest(const parser::OpenMPConstruct &c) {
// 2.12.5 Target Construct Restriction
bool eligibleTarget{true};
llvm::omp::Directive ineligibleTargetDir;
+ parser::CharBlock source;
common::visit(
common::visitors{
[&](const parser::OpenMPBlockConstruct &c) {
- const auto &beginBlockDir{
- std::get<parser::OmpBeginBlockDirective>(c.t)};
- const auto &beginDir{
- std::get<parser::OmpBlockDirective>(beginBlockDir.t)};
- if (beginDir.v == llvm::omp::Directive::OMPD_target_data) {
+ const parser::OmpDirectiveSpecification &beginSpec{c.BeginDir()};
+ source = beginSpec.DirName().source;
+ if (beginSpec.DirId() == llvm::omp::Directive::OMPD_target_data) {
eligibleTarget = false;
- ineligibleTargetDir = beginDir.v;
+ ineligibleTargetDir = beginSpec.DirId();
}
},
[&](const parser::OpenMPStandaloneConstruct &c) {
common::visit(
common::visitors{
[&](const parser::OpenMPSimpleStandaloneConstruct &c) {
+ source = c.v.DirName().source;
switch (llvm::omp::Directive dirId{c.v.DirId()}) {
case llvm::omp::Directive::OMPD_target_update:
case llvm::omp::Directive::OMPD_target_enter_data:
@@ -763,6 +764,7 @@ void OmpStructureChecker::CheckTargetNest(const parser::OpenMPConstruct &c) {
std::get<parser::OmpBeginLoopDirective>(c.t)};
const auto &beginDir{
std::get<parser::OmpLoopDirective>(beginLoopDir.t)};
+ source = beginLoopDir.source;
if (llvm::omp::allTargetSet.test(beginDir.v)) {
eligibleTarget = false;
ineligibleTargetDir = beginDir.v;
@@ -772,8 +774,7 @@ void OmpStructureChecker::CheckTargetNest(const parser::OpenMPConstruct &c) {
},
c.u);
if (!eligibleTarget) {
- context_.Warn(common::UsageWarning::OpenMPUsage,
- parser::FindSourceLocation(c),
+ context_.Warn(common::UsageWarning::OpenMPUsage, source,
"If %s directive is nested inside TARGET region, the behaviour is unspecified"_port_en_US,
parser::ToUpperCaseLetters(
getDirectiveName(ineligibleTargetDir).str()));
@@ -781,22 +782,18 @@ void OmpStructureChecker::CheckTargetNest(const parser::OpenMPConstruct &c) {
}
void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
- const auto &beginBlockDir{std::get<parser::OmpBeginBlockDirective>(x.t)};
- const auto &endBlockDir{std::get<parser::OmpEndBlockDirective>(x.t)};
- const auto &beginDir{std::get<parser::OmpBlockDirective>(beginBlockDir.t)};
- const auto &endDir{std::get<parser::OmpBlockDirective>(endBlockDir.t)};
+ const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
+ const std::optional<parser::OmpEndDirective> &endSpec{x.EndDir()};
const parser::Block &block{std::get<parser::Block>(x.t)};
- CheckMatching<parser::OmpBlockDirective>(beginDir, endDir);
-
- PushContextAndClauseSets(beginDir.source, beginDir.v);
+ PushContextAndClauseSets(beginSpec.DirName().source, beginSpec.DirId());
if (llvm::omp::allTargetSet.test(GetContext().directive)) {
EnterDirectiveNest(TargetNest);
}
if (CurrentDirectiveIsNested()) {
if (llvm::omp::bottomTeamsSet.test(GetContextParent().directive)) {
- HasInvalidTeamsNesting(beginDir.v, beginDir.source);
+ HasInvalidTeamsNesting(beginSpec.DirId(), beginSpec.source);
}
if (GetContext().directive == llvm::omp::Directive::OMPD_master) {
CheckMasterNesting(x);
@@ -805,7 +802,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
// region or a target region.
if (GetContext().directive == llvm::omp::Directive::OMPD_teams &&
GetContextParent().directive != llvm::omp::Directive::OMPD_target) {
- context_.Say(parser::FindSourceLocation(x),
+ context_.Say(x.BeginDir().DirName().source,
"%s region can only be strictly nested within the implicit parallel "
"region or TARGET region"_err_en_US,
ContextDirectiveAsFortran());
@@ -822,12 +819,12 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
}
}
- CheckNoBranching(block, beginDir.v, beginDir.source);
+ CheckNoBranching(block, beginSpec.DirId(), beginSpec.source);
// Target block constructs are target device constructs. Keep track of
// whether any such construct has been visited to later check that REQUIRES
// directives for target-related options don't appear after them.
- if (llvm::omp::allTargetSet.test(beginDir.v)) {
+ if (llvm::omp::allTargetSet.test(beginSpec.DirId())) {
deviceConstructFound_ = true;
}
@@ -837,14 +834,14 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
bool foundNowait{false};
parser::CharBlock NowaitSource;
- auto catchCopyPrivateNowaitClauses = [&](const auto &dir, bool endDir) {
- for (auto &clause : std::get<parser::OmpClauseList>(dir.t).v) {
+ auto catchCopyPrivateNowaitClauses = [&](const auto &dirSpec, bool isEnd) {
+ for (auto &clause : dirSpec.Clauses().v) {
if (clause.Id() == llvm::omp::Clause::OMPC_copyprivate) {
for (const auto &ompObject : GetOmpObjectList(clause)->v) {
const auto *name{parser::Unwrap<parser::Name>(ompObject)};
if (Symbol * symbol{name->symbol}) {
if (singleCopyprivateSyms.count(symbol)) {
- if (endDir) {
+ if (isEnd) {
context_.Warn(common::UsageWarning::OpenMPUsage, name->source,
"The COPYPRIVATE clause with '%s' is already used on the SINGLE directive"_warn_en_US,
name->ToString());
@@ -858,7 +855,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
"'%s' appears in more than one COPYPRIVATE clause on the END SINGLE directive"_err_en_US,
name->ToString());
} else {
- if (endDir) {
+ if (isEnd) {
endSingleCopyprivateSyms.insert(symbol);
} else {
singleCopyprivateSyms.insert(symbol);
@@ -871,7 +868,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
context_.Say(clause.source,
"At most one NOWAIT clause can appear on the SINGLE directive"_err_en_US);
} else {
- foundNowait = !endDir;
+ foundNowait = !isEnd;
}
if (!NowaitSource.ToString().size()) {
NowaitSource = clause.source;
@@ -879,8 +876,10 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
}
}
};
- catchCopyPrivateNowaitClauses(beginBlockDir, false);
- catchCopyPrivateNowaitClauses(endBlockDir, true);
+ catchCopyPrivateNowaitClauses(beginSpec, false);
+ if (endSpec) {
+ catchCopyPrivateNowaitClauses(*endSpec, true);
+ }
unsigned version{context_.langOptions().OpenMPVersion};
if (version <= 52 && NowaitSource.ToString().size() &&
(singleCopyprivateSyms.size() || endSingleCopyprivateSyms.size())) {
@@ -889,7 +888,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
}
}
- switch (beginDir.v) {
+ switch (beginSpec.DirId()) {
case llvm::omp::Directive::OMPD_target:
if (CheckTargetBlockOnlyTeams(block)) {
EnterDirectiveNest(TargetBlockOnlyTeams);
@@ -897,27 +896,25 @@ void OmpStructureChecker::Enter(const parser::OpenMPBlockConstruct &x) {
break;
case llvm::omp::OMPD_workshare:
case llvm::omp::OMPD_parallel_workshare:
- CheckWorkshareBlockStmts(block, beginDir.source);
+ CheckWorkshareBlockStmts(block, beginSpec.source);
HasInvalidWorksharingNesting(
- beginDir.source, llvm::omp::nestedWorkshareErrSet);
+ beginSpec.source, llvm::omp::nestedWorkshareErrSet);
break;
case llvm::omp::Directive::OMPD_scope:
case llvm::omp::Directive::OMPD_single:
// TODO: This check needs to be extended while implementing nesting of
// regions checks.
HasInvalidWorksharingNesting(
- beginDir.source, llvm::omp::nestedWorkshareErrSet);
+ beginSpec.source, llvm::omp::nestedWorkshareErrSet);
break;
- case llvm::omp::Directive::OMPD_task: {
- const auto &clauses{std::get<parser::OmpClauseList>(beginBlockDir.t)};
- for (const auto &clause : clauses.v) {
+ case llvm::omp::Directive::OMPD_task:
+ for (const auto &clause : beginSpec.Clauses().v) {
if (std::get_if<parser::OmpClause::Untied>(&clause.u)) {
OmpUnitedTaskDesignatorChecker check{context_};
parser::Walk(block, check);
}
}
break;
- }
default:
break;
}
@@ -930,7 +927,7 @@ void OmpStructureChecker::CheckMasterNesting(
// TODO: Expand the check to include `LOOP` construct as well when it is
// supported.
if (IsCloselyNestedRegion(llvm::omp::nestedMasterErrSet)) {
- context_.Say(parser::FindSourceLocation(x),
+ context_.Say(x.BeginDir().source,
"`MASTER` region may not be closely nested inside of `WORKSHARING`, "
"`LOOP`, `TASK`, `TASKLOOP`,"
" or `ATOMIC` region."_err_en_US);
@@ -1030,7 +1027,7 @@ void OmpStructureChecker::ChecksOnOrderedAsBlock() {
}
}
-void OmpStructureChecker::Leave(const parser::OmpBeginBlockDirective &) {
+void OmpStructureChecker::Leave(const parser::OmpBeginDirective &) {
switch (GetContext().directive) {
case llvm::omp::Directive::OMPD_ordered:
// [5.1] 2.19.9 Ordered Construct Restriction
@@ -1053,10 +1050,11 @@ void OmpStructureChecker::Enter(const parser::OpenMPSectionsConstruct &x) {
PushContextAndClauseSets(beginDir.source, beginDir.v);
AddEndDirectiveClauses(std::get<parser::OmpClauseList>(endSectionsDir.t));
- const auto &sectionBlocks{std::get<parser::OmpSectionBlocks>(x.t)};
- for (const parser::OpenMPConstruct &block : sectionBlocks.v) {
- CheckNoBranching(std::get<parser::OpenMPSectionConstruct>(block.u).v,
- beginDir.v, beginDir.source);
+ const auto &sectionBlocks{std::get<std::list<parser::OpenMPConstruct>>(x.t)};
+ for (const parser::OpenMPConstruct &construct : sectionBlocks) {
+ auto &section{std::get<parser::OpenMPSectionConstruct>(construct.u)};
+ CheckNoBranching(
+ std::get<parser::Block>(section.t), beginDir.v, beginDir.source);
}
HasInvalidWorksharingNesting(
beginDir.source, llvm::omp::nestedWorkshareErrSet);
@@ -1564,9 +1562,10 @@ void OmpStructureChecker::Leave(const parser::OpenMPDeclareTargetConstruct &x) {
},
[&](const parser::OmpClause::Enter &enterClause) {
enterClauseFound = true;
- CheckSymbolNames(dir.source, enterClause.v);
- CheckVarIsNotPartOfAnotherVar(dir.source, enterClause.v);
- CheckThreadprivateOrDeclareTargetVar(enterClause.v);
+ auto &objList{std::get<parser::OmpObjectList>(enterClause.v.t)};
+ CheckSymbolNames(dir.source, objList);
+ CheckVarIsNotPartOfAnotherVar(dir.source, objList);
+ CheckThreadprivateOrDeclareTargetVar(objList);
},
[&](const parser::OmpClause::DeviceType &deviceTypeClause) {
deviceTypeClauseFound = true;
@@ -1595,7 +1594,7 @@ void OmpStructureChecker::Enter(const parser::OmpErrorDirective &x) {
}
void OmpStructureChecker::Enter(const parser::OpenMPDispatchConstruct &x) {
- auto &dirSpec{std::get<parser::OmpDirectiveSpecification>(x.t)};
+ const parser::OmpDirectiveSpecification &dirSpec{x.BeginDir()};
const auto &block{std::get<parser::Block>(x.t)};
PushContextAndClauseSets(
dirSpec.DirName().source, llvm::omp::Directive::OMPD_dispatch);
@@ -1666,7 +1665,7 @@ void OmpStructureChecker::Leave(const parser::OpenMPExecutableAllocate &x) {
void OmpStructureChecker::Enter(const parser::OpenMPAllocatorsConstruct &x) {
isPredefinedAllocator = true;
- auto &dirSpec{std::get<parser::OmpDirectiveSpecification>(x.t)};
+ const parser::OmpDirectiveSpecification &dirSpec{x.BeginDir()};
auto &block{std::get<parser::Block>(x.t)};
PushContextAndClauseSets(
dirSpec.DirName().source, llvm::omp::Directive::OMPD_allocators);
@@ -1697,7 +1696,7 @@ void OmpStructureChecker::Enter(const parser::OpenMPAllocatorsConstruct &x) {
}
void OmpStructureChecker::Leave(const parser::OpenMPAllocatorsConstruct &x) {
- auto &dirSpec{std::get<parser::OmpDirectiveSpecification>(x.t)};
+ const parser::OmpDirectiveSpecification &dirSpec{x.BeginDir()};
for (const auto &clause : dirSpec.Clauses().v) {
if (const auto *allocClause{
@@ -1731,7 +1730,7 @@ void OmpStructureChecker::CheckBarrierNesting(
// TODO: Expand the check to include `LOOP` construct as well when it is
// supported.
if (IsCloselyNestedRegion(llvm::omp::nestedBarrierErrSet)) {
- context_.Say(parser::FindSourceLocation(x),
+ context_.Say(x.v.DirName().source,
"`BARRIER` region may not be closely nested inside of `WORKSHARING`, "
"`LOOP`, `TASK`, `TASKLOOP`,"
"`CRITICAL`, `ORDERED`, `ATOMIC` or `MASTER` region."_err_en_US);
@@ -2271,22 +2270,21 @@ void OmpStructureChecker::CheckCancellationNest(
}
}
-void OmpStructureChecker::Enter(const parser::OmpEndBlockDirective &x) {
- const auto &dir{std::get<parser::OmpBlockDirective>(x.t)};
- ResetPartialContext(dir.source);
- switch (dir.v) {
+void OmpStructureChecker::Enter(const parser::OmpEndDirective &x) {
+ parser::CharBlock source{x.DirName().source};
+ ResetPartialContext(source);
+ switch (x.DirId()) {
case llvm::omp::Directive::OMPD_scope:
- PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_end_scope);
+ PushContextAndClauseSets(source, llvm::omp::Directive::OMPD_end_scope);
break;
// 2.7.3 end-single-clause -> copyprivate-clause |
// nowait-clause
case llvm::omp::Directive::OMPD_single:
- PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_end_single);
+ PushContextAndClauseSets(source, llvm::omp::Directive::OMPD_end_single);
break;
// 2.7.4 end-workshare -> END WORKSHARE [nowait-clause]
case llvm::omp::Directive::OMPD_workshare:
- PushContextAndClauseSets(
- dir.source, llvm::omp::Directive::OMPD_end_workshare);
+ PushContextAndClauseSets(source, llvm::omp::Directive::OMPD_end_workshare);
break;
default:
// no clauses are allowed
@@ -2299,7 +2297,7 @@ void OmpStructureChecker::Enter(const parser::OmpEndBlockDirective &x) {
// constructs unless a nowait clause is specified. Only OMPD_end_single and
// end_workshareare popped as they are pushed while entering the
// EndBlockDirective.
-void OmpStructureChecker::Leave(const parser::OmpEndBlockDirective &x) {
+void OmpStructureChecker::Leave(const parser::OmpEndDirective &x) {
if ((GetContext().directive == llvm::omp::Directive::OMPD_end_scope) ||
(GetContext().directive == llvm::omp::Directive::OMPD_end_single) ||
(GetContext().directive == llvm::omp::Directive::OMPD_end_workshare)) {
@@ -4024,7 +4022,11 @@ void OmpStructureChecker::Enter(const parser::OmpClause::HasDeviceAddr &x) {
void OmpStructureChecker::Enter(const parser::OmpClause::Enter &x) {
CheckAllowedClause(llvm::omp::Clause::OMPC_enter);
- const parser::OmpObjectList &objList{x.v};
+ if (!OmpVerifyModifiers(
+ x.v, llvm::omp::OMPC_enter, GetContext().clauseSource, context_)) {
+ return;
+ }
+ const parser::OmpObjectList &objList{std::get<parser::OmpObjectList>(x.v.t)};
SymbolSourceMap symbols;
GetSymbolsInObjectList(objList, symbols);
for (const auto &[symbol, source] : symbols) {
@@ -4151,21 +4153,26 @@ void OmpStructureChecker::CheckArraySection(
// Detect this by looking for array accesses on character variables which are
// not arrays.
bool isSubstring{false};
- evaluate::ExpressionAnalyzer ea{context_};
- if (MaybeExpr expr = ea.Analyze(arrayElement.base)) {
- std::optional<evaluate::Shape> shape = evaluate::GetShape(expr);
- // Not an array: rank 0
- if (shape && shape->size() == 0) {
- if (std::optional<evaluate::DynamicType> type = expr->GetType()) {
- if (type->category() == evaluate::TypeCategory::Character) {
- // Substrings are explicitly denied by the standard [6.0:163:9-11].
- // This is supported as an extension. This restriction was added in
- // OpenMP 5.2.
- isSubstring = true;
- context_.Say(GetContext().clauseSource,
- "The use of substrings in OpenMP argument lists has been disallowed since OpenMP 5.2."_port_en_US);
- } else {
- llvm_unreachable("Array indexing on a variable that isn't an array");
+ // Cannot analyze a base of an assumed-size array on its own. If we know
+ // this is an array (assumed-size or not) we can ignore it, since we're
+ // looking for strings.
+ if (!IsAssumedSizeArray(*name.symbol)) {
+ evaluate::ExpressionAnalyzer ea{context_};
+ if (MaybeExpr expr = ea.Analyze(arrayElement.base)) {
+ if (expr->Rank() == 0) {
+ // Not an array: rank 0
+ if (std::optional<evaluate::DynamicType> type = expr->GetType()) {
+ if (type->category() == evaluate::TypeCategory::Character) {
+ // Substrings are explicitly denied by the standard [6.0:163:9-11].
+ // This is supported as an extension. This restriction was added in
+ // OpenMP 5.2.
+ isSubstring = true;
+ context_.Say(GetContext().clauseSource,
+ "The use of substrings in OpenMP argument lists has been disallowed since OpenMP 5.2."_port_en_US);
+ } else {
+ llvm_unreachable(
+ "Array indexing on a variable that isn't an array");
+ }
}
}
}
@@ -4343,11 +4350,8 @@ bool OmpStructureChecker::CheckTargetBlockOnlyTeams(
parser::Unwrap<parser::OpenMPConstruct>(*it)}) {
if (const auto *ompBlockConstruct{
std::get_if<parser::OpenMPBlockConstruct>(&ompConstruct->u)}) {
- const auto &beginBlockDir{
- std::get<parser::OmpBeginBlockDirective>(ompBlockConstruct->t)};
- const auto &beginDir{
- std::get<parser::OmpBlockDirective>(beginBlockDir.t)};
- if (beginDir.v == llvm::omp::Directive::OMPD_teams) {
+ llvm::omp::Directive dirId{ompBlockConstruct->BeginDir().DirId()};
+ if (dirId == llvm::omp::Directive::OMPD_teams) {
nestedTeams = true;
}
}
@@ -4393,11 +4397,7 @@ void OmpStructureChecker::CheckWorkshareBlockStmts(
auto currentDir{llvm::omp::Directive::OMPD_unknown};
if (const auto *ompBlockConstruct{
std::get_if<parser::OpenMPBlockConstruct>(&ompConstruct->u)}) {
- const auto &beginBlockDir{
- std::get<parser::OmpBeginBlockDirective>(ompBlockConstruct->t)};
- const auto &beginDir{
- std::get<parser::OmpBlockDirective>(beginBlockDir.t)};
- currentDir = beginDir.v;
+ currentDir = ompBlockConstruct->BeginDir().DirId();
} else if (const auto *ompLoopConstruct{
std::get_if<parser::OpenMPLoopConstruct>(
&ompConstruct->u)}) {
@@ -4479,17 +4479,18 @@ const parser::OmpObjectList *OmpStructureChecker::GetOmpObjectList(
const parser::OmpClause &clause) {
// Clauses with OmpObjectList as its data member
- using MemberObjectListClauses = std::tuple<parser::OmpClause::Copyprivate,
- parser::OmpClause::Copyin, parser::OmpClause::Enter,
- parser::OmpClause::Firstprivate, parser::OmpClause::Link,
- parser::OmpClause::Private, parser::OmpClause::Shared,
- parser::OmpClause::UseDevicePtr, parser::OmpClause::UseDeviceAddr>;
+ using MemberObjectListClauses =
+ std::tuple<parser::OmpClause::Copyprivate, parser::OmpClause::Copyin,
+ parser::OmpClause::Firstprivate, parser::OmpClause::Link,
+ parser::OmpClause::Private, parser::OmpClause::Shared,
+ parser::OmpClause::UseDevicePtr, parser::OmpClause::UseDeviceAddr>;
// Clauses with OmpObjectList in the tuple
- using TupleObjectListClauses = std::tuple<parser::OmpClause::Aligned,
- parser::OmpClause::Allocate, parser::OmpClause::From,
- parser::OmpClause::Lastprivate, parser::OmpClause::Map,
- parser::OmpClause::Reduction, parser::OmpClause::To>;
+ using TupleObjectListClauses =
+ std::tuple<parser::OmpClause::Aligned, parser::OmpClause::Allocate,
+ parser::OmpClause::From, parser::OmpClause::Lastprivate,
+ parser::OmpClause::Map, parser::OmpClause::Reduction,
+ parser::OmpClause::To, parser::OmpClause::Enter>;
// TODO:: Generate the tuples using TableGen.
// Handle other constructs with OmpObjectList such as OpenMPThreadprivate.