From e5443232b9626b9be260042e47c4fb1cc071ca64 Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Mon, 31 Aug 2026 11:22:22 -0700 Subject: [PATCH 1/7] [SYCL] Avoid redundant deep device type checks Skip types that cannot contain zero-length arrays, use an inline visited set, and cache successfully validated complete record definitions. This reduces temporary allocations from repeated SYCL type validation while preserving diagnostics for invalid and incomplete records. Assisted-by: GPT-5.6 Terra --- clang/include/clang/Sema/SemaSYCL.h | 6 ++--- clang/lib/Sema/Sema.cpp | 6 ++--- clang/lib/Sema/SemaSYCL.cpp | 41 +++++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/clang/include/clang/Sema/SemaSYCL.h b/clang/include/clang/Sema/SemaSYCL.h index c05886f988a93..e7763605dcc37 100644 --- a/clang/include/clang/Sema/SemaSYCL.h +++ b/clang/include/clang/Sema/SemaSYCL.h @@ -289,6 +289,8 @@ class SemaSYCL : public SemaBase { // special types inside. Relevant for free function kernels only. llvm::DenseSet StructsWithSpecialTypes; + llvm::DenseSet DeepTypeCheckedRecords; + public: SemaSYCL(Sema &S); @@ -319,9 +321,7 @@ class SemaSYCL : public SemaBase { DeviceDiagnosticReason Reason = DeviceDiagnosticReason::Sycl | DeviceDiagnosticReason::Esimd); - void deepTypeCheckForDevice(SourceLocation UsedAt, - llvm::DenseSet Visited, - ValueDecl *DeclToCheck); + void deepTypeCheckForDevice(SourceLocation UsedAt, ValueDecl *DeclToCheck); const KernelFDPairs &getKernelFDPairs() { return SyclKernelsToOpenCLKernels; } diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 095a559104687..e1e9d6eaae388 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -2438,10 +2438,8 @@ void Sema::checkTypeSupport(QualType Ty, SourceLocation Loc, ValueDecl *D) { // declarations can be replaced with an array of bytes of the same size during // codegen, such replacement doesn't seem to be possible for types without // constant byte size like zero length arrays. So, do a deep check for SYCL. - if (D && LangOpts.SYCLIsDevice) { - llvm::DenseSet Visited; - SYCL().deepTypeCheckForDevice(Loc, Visited, D); - } + if (D && LangOpts.SYCLIsDevice) + SYCL().deepTypeCheckForDevice(Loc, D); Decl *C = cast(getCurLexicalContext()); diff --git a/clang/lib/Sema/SemaSYCL.cpp b/clang/lib/Sema/SemaSYCL.cpp index 5d15f2a63d6b8..848a2ad959d9c 100644 --- a/clang/lib/Sema/SemaSYCL.cpp +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -425,6 +425,22 @@ static bool isZeroSizedArray(SemaSYCL &S, QualType Ty) { return false; } +static bool needsDeepTypeCheck(SemaSYCL &S, QualType Ty, + const RecordDecl *&RootRecord) { + RootRecord = nullptr; + while (Ty->isAnyPointerType() || Ty->isArrayType() || + Ty->isReferenceType()) { + if (isZeroSizedArray(S, Ty)) + return true; + if (Ty->isArrayType()) + Ty = QualType{Ty->getArrayElementTypeNoTypeQual(), 0}; + else + Ty = Ty->getPointeeType(); + } + RootRecord = Ty->getAsRecordDecl(); + return RootRecord != nullptr; +} + static void checkSYCLType(SemaSYCL &S, QualType Ty, SourceRange Loc, llvm::DenseSet Visited, SourceRange UsedAtLoc = SourceRange()) { @@ -5986,13 +6002,24 @@ SemaSYCL::DiagIfDeviceCode(SourceLocation Loc, unsigned DiagID, } void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, - llvm::DenseSet Visited, ValueDecl *DeclToCheck) { assert(getLangOpts().SYCLIsDevice && "Should only be called during SYCL compilation"); + const RecordDecl *RootRecord = nullptr; + if (!needsDeepTypeCheck(*this, DeclToCheck->getType(), RootRecord)) + return; + if (RootRecord && RootRecord->isCompleteDefinition() && + DeepTypeCheckedRecords.contains( + cast(RootRecord->getCanonicalDecl()))) + return; + // Emit notes only for the first discovered declaration of unsupported type // to avoid mess of notes. This flag is to track that error already happened. bool NeedToEmitNotes = true; + bool FoundError = false; + bool CanCacheResult = + RootRecord && RootRecord->isCompleteDefinition(); + llvm::SmallDenseSet Visited; auto Check = [&](QualType TypeToCheck, const ValueDecl *D) { bool ErrorFound = false; @@ -6035,6 +6062,8 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, if (!Visited.insert(NextTy).second) continue; + if (NextTy->isDependentType()) + CanCacheResult = false; auto EmitHistory = [&]() { // The first element is always nullptr. @@ -6049,6 +6078,7 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, if (NeedToEmitNotes) EmitHistory(); NeedToEmitNotes = false; + FoundError = true; } // In case pointer/array/reference type is met get pointee type, then @@ -6063,10 +6093,13 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, if (NeedToEmitNotes) EmitHistory(); NeedToEmitNotes = false; + FoundError = true; } } if (const auto *RecDecl = NextTy->getAsRecordDecl()) { + if (!RecDecl->isCompleteDefinition()) + CanCacheResult = false; if (auto *NextFD = dyn_cast(Next)) History.push_back(NextFD); // When nullptr is discovered, this means we've gone back up a level, so @@ -6075,6 +6108,10 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, llvm::append_range(StackForRecursion, RecDecl->fields()); } } while (!StackForRecursion.empty()); + + if (CanCacheResult && !FoundError) + DeepTypeCheckedRecords.insert( + cast(RootRecord->getCanonicalDecl())); } void SemaSYCL::finalizeSYCLDelayedAnalysis(const FunctionDecl *Caller, @@ -8041,7 +8078,7 @@ bool SYCLIntegrationFooter::emit(raw_ostream &OS) { for (const VarDecl *VD : GlobalVars) { VD = VD->getCanonicalDecl(); - // Skip if this isn't a SpecIdType, DeviceGlobal, or HostPipe. This + // Skip if this isn't a SpecIdType, DeviceGlobal, or HostPipe. This // can happen if it was a deduced type. if (!SemaSYCL::isSyclType(VD->getType(), SYCLTypeAttr::specialization_id) && !SemaSYCL::isSyclType(VD->getType(), SYCLTypeAttr::host_pipe) && From 0c7cbfe7a21bae013c29be9d45f76f741dc6682b Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Mon, 31 Aug 2026 13:38:56 -0700 Subject: [PATCH 2/7] Fix formatting --- clang/lib/Sema/SemaSYCL.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/clang/lib/Sema/SemaSYCL.cpp b/clang/lib/Sema/SemaSYCL.cpp index 848a2ad959d9c..17d1db0c2c443 100644 --- a/clang/lib/Sema/SemaSYCL.cpp +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -428,8 +428,7 @@ static bool isZeroSizedArray(SemaSYCL &S, QualType Ty) { static bool needsDeepTypeCheck(SemaSYCL &S, QualType Ty, const RecordDecl *&RootRecord) { RootRecord = nullptr; - while (Ty->isAnyPointerType() || Ty->isArrayType() || - Ty->isReferenceType()) { + while (Ty->isAnyPointerType() || Ty->isArrayType() || Ty->isReferenceType()) { if (isZeroSizedArray(S, Ty)) return true; if (Ty->isArrayType()) @@ -6017,8 +6016,7 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, // to avoid mess of notes. This flag is to track that error already happened. bool NeedToEmitNotes = true; bool FoundError = false; - bool CanCacheResult = - RootRecord && RootRecord->isCompleteDefinition(); + bool CanCacheResult = RootRecord && RootRecord->isCompleteDefinition(); llvm::SmallDenseSet Visited; auto Check = [&](QualType TypeToCheck, const ValueDecl *D) { @@ -6110,8 +6108,8 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, } while (!StackForRecursion.empty()); if (CanCacheResult && !FoundError) - DeepTypeCheckedRecords.insert( - cast(RootRecord->getCanonicalDecl())); + DeepTypeCheckedRecords.insert( + cast(RootRecord->getCanonicalDecl())); } void SemaSYCL::finalizeSYCLDelayedAnalysis(const FunctionDecl *Caller, From ee14b6344d4c86f1ea5f157e408373216b635a97 Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Mon, 31 Aug 2026 14:50:52 -0700 Subject: [PATCH 3/7] Return a pair from the helper function instead of an "output" parameter --- clang/lib/Sema/SemaSYCL.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/clang/lib/Sema/SemaSYCL.cpp b/clang/lib/Sema/SemaSYCL.cpp index 17d1db0c2c443..7c77d155f9c61 100644 --- a/clang/lib/Sema/SemaSYCL.cpp +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -425,19 +425,19 @@ static bool isZeroSizedArray(SemaSYCL &S, QualType Ty) { return false; } -static bool needsDeepTypeCheck(SemaSYCL &S, QualType Ty, - const RecordDecl *&RootRecord) { - RootRecord = nullptr; +static std::pair needsDeepTypeCheck(SemaSYCL &S, + QualType Ty) { + const RecordDecl *RootRecord = nullptr; while (Ty->isAnyPointerType() || Ty->isArrayType() || Ty->isReferenceType()) { if (isZeroSizedArray(S, Ty)) - return true; + return {nullptr, true}; if (Ty->isArrayType()) Ty = QualType{Ty->getArrayElementTypeNoTypeQual(), 0}; else Ty = Ty->getPointeeType(); } RootRecord = Ty->getAsRecordDecl(); - return RootRecord != nullptr; + return {RootRecord, RootRecord != nullptr}; } static void checkSYCLType(SemaSYCL &S, QualType Ty, SourceRange Loc, @@ -6004,8 +6004,9 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, ValueDecl *DeclToCheck) { assert(getLangOpts().SYCLIsDevice && "Should only be called during SYCL compilation"); - const RecordDecl *RootRecord = nullptr; - if (!needsDeepTypeCheck(*this, DeclToCheck->getType(), RootRecord)) + const auto [RootRecord, NeedsCheck] = + needsDeepTypeCheck(*this, DeclToCheck->getType()); + if (!NeedsCheck) return; if (RootRecord && RootRecord->isCompleteDefinition() && DeepTypeCheckedRecords.contains( From ee2d7fe2971ff63c0340e7301794de4fdb0c6d32 Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Wed, 2 Sep 2026 10:12:04 -0700 Subject: [PATCH 4/7] Use CanonicalDeclPtr to avoid the possibility of storing pointers to non-canonical declarations --- clang/include/clang/Sema/SemaSYCL.h | 2 +- clang/lib/Sema/SemaSYCL.cpp | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/clang/include/clang/Sema/SemaSYCL.h b/clang/include/clang/Sema/SemaSYCL.h index e7763605dcc37..0c45945e4edef 100644 --- a/clang/include/clang/Sema/SemaSYCL.h +++ b/clang/include/clang/Sema/SemaSYCL.h @@ -289,7 +289,7 @@ class SemaSYCL : public SemaBase { // special types inside. Relevant for free function kernels only. llvm::DenseSet StructsWithSpecialTypes; - llvm::DenseSet DeepTypeCheckedRecords; + llvm::DenseSet> DeepTypeCheckedRecords; public: SemaSYCL(Sema &S); diff --git a/clang/lib/Sema/SemaSYCL.cpp b/clang/lib/Sema/SemaSYCL.cpp index 7c77d155f9c61..92e5046037435 100644 --- a/clang/lib/Sema/SemaSYCL.cpp +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -6009,8 +6009,7 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, if (!NeedsCheck) return; if (RootRecord && RootRecord->isCompleteDefinition() && - DeepTypeCheckedRecords.contains( - cast(RootRecord->getCanonicalDecl()))) + DeepTypeCheckedRecords.contains(RootRecord)) return; // Emit notes only for the first discovered declaration of unsupported type @@ -6109,8 +6108,7 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, } while (!StackForRecursion.empty()); if (CanCacheResult && !FoundError) - DeepTypeCheckedRecords.insert( - cast(RootRecord->getCanonicalDecl())); + DeepTypeCheckedRecords.insert(RootRecord); } void SemaSYCL::finalizeSYCLDelayedAnalysis(const FunctionDecl *Caller, From bd3ffc4c384dc33a4fcf32b4231ee88eba9939bd Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Wed, 2 Sep 2026 10:15:19 -0700 Subject: [PATCH 5/7] Differentiate zero-size arrays from other null pointer to declaration cases --- clang/lib/Sema/SemaSYCL.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/clang/lib/Sema/SemaSYCL.cpp b/clang/lib/Sema/SemaSYCL.cpp index 92e5046037435..d33c4c7edf899 100644 --- a/clang/lib/Sema/SemaSYCL.cpp +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -427,7 +427,6 @@ static bool isZeroSizedArray(SemaSYCL &S, QualType Ty) { static std::pair needsDeepTypeCheck(SemaSYCL &S, QualType Ty) { - const RecordDecl *RootRecord = nullptr; while (Ty->isAnyPointerType() || Ty->isArrayType() || Ty->isReferenceType()) { if (isZeroSizedArray(S, Ty)) return {nullptr, true}; @@ -436,8 +435,7 @@ static std::pair needsDeepTypeCheck(SemaSYCL &S, else Ty = Ty->getPointeeType(); } - RootRecord = Ty->getAsRecordDecl(); - return {RootRecord, RootRecord != nullptr}; + return {Ty->getAsRecordDecl(), false}; } static void checkSYCLType(SemaSYCL &S, QualType Ty, SourceRange Loc, @@ -6004,9 +6002,9 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, ValueDecl *DeclToCheck) { assert(getLangOpts().SYCLIsDevice && "Should only be called during SYCL compilation"); - const auto [RootRecord, NeedsCheck] = + const auto [RootRecord, HasZeroSizedArray] = needsDeepTypeCheck(*this, DeclToCheck->getType()); - if (!NeedsCheck) + if (!RootRecord && !HasZeroSizedArray) return; if (RootRecord && RootRecord->isCompleteDefinition() && DeepTypeCheckedRecords.contains(RootRecord)) From e61120882e81f338591e055a7c984d0d820f30b8 Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Wed, 2 Sep 2026 14:17:38 -0700 Subject: [PATCH 6/7] Cache already validated nested records --- clang/lib/Sema/SemaSYCL.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaSYCL.cpp b/clang/lib/Sema/SemaSYCL.cpp index d33c4c7edf899..7a59037ee6580 100644 --- a/clang/lib/Sema/SemaSYCL.cpp +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -6016,6 +6016,7 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, bool FoundError = false; bool CanCacheResult = RootRecord && RootRecord->isCompleteDefinition(); llvm::SmallDenseSet Visited; + llvm::SmallDenseSet, 8> VisitedRecords; auto Check = [&](QualType TypeToCheck, const ValueDecl *D) { bool ErrorFound = false; @@ -6096,6 +6097,10 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, if (const auto *RecDecl = NextTy->getAsRecordDecl()) { if (!RecDecl->isCompleteDefinition()) CanCacheResult = false; + else if (DeepTypeCheckedRecords.contains(RecDecl)) + continue; + else + VisitedRecords.insert(RecDecl); if (auto *NextFD = dyn_cast(Next)) History.push_back(NextFD); // When nullptr is discovered, this means we've gone back up a level, so @@ -6106,7 +6111,7 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, } while (!StackForRecursion.empty()); if (CanCacheResult && !FoundError) - DeepTypeCheckedRecords.insert(RootRecord); + DeepTypeCheckedRecords.insert_range(VisitedRecords); } void SemaSYCL::finalizeSYCLDelayedAnalysis(const FunctionDecl *Caller, From 75b23ca717998cd60481d3254be76d7261c73676 Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Thu, 3 Sep 2026 12:53:20 -0700 Subject: [PATCH 7/7] Add comments --- clang/lib/Sema/SemaSYCL.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clang/lib/Sema/SemaSYCL.cpp b/clang/lib/Sema/SemaSYCL.cpp index 7a59037ee6580..40d09c7119815 100644 --- a/clang/lib/Sema/SemaSYCL.cpp +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -428,6 +428,8 @@ static bool isZeroSizedArray(SemaSYCL &S, QualType Ty) { static std::pair needsDeepTypeCheck(SemaSYCL &S, QualType Ty) { while (Ty->isAnyPointerType() || Ty->isArrayType() || Ty->isReferenceType()) { + // A zero-length array has no record to traverse, but the DFS below must + // still visit it to emit the required diagnostic. if (isZeroSizedArray(S, Ty)) return {nullptr, true}; if (Ty->isArrayType()) @@ -6016,6 +6018,7 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, bool FoundError = false; bool CanCacheResult = RootRecord && RootRecord->isCompleteDefinition(); llvm::SmallDenseSet Visited; + // Cache complete nested records after this whole traversal succeeds. llvm::SmallDenseSet, 8> VisitedRecords; auto Check = [&](QualType TypeToCheck, const ValueDecl *D) { @@ -6059,6 +6062,8 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, if (!Visited.insert(NextTy).second) continue; + // A dependent type can resolve differently when instantiated, so an + // error-free traversal cannot be reused for later instantiations. if (NextTy->isDependentType()) CanCacheResult = false; @@ -6095,6 +6100,7 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, } if (const auto *RecDecl = NextTy->getAsRecordDecl()) { + // An incomplete record can acquire unsupported fields when completed. if (!RecDecl->isCompleteDefinition()) CanCacheResult = false; else if (DeepTypeCheckedRecords.contains(RecDecl))