Author: Zahira Ammarguellat Date: 2026-10-26T04:30:54-04:00 New Revision: c2d9a6f71ebd7587a53bb50905ff657141a3ad6b
URL: https://github.com/llvm/llvm-project/commit/c2d9a6f71ebd7587a53bb50905ff657141a3ad6b DIFF: https://github.com/llvm/llvm-project/commit/c2d9a6f71ebd7587a53bb50905ff657141a3ad6b.diff LOG: [OpenMP] Add runtime selection for metadirective with non-constant conditions. (#192455) This patch implements runtime evaluation and selection for `OpenMP` metadirectives when user conditions are non-constant expressions. Added: clang/test/OpenMP/metadirective_runtime_messages.cpp clang/test/OpenMP/metadirective_user_condition_parse.cpp clang/test/OpenMP/metadirective_variant_clauses.cpp Modified: clang/include/clang/Basic/DiagnosticParseKinds.td clang/include/clang/Basic/LangOptions.def clang/include/clang/Options/Options.td clang/include/clang/Sema/SemaOpenMP.h clang/lib/Parse/ParseOpenMP.cpp clang/lib/Sema/SemaOpenMP.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index b73af2f88f5b7e..8b6cf3455facae 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1565,6 +1565,9 @@ def err_omp_expected_context_selector : Error<"expected valid context selector in %0">; def warn_omp_default_deprecated : Warning<"'default' clause for" " 'metadirective' is deprecated; use 'otherwise' instead">, InGroup<Deprecated>; +def err_omp_metadirective_standalone_variant : Error< + "standalone directive variants are not supported in a 'metadirective' " + "with non-constant user conditions">; def err_omp_requires_out_inout_depend_type : Error< "reserved locator 'omp_all_memory' requires 'out' or 'inout' " "dependency types">; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 155d365771b836..b4a57a9c0944b1 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -243,6 +243,7 @@ LANGOPT(OpenMPNoNestedParallelism , 1, 0, NotCompatible, "Assume that no thread LANGOPT(OpenMPOffloadMandatory , 1, 0, NotCompatible, "Assert that offloading is mandatory and do not create a host fallback.") LANGOPT(OpenMPForceUSM , 1, 0, NotCompatible, "Enable OpenMP unified shared memory mode via compiler.") LANGOPT(OpenMPTargetAtomicReduction , 1, 0, NotCompatible, "Use atomic operations for OpenMP GPU cross-team reductions where supported.") +LANGOPT(OpenMPMetadirectiveRuntimeSelection , 1, 0, NotCompatible, "Enable runtime selection of metadirective variants with non-constant user conditions.") LANGOPT(NoGPULib , 1, 0, NotCompatible, "Indicate a build without the standard GPU libraries.") LANGOPT(HLSL, 1, 0, NotCompatible, "HLSL") diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index b760c3a1213460..4419e3cf6f98c5 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -9297,6 +9297,13 @@ def fopenmp_host_ir_file_path : Separate<["-"], "fopenmp-host-ir-file-path">, } // let Visibility = [CC1Option, FC1Option] +def fopenmp_metadirective_runtime_selection + : Flag<["-"], "fopenmp-metadirective-runtime-selection">, + Visibility<[CC1Option]>, + HelpText<"Enable runtime selection of metadirective variants with " + "non-constant user conditions.">, + MarshallingInfoFlag<LangOpts<"OpenMPMetadirectiveRuntimeSelection">>; + //===----------------------------------------------------------------------===// // SYCL Options //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/Sema/SemaOpenMP.h b/clang/include/clang/Sema/SemaOpenMP.h index 36c43cd2be0a25..89cc9364c6f12e 100644 --- a/clang/include/clang/Sema/SemaOpenMP.h +++ b/clang/include/clang/Sema/SemaOpenMP.h @@ -217,6 +217,14 @@ class SemaOpenMP : public SemaBase { Stmt *AStmt, SourceLocation StartLoc, SourceLocation EndLoc); + /// Called for metadirectives with user conditions that require runtime + /// selection. \p Variants holds one complete directive (or the plain + /// associated statement) per entry of \p TraitInfos. + StmtResult ActOnOpenMPMetaDirective(SourceLocation StartLoc, + SourceLocation EndLoc, + ArrayRef<OMPTraitInfo *> TraitInfos, + ArrayRef<Stmt *> Variants); + // OpenMP directives and clauses. /// Called on correct id-expression from the '#pragma omp /// threadprivate'. diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index 95c26f35fff03a..149ca7d9c0d594 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -2614,10 +2614,11 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( case OMPD_metadirective: { ConsumeToken(); SmallVector<VariantMatchInfo, 4> VMIs; + SmallVector<OMPTraitInfo *, 4> TraitInfos; // First iteration of parsing all clauses of metadirective. - // This iteration only parses and collects all context selector ignoring the - // associated directives. + // This iteration only parses and collects all context selectors ignoring + // the associated directives. TentativeParsingAction TPA(*this); ASTContext &ASTContext = Actions.getASTContext(); @@ -2693,8 +2694,10 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( TI.getAsVariantMatchInfo(ASTContext, VMI); VMIs.push_back(VMI); + TraitInfos.push_back(&TI); } + SourceLocation EndLoc = Tok.getLocation(); TPA.Revert(); // End of the first iteration. Parser is reset to the start of metadirective @@ -2712,6 +2715,91 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( // A single match is returned for OpenMP 5.0 int BestIdx = getBestVariantMatchForContext(VMIs, OMPCtx); + // Check if we have user conditions with non-constant expressions that + // require runtime selection. + bool HasNonConstUserCondition = + llvm::any_of(TraitInfos, [&](OMPTraitInfo *TI) { + return TI->anyScoreOrCondition([&](Expr *&E, bool IsScore) { + return !IsScore && E && !E->getIntegerConstantExpr(ASTContext); + }); + }); + + if (HasNonConstUserCondition && + getLangOpts().OpenMPMetadirectiveRuntimeSelection) { + // OpenMP 5.2, 7.4: without an 'otherwise' clause the effect is as if one + // was specified without an associated directive variant. Only + // 'otherwise' (or the deprecated 'default') has no context selectors. + unsigned NumClauses = TraitInfos.size(); + if (llvm::none_of(TraitInfos, [](const OMPTraitInfo *TI) { + return TI->Sets.empty(); + })) + TraitInfos.push_back(&ASTContext.getNewOMPTraitInfo()); + + // Reuse the normal metadirective variant parse per clause so every + // variant is a complete directive node with its own body and its own + // data-sharing context. Diagnostics in the associated statement are + // reported once per variant. + SmallVector<Stmt *, 4> Variants; + bool HasError = false; + for (unsigned I : llvm::seq<unsigned>(TraitInfos.size())) { + bool HasDirective = false; + if (I < NumClauses) { + OpenMPClauseKind CKind = getOpenMPClauseKind(PP.getSpelling(Tok)); + ConsumeToken(); + T.consumeOpen(); + if (CKind == OMPC_when) { + // The selectors were parsed into TraitInfos[I] by the first pass; + // skip them so their diagnostics are not repeated. + SkipUntil(tok::colon, StopBeforeMatch); + TryConsumeToken(tok::colon); + } + HasDirective = Tok.isNot(tok::r_paren) && + Tok.isNot(tok::annot_pragma_openmp_end); + } + // Re-parse the directive and body for every variant but the last. + std::optional<TentativeParsingAction> VariantTPA; + if (I + 1 < TraitInfos.size()) + VariantTPA.emplace(*this, /*Unannotated=*/true); + + StmtResult Variant; + if (HasDirective) { + Variant = ParseOpenMPDeclarativeOrExecutableDirective( + StmtCtx, /*ReadDirectiveWithinMetadirective=*/true); + } else { + // No directive variant: skip to the end of the pragma. + while (Tok.isNot(tok::annot_pragma_openmp_end)) + ConsumeAnyToken(); + ConsumeAnnotationToken(); + } + // No directive or 'nothing': the variant is the associated statement. + if (!Variant.isInvalid() && !Variant.isUsable()) { + ParsingOpenMPDirectiveRAII NormalScope(*this, /*Value=*/false); + Variant = ParseStatement(); + } + // A standalone variant does not take the associated statement while + // the others do; reject the mix until Sema models it. + if (auto *D = dyn_cast_or_null<OMPExecutableDirective>(Variant.get()); + D && !D->hasAssociatedStmt()) { + Diag(Loc, diag::err_omp_metadirective_standalone_variant); + Variant = StmtError(); + } + + if (VariantTPA) { + VariantTPA->Revert(); + SkipUntil(tok::r_paren, StopBeforeMatch); + T.consumeClose(); + } + if (Variant.isInvalid()) + HasError = true; + else + Variants.push_back(Variant.get()); + } + if (HasError) + return StmtError(); + return Actions.OpenMP().ActOnOpenMPMetaDirective(Loc, EndLoc, TraitInfos, + Variants); + } + int Idx = 0; // In OpenMP 5.0 metadirective is either replaced by another directive or // ignored. diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp index d5dc3266973555..0963d61df2410d 100644 --- a/clang/lib/Sema/SemaOpenMP.cpp +++ b/clang/lib/Sema/SemaOpenMP.cpp @@ -3843,6 +3843,19 @@ StmtResult SemaOpenMP::ActOnOpenMPAssumeDirective(ArrayRef<OMPClause *> Clauses, AStmt); } +StmtResult SemaOpenMP::ActOnOpenMPMetaDirective( + SourceLocation StartLoc, SourceLocation EndLoc, + ArrayRef<OMPTraitInfo *> TraitInfos, ArrayRef<Stmt *> Variants) { + assert(TraitInfos.size() == Variants.size() && + "Expected one variant per metadirective clause"); + // TODO Phase 3: Build OMPMetaDirective AST node holding all variants. + // TODO Phase 4: Codegen - generate runtime if-else chain. + // Temporary stub: return the first variant until OMPMetaDirective exists. + if (Variants.empty()) + return StmtError(); + return Variants.front(); +} + OMPRequiresDecl * SemaOpenMP::CheckOMPRequiresDecl(SourceLocation Loc, ArrayRef<OMPClause *> ClauseList) { diff --git a/clang/test/OpenMP/metadirective_runtime_messages.cpp b/clang/test/OpenMP/metadirective_runtime_messages.cpp new file mode 100644 index 00000000000000..7419fbe010e19a --- /dev/null +++ b/clang/test/OpenMP/metadirective_runtime_messages.cpp @@ -0,0 +1,72 @@ +// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=52 -std=c++11 \ +// RUN: -fopenmp-metadirective-runtime-selection -fsyntax-only %s + +// Body diagnostics are reported once per variant. +void body_error_per_variant(int flag) { +#pragma omp metadirective \ + when(user={condition(flag)}: parallel) \ + otherwise(single) + { + undeclared = 1; // expected-error 2 {{use of undeclared identifier 'undeclared'}} + } +} + +// Without 'otherwise', the implicit plain-statement variant is parsed too. +void body_error_no_otherwise(int flag) { +#pragma omp metadirective \ + when(user={condition(flag)}: parallel) + { + undeclared2 = 1; // expected-error 2 {{use of undeclared identifier 'undeclared2'}} + } +} + +// Variant-specific errors are reported for the variant they apply to. +void variant_specific_errors(int flag) { +#pragma omp metadirective \ + when(user={condition(flag)}: for) \ + otherwise(single) + for (int i = 0; i < 10; ++i) { +#pragma omp barrier // expected-error {{region cannot be closely nested inside 'for' region}} expected-error {{region cannot be closely nested inside 'single' region}} + } +} + +// A ':' inside the condition (ternary) does not end the selectors. +void ternary_in_condition(int a, int b) { +#pragma omp metadirective \ + when(user={condition(a ? b : 0)}: parallel) \ + otherwise(single) + { + } +} + +// A 'nothing' variant still consumes the associated statement. +void nothing_variant(int flag) { +#pragma omp metadirective \ + when(user={condition(flag)}: nothing) \ + otherwise(single) + { + undeclared3 = 1; // expected-error 2 {{use of undeclared identifier 'undeclared3'}} + } +} + +// Each variant resolves names in the associated statement again. +void local_decl(int flag) { +#pragma omp metadirective \ + when(user={condition(flag)}: parallel) \ + otherwise(single) + { + struct L { int v; }; + L l; + l.v = 1; + } +} + +// Standalone directive variants are rejected for now. +void standalone_variant(int flag) { + // expected-error@+1 {{standalone directive variants are not supported in a 'metadirective' with non-constant user conditions}} +#pragma omp metadirective \ + when(user={condition(flag)}: barrier) \ + otherwise(single) + { + } +} diff --git a/clang/test/OpenMP/metadirective_user_condition_parse.cpp b/clang/test/OpenMP/metadirective_user_condition_parse.cpp new file mode 100644 index 00000000000000..b517c00c2e1680 --- /dev/null +++ b/clang/test/OpenMP/metadirective_user_condition_parse.cpp @@ -0,0 +1,82 @@ +// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=52 -std=c++11 \ +// RUN: -fopenmp-metadirective-runtime-selection -fsyntax-only %s +// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=52 -std=c++11 \ +// RUN: -fsyntax-only %s + +// expected-no-diagnostics + +void test_runtime_condition(int flag) { +#pragma omp metadirective \ + when(user={condition(flag)}: parallel) \ + otherwise(single) + { + int x = 0; + } +} + +void test_two_conditions(int flag1, int flag2) { +#pragma omp metadirective \ + when(user={condition(flag1)}: parallel) \ + when(user={condition(flag2)}: single) \ + otherwise() + { + int y = 1; + } +} + +void test_complex_condition(int a, int b) { +#pragma omp metadirective \ + when(user={condition(a > b)}: parallel) \ + otherwise(single) + { + int z = 2; + } +} + +void test_logical_condition(bool flag1, bool flag2) { +#pragma omp metadirective \ + when(user={condition(flag1 && flag2)}: parallel) \ + otherwise() + { + int w = 3; + } +} + +void test_multiple_variants(int flag1, int flag2, int flag3) { +#pragma omp metadirective \ + when(user={condition(flag1)}: parallel) \ + when(user={condition(flag2)}: single) \ + when(user={condition(flag3)}: teams) \ + otherwise() + { + int v = 4; + } +} + +void test_otherwise_only() { +#pragma omp metadirective otherwise(parallel) + { + int u = 5; + } +} + +void test_ diff erent_directives(int flag) { +#pragma omp metadirective \ + when(user={condition(flag)}: teams) \ + otherwise(task) + { + int t = 6; + } +} + +void test_nested_statement(int flag) { +#pragma omp metadirective \ + when(user={condition(flag)}: parallel) \ + otherwise() + { + for (int i = 0; i < 10; ++i) { + int s = i; + } + } +} + diff --git a/clang/test/OpenMP/metadirective_variant_clauses.cpp b/clang/test/OpenMP/metadirective_variant_clauses.cpp new file mode 100644 index 00000000000000..9a3d0e675a5493 --- /dev/null +++ b/clang/test/OpenMP/metadirective_variant_clauses.cpp @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=52 -std=c++11 \ +// RUN: -fopenmp-metadirective-runtime-selection -fsyntax-only %s +// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=52 -std=c++11 \ +// RUN: -fsyntax-only %s + +// expected-no-diagnostics + +void test_variant_clauses_function_scope(int flag) { + int x = 0; + int y = 0; + +#pragma omp metadirective \ + when(user={condition(flag)}: parallel private(x)) \ + when(user={condition(!flag)}: parallel shared(y)) + { + x = 1; + y = 2; + } +} + +void test_nested_metadirective(int flag1, int flag2) { + int x = 0; + +#pragma omp parallel private(x) + { +#pragma omp metadirective \ + when(user={condition(flag1)}: for) \ + when(user={condition(flag2)}: single private(x)) + for (int i = 0; i < 10; ++i) { + x = i; + } + } +} + +void test_multiple_clauses(int flag) { + int x = 0, y = 0, z = 0; + +#pragma omp metadirective \ + when(user={condition(flag)}: parallel private(x) shared(y) firstprivate(z)) \ + otherwise(single private(x)) + { + x = y + z; + } +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
