https://github.com/ykhatav updated 
https://github.com/llvm/llvm-project/pull/198868

>From 6c546f7211978cb54ee0716b330fe0fa6b44012c Mon Sep 17 00:00:00 2001
From: "Khatavkar, Yashasvi" <[email protected]>
Date: Wed, 20 May 2026 11:34:43 -0700
Subject: [PATCH 1/2] Add parsing support for prefer_type modifier extensions
 for interop object

---
 clang/include/clang/AST/OpenMPClause.h        |  82 ++++++++++--
 .../clang/Basic/DiagnosticParseKinds.td       |   2 +
 clang/include/clang/Basic/OpenMPKinds.h       |  10 +-
 clang/lib/AST/OpenMPClause.cpp                |  98 ++++++++++++---
 clang/lib/Parse/ParseOpenMP.cpp               | 117 ++++++++++++++++--
 clang/lib/Sema/SemaOpenMP.cpp                 |  11 +-
 clang/lib/Sema/TreeTransform.h                |  27 +++-
 clang/lib/Serialization/ASTReader.cpp         |  22 +++-
 clang/lib/Serialization/ASTWriter.cpp         |  12 ++
 .../interop_prefer_type_brace_ast_print.cpp   |  71 +++++++++++
 .../interop_prefer_type_brace_messages.cpp    |  42 +++++++
 11 files changed, 442 insertions(+), 52 deletions(-)
 create mode 100644 clang/test/OpenMP/interop_prefer_type_brace_ast_print.cpp
 create mode 100644 clang/test/OpenMP/interop_prefer_type_brace_messages.cpp

