[clang] Add parsing support for prefer_type modifier extensions for interop o… (PR #198868)
via cfe-commits
cfe-commits at lists.llvm.org
Fri May 22 11:36:18 PDT 2026
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" <yashasvi.khatavkar at intel.com>
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 at +2 {{only one 'fr' selector allowed per preference-specification}}
+ // expected-error at +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 at +2 {{prefer_list item must be a string literal or constant integral expression}}
+ // expected-error at +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 at +2 {{prefer_list item must be a string literal or constant integral expression}}
+ // expected-error at +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 at +2 {{expected <string_literal>}}
+ // expected-error at +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 at +2 {{expected '(' after 'fr'}}
+ // expected-error at +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 at +2 {{expected '(' after 'attr'}}
+ // expected-error at +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 at +2 {{expected interop type: 'target' and/or 'targetsync'}}
+ // expected-error at +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" <yashasvi.khatavkar at intel.com>
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);
More information about the cfe-commits
mailing list