[clang] [clang][OpenMP] Add OMP 6.0 prefer_type({fr,attr}) parsing for interop (PR #198868)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 8 07:33:14 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/8] 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/8] 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);
>From 44b4cc935cdb7542124033f8a7fe3014c399f583 Mon Sep 17 00:00:00 2001
From: "Khatavkar, Yashasvi" <yashasvi.khatavkar at intel.com>
Date: Mon, 1 Jun 2026 08:29:40 -0700
Subject: [PATCH 3/8] Address review comments
---
clang/include/clang/AST/OpenMPClause.h | 31 +++++++++-----
clang/include/clang/AST/RecursiveASTVisitor.h | 4 ++
.../clang/Basic/DiagnosticParseKinds.td | 6 +++
clang/lib/AST/OpenMPClause.cpp | 15 +++++--
clang/lib/AST/StmtProfile.cpp | 17 +++++++-
clang/lib/Parse/ParseOpenMP.cpp | 41 +++++++++++++++----
.../interop_prefer_type_brace_messages.cpp | 22 +++++++++-
7 files changed, 111 insertions(+), 25 deletions(-)
diff --git a/clang/include/clang/AST/OpenMPClause.h b/clang/include/clang/AST/OpenMPClause.h
index 629d03f9e74a6..3e2d6967d71f8 100644
--- a/clang/include/clang/AST/OpenMPClause.h
+++ b/clang/include/clang/AST/OpenMPClause.h
@@ -8863,8 +8863,8 @@ class OMPInitClause final
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[]).
+ /// Total number of attr() exprs across all pref-specs; equals the last entry
+ /// of the trailing unsigned[] of cumulative end offsets (or 0 if no prefs).
unsigned NumAttrs = 0;
/// Trailing-objects layout (single contiguous Expr* array):
@@ -8874,7 +8874,10 @@ class OMPInitClause final
/// [varlist_size() ..] = flat list of attr exprs, concatenated in
/// pref-spec order
/// unsigned[ NumPrefs ]:
- /// [i] = number of attr() exprs in pref-spec i
+ /// [i] = end offset of pref-spec i's attrs in the flat
+ /// attr block (inclusive cumulative attr count);
+ /// spec i's attrs are [ends[i-1], ends[i]), with
+ /// an implicit 0 before ends[0]
///
/// varlist_size() = 1 + NumPrefs, so OMPVarListClause iteration covers
/// InteropVar + the Fr block.
@@ -8976,20 +8979,26 @@ class OMPInitClause final
/// 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 {
+ /// All attr() exprs across every pref-spec, in pref-spec order (flat block).
+ ArrayRef<Expr *> getAttrs() const {
+ return ArrayRef<Expr *>(getTrailingObjects<Expr *>() + varlist_size(),
+ NumAttrs);
+ }
+
+ /// Per-pref-spec attr end offsets: entry i is the inclusive cumulative attr
+ /// count through pref-spec i (one past its last attr in the flat attr block).
+ ArrayRef<unsigned> getAttrEnds() 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])};
+ ArrayRef<unsigned> AttrEnds = getAttrEnds();
+ unsigned Start = (I == 0) ? 0 : AttrEnds[I - 1];
+ unsigned Count = AttrEnds[I] - Start;
+ return PrefView{E[1 + I],
+ ArrayRef<Expr *>(E + varlist_size() + Start, Count)};
}
PrefView getPref(unsigned I) const {
return const_cast<OMPInitClause *>(this)->getPref(I);
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index b5be0910194bd..0da9b4f0a8350 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -3797,6 +3797,10 @@ bool RecursiveASTVisitor<Derived>::VisitOMPNogroupClause(OMPNogroupClause *) {
template <typename Derived>
bool RecursiveASTVisitor<Derived>::VisitOMPInitClause(OMPInitClause *C) {
TRY_TO(VisitOMPClauseList(C));
+ // VisitOMPClauseList covers the interop var and the per-pref-spec fr exprs
+ // (the varlist); the prefer_type attr() exprs live outside it.
+ for (Expr *A : C->getAttrs())
+ TRY_TO(TraverseStmt(A));
return true;
}
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 6abda0671add2..0dc7d0fbeac0b 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1693,6 +1693,12 @@ def warn_omp_more_one_interop_type
InGroup<OpenMPClauses>;
def err_omp_interop_multiple_fr : Error<
"only one 'fr' selector allowed per preference-specification">;
+def err_omp_prefer_type_brace_60 : Error<
+ "brace-grouped 'prefer_type' argument requires OpenMP version 6.0 or later">;
+def err_omp_expected_fr_or_attr_selector : Error<
+ "expected 'fr' or 'attr' selector in 'prefer_type'">;
+def err_omp_expected_pref_spec : Error<
+ "expected preference-specification in 'prefer_type'">;
def err_expected_sequence_or_directive : Error<
"expected an OpenMP 'directive' or 'sequence' attribute argument">;
def ext_omp_attributes : ExtWarn<
diff --git a/clang/lib/AST/OpenMPClause.cpp b/clang/lib/AST/OpenMPClause.cpp
index 9fce666ce00d8..0ba0e78ff1333 100644
--- a/clang/lib/AST/OpenMPClause.cpp
+++ b/clang/lib/AST/OpenMPClause.cpp
@@ -1823,12 +1823,13 @@ OMPInitClause *OMPInitClause::Create(const ASTContext &C, Expr *InteropVar,
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;
+ unsigned *AttrEnds = Clause->getTrailingObjects<unsigned>();
+ unsigned AttrBase = 1 + NumPrefs;
+ unsigned AttrPos = AttrBase;
for (unsigned I = 0; I < NumPrefs; ++I) {
- Counts[I] = InteropInfo.Prefs[I].Attrs.size();
for (Expr *A : InteropInfo.Prefs[I].Attrs)
E[AttrPos++] = A;
+ AttrEnds[I] = AttrPos - AttrBase;
}
return Clause;
}
@@ -1849,7 +1850,13 @@ void OMPInitClause::setAttrs(ArrayRef<unsigned> Counts,
"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>());
+ // Store inclusive cumulative counts (end offsets)
+ unsigned *AttrEnds = getTrailingObjects<unsigned>();
+ unsigned Run = 0;
+ for (unsigned I = 0, E = Counts.size(); I < E; ++I) {
+ Run += Counts[I];
+ AttrEnds[I] = Run;
+ }
llvm::copy(Attrs, getTrailingObjects<Expr *>() + varlist_size());
}
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index eb25e5260fd1a..50b1fc572bbd2 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -40,6 +40,9 @@ namespace {
void VisitStmt(const Stmt *S);
+ /// Fold a scalar value into the profile
+ void VisitInteger(uint64_t Value) { ID.AddInteger(Value); }
+
void VisitStmtNoChildren(const Stmt *S) {
HandleStmtClass(S->getStmtClass());
}
@@ -658,7 +661,19 @@ void OMPClauseProfiler::VisitOMPSIMDClause(const OMPSIMDClause *) {}
void OMPClauseProfiler::VisitOMPNogroupClause(const OMPNogroupClause *) {}
void OMPClauseProfiler::VisitOMPInitClause(const OMPInitClause *C) {
- VisitOMPClauseList(C);
+ // Enumerate per pref-spec so the {fr, attr} grouping is part of the profile.
+ Profiler->VisitStmt(C->getInteropVar());
+ unsigned NumPrefs = C->getNumPrefs();
+ Profiler->VisitInteger(NumPrefs);
+ for (unsigned I = 0; I < NumPrefs; ++I) {
+ OMPInitClause::PrefView P = C->getPref(I);
+ Profiler->VisitInteger(P.Fr ? 1 : 0); // Fr may be null for an attr-only spec
+ if (P.Fr)
+ Profiler->VisitStmt(P.Fr);
+ Profiler->VisitInteger(P.Attrs.size()); // pref-spec boundary
+ for (const Expr *A : P.Attrs)
+ Profiler->VisitStmt(A);
+ }
}
void OMPClauseProfiler::VisitOMPUseClause(const OMPUseClause *C) {
diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp
index de844460c0a15..93113cbde419a 100644
--- a/clang/lib/Parse/ParseOpenMP.cpp
+++ b/clang/lib/Parse/ParseOpenMP.cpp
@@ -3710,6 +3710,12 @@ bool Parser::ParseOMPInteropAttrSelector(SmallVectorImpl<Expr *> &Attrs) {
return true;
}
bool HasError = false;
+ // attr() requires at least one ext-string-literal argument; an empty list is
+ // not permitted by the prefer_type grammar.
+ if (Tok.is(tok::r_paren)) {
+ Diag(Tok, diag::err_expected) << tok::string_literal;
+ HasError = true;
+ }
while (Tok.isNot(tok::r_paren) && Tok.isNot(tok::r_brace) &&
Tok.isNot(tok::annot_pragma_openmp_end)) {
if (Tok.is(tok::string_literal)) {
@@ -3762,9 +3768,22 @@ bool Parser::ParseOMPInteropInfo(OMPInteropInfo &InteropInfo,
if (PT.expectAndConsume(diag::err_expected_lparen_after, "prefer_type"))
HasError = true;
- while (Tok.isNot(tok::r_paren)) {
+ // prefer_type requires at least one preference-specification.
+ if (Tok.is(tok::r_paren)) {
+ Diag(Tok, diag::err_omp_expected_pref_spec);
+ HasError = true;
+ }
+
+ while (Tok.isNot(tok::r_paren) &&
+ Tok.isNot(tok::annot_pragma_openmp_end)) {
// OMP 6.0: { fr(...), attr(...) } brace-grouped pref-spec
if (Tok.is(tok::l_brace)) {
+ // The brace-grouped form was introduced in OpenMP 6.0; earlier
+ // versions only allow the flat foreign-runtime-id list.
+ if (getLangOpts().OpenMP < 60) {
+ Diag(Tok, diag::err_omp_prefer_type_brace_60);
+ HasError = true;
+ }
BalancedDelimiterTracker BT(*this, tok::l_brace,
tok::annot_pragma_openmp_end);
BT.consumeOpen();
@@ -3772,13 +3791,17 @@ bool Parser::ParseOMPInteropInfo(OMPInteropInfo &InteropInfo,
SmallVector<Expr *, 2> AttrExprs;
bool SeenFr = false;
+ // A pref-spec requires at least one 'fr'/'attr' selector; {} is not
+ // permitted by the grammar.
+ if (Tok.is(tok::r_brace)) {
+ Diag(Tok, diag::err_omp_expected_fr_or_attr_selector);
+ HasError = true;
+ }
+
while (Tok.isNot(tok::r_brace) &&
Tok.isNot(tok::annot_pragma_openmp_end)) {
- if (!Tok.is(tok::identifier)) {
- HasError = true;
- Diag(Tok, diag::err_omp_expected_interop_type);
- ConsumeToken();
- } else if (Tok.getIdentifierInfo()->isStr("fr")) {
+ if (Tok.is(tok::identifier) &&
+ Tok.getIdentifierInfo()->isStr("fr")) {
if (SeenFr) {
Diag(Tok, diag::err_omp_interop_multiple_fr);
HasError = true;
@@ -3794,12 +3817,14 @@ bool Parser::ParseOMPInteropInfo(OMPInteropInfo &InteropInfo,
FrExpr = Fr.get();
else
HasError = true;
- } else if (Tok.getIdentifierInfo()->isStr("attr")) {
+ } else if (Tok.is(tok::identifier) &&
+ Tok.getIdentifierInfo()->isStr("attr")) {
if (ParseOMPInteropAttrSelector(AttrExprs))
HasError = true;
} else {
+ // Neither 'fr' nor 'attr' (a non-identifier or some other word).
HasError = true;
- Diag(Tok, diag::err_omp_expected_interop_type);
+ Diag(Tok, diag::err_omp_expected_fr_or_attr_selector);
ConsumeToken();
}
if (Tok.is(tok::comma))
diff --git a/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp b/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
index 2c9d7bbc1a5f1..642a34a1413a2 100644
--- a/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
+++ b/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
@@ -27,6 +27,11 @@ static void foo() {
// 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)
+ // Empty attr() — at least one ext-string-literal is required.
+ // 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()}), 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)
@@ -36,7 +41,22 @@ static void foo() {
#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 +2 {{expected 'fr' or 'attr' selector in 'prefer_type'}}
// 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)
+
+ // A non-identifier where a selector is expected is rejected too.
+ // expected-error at +2 {{expected 'fr' or 'attr' selector in 'prefer_type'}}
+ // expected-error at +1 {{expected at least one 'init', 'use', 'destroy', or 'nowait' clause for '#pragma omp interop'}}
+ #pragma omp interop init(prefer_type({42}), targetsync: obj)
+
+ // An empty pref-spec '{}' requires at least one 'fr'/'attr' selector.
+ // expected-error at +2 {{expected 'fr' or 'attr' selector in 'prefer_type'}}
+ // expected-error at +1 {{expected at least one 'init', 'use', 'destroy', or 'nowait' clause for '#pragma omp interop'}}
+ #pragma omp interop init(prefer_type({}), targetsync: obj)
+
+ // An empty prefer_type() requires at least one preference-specification.
+ // expected-error at +2 {{expected preference-specification in 'prefer_type'}}
+ // expected-error at +1 {{expected at least one 'init', 'use', 'destroy', or 'nowait' clause for '#pragma omp interop'}}
+ #pragma omp interop init(prefer_type(), targetsync: obj)
}
>From 96fbe4470463fa40ba0920d75e2be1e0af7eac90 Mon Sep 17 00:00:00 2001
From: "Khatavkar, Yashasvi" <yashasvi.khatavkar at intel.com>
Date: Mon, 1 Jun 2026 08:36:12 -0700
Subject: [PATCH 4/8] Fix formatting
---
clang/lib/AST/OpenMPClause.cpp | 4 ++--
clang/lib/AST/StmtProfile.cpp | 3 ++-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/clang/lib/AST/OpenMPClause.cpp b/clang/lib/AST/OpenMPClause.cpp
index 0ba0e78ff1333..9b7a8ec566197 100644
--- a/clang/lib/AST/OpenMPClause.cpp
+++ b/clang/lib/AST/OpenMPClause.cpp
@@ -1824,12 +1824,12 @@ OMPInitClause *OMPInitClause::Create(const ASTContext &C, Expr *InteropVar,
for (unsigned I = 0; I < NumPrefs; ++I)
E[1 + I] = InteropInfo.Prefs[I].Fr;
unsigned *AttrEnds = Clause->getTrailingObjects<unsigned>();
- unsigned AttrBase = 1 + NumPrefs;
+ unsigned AttrBase = 1 + NumPrefs;
unsigned AttrPos = AttrBase;
for (unsigned I = 0; I < NumPrefs; ++I) {
for (Expr *A : InteropInfo.Prefs[I].Attrs)
E[AttrPos++] = A;
- AttrEnds[I] = AttrPos - AttrBase;
+ AttrEnds[I] = AttrPos - AttrBase;
}
return Clause;
}
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 50b1fc572bbd2..3965d939e0d46 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -667,7 +667,8 @@ void OMPClauseProfiler::VisitOMPInitClause(const OMPInitClause *C) {
Profiler->VisitInteger(NumPrefs);
for (unsigned I = 0; I < NumPrefs; ++I) {
OMPInitClause::PrefView P = C->getPref(I);
- Profiler->VisitInteger(P.Fr ? 1 : 0); // Fr may be null for an attr-only spec
+ Profiler->VisitInteger(P.Fr ? 1
+ : 0); // Fr may be null for an attr-only spec
if (P.Fr)
Profiler->VisitStmt(P.Fr);
Profiler->VisitInteger(P.Attrs.size()); // pref-spec boundary
>From 2e8b220dbdcd8579da1729d19b150af7e5cab5e7 Mon Sep 17 00:00:00 2001
From: "Khatavkar, Yashasvi" <yashasvi.khatavkar at intel.com>
Date: Thu, 4 Jun 2026 08:49:47 -0700
Subject: [PATCH 5/8] Modify the test case to include a version check
---
.../interop_prefer_type_brace_messages.cpp | 25 ++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp b/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
index 642a34a1413a2..7c8b685be9634 100644
--- a/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
+++ b/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
@@ -1,9 +1,11 @@
-// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=60 -std=c++11 -o - %s
+// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=60 -std=c++11 -o - %s
+// RUN: %clang_cc1 -verify=pre-omp60 -fopenmp -fopenmp-version=51 -std=c++11 -DPRE_OMP60 -o - %s
// Diagnostics for the OMP 6.0 brace-grouped prefer_type syntax.
typedef void *omp_interop_t;
+#ifndef PRE_OMP60
static void foo() {
omp_interop_t obj;
@@ -60,3 +62,24 @@ static void foo() {
// expected-error at +1 {{expected at least one 'init', 'use', 'destroy', or 'nowait' clause for '#pragma omp interop'}}
#pragma omp interop init(prefer_type(), targetsync: obj)
}
+#endif // PRE_OMP60
+
+// Brace-grouped syntax is rejected before OpenMP 6.0.
+#ifdef PRE_OMP60
+static void foo() {
+ omp_interop_t obj;
+
+ // Flat string list — valid before OMP 6.0, no error expected.
+ #pragma omp interop init(prefer_type("sycl", "level_zero", "opencl"), targetsync: obj)
+
+ // fr() with a string literal.
+ // pre-omp60-error at +2 {{brace-grouped 'prefer_type' argument requires OpenMP version 6.0 or later}}
+ // pre-omp60-error at +1 {{expected at least one 'init', 'use', 'destroy', or 'nowait' clause for '#pragma omp interop'}}
+ #pragma omp interop init(prefer_type({fr("cuda")}), targetsync: obj)
+
+ // attr() with a single string literal.
+ // pre-omp60-error at +2 {{brace-grouped 'prefer_type' argument requires OpenMP version 6.0 or later}}
+ // pre-omp60-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)
+}
+#endif // PRE_OMP60
>From 28f5a0261ee19b7cdc5264a0397cf361cd6b93f8 Mon Sep 17 00:00:00 2001
From: "Khatavkar, Yashasvi" <yashasvi.khatavkar at intel.com>
Date: Fri, 5 Jun 2026 10:17:43 -0700
Subject: [PATCH 6/8] Address review (nit) comment
---
.../interop_prefer_type_brace_messages.cpp | 40 +++++++++----------
1 file changed, 19 insertions(+), 21 deletions(-)
diff --git a/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp b/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
index 7c8b685be9634..9b183ca564dd4 100644
--- a/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
+++ b/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
@@ -5,7 +5,25 @@
typedef void *omp_interop_t;
-#ifndef PRE_OMP60
+#ifdef PRE_OMP60
+// Brace-grouped syntax is rejected before OpenMP 6.0.
+static void foo() {
+ omp_interop_t obj;
+
+ // Flat string list — valid before OMP 6.0, no error expected.
+ #pragma omp interop init(prefer_type("sycl", "level_zero", "opencl"), targetsync: obj)
+
+ // fr() with a string literal.
+ // pre-omp60-error at +2 {{brace-grouped 'prefer_type' argument requires OpenMP version 6.0 or later}}
+ // pre-omp60-error at +1 {{expected at least one 'init', 'use', 'destroy', or 'nowait' clause for '#pragma omp interop'}}
+ #pragma omp interop init(prefer_type({fr("cuda")}), targetsync: obj)
+
+ // attr() with a single string literal.
+ // pre-omp60-error at +2 {{brace-grouped 'prefer_type' argument requires OpenMP version 6.0 or later}}
+ // pre-omp60-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)
+}
+#else
static void foo() {
omp_interop_t obj;
@@ -63,23 +81,3 @@ static void foo() {
#pragma omp interop init(prefer_type(), targetsync: obj)
}
#endif // PRE_OMP60
-
-// Brace-grouped syntax is rejected before OpenMP 6.0.
-#ifdef PRE_OMP60
-static void foo() {
- omp_interop_t obj;
-
- // Flat string list — valid before OMP 6.0, no error expected.
- #pragma omp interop init(prefer_type("sycl", "level_zero", "opencl"), targetsync: obj)
-
- // fr() with a string literal.
- // pre-omp60-error at +2 {{brace-grouped 'prefer_type' argument requires OpenMP version 6.0 or later}}
- // pre-omp60-error at +1 {{expected at least one 'init', 'use', 'destroy', or 'nowait' clause for '#pragma omp interop'}}
- #pragma omp interop init(prefer_type({fr("cuda")}), targetsync: obj)
-
- // attr() with a single string literal.
- // pre-omp60-error at +2 {{brace-grouped 'prefer_type' argument requires OpenMP version 6.0 or later}}
- // pre-omp60-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)
-}
-#endif // PRE_OMP60
>From 0f81949a63accd53047132493b741be81c06ec93 Mon Sep 17 00:00:00 2001
From: "Khatavkar, Yashasvi" <yashasvi.khatavkar at intel.com>
Date: Mon, 8 Jun 2026 07:28:38 -0700
Subject: [PATCH 7/8] Update CIndex.cpp and add sema checks for attr()
---
.../clang/Basic/DiagnosticSemaKinds.td | 6 +++
clang/lib/Sema/SemaOpenMP.cpp | 41 ++++++++++++++-----
.../interop_prefer_type_brace_messages.cpp | 13 ++++++
clang/tools/libclang/CIndex.cpp | 2 +
4 files changed, 51 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9cd3a5b2fc049..90bad0e21e59b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12713,6 +12713,12 @@ def err_omp_interop_variable_wrong_type : Error<
def err_omp_interop_prefer_type : Error<
"prefer_list item must be a string literal or constant integral "
"expression">;
+def err_omp_interop_attr_not_string : Error<
+ "attr() argument must be a string literal">;
+def err_omp_interop_attr_missing_ompx_prefix : Error<
+ "attr() argument '%0' must start with the 'ompx_' prefix">;
+def err_omp_interop_attr_contains_comma : Error<
+ "attr() argument '%0' must not contain a comma">;
def err_omp_interop_bad_depend_clause : Error<
"'depend' clause requires the 'targetsync' interop type">;
def err_omp_interop_var_multiple_actions : Error<
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index ff3afd932bc27..b30b3e1d206dd 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -18968,22 +18968,41 @@ OMPClause *SemaOpenMP::ActOnOpenMPInitClause(
return nullptr;
// Check prefer_type values. fr() arguments are either string literals or
- // constant integral expressions; null Fr is only valid in OMP 6.0
+ // constant integral expressions; null Fr is only valid in OMP 6.0.
+ // attr() arguments must be ext-string-literals with the 'ompx_' prefix
+ // (OpenMP 6.0 spec, section 16.1.3).
for (const OMPInteropPref &P : InteropInfo.Prefs) {
const Expr *E = P.Fr;
if (!E) {
assert(InteropInfo.HasPreferAttrs && "null Fr requires OMP 6.0 syntax");
- continue;
+ } else if (!E->isValueDependent() && !E->isTypeDependent() &&
+ !E->isInstantiationDependent() &&
+ !E->containsUnexpandedParameterPack()) {
+ if (!E->isIntegerConstantExpr(getASTContext()) && !isa<StringLiteral>(E)) {
+ Diag(E->getExprLoc(), diag::err_omp_interop_prefer_type);
+ return nullptr;
+ }
+ }
+ for (const Expr *A : P.Attrs) {
+ if (A->isValueDependent() || A->isTypeDependent() ||
+ A->isInstantiationDependent() || A->containsUnexpandedParameterPack())
+ continue;
+ const auto *SL = dyn_cast<StringLiteral>(A);
+ if (!SL) {
+ Diag(A->getExprLoc(), diag::err_omp_interop_attr_not_string);
+ return nullptr;
+ }
+ if (!SL->getString().starts_with("ompx_")) {
+ Diag(A->getExprLoc(), diag::err_omp_interop_attr_missing_ompx_prefix)
+ << SL->getString();
+ return nullptr;
+ }
+ if (SL->getString().contains(',')) {
+ Diag(A->getExprLoc(), diag::err_omp_interop_attr_contains_comma)
+ << SL->getString();
+ return nullptr;
+ }
}
- if (E->isValueDependent() || E->isTypeDependent() ||
- E->isInstantiationDependent() || E->containsUnexpandedParameterPack())
- continue;
- if (E->isIntegerConstantExpr(getASTContext()))
- continue;
- if (isa<StringLiteral>(E))
- continue;
- Diag(E->getExprLoc(), diag::err_omp_interop_prefer_type);
- return nullptr;
}
return OMPInitClause::Create(getASTContext(), InteropVar, InteropInfo,
diff --git a/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp b/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
index 9b183ca564dd4..ec2d8feaba167 100644
--- a/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
+++ b/clang/test/OpenMP/interop_prefer_type_brace_messages.cpp
@@ -52,6 +52,19 @@ static void foo() {
// 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()}), targetsync: obj)
+ // attr() argument without the required 'ompx_' prefix.
+ // expected-error at +2 {{attr() argument 'cuda_prop' must start with the 'ompx_' prefix}}
+ // 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("cuda_prop")}), targetsync: obj)
+
+ // Valid attr() with 'ompx_' prefix — no error expected.
+ #pragma omp interop init(prefer_type({attr("ompx_propX")}), targetsync: obj)
+
+ // attr() argument with a comma — not permitted by the grammar.
+ // expected-error at +2 {{attr() argument 'ompx_a,b' must not contain a comma}}
+ // 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_a,b")}), 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)
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 350cd2135657d..eb541ef36c0ae 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -2467,6 +2467,8 @@ void OMPClauseEnqueue::VisitOMPNogroupClause(const OMPNogroupClause *) {}
void OMPClauseEnqueue::VisitOMPInitClause(const OMPInitClause *C) {
VisitOMPClauseList(C);
+ for (const Expr *A : C->getAttrs())
+ Visitor->AddStmt(A);
}
void OMPClauseEnqueue::VisitOMPUseClause(const OMPUseClause *C) {
>From 81397ea05ac2fab55a163092aa9503032f867734 Mon Sep 17 00:00:00 2001
From: "Khatavkar, Yashasvi" <yashasvi.khatavkar at intel.com>
Date: Mon, 8 Jun 2026 07:32:55 -0700
Subject: [PATCH 8/8] Fix formatting
---
clang/lib/Sema/SemaOpenMP.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index b30b3e1d206dd..05cd06f02ac27 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -18978,7 +18978,8 @@ OMPClause *SemaOpenMP::ActOnOpenMPInitClause(
} else if (!E->isValueDependent() && !E->isTypeDependent() &&
!E->isInstantiationDependent() &&
!E->containsUnexpandedParameterPack()) {
- if (!E->isIntegerConstantExpr(getASTContext()) && !isa<StringLiteral>(E)) {
+ if (!E->isIntegerConstantExpr(getASTContext()) &&
+ !isa<StringLiteral>(E)) {
Diag(E->getExprLoc(), diag::err_omp_interop_prefer_type);
return nullptr;
}
More information about the cfe-commits
mailing list