[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 16 00:31:01 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/8] 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/8] 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/8]  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/8] 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/8] 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/8] 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/8] 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/8] 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.



More information about the cfe-commits mailing list