[clang] [llvm] [OpenMP] Add runtime selection for metadirective with non-constant conditions. (PR #192455)
Zahira Ammarguellat via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 23 06:53:30 PDT 2026
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 <zahira.ammarguellat at intel.com>
Date: Mon, 17 Aug 2026 14:21:21 -0700
Subject: [PATCH 1/9] 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 <zahira.ammarguellat at intel.com>
Date: Tue, 18 Aug 2026 06:08:45 -0700
Subject: [PATCH 2/9] 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 <zahira.ammarguellat at intel.com>
Date: Fri, 21 Aug 2026 14:04:49 -0700
Subject: [PATCH 3/9] 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 <zahira.ammarguellat at intel.com>
Date: Mon, 31 Aug 2026 12:32:09 -0700
Subject: [PATCH 4/9] 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 <zahira.ammarguellat at intel.com>
Date: Thu, 3 Sep 2026 08:23:05 -0700
Subject: [PATCH 5/9] 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 <zahira.ammarguellat at intel.com>
Date: Thu, 15 Oct 2026 05:55:39 -0700
Subject: [PATCH 6/9] 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 <zahira.ammarguellat at intel.com>
Date: Thu, 15 Oct 2026 10:04:54 -0700
Subject: [PATCH 7/9] 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 <zahira.ammarguellat at intel.com>
Date: Fri, 16 Oct 2026 00:30:38 -0700
Subject: [PATCH 8/9] 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 <zahira.ammarguellat at intel.com>
Date: Fri, 23 Oct 2026 06:53:06 -0700
Subject: [PATCH 9/9] 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;
+ }
+}
More information about the cfe-commits
mailing list