[flang][OpenMP] Parse cancel-directive-name as clause (#130146)

The cancellable construct names on CANCEL or CANCELLATION POINT
directives are actually clauses (with the same names as the
corresponding constructs).

Instead of parsing them into a custom structure, parse them as a clause,
which will make CANCEL/CANCELLATION POINT follow the same uniform scheme
as other constructs (<directive> [(<arguments>)] [clauses]).
This commit is contained in:
Krzysztof Parzyszek 2025-03-10 11:58:02 -05:00 committed by GitHub
parent 7e292772ad
commit 5ba7a3bd4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 182 additions and 77 deletions

View File

@ -456,8 +456,6 @@ public:
READ_FEATURE(OmpBeginLoopDirective)
READ_FEATURE(OmpBeginSectionsDirective)
READ_FEATURE(OmpBlockDirective)
READ_FEATURE(OmpCancelType)
READ_FEATURE(OmpCancelType::Type)
READ_FEATURE(OmpClause)
READ_FEATURE(OmpClauseList)
READ_FEATURE(OmpCriticalDirective)
@ -551,7 +549,6 @@ public:
READ_FEATURE(OpenMPAtomicConstruct)
READ_FEATURE(OpenMPBlockConstruct)
READ_FEATURE(OpenMPCancelConstruct)
READ_FEATURE(OpenMPCancelConstruct::If)
READ_FEATURE(OpenMPCancellationPointConstruct)
READ_FEATURE(OpenMPConstruct)
READ_FEATURE(OpenMPCriticalConstruct)

View File

@ -265,9 +265,6 @@ void OpenMPCounterVisitor::Post(const OmpDirectiveNameModifier &c) {
clauseDetails +=
"name_modifier=" + llvm::omp::getOpenMPDirectiveName(c.v).str() + ";";
}
void OpenMPCounterVisitor::Post(const OmpCancelType::Type &c) {
clauseDetails += "type=" + std::string{OmpCancelType::EnumToString(c)} + ";";
}
void OpenMPCounterVisitor::Post(const OmpClause &c) {
PostClauseCommon(normalize_clause_name(c.source.ToString()));
clauseDetails.clear();

View File

@ -78,7 +78,6 @@ struct OpenMPCounterVisitor {
void Post(const OmpMapType::Value &c);
void Post(const OmpScheduleClause::Kind &c);
void Post(const OmpDirectiveNameModifier &c);
void Post(const OmpCancelType::Type &c);
void Post(const OmpClause &c);
void PostClauseCommon(const ClauseInfo &ci);

View File

@ -540,12 +540,11 @@ public:
"llvm::omp::Directive = ", llvm::omp::getOpenMPDirectiveName(x))
.str();
}
NODE(parser, OmpCancelType)
NODE_ENUM(OmpCancelType, Type)
NODE(parser, OmpClause)
#define GEN_FLANG_DUMP_PARSE_TREE_CLAUSES
#include "llvm/Frontend/OpenMP/OMP.inc"
NODE(parser, OmpClauseList)
NODE(parser, OmpCancellationConstructTypeClause)
NODE(parser, OmpContainsClause)
NODE(parser, OmpCriticalDirective)
NODE(parser, OmpErrorDirective)
@ -688,7 +687,6 @@ public:
NODE(parser, OpenMPAtomicConstruct)
NODE(parser, OpenMPBlockConstruct)
NODE(parser, OpenMPCancelConstruct)
NODE(OpenMPCancelConstruct, If)
NODE(parser, OpenMPCancellationPointConstruct)
NODE(parser, OpenMPConstruct)
NODE(parser, OpenMPCriticalConstruct)

View File

@ -4047,6 +4047,12 @@ struct OmpBindClause {
WRAPPER_CLASS_BOILERPLATE(OmpBindClause, Binding);
};
// Artificial clause to represent a cancellable construct.
struct OmpCancellationConstructTypeClause {
TUPLE_CLASS_BOILERPLATE(OmpCancellationConstructTypeClause);
std::tuple<OmpDirectiveName, std::optional<ScalarLogicalExpr>> t;
};
// Ref: [5.2:214]
//
// contains-clause ->
@ -4859,26 +4865,18 @@ struct OmpLoopDirective {
CharBlock source;
};
// 2.14.1 construct-type-clause -> PARALLEL | SECTIONS | DO | TASKGROUP
struct OmpCancelType {
ENUM_CLASS(Type, Parallel, Sections, Do, Taskgroup)
WRAPPER_CLASS_BOILERPLATE(OmpCancelType, Type);
CharBlock source;
};
// 2.14.2 cancellation-point -> CANCELLATION POINT construct-type-clause
struct OpenMPCancellationPointConstruct {
TUPLE_CLASS_BOILERPLATE(OpenMPCancellationPointConstruct);
CharBlock source;
std::tuple<Verbatim, OmpCancelType> t;
std::tuple<Verbatim, OmpClauseList> t;
};
// 2.14.1 cancel -> CANCEL construct-type-clause [ [,] if-clause]
struct OpenMPCancelConstruct {
TUPLE_CLASS_BOILERPLATE(OpenMPCancelConstruct);
WRAPPER_CLASS(If, ScalarLogicalExpr);
CharBlock source;
std::tuple<Verbatim, OmpCancelType, std::optional<If>> t;
std::tuple<Verbatim, OmpClauseList> t;
};
// Ref: [5.0:254-255], [5.1:287-288], [5.2:322-323]

View File

@ -50,6 +50,20 @@ template <typename Parser> constexpr auto unwrap(const Parser &p) {
return UnwrapParser<Parser>(p);
}
// Check (without advancing the parsing location) if the next thing in the
// input would be accepted by the "checked" parser, and if so, run the "parser"
// parser.
// The intended use is with the "checker" parser being some token, followed
// by a more complex parser that consumes the token plus more things, e.g.
// "PARALLEL"_id >= Parser<OmpDirectiveSpecification>{}.
//
// The >= has a higher precedence than ||, so it can be used just like >>
// in an alternatives parser without parentheses.
template <typename PA, typename PB>
constexpr auto operator>=(PA checker, PB parser) {
return lookAhead(checker) >> parser;
}
/// Parse OpenMP directive name (this includes compound directives).
struct OmpDirectiveNameParser {
using resultType = OmpDirectiveName;
@ -575,6 +589,9 @@ TYPE_PARSER(construct<OmpAffinityClause>(
maybe(nonemptyList(Parser<OmpAffinityClause::Modifier>{}) / ":"),
Parser<OmpObjectList>{}))
TYPE_PARSER(construct<OmpCancellationConstructTypeClause>(
OmpDirectiveNameParser{}, maybe(parenthesized(scalarLogicalExpr))))
// 2.15.3.1 DEFAULT (PRIVATE | FIRSTPRIVATE | SHARED | NONE)
TYPE_PARSER(construct<OmpDefaultClause::DataSharingAttribute>(
"PRIVATE" >> pure(OmpDefaultClause::DataSharingAttribute::Private) ||
@ -804,8 +821,9 @@ TYPE_PARSER(construct<OmpAbsentClause>(many(maybe(","_tok) >>
TYPE_PARSER(construct<OmpContainsClause>(many(maybe(","_tok) >>
construct<llvm::omp::Directive>(unwrap(OmpDirectiveNameParser{})))))
TYPE_PARSER("ABSENT" >> construct<OmpClause>(construct<OmpClause::Absent>(
parenthesized(Parser<OmpAbsentClause>{}))) ||
TYPE_PARSER( //
"ABSENT" >> construct<OmpClause>(construct<OmpClause::Absent>(
parenthesized(Parser<OmpAbsentClause>{}))) ||
"ACQUIRE" >> construct<OmpClause>(construct<OmpClause::Acquire>()) ||
"ACQ_REL" >> construct<OmpClause>(construct<OmpClause::AcqRel>()) ||
"AFFINITY" >> construct<OmpClause>(construct<OmpClause::Affinity>(
@ -982,7 +1000,20 @@ TYPE_PARSER("ABSENT" >> construct<OmpClause>(construct<OmpClause::Absent>(
"UPDATE" >> construct<OmpClause>(construct<OmpClause::Update>(
parenthesized(Parser<OmpUpdateClause>{}))) ||
"WHEN" >> construct<OmpClause>(construct<OmpClause::When>(
parenthesized(Parser<OmpWhenClause>{}))))
parenthesized(Parser<OmpWhenClause>{}))) ||
// Cancellable constructs
"DO"_id >=
construct<OmpClause>(construct<OmpClause::CancellationConstructType>(
Parser<OmpCancellationConstructTypeClause>{})) ||
"PARALLEL"_id >=
construct<OmpClause>(construct<OmpClause::CancellationConstructType>(
Parser<OmpCancellationConstructTypeClause>{})) ||
"SECTIONS"_id >=
construct<OmpClause>(construct<OmpClause::CancellationConstructType>(
Parser<OmpCancellationConstructTypeClause>{})) ||
"TASKGROUP"_id >=
construct<OmpClause>(construct<OmpClause::CancellationConstructType>(
Parser<OmpCancellationConstructTypeClause>{})))
// [Clause, [Clause], ...]
TYPE_PARSER(sourced(construct<OmpClauseList>(
@ -1096,20 +1127,13 @@ TYPE_PARSER(sourced(construct<OmpLoopDirective>(first(
TYPE_PARSER(sourced(construct<OmpBeginLoopDirective>(
sourced(Parser<OmpLoopDirective>{}), Parser<OmpClauseList>{})))
// 2.14.1 construct-type-clause -> PARALLEL | SECTIONS | DO | TASKGROUP
TYPE_PARSER(sourced(construct<OmpCancelType>(
first("PARALLEL" >> pure(OmpCancelType::Type::Parallel),
"SECTIONS" >> pure(OmpCancelType::Type::Sections),
"DO" >> pure(OmpCancelType::Type::Do),
"TASKGROUP" >> pure(OmpCancelType::Type::Taskgroup)))))
// 2.14.2 Cancellation Point construct
TYPE_PARSER(sourced(construct<OpenMPCancellationPointConstruct>(
verbatim("CANCELLATION POINT"_tok), Parser<OmpCancelType>{})))
verbatim("CANCELLATION POINT"_tok), Parser<OmpClauseList>{})))
// 2.14.1 Cancel construct
TYPE_PARSER(sourced(construct<OpenMPCancelConstruct>(verbatim("CANCEL"_tok),
Parser<OmpCancelType>{}, maybe("IF" >> parenthesized(scalarLogicalExpr)))))
TYPE_PARSER(sourced(construct<OpenMPCancelConstruct>(
verbatim("CANCEL"_tok), Parser<OmpClauseList>{})))
TYPE_PARSER(sourced(construct<OmpFailClause>(
parenthesized(indirect(Parser<OmpMemoryOrderClause>{})))))
@ -1193,9 +1217,10 @@ TYPE_PARSER(
sourced(construct<OpenMPStandaloneConstruct>(
Parser<OpenMPSimpleStandaloneConstruct>{}) ||
construct<OpenMPStandaloneConstruct>(Parser<OpenMPFlushConstruct>{}) ||
construct<OpenMPStandaloneConstruct>(Parser<OpenMPCancelConstruct>{}) ||
// Try CANCELLATION POINT before CANCEL.
construct<OpenMPStandaloneConstruct>(
Parser<OpenMPCancellationPointConstruct>{}) ||
construct<OpenMPStandaloneConstruct>(Parser<OpenMPCancelConstruct>{}) ||
construct<OpenMPStandaloneConstruct>(
Parser<OmpMetadirectiveDirective>{}) ||
construct<OpenMPStandaloneConstruct>(Parser<OpenMPDepobjConstruct>{})) /

View File

@ -2851,15 +2851,14 @@ public:
void Unparse(const OpenMPCancellationPointConstruct &x) {
BeginOpenMP();
Word("!$OMP CANCELLATION POINT ");
Walk(std::get<OmpCancelType>(x.t));
Walk(std::get<OmpClauseList>(x.t));
Put("\n");
EndOpenMP();
}
void Unparse(const OpenMPCancelConstruct &x) {
BeginOpenMP();
Word("!$OMP CANCEL ");
Walk(std::get<OmpCancelType>(x.t));
Walk(std::get<std::optional<OpenMPCancelConstruct::If>>(x.t));
Walk(std::get<OmpClauseList>(x.t));
Put("\n");
EndOpenMP();
}
@ -3034,7 +3033,6 @@ public:
OmpDeviceTypeClause, DeviceTypeDescription) // OMP device_type
WALK_NESTED_ENUM(OmpReductionModifier, Value) // OMP reduction-modifier
WALK_NESTED_ENUM(OmpExpectation, Value) // OMP motion-expectation
WALK_NESTED_ENUM(OmpCancelType, Type) // OMP cancel-type
WALK_NESTED_ENUM(OmpOrderClause, Ordering) // OMP ordering
WALK_NESTED_ENUM(OmpOrderModifier, Value) // OMP order-modifier
WALK_NESTED_ENUM(OmpPrescriptiveness, Value) // OMP prescriptiveness

View File

@ -2244,9 +2244,13 @@ void OmpStructureChecker::Leave(const parser::OpenMPFlushConstruct &x) {
void OmpStructureChecker::Enter(const parser::OpenMPCancelConstruct &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
const auto &type{std::get<parser::OmpCancelType>(x.t)};
const auto &clauses{std::get<parser::OmpClauseList>(x.t)};
PushContextAndClauseSets(dir.source, llvm::omp::Directive::OMPD_cancel);
CheckCancellationNest(dir.source, type.v);
if (auto maybeConstruct{GetCancelType(
llvm::omp::Directive::OMPD_cancel, x.source, clauses)}) {
CheckCancellationNest(dir.source, *maybeConstruct);
}
}
void OmpStructureChecker::Leave(const parser::OpenMPCancelConstruct &) {
@ -2296,13 +2300,37 @@ void OmpStructureChecker::Leave(const parser::OpenMPCriticalConstruct &) {
dirContext_.pop_back();
}
void OmpStructureChecker::Enter(
const parser::OmpClause::CancellationConstructType &x) {
// Do not call CheckAllowed/CheckAllowedClause, because in case of an error
// it will print "CANCELLATION_CONSTRUCT_TYPE" as the clause name instead of
// the contained construct name.
auto &dirName{std::get<parser::OmpDirectiveName>(x.v.t)};
switch (dirName.v) {
case llvm::omp::Directive::OMPD_do:
case llvm::omp::Directive::OMPD_parallel:
case llvm::omp::Directive::OMPD_sections:
case llvm::omp::Directive::OMPD_taskgroup:
break;
default:
context_.Say(dirName.source, "%s is not a cancellable construct"_err_en_US,
parser::ToUpperCaseLetters(
llvm::omp::getOpenMPDirectiveName(dirName.v).str()));
break;
}
}
void OmpStructureChecker::Enter(
const parser::OpenMPCancellationPointConstruct &x) {
const auto &dir{std::get<parser::Verbatim>(x.t)};
const auto &type{std::get<parser::OmpCancelType>(x.t)};
const auto &clauses{std::get<parser::OmpClauseList>(x.t)};
PushContextAndClauseSets(
dir.source, llvm::omp::Directive::OMPD_cancellation_point);
CheckCancellationNest(dir.source, type.v);
if (auto maybeConstruct{GetCancelType(
llvm::omp::Directive::OMPD_cancellation_point, x.source, clauses)}) {
CheckCancellationNest(dir.source, *maybeConstruct);
}
}
void OmpStructureChecker::Leave(
@ -2310,8 +2338,42 @@ void OmpStructureChecker::Leave(
dirContext_.pop_back();
}
std::optional<llvm::omp::Directive> OmpStructureChecker::GetCancelType(
llvm::omp::Directive cancelDir, const parser::CharBlock &cancelSource,
const parser::OmpClauseList &clauses) {
// Given clauses from CANCEL or CANCELLATION_POINT, identify the construct
// to which the cancellation applies.
std::optional<llvm::omp::Directive> cancelee;
llvm::StringRef cancelName{llvm::omp::getOpenMPDirectiveName(cancelDir)};
for (const parser::OmpClause &clause : clauses.v) {
using CancellationConstructType =
parser::OmpClause::CancellationConstructType;
if (auto *cctype{std::get_if<CancellationConstructType>(&clause.u)}) {
if (cancelee) {
context_.Say(cancelSource,
"Multiple cancel-directive-name clauses are not allowed on the %s construct"_err_en_US,
parser::ToUpperCaseLetters(cancelName.str()));
return std::nullopt;
}
cancelee = std::get<parser::OmpDirectiveName>(cctype->v.t).v;
}
}
if (!cancelee) {
context_.Say(cancelSource,
"Missing cancel-directive-name clause on the %s construct"_err_en_US,
parser::ToUpperCaseLetters(cancelName.str()));
return std::nullopt;
}
return cancelee;
}
void OmpStructureChecker::CheckCancellationNest(
const parser::CharBlock &source, const parser::OmpCancelType::Type &type) {
const parser::CharBlock &source, llvm::omp::Directive type) {
llvm::StringRef typeName{llvm::omp::getOpenMPDirectiveName(type)};
if (CurrentDirectiveIsNested()) {
// If construct-type-clause is taskgroup, the cancellation construct must be
// closely nested inside a task or a taskloop construct and the cancellation
@ -2322,8 +2384,9 @@ void OmpStructureChecker::CheckCancellationNest(
// that matches the type specified in construct-type-clause of the
// cancellation construct.
bool eligibleCancellation{false};
switch (type) {
case parser::OmpCancelType::Type::Taskgroup:
case llvm::omp::Directive::OMPD_taskgroup:
if (llvm::omp::nestedCancelTaskgroupAllowedSet.test(
GetContextParent().directive)) {
eligibleCancellation = true;
@ -2349,38 +2412,37 @@ void OmpStructureChecker::CheckCancellationNest(
}
if (!eligibleCancellation) {
context_.Say(source,
"With %s clause, %s construct must be closely nested inside TASK "
"or TASKLOOP construct and %s region must be closely nested inside "
"TASKGROUP region"_err_en_US,
parser::ToUpperCaseLetters(
parser::OmpCancelType::EnumToString(type)),
"With %s clause, %s construct must be closely nested inside TASK or TASKLOOP construct and %s region must be closely nested inside TASKGROUP region"_err_en_US,
parser::ToUpperCaseLetters(typeName.str()),
ContextDirectiveAsFortran(), ContextDirectiveAsFortran());
}
return;
case parser::OmpCancelType::Type::Sections:
case llvm::omp::Directive::OMPD_sections:
if (llvm::omp::nestedCancelSectionsAllowedSet.test(
GetContextParent().directive)) {
eligibleCancellation = true;
}
break;
case parser::OmpCancelType::Type::Do:
case llvm::omp::Directive::OMPD_do:
if (llvm::omp::nestedCancelDoAllowedSet.test(
GetContextParent().directive)) {
eligibleCancellation = true;
}
break;
case parser::OmpCancelType::Type::Parallel:
case llvm::omp::Directive::OMPD_parallel:
if (llvm::omp::nestedCancelParallelAllowedSet.test(
GetContextParent().directive)) {
eligibleCancellation = true;
}
break;
default:
// This should have been diagnosed by this point.
llvm_unreachable("Unexpected directive");
}
if (!eligibleCancellation) {
context_.Say(source,
"With %s clause, %s construct cannot be closely nested inside %s "
"construct"_err_en_US,
parser::ToUpperCaseLetters(parser::OmpCancelType::EnumToString(type)),
"With %s clause, %s construct cannot be closely nested inside %s construct"_err_en_US,
parser::ToUpperCaseLetters(typeName.str()),
ContextDirectiveAsFortran(),
parser::ToUpperCaseLetters(
getDirectiveName(GetContextParent().directive).str()));
@ -2388,38 +2450,33 @@ void OmpStructureChecker::CheckCancellationNest(
} else {
// The cancellation directive cannot be orphaned.
switch (type) {
case parser::OmpCancelType::Type::Taskgroup:
case llvm::omp::Directive::OMPD_taskgroup:
context_.Say(source,
"%s %s directive is not closely nested inside "
"TASK or TASKLOOP"_err_en_US,
"%s %s directive is not closely nested inside TASK or TASKLOOP"_err_en_US,
ContextDirectiveAsFortran(),
parser::ToUpperCaseLetters(
parser::OmpCancelType::EnumToString(type)));
parser::ToUpperCaseLetters(typeName.str()));
break;
case parser::OmpCancelType::Type::Sections:
case llvm::omp::Directive::OMPD_sections:
context_.Say(source,
"%s %s directive is not closely nested inside "
"SECTION or SECTIONS"_err_en_US,
"%s %s directive is not closely nested inside SECTION or SECTIONS"_err_en_US,
ContextDirectiveAsFortran(),
parser::ToUpperCaseLetters(
parser::OmpCancelType::EnumToString(type)));
parser::ToUpperCaseLetters(typeName.str()));
break;
case parser::OmpCancelType::Type::Do:
case llvm::omp::Directive::OMPD_do:
context_.Say(source,
"%s %s directive is not closely nested inside "
"the construct that matches the DO clause type"_err_en_US,
"%s %s directive is not closely nested inside the construct that matches the DO clause type"_err_en_US,
ContextDirectiveAsFortran(),
parser::ToUpperCaseLetters(
parser::OmpCancelType::EnumToString(type)));
parser::ToUpperCaseLetters(typeName.str()));
break;
case parser::OmpCancelType::Type::Parallel:
case llvm::omp::Directive::OMPD_parallel:
context_.Say(source,
"%s %s directive is not closely nested inside "
"the construct that matches the PARALLEL clause type"_err_en_US,
"%s %s directive is not closely nested inside the construct that matches the PARALLEL clause type"_err_en_US,
ContextDirectiveAsFortran(),
parser::ToUpperCaseLetters(
parser::OmpCancelType::EnumToString(type)));
parser::ToUpperCaseLetters(typeName.str()));
break;
default:
// This should have been diagnosed by this point.
llvm_unreachable("Unexpected directive");
}
}
}
@ -3106,7 +3163,6 @@ CHECK_SIMPLE_CLAUSE(MemoryOrder, OMPC_memory_order)
CHECK_SIMPLE_CLAUSE(Bind, OMPC_bind)
CHECK_SIMPLE_CLAUSE(Align, OMPC_align)
CHECK_SIMPLE_CLAUSE(Compare, OMPC_compare)
CHECK_SIMPLE_CLAUSE(CancellationConstructType, OMPC_cancellation_construct_type)
CHECK_SIMPLE_CLAUSE(OmpxAttribute, OMPC_ompx_attribute)
CHECK_SIMPLE_CLAUSE(Weak, OMPC_weak)

View File

@ -275,8 +275,11 @@ private:
void CheckTargetUpdate();
void CheckDependenceType(const parser::OmpDependenceType::Value &x);
void CheckTaskDependenceType(const parser::OmpTaskDependenceType::Value &x);
std::optional<llvm::omp::Directive> GetCancelType(
llvm::omp::Directive cancelDir, const parser::CharBlock &cancelSource,
const parser::OmpClauseList &clauses);
void CheckCancellationNest(
const parser::CharBlock &source, const parser::OmpCancelType::Type &type);
const parser::CharBlock &source, llvm::omp::Directive type);
std::int64_t GetOrdCollapseLevel(const parser::OpenMPLoopConstruct &x);
void CheckReductionObjects(
const parser::OmpObjectList &objects, llvm::omp::Clause clauseId);

View File

@ -0,0 +1,29 @@
!RUN: %python %S/../test_errors.py %s %flang -fopenmp
subroutine f00
!$omp parallel
!ERROR: Missing cancel-directive-name clause on the CANCEL construct
!$omp cancel
!$omp end parallel
end
subroutine f01
!$omp parallel
!ERROR: Multiple cancel-directive-name clauses are not allowed on the CANCEL construct
!$omp cancel parallel parallel
!$omp end parallel
end
subroutine f02
!$omp parallel
!ERROR: Missing cancel-directive-name clause on the CANCELLATION POINT construct
!$omp cancellation point
!$omp end parallel
end
subroutine f03
!$omp parallel
!ERROR: Multiple cancel-directive-name clauses are not allowed on the CANCELLATION POINT construct
!$omp cancellation point parallel parallel
!$omp end parallel
end

View File

@ -105,6 +105,7 @@ def OMPC_CancellationConstructType : Clause<"cancellation_construct_type"> {
OMP_CANCELLATION_CONSTRUCT_Taskgroup,
OMP_CANCELLATION_CONSTRUCT_None
];
let flangClass = "OmpCancellationConstructTypeClause";
}
def OMPC_Contains : Clause<"contains"> {
let clangClass = "OMPContainsClause";
@ -647,12 +648,16 @@ def OMP_BeginDeclareVariant : Directive<"begin declare variant"> {
}
def OMP_Cancel : Directive<"cancel"> {
let allowedOnceClauses = [
VersionedClause<OMPC_CancellationConstructType>,
VersionedClause<OMPC_If>,
];
let association = AS_None;
let category = CA_Executable;
}
def OMP_CancellationPoint : Directive<"cancellation point"> {
let allowedOnceClauses = [
VersionedClause<OMPC_CancellationConstructType>,
];
let association = AS_None;
let category = CA_Executable;
}