diff --git a/clang/include/clang/Sema/SemaSYCL.h b/clang/include/clang/Sema/SemaSYCL.h index c05886f988a93..0c45945e4edef 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..40d09c7119815 100644 --- a/clang/lib/Sema/SemaSYCL.cpp +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -425,6 +425,21 @@ static bool isZeroSizedArray(SemaSYCL &S, QualType Ty) { return false; } +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()) + Ty = QualType{Ty->getArrayElementTypeNoTypeQual(), 0}; + else + Ty = Ty->getPointeeType(); + } + return {Ty->getAsRecordDecl(), false}; +} + static void checkSYCLType(SemaSYCL &S, QualType Ty, SourceRange Loc, llvm::DenseSet Visited, SourceRange UsedAtLoc = SourceRange()) { @@ -5986,13 +6001,25 @@ 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 auto [RootRecord, HasZeroSizedArray] = + needsDeepTypeCheck(*this, DeclToCheck->getType()); + if (!RootRecord && !HasZeroSizedArray) + return; + if (RootRecord && RootRecord->isCompleteDefinition() && + DeepTypeCheckedRecords.contains(RootRecord)) + 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; + // Cache complete nested records after this whole traversal succeeds. + llvm::SmallDenseSet, 8> VisitedRecords; auto Check = [&](QualType TypeToCheck, const ValueDecl *D) { bool ErrorFound = false; @@ -6035,6 +6062,10 @@ 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; auto EmitHistory = [&]() { // The first element is always nullptr. @@ -6049,6 +6080,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 +6095,18 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, if (NeedToEmitNotes) EmitHistory(); NeedToEmitNotes = false; + FoundError = true; } } 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)) + 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 @@ -6075,6 +6115,9 @@ void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt, llvm::append_range(StackForRecursion, RecDecl->fields()); } } while (!StackForRecursion.empty()); + + if (CanCacheResult && !FoundError) + DeepTypeCheckedRecords.insert_range(VisitedRecords); } void SemaSYCL::finalizeSYCLDelayedAnalysis(const FunctionDecl *Caller, @@ -8041,7 +8084,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) &&