diff --git a/clang/include/clang/AST/OpenMPClause.h 
b/clang/include/clang/AST/OpenMPClause.h
index ccf2c40bc5efa..629d03f9e74a6 100644
--- a/clang/include/clang/AST/OpenMPClause.h
+++ b/clang/include/clang/AST/OpenMPClause.h
@@ -8851,7 +8851,7 @@ class OMPOrderClause final : public OMPClause {
 /// \endcode
 class OMPInitClause final
     : public OMPVarListClause<OMPInitClause>,
-      private llvm::TrailingObjects<OMPInitClause, Expr *> {
+      private llvm::TrailingObjects<OMPInitClause, Expr *, unsigned> {
   friend class OMPClauseReader;
   friend OMPVarListClause;
   friend TrailingObjects;
@@ -8861,6 +8861,30 @@ class OMPInitClause final
 
   bool IsTarget = false;
   bool IsTargetSync = false;
+  bool HasPreferAttrs = false;
+
+  /// Total number of attr() exprs across all pref-specs (sum of the
+  /// per-pref-spec counts in the trailing unsigned[]).
+  unsigned NumAttrs = 0;
+
+  /// Trailing-objects layout (single contiguous Expr* array):
+  ///   Expr*[ varlist_size() + NumAttrs ]:
+  ///     [0]                    = InteropVar
+  ///     [1 .. NumPrefs]        = Fr expr per pref-spec (null if attr-only)
+  ///     [varlist_size() ..]    = flat list of attr exprs, concatenated in
+  ///                              pref-spec order
+  ///   unsigned[ NumPrefs ]:
+  ///     [i]                    = number of attr() exprs in pref-spec i
+  ///
+  /// varlist_size() = 1 + NumPrefs, so OMPVarListClause iteration covers
+  /// InteropVar + the Fr block.
+
+  size_t numTrailingObjects(OverloadToken<Expr *>) const {
+    return varlist_size() + NumAttrs;
+  }
+  size_t numTrailingObjects(OverloadToken<unsigned>) const {
+    return getNumPrefs();
+  }
 
   void setInteropVar(Expr *E) { varlist_begin()[0] = E; }
 
@@ -8868,6 +8892,10 @@ class OMPInitClause final
 
   void setIsTargetSync(bool V) { IsTargetSync = V; }
 
+  void setHasPreferAttrs(bool V) { HasPreferAttrs = V; }
+
+  void setAttrs(ArrayRef<unsigned> Counts, ArrayRef<Expr *> Attrs);
+
   /// Sets the location of the interop variable.
   void setVarLoc(SourceLocation Loc) { VarLoc = Loc; }
 
@@ -8879,7 +8907,7 @@ class OMPInitClause final
   /// \param LParenLoc Location of '('.
   /// \param VarLoc Location of the interop variable.
   /// \param EndLoc Ending location of the clause.
-  /// \param N Number of expressions.
+  /// \param N Number of varlist entries (1 + NumPrefs).
   OMPInitClause(bool IsTarget, bool IsTargetSync, SourceLocation StartLoc,
                 SourceLocation LParenLoc, SourceLocation VarLoc,
                 SourceLocation EndLoc, unsigned N)
@@ -8894,6 +8922,14 @@ class OMPInitClause final
   }
 
 public:
+  struct PrefView {
+    /// Foreign-runtime-id expression. Null for attr-only specs.
+    Expr *Fr;
+    /// attr() string-literal expressions. Empty for fr-only or OMP 5.1
+    /// flat specs.
+    ArrayRef<Expr *> Attrs;
+  };
+
   /// Creates a fully specified clause.
   ///
   /// \param C AST context.
@@ -8909,11 +8945,14 @@ class OMPInitClause final
                                SourceLocation LParenLoc, SourceLocation VarLoc,
                                SourceLocation EndLoc);
 
-  /// Creates an empty clause with \a N expressions.
+  /// Creates an empty clause sized for \a NumPrefs pref-specs and \a NumAttrs
+  /// total attr() exprs across them.
   ///
   /// \param C AST context.
-  /// \param N Number of expression items.
-  static OMPInitClause *CreateEmpty(const ASTContext &C, unsigned N);
+  /// \param NumPrefs Number of pref-specs (length of the Fr block).
+  /// \param NumAttrs Total attr() exprs across all pref-specs.
+  static OMPInitClause *CreateEmpty(const ASTContext &C, unsigned NumPrefs,
+                                    unsigned NumAttrs);
 
   /// Returns the location of the interop variable.
   SourceLocation getVarLoc() const { return VarLoc; }
@@ -8928,9 +8967,38 @@ class OMPInitClause final
   /// Returns true is interop-type 'targetsync' is used.
   bool getIsTargetSync() const { return IsTargetSync; }
 
+  /// Returns true if OMP 6.0 {fr/attr} syntax is used.
+  bool getHasPreferAttrs() const { return HasPreferAttrs; }
+
+  /// Number of pref-specs in prefer_type(...).
+  unsigned getNumPrefs() const { return varlist_size() - 1; }
+
+  /// Total attrs across all pref-specs.
+  unsigned getNumAttrs() const { return NumAttrs; }
+
+  /// Per-pref-spec attr counts (one entry per pref-spec).
+  ArrayRef<unsigned> getAttrCounts() const {
+    return getTrailingObjects<unsigned>(getNumPrefs());
+  }
+
+  PrefView getPref(unsigned I) {
+    assert(I < getNumPrefs() && "pref-spec index out of range");
+    Expr **E = getTrailingObjects<Expr *>();
+    ArrayRef<unsigned> Counts = getAttrCounts();
+    unsigned AttrStart = 0;
+    for (unsigned K = 0; K < I; ++K)
+      AttrStart += Counts[K];
+    return PrefView{
+        E[1 + I], ArrayRef<Expr *>(E + varlist_size() + AttrStart, Counts[I])};
+  }
+  PrefView getPref(unsigned I) const {
+    return const_cast<OMPInitClause *>(this)->getPref(I);
+  }
+
   child_range children() {
-    return child_range(reinterpret_cast<Stmt **>(varlist_begin()),
-                       reinterpret_cast<Stmt **>(varlist_end()));
+    return child_range(
+        reinterpret_cast<Stmt **>(varlist_begin()),
+        reinterpret_cast<Stmt **>(varlist_begin() + varlist_size() + 
NumAttrs));
   }
 
   const_child_range children() const {
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td 
b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 7bcd1870a2600..6abda0671add2 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1691,6 +1691,8 @@ def err_omp_expected_interop_type : Error<
 def warn_omp_more_one_interop_type
   : Warning<"interop type '%0' cannot be specified more than once">,
     InGroup<OpenMPClauses>;
+def err_omp_interop_multiple_fr : Error<
+  "only one 'fr' selector allowed per preference-specification">;
 def err_expected_sequence_or_directive : Error<
   "expected an OpenMP 'directive' or 'sequence' attribute argument">;
 def ext_omp_attributes : ExtWarn<
diff --git a/clang/include/clang/Basic/OpenMPKinds.h 
b/clang/include/clang/Basic/OpenMPKinds.h
index 4e83bfcd0128b..72b25e0908a34 100644
--- a/clang/include/clang/Basic/OpenMPKinds.h
+++ b/clang/include/clang/Basic/OpenMPKinds.h
@@ -292,12 +292,20 @@ static constexpr unsigned 
NumberOfOMPAllocateClauseModifiers =
 
 /// Contains 'interop' data for 'append_args' and 'init' clauses.
 class Expr;
+/// One entry of a prefer_type list. Each pref-spec carries an optional fr()
+/// foreign-runtime-id expression and zero or more attr() ext-string-literal
+/// expressions. Fr is nullptr for attr-only specs.
+struct OMPInteropPref final {
+  Expr *Fr = nullptr;
+  llvm::SmallVector<Expr *, 2> Attrs;
+};
 struct OMPInteropInfo final {
   OMPInteropInfo(bool IsTarget = false, bool IsTargetSync = false)
       : IsTarget(IsTarget), IsTargetSync(IsTargetSync) {}
   bool IsTarget;
   bool IsTargetSync;
-  llvm::SmallVector<Expr *, 4> PreferTypes;
+  bool HasPreferAttrs = false;
+  llvm::SmallVector<OMPInteropPref, 4> Prefs;
 };
 
 OpenMPDefaultClauseVariableCategory
diff --git a/clang/lib/AST/OpenMPClause.cpp b/clang/lib/AST/OpenMPClause.cpp
index 3a35e17aff40b..9fce666ce00d8 100644
--- a/clang/lib/AST/OpenMPClause.cpp
+++ b/clang/lib/AST/OpenMPClause.cpp
@@ -1805,19 +1805,52 @@ OMPInitClause *OMPInitClause::Create(const ASTContext 
&C, Expr *InteropVar,
                                      SourceLocation VarLoc,
                                      SourceLocation EndLoc) {
 
-  void *Mem =
-      C.Allocate(totalSizeToAlloc<Expr *>(InteropInfo.PreferTypes.size() + 1));
-  auto *Clause = new (Mem) OMPInitClause(
-      InteropInfo.IsTarget, InteropInfo.IsTargetSync, StartLoc, LParenLoc,
-      VarLoc, EndLoc, InteropInfo.PreferTypes.size() + 1);
-  Clause->setInteropVar(InteropVar);
-  llvm::copy(InteropInfo.PreferTypes, Clause->getTrailingObjects() + 1);
+  unsigned NumPrefs = InteropInfo.Prefs.size();
+  unsigned NumAttrs = 0;
+  for (const OMPInteropPref &P : InteropInfo.Prefs)
+    NumAttrs += P.Attrs.size();
+
+  // Trailing layout: Expr*[1 + NumPrefs + NumAttrs], unsigned[NumPrefs].
+  void *Mem = C.Allocate(
+      totalSizeToAlloc<Expr *, unsigned>(1 + NumPrefs + NumAttrs, NumPrefs));
+  auto *Clause = new (Mem)
+      OMPInitClause(InteropInfo.IsTarget, InteropInfo.IsTargetSync, StartLoc,
+                    LParenLoc, VarLoc, EndLoc, /*VarListN=*/1 + NumPrefs);
+  Clause->NumAttrs = NumAttrs;
+  Clause->HasPreferAttrs = InteropInfo.HasPreferAttrs;
+
+  Expr **E = Clause->getTrailingObjects<Expr *>();
+  E[0] = InteropVar;
+  for (unsigned I = 0; I < NumPrefs; ++I)
+    E[1 + I] = InteropInfo.Prefs[I].Fr;
+  unsigned *Counts = Clause->getTrailingObjects<unsigned>();
+  unsigned AttrPos = 1 + NumPrefs;
+  for (unsigned I = 0; I < NumPrefs; ++I) {
+    Counts[I] = InteropInfo.Prefs[I].Attrs.size();
+    for (Expr *A : InteropInfo.Prefs[I].Attrs)
+      E[AttrPos++] = A;
+  }
   return Clause;
 }
 
-OMPInitClause *OMPInitClause::CreateEmpty(const ASTContext &C, unsigned N) {
-  void *Mem = C.Allocate(totalSizeToAlloc<Expr *>(N));
-  return new (Mem) OMPInitClause(N);
+OMPInitClause *OMPInitClause::CreateEmpty(const ASTContext &C,
+                                          unsigned NumPrefs,
+                                          unsigned NumAttrs) {
+  void *Mem = C.Allocate(
+      totalSizeToAlloc<Expr *, unsigned>(1 + NumPrefs + NumAttrs, NumPrefs));
+  auto *Clause = new (Mem) OMPInitClause(/*VarListN=*/1 + NumPrefs);
+  Clause->NumAttrs = NumAttrs;
+  return Clause;
+}
+
+void OMPInitClause::setAttrs(ArrayRef<unsigned> Counts,
+                             ArrayRef<Expr *> Attrs) {
+  assert(Counts.size() == getNumPrefs() &&
+         "attr-count vector size must match number of pref-specs");
+  assert(Attrs.size() == NumAttrs &&
+         "attr-expr count must match preallocated NumAttrs");
+  llvm::copy(Counts, getTrailingObjects<unsigned>());
+  llvm::copy(Attrs, getTrailingObjects<Expr *>() + varlist_size());
 }
 
 OMPBindClause *
@@ -2388,17 +2421,42 @@ void OMPClausePrinter::VisitOMPHintClause(OMPHintClause 
*Node) {
 
 void OMPClausePrinter::VisitOMPInitClause(OMPInitClause *Node) {
   OS << "init(";
-  bool First = true;
-  for (const Expr *E : Node->prefs()) {
-    if (First)
-      OS << "prefer_type(";
-    else
-      OS << ",";
-    E->printPretty(OS, nullptr, Policy);
-    First = false;
-  }
-  if (!First)
+  unsigned NumPrefs = Node->getNumPrefs();
+  if (NumPrefs > 0) {
+    OS << "prefer_type(";
+    if (Node->getHasPreferAttrs()) {
+      // OMP 6.0 brace-grouped form
+      llvm::interleaveComma(
+          llvm::seq<unsigned>(0, NumPrefs), OS, [&](unsigned I) {
+            OMPInitClause::PrefView P = Node->getPref(I);
+            OS << "{";
+            if (P.Fr) {
+              OS << "fr(";
+              P.Fr->printPretty(OS, nullptr, Policy);
+              OS << ")";
+              if (!P.Attrs.empty())
+                OS << ", ";
+            }
+            if (!P.Attrs.empty()) {
+              OS << "attr(";
+              llvm::interleaveComma(P.Attrs, OS, [&](const Expr *A) {
+                A->printPretty(OS, nullptr, Policy);
+              });
+              OS << ")";
+            }
+            OS << "}";
+          });
+    } else {
+      llvm::interleave(
+          llvm::seq<unsigned>(0, NumPrefs), OS,
+          [&](unsigned I) {
+            if (Expr *Fr = Node->getPref(I).Fr)
+              Fr->printPretty(OS, nullptr, Policy);
+          },
+          ",");
+    }
     OS << "), ";
+  }
   if (Node->getIsTarget())
     OS << "target";
   if (Node->getIsTargetSync()) {
diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp
index 7f3c575fb68bb..aab5fb035b912 100644
--- a/clang/lib/Parse/ParseOpenMP.cpp
+++ b/clang/lib/Parse/ParseOpenMP.cpp
@@ -3690,9 +3690,8 @@ bool Parser::ParseOMPInteropInfo(OMPInteropInfo 
&InteropInfo,
 
   while (Tok.is(tok::identifier)) {
     // Currently prefer_type is only allowed with 'init' and it must be first.
-    bool PreferTypeAllowed = Kind == OMPC_init &&
-                             InteropInfo.PreferTypes.empty() && !IsTarget &&
-                             !IsTargetSync;
+    bool PreferTypeAllowed = Kind == OMPC_init && InteropInfo.Prefs.empty() &&
+                             !IsTarget && !IsTargetSync;
     if (Tok.getIdentifierInfo()->isStr("target")) {
       // OpenMP 5.1 [2.15.1, interop Construct, Restrictions]
       // Each interop-type may be specified on an action-clause at most
@@ -3715,17 +3714,109 @@ bool Parser::ParseOMPInteropInfo(OMPInteropInfo 
&InteropInfo,
         HasError = true;
 
       while (Tok.isNot(tok::r_paren)) {
-        SourceLocation Loc = Tok.getLocation();
-        ExprResult LHS = ParseCastExpression(CastParseKind::AnyCastExpr);
-        ExprResult PTExpr = ParseRHSOfBinaryExpression(LHS, prec::Conditional);
-        PTExpr = Actions.ActOnFinishFullExpr(PTExpr.get(), Loc,
-                                             /*DiscardedValue=*/false);
-        if (PTExpr.isUsable()) {
-          InteropInfo.PreferTypes.push_back(PTExpr.get());
-        } else {
-          HasError = true;
-          SkipUntil(tok::comma, tok::r_paren, tok::annot_pragma_openmp_end,
+        // OMP 6.0: { fr(...), attr(...) } brace-grouped pref-spec
+        if (Tok.is(tok::l_brace)) {
+          BalancedDelimiterTracker BT(*this, tok::l_brace,
+                                      tok::annot_pragma_openmp_end);
+          BT.consumeOpen();
+          Expr *FrExpr = nullptr;
+          SmallVector<Expr *, 2> AttrExprs;
+          bool SeenFr = false;
+
+          while (Tok.isNot(tok::r_brace) &&
+                 Tok.isNot(tok::annot_pragma_openmp_end)) {
+            if (Tok.is(tok::identifier) &&
+                Tok.getIdentifierInfo()->isStr("fr")) {
+              if (SeenFr) {
+                // Diagnose and skip the duplicate fr(...) entirely
+                Diag(Tok, diag::err_omp_interop_multiple_fr);
+                HasError = true;
+                ConsumeToken(); // 'fr'
+                SkipUntil(
+                    {tok::comma, tok::r_brace, tok::annot_pragma_openmp_end},
                     StopBeforeMatch);
+                continue;
+              }
+              SeenFr = true;
+              ConsumeToken(); // 'fr'
+              BalancedDelimiterTracker FT(*this, tok::l_paren,
+                                          tok::annot_pragma_openmp_end);
+              if (FT.expectAndConsume(diag::err_expected_lparen_after, "fr")) {
+                HasError = true;
+                SkipUntil({tok::comma, tok::r_brace, tok::r_paren,
+                           tok::annot_pragma_openmp_end},
+                          StopBeforeMatch);
+                continue;
+              }
+              SourceLocation Loc = Tok.getLocation();
+              ExprResult LHS = ParseCastExpression(CastParseKind::AnyCastExpr);
+              ExprResult Arg =
+                  ParseRHSOfBinaryExpression(LHS, prec::Conditional);
+              Arg = Actions.ActOnFinishFullExpr(Arg.get(), Loc, false);
+              if (Arg.isUsable())
+                FrExpr = Arg.get();
+              else
+                HasError = true;
+              FT.consumeClose();
+            } else if (Tok.is(tok::identifier) &&
+                       Tok.getIdentifierInfo()->isStr("attr")) {
+              ConsumeToken(); // 'attr'
+              BalancedDelimiterTracker AT(*this, tok::l_paren,
+                                          tok::annot_pragma_openmp_end);
+              if (AT.expectAndConsume(diag::err_expected_lparen_after,
+                                      "attr")) {
+                HasError = true;
+                SkipUntil({tok::comma, tok::r_brace, tok::r_paren,
+                           tok::annot_pragma_openmp_end},
+                          StopBeforeMatch);
+                continue;
+              }
+              while (Tok.isNot(tok::r_paren) && Tok.isNot(tok::r_brace) &&
+                     Tok.isNot(tok::annot_pragma_openmp_end)) {
+                if (Tok.is(tok::string_literal)) {
+                  ExprResult StrRes = ParseStringLiteralExpression();
+                  if (!StrRes.isUsable())
+                    HasError = true;
+                  else
+                    AttrExprs.push_back(StrRes.get());
+                } else {
+                  HasError = true;
+                  Diag(Tok, diag::err_expected) << tok::string_literal;
+                  ConsumeToken();
+                }
+                if (Tok.is(tok::comma))
+                  ConsumeToken();
+              }
+              AT.consumeClose();
+            } else {
+              HasError = true;
+              Diag(Tok, diag::err_omp_expected_interop_type);
+              ConsumeToken();
+            }
+            if (Tok.is(tok::comma))
+              ConsumeToken();
+          }
+          if (BT.consumeClose())
+            HasError = true;
+
+          InteropInfo.Prefs.push_back({FrExpr, std::move(AttrExprs)});
+          InteropInfo.HasPreferAttrs = true;
+        } else {
+          // OMP 5.1: flat foreign-runtime-id (string or int). Stored as a
+          // pref-spec with Fr=expr and no attr() entries.
+          SourceLocation Loc = Tok.getLocation();
+          ExprResult LHS = ParseCastExpression(CastParseKind::AnyCastExpr);
+          ExprResult PTExpr =
+              ParseRHSOfBinaryExpression(LHS, prec::Conditional);
+          PTExpr = Actions.ActOnFinishFullExpr(PTExpr.get(), Loc,
+                                               /*DiscardedValue=*/false);
+          if (PTExpr.isUsable()) {
+            InteropInfo.Prefs.push_back({PTExpr.get(), {}});
+          } else {
+            HasError = true;
+            SkipUntil(tok::comma, tok::r_paren, tok::annot_pragma_openmp_end,
+                      StopBeforeMatch);
+          }
         }
 
         if (Tok.is(tok::comma))
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index d6f6bc919a31b..ff3afd932bc27 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -18967,9 +18967,14 @@ OMPClause *SemaOpenMP::ActOnOpenMPInitClause(
   if (!isValidInteropVariable(SemaRef, InteropVar, VarLoc, OMPC_init))
     return nullptr;
 
-  // Check prefer_type values.  These foreign-runtime-id values are either
-  // string literals or constant integral expressions.
-  for (const Expr *E : InteropInfo.PreferTypes) {
+  // Check prefer_type values. fr() arguments are either string literals or
+  // constant integral expressions; null Fr is only valid in OMP 6.0
+  for (const OMPInteropPref &P : InteropInfo.Prefs) {
+    const Expr *E = P.Fr;
+    if (!E) {
+      assert(InteropInfo.HasPreferAttrs && "null Fr requires OMP 6.0 syntax");
+      continue;
+    }
     if (E->isValueDependent() || E->isTypeDependent() ||
         E->isInstantiationDependent() || E->containsUnexpandedParameterPack())
       continue;
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 9c0479fe8aa4f..736a30fe7535e 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -10997,13 +10997,28 @@ OMPClause 
*TreeTransform<Derived>::TransformOMPInitClause(OMPInitClause *C) {
     return nullptr;
 
   OMPInteropInfo InteropInfo(C->getIsTarget(), C->getIsTargetSync());
-  InteropInfo.PreferTypes.reserve(C->varlist_size() - 1);
-  for (Expr *E : llvm::drop_begin(C->varlist())) {
-    ExprResult ER = getDerived().TransformExpr(cast<Expr>(E));
-    if (ER.isInvalid())
-      return nullptr;
-    InteropInfo.PreferTypes.push_back(ER.get());
+  unsigned NumPrefs = C->getNumPrefs();
+  InteropInfo.Prefs.reserve(NumPrefs);
+  for (unsigned I = 0; I < NumPrefs; ++I) {
+    OMPInitClause::PrefView P = C->getPref(I);
+    Expr *NewFr = nullptr;
+    if (P.Fr) {
+      ExprResult ER = getDerived().TransformExpr(P.Fr);
+      if (ER.isInvalid())
+        return nullptr;
+      NewFr = ER.get();
+    }
+    SmallVector<Expr *, 2> NewAttrs;
+    NewAttrs.reserve(P.Attrs.size());
+    for (Expr *A : P.Attrs) {
+      ExprResult ER = getDerived().TransformExpr(A);
+      if (ER.isInvalid())
+        return nullptr;
+      NewAttrs.push_back(ER.get());
+    }
+    InteropInfo.Prefs.push_back({NewFr, std::move(NewAttrs)});
   }
+  InteropInfo.HasPreferAttrs = C->getHasPreferAttrs();
   return getDerived().RebuildOMPInitClause(IVR.get(), InteropInfo,
                                            C->getBeginLoc(), C->getLParenLoc(),
                                            C->getVarLoc(), C->getEndLoc());
diff --git a/clang/lib/Serialization/ASTReader.cpp 
b/clang/lib/Serialization/ASTReader.cpp
index 06bc8f8c8c13e..28e0bc8d22087 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -11786,9 +11786,13 @@ OMPClause *OMPClauseReader::readClause() {
   case llvm::omp::OMPC_order:
     C = new (Context) OMPOrderClause();
     break;
-  case llvm::omp::OMPC_init:
-    C = OMPInitClause::CreateEmpty(Context, Record.readInt());
+  case llvm::omp::OMPC_init: {
+    unsigned VarListSize = Record.readInt();
+    unsigned NumAttrs = Record.readInt();
+    C = OMPInitClause::CreateEmpty(Context, /*NumPrefs=*/VarListSize - 1,
+                                   NumAttrs);
     break;
+  }
   case llvm::omp::OMPC_use:
     C = new (Context) OMPUseClause();
     break;
@@ -12103,6 +12107,20 @@ void OMPClauseReader::VisitOMPInitClause(OMPInitClause 
*C) {
   C->setVarRefs(Vars);
   C->setIsTarget(Record.readBool());
   C->setIsTargetSync(Record.readBool());
+  C->setHasPreferAttrs(Record.readBool());
+
+  unsigned NumPrefs = C->getNumPrefs();
+  SmallVector<unsigned, 4> Counts;
+  SmallVector<Expr *, 8> Attrs;
+  Counts.reserve(NumPrefs);
+  for (unsigned I = 0; I < NumPrefs; ++I) {
+    unsigned NA = Record.readInt();
+    Counts.push_back(NA);
+    for (unsigned J = 0; J < NA; ++J)
+      Attrs.push_back(Record.readSubExpr());
+  }
+  C->setAttrs(Counts, Attrs);
+
   C->setLParenLoc(Record.readSourceLocation());
   C->setVarLoc(Record.readSourceLocation());
 }
diff --git a/clang/lib/Serialization/ASTWriter.cpp 
b/clang/lib/Serialization/ASTWriter.cpp
index 074b0fccdb65d..86178c8481cdf 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -8282,11 +8282,23 @@ void OMPClauseWriter::VisitOMPSIMDClause(OMPSIMDClause 
*) {}
 void OMPClauseWriter::VisitOMPNogroupClause(OMPNogroupClause *) {}
 
 void OMPClauseWriter::VisitOMPInitClause(OMPInitClause *C) {
+  // Sizes for CreateEmpty on the read side: varlist_size = 1 + NumPrefs, then
+  // NumAttrs (total attrs across all pref-specs).
   Record.push_back(C->varlist_size());
+  Record.push_back(C->getNumAttrs());
+  // Varlist (interop var + Fr block).
   for (Expr *VE : C->varlist())
     Record.AddStmt(VE);
   Record.writeBool(C->getIsTarget());
   Record.writeBool(C->getIsTargetSync());
+  Record.writeBool(C->getHasPreferAttrs());
+  // Per-pref-spec: attr count + that many attr exprs, in order.
+  for (unsigned I = 0, E = C->getNumPrefs(); I < E; ++I) {
+    OMPInitClause::PrefView P = C->getPref(I);
+    Record.push_back(P.Attrs.size());
+    for (Expr *A : P.Attrs)
+      Record.AddStmt(A);
+  }
   Record.AddSourceLocation(C->getLParenLoc());
   Record.AddSourceLocation(C->getVarLoc());
 }
diff --git a/clang/test/OpenMP/interop_prefer_type_brace_ast_print.cpp 
b/clang/test/OpenMP/interop_prefer_type_brace_ast_print.cpp
new file mode 100644
index 0000000000000..55936827d934e
--- /dev/null
+++ b/clang/test/OpenMP/interop_prefer_type_brace_ast_print.cpp
@@ -0,0 +1,71 @@
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN:   -fsyntax-only -verify %s
+
+// expected-no-diagnostics
+
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN:   -ast-print %s | FileCheck %s --check-prefix=PRINT
+
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN:   -emit-pch -o %t %s
+
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN:   -include-pch %t -ast-print %s | FileCheck %s --check-prefix=PRINT
+
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN:   -ast-dump %s | FileCheck %s --check-prefix=DUMP
+
+#ifndef HEADER
+#define HEADER
+
+typedef void *omp_interop_t;
+
+void brace_specs() {
+  omp_interop_t obj;
+
+  // Single brace entry with fr() only.
+  // PRINT: #pragma omp interop init(prefer_type({fr("sycl")}), targetsync : 
obj)
+  #pragma omp interop init(prefer_type({fr("sycl")}), targetsync: obj)
+
+  // Multiple brace entries, each with fr() only.
+  // PRINT: #pragma omp interop init(prefer_type({fr("sycl")}, 
{fr("level_zero")}, {fr("opencl")}), targetsync : obj)
+  #pragma omp interop init(prefer_type({fr("sycl")}, {fr("level_zero")}, \
+                                       {fr("opencl")}), targetsync: obj)
+
+  // fr() + attr() on one entry.
+  // PRINT: #pragma omp interop init(prefer_type({fr("sycl"), 
attr("ompx_propX")}), targetsync : obj)
+  #pragma omp interop init(prefer_type({fr("sycl"), attr("ompx_propX")}), \
+                           targetsync: obj)
+
+  // attr() only (no fr()) — any runtime.
+  // PRINT: #pragma omp interop init(prefer_type({attr("ompx_propX", 
"ompx_propY")}), targetsync : obj)
+  #pragma omp interop init(prefer_type({attr("ompx_propX", "ompx_propY")}), \
+                           targetsync: obj)
+
+  // Integer expression inside fr() on brace-grouped entry.
+  // PRINT: #pragma omp interop init(prefer_type({fr(4)}, {fr(6)}), targetsync 
: obj)
+  #pragma omp interop init(prefer_type({fr(4)}, {fr(6)}), targetsync: obj)
+
+  // Mixed flat + brace entries
+  // PRINT: #pragma omp interop init(prefer_type({fr("sycl")}, 
{fr("opencl")}), targetsync : obj)
+  #pragma omp interop init(prefer_type("sycl", {fr("opencl")}), \
+                           targetsync: obj)
+
+  // Multiple pref-specs mixing fr-only, fr+attr, and attr-only.
+  // PRINT: #pragma omp interop init(prefer_type({fr("sycl"), 
attr("ompx_propX")}, {fr("level_zero")}, {attr("ompx_propY")}), targetsync : 
obj)
+  #pragma omp interop init(prefer_type({fr("sycl"), attr("ompx_propX")}, 
{fr("level_zero")}, {attr("ompx_propY")}), targetsync: obj)
+}
+
+template <int N>
+void tmpl(omp_interop_t obj) {
+  // PRINT: #pragma omp interop init(prefer_type({fr(N), attr("ompx_propX")}), 
targetsync : obj)
+  #pragma omp interop init(prefer_type({fr(N), attr("ompx_propX")}), 
targetsync: obj)
+}
+
+// DUMP: FunctionDecl {{.*}} tmpl 'void (omp_interop_t)' 
explicit_instantiation_definition
+// DUMP: TemplateArgument integral '7'
+// DUMP: OMPInitClause
+// DUMP: IntegerLiteral {{.*}} 'int' 7
+// DUMP: StringLiteral {{.*}} "ompx_propX"
+template void tmpl<7>(omp_interop_t);
+#endif // HEADER
diff --git a/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp 
b/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
new file mode 100644
index 0000000000000..2c9d7bbc1a5f1
--- /dev/null
+++ b/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=60 -std=c++11 -o - %s
+
+// Diagnostics for the OMP 6.0 brace-grouped prefer_type syntax.
+
+typedef void *omp_interop_t;
+
+static void foo() {
+  omp_interop_t obj;
+
+  // expected-error@+2 {{only one 'fr' selector allowed per 
preference-specification}}
+  // expected-error@+1 {{expected at least one 'init', 'use', 'destroy', or 
'nowait' clause for '#pragma omp interop'}}
+  #pragma omp interop init(prefer_type({fr("sycl"), fr("opencl")}), 
targetsync: obj)
+
+  // Non-constant variable expression inside fr().
+  int x = 2;
+  // expected-error@+2 {{prefer_list item must be a string literal or constant 
integral expression}}
+  // expected-error@+1 {{expected at least one 'init', 'use', 'destroy', or 
'nowait' clause for '#pragma omp interop'}}
+  #pragma omp interop init(prefer_type({fr(x)}), targetsync: obj)
+
+  // Floating-point literal inside fr().
+  // expected-error@+2 {{prefer_list item must be a string literal or constant 
integral expression}}
+  // expected-error@+1 {{expected at least one 'init', 'use', 'destroy', or 
'nowait' clause for '#pragma omp interop'}}
+  #pragma omp interop init(prefer_type({fr(1.5)}), targetsync: obj)
+
+  // Integer inside attr() — only string literals allowed.
+  // expected-error@+2 {{expected <string_literal>}}
+  // expected-error@+1 {{expected at least one 'init', 'use', 'destroy', or 
'nowait' clause for '#pragma omp interop'}}
+  #pragma omp interop init(prefer_type({attr(1)}), targetsync: obj)
+
+  // expected-error@+2 {{expected '(' after 'fr'}}
+  // expected-error@+1 {{expected at least one 'init', 'use', 'destroy', or 
'nowait' clause for '#pragma omp interop'}}
+  #pragma omp interop init(prefer_type({fr "sycl"}), targetsync: obj)
+
+  // expected-error@+2 {{expected '(' after 'attr'}}
+  // expected-error@+1 {{expected at least one 'init', 'use', 'destroy', or 
'nowait' clause for '#pragma omp interop'}}
+  #pragma omp interop init(prefer_type({attr "ompx_propX"}), targetsync: obj)
+
+  // Anything that is not 'fr' or 'attr' is rejected by the brace parser.
+  // expected-error@+2 {{expected interop type: 'target' and/or 'targetsync'}}
+  // expected-error@+1 {{expected at least one 'init', 'use', 'destroy', or 
'nowait' clause for '#pragma omp interop'}}
+  #pragma omp interop init(prefer_type({foo}), targetsync: obj)
+}

