[flang][OpenMP] Semantic checks for IN_REDUCTION and TASK_REDUCTION (#118841)
Update parsing of these two clauses and add semantic checks for them. Simplify some code in IsReductionAllowedForType and CheckReductionOperator.
This commit is contained in:
parent
6cfad635d5
commit
58f9c4fc00
@ -593,7 +593,10 @@ public:
|
||||
NODE(parser, OmpReductionClause)
|
||||
NODE(OmpReductionClause, Modifier)
|
||||
NODE(parser, OmpInReductionClause)
|
||||
NODE(OmpInReductionClause, Modifier)
|
||||
NODE(parser, OmpReductionCombiner)
|
||||
NODE(parser, OmpTaskReductionClause)
|
||||
NODE(OmpTaskReductionClause, Modifier)
|
||||
NODE(OmpReductionCombiner, FunctionCombiner)
|
||||
NODE(parser, OmpReductionInitializerClause)
|
||||
NODE(parser, OmpReductionIdentifier)
|
||||
|
@ -3960,11 +3960,14 @@ struct OmpIfClause {
|
||||
std::tuple<MODIFIERS(), ScalarLogicalExpr> t;
|
||||
};
|
||||
|
||||
// OMP 5.0 2.19.5.6 in_reduction-clause -> IN_REDUCTION (reduction-identifier:
|
||||
// variable-name-list)
|
||||
// Ref: [5.0:170-176], [5.1:197-205], [5.2:138-139]
|
||||
//
|
||||
// in-reduction-clause ->
|
||||
// IN_REDUCTION(reduction-identifier: list) // since 5.0
|
||||
struct OmpInReductionClause {
|
||||
TUPLE_CLASS_BOILERPLATE(OmpInReductionClause);
|
||||
std::tuple<OmpReductionIdentifier, OmpObjectList> t;
|
||||
MODIFIER_BOILERPLATE(OmpReductionIdentifier);
|
||||
std::tuple<MODIFIERS(), OmpObjectList> t;
|
||||
};
|
||||
|
||||
// Ref: [4.5:199-201], [5.0:288-290], [5.1:321-322], [5.2:115-117]
|
||||
@ -4079,6 +4082,16 @@ struct OmpScheduleClause {
|
||||
std::tuple<MODIFIERS(), Kind, std::optional<ScalarIntExpr>> t;
|
||||
};
|
||||
|
||||
// Ref: [5.0:232-234], [5.1:264-266], [5.2:137]
|
||||
//
|
||||
// task-reduction-clause ->
|
||||
// TASK_REDUCTION(reduction-identifier: list) // since 5.0
|
||||
struct OmpTaskReductionClause {
|
||||
TUPLE_CLASS_BOILERPLATE(OmpTaskReductionClause);
|
||||
MODIFIER_BOILERPLATE(OmpReductionIdentifier);
|
||||
std::tuple<MODIFIERS(), OmpObjectList> t;
|
||||
};
|
||||
|
||||
// Ref: [4.5:107-109], [5.0:176-180], [5.1:205-210], [5.2:167-168]
|
||||
//
|
||||
// to-clause (in DECLARE TARGET) ->
|
||||
|
@ -859,10 +859,14 @@ Init make(const parser::OmpClause::Init &inp,
|
||||
InReduction make(const parser::OmpClause::InReduction &inp,
|
||||
semantics::SemanticsContext &semaCtx) {
|
||||
// inp.v -> parser::OmpInReductionClause
|
||||
auto &t0 = std::get<parser::OmpReductionIdentifier>(inp.v.t);
|
||||
auto &mods = semantics::OmpGetModifiers(inp.v);
|
||||
auto *m0 =
|
||||
semantics::OmpGetUniqueModifier<parser::OmpReductionIdentifier>(mods);
|
||||
auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);
|
||||
assert(m0 && "OmpReductionIdentifier is required");
|
||||
|
||||
return InReduction{
|
||||
{/*ReductionIdentifiers=*/{makeReductionOperator(t0, semaCtx)},
|
||||
{/*ReductionIdentifiers=*/{makeReductionOperator(*m0, semaCtx)},
|
||||
/*List=*/makeObjects(t1, semaCtx)}};
|
||||
}
|
||||
|
||||
@ -1155,17 +1159,17 @@ Reduction make(const parser::OmpClause::Reduction &inp,
|
||||
);
|
||||
|
||||
auto &mods = semantics::OmpGetModifiers(inp.v);
|
||||
auto *t0 =
|
||||
auto *m0 =
|
||||
semantics::OmpGetUniqueModifier<parser::OmpReductionModifier>(mods);
|
||||
auto *t1 =
|
||||
auto *m1 =
|
||||
semantics::OmpGetUniqueModifier<parser::OmpReductionIdentifier>(mods);
|
||||
auto &t2 = std::get<parser::OmpObjectList>(inp.v.t);
|
||||
assert(t1 && "OmpReductionIdentifier is required");
|
||||
auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);
|
||||
assert(m1 && "OmpReductionIdentifier is required");
|
||||
|
||||
return Reduction{
|
||||
{/*ReductionModifier=*/maybeApplyToV(convert, t0),
|
||||
/*ReductionIdentifiers=*/{makeReductionOperator(*t1, semaCtx)},
|
||||
/*List=*/makeObjects(t2, semaCtx)}};
|
||||
{/*ReductionModifier=*/maybeApplyToV(convert, m0),
|
||||
/*ReductionIdentifiers=*/{makeReductionOperator(*m1, semaCtx)},
|
||||
/*List=*/makeObjects(t1, semaCtx)}};
|
||||
}
|
||||
|
||||
// Relaxed: empty
|
||||
@ -1259,13 +1263,13 @@ TaskReduction make(const parser::OmpClause::TaskReduction &inp,
|
||||
semantics::SemanticsContext &semaCtx) {
|
||||
// inp.v -> parser::OmpReductionClause
|
||||
auto &mods = semantics::OmpGetModifiers(inp.v);
|
||||
auto *t0 =
|
||||
auto *m0 =
|
||||
semantics::OmpGetUniqueModifier<parser::OmpReductionIdentifier>(mods);
|
||||
auto &t1 = std::get<parser::OmpObjectList>(inp.v.t);
|
||||
assert(t0 && "OmpReductionIdentifier is required");
|
||||
assert(m0 && "OmpReductionIdentifier is required");
|
||||
|
||||
return TaskReduction{
|
||||
{/*ReductionIdentifiers=*/{makeReductionOperator(*t0, semaCtx)},
|
||||
{/*ReductionIdentifiers=*/{makeReductionOperator(*m0, semaCtx)},
|
||||
/*List=*/makeObjects(t1, semaCtx)}};
|
||||
}
|
||||
|
||||
|
@ -282,6 +282,9 @@ TYPE_PARSER(sourced(
|
||||
|
||||
TYPE_PARSER(sourced(construct<OmpIfClause::Modifier>(OmpDirectiveNameParser{})))
|
||||
|
||||
TYPE_PARSER(sourced(construct<OmpInReductionClause::Modifier>(
|
||||
Parser<OmpReductionIdentifier>{})))
|
||||
|
||||
TYPE_PARSER(sourced(construct<OmpLastprivateClause::Modifier>(
|
||||
Parser<OmpLastprivateModifier>{})))
|
||||
|
||||
@ -306,6 +309,9 @@ TYPE_PARSER(sourced(construct<OmpScheduleClause::Modifier>(sourced(
|
||||
construct<OmpScheduleClause::Modifier>(Parser<OmpChunkModifier>{}) ||
|
||||
construct<OmpScheduleClause::Modifier>(Parser<OmpOrderingModifier>{})))))
|
||||
|
||||
TYPE_PARSER(sourced(construct<OmpTaskReductionClause::Modifier>(
|
||||
Parser<OmpReductionIdentifier>{})))
|
||||
|
||||
TYPE_PARSER(sourced(construct<OmpToClause::Modifier>(
|
||||
sourced(construct<OmpToClause::Modifier>(Parser<OmpExpectation>{}) ||
|
||||
construct<OmpToClause::Modifier>(Parser<OmpMapper>{}) ||
|
||||
@ -407,7 +413,12 @@ TYPE_PARSER(construct<OmpReductionClause>(
|
||||
|
||||
// OMP 5.0 2.19.5.6 IN_REDUCTION (reduction-identifier: variable-name-list)
|
||||
TYPE_PARSER(construct<OmpInReductionClause>(
|
||||
Parser<OmpReductionIdentifier>{} / ":", Parser<OmpObjectList>{}))
|
||||
maybe(nonemptyList(Parser<OmpInReductionClause::Modifier>{}) / ":"),
|
||||
Parser<OmpObjectList>{}))
|
||||
|
||||
TYPE_PARSER(construct<OmpTaskReductionClause>(
|
||||
maybe(nonemptyList(Parser<OmpTaskReductionClause::Modifier>{}) / ":"),
|
||||
Parser<OmpObjectList>{}))
|
||||
|
||||
// OMP 5.0 2.11.4 allocate-clause -> ALLOCATE ([allocator:] variable-name-list)
|
||||
// OMP 5.2 2.13.4 allocate-clause -> ALLOCATE ([allocate-modifier
|
||||
@ -609,15 +620,15 @@ TYPE_PARSER(
|
||||
parenthesized(Parser<OmpObjectList>{}))) ||
|
||||
"PROC_BIND" >> construct<OmpClause>(construct<OmpClause::ProcBind>(
|
||||
parenthesized(Parser<OmpProcBindClause>{}))) ||
|
||||
"REDUCTION" >> construct<OmpClause>(construct<OmpClause::Reduction>(
|
||||
parenthesized(Parser<OmpReductionClause>{}))) ||
|
||||
"REDUCTION"_id >> construct<OmpClause>(construct<OmpClause::Reduction>(
|
||||
parenthesized(Parser<OmpReductionClause>{}))) ||
|
||||
"IN_REDUCTION" >> construct<OmpClause>(construct<OmpClause::InReduction>(
|
||||
parenthesized(Parser<OmpInReductionClause>{}))) ||
|
||||
"DETACH" >> construct<OmpClause>(construct<OmpClause::Detach>(
|
||||
parenthesized(Parser<OmpDetachClause>{}))) ||
|
||||
"TASK_REDUCTION" >>
|
||||
construct<OmpClause>(construct<OmpClause::TaskReduction>(
|
||||
parenthesized(Parser<OmpReductionClause>{}))) ||
|
||||
parenthesized(Parser<OmpTaskReductionClause>{}))) ||
|
||||
"RELAXED" >> construct<OmpClause>(construct<OmpClause::Relaxed>()) ||
|
||||
"RELEASE" >> construct<OmpClause>(construct<OmpClause::Release>()) ||
|
||||
"REVERSE_OFFLOAD" >>
|
||||
|
@ -2143,13 +2143,18 @@ public:
|
||||
}
|
||||
void Unparse(const OmpReductionClause &x) {
|
||||
using Modifier = OmpReductionClause::Modifier;
|
||||
Walk(std::get<std::optional<std::list<Modifier>>>(x.t), ":");
|
||||
Walk(std::get<std::optional<std::list<Modifier>>>(x.t), ": ");
|
||||
Walk(std::get<OmpObjectList>(x.t));
|
||||
}
|
||||
void Unparse(const OmpDetachClause &x) { Walk(x.v); }
|
||||
void Unparse(const OmpInReductionClause &x) {
|
||||
Walk(std::get<OmpReductionIdentifier>(x.t));
|
||||
Put(":");
|
||||
using Modifier = OmpInReductionClause::Modifier;
|
||||
Walk(std::get<std::optional<std::list<Modifier>>>(x.t), ": ");
|
||||
Walk(std::get<OmpObjectList>(x.t));
|
||||
}
|
||||
void Unparse(const OmpTaskReductionClause &x) {
|
||||
using Modifier = OmpTaskReductionClause::Modifier;
|
||||
Walk(std::get<std::optional<std::list<Modifier>>>(x.t), ": ");
|
||||
Walk(std::get<OmpObjectList>(x.t));
|
||||
}
|
||||
void Unparse(const OmpAllocateClause &x) {
|
||||
|
@ -2841,7 +2841,6 @@ CHECK_SIMPLE_CLAUSE(Grainsize, OMPC_grainsize)
|
||||
CHECK_SIMPLE_CLAUSE(Hint, OMPC_hint)
|
||||
CHECK_SIMPLE_CLAUSE(Holds, OMPC_holds)
|
||||
CHECK_SIMPLE_CLAUSE(Inclusive, OMPC_inclusive)
|
||||
CHECK_SIMPLE_CLAUSE(InReduction, OMPC_in_reduction)
|
||||
CHECK_SIMPLE_CLAUSE(Match, OMPC_match)
|
||||
CHECK_SIMPLE_CLAUSE(Nontemporal, OMPC_nontemporal)
|
||||
CHECK_SIMPLE_CLAUSE(NumTasks, OMPC_num_tasks)
|
||||
@ -2863,7 +2862,6 @@ CHECK_SIMPLE_CLAUSE(ProcBind, OMPC_proc_bind)
|
||||
CHECK_SIMPLE_CLAUSE(Simd, OMPC_simd)
|
||||
CHECK_SIMPLE_CLAUSE(Sizes, OMPC_sizes)
|
||||
CHECK_SIMPLE_CLAUSE(Permutation, OMPC_permutation)
|
||||
CHECK_SIMPLE_CLAUSE(TaskReduction, OMPC_task_reduction)
|
||||
CHECK_SIMPLE_CLAUSE(Uniform, OMPC_uniform)
|
||||
CHECK_SIMPLE_CLAUSE(Unknown, OMPC_unknown)
|
||||
CHECK_SIMPLE_CLAUSE(Untied, OMPC_untied)
|
||||
@ -2978,14 +2976,17 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Reduction &x) {
|
||||
|
||||
if (OmpVerifyModifiers(x.v, llvm::omp::OMPC_reduction,
|
||||
GetContext().clauseSource, context_)) {
|
||||
if (CheckReductionOperators(x)) {
|
||||
CheckReductionTypeList(x);
|
||||
}
|
||||
auto &modifiers{OmpGetModifiers(x.v)};
|
||||
const auto *ident{
|
||||
OmpGetUniqueModifier<parser::OmpReductionIdentifier>(modifiers)};
|
||||
assert(ident && "reduction-identifier is a required modifier");
|
||||
if (CheckReductionOperator(*ident, OmpGetModifierSource(modifiers, ident),
|
||||
llvm::omp::OMPC_reduction)) {
|
||||
CheckReductionObjectTypes(objects, *ident);
|
||||
}
|
||||
using ReductionModifier = parser::OmpReductionModifier;
|
||||
if (auto *maybeModifier{
|
||||
OmpGetUniqueModifier<ReductionModifier>(modifiers)}) {
|
||||
CheckReductionModifier(*maybeModifier);
|
||||
if (auto *modifier{OmpGetUniqueModifier<ReductionModifier>(modifiers)}) {
|
||||
CheckReductionModifier(*modifier);
|
||||
}
|
||||
}
|
||||
CheckReductionObjects(objects, llvm::omp::Clause::OMPC_reduction);
|
||||
@ -2997,70 +2998,88 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Reduction &x) {
|
||||
}
|
||||
}
|
||||
|
||||
bool OmpStructureChecker::CheckReductionOperators(
|
||||
const parser::OmpClause::Reduction &x) {
|
||||
bool ok = false;
|
||||
auto &modifiers{OmpGetModifiers(x.v)};
|
||||
if (const auto *ident{
|
||||
OmpGetUniqueModifier<parser::OmpReductionIdentifier>(modifiers)}) {
|
||||
void OmpStructureChecker::Enter(const parser::OmpClause::InReduction &x) {
|
||||
CheckAllowedClause(llvm::omp::Clause::OMPC_in_reduction);
|
||||
auto &objects{std::get<parser::OmpObjectList>(x.v.t)};
|
||||
|
||||
auto visitOperator{[&](const parser::DefinedOperator &dOpr) {
|
||||
if (const auto *intrinsicOp{
|
||||
std::get_if<parser::DefinedOperator::IntrinsicOperator>(
|
||||
&dOpr.u)}) {
|
||||
ok = CheckIntrinsicOperator(*intrinsicOp);
|
||||
} else {
|
||||
context_.Say(GetContext().clauseSource,
|
||||
"Invalid reduction operator in REDUCTION clause."_err_en_US,
|
||||
ContextDirectiveAsFortran());
|
||||
}
|
||||
}};
|
||||
|
||||
auto visitDesignator{[&](const parser::ProcedureDesignator &procD) {
|
||||
const parser::Name *name{std::get_if<parser::Name>(&procD.u)};
|
||||
if (name && name->symbol) {
|
||||
const SourceName &realName{name->symbol->GetUltimate().name()};
|
||||
if (realName == "max" || realName == "min" || realName == "iand" ||
|
||||
realName == "ior" || realName == "ieor") {
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
context_.Say(GetContext().clauseSource,
|
||||
"Invalid reduction identifier in REDUCTION "
|
||||
"clause."_err_en_US,
|
||||
ContextDirectiveAsFortran());
|
||||
}
|
||||
}};
|
||||
common::visit(common::visitors{visitOperator, visitDesignator}, ident->u);
|
||||
if (OmpVerifyModifiers(x.v, llvm::omp::OMPC_in_reduction,
|
||||
GetContext().clauseSource, context_)) {
|
||||
auto &modifiers{OmpGetModifiers(x.v)};
|
||||
const auto *ident{
|
||||
OmpGetUniqueModifier<parser::OmpReductionIdentifier>(modifiers)};
|
||||
assert(ident && "reduction-identifier is a required modifier");
|
||||
if (CheckReductionOperator(*ident, OmpGetModifierSource(modifiers, ident),
|
||||
llvm::omp::OMPC_in_reduction)) {
|
||||
CheckReductionObjectTypes(objects, *ident);
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
CheckReductionObjects(objects, llvm::omp::Clause::OMPC_in_reduction);
|
||||
}
|
||||
|
||||
bool OmpStructureChecker::CheckIntrinsicOperator(
|
||||
const parser::DefinedOperator::IntrinsicOperator &op) {
|
||||
void OmpStructureChecker::Enter(const parser::OmpClause::TaskReduction &x) {
|
||||
CheckAllowedClause(llvm::omp::Clause::OMPC_task_reduction);
|
||||
auto &objects{std::get<parser::OmpObjectList>(x.v.t)};
|
||||
|
||||
switch (op) {
|
||||
case parser::DefinedOperator::IntrinsicOperator::Add:
|
||||
case parser::DefinedOperator::IntrinsicOperator::Multiply:
|
||||
case parser::DefinedOperator::IntrinsicOperator::AND:
|
||||
case parser::DefinedOperator::IntrinsicOperator::OR:
|
||||
case parser::DefinedOperator::IntrinsicOperator::EQV:
|
||||
case parser::DefinedOperator::IntrinsicOperator::NEQV:
|
||||
return true;
|
||||
case parser::DefinedOperator::IntrinsicOperator::Subtract:
|
||||
context_.Say(GetContext().clauseSource,
|
||||
"The minus reduction operator is deprecated since OpenMP 5.2 and is "
|
||||
"not supported in the REDUCTION clause."_err_en_US,
|
||||
ContextDirectiveAsFortran());
|
||||
break;
|
||||
default:
|
||||
context_.Say(GetContext().clauseSource,
|
||||
"Invalid reduction operator in REDUCTION clause."_err_en_US,
|
||||
ContextDirectiveAsFortran());
|
||||
if (OmpVerifyModifiers(x.v, llvm::omp::OMPC_task_reduction,
|
||||
GetContext().clauseSource, context_)) {
|
||||
auto &modifiers{OmpGetModifiers(x.v)};
|
||||
const auto *ident{
|
||||
OmpGetUniqueModifier<parser::OmpReductionIdentifier>(modifiers)};
|
||||
assert(ident && "reduction-identifier is a required modifier");
|
||||
if (CheckReductionOperator(*ident, OmpGetModifierSource(modifiers, ident),
|
||||
llvm::omp::OMPC_task_reduction)) {
|
||||
CheckReductionObjectTypes(objects, *ident);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
CheckReductionObjects(objects, llvm::omp::Clause::OMPC_task_reduction);
|
||||
}
|
||||
|
||||
bool OmpStructureChecker::CheckReductionOperator(
|
||||
const parser::OmpReductionIdentifier &ident, parser::CharBlock source,
|
||||
llvm::omp::Clause clauseId) {
|
||||
auto visitOperator{[&](const parser::DefinedOperator &dOpr) {
|
||||
if (const auto *intrinsicOp{
|
||||
std::get_if<parser::DefinedOperator::IntrinsicOperator>(&dOpr.u)}) {
|
||||
switch (*intrinsicOp) {
|
||||
case parser::DefinedOperator::IntrinsicOperator::Add:
|
||||
case parser::DefinedOperator::IntrinsicOperator::Multiply:
|
||||
case parser::DefinedOperator::IntrinsicOperator::AND:
|
||||
case parser::DefinedOperator::IntrinsicOperator::OR:
|
||||
case parser::DefinedOperator::IntrinsicOperator::EQV:
|
||||
case parser::DefinedOperator::IntrinsicOperator::NEQV:
|
||||
return true;
|
||||
case parser::DefinedOperator::IntrinsicOperator::Subtract:
|
||||
context_.Say(GetContext().clauseSource,
|
||||
"The minus reduction operator is deprecated since OpenMP 5.2 and is not supported in the REDUCTION clause."_err_en_US,
|
||||
ContextDirectiveAsFortran());
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
context_.Say(source, "Invalid reduction operator in %s clause."_err_en_US,
|
||||
parser::ToUpperCaseLetters(getClauseName(clauseId).str()));
|
||||
return false;
|
||||
}};
|
||||
|
||||
auto visitDesignator{[&](const parser::ProcedureDesignator &procD) {
|
||||
const parser::Name *name{std::get_if<parser::Name>(&procD.u)};
|
||||
bool valid{false};
|
||||
if (name && name->symbol) {
|
||||
const SourceName &realName{name->symbol->GetUltimate().name()};
|
||||
valid =
|
||||
llvm::is_contained({"max", "min", "iand", "ior", "ieor"}, realName);
|
||||
}
|
||||
if (!valid) {
|
||||
context_.Say(source,
|
||||
"Invalid reduction identifier in %s clause."_err_en_US,
|
||||
parser::ToUpperCaseLetters(getClauseName(clauseId).str()));
|
||||
}
|
||||
return valid;
|
||||
}};
|
||||
|
||||
return common::visit(
|
||||
common::visitors{visitOperator, visitDesignator}, ident.u);
|
||||
}
|
||||
|
||||
/// Check restrictions on objects that are common to all reduction clauses.
|
||||
@ -3074,7 +3093,7 @@ void OmpStructureChecker::CheckReductionObjects(
|
||||
for (const parser::OmpObject &object : objects.v) {
|
||||
CheckIfContiguous(object);
|
||||
}
|
||||
CheckReductionArraySection(objects);
|
||||
CheckReductionArraySection(objects, clauseId);
|
||||
// An object must be definable.
|
||||
CheckDefinableObjects(symbols, clauseId);
|
||||
// Procedure pointers are not allowed.
|
||||
@ -3127,100 +3146,82 @@ void OmpStructureChecker::CheckReductionObjects(
|
||||
}
|
||||
|
||||
static bool IsReductionAllowedForType(
|
||||
const parser::OmpClause::Reduction &x, const DeclTypeSpec &type) {
|
||||
auto &modifiers{OmpGetModifiers(x.v)};
|
||||
const auto *definedOp{
|
||||
OmpGetUniqueModifier<parser::OmpReductionIdentifier>(modifiers)};
|
||||
if (!definedOp) {
|
||||
return false;
|
||||
}
|
||||
// TODO: user defined reduction operators. Just allow everything for now.
|
||||
bool ok{true};
|
||||
|
||||
auto IsLogical{[](const DeclTypeSpec &type) -> bool {
|
||||
const parser::OmpReductionIdentifier &ident, const DeclTypeSpec &type) {
|
||||
auto isLogical{[](const DeclTypeSpec &type) -> bool {
|
||||
return type.category() == DeclTypeSpec::Logical;
|
||||
}};
|
||||
auto IsCharacter{[](const DeclTypeSpec &type) -> bool {
|
||||
auto isCharacter{[](const DeclTypeSpec &type) -> bool {
|
||||
return type.category() == DeclTypeSpec::Character;
|
||||
}};
|
||||
|
||||
common::visit(
|
||||
common::visitors{
|
||||
[&](const parser::DefinedOperator &dOpr) {
|
||||
if (const auto *intrinsicOp{
|
||||
std::get_if<parser::DefinedOperator::IntrinsicOperator>(
|
||||
&dOpr.u)}) {
|
||||
// OMP5.2: The type [...] of a list item that appears in a
|
||||
// reduction clause must be valid for the combiner expression
|
||||
// See F2023: Table 10.2
|
||||
// .LT., .LE., .GT., .GE. are handled as procedure designators
|
||||
// below.
|
||||
switch (*intrinsicOp) {
|
||||
case parser::DefinedOperator::IntrinsicOperator::Multiply:
|
||||
[[fallthrough]];
|
||||
case parser::DefinedOperator::IntrinsicOperator::Add:
|
||||
[[fallthrough]];
|
||||
case parser::DefinedOperator::IntrinsicOperator::Subtract:
|
||||
ok = type.IsNumeric(TypeCategory::Integer) ||
|
||||
type.IsNumeric(TypeCategory::Real) ||
|
||||
type.IsNumeric(TypeCategory::Complex);
|
||||
break;
|
||||
auto checkOperator{[&](const parser::DefinedOperator &dOpr) {
|
||||
if (const auto *intrinsicOp{
|
||||
std::get_if<parser::DefinedOperator::IntrinsicOperator>(&dOpr.u)}) {
|
||||
// OMP5.2: The type [...] of a list item that appears in a
|
||||
// reduction clause must be valid for the combiner expression
|
||||
// See F2023: Table 10.2
|
||||
// .LT., .LE., .GT., .GE. are handled as procedure designators
|
||||
// below.
|
||||
switch (*intrinsicOp) {
|
||||
case parser::DefinedOperator::IntrinsicOperator::Multiply:
|
||||
case parser::DefinedOperator::IntrinsicOperator::Add:
|
||||
case parser::DefinedOperator::IntrinsicOperator::Subtract:
|
||||
return type.IsNumeric(TypeCategory::Integer) ||
|
||||
type.IsNumeric(TypeCategory::Real) ||
|
||||
type.IsNumeric(TypeCategory::Complex);
|
||||
|
||||
case parser::DefinedOperator::IntrinsicOperator::AND:
|
||||
[[fallthrough]];
|
||||
case parser::DefinedOperator::IntrinsicOperator::OR:
|
||||
[[fallthrough]];
|
||||
case parser::DefinedOperator::IntrinsicOperator::EQV:
|
||||
[[fallthrough]];
|
||||
case parser::DefinedOperator::IntrinsicOperator::NEQV:
|
||||
ok = IsLogical(type);
|
||||
break;
|
||||
case parser::DefinedOperator::IntrinsicOperator::AND:
|
||||
case parser::DefinedOperator::IntrinsicOperator::OR:
|
||||
case parser::DefinedOperator::IntrinsicOperator::EQV:
|
||||
case parser::DefinedOperator::IntrinsicOperator::NEQV:
|
||||
return isLogical(type);
|
||||
|
||||
// Reduction identifier is not in OMP5.2 Table 5.2
|
||||
default:
|
||||
DIE("This should have been caught in CheckIntrinsicOperator");
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
[&](const parser::ProcedureDesignator &procD) {
|
||||
const parser::Name *name{std::get_if<parser::Name>(&procD.u)};
|
||||
if (name && name->symbol) {
|
||||
const SourceName &realName{name->symbol->GetUltimate().name()};
|
||||
// OMP5.2: The type [...] of a list item that appears in a
|
||||
// reduction clause must be valid for the combiner expression
|
||||
if (realName == "iand" || realName == "ior" ||
|
||||
realName == "ieor") {
|
||||
// IAND: arguments must be integers: F2023 16.9.100
|
||||
// IEOR: arguments must be integers: F2023 16.9.106
|
||||
// IOR: arguments must be integers: F2023 16.9.111
|
||||
ok = type.IsNumeric(TypeCategory::Integer);
|
||||
} else if (realName == "max" || realName == "min") {
|
||||
// MAX: arguments must be integer, real, or character:
|
||||
// F2023 16.9.135
|
||||
// MIN: arguments must be integer, real, or character:
|
||||
// F2023 16.9.141
|
||||
ok = type.IsNumeric(TypeCategory::Integer) ||
|
||||
type.IsNumeric(TypeCategory::Real) || IsCharacter(type);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
definedOp->u);
|
||||
// Reduction identifier is not in OMP5.2 Table 5.2
|
||||
default:
|
||||
DIE("This should have been caught in CheckIntrinsicOperator");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}};
|
||||
|
||||
return ok;
|
||||
auto checkDesignator{[&](const parser::ProcedureDesignator &procD) {
|
||||
const parser::Name *name{std::get_if<parser::Name>(&procD.u)};
|
||||
if (name && name->symbol) {
|
||||
const SourceName &realName{name->symbol->GetUltimate().name()};
|
||||
// OMP5.2: The type [...] of a list item that appears in a
|
||||
// reduction clause must be valid for the combiner expression
|
||||
if (realName == "iand" || realName == "ior" || realName == "ieor") {
|
||||
// IAND: arguments must be integers: F2023 16.9.100
|
||||
// IEOR: arguments must be integers: F2023 16.9.106
|
||||
// IOR: arguments must be integers: F2023 16.9.111
|
||||
return type.IsNumeric(TypeCategory::Integer);
|
||||
} else if (realName == "max" || realName == "min") {
|
||||
// MAX: arguments must be integer, real, or character:
|
||||
// F2023 16.9.135
|
||||
// MIN: arguments must be integer, real, or character:
|
||||
// F2023 16.9.141
|
||||
return type.IsNumeric(TypeCategory::Integer) ||
|
||||
type.IsNumeric(TypeCategory::Real) || isCharacter(type);
|
||||
}
|
||||
}
|
||||
// TODO: user defined reduction operators. Just allow everything for now.
|
||||
return true;
|
||||
}};
|
||||
|
||||
return common::visit(
|
||||
common::visitors{checkOperator, checkDesignator}, ident.u);
|
||||
}
|
||||
|
||||
void OmpStructureChecker::CheckReductionTypeList(
|
||||
const parser::OmpClause::Reduction &x) {
|
||||
const auto &ompObjectList{std::get<parser::OmpObjectList>(x.v.t)};
|
||||
void OmpStructureChecker::CheckReductionObjectTypes(
|
||||
const parser::OmpObjectList &objects,
|
||||
const parser::OmpReductionIdentifier &ident) {
|
||||
SymbolSourceMap symbols;
|
||||
GetSymbolsInObjectList(ompObjectList, symbols);
|
||||
GetSymbolsInObjectList(objects, symbols);
|
||||
|
||||
for (auto &[symbol, source] : symbols) {
|
||||
if (auto *type{symbol->GetType()}) {
|
||||
if (!IsReductionAllowedForType(x, *type)) {
|
||||
if (!IsReductionAllowedForType(ident, *type)) {
|
||||
context_.Say(source,
|
||||
"The type of '%s' is incompatible with the reduction operator."_err_en_US,
|
||||
symbol->name());
|
||||
@ -3283,13 +3284,12 @@ void OmpStructureChecker::CheckReductionModifier(
|
||||
}
|
||||
|
||||
void OmpStructureChecker::CheckReductionArraySection(
|
||||
const parser::OmpObjectList &ompObjectList) {
|
||||
const parser::OmpObjectList &ompObjectList, llvm::omp::Clause clauseId) {
|
||||
for (const auto &ompObject : ompObjectList.v) {
|
||||
if (const auto *dataRef{parser::Unwrap<parser::DataRef>(ompObject)}) {
|
||||
if (const auto *arrayElement{
|
||||
parser::Unwrap<parser::ArrayElement>(ompObject)}) {
|
||||
CheckArraySection(*arrayElement, GetLastName(*dataRef),
|
||||
llvm::omp::Clause::OMPC_reduction);
|
||||
CheckArraySection(*arrayElement, GetLastName(*dataRef), clauseId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -230,10 +230,10 @@ private:
|
||||
std::int64_t GetOrdCollapseLevel(const parser::OpenMPLoopConstruct &x);
|
||||
void CheckReductionObjects(
|
||||
const parser::OmpObjectList &objects, llvm::omp::Clause clauseId);
|
||||
bool CheckReductionOperators(const parser::OmpClause::Reduction &);
|
||||
bool CheckIntrinsicOperator(
|
||||
const parser::DefinedOperator::IntrinsicOperator &);
|
||||
void CheckReductionTypeList(const parser::OmpClause::Reduction &);
|
||||
bool CheckReductionOperator(const parser::OmpReductionIdentifier &ident,
|
||||
parser::CharBlock source, llvm::omp::Clause clauseId);
|
||||
void CheckReductionObjectTypes(const parser::OmpObjectList &objects,
|
||||
const parser::OmpReductionIdentifier &ident);
|
||||
void CheckReductionModifier(const parser::OmpReductionModifier &);
|
||||
void CheckMasterNesting(const parser::OpenMPBlockConstruct &x);
|
||||
void ChecksOnOrderedAsBlock();
|
||||
@ -241,7 +241,8 @@ private:
|
||||
void CheckScan(const parser::OpenMPSimpleStandaloneConstruct &x);
|
||||
void ChecksOnOrderedAsStandalone();
|
||||
void CheckOrderedDependClause(std::optional<std::int64_t> orderedValue);
|
||||
void CheckReductionArraySection(const parser::OmpObjectList &ompObjectList);
|
||||
void CheckReductionArraySection(
|
||||
const parser::OmpObjectList &ompObjectList, llvm::omp::Clause clauseId);
|
||||
void CheckArraySection(const parser::ArrayElement &arrayElement,
|
||||
const parser::Name &name, const llvm::omp::Clause clause);
|
||||
void CheckSharedBindingInOuterContext(
|
||||
|
@ -5,16 +5,16 @@
|
||||
|
||||
subroutine omp_in_reduction_taskgroup()
|
||||
integer :: z, i
|
||||
!CHECK: !$OMP TASKGROUP TASK_REDUCTION(+:z)
|
||||
!CHECK: !$OMP TASKGROUP TASK_REDUCTION(+: z)
|
||||
!$omp taskgroup task_reduction(+:z)
|
||||
!CHECK-NEXT: !$OMP TASK IN_REDUCTION(+:z)
|
||||
!CHECK-NEXT: !$OMP TASK IN_REDUCTION(+: z)
|
||||
!$omp task in_reduction(+:z)
|
||||
!CHECK-NEXT: z=z+5_4
|
||||
z = z + 5
|
||||
!CHECK-NEXT: !$OMP END TASK
|
||||
!$omp end task
|
||||
|
||||
!CHECK-NEXT: !$OMP TASKLOOP IN_REDUCTION(+:z)
|
||||
!CHECK-NEXT: !$OMP TASKLOOP IN_REDUCTION(+: z)
|
||||
!$omp taskloop in_reduction(+:z)
|
||||
!CHECK-NEXT: DO i=1_4,10_4
|
||||
do i=1,10
|
||||
@ -31,7 +31,7 @@ end subroutine omp_in_reduction_taskgroup
|
||||
!PARSE-TREE: OpenMPConstruct -> OpenMPBlockConstruct
|
||||
!PARSE-TREE-NEXT: OmpBeginBlockDirective
|
||||
!PARSE-TREE-NEXT: OmpBlockDirective -> llvm::omp::Directive = taskgroup
|
||||
!PARSE-TREE-NEXT: OmpClauseList -> OmpClause -> TaskReduction -> OmpReductionClause
|
||||
!PARSE-TREE-NEXT: OmpClauseList -> OmpClause -> TaskReduction -> OmpTaskReductionClause
|
||||
|
||||
!PARSE-TREE: OpenMPConstruct -> OpenMPBlockConstruct
|
||||
!PARSE-TREE-NEXT: OmpBeginBlockDirective
|
||||
@ -49,9 +49,9 @@ end subroutine omp_in_reduction_taskgroup
|
||||
|
||||
subroutine omp_in_reduction_parallel()
|
||||
integer :: z
|
||||
!CHECK: !$OMP PARALLEL REDUCTION(+:z)
|
||||
!CHECK: !$OMP PARALLEL REDUCTION(+: z)
|
||||
!$omp parallel reduction(+:z)
|
||||
!CHECK-NEXT: !$OMP TASKLOOP SIMD IN_REDUCTION(+:z)
|
||||
!CHECK-NEXT: !$OMP TASKLOOP SIMD IN_REDUCTION(+: z)
|
||||
!$omp taskloop simd in_reduction(+:z)
|
||||
!CHECK-NEXT: DO i=1_4,10_4
|
||||
do i=1,10
|
||||
|
@ -4,7 +4,7 @@
|
||||
subroutine foo()
|
||||
integer :: i, j
|
||||
j = 0
|
||||
! CHECK: !$OMP DO REDUCTION(TASK, *:j)
|
||||
! CHECK: !$OMP DO REDUCTION(TASK, *: j)
|
||||
! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
|
||||
! PARSE-TREE: | | | OmpBeginLoopDirective
|
||||
! PARSE-TREE: | | | | OmpLoopDirective -> llvm::omp::Directive = do
|
||||
|
23
flang/test/Parser/OpenMP/task-reduction-clause.f90
Normal file
23
flang/test/Parser/OpenMP/task-reduction-clause.f90
Normal file
@ -0,0 +1,23 @@
|
||||
!RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=50 %s | FileCheck --ignore-case --check-prefix="UNPARSE" %s
|
||||
!RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=50 %s | FileCheck --check-prefix="PARSE-TREE" %s
|
||||
|
||||
subroutine f00
|
||||
integer :: x
|
||||
!$omp taskgroup task_reduction(+: x)
|
||||
x = x + 1
|
||||
!$omp end taskgroup
|
||||
end
|
||||
|
||||
!UNPARSE: SUBROUTINE f00
|
||||
!UNPARSE: INTEGER x
|
||||
!UNPARSE: !$OMP TASKGROUP TASK_REDUCTION(+: x)
|
||||
!UNPARSE: x=x+1_4
|
||||
!UNPARSE: !$OMP END TASKGROUP
|
||||
!UNPARSE: END SUBROUTINE
|
||||
|
||||
!PARSE-TREE: OmpBeginBlockDirective
|
||||
!PARSE-TREE: | OmpBlockDirective -> llvm::omp::Directive = taskgroup
|
||||
!PARSE-TREE: | OmpClauseList -> OmpClause -> TaskReduction -> OmpTaskReductionClause
|
||||
!PARSE-TREE: | | Modifier -> OmpReductionIdentifier -> DefinedOperator -> IntrinsicOperator = Add
|
||||
!PARSE-TREE: | | OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'x'
|
||||
!PARSE-TREE: Block
|
@ -70,13 +70,13 @@ end module
|
||||
!CHECK: !DIR$ IGNORE_TKR x5
|
||||
!CHECK: !DIR$ IGNORE_TKR x6
|
||||
!CHECK: STOP 1_4
|
||||
!CHECK: !$OMP PARALLEL DO REDUCTION(+:x)
|
||||
!CHECK: !$OMP PARALLEL DO REDUCTION(+: x)
|
||||
!CHECK: DO j1=1_4,n
|
||||
!CHECK: END DO
|
||||
!CHECK: !$OMP PARALLEL DO REDUCTION(+:x)
|
||||
!CHECK: !$OMP PARALLEL DO REDUCTION(+: x)
|
||||
!CHECK: DO j2=1_4,n
|
||||
!CHECK: END DO
|
||||
!CHECK: !$OMP PARALLEL DO REDUCTION(+:x)
|
||||
!CHECK: !$OMP PARALLEL DO REDUCTION(+: x)
|
||||
!CHECK: DO j3=1_4,n
|
||||
!CHECK: END DO
|
||||
!CHECK: END SUBROUTINE
|
||||
|
70
flang/test/Semantics/OpenMP/in-reduction.f90
Normal file
70
flang/test/Semantics/OpenMP/in-reduction.f90
Normal file
@ -0,0 +1,70 @@
|
||||
!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=50
|
||||
|
||||
subroutine f00
|
||||
real :: x
|
||||
!ERROR: The type of 'x' is incompatible with the reduction operator.
|
||||
!$omp target in_reduction(.or.: x)
|
||||
!$omp end target
|
||||
end
|
||||
|
||||
subroutine f01
|
||||
real :: x
|
||||
!ERROR: Invalid reduction operator in IN_REDUCTION clause.
|
||||
!$omp target in_reduction(.not.: x)
|
||||
!$omp end target
|
||||
end
|
||||
|
||||
subroutine f02(p)
|
||||
integer, pointer, intent(in) :: p
|
||||
!ERROR: Pointer 'p' with the INTENT(IN) attribute may not appear in a IN_REDUCTION clause
|
||||
!$omp target in_reduction(+: p)
|
||||
!$omp end target
|
||||
end
|
||||
|
||||
subroutine f03
|
||||
common /c/ a, b
|
||||
!ERROR: Common block names are not allowed in IN_REDUCTION clause
|
||||
!$omp target in_reduction(+: /c/)
|
||||
!$omp end target
|
||||
end
|
||||
|
||||
subroutine f04
|
||||
integer :: x(10)
|
||||
!ERROR: Reference to 'x' must be a contiguous object
|
||||
!$omp target in_reduction(+: x(1:10:2))
|
||||
!$omp end target
|
||||
end
|
||||
|
||||
subroutine f05
|
||||
integer :: x(10)
|
||||
!ERROR: 'x' in IN_REDUCTION clause is a zero size array section
|
||||
!$omp target in_reduction(+: x(1:0))
|
||||
!$omp end target
|
||||
end
|
||||
|
||||
subroutine f06
|
||||
type t
|
||||
integer :: a(10)
|
||||
end type
|
||||
type(t) :: x
|
||||
!ERROR: The base expression of an array element or section in IN_REDUCTION clause must be an identifier
|
||||
!$omp target in_reduction(+: x%a(2))
|
||||
!$omp end target
|
||||
end
|
||||
|
||||
subroutine f07
|
||||
type t
|
||||
integer :: a(10)
|
||||
end type
|
||||
type(t) :: x
|
||||
!ERROR: The base expression of an array element or section in IN_REDUCTION clause must be an identifier
|
||||
!$omp target in_reduction(+: x%a(1:10))
|
||||
!$omp end target
|
||||
end
|
||||
|
||||
subroutine f08
|
||||
integer :: x
|
||||
!ERROR: Type parameter inquiry is not permitted in IN_REDUCTION clause
|
||||
!$omp target in_reduction(+: x%kind)
|
||||
!$omp end target
|
||||
end
|
@ -130,13 +130,14 @@ subroutine dotprod (b, c, n, block_size, num_teams, block_threads)
|
||||
!REF: /dotprod/sum
|
||||
sum = 0.0e0
|
||||
!$omp target map(to:b,c) map(tofrom:sum)
|
||||
!$omp teams num_teams(num_teams) thread_limit(block_threads) reduction(+:sum)
|
||||
!$omp teams num_teams(num_teams) thread_limit(block_threads) reduction(+: sum&
|
||||
!$OMP&)
|
||||
!$omp distribute
|
||||
!DEF: /dotprod/OtherConstruct1/OtherConstruct1/OtherConstruct1/i0 (OmpPrivate, OmpPreDetermined) HostAssoc INTEGER(4)
|
||||
!REF: /dotprod/n
|
||||
!REF: /dotprod/block_size
|
||||
do i0=1,n,block_size
|
||||
!$omp parallel do reduction(+:sum)
|
||||
!$omp parallel do reduction(+: sum)
|
||||
!DEF: /dotprod/OtherConstruct1/OtherConstruct1/OtherConstruct1/OtherConstruct1/i (OmpPrivate, OmpPreDetermined) HostAssoc INTEGER(4)
|
||||
!DEF: /dotprod/OtherConstruct1/OtherConstruct1/OtherConstruct1/OtherConstruct1/i0 HostAssoc INTEGER(4)
|
||||
!DEF: /dotprod/min ELEMENTAL, INTRINSIC, PURE (Function) ProcEntity
|
||||
|
70
flang/test/Semantics/OpenMP/task-reduction.f90
Normal file
70
flang/test/Semantics/OpenMP/task-reduction.f90
Normal file
@ -0,0 +1,70 @@
|
||||
!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=50
|
||||
|
||||
subroutine f00
|
||||
real :: x
|
||||
!ERROR: The type of 'x' is incompatible with the reduction operator.
|
||||
!$omp taskgroup task_reduction(.or.: x)
|
||||
!$omp end taskgroup
|
||||
end
|
||||
|
||||
subroutine f01
|
||||
real :: x
|
||||
!ERROR: Invalid reduction operator in TASK_REDUCTION clause.
|
||||
!$omp taskgroup task_reduction(.not.: x)
|
||||
!$omp end taskgroup
|
||||
end
|
||||
|
||||
subroutine f02(p)
|
||||
integer, pointer, intent(in) :: p
|
||||
!ERROR: Pointer 'p' with the INTENT(IN) attribute may not appear in a TASK_REDUCTION clause
|
||||
!$omp taskgroup task_reduction(+: p)
|
||||
!$omp end taskgroup
|
||||
end
|
||||
|
||||
subroutine f03
|
||||
common /c/ a, b
|
||||
!ERROR: Common block names are not allowed in TASK_REDUCTION clause
|
||||
!$omp taskgroup task_reduction(+: /c/)
|
||||
!$omp end taskgroup
|
||||
end
|
||||
|
||||
subroutine f04
|
||||
integer :: x(10)
|
||||
!ERROR: Reference to 'x' must be a contiguous object
|
||||
!$omp taskgroup task_reduction(+: x(1:10:2))
|
||||
!$omp end taskgroup
|
||||
end
|
||||
|
||||
subroutine f05
|
||||
integer :: x(10)
|
||||
!ERROR: 'x' in TASK_REDUCTION clause is a zero size array section
|
||||
!$omp taskgroup task_reduction(+: x(1:0))
|
||||
!$omp end taskgroup
|
||||
end
|
||||
|
||||
subroutine f06
|
||||
type t
|
||||
integer :: a(10)
|
||||
end type
|
||||
type(t) :: x
|
||||
!ERROR: The base expression of an array element or section in TASK_REDUCTION clause must be an identifier
|
||||
!$omp taskgroup task_reduction(+: x%a(2))
|
||||
!$omp end taskgroup
|
||||
end
|
||||
|
||||
subroutine f07
|
||||
type t
|
||||
integer :: a(10)
|
||||
end type
|
||||
type(t) :: x
|
||||
!ERROR: The base expression of an array element or section in TASK_REDUCTION clause must be an identifier
|
||||
!$omp taskgroup task_reduction(+: x%a(1:10))
|
||||
!$omp end taskgroup
|
||||
end
|
||||
|
||||
subroutine f08
|
||||
integer :: x
|
||||
!ERROR: Type parameter inquiry is not permitted in TASK_REDUCTION clause
|
||||
!$omp taskgroup task_reduction(+: x%kind)
|
||||
!$omp end taskgroup
|
||||
end
|
@ -41,6 +41,8 @@ use omp_lib
|
||||
!$omp task
|
||||
!$omp taskgroup task_reduction(+ : reduction_var)
|
||||
print *, "The "
|
||||
!ERROR: The type of 'reduction_var' is incompatible with the reduction operator.
|
||||
!ERROR: The type of 'reduction_var' is incompatible with the reduction operator.
|
||||
!$omp taskgroup task_reduction(.or. : reduction_var) task_reduction(.and. : reduction_var)
|
||||
print *, "almighty sun"
|
||||
!$omp end taskgroup
|
||||
|
@ -464,7 +464,7 @@ def OMPC_Sizes: Clause<"sizes"> {
|
||||
}
|
||||
def OMPC_TaskReduction : Clause<"task_reduction"> {
|
||||
let clangClass = "OMPTaskReductionClause";
|
||||
let flangClass = "OmpReductionClause";
|
||||
let flangClass = "OmpTaskReductionClause";
|
||||
}
|
||||
def OMPC_ThreadLimit : Clause<"thread_limit"> {
|
||||
let clangClass = "OMPThreadLimitClause";
|
||||
|
Loading…
x
Reference in New Issue
Block a user