https://github.com/zahiraam updated https://github.com/llvm/llvm-project/pull/192455
>From a8a2ba6bb8c246f0b2b0bf57b871d98e4bc597e6 Mon Sep 17 00:00:00 2001 From: Zahira Ammarguellat <[email protected]> Date: Mon, 17 Aug 2026 14:21:21 -0700 Subject: [PATCH 01/10] Implement phase 1 of plan --- clang/include/clang/AST/OpenMPClause.h | 13 ++ clang/include/clang/Sema/SemaOpenMP.h | 8 ++ clang/lib/AST/OpenMPClause.cpp | 4 +- clang/lib/Parse/ParseOpenMP.cpp | 91 +++++++++++- clang/lib/Sema/SemaOpenMP.cpp | 9 ++ .../metadirective_user_condition_parse.cpp | 134 ++++++++++++++++++ .../include/llvm/Frontend/OpenMP/OMPContext.h | 4 + 7 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 clang/test/OpenMP/metadirective_user_condition_parse.cpp diff --git a/clang/include/clang/AST/OpenMPClause.h b/clang/include/clang/AST/OpenMPClause.h index ec84f10956ff43..c39af8d3148f88 100644 --- a/clang/include/clang/AST/OpenMPClause.h +++ b/clang/include/clang/AST/OpenMPClause.h @@ -10129,6 +10129,19 @@ class OMPTraitInfo { return false; } + /// Check if this trait info contains any user conditions. + bool hasUserCondition() const { + for (const OMPTraitSet &Set : Sets) { + if (Set.Kind != llvm::omp::TraitSet::user) + continue; + for (const OMPTraitSelector &Selector : Set.Selectors) { + if (Selector.Kind == llvm::omp::TraitSelector::user_condition) + return true; + } + } + return false; + } + /// Print a human readable representation into \p OS. void print(llvm::raw_ostream &OS, const PrintingPolicy &Policy) const; }; diff --git a/clang/include/clang/Sema/SemaOpenMP.h b/clang/include/clang/Sema/SemaOpenMP.h index a5f357c15f5c40..71a43da569de68 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 may require runtime + /// selection. + StmtResult ActOnOpenMPMetaDirective( + SourceLocation StartLoc, SourceLocation EndLoc, + ArrayRef<OMPTraitInfo *> TraitInfos, + ArrayRef<OpenMPClauseKind> ClauseKinds, + ArrayRef<OpenMPDirectiveKind> DirectiveKinds, Stmt *AssociatedStmt); + // OpenMP directives and clauses. /// Called on correct id-expression from the '#pragma omp /// threadprivate'. diff --git a/clang/lib/AST/OpenMPClause.cpp b/clang/lib/AST/OpenMPClause.cpp index 2061d5395ac658..1a4c03f3bf1b11 100644 --- a/clang/lib/AST/OpenMPClause.cpp +++ b/clang/lib/AST/OpenMPClause.cpp @@ -3131,8 +3131,10 @@ void OMPTraitInfo::getAsVariantMatchInfo(ASTContext &ASTCtx, VMI.addTrait(CondVal->isZero() ? TraitProperty::user_condition_false : TraitProperty::user_condition_true, "<condition>"); - else + else { VMI.addTrait(TraitProperty::user_condition_false, "<condition>"); + VMI.HasNonConstantUserCondition = true; + } continue; } diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index 0ce484c5e907d3..1f2c48f17e9f1d 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -108,6 +108,22 @@ static OpenMPDirectiveKind parseOpenMPDirectiveKind(Parser &P) { return checkOpenMPDirectiveName(P, Loc, S->Value, Concat); } +/// Skip tokens until reaching the matching closing parenthesis. +/// Handles nested parentheses correctly. +static void skipToMatchingParen(Parser &P) { + int ParenDepth = 0; + while ((P.getCurToken().isNot(tok::r_paren) || ParenDepth != 0) && + P.getCurToken().isNot(tok::annot_pragma_openmp_end) && + P.getCurToken().isNot(tok::eof)) { + if (P.getCurToken().is(tok::l_paren)) + ParenDepth++; + if (P.getCurToken().is(tok::r_paren) && ParenDepth > 0) + ParenDepth--; + if (ParenDepth > 0 || P.getCurToken().isNot(tok::r_paren)) + P.ConsumeAnyToken(); + } +} + static DeclarationName parseOpenMPReductionId(Parser &P) { Token Tok = P.getCurToken(); Sema &Actions = P.getActions(); @@ -2614,10 +2630,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,6 +2710,7 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( TI.getAsVariantMatchInfo(ASTContext, VMI); VMIs.push_back(VMI); + TraitInfos.push_back(&TI); } TPA.Revert(); @@ -2712,6 +2730,75 @@ 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 HasUserCondition = false; + for (const VariantMatchInfo &VMI : VMIs) { + if (VMI.HasNonConstantUserCondition) { + HasUserCondition = true; + break; + } + } + + // If we have user conditions that couldn't be resolved at compile time, + // parse all variants and the body. + if (HasUserCondition) { + SmallVector<OpenMPDirectiveKind, 4> DirectiveKinds; + SmallVector<OpenMPClauseKind, 4> ClauseKinds; + + // TODO: Phase 2 - Parse directive clauses and store them. + // For now in Phase 1, we only extract directive kinds. + // Sema will extract conditions from TraitInfos. + + BalancedDelimiterTracker T(*this, tok::l_paren, + tok::annot_pragma_openmp_end); + while (Tok.isNot(tok::annot_pragma_openmp_end)) { + OpenMPClauseKind CKind = + Tok.isAnnotation() ? OMPC_unknown + : getOpenMPClauseKind(PP.getSpelling(Tok)); + SourceLocation ClauseLoc = ConsumeToken(); + + // Parse '('. + T.consumeOpen(); + + if (CKind == OMPC_when) { + OMPTraitInfo &TI = Actions.getASTContext().getNewOMPTraitInfo(); + parseOMPContextSelectors(ClauseLoc, TI); + + // Parse ':'. + if (Tok.is(tok::colon)) + ConsumeAnyToken(); + } + + // Parse directive kind only for now. + OpenMPDirectiveKind DKind = OMPD_unknown; + if (!Tok.is(tok::r_paren)) { + DKind = parseOpenMPDirectiveKind(*this); + skipToMatchingParen(*this); + } + + // Parse ')'. + if (Tok.is(tok::r_paren)) + T.consumeClose(); + + DirectiveKinds.push_back(DKind); + ClauseKinds.push_back(CKind); + } + + SourceLocation EndLoc = Tok.getLocation(); + ConsumeAnnotationToken(); + + // Parse the body statement. + StmtResult AssociatedStmt = ParseStatement(); + if (AssociatedStmt.isInvalid()) + return StmtError(); + + // Pass to Sema for Phase 2 processing. + return Actions.OpenMP().ActOnOpenMPMetaDirective( + Loc, EndLoc, TraitInfos, ClauseKinds, DirectiveKinds, + AssociatedStmt.get()); + } + 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 2e4d9f2f82f0b7..90768a0998c8d3 100644 --- a/clang/lib/Sema/SemaOpenMP.cpp +++ b/clang/lib/Sema/SemaOpenMP.cpp @@ -3771,6 +3771,15 @@ StmtResult SemaOpenMP::ActOnOpenMPAssumeDirective(ArrayRef<OMPClause *> Clauses, AStmt); } +StmtResult SemaOpenMP::ActOnOpenMPMetaDirective( + SourceLocation StartLoc, SourceLocation EndLoc, + ArrayRef<OMPTraitInfo *> TraitInfos, ArrayRef<OpenMPClauseKind> ClauseKinds, + ArrayRef<OpenMPDirectiveKind> DirectiveKinds, Stmt *AssociatedStmt) { + // Stub for Phase 1 (Parser) testing. + // Sema will extract conditions from TraitInfos in Phase 2. + return AssociatedStmt; +} + OMPRequiresDecl * SemaOpenMP::CheckOMPRequiresDecl(SourceLocation Loc, ArrayRef<OMPClause *> ClauseList) { 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..7306a5ab107fad --- /dev/null +++ b/clang/test/OpenMP/metadirective_user_condition_parse.cpp @@ -0,0 +1,134 @@ +// 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_different_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; + } + } +} + +template <int N> +void test_nontype_template(int flag) { +#pragma omp metadirective \ + when(user={condition(N > 0)}: parallel) \ + otherwise(single) + { + int x = N; + } +} + +template <int Threshold> +void test_threshold_condition(int value) { +#pragma omp metadirective \ + when(user={condition(value > Threshold)}: parallel) \ + otherwise() + { + int y = value; + } +} + +template <bool UseParallel> +void test_bool_template() { +#pragma omp metadirective \ + when(user={condition(UseParallel)}: parallel) \ + otherwise(single) + { + int z = 0; + } +} + +template <typename T> +void test_sizeof_condition(T* ptr) { +#pragma omp metadirective \ + when(user={condition(sizeof(T) > 4)}: parallel) \ + otherwise(single) + { + T val = *ptr; + } +} + +void instantiate_templates() { + int flag = 1; + int value = 10; + int iptr; + double dptr; + + test_nontype_template<5>(flag); + test_nontype_template<-3>(flag); + test_threshold_condition<100>(value); + test_bool_template<true>(); + test_bool_template<false>(); + test_sizeof_condition<int>(&iptr); + test_sizeof_condition<double>(&dptr); +} diff --git a/llvm/include/llvm/Frontend/OpenMP/OMPContext.h b/llvm/include/llvm/Frontend/OpenMP/OMPContext.h index 7849d326659944..7c069721ba6772 100644 --- a/llvm/include/llvm/Frontend/OpenMP/OMPContext.h +++ b/llvm/include/llvm/Frontend/OpenMP/OMPContext.h @@ -156,6 +156,10 @@ struct VariantMatchInfo { SmallVector<StringRef, 8> ISATraits; SmallVector<TraitProperty, 8> ConstructTraits; SmallDenseMap<TraitProperty, APInt> ScoreMap; + + /// True if this variant has a user condition that could not be evaluated at + /// compile time (non-constant expression). + bool HasNonConstantUserCondition = false; }; /// The context for a source location is made up of active property traits, >From 57cccb876e9a98a991ee627593933a15c1da5369 Mon Sep 17 00:00:00 2001 From: Zahira Ammarguellat <[email protected]> Date: Tue, 18 Aug 2026 06:08:45 -0700 Subject: [PATCH 02/10] Fix format --- clang/include/clang/Sema/SemaOpenMP.h | 11 ++++++----- clang/lib/Parse/ParseOpenMP.cpp | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/clang/include/clang/Sema/SemaOpenMP.h b/clang/include/clang/Sema/SemaOpenMP.h index 71a43da569de68..1b6faa20b31df7 100644 --- a/clang/include/clang/Sema/SemaOpenMP.h +++ b/clang/include/clang/Sema/SemaOpenMP.h @@ -219,11 +219,12 @@ class SemaOpenMP : public SemaBase { /// Called for metadirectives with user conditions that may require runtime /// selection. - StmtResult ActOnOpenMPMetaDirective( - SourceLocation StartLoc, SourceLocation EndLoc, - ArrayRef<OMPTraitInfo *> TraitInfos, - ArrayRef<OpenMPClauseKind> ClauseKinds, - ArrayRef<OpenMPDirectiveKind> DirectiveKinds, Stmt *AssociatedStmt); + StmtResult + ActOnOpenMPMetaDirective(SourceLocation StartLoc, SourceLocation EndLoc, + ArrayRef<OMPTraitInfo *> TraitInfos, + ArrayRef<OpenMPClauseKind> ClauseKinds, + ArrayRef<OpenMPDirectiveKind> DirectiveKinds, + Stmt *AssociatedStmt); // OpenMP directives and clauses. /// Called on correct id-expression from the '#pragma omp diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index 1f2c48f17e9f1d..39d80ca095aa30 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -2753,9 +2753,9 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( BalancedDelimiterTracker T(*this, tok::l_paren, tok::annot_pragma_openmp_end); while (Tok.isNot(tok::annot_pragma_openmp_end)) { - OpenMPClauseKind CKind = - Tok.isAnnotation() ? OMPC_unknown - : getOpenMPClauseKind(PP.getSpelling(Tok)); + OpenMPClauseKind CKind = Tok.isAnnotation() + ? OMPC_unknown + : getOpenMPClauseKind(PP.getSpelling(Tok)); SourceLocation ClauseLoc = ConsumeToken(); // Parse '('. >From b65daef1bb0f17b8d58ef98f72e91a49fee11267 Mon Sep 17 00:00:00 2001 From: Zahira Ammarguellat <[email protected]> Date: Fri, 21 Aug 2026 14:04:49 -0700 Subject: [PATCH 03/10] Phase 2 of metadirective runtime selection --- clang/include/clang/Lex/Preprocessor.h | 5 + clang/include/clang/Sema/SemaOpenMP.h | 13 +- clang/lib/Lex/PPCaching.cpp | 18 ++ clang/lib/Parse/ParseOpenMP.cpp | 187 ++++++++++++++---- clang/lib/Sema/SemaOpenMP.cpp | 28 ++- .../metadirective_user_condition_parse.cpp | 54 ----- 6 files changed, 207 insertions(+), 98 deletions(-) diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index d94f3d2cbe8ed5..575bb0af6127c5 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -1806,6 +1806,11 @@ class Preprocessor { /// Disable the last EnableBacktrackAtThisPos call. void CommitBacktrackedTokens(); + /// Get the tokens cached since EnableBacktrackAtThisPos() and commit + /// the backtrack. The main token stream remains at the current position + /// (advanced past the cached tokens). + ArrayRef<Token> GetAndCommitBacktrackedTokens(); + /// Make Preprocessor re-lex the tokens that were lexed since /// EnableBacktrackAtThisPos() was previously called. void Backtrack(); diff --git a/clang/include/clang/Sema/SemaOpenMP.h b/clang/include/clang/Sema/SemaOpenMP.h index 1b6faa20b31df7..a4a4a63b6ee795 100644 --- a/clang/include/clang/Sema/SemaOpenMP.h +++ b/clang/include/clang/Sema/SemaOpenMP.h @@ -219,12 +219,13 @@ class SemaOpenMP : public SemaBase { /// Called for metadirectives with user conditions that may require runtime /// selection. - StmtResult - ActOnOpenMPMetaDirective(SourceLocation StartLoc, SourceLocation EndLoc, - ArrayRef<OMPTraitInfo *> TraitInfos, - ArrayRef<OpenMPClauseKind> ClauseKinds, - ArrayRef<OpenMPDirectiveKind> DirectiveKinds, - Stmt *AssociatedStmt); + StmtResult ActOnOpenMPMetaDirective( + SourceLocation StartLoc, SourceLocation EndLoc, + ArrayRef<OMPTraitInfo *> TraitInfos, + ArrayRef<OpenMPClauseKind> ClauseKinds, + ArrayRef<OpenMPDirectiveKind> DirectiveKinds, + ArrayRef<ArrayRef<OMPClause *>> DirectiveClauses, + ArrayRef<Stmt *> VariantBodies); // OpenMP directives and clauses. /// Called on correct id-expression from the '#pragma omp diff --git a/clang/lib/Lex/PPCaching.cpp b/clang/lib/Lex/PPCaching.cpp index 3f0ebd84556856..b21d97dd77fa08 100644 --- a/clang/lib/Lex/PPCaching.cpp +++ b/clang/lib/Lex/PPCaching.cpp @@ -61,6 +61,24 @@ void Preprocessor::CommitBacktrackedTokens() { PopUnannotatedBacktrackTokens(); } +// Get the tokens cached since EnableBacktrackAtThisPos() and commit +// the backtrack. +ArrayRef<Token> Preprocessor::GetAndCommitBacktrackedTokens() { + assert(isBacktrackEnabled() && + "Should only be called when backtracking is enabled"); + + auto [LastPos, Unannotated] = LastBacktrackPos(); + + // Get range of tokens cached since EnableBacktrackAtThisPos. + ArrayRef<Token> CachedRange(CachedTokens.begin() + LastPos, + CachedTokens.begin() + CachedLexPos); + + // Commit backtrack to keep stream advanced. + CommitBacktrackedTokens(); + + return CachedRange; +} + // Make Preprocessor re-lex the tokens that were lexed since // EnableBacktrackAtThisPos() was previously called. void Preprocessor::Backtrack() { diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index 39d80ca095aa30..fc3ee0b0acbde6 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -108,22 +108,6 @@ static OpenMPDirectiveKind parseOpenMPDirectiveKind(Parser &P) { return checkOpenMPDirectiveName(P, Loc, S->Value, Concat); } -/// Skip tokens until reaching the matching closing parenthesis. -/// Handles nested parentheses correctly. -static void skipToMatchingParen(Parser &P) { - int ParenDepth = 0; - while ((P.getCurToken().isNot(tok::r_paren) || ParenDepth != 0) && - P.getCurToken().isNot(tok::annot_pragma_openmp_end) && - P.getCurToken().isNot(tok::eof)) { - if (P.getCurToken().is(tok::l_paren)) - ParenDepth++; - if (P.getCurToken().is(tok::r_paren) && ParenDepth > 0) - ParenDepth--; - if (ParenDepth > 0 || P.getCurToken().isNot(tok::r_paren)) - P.ConsumeAnyToken(); - } -} - static DeclarationName parseOpenMPReductionId(Parser &P) { Token Tok = P.getCurToken(); Sema &Actions = P.getActions(); @@ -2740,22 +2724,20 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( } } - // If we have user conditions that couldn't be resolved at compile time, - // parse all variants and the body. + // Different directives have different data-sharing attributes, so each + // variant needs its own CapturedStmt with proper DSA context. + // We manually cache body tokens and inject them for each variant parse. if (HasUserCondition) { - SmallVector<OpenMPDirectiveKind, 4> DirectiveKinds; SmallVector<OpenMPClauseKind, 4> ClauseKinds; - - // TODO: Phase 2 - Parse directive clauses and store them. - // For now in Phase 1, we only extract directive kinds. - // Sema will extract conditions from TraitInfos. + SmallVector<OpenMPDirectiveKind, 4> DirectiveKinds; + SmallVector<SmallVector<OMPClause *, 5>, 4> DirectiveClauses; BalancedDelimiterTracker T(*this, tok::l_paren, tok::annot_pragma_openmp_end); while (Tok.isNot(tok::annot_pragma_openmp_end)) { - OpenMPClauseKind CKind = Tok.isAnnotation() - ? OMPC_unknown - : getOpenMPClauseKind(PP.getSpelling(Tok)); + OpenMPClauseKind CKind = + Tok.isAnnotation() ? OMPC_unknown + : getOpenMPClauseKind(PP.getSpelling(Tok)); SourceLocation ClauseLoc = ConsumeToken(); // Parse '('. @@ -2770,33 +2752,170 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( ConsumeAnyToken(); } - // Parse directive kind only for now. + // Parse the directive kind and clauses manually. OpenMPDirectiveKind DKind = OMPD_unknown; + SmallVector<OMPClause *, 5> Clauses; + if (!Tok.is(tok::r_paren)) { + // Parse directive kind (handles combined directives like + // 'parallel for simd'). DKind = parseOpenMPDirectiveKind(*this); - skipToMatchingParen(*this); + + // Consume the last token of the directive name. + if (Tok.isNot(tok::annot_pragma_openmp_end) && + Tok.isNot(tok::r_paren)) + ConsumeAnyToken(); + + // Parse clauses for this directive if any exist. + // We stop at ')' which ends the metadirective variant. + while (Tok.isNot(tok::r_paren) && + Tok.isNot(tok::annot_pragma_openmp_end)) { + // Check if current token is a clause keyword. + OpenMPClauseKind ClauseKind = + Tok.isAnnotation() + ? OMPC_unknown + : getOpenMPClauseKind(PP.getSpelling(Tok)); + + // If not a clause keyword, we've parsed all clauses. + if (ClauseKind == OMPC_unknown) + break; + + Actions.OpenMP().StartOpenMPClause(ClauseKind); + OMPClause *Clause = + ParseOpenMPClause(DKind, ClauseKind, + /* FirstClause */ Clauses.empty()); + Actions.OpenMP().EndOpenMPClause(); + + if (Clause) + Clauses.push_back(Clause); + + // Check for comma separator between clauses. + if (Tok.is(tok::comma)) + ConsumeAnyToken(); + else if (Tok.isNot(tok::r_paren) && + Tok.isNot(tok::annot_pragma_openmp_end)) { + // Unexpected token - not comma, not closing paren. + break; + } + } } // Parse ')'. if (Tok.is(tok::r_paren)) T.consumeClose(); - DirectiveKinds.push_back(DKind); ClauseKinds.push_back(CKind); + DirectiveKinds.push_back(DKind); + DirectiveClauses.push_back(Clauses); } SourceLocation EndLoc = Tok.getLocation(); ConsumeAnnotationToken(); - // Parse the body statement. - StmtResult AssociatedStmt = ParseStatement(); - if (AssociatedStmt.isInvalid()) + // Manually cache all body tokens for unlimited replay. + SmallVector<Token, 64> BodyTokens; + + // Cache the current token before enabling backtracking. + BodyTokens.push_back(Tok); + + // Parse the statement once and cache remaining tokens. + PP.EnableBacktrackAtThisPos(); + { + ParsingOpenMPDirectiveRAII NormalScope(*this, /*Value=*/false); + StmtResult BodyStmt = ParseStatement(); + if (BodyStmt.isInvalid()) { + PP.Backtrack(); + return StmtError(); + } + } + + // Get cached tokens and commit (keeps stream advanced). + ArrayRef<Token> CachedRange = PP.GetAndCommitBacktrackedTokens(); + BodyTokens.append(CachedRange.begin(), CachedRange.end()); + + if (BodyTokens.empty()) { + Diag(Tok, diag::err_expected_statement); return StmtError(); + } + + // Add an EOF token with marker to end the injected stream. + Token EofToken; + EofToken.startToken(); + EofToken.setKind(tok::eof); + EofToken.setLocation(Tok.getLocation()); + EofToken.setEofData(this); + BodyTokens.push_back(EofToken); + + // Parse the body separately for each variant to get correct DSA. + SmallVector<Stmt *, 4> VariantBodies; + for (unsigned i = 0; i < DirectiveKinds.size(); ++i) { + if (DirectiveKinds[i] == OMPD_unknown) { + VariantBodies.push_back(nullptr); + continue; + } + + auto TokensCopy = std::make_unique<Token[]>(BodyTokens.size()); + std::copy(BodyTokens.begin(), BodyTokens.end(), TokensCopy.get()); + PP.EnterTokenStream(std::move(TokensCopy), BodyTokens.size(), + /*DisableMacroExpansion=*/false, + /*IsReinject=*/false); + // Consume first token from the injected stream to update Tok. + ConsumeAnyToken(); + + // Start DSA block for this directive. + Actions.OpenMP().StartOpenMPDSABlock(DirectiveKinds[i], + DeclarationNameInfo(), + getCurScope(), Loc); + + // Start captured region for this directive. + Actions.OpenMP().ActOnOpenMPRegionStart(DirectiveKinds[i], + getCurScope()); + + // Parse the body. + StmtResult Body; + ParsingOpenMPDirectiveRAII NormalScope(*this, /*Value=*/false); + { + Sema::CompoundScopeRAII Scope(Actions); + Body = ParseStatement(); + } + + if (Body.isInvalid()) + return StmtError(); + + // End captured region - wraps body in CapturedStmt. + Body = Actions.OpenMP().ActOnOpenMPRegionEnd(Body, + DirectiveClauses[i]); + if (Body.isInvalid()) + return StmtError(); + + // End DSA block for this variant. + Actions.OpenMP().EndOpenMPDSABlock(Body.get()); + + VariantBodies.push_back(Body.get()); + + // Clean up this variant's injected token stream. + // Skip cleanup for the last variant to avoid stream corruption. + if (i < DirectiveKinds.size() - 1) { + while (Tok.isNot(tok::eof)) + ConsumeAnyToken(); + + // Consume the marked EOF to pop the injected stream. + if (Tok.is(tok::eof) && Tok.getEofData() == this) + ConsumeAnyToken(); + } + } + + // The last variant's token stream remains active. The variants' + // CapturedStmts already include the body. + + // Convert DirectiveClauses to ArrayRef<ArrayRef<OMPClause *>>. + SmallVector<ArrayRef<OMPClause *>, 4> ClausesArrayRefs; + for (const auto &Clauses : DirectiveClauses) + ClausesArrayRefs.push_back(Clauses); - // Pass to Sema for Phase 2 processing. return Actions.OpenMP().ActOnOpenMPMetaDirective( Loc, EndLoc, TraitInfos, ClauseKinds, DirectiveKinds, - AssociatedStmt.get()); + ClausesArrayRefs, VariantBodies); } int Idx = 0; diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp index 90768a0998c8d3..14a6988cbd53a7 100644 --- a/clang/lib/Sema/SemaOpenMP.cpp +++ b/clang/lib/Sema/SemaOpenMP.cpp @@ -3774,10 +3774,30 @@ StmtResult SemaOpenMP::ActOnOpenMPAssumeDirective(ArrayRef<OMPClause *> Clauses, StmtResult SemaOpenMP::ActOnOpenMPMetaDirective( SourceLocation StartLoc, SourceLocation EndLoc, ArrayRef<OMPTraitInfo *> TraitInfos, ArrayRef<OpenMPClauseKind> ClauseKinds, - ArrayRef<OpenMPDirectiveKind> DirectiveKinds, Stmt *AssociatedStmt) { - // Stub for Phase 1 (Parser) testing. - // Sema will extract conditions from TraitInfos in Phase 2. - return AssociatedStmt; + ArrayRef<OpenMPDirectiveKind> DirectiveKinds, + ArrayRef<ArrayRef<OMPClause *>> DirectiveClauses, + ArrayRef<Stmt *> VariantBodies) { + + assert((ClauseKinds.size() == DirectiveKinds.size() && + DirectiveKinds.size() == DirectiveClauses.size() && + DirectiveClauses.size() == VariantBodies.size()) && + "Mismatched variant arrays"); + + // VariantBodies are complete CapturedStmt nodes with DSA from Parser. + // TODO Phase 3: Build OMPMetaDirective AST node holding all variants. + // TODO Phase 3: Extract conditions from TraitInfos. + // TODO Phase 4: Codegen - generate runtime if-else chain. + + // Temporary stub: return first variant until OMPMetaDirective exists. + // Early return if no variants were successfully parsed. + if (VariantBodies.empty()) + return StmtError(); + + // Return first variant if it's valid. + if (VariantBodies[0]) + return VariantBodies[0]; + + return StmtError(); } OMPRequiresDecl * diff --git a/clang/test/OpenMP/metadirective_user_condition_parse.cpp b/clang/test/OpenMP/metadirective_user_condition_parse.cpp index 7306a5ab107fad..6d121c42302e4b 100644 --- a/clang/test/OpenMP/metadirective_user_condition_parse.cpp +++ b/clang/test/OpenMP/metadirective_user_condition_parse.cpp @@ -78,57 +78,3 @@ void test_nested_statement(int flag) { } } -template <int N> -void test_nontype_template(int flag) { -#pragma omp metadirective \ - when(user={condition(N > 0)}: parallel) \ - otherwise(single) - { - int x = N; - } -} - -template <int Threshold> -void test_threshold_condition(int value) { -#pragma omp metadirective \ - when(user={condition(value > Threshold)}: parallel) \ - otherwise() - { - int y = value; - } -} - -template <bool UseParallel> -void test_bool_template() { -#pragma omp metadirective \ - when(user={condition(UseParallel)}: parallel) \ - otherwise(single) - { - int z = 0; - } -} - -template <typename T> -void test_sizeof_condition(T* ptr) { -#pragma omp metadirective \ - when(user={condition(sizeof(T) > 4)}: parallel) \ - otherwise(single) - { - T val = *ptr; - } -} - -void instantiate_templates() { - int flag = 1; - int value = 10; - int iptr; - double dptr; - - test_nontype_template<5>(flag); - test_nontype_template<-3>(flag); - test_threshold_condition<100>(value); - test_bool_template<true>(); - test_bool_template<false>(); - test_sizeof_condition<int>(&iptr); - test_sizeof_condition<double>(&dptr); -} >From cba2ebd8323377cfee1ade555b74b40396a9aa4f Mon Sep 17 00:00:00 2001 From: Zahira Ammarguellat <[email protected]> Date: Mon, 31 Aug 2026 12:32:09 -0700 Subject: [PATCH 04/10] Fixed format, addressed part of the review comments --- clang/include/clang/AST/OpenMPClause.h | 16 +++---- clang/include/clang/Sema/SemaOpenMP.h | 14 +++--- clang/lib/Lex/PPCaching.cpp | 2 +- clang/lib/Parse/ParseOpenMP.cpp | 60 +++++++++++++------------- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/clang/include/clang/AST/OpenMPClause.h b/clang/include/clang/AST/OpenMPClause.h index c39af8d3148f88..0bb46f8d1da0c0 100644 --- a/clang/include/clang/AST/OpenMPClause.h +++ b/clang/include/clang/AST/OpenMPClause.h @@ -10131,15 +10131,13 @@ class OMPTraitInfo { /// Check if this trait info contains any user conditions. bool hasUserCondition() const { - for (const OMPTraitSet &Set : Sets) { - if (Set.Kind != llvm::omp::TraitSet::user) - continue; - for (const OMPTraitSelector &Selector : Set.Selectors) { - if (Selector.Kind == llvm::omp::TraitSelector::user_condition) - return true; - } - } - return false; + return llvm::any_of(Sets, [](const OMPTraitSet &Set) { + return Set.Kind == llvm::omp::TraitSet::user && + llvm::any_of(Set.Selectors, [](const OMPTraitSelector &Selector) { + return Selector.Kind == + llvm::omp::TraitSelector::user_condition; + }); + }); } /// Print a human readable representation into \p OS. diff --git a/clang/include/clang/Sema/SemaOpenMP.h b/clang/include/clang/Sema/SemaOpenMP.h index a4a4a63b6ee795..ba4c9e8af8b9ac 100644 --- a/clang/include/clang/Sema/SemaOpenMP.h +++ b/clang/include/clang/Sema/SemaOpenMP.h @@ -219,13 +219,13 @@ class SemaOpenMP : public SemaBase { /// Called for metadirectives with user conditions that may require runtime /// selection. - StmtResult ActOnOpenMPMetaDirective( - SourceLocation StartLoc, SourceLocation EndLoc, - ArrayRef<OMPTraitInfo *> TraitInfos, - ArrayRef<OpenMPClauseKind> ClauseKinds, - ArrayRef<OpenMPDirectiveKind> DirectiveKinds, - ArrayRef<ArrayRef<OMPClause *>> DirectiveClauses, - ArrayRef<Stmt *> VariantBodies); + StmtResult + ActOnOpenMPMetaDirective(SourceLocation StartLoc, SourceLocation EndLoc, + ArrayRef<OMPTraitInfo *> TraitInfos, + ArrayRef<OpenMPClauseKind> ClauseKinds, + ArrayRef<OpenMPDirectiveKind> DirectiveKinds, + ArrayRef<ArrayRef<OMPClause *>> DirectiveClauses, + ArrayRef<Stmt *> VariantBodies); // OpenMP directives and clauses. /// Called on correct id-expression from the '#pragma omp diff --git a/clang/lib/Lex/PPCaching.cpp b/clang/lib/Lex/PPCaching.cpp index b21d97dd77fa08..be1e8d88630bb0 100644 --- a/clang/lib/Lex/PPCaching.cpp +++ b/clang/lib/Lex/PPCaching.cpp @@ -71,7 +71,7 @@ ArrayRef<Token> Preprocessor::GetAndCommitBacktrackedTokens() { // Get range of tokens cached since EnableBacktrackAtThisPos. ArrayRef<Token> CachedRange(CachedTokens.begin() + LastPos, - CachedTokens.begin() + CachedLexPos); + CachedTokens.begin() + CachedLexPos); // Commit backtrack to keep stream advanced. CommitBacktrackedTokens(); diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index fc3ee0b0acbde6..47d1ada0ca0da6 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -2716,13 +2716,9 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( // Check if we have user conditions with non-constant expressions that // require runtime selection. - bool HasUserCondition = false; - for (const VariantMatchInfo &VMI : VMIs) { - if (VMI.HasNonConstantUserCondition) { - HasUserCondition = true; - break; - } - } + bool HasUserCondition = llvm::any_of(VMIs, [](const VariantMatchInfo &VMI) { + return VMI.HasNonConstantUserCondition; + }); // Different directives have different data-sharing attributes, so each // variant needs its own CapturedStmt with proper DSA context. @@ -2735,9 +2731,9 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( BalancedDelimiterTracker T(*this, tok::l_paren, tok::annot_pragma_openmp_end); while (Tok.isNot(tok::annot_pragma_openmp_end)) { - OpenMPClauseKind CKind = - Tok.isAnnotation() ? OMPC_unknown - : getOpenMPClauseKind(PP.getSpelling(Tok)); + OpenMPClauseKind CKind = Tok.isAnnotation() + ? OMPC_unknown + : getOpenMPClauseKind(PP.getSpelling(Tok)); SourceLocation ClauseLoc = ConsumeToken(); // Parse '('. @@ -2772,9 +2768,8 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( Tok.isNot(tok::annot_pragma_openmp_end)) { // Check if current token is a clause keyword. OpenMPClauseKind ClauseKind = - Tok.isAnnotation() - ? OMPC_unknown - : getOpenMPClauseKind(PP.getSpelling(Tok)); + Tok.isAnnotation() ? OMPC_unknown + : getOpenMPClauseKind(PP.getSpelling(Tok)); // If not a clause keyword, we've parsed all clauses. if (ClauseKind == OMPC_unknown) @@ -2818,11 +2813,23 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( // Cache the current token before enabling backtracking. BodyTokens.push_back(Tok); - // Parse the statement once and cache remaining tokens. + // Parse the statement once to cache tokens. + // Suppress diagnostics since this is only for token boundary detection. + // Actual parsing with DSA happens in the variant loop. PP.EnableBacktrackAtThisPos(); { ParsingOpenMPDirectiveRAII NormalScope(*this, /*Value=*/false); + + // Suppress diagnostics during token caching parse. + DiagnosticsEngine &Diags = PP.getDiagnostics(); + bool OldSuppressAllDiagnostics = Diags.getSuppressAllDiagnostics(); + Diags.setSuppressAllDiagnostics(true); + StmtResult BodyStmt = ParseStatement(); + + // Restore diagnostic state. + Diags.setSuppressAllDiagnostics(OldSuppressAllDiagnostics); + if (BodyStmt.isInvalid()) { PP.Backtrack(); return StmtError(); @@ -2833,10 +2840,7 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( ArrayRef<Token> CachedRange = PP.GetAndCommitBacktrackedTokens(); BodyTokens.append(CachedRange.begin(), CachedRange.end()); - if (BodyTokens.empty()) { - Diag(Tok, diag::err_expected_statement); - return StmtError(); - } + assert(!BodyTokens.empty() && "Body tokens should not be empty"); // Add an EOF token with marker to end the injected stream. Token EofToken; @@ -2848,8 +2852,8 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( // Parse the body separately for each variant to get correct DSA. SmallVector<Stmt *, 4> VariantBodies; - for (unsigned i = 0; i < DirectiveKinds.size(); ++i) { - if (DirectiveKinds[i] == OMPD_unknown) { + for (unsigned I : llvm::seq<unsigned>(DirectiveKinds.size())) { + if (DirectiveKinds[I] == OMPD_unknown) { VariantBodies.push_back(nullptr); continue; } @@ -2857,18 +2861,17 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( auto TokensCopy = std::make_unique<Token[]>(BodyTokens.size()); std::copy(BodyTokens.begin(), BodyTokens.end(), TokensCopy.get()); PP.EnterTokenStream(std::move(TokensCopy), BodyTokens.size(), - /*DisableMacroExpansion=*/false, - /*IsReinject=*/false); + /*DisableMacroExpansion=*/false, + /*IsReinject=*/false); // Consume first token from the injected stream to update Tok. ConsumeAnyToken(); // Start DSA block for this directive. - Actions.OpenMP().StartOpenMPDSABlock(DirectiveKinds[i], - DeclarationNameInfo(), - getCurScope(), Loc); + Actions.OpenMP().StartOpenMPDSABlock( + DirectiveKinds[I], DeclarationNameInfo(), getCurScope(), Loc); // Start captured region for this directive. - Actions.OpenMP().ActOnOpenMPRegionStart(DirectiveKinds[i], + Actions.OpenMP().ActOnOpenMPRegionStart(DirectiveKinds[I], getCurScope()); // Parse the body. @@ -2883,8 +2886,7 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( return StmtError(); // End captured region - wraps body in CapturedStmt. - Body = Actions.OpenMP().ActOnOpenMPRegionEnd(Body, - DirectiveClauses[i]); + Body = Actions.OpenMP().ActOnOpenMPRegionEnd(Body, DirectiveClauses[I]); if (Body.isInvalid()) return StmtError(); @@ -2895,7 +2897,7 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( // Clean up this variant's injected token stream. // Skip cleanup for the last variant to avoid stream corruption. - if (i < DirectiveKinds.size() - 1) { + if (I < DirectiveKinds.size() - 1) { while (Tok.isNot(tok::eof)) ConsumeAnyToken(); >From ee610a43c936e65a08e2a587b5b42943cf7ad98e Mon Sep 17 00:00:00 2001 From: Zahira Ammarguellat <[email protected]> Date: Thu, 3 Sep 2026 08:23:05 -0700 Subject: [PATCH 05/10] Addressed review comments --- clang/include/clang/AST/OpenMPClause.h | 3 +- clang/lib/Parse/ParseOpenMP.cpp | 157 ++++++------------ .../OpenMP/metadirective_variant_clauses.cpp | 42 +++++ 3 files changed, 92 insertions(+), 110 deletions(-) create mode 100644 clang/test/OpenMP/metadirective_variant_clauses.cpp diff --git a/clang/include/clang/AST/OpenMPClause.h b/clang/include/clang/AST/OpenMPClause.h index 0bb46f8d1da0c0..087b961abf58be 100644 --- a/clang/include/clang/AST/OpenMPClause.h +++ b/clang/include/clang/AST/OpenMPClause.h @@ -10134,8 +10134,7 @@ class OMPTraitInfo { return llvm::any_of(Sets, [](const OMPTraitSet &Set) { return Set.Kind == llvm::omp::TraitSet::user && llvm::any_of(Set.Selectors, [](const OMPTraitSelector &Selector) { - return Selector.Kind == - llvm::omp::TraitSelector::user_condition; + return Selector.Kind == llvm::omp::TraitSelector::user_condition; }); }); } diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index 47d1ada0ca0da6..335edeb17804f2 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -2748,30 +2748,33 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( ConsumeAnyToken(); } - // Parse the directive kind and clauses manually. OpenMPDirectiveKind DKind = OMPD_unknown; SmallVector<OMPClause *, 5> Clauses; if (!Tok.is(tok::r_paren)) { - // Parse directive kind (handles combined directives like - // 'parallel for simd'). + // Parse directive kind (e.g., 'parallel', 'single'). DKind = parseOpenMPDirectiveKind(*this); - // Consume the last token of the directive name. if (Tok.isNot(tok::annot_pragma_openmp_end) && Tok.isNot(tok::r_paren)) ConsumeAnyToken(); - // Parse clauses for this directive if any exist. - // We stop at ')' which ends the metadirective variant. + // Open temporary DSA block so clauses have an active directive frame. + if (DKind != OMPD_unknown) + Actions.OpenMP().StartOpenMPDSABlock(DKind, DeclarationNameInfo(), + getCurScope(), ClauseLoc); + + // Parse clauses for this directive variant. while (Tok.isNot(tok::r_paren) && Tok.isNot(tok::annot_pragma_openmp_end)) { - // Check if current token is a clause keyword. + // Skip optional commas between clauses. + if (Tok.is(tok::comma)) + ConsumeAnyToken(); + OpenMPClauseKind ClauseKind = Tok.isAnnotation() ? OMPC_unknown : getOpenMPClauseKind(PP.getSpelling(Tok)); - // If not a clause keyword, we've parsed all clauses. if (ClauseKind == OMPC_unknown) break; @@ -2783,21 +2786,16 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( if (Clause) Clauses.push_back(Clause); - - // Check for comma separator between clauses. - if (Tok.is(tok::comma)) - ConsumeAnyToken(); - else if (Tok.isNot(tok::r_paren) && - Tok.isNot(tok::annot_pragma_openmp_end)) { - // Unexpected token - not comma, not closing paren. - break; - } } + + // Close the temporary DSA block for header parsing. + if (DKind != OMPD_unknown) + Actions.OpenMP().EndOpenMPDSABlock(nullptr); } - // Parse ')'. - if (Tok.is(tok::r_paren)) - T.consumeClose(); + // Parse ')' or recover to pragma end on syntax error. + if (T.consumeClose()) + T.skipToEnd(); ClauseKinds.push_back(CKind); DirectiveKinds.push_back(DKind); @@ -2807,114 +2805,57 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( SourceLocation EndLoc = Tok.getLocation(); ConsumeAnnotationToken(); - // Manually cache all body tokens for unlimited replay. - SmallVector<Token, 64> BodyTokens; - - // Cache the current token before enabling backtracking. - BodyTokens.push_back(Tok); - - // Parse the statement once to cache tokens. - // Suppress diagnostics since this is only for token boundary detection. - // Actual parsing with DSA happens in the variant loop. - PP.EnableBacktrackAtThisPos(); - { - ParsingOpenMPDirectiveRAII NormalScope(*this, /*Value=*/false); - - // Suppress diagnostics during token caching parse. - DiagnosticsEngine &Diags = PP.getDiagnostics(); - bool OldSuppressAllDiagnostics = Diags.getSuppressAllDiagnostics(); - Diags.setSuppressAllDiagnostics(true); - - StmtResult BodyStmt = ParseStatement(); - - // Restore diagnostic state. - Diags.setSuppressAllDiagnostics(OldSuppressAllDiagnostics); - - if (BodyStmt.isInvalid()) { - PP.Backtrack(); - return StmtError(); - } - } - - // Get cached tokens and commit (keeps stream advanced). - ArrayRef<Token> CachedRange = PP.GetAndCommitBacktrackedTokens(); - BodyTokens.append(CachedRange.begin(), CachedRange.end()); - - assert(!BodyTokens.empty() && "Body tokens should not be empty"); - - // Add an EOF token with marker to end the injected stream. - Token EofToken; - EofToken.startToken(); - EofToken.setKind(tok::eof); - EofToken.setLocation(Tok.getLocation()); - EofToken.setEofData(this); - BodyTokens.push_back(EofToken); - - // Parse the body separately for each variant to get correct DSA. + // Parse the body separately for each variant to establish proper DSA. SmallVector<Stmt *, 4> VariantBodies; - for (unsigned I : llvm::seq<unsigned>(DirectiveKinds.size())) { - if (DirectiveKinds[I] == OMPD_unknown) { - VariantBodies.push_back(nullptr); - continue; - } - auto TokensCopy = std::make_unique<Token[]>(BodyTokens.size()); - std::copy(BodyTokens.begin(), BodyTokens.end(), TokensCopy.get()); - PP.EnterTokenStream(std::move(TokensCopy), BodyTokens.size(), - /*DisableMacroExpansion=*/false, - /*IsReinject=*/false); - // Consume first token from the injected stream to update Tok. - ConsumeAnyToken(); + for (unsigned I : llvm::seq<unsigned>(DirectiveKinds.size())) { + std::optional<TentativeParsingAction> TPA; + if (I < DirectiveKinds.size() - 1) + TPA.emplace(*this); - // Start DSA block for this directive. - Actions.OpenMP().StartOpenMPDSABlock( - DirectiveKinds[I], DeclarationNameInfo(), getCurScope(), Loc); + const bool HasDirective = (DirectiveKinds[I] != OMPD_unknown); - // Start captured region for this directive. - Actions.OpenMP().ActOnOpenMPRegionStart(DirectiveKinds[I], - getCurScope()); + if (HasDirective) { + Actions.OpenMP().StartOpenMPDSABlock( + DirectiveKinds[I], DeclarationNameInfo(), getCurScope(), Loc); + Actions.OpenMP().ActOnOpenMPRegionStart(DirectiveKinds[I], + getCurScope()); + } - // Parse the body. StmtResult Body; ParsingOpenMPDirectiveRAII NormalScope(*this, /*Value=*/false); { Sema::CompoundScopeRAII Scope(Actions); Body = ParseStatement(); } - - if (Body.isInvalid()) - return StmtError(); - - // End captured region - wraps body in CapturedStmt. - Body = Actions.OpenMP().ActOnOpenMPRegionEnd(Body, DirectiveClauses[I]); - if (Body.isInvalid()) + if (Body.isInvalid()) { + if (HasDirective) + Actions.OpenMP().EndOpenMPDSABlock(nullptr); + if (TPA) + TPA->Revert(); return StmtError(); + } + if (HasDirective) { + Body = + Actions.OpenMP().ActOnOpenMPRegionEnd(Body, DirectiveClauses[I]); + if (Body.isInvalid()) { + Actions.OpenMP().EndOpenMPDSABlock(nullptr); + if (TPA) + TPA->Revert(); + return StmtError(); + } - // End DSA block for this variant. - Actions.OpenMP().EndOpenMPDSABlock(Body.get()); - - VariantBodies.push_back(Body.get()); - - // Clean up this variant's injected token stream. - // Skip cleanup for the last variant to avoid stream corruption. - if (I < DirectiveKinds.size() - 1) { - while (Tok.isNot(tok::eof)) - ConsumeAnyToken(); - - // Consume the marked EOF to pop the injected stream. - if (Tok.is(tok::eof) && Tok.getEofData() == this) - ConsumeAnyToken(); + Actions.OpenMP().EndOpenMPDSABlock(Body.get()); } + VariantBodies.push_back(Body.get()); + if (TPA) + TPA->Revert(); } - // The last variant's token stream remains active. The variants' - // CapturedStmts already include the body. - // Convert DirectiveClauses to ArrayRef<ArrayRef<OMPClause *>>. SmallVector<ArrayRef<OMPClause *>, 4> ClausesArrayRefs; for (const auto &Clauses : DirectiveClauses) ClausesArrayRefs.push_back(Clauses); - return Actions.OpenMP().ActOnOpenMPMetaDirective( Loc, EndLoc, TraitInfos, ClauseKinds, DirectiveKinds, ClausesArrayRefs, VariantBodies); diff --git a/clang/test/OpenMP/metadirective_variant_clauses.cpp b/clang/test/OpenMP/metadirective_variant_clauses.cpp new file mode 100644 index 00000000000000..a379a16c17ba8c --- /dev/null +++ b/clang/test/OpenMP/metadirective_variant_clauses.cpp @@ -0,0 +1,42 @@ +// 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; + } +} >From 7277b283ef38f05ff1bef7723da25803e411b377 Mon Sep 17 00:00:00 2001 From: Zahira Ammarguellat <[email protected]> Date: Thu, 15 Oct 2026 05:55:39 -0700 Subject: [PATCH 06/10] Removed unused code and added runtime flag --- clang/include/clang/AST/OpenMPClause.h | 10 ---------- clang/include/clang/Basic/LangOptions.def | 1 + clang/include/clang/Lex/Preprocessor.h | 5 ----- clang/include/clang/Options/Options.td | 7 +++++++ clang/lib/Lex/PPCaching.cpp | 18 ------------------ clang/lib/Parse/ParseOpenMP.cpp | 3 ++- .../metadirective_user_condition_parse.cpp | 2 ++ .../OpenMP/metadirective_variant_clauses.cpp | 2 ++ 8 files changed, 14 insertions(+), 34 deletions(-) diff --git a/clang/include/clang/AST/OpenMPClause.h b/clang/include/clang/AST/OpenMPClause.h index 087b961abf58be..ec84f10956ff43 100644 --- a/clang/include/clang/AST/OpenMPClause.h +++ b/clang/include/clang/AST/OpenMPClause.h @@ -10129,16 +10129,6 @@ class OMPTraitInfo { return false; } - /// Check if this trait info contains any user conditions. - bool hasUserCondition() const { - return llvm::any_of(Sets, [](const OMPTraitSet &Set) { - return Set.Kind == llvm::omp::TraitSet::user && - llvm::any_of(Set.Selectors, [](const OMPTraitSelector &Selector) { - return Selector.Kind == llvm::omp::TraitSelector::user_condition; - }); - }); - } - /// Print a human readable representation into \p OS. void print(llvm::raw_ostream &OS, const PrintingPolicy &Policy) const; }; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index d7637f2dfd5079..c4507ddf946596 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/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 575bb0af6127c5..d94f3d2cbe8ed5 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -1806,11 +1806,6 @@ class Preprocessor { /// Disable the last EnableBacktrackAtThisPos call. void CommitBacktrackedTokens(); - /// Get the tokens cached since EnableBacktrackAtThisPos() and commit - /// the backtrack. The main token stream remains at the current position - /// (advanced past the cached tokens). - ArrayRef<Token> GetAndCommitBacktrackedTokens(); - /// Make Preprocessor re-lex the tokens that were lexed since /// EnableBacktrackAtThisPos() was previously called. void Backtrack(); diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index e991dba4480338..c0d2a213272021 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -9207,6 +9207,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/lib/Lex/PPCaching.cpp b/clang/lib/Lex/PPCaching.cpp index be1e8d88630bb0..3f0ebd84556856 100644 --- a/clang/lib/Lex/PPCaching.cpp +++ b/clang/lib/Lex/PPCaching.cpp @@ -61,24 +61,6 @@ void Preprocessor::CommitBacktrackedTokens() { PopUnannotatedBacktrackTokens(); } -// Get the tokens cached since EnableBacktrackAtThisPos() and commit -// the backtrack. -ArrayRef<Token> Preprocessor::GetAndCommitBacktrackedTokens() { - assert(isBacktrackEnabled() && - "Should only be called when backtracking is enabled"); - - auto [LastPos, Unannotated] = LastBacktrackPos(); - - // Get range of tokens cached since EnableBacktrackAtThisPos. - ArrayRef<Token> CachedRange(CachedTokens.begin() + LastPos, - CachedTokens.begin() + CachedLexPos); - - // Commit backtrack to keep stream advanced. - CommitBacktrackedTokens(); - - return CachedRange; -} - // Make Preprocessor re-lex the tokens that were lexed since // EnableBacktrackAtThisPos() was previously called. void Preprocessor::Backtrack() { diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index 335edeb17804f2..286aad16d3509f 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -2723,7 +2723,8 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( // Different directives have different data-sharing attributes, so each // variant needs its own CapturedStmt with proper DSA context. // We manually cache body tokens and inject them for each variant parse. - if (HasUserCondition) { + if (HasUserCondition && + getLangOpts().OpenMPMetadirectiveRuntimeSelection) { SmallVector<OpenMPClauseKind, 4> ClauseKinds; SmallVector<OpenMPDirectiveKind, 4> DirectiveKinds; SmallVector<SmallVector<OMPClause *, 5>, 4> DirectiveClauses; diff --git a/clang/test/OpenMP/metadirective_user_condition_parse.cpp b/clang/test/OpenMP/metadirective_user_condition_parse.cpp index 6d121c42302e4b..b517c00c2e1680 100644 --- a/clang/test/OpenMP/metadirective_user_condition_parse.cpp +++ b/clang/test/OpenMP/metadirective_user_condition_parse.cpp @@ -1,4 +1,6 @@ // 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 diff --git a/clang/test/OpenMP/metadirective_variant_clauses.cpp b/clang/test/OpenMP/metadirective_variant_clauses.cpp index a379a16c17ba8c..9a3d0e675a5493 100644 --- a/clang/test/OpenMP/metadirective_variant_clauses.cpp +++ b/clang/test/OpenMP/metadirective_variant_clauses.cpp @@ -1,4 +1,6 @@ // 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 >From de5a82a03d67dcfd6165b611804309422da77431 Mon Sep 17 00:00:00 2001 From: Zahira Ammarguellat <[email protected]> Date: Thu, 15 Oct 2026 10:04:54 -0700 Subject: [PATCH 07/10] Parse each metadirective variant through the normal directive path --- clang/include/clang/Sema/SemaOpenMP.h | 16 +- clang/lib/Parse/ParseOpenMP.cpp | 188 ++++++------------ clang/lib/Sema/SemaOpenMP.cpp | 28 +-- .../OpenMP/metadirective_runtime_messages.cpp | 40 ++++ 4 files changed, 112 insertions(+), 160 deletions(-) create mode 100644 clang/test/OpenMP/metadirective_runtime_messages.cpp diff --git a/clang/include/clang/Sema/SemaOpenMP.h b/clang/include/clang/Sema/SemaOpenMP.h index ba4c9e8af8b9ac..3c4f2b4095631e 100644 --- a/clang/include/clang/Sema/SemaOpenMP.h +++ b/clang/include/clang/Sema/SemaOpenMP.h @@ -217,15 +217,13 @@ class SemaOpenMP : public SemaBase { Stmt *AStmt, SourceLocation StartLoc, SourceLocation EndLoc); - /// Called for metadirectives with user conditions that may require runtime - /// selection. - StmtResult - ActOnOpenMPMetaDirective(SourceLocation StartLoc, SourceLocation EndLoc, - ArrayRef<OMPTraitInfo *> TraitInfos, - ArrayRef<OpenMPClauseKind> ClauseKinds, - ArrayRef<OpenMPDirectiveKind> DirectiveKinds, - ArrayRef<ArrayRef<OMPClause *>> DirectiveClauses, - ArrayRef<Stmt *> VariantBodies); + /// 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 diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index 286aad16d3509f..1d1df9adeb42fb 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -2697,6 +2697,7 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( TraitInfos.push_back(&TI); } + SourceLocation EndLoc = Tok.getLocation(); TPA.Revert(); // End of the first iteration. Parser is reset to the start of metadirective @@ -2720,146 +2721,75 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( return VMI.HasNonConstantUserCondition; }); - // Different directives have different data-sharing attributes, so each - // variant needs its own CapturedStmt with proper DSA context. - // We manually cache body tokens and inject them for each variant parse. if (HasUserCondition && getLangOpts().OpenMPMetadirectiveRuntimeSelection) { - SmallVector<OpenMPClauseKind, 4> ClauseKinds; - SmallVector<OpenMPDirectiveKind, 4> DirectiveKinds; - SmallVector<SmallVector<OMPClause *, 5>, 4> DirectiveClauses; - - BalancedDelimiterTracker T(*this, tok::l_paren, - tok::annot_pragma_openmp_end); - while (Tok.isNot(tok::annot_pragma_openmp_end)) { - OpenMPClauseKind CKind = Tok.isAnnotation() - ? OMPC_unknown - : getOpenMPClauseKind(PP.getSpelling(Tok)); - SourceLocation ClauseLoc = ConsumeToken(); - - // Parse '('. - T.consumeOpen(); - - if (CKind == OMPC_when) { - OMPTraitInfo &TI = Actions.getASTContext().getNewOMPTraitInfo(); - parseOMPContextSelectors(ClauseLoc, TI); - - // Parse ':'. - if (Tok.is(tok::colon)) - ConsumeAnyToken(); + // 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(&Actions.getASTContext().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())) { + // Re-parse from the first clause for every variant but the last. + std::optional<TentativeParsingAction> VariantTPA; + if (I + 1 < TraitInfos.size()) + VariantTPA.emplace(*this); + + for (unsigned J = 0, E = std::min(I, NumClauses); J != E; ++J) { + ConsumeToken(); // Clause name. + T.consumeOpen(); // '(' + SkipUntil(tok::r_paren, StopBeforeMatch); + T.consumeClose(); // ')' } - OpenMPDirectiveKind DKind = OMPD_unknown; - SmallVector<OMPClause *, 5> Clauses; - - if (!Tok.is(tok::r_paren)) { - // Parse directive kind (e.g., 'parallel', 'single'). - DKind = parseOpenMPDirectiveKind(*this); - - if (Tok.isNot(tok::annot_pragma_openmp_end) && - Tok.isNot(tok::r_paren)) - ConsumeAnyToken(); - - // Open temporary DSA block so clauses have an active directive frame. - if (DKind != OMPD_unknown) - Actions.OpenMP().StartOpenMPDSABlock(DKind, DeclarationNameInfo(), - getCurScope(), ClauseLoc); - - // Parse clauses for this directive variant. - while (Tok.isNot(tok::r_paren) && - Tok.isNot(tok::annot_pragma_openmp_end)) { - // Skip optional commas between clauses. - if (Tok.is(tok::comma)) - ConsumeAnyToken(); - - OpenMPClauseKind ClauseKind = - Tok.isAnnotation() ? OMPC_unknown - : getOpenMPClauseKind(PP.getSpelling(Tok)); - - if (ClauseKind == OMPC_unknown) - break; - - Actions.OpenMP().StartOpenMPClause(ClauseKind); - OMPClause *Clause = - ParseOpenMPClause(DKind, ClauseKind, - /* FirstClause */ Clauses.empty()); - Actions.OpenMP().EndOpenMPClause(); - - if (Clause) - Clauses.push_back(Clause); + 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); + ConsumeToken(); // ':' } - - // Close the temporary DSA block for header parsing. - if (DKind != OMPD_unknown) - Actions.OpenMP().EndOpenMPDSABlock(nullptr); + HasDirective = Tok.isNot(tok::r_paren); } - // Parse ')' or recover to pragma end on syntax error. - if (T.consumeClose()) - T.skipToEnd(); - - ClauseKinds.push_back(CKind); - DirectiveKinds.push_back(DKind); - DirectiveClauses.push_back(Clauses); - } - - SourceLocation EndLoc = Tok.getLocation(); - ConsumeAnnotationToken(); - - // Parse the body separately for each variant to establish proper DSA. - SmallVector<Stmt *, 4> VariantBodies; - - for (unsigned I : llvm::seq<unsigned>(DirectiveKinds.size())) { - std::optional<TentativeParsingAction> TPA; - if (I < DirectiveKinds.size() - 1) - TPA.emplace(*this); - - const bool HasDirective = (DirectiveKinds[I] != OMPD_unknown); - + StmtResult Variant; if (HasDirective) { - Actions.OpenMP().StartOpenMPDSABlock( - DirectiveKinds[I], DeclarationNameInfo(), getCurScope(), Loc); - Actions.OpenMP().ActOnOpenMPRegionStart(DirectiveKinds[I], - getCurScope()); - } - - StmtResult Body; - ParsingOpenMPDirectiveRAII NormalScope(*this, /*Value=*/false); - { - Sema::CompoundScopeRAII Scope(Actions); - Body = ParseStatement(); - } - if (Body.isInvalid()) { - if (HasDirective) - Actions.OpenMP().EndOpenMPDSABlock(nullptr); - if (TPA) - TPA->Revert(); - return StmtError(); + Variant = ParseOpenMPDeclarativeOrExecutableDirective( + StmtCtx, /*ReadDirectiveWithinMetadirective=*/true); + } else { + // No directive variant: the variant is the plain associated + // statement. + while (Tok.isNot(tok::annot_pragma_openmp_end)) + ConsumeAnyToken(); + ConsumeAnnotationToken(); + ParsingOpenMPDirectiveRAII NormalScope(*this, /*Value=*/false); + Variant = ParseStatement(); } - if (HasDirective) { - Body = - Actions.OpenMP().ActOnOpenMPRegionEnd(Body, DirectiveClauses[I]); - if (Body.isInvalid()) { - Actions.OpenMP().EndOpenMPDSABlock(nullptr); - if (TPA) - TPA->Revert(); - return StmtError(); - } - Actions.OpenMP().EndOpenMPDSABlock(Body.get()); - } - VariantBodies.push_back(Body.get()); - if (TPA) - TPA->Revert(); + if (VariantTPA) + VariantTPA->Revert(); + if (Variant.isInvalid()) + HasError = true; + else + Variants.push_back(Variant.get()); } - - // Convert DirectiveClauses to ArrayRef<ArrayRef<OMPClause *>>. - SmallVector<ArrayRef<OMPClause *>, 4> ClausesArrayRefs; - for (const auto &Clauses : DirectiveClauses) - ClausesArrayRefs.push_back(Clauses); - return Actions.OpenMP().ActOnOpenMPMetaDirective( - Loc, EndLoc, TraitInfos, ClauseKinds, DirectiveKinds, - ClausesArrayRefs, VariantBodies); + if (HasError) + return StmtError(); + return Actions.OpenMP().ActOnOpenMPMetaDirective(Loc, EndLoc, TraitInfos, + Variants); } int Idx = 0; diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp index 14a6988cbd53a7..c07092276023db 100644 --- a/clang/lib/Sema/SemaOpenMP.cpp +++ b/clang/lib/Sema/SemaOpenMP.cpp @@ -3773,31 +3773,15 @@ StmtResult SemaOpenMP::ActOnOpenMPAssumeDirective(ArrayRef<OMPClause *> Clauses, StmtResult SemaOpenMP::ActOnOpenMPMetaDirective( SourceLocation StartLoc, SourceLocation EndLoc, - ArrayRef<OMPTraitInfo *> TraitInfos, ArrayRef<OpenMPClauseKind> ClauseKinds, - ArrayRef<OpenMPDirectiveKind> DirectiveKinds, - ArrayRef<ArrayRef<OMPClause *>> DirectiveClauses, - ArrayRef<Stmt *> VariantBodies) { - - assert((ClauseKinds.size() == DirectiveKinds.size() && - DirectiveKinds.size() == DirectiveClauses.size() && - DirectiveClauses.size() == VariantBodies.size()) && - "Mismatched variant arrays"); - - // VariantBodies are complete CapturedStmt nodes with DSA from Parser. + 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 3: Extract conditions from TraitInfos. // TODO Phase 4: Codegen - generate runtime if-else chain. - - // Temporary stub: return first variant until OMPMetaDirective exists. - // Early return if no variants were successfully parsed. - if (VariantBodies.empty()) + // Temporary stub: return the first variant until OMPMetaDirective exists. + if (Variants.empty()) return StmtError(); - - // Return first variant if it's valid. - if (VariantBodies[0]) - return VariantBodies[0]; - - return StmtError(); + return Variants.front(); } OMPRequiresDecl * diff --git a/clang/test/OpenMP/metadirective_runtime_messages.cpp b/clang/test/OpenMP/metadirective_runtime_messages.cpp new file mode 100644 index 00000000000000..7c2b5da55a6b0b --- /dev/null +++ b/clang/test/OpenMP/metadirective_runtime_messages.cpp @@ -0,0 +1,40 @@ +// 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) + { + } +} >From f503493e0b8acc03928b2ef4608a4d7387bb8063 Mon Sep 17 00:00:00 2001 From: Zahira Ammarguellat <[email protected]> Date: Fri, 16 Oct 2026 00:30:38 -0700 Subject: [PATCH 08/10] Fix fomat --- clang/lib/Parse/ParseOpenMP.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index 1d1df9adeb42fb..be05dc84370093 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -2721,8 +2721,7 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( return VMI.HasNonConstantUserCondition; }); - if (HasUserCondition && - getLangOpts().OpenMPMetadirectiveRuntimeSelection) { + if (HasUserCondition && 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. >From 8ea7bf960258b23d6766f3991ac080ae507198e9 Mon Sep 17 00:00:00 2001 From: Zahira Ammarguellat <[email protected]> Date: Fri, 23 Oct 2026 06:53:06 -0700 Subject: [PATCH 09/10] Applied suggestions --- clang/lib/Parse/ParseOpenMP.cpp | 34 +++++++++---------- .../OpenMP/metadirective_runtime_messages.cpp | 22 ++++++++++++ 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index be05dc84370093..4ceab7855d9ae0 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -2729,7 +2729,7 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( if (llvm::none_of(TraitInfos, [](const OMPTraitInfo *TI) { return TI->Sets.empty(); })) - TraitInfos.push_back(&Actions.getASTContext().getNewOMPTraitInfo()); + 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 @@ -2738,18 +2738,6 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( SmallVector<Stmt *, 4> Variants; bool HasError = false; for (unsigned I : llvm::seq<unsigned>(TraitInfos.size())) { - // Re-parse from the first clause for every variant but the last. - std::optional<TentativeParsingAction> VariantTPA; - if (I + 1 < TraitInfos.size()) - VariantTPA.emplace(*this); - - for (unsigned J = 0, E = std::min(I, NumClauses); J != E; ++J) { - ConsumeToken(); // Clause name. - T.consumeOpen(); // '(' - SkipUntil(tok::r_paren, StopBeforeMatch); - T.consumeClose(); // ')' - } - bool HasDirective = false; if (I < NumClauses) { OpenMPClauseKind CKind = getOpenMPClauseKind(PP.getSpelling(Tok)); @@ -2759,27 +2747,37 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( // The selectors were parsed into TraitInfos[I] by the first pass; // skip them so their diagnostics are not repeated. SkipUntil(tok::colon, StopBeforeMatch); - ConsumeToken(); // ':' + TryConsumeToken(tok::colon); } - HasDirective = Tok.isNot(tok::r_paren); + 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: the variant is the plain associated - // statement. + // 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(); } - if (VariantTPA) + if (VariantTPA) { VariantTPA->Revert(); + SkipUntil(tok::r_paren, StopBeforeMatch); + T.consumeClose(); + } if (Variant.isInvalid()) HasError = true; else diff --git a/clang/test/OpenMP/metadirective_runtime_messages.cpp b/clang/test/OpenMP/metadirective_runtime_messages.cpp index 7c2b5da55a6b0b..f394d46eeddf68 100644 --- a/clang/test/OpenMP/metadirective_runtime_messages.cpp +++ b/clang/test/OpenMP/metadirective_runtime_messages.cpp @@ -38,3 +38,25 @@ void ternary_in_condition(int a, int b) { { } } + +// 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; + } +} >From 406b320c6572954032cd929c292c651b8366d8f7 Mon Sep 17 00:00:00 2001 From: Zahira Ammarguellat <[email protected]> Date: Sun, 25 Oct 2026 02:26:45 -0700 Subject: [PATCH 10/10] Applied suggestions --- .../clang/Basic/DiagnosticParseKinds.td | 3 +++ clang/lib/AST/OpenMPClause.cpp | 4 +--- clang/lib/Parse/ParseOpenMP.cpp | 21 ++++++++++++++----- .../OpenMP/metadirective_runtime_messages.cpp | 10 +++++++++ .../include/llvm/Frontend/OpenMP/OMPContext.h | 4 ---- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 4ca2ed38363c4b..4cc341c8ff44d0 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1567,6 +1567,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/lib/AST/OpenMPClause.cpp b/clang/lib/AST/OpenMPClause.cpp index 1a4c03f3bf1b11..2061d5395ac658 100644 --- a/clang/lib/AST/OpenMPClause.cpp +++ b/clang/lib/AST/OpenMPClause.cpp @@ -3131,10 +3131,8 @@ void OMPTraitInfo::getAsVariantMatchInfo(ASTContext &ASTCtx, VMI.addTrait(CondVal->isZero() ? TraitProperty::user_condition_false : TraitProperty::user_condition_true, "<condition>"); - else { + else VMI.addTrait(TraitProperty::user_condition_false, "<condition>"); - VMI.HasNonConstantUserCondition = true; - } continue; } diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index 4ceab7855d9ae0..55f5d5dc42fa49 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -2717,11 +2717,15 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( // Check if we have user conditions with non-constant expressions that // require runtime selection. - bool HasUserCondition = llvm::any_of(VMIs, [](const VariantMatchInfo &VMI) { - return VMI.HasNonConstantUserCondition; - }); - - if (HasUserCondition && getLangOpts().OpenMPMetadirectiveRuntimeSelection) { + 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. @@ -2772,6 +2776,13 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective( 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(); diff --git a/clang/test/OpenMP/metadirective_runtime_messages.cpp b/clang/test/OpenMP/metadirective_runtime_messages.cpp index f394d46eeddf68..7419fbe010e19a 100644 --- a/clang/test/OpenMP/metadirective_runtime_messages.cpp +++ b/clang/test/OpenMP/metadirective_runtime_messages.cpp @@ -60,3 +60,13 @@ void local_decl(int flag) { 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/llvm/include/llvm/Frontend/OpenMP/OMPContext.h b/llvm/include/llvm/Frontend/OpenMP/OMPContext.h index 7c069721ba6772..7849d326659944 100644 --- a/llvm/include/llvm/Frontend/OpenMP/OMPContext.h +++ b/llvm/include/llvm/Frontend/OpenMP/OMPContext.h @@ -156,10 +156,6 @@ struct VariantMatchInfo { SmallVector<StringRef, 8> ISATraits; SmallVector<TraitProperty, 8> ConstructTraits; SmallDenseMap<TraitProperty, APInt> ScoreMap; - - /// True if this variant has a user condition that could not be evaluated at - /// compile time (non-constant expression). - bool HasNonConstantUserCondition = false; }; /// The context for a source location is made up of active property traits, _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