>From 68353435bc61c1b8815d070431b1d8deed71eab4 Mon Sep 17 00:00:00 2001
From: "Khatavkar, Yashasvi" <[email protected]>
Date: Fri, 22 May 2026 11:35:18 -0700
Subject: [PATCH 2/2] refactor parsing code

---
 clang/include/clang/Parse/Parser.h |   6 ++
 clang/lib/Parse/ParseOpenMP.cpp    | 109 ++++++++++++++++-------------
 2 files changed, 65 insertions(+), 50 deletions(-)

diff --git a/clang/include/clang/Parse/Parser.h 
b/clang/include/clang/Parse/Parser.h
index dc3dc8a4ae0e9..ada8ca7e7e52c 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7001,6 +7001,12 @@ class Parser : public CodeCompletionHandler {
   /// Parses the 'interop' parts of the 'append_args' and 'init' clauses.
   bool ParseOMPInteropInfo(OMPInteropInfo &InteropInfo, OpenMPClauseKind Kind);
 
+  /// Parses 'fr(<foreign-runtime-id>)'.
+  ExprResult ParseOMPInteropFrSelector();
+
+  /// Parses 'attr(<string-literal>[, ...])', appending to \p Attrs.
+  bool ParseOMPInteropAttrSelector(SmallVectorImpl<Expr *> &Attrs);
+
   /// Parses clause with an interop variable of kind \a Kind.
   ///
   /// \verbatim
diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp
index aab5fb035b912..de844460c0a15 100644
--- a/clang/lib/Parse/ParseOpenMP.cpp
+++ b/clang/lib/Parse/ParseOpenMP.cpp
@@ -3681,6 +3681,55 @@ bool Parser::ParseOpenMPIndirectClause(
   return false;
 }
 
+ExprResult Parser::ParseOMPInteropFrSelector() {
+  ConsumeToken(); // 'fr'
+  BalancedDelimiterTracker FT(*this, tok::l_paren,
+                              tok::annot_pragma_openmp_end);
+  if (FT.expectAndConsume(diag::err_expected_lparen_after, "fr")) {
+    SkipUntil(
+        {tok::comma, tok::r_brace, tok::r_paren, tok::annot_pragma_openmp_end},
+        StopBeforeMatch);
+    return ExprError();
+  }
+  SourceLocation Loc = Tok.getLocation();
+  ExprResult LHS = ParseCastExpression(CastParseKind::AnyCastExpr);
+  ExprResult Arg = ParseRHSOfBinaryExpression(LHS, prec::Conditional);
+  Arg = Actions.ActOnFinishFullExpr(Arg.get(), Loc, /*DiscardedValue=*/false);
+  FT.consumeClose();
+  return Arg;
+}
+
+bool Parser::ParseOMPInteropAttrSelector(SmallVectorImpl<Expr *> &Attrs) {
+  ConsumeToken(); // 'attr'
+  BalancedDelimiterTracker AT(*this, tok::l_paren,
+                              tok::annot_pragma_openmp_end);
+  if (AT.expectAndConsume(diag::err_expected_lparen_after, "attr")) {
+    SkipUntil(
+        {tok::comma, tok::r_brace, tok::r_paren, tok::annot_pragma_openmp_end},
+        StopBeforeMatch);
+    return true;
+  }
+  bool HasError = false;
+  while (Tok.isNot(tok::r_paren) && Tok.isNot(tok::r_brace) &&
+         Tok.isNot(tok::annot_pragma_openmp_end)) {
+    if (Tok.is(tok::string_literal)) {
+      ExprResult S = ParseStringLiteralExpression();
+      if (S.isUsable())
+        Attrs.push_back(S.get());
+      else
+        HasError = true;
+    } else {
+      HasError = true;
+      Diag(Tok, diag::err_expected) << tok::string_literal;
+      ConsumeToken();
+    }
+    if (Tok.is(tok::comma))
+      ConsumeToken();
+  }
+  AT.consumeClose();
+  return HasError;
+}
+
 bool Parser::ParseOMPInteropInfo(OMPInteropInfo &InteropInfo,
                                  OpenMPClauseKind Kind) {
   const Token &Tok = getCurToken();
@@ -3725,10 +3774,12 @@ bool Parser::ParseOMPInteropInfo(OMPInteropInfo 
&InteropInfo,
 
           while (Tok.isNot(tok::r_brace) &&
                  Tok.isNot(tok::annot_pragma_openmp_end)) {
-            if (Tok.is(tok::identifier) &&
-                Tok.getIdentifierInfo()->isStr("fr")) {
+            if (!Tok.is(tok::identifier)) {
+              HasError = true;
+              Diag(Tok, diag::err_omp_expected_interop_type);
+              ConsumeToken();
+            } else if (Tok.getIdentifierInfo()->isStr("fr")) {
               if (SeenFr) {
-                // Diagnose and skip the duplicate fr(...) entirely
                 Diag(Tok, diag::err_omp_interop_multiple_fr);
                 HasError = true;
                 ConsumeToken(); // 'fr'
@@ -3738,56 +3789,14 @@ bool Parser::ParseOMPInteropInfo(OMPInteropInfo 
&InteropInfo,
                 continue;
               }
               SeenFr = true;
-              ConsumeToken(); // 'fr'
-              BalancedDelimiterTracker FT(*this, tok::l_paren,
-                                          tok::annot_pragma_openmp_end);
-              if (FT.expectAndConsume(diag::err_expected_lparen_after, "fr")) {
-                HasError = true;
-                SkipUntil({tok::comma, tok::r_brace, tok::r_paren,
-                           tok::annot_pragma_openmp_end},
-                          StopBeforeMatch);
-                continue;
-              }
-              SourceLocation Loc = Tok.getLocation();
-              ExprResult LHS = ParseCastExpression(CastParseKind::AnyCastExpr);
-              ExprResult Arg =
-                  ParseRHSOfBinaryExpression(LHS, prec::Conditional);
-              Arg = Actions.ActOnFinishFullExpr(Arg.get(), Loc, false);
-              if (Arg.isUsable())
-                FrExpr = Arg.get();
+              ExprResult Fr = ParseOMPInteropFrSelector();
+              if (Fr.isUsable())
+                FrExpr = Fr.get();
               else
                 HasError = true;
-              FT.consumeClose();
-            } else if (Tok.is(tok::identifier) &&
-                       Tok.getIdentifierInfo()->isStr("attr")) {
-              ConsumeToken(); // 'attr'
-              BalancedDelimiterTracker AT(*this, tok::l_paren,
-                                          tok::annot_pragma_openmp_end);
-              if (AT.expectAndConsume(diag::err_expected_lparen_after,
-                                      "attr")) {
+            } else if (Tok.getIdentifierInfo()->isStr("attr")) {
+              if (ParseOMPInteropAttrSelector(AttrExprs))
                 HasError = true;
-                SkipUntil({tok::comma, tok::r_brace, tok::r_paren,
-                           tok::annot_pragma_openmp_end},
-                          StopBeforeMatch);
-                continue;
-              }
-              while (Tok.isNot(tok::r_paren) && Tok.isNot(tok::r_brace) &&
-                     Tok.isNot(tok::annot_pragma_openmp_end)) {
-                if (Tok.is(tok::string_literal)) {
-                  ExprResult StrRes = ParseStringLiteralExpression();
-                  if (!StrRes.isUsable())
-                    HasError = true;
-                  else
-                    AttrExprs.push_back(StrRes.get());
-                } else {
-                  HasError = true;
-                  Diag(Tok, diag::err_expected) << tok::string_literal;
-                  ConsumeToken();
-                }
-                if (Tok.is(tok::comma))
-                  ConsumeToken();
-              }
-              AT.consumeClose();
             } else {
               HasError = true;
               Diag(Tok, diag::err_omp_expected_interop_type);

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to