From 8ea8e7f52908a831ab44ffca1e0c8fe207a409c8 Mon Sep 17 00:00:00 2001 From: Tom Honermann Date: Thu, 9 Jan 2025 15:42:29 -0500 Subject: [PATCH] [SYCL] Basic diagnostics for the sycl_kernel_entry_point attribute. (#120327) The `sycl_kernel_entry_point` attribute is used to declare a function that defines a pattern for an offload kernel entry point. The attribute requires a single type argument that specifies a class type that meets the requirements for a SYCL kernel name as described in section 5.2, "Naming of kernels", of the SYCL 2020 specification. A unique kernel name type is required for each function declared with the attribute. The attribute may not first appear on a declaration that follows a definition of the function. The function is required to have a non-deduced `void` return type. The function must not be a non-static member function, be deleted or defaulted, be declared with the `constexpr` or `consteval` specifiers, be declared with the `[[noreturn]]` attribute, be a coroutine, or accept variadic arguments. Diagnostics are not yet provided for the following: - Use of a type as a kernel name that does not satisfy the forward declarability requirements specified in section 5.2, "Naming of kernels", of the SYCL 2020 specification. - Use of a type as a parameter of the attributed function that does not satisfy the kernel parameter requirements specified in section 4.12.4, "Rules for parameter passing to kernels", of the SYCL 2020 specification (each such function parameter constitutes a kernel parameter). - Use of language features that are not permitted in device functions as specified in section 5.4, "Language restrictions for device functions", of the SYCL 2020 specification. There are several issues noted by various FIXME comments. - The diagnostic generated for kernel name conflicts needs additional work to better detail the relevant source locations; such as the location of each declaration as well as the original source of each kernel name. - A number of the tests illustrate spurious errors being produced due to attributes that appertain to function templates being instantiated too early (during overload resolution as opposed to after an overload is selected). Included changes allow the `SYCLKernelEntryPointAttr` attribute to be marked as invalid if a `sycl_kernel_entry_point` attribute is used incorrectly. This is intended to prevent trying to emit an offload kernel entry point without having to mark the associated function as invalid since doing so would affect overload resolution; which this attribute should not do. Unfortunately, Clang eagerly instantiates attributes that appertain to functions with the result that errors might be issued for function declarations that are never selected by overload resolution. Tests have been added to demonstrate this. Further work will be needed to address these issues (for this and other attributes). --- clang/include/clang/AST/ASTContext.h | 10 + clang/include/clang/Basic/Attr.td | 13 +- clang/include/clang/Basic/AttrDocs.td | 2 +- clang/include/clang/Basic/DiagnosticGroups.td | 1 + .../clang/Basic/DiagnosticSemaKinds.td | 27 ++ clang/include/clang/Sema/SemaSYCL.h | 2 + clang/lib/AST/ASTContext.cpp | 13 + clang/lib/Sema/SemaDecl.cpp | 36 +- clang/lib/Sema/SemaLambda.cpp | 5 + clang/lib/Sema/SemaSYCL.cpp | 156 ++++++++ clang/lib/Serialization/ASTReaderDecl.cpp | 13 +- .../ast-dump-sycl-kernel-entry-point.cpp | 23 +- ...-kernel-entry-point-attr-appertainment.cpp | 352 ++++++++++++++++++ .../sycl-kernel-entry-point-attr-grammar.cpp | 15 - ...el-entry-point-attr-kernel-name-module.cpp | 104 ++++++ ...ernel-entry-point-attr-kernel-name-pch.cpp | 36 ++ ...cl-kernel-entry-point-attr-kernel-name.cpp | 118 ++++++ .../sycl-kernel-entry-point-attr-sfinae.cpp | 65 ++++ 18 files changed, 966 insertions(+), 25 deletions(-) create mode 100644 clang/test/SemaSYCL/sycl-kernel-entry-point-attr-appertainment.cpp create mode 100644 clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name-module.cpp create mode 100644 clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name-pch.cpp create mode 100644 clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name.cpp create mode 100644 clang/test/SemaSYCL/sycl-kernel-entry-point-attr-sfinae.cpp diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 1e89a6805ce9c6..0e07c5d6ce8fba 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3360,6 +3360,16 @@ class ASTContext : public RefCountedBase { /// this function. void registerSYCLEntryPointFunction(FunctionDecl *FD); + /// Given a type used as a SYCL kernel name, returns a reference to the + /// metadata generated from the corresponding SYCL kernel entry point. + /// Aborts if the provided type is not a registered SYCL kernel name. + const SYCLKernelInfo &getSYCLKernelInfo(QualType T) const; + + /// Returns a pointer to the metadata generated from the corresponding + /// SYCLkernel entry point if the provided type corresponds to a registered + /// SYCL kernel name. Returns a null pointer otherwise. + const SYCLKernelInfo *findSYCLKernelInfo(QualType T) const; + //===--------------------------------------------------------------------===// // Statistics //===--------------------------------------------------------------------===// diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index e51a74655dd5ec..5039c20d8b73be 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1516,11 +1516,22 @@ def SYCLKernel : InheritableAttr { def SYCLKernelEntryPoint : InheritableAttr { let Spellings = [Clang<"sycl_kernel_entry_point">]; - let Args = [TypeArgument<"KernelName">]; + let Args = [ + // KernelName is required and specifies the kernel name type. + TypeArgument<"KernelName">, + // InvalidAttr is a fake argument used to track whether the + // semantic requirements of the attribute have been satisified. + // A fake argument is used to enable serialization support. + DefaultBoolArgument<"Invalid", /*default=*/0, /*fake=*/1> + ]; let Subjects = SubjectList<[Function], ErrorDiag>; let TemplateDependent = 1; let LangOpts = [SYCLHost, SYCLDevice]; let Documentation = [SYCLKernelEntryPointDocs]; + let AdditionalMembers = [{ + void setInvalidAttr() { invalid = true; } + bool isInvalidAttr() const { return invalid; } + }]; } def SYCLSpecialClass: InheritableAttr { diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index b8d702e41aa0bb..506fe38eb882b2 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -475,7 +475,7 @@ not first appear on a declaration that follows a definition of the function. The attribute only appertains to functions and only those that meet the following requirements. -* Has a ``void`` return type. +* Has a non-deduced ``void`` return type. * Is not a non-static member function, constructor, or destructor. * Is not a C variadic function. * Is not a coroutine. diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 3ac490d30371b1..594e99a19b64d6 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -648,6 +648,7 @@ def PoundPragmaMessage : DiagGroup<"#pragma-messages">, def : DiagGroup<"redundant-decls">; def RedeclaredClassMember : DiagGroup<"redeclared-class-member">; def GNURedeclaredEnum : DiagGroup<"gnu-redeclared-enum">; +def RedundantAttribute : DiagGroup<"redundant-attribute">; def RedundantMove : DiagGroup<"redundant-move">; def Register : DiagGroup<"register", [DeprecatedRegister]>; def ReturnTypeCLinkage : DiagGroup<"return-type-c-linkage">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ab2d6237c1cab8..d4e897868f1a9a 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -12429,6 +12429,33 @@ def err_sycl_special_type_num_init_method : Error< "types with 'sycl_special_class' attribute must have one and only one '__init' " "method defined">; +// SYCL kernel entry point diagnostics +def err_sycl_entry_point_invalid : Error< + "'sycl_kernel_entry_point' attribute cannot be applied to a" + " %select{non-static member function|variadic function|deleted function|" + "defaulted function|constexpr function|consteval function|" + "function declared with the 'noreturn' attribute|coroutine}0">; +def err_sycl_entry_point_invalid_redeclaration : Error< + "'sycl_kernel_entry_point' kernel name argument does not match prior" + " declaration%diff{: $ vs $|}0,1">; +def err_sycl_kernel_name_conflict : Error< + "'sycl_kernel_entry_point' kernel name argument conflicts with a previous" + " declaration">; +def warn_sycl_kernel_name_not_a_class_type : Warning< + "%0 is not a valid SYCL kernel name type; a non-union class type is required">, + InGroup>, DefaultError; +def warn_sycl_entry_point_redundant_declaration : Warning< + "redundant 'sycl_kernel_entry_point' attribute">, InGroup; +def err_sycl_entry_point_after_definition : Error< + "'sycl_kernel_entry_point' attribute cannot be added to a function after the" + " function is defined">; +def err_sycl_entry_point_return_type : Error< + "'sycl_kernel_entry_point' attribute only applies to functions with a" + " 'void' return type">; +def err_sycl_entry_point_deduced_return_type : Error< + "'sycl_kernel_entry_point' attribute only applies to functions with a" + " non-deduced 'void' return type">; + def warn_cuda_maxclusterrank_sm_90 : Warning< "maxclusterrank requires sm_90 or higher, CUDA arch provided: %0, ignoring " "%1 attribute">, InGroup; diff --git a/clang/include/clang/Sema/SemaSYCL.h b/clang/include/clang/Sema/SemaSYCL.h index c9f3358124eda7..5bb0de40c886c7 100644 --- a/clang/include/clang/Sema/SemaSYCL.h +++ b/clang/include/clang/Sema/SemaSYCL.h @@ -63,6 +63,8 @@ class SemaSYCL : public SemaBase { void handleKernelAttr(Decl *D, const ParsedAttr &AL); void handleKernelEntryPointAttr(Decl *D, const ParsedAttr &AL); + + void CheckSYCLEntryPointFunctionDecl(FunctionDecl *FD); }; } // namespace clang diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index b10513f49a8d16..8f04b58841964a 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -14502,6 +14502,19 @@ void ASTContext::registerSYCLEntryPointFunction(FunctionDecl *FD) { std::make_pair(KernelNameType, BuildSYCLKernelInfo(KernelNameType, FD))); } +const SYCLKernelInfo &ASTContext::getSYCLKernelInfo(QualType T) const { + CanQualType KernelNameType = getCanonicalType(T); + return SYCLKernels.at(KernelNameType); +} + +const SYCLKernelInfo *ASTContext::findSYCLKernelInfo(QualType T) const { + CanQualType KernelNameType = getCanonicalType(T); + auto IT = SYCLKernels.find(KernelNameType); + if (IT != SYCLKernels.end()) + return &IT->second; + return nullptr; +} + OMPTraitInfo &ASTContext::getNewOMPTraitInfo() { OMPTraitInfoVector.emplace_back(new OMPTraitInfo()); return *OMPTraitInfoVector.back(); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 4001c4d263f1d2..75920052c4f0cc 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -52,6 +52,7 @@ #include "clang/Sema/SemaOpenMP.h" #include "clang/Sema/SemaPPC.h" #include "clang/Sema/SemaRISCV.h" +#include "clang/Sema/SemaSYCL.h" #include "clang/Sema/SemaSwift.h" #include "clang/Sema/SemaWasm.h" #include "clang/Sema/Template.h" @@ -2923,7 +2924,7 @@ static void checkNewAttributesAfterDef(Sema &S, Decl *New, const Decl *Old) { AttrVec &NewAttributes = New->getAttrs(); for (unsigned I = 0, E = NewAttributes.size(); I != E;) { - const Attr *NewAttribute = NewAttributes[I]; + Attr *NewAttribute = NewAttributes[I]; if (isa(NewAttribute) || isa(NewAttribute)) { if (FunctionDecl *FD = dyn_cast(New)) { @@ -3018,6 +3019,16 @@ static void checkNewAttributesAfterDef(Sema &S, Decl *New, const Decl *Old) { // declarations after definitions. ++I; continue; + } else if (isa(NewAttribute)) { + // Elevate latent uses of the sycl_kernel_entry_point attribute to an + // error since the definition will have already been created without + // the semantic effects of the attribute having been applied. + S.Diag(NewAttribute->getLocation(), + diag::err_sycl_entry_point_after_definition); + S.Diag(Def->getLocation(), diag::note_previous_definition); + cast(NewAttribute)->setInvalidAttr(); + ++I; + continue; } S.Diag(NewAttribute->getLocation(), @@ -12142,8 +12153,8 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD, if (LangOpts.OpenMP) OpenMP().ActOnFinishedFunctionDefinitionInOpenMPAssumeScope(NewFD); - if (LangOpts.isSYCL() && NewFD->hasAttr()) - getASTContext().registerSYCLEntryPointFunction(NewFD); + if (NewFD->hasAttr()) + SYCL().CheckSYCLEntryPointFunctionDecl(NewFD); // Semantic checking for this function declaration (in isolation). @@ -15975,6 +15986,25 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, CheckCoroutineWrapper(FD); } + // Diagnose invalid SYCL kernel entry point function declarations. + if (FD && !FD->isInvalidDecl() && FD->hasAttr()) { + SYCLKernelEntryPointAttr *SKEPAttr = + FD->getAttr(); + if (FD->isDefaulted()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid) + << /*defaulted function*/ 3; + SKEPAttr->setInvalidAttr(); + } else if (FD->isDeleted()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid) + << /*deleted function*/ 2; + SKEPAttr->setInvalidAttr(); + } else if (FSI->isCoroutine()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid) + << /*coroutine*/ 7; + SKEPAttr->setInvalidAttr(); + } + } + { // Do not call PopExpressionEvaluationContext() if it is a lambda because // one is already popped when finishing the lambda in BuildLambdaExpr(). diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp index a67c0b2b367d1a..f2c3a816b3b5d3 100644 --- a/clang/lib/Sema/SemaLambda.cpp +++ b/clang/lib/Sema/SemaLambda.cpp @@ -24,6 +24,7 @@ #include "clang/Sema/SemaCUDA.h" #include "clang/Sema/SemaInternal.h" #include "clang/Sema/SemaOpenMP.h" +#include "clang/Sema/SemaSYCL.h" #include "clang/Sema/Template.h" #include "llvm/ADT/STLExtras.h" #include @@ -1948,6 +1949,10 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap, ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) { LambdaScopeInfo LSI = *cast(FunctionScopes.back()); + + if (LSI.CallOperator->hasAttr()) + SYCL().CheckSYCLEntryPointFunctionDecl(LSI.CallOperator); + ActOnFinishFunctionBody(LSI.CallOperator, Body); return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI); diff --git a/clang/lib/Sema/SemaSYCL.cpp b/clang/lib/Sema/SemaSYCL.cpp index d4fddeb01d0fc8..ce53990fdcb18f 100644 --- a/clang/lib/Sema/SemaSYCL.cpp +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -10,7 +10,9 @@ #include "clang/Sema/SemaSYCL.h" #include "clang/AST/Mangle.h" +#include "clang/AST/SYCLKernelInfo.h" #include "clang/AST/TypeOrdering.h" +#include "clang/Basic/Diagnostic.h" #include "clang/Sema/Attr.h" #include "clang/Sema/ParsedAttr.h" #include "clang/Sema/Sema.h" @@ -206,3 +208,157 @@ void SemaSYCL::handleKernelEntryPointAttr(Decl *D, const ParsedAttr &AL) { D->addAttr(::new (SemaRef.Context) SYCLKernelEntryPointAttr(SemaRef.Context, AL, TSI)); } + +// Given a potentially qualified type, SourceLocationForUserDeclaredType() +// returns the source location of the canonical declaration of the unqualified +// desugared user declared type, if any. For non-user declared types, an +// invalid source location is returned. The intended usage of this function +// is to identify an appropriate source location, if any, for a +// "entity declared here" diagnostic note. +static SourceLocation SourceLocationForUserDeclaredType(QualType QT) { + SourceLocation Loc; + const Type *T = QT->getUnqualifiedDesugaredType(); + if (const TagType *TT = dyn_cast(T)) + Loc = TT->getDecl()->getLocation(); + else if (const ObjCInterfaceType *ObjCIT = dyn_cast(T)) + Loc = ObjCIT->getDecl()->getLocation(); + return Loc; +} + +static bool CheckSYCLKernelName(Sema &S, SourceLocation Loc, + QualType KernelName) { + assert(!KernelName->isDependentType()); + + if (!KernelName->isStructureOrClassType()) { + // SYCL 2020 section 5.2, "Naming of kernels", only requires that the + // kernel name be a C++ typename. However, the definition of "kernel name" + // in the glossary states that a kernel name is a class type. Neither + // section explicitly states whether the kernel name type can be + // cv-qualified. For now, kernel name types are required to be class types + // and that they may be cv-qualified. The following issue requests + // clarification from the SYCL WG. + // https://github.com/KhronosGroup/SYCL-Docs/issues/568 + S.Diag(Loc, diag::warn_sycl_kernel_name_not_a_class_type) << KernelName; + SourceLocation DeclTypeLoc = SourceLocationForUserDeclaredType(KernelName); + if (DeclTypeLoc.isValid()) + S.Diag(DeclTypeLoc, diag::note_entity_declared_at) << KernelName; + return true; + } + + return false; +} + +void SemaSYCL::CheckSYCLEntryPointFunctionDecl(FunctionDecl *FD) { + // Ensure that all attributes present on the declaration are consistent + // and warn about any redundant ones. + SYCLKernelEntryPointAttr *SKEPAttr = nullptr; + for (auto *SAI : FD->specific_attrs()) { + if (!SKEPAttr) { + SKEPAttr = SAI; + continue; + } + if (!getASTContext().hasSameType(SAI->getKernelName(), + SKEPAttr->getKernelName())) { + Diag(SAI->getLocation(), diag::err_sycl_entry_point_invalid_redeclaration) + << SAI->getKernelName() << SKEPAttr->getKernelName(); + Diag(SKEPAttr->getLocation(), diag::note_previous_attribute); + SAI->setInvalidAttr(); + } else { + Diag(SAI->getLocation(), + diag::warn_sycl_entry_point_redundant_declaration); + Diag(SKEPAttr->getLocation(), diag::note_previous_attribute); + } + } + assert(SKEPAttr && "Missing sycl_kernel_entry_point attribute"); + + // Ensure the kernel name type is valid. + if (!SKEPAttr->getKernelName()->isDependentType() && + CheckSYCLKernelName(SemaRef, SKEPAttr->getLocation(), + SKEPAttr->getKernelName())) + SKEPAttr->setInvalidAttr(); + + // Ensure that an attribute present on the previous declaration + // matches the one on this declaration. + FunctionDecl *PrevFD = FD->getPreviousDecl(); + if (PrevFD && !PrevFD->isInvalidDecl()) { + const auto *PrevSKEPAttr = PrevFD->getAttr(); + if (PrevSKEPAttr && !PrevSKEPAttr->isInvalidAttr()) { + if (!getASTContext().hasSameType(SKEPAttr->getKernelName(), + PrevSKEPAttr->getKernelName())) { + Diag(SKEPAttr->getLocation(), + diag::err_sycl_entry_point_invalid_redeclaration) + << SKEPAttr->getKernelName() << PrevSKEPAttr->getKernelName(); + Diag(PrevSKEPAttr->getLocation(), diag::note_previous_decl) << PrevFD; + SKEPAttr->setInvalidAttr(); + } + } + } + + if (const auto *MD = dyn_cast(FD)) { + if (!MD->isStatic()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid) + << /*non-static member function*/ 0; + SKEPAttr->setInvalidAttr(); + } + } + + if (FD->isVariadic()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid) + << /*variadic function*/ 1; + SKEPAttr->setInvalidAttr(); + } + + if (FD->isDefaulted()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid) + << /*defaulted function*/ 3; + SKEPAttr->setInvalidAttr(); + } else if (FD->isDeleted()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid) + << /*deleted function*/ 2; + SKEPAttr->setInvalidAttr(); + } + + if (FD->isConsteval()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid) + << /*consteval function*/ 5; + SKEPAttr->setInvalidAttr(); + } else if (FD->isConstexpr()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid) + << /*constexpr function*/ 4; + SKEPAttr->setInvalidAttr(); + } + + if (FD->isNoReturn()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid) + << /*function declared with the 'noreturn' attribute*/ 6; + SKEPAttr->setInvalidAttr(); + } + + if (FD->getReturnType()->isUndeducedType()) { + Diag(SKEPAttr->getLocation(), + diag::err_sycl_entry_point_deduced_return_type); + SKEPAttr->setInvalidAttr(); + } else if (!FD->getReturnType()->isDependentType() && + !FD->getReturnType()->isVoidType()) { + Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_return_type); + SKEPAttr->setInvalidAttr(); + } + + if (!FD->isInvalidDecl() && !FD->isTemplated() && + !SKEPAttr->isInvalidAttr()) { + const SYCLKernelInfo *SKI = + getASTContext().findSYCLKernelInfo(SKEPAttr->getKernelName()); + if (SKI) { + if (!declaresSameEntity(FD, SKI->getKernelEntryPointDecl())) { + // FIXME: This diagnostic should include the origin of the kernel + // FIXME: names; not just the locations of the conflicting declarations. + Diag(FD->getLocation(), diag::err_sycl_kernel_name_conflict); + Diag(SKI->getKernelEntryPointDecl()->getLocation(), + diag::note_previous_declaration); + SKEPAttr->setInvalidAttr(); + } + } else { + getASTContext().registerSYCLEntryPointFunction(FD); + } + } +} diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp index 8c60e85c93d70e..dee5169ae5723a 100644 --- a/clang/lib/Serialization/ASTReaderDecl.cpp +++ b/clang/lib/Serialization/ASTReaderDecl.cpp @@ -1134,8 +1134,19 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) { // the presence of a sycl_kernel_entry_point attribute, register it so that // associated metadata is recreated. if (FD->hasAttr()) { + auto *SKEPAttr = FD->getAttr(); ASTContext &C = Reader.getContext(); - C.registerSYCLEntryPointFunction(FD); + const SYCLKernelInfo *SKI = C.findSYCLKernelInfo(SKEPAttr->getKernelName()); + if (SKI) { + if (!declaresSameEntity(FD, SKI->getKernelEntryPointDecl())) { + Reader.Diag(FD->getLocation(), diag::err_sycl_kernel_name_conflict); + Reader.Diag(SKI->getKernelEntryPointDecl()->getLocation(), + diag::note_previous_declaration); + SKEPAttr->setInvalidAttr(); + } + } else { + C.registerSYCLEntryPointFunction(FD); + } } } diff --git a/clang/test/ASTSYCL/ast-dump-sycl-kernel-entry-point.cpp b/clang/test/ASTSYCL/ast-dump-sycl-kernel-entry-point.cpp index c351f3b7d03eab..0189cf0402d3a3 100644 --- a/clang/test/ASTSYCL/ast-dump-sycl-kernel-entry-point.cpp +++ b/clang/test/ASTSYCL/ast-dump-sycl-kernel-entry-point.cpp @@ -107,14 +107,29 @@ void skep5>(long) { // CHECK: | |-TemplateArgument type 'long' // CHECK: | `-SYCLKernelEntryPointAttr {{.*}} KN<5, 2> +// FIXME: C++23 [temp.expl.spec]p12 states: +// FIXME: ... Similarly, attributes appearing in the declaration of a template +// FIXME: have no effect on an explicit specialization of that template. +// FIXME: Clang currently instantiates a function template specialization from +// FIXME: the function template declaration and links it as a previous +// FIXME: declaration of an explicit specialization. The instantiated +// FIXME: declaration includes attributes instantiated from the function +// FIXME: template declaration. When the instantiated declaration and the +// FIXME: explicit specialization both specify a sycl_kernel_entry_point +// FIXME: attribute with different kernel name types, a spurious diagnostic +// FIXME: is issued. The following test case is incorrectly diagnosed as +// FIXME: having conflicting kernel name types (KN<5,3> vs the incorrectly +// FIXME: inherited KN<5,-1>). +#if 0 template<> [[clang::sycl_kernel_entry_point(KN<5,3>)]] void skep5>(long long) { } -// CHECK: |-FunctionDecl {{.*}} prev {{.*}} skep5 'void (long long)' explicit_specialization -// CHECK-NEXT: | |-TemplateArgument type 'KN<5, -1>' -// CHECK: | |-TemplateArgument type 'long long' -// CHECK: | `-SYCLKernelEntryPointAttr {{.*}} KN<5, 3> +// FIXME-CHECK: |-FunctionDecl {{.*}} prev {{.*}} skep5 'void (long long)' explicit_specialization +// FIXME-CHECK-NEXT: | |-TemplateArgument type 'KN<5, -1>' +// FIXME-CHECK: | |-TemplateArgument type 'long long' +// FIXME-CHECK: | `-SYCLKernelEntryPointAttr {{.*}} KN<5, 3> +#endif template void skep5>(int); // Checks are located with the primary template declaration above. diff --git a/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-appertainment.cpp b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-appertainment.cpp new file mode 100644 index 00000000000000..5b3cf9853173dd --- /dev/null +++ b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-appertainment.cpp @@ -0,0 +1,352 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fsyntax-only -fsycl-is-device -verify %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fsyntax-only -fsycl-is-device -verify %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fsyntax-only -fsycl-is-device -verify %s + +// These tests validate appertainment for the sycl_kernel_entry_point attribute. + +#if __cplusplus >= 202002L +// Mock coroutine support. +namespace std { + +template +struct coroutine_handle { + template + coroutine_handle(const coroutine_handle&); + static coroutine_handle from_address(void *addr); +}; + +template +struct coroutine_traits { + struct suspend_never { + bool await_ready() const noexcept; + void await_suspend(std::coroutine_handle<>) const noexcept; + void await_resume() const noexcept; + }; + struct promise_type { + void get_return_object() noexcept; + suspend_never initial_suspend() const noexcept; + suspend_never final_suspend() const noexcept; + void return_void() noexcept; + void unhandled_exception() noexcept; + }; +}; + +} +#endif + +// A unique kernel name type is required for each declared kernel entry point. +template struct KN; + + +//////////////////////////////////////////////////////////////////////////////// +// Valid declarations. +//////////////////////////////////////////////////////////////////////////////// + +// Function declaration with GNU attribute spelling +__attribute__((sycl_kernel_entry_point(KN<1>))) +void ok1(); + +// Function declaration with Clang attribute spelling. +[[clang::sycl_kernel_entry_point(KN<2>)]] +void ok2(); + +// Function definition. +[[clang::sycl_kernel_entry_point(KN<3>)]] +void ok3() {} + +// Function template definition. +template +[[clang::sycl_kernel_entry_point(KNT)]] +void ok4(T) {} + +// Function template explicit specialization. +template<> +[[clang::sycl_kernel_entry_point(KN<4,1>)]] +void ok4>(int) {} + +// Function template explicit instantiation. +template void ok4, long>(long); + +namespace NS { +// Function declaration at namespace scope. +[[clang::sycl_kernel_entry_point(KN<5>)]] +void ok5(); +} + +struct S6 { + // Static member function declaration. + [[clang::sycl_kernel_entry_point(KN<6>)]] + static void ok6(); +}; + +// Dependent hidden friend definition. +template +struct S7 { + [[clang::sycl_kernel_entry_point(KNT)]] + friend void ok7(S7) {} +}; +void test_ok7() { + ok7(S7>{}); +} + +// Non-dependent hidden friend definition. +struct S8Base {}; +template +struct S8 : S8Base { + [[clang::sycl_kernel_entry_point(KN<8>)]] + friend void ok8(const S8Base&) {} +}; +void test_ok8() { + ok8(S8{}); +} + +// The sycl_kernel_entry_point attribute must match across declarations and +// cannot be added for the first time after a definition. +[[clang::sycl_kernel_entry_point(KN<9>)]] +void ok9(); +[[clang::sycl_kernel_entry_point(KN<9>)]] +void ok9(); +[[clang::sycl_kernel_entry_point(KN<10>)]] +void ok10(); +void ok10() {} +void ok11(); +[[clang::sycl_kernel_entry_point(KN<11>)]] +void ok11() {} + +using VOID = void; +[[clang::sycl_kernel_entry_point(KN<12>)]] +VOID ok12(); +[[clang::sycl_kernel_entry_point(KN<13>)]] +const void ok13(); + +#if __cplusplus >= 202302L +auto ok14 = [] [[clang::sycl_kernel_entry_point(KN<14>)]] static -> void {}; +#endif + +template +struct S15 { + // Don't diagnose a dependent return type as a non-void type. + [[clang::sycl_kernel_entry_point(KNT)]] + static T ok15(); +}; + + +//////////////////////////////////////////////////////////////////////////////// +// Invalid declarations. +//////////////////////////////////////////////////////////////////////////////// + +// The sycl_kernel_entry_point attribute cannot appertain to main() because +// main() has a non-void return type. However, if the requirement for a void +// return type were to be relaxed or if an allowance was made for main() to +// return void (as gcc allows in some modes and as has been proposed to WG21 +// on occassion), main() still can't function as a SYCL kernel entry point, +// so this test ensures such attempted uses of the attribute are rejected. +struct Smain; +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions with a 'void' return type}} +[[clang::sycl_kernel_entry_point(Smain)]] +int main(); + +template struct BADKN; + +struct B1 { + // Non-static data member declaration. + // expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} + [[clang::sycl_kernel_entry_point(BADKN<1>)]] + int bad1; +}; + +struct B2 { + // Static data member declaration. + // expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} + [[clang::sycl_kernel_entry_point(BADKN<2>)]] + static int bad2; +}; + +struct B3 { + // Non-static member function declaration. + // expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a non-static member function}} + [[clang::sycl_kernel_entry_point(BADKN<3>)]] + void bad3(); +}; + +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} +namespace bad4 [[clang::sycl_kernel_entry_point(BADKN<4>)]] {} + +#if __cplusplus >= 202002L +// expected-error@+2 {{'sycl_kernel_entry_point' attribute only applies to functions}} +template +concept bad5 [[clang::sycl_kernel_entry_point(BADKN<5>)]] = true; +#endif + +// Type alias declarations. +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} +typedef void bad6 [[clang::sycl_kernel_entry_point(BADKN<6>)]] (); +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} +using bad7 [[clang::sycl_kernel_entry_point(BADKN<7>)]] = void(); +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} +using bad8 [[clang::sycl_kernel_entry_point(BADKN<8>)]] = int; +// expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to types}} +using bad9 = int [[clang::sycl_kernel_entry_point(BADKN<9>)]]; +// expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to types}} +using bad10 = int() [[clang::sycl_kernel_entry_point(BADKN<10>)]]; + +// Variable declaration. +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} +[[clang::sycl_kernel_entry_point(BADKN<11>)]] +int bad11; + +// Class declaration. +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} +struct [[clang::sycl_kernel_entry_point(BADKN<12>)]] bad12; + +// Enumeration declaration. +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} +enum [[clang::sycl_kernel_entry_point(BADKN<13>)]] bad13 {}; + +// Enumerator. +// expected-error@+2 {{'sycl_kernel_entry_point' attribute only applies to functions}} +enum { + bad14 [[clang::sycl_kernel_entry_point(BADKN<14>)]] +}; + +// Attribute added after the definition. +// expected-error@+3 {{'sycl_kernel_entry_point' attribute cannot be added to a function after the function is defined}} +// expected-note@+1 {{previous definition is here}} +void bad15() {} +[[clang::sycl_kernel_entry_point(BADKN<15>)]] +void bad15(); + +// The function must return void. +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions with a 'void' return type}} +[[clang::sycl_kernel_entry_point(BADKN<16>)]] +int bad16(); + +// Function parameters. +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} +void bad17(void (fp [[clang::sycl_kernel_entry_point(BADKN<17>)]])()); + +// Function template parameters. +// FIXME: Clang currently ignores attributes that appear in template parameters +// FIXME: and the C++ standard is unclear regarding whether such attributes are +// FIXME: permitted. P3324 (Attributes for namespace aliases, template +// FIXME: parameters, and lambda captures) seeks to clarify the situation. +// FIXME-expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions}} +template)]])()> +void bad18(); + +#if __cplusplus >= 202002L +// expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a coroutine}} +[[clang::sycl_kernel_entry_point(BADKN<19>)]] +void bad19() { + co_return; +} +#endif + +struct B20 { + // expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a non-static member function}} + [[clang::sycl_kernel_entry_point(BADKN<20>)]] + B20(); +}; + +struct B21 { + // expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a non-static member function}} + [[clang::sycl_kernel_entry_point(BADKN<21>)]] + ~B21(); +}; + +// expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a variadic function}} +[[clang::sycl_kernel_entry_point(BADKN<22>)]] +void bad22(...); + +// expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a deleted function}} +[[clang::sycl_kernel_entry_point(BADKN<23>)]] +void bad23() = delete; + +// expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a constexpr function}} +[[clang::sycl_kernel_entry_point(BADKN<24>)]] +constexpr void bad24() {} + +#if __cplusplus >= 202002L +// expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a consteval function}} +[[clang::sycl_kernel_entry_point(BADKN<25>)]] +consteval void bad25() {} +#endif + +// expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a function declared with the 'noreturn' attribute}} +[[clang::sycl_kernel_entry_point(BADKN<26>)]] +[[noreturn]] void bad26(); + +// expected-error@+3 {{attribute 'target' multiversioning cannot be combined with attribute 'sycl_kernel_entry_point'}} +__attribute__((target("avx"))) void bad27(); +[[clang::sycl_kernel_entry_point(BADKN<27>)]] +__attribute__((target("sse4.2"))) void bad27(); + +template +struct B28 { + // expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a deleted function}} + [[clang::sycl_kernel_entry_point(KNT)]] + friend void bad28() = delete; +}; + +#if __cplusplus >= 202002L +template +struct B29 { + // expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a defaulted function}} + [[clang::sycl_kernel_entry_point(KNT)]] + friend T operator==(B29, B29) = default; +}; +#endif + +#if __cplusplus >= 202002L +template +struct B30 { + // expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a coroutine}} + [[clang::sycl_kernel_entry_point(KNT)]] + friend void bad30() { co_return; } +}; +#endif + +template +struct B31 { + // expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a variadic function}} + [[clang::sycl_kernel_entry_point(KNT)]] + friend void bad31(...) {} +}; + +template +struct B32 { + // expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a constexpr function}} + [[clang::sycl_kernel_entry_point(KNT)]] + friend constexpr void bad32() {} +}; + +#if __cplusplus >= 202002L +template +struct B33 { + // expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a consteval function}} + [[clang::sycl_kernel_entry_point(KNT)]] + friend consteval void bad33() {} +}; +#endif + +template +struct B34 { + // expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a function declared with the 'noreturn' attribute}} + [[clang::sycl_kernel_entry_point(KNT)]] + [[noreturn]] friend void bad34() {} +}; + +#if __cplusplus >= 202302L +// expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a non-static member function}} +auto bad35 = [] [[clang::sycl_kernel_entry_point(BADKN<35>)]] -> void {}; +#endif + +#if __cplusplus >= 202302L +// expected-error@+1 {{'sycl_kernel_entry_point' attribute only applies to functions with a non-deduced 'void' return type}} +auto bad36 = [] [[clang::sycl_kernel_entry_point(BADKN<36>)]] static {}; +#endif + +#if __cplusplus >= 202302L +// expected-error@+1 {{'sycl_kernel_entry_point' attribute cannot be applied to a coroutine}} +auto bad37 = [] [[clang::sycl_kernel_entry_point(BADKN<37>)]] static -> void { co_return; }; +#endif diff --git a/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-grammar.cpp b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-grammar.cpp index c63d241163e618..14b5d2746631d6 100644 --- a/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-grammar.cpp +++ b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-grammar.cpp @@ -120,18 +120,3 @@ template concept C = true; // expected-error@+1 {{expected a type}} [[clang::sycl_kernel_entry_point(C)]] void bad11(); #endif - -struct B12; // #B12-decl -// FIXME: C++23 [temp.expl.spec]p12 states: -// FIXME: ... Similarly, attributes appearing in the declaration of a template -// FIXME: have no effect on an explicit specialization of that template. -// FIXME: Clang currently instantiates and propagates attributes from a function -// FIXME: template to its explicit specializations resulting in the following -// FIXME: spurious error. -// expected-error@+4 {{incomplete type 'B12' named in nested name specifier}} -// expected-note@+5 {{in instantiation of function template specialization 'bad12' requested here}} -// expected-note@#B12-decl {{forward declaration of 'B12'}} -template -[[clang::sycl_kernel_entry_point(typename T::not_found)]] void bad12() {} -template<> -void bad12() {} diff --git a/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name-module.cpp b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name-module.cpp new file mode 100644 index 00000000000000..83c3e5ca267ab2 --- /dev/null +++ b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name-module.cpp @@ -0,0 +1,104 @@ +// Test that SYCL kernel name conflicts that occur across module boundaries are +// properly diagnosed and that declarations are properly merged so that spurious +// conflicts are not reported. + +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t \ +// RUN: -std=c++17 -fsycl-is-host %t/test.cpp -verify +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t \ +// RUN: -std=c++17 -fsycl-is-device %t/test.cpp -verify + +#--- module.modulemap +module M1 { header "m1.h" } +module M2 { header "m2.h" } + + +#--- common.h +template struct KN; + +[[clang::sycl_kernel_entry_point(KN<1>)]] +void common_test1() {} + +template +[[clang::sycl_kernel_entry_point(T)]] +void common_test2() {} +template void common_test2>(); + + +#--- m1.h +#include "common.h" + +[[clang::sycl_kernel_entry_point(KN<3>)]] +void m1_test3() {} // << expected previous declaration note here. + +template +[[clang::sycl_kernel_entry_point(T)]] +void m1_test4() {} // << expected previous declaration note here. +template void m1_test4>(); + +[[clang::sycl_kernel_entry_point(KN<5>)]] +void m1_test5() {} // << expected previous declaration note here. + +template +[[clang::sycl_kernel_entry_point(T)]] +void m1_test6() {} // << expected previous declaration note here. +template void m1_test6>(); + + +#--- m2.h +#include "common.h" + +[[clang::sycl_kernel_entry_point(KN<3>)]] +void m2_test3() {} // << expected kernel name conflict here. + +template +[[clang::sycl_kernel_entry_point(T)]] +void m2_test4() {} // << expected kernel name conflict here. +template void m2_test4>(); + +[[clang::sycl_kernel_entry_point(KN<7>)]] +void m2_test7() {} // << expected previous declaration note here. + +template +[[clang::sycl_kernel_entry_point(T)]] +void m2_test8() {} // << expected previous declaration note here. +template void m2_test8>(); + + +#--- test.cpp +#include "m1.h" +#include "m2.h" + +// Expected diagnostics for m1_test3() and m2_test3(): +// expected-error@m2.h:4 {{'sycl_kernel_entry_point' kernel name argument conflicts with a previous declaration}} +// expected-note@m1.h:12 {{previous declaration is here}} + +// Expected diagnostics for m1_test4>() and m2_test4>(): +// expected-error@m2.h:8 {{'sycl_kernel_entry_point' kernel name argument conflicts with a previous declaration}} +// expected-note@m1.h:16 {{previous declaration is here}} + +// expected-error@+3 {{'sycl_kernel_entry_point' kernel name argument conflicts with a previous declaration}} +// expected-note@m1.h:4 {{previous declaration is here}} +[[clang::sycl_kernel_entry_point(KN<5>)]] +void test5() {} + +// expected-error@+3 {{'sycl_kernel_entry_point' kernel name argument conflicts with a previous declaration}} +// expected-note@m1.h:8 {{previous declaration is here}} +[[clang::sycl_kernel_entry_point(KN<6>)]] +void test6() {} + +// expected-error@+3 {{'sycl_kernel_entry_point' kernel name argument conflicts with a previous declaration}} +// expected-note@m2.h:12 {{previous declaration is here}} +[[clang::sycl_kernel_entry_point(KN<7>)]] +void test7() {} + +// expected-error@+3 {{'sycl_kernel_entry_point' kernel name argument conflicts with a previous declaration}} +// expected-note@m2.h:16 {{previous declaration is here}} +[[clang::sycl_kernel_entry_point(KN<8>)]] +void test8() {} + +void f() { + common_test1(); + common_test2>(); +} diff --git a/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name-pch.cpp b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name-pch.cpp new file mode 100644 index 00000000000000..0814d898d1c0e0 --- /dev/null +++ b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name-pch.cpp @@ -0,0 +1,36 @@ +// Test that SYCL kernel name conflicts that occur across PCH boundaries are +// properly diagnosed. + +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: %clang_cc1 -std=c++17 -fsycl-is-host -emit-pch -x c++-header \ +// RUN: %t/pch.h -o %t/pch.h.host.pch +// RUN: %clang_cc1 -std=c++17 -fsycl-is-host -verify \ +// RUN: -include-pch %t/pch.h.host.pch %t/test.cpp +// RUN: %clang_cc1 -std=c++17 -fsycl-is-device -emit-pch -x c++-header \ +// RUN: %t/pch.h -o %t/pch.h.device.pch +// RUN: %clang_cc1 -std=c++17 -fsycl-is-device -verify \ +// RUN: -include-pch %t/pch.h.device.pch %t/test.cpp + +#--- pch.h +template struct KN; + +[[clang::sycl_kernel_entry_point(KN<1>)]] +void pch_test1() {} // << expected previous declaration note here. + +template +[[clang::sycl_kernel_entry_point(T)]] +void pch_test2() {} // << expected previous declaration note here. +template void pch_test2>(); + + +#--- test.cpp +// expected-error@+3 {{'sycl_kernel_entry_point' kernel name argument conflicts with a previous declaration}} +// expected-note@pch.h:4 {{previous declaration is here}} +[[clang::sycl_kernel_entry_point(KN<1>)]] +void test1() {} + +// expected-error@+3 {{'sycl_kernel_entry_point' kernel name argument conflicts with a previous declaration}} +// expected-note@pch.h:8 {{previous declaration is here}} +[[clang::sycl_kernel_entry_point(KN<2>)]] +void test2() {} diff --git a/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name.cpp b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name.cpp new file mode 100644 index 00000000000000..78dd89696c02d4 --- /dev/null +++ b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-kernel-name.cpp @@ -0,0 +1,118 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fsyntax-only -fsycl-is-device -verify %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fsyntax-only -fsycl-is-device -verify %s + +// These tests validate that the kernel name type argument provided to the +// sycl_kernel_entry_point attribute meets the requirements of a SYCL kernel +// name as described in section 5.2, "Naming of kernels", of the SYCL 2020 +// specification. + +struct S1; +// expected-warning@+3 {{redundant 'sycl_kernel_entry_point' attribute}} +// expected-note@+1 {{previous attribute is here}} +[[clang::sycl_kernel_entry_point(S1), + clang::sycl_kernel_entry_point(S1)]] +void ok1(); + +// expected-error@+1 {{'int' is not a valid SYCL kernel name type; a non-union class type is required}} +[[clang::sycl_kernel_entry_point(int)]] void bad2(); + +// expected-error@+1 {{'int ()' is not a valid SYCL kernel name type; a non-union class type is required}} +[[clang::sycl_kernel_entry_point(int())]] void bad3(); + +// expected-error@+1 {{'int (*)()' is not a valid SYCL kernel name type; a non-union class type is required}} +[[clang::sycl_kernel_entry_point(int(*)())]] void bad4(); + +// expected-error@+1 {{'int (&)()' is not a valid SYCL kernel name type; a non-union class type is required}} +[[clang::sycl_kernel_entry_point(int(&)())]] void bad5(); + +// expected-error@+1 {{'decltype(nullptr)' (aka 'std::nullptr_t') is not a valid SYCL kernel name type; a non-union class type is required}} +[[clang::sycl_kernel_entry_point(decltype(nullptr))]] void bad6(); + +union U7; // #U7-decl +// expected-error@+2 {{'U7' is not a valid SYCL kernel name type; a non-union class type is required}} +// expected-note@#U7-decl {{'U7' declared here}} +[[clang::sycl_kernel_entry_point(U7)]] void bad7(); + +enum E8 {}; // #E8-decl +// expected-error@+2 {{'E8' is not a valid SYCL kernel name type; a non-union class type is required}} +// expected-note@#E8-decl {{'E8' declared here}} +[[clang::sycl_kernel_entry_point(E8)]] void bad8(); + +enum E9 : int; // #E9-decl +// expected-error@+2 {{'E9' is not a valid SYCL kernel name type; a non-union class type is required}} +// expected-note@#E9-decl {{'E9' declared here}} +[[clang::sycl_kernel_entry_point(E9)]] void bad9(); + +struct B10 { + struct MS; +}; +// FIXME-expected-error@+1 {{'sycl_kernel_entry_point' attribute argument must be a forward declarable class type}} +[[clang::sycl_kernel_entry_point(B10::MS)]] void bad10(); + +struct B11 { + struct MS; +}; +// FIXME-expected-error@+3 {{'sycl_kernel_entry_point' attribute argument must be a forward declarable class type}} +template +[[clang::sycl_kernel_entry_point(typename T::MS)]] void bad11() {} +template void bad11(); + +template +[[clang::sycl_kernel_entry_point(T)]] void bad12(); +void f12() { + // FIXME-expected-error@+2 {{'sycl_kernel_entry_point' attribute argument must be a forward declarable class type}} + struct LS; + bad12(); +} + +struct B13_1; +struct B13_2; +// expected-error@+3 {{'sycl_kernel_entry_point' kernel name argument does not match prior declaration: 'B13_2' vs 'B13_1'}} +// expected-note@+1 {{'bad13' declared here}} +[[clang::sycl_kernel_entry_point(B13_1)]] void bad13(); +[[clang::sycl_kernel_entry_point(B13_2)]] void bad13() {} + +struct B14_1; +struct B14_2; +// expected-error@+3 {{'sycl_kernel_entry_point' kernel name argument does not match prior declaration: 'B14_2' vs 'B14_1'}} +// expected-note@+1 {{previous attribute is here}} +[[clang::sycl_kernel_entry_point(B14_1), + clang::sycl_kernel_entry_point(B14_2)]] +void bad14(); + +struct B15; +// expected-error@+3 {{'sycl_kernel_entry_point' kernel name argument conflicts with a previous declaration}} +// expected-note@+1 {{previous declaration is here}} +[[clang::sycl_kernel_entry_point(B15)]] void bad15_1(); +[[clang::sycl_kernel_entry_point(B15)]] void bad15_2(); + +struct B16_1; +struct B16_2; +// expected-error@+4 {{'sycl_kernel_entry_point' kernel name argument does not match prior declaration: 'B16_2' vs 'B16_1'}} +// expected-note@+1 {{'bad16' declared here}} +[[clang::sycl_kernel_entry_point(B16_1)]] void bad16(); +void bad16(); // The attribute from the previous declaration is inherited. +[[clang::sycl_kernel_entry_point(B16_2)]] void bad16(); + +template +struct B17 { + // expected-error@+1 {{'int' is not a valid SYCL kernel name type; a non-union class type is required}} + [[clang::sycl_kernel_entry_point(int)]] + static void bad17(); +}; + +template +struct B18 { + // expected-error@+1 {{'int' is not a valid SYCL kernel name type; a non-union class type is required}} + [[clang::sycl_kernel_entry_point(int)]] + friend void bad18() {} +}; + +template +struct B19 { + // expected-error@+1 {{'int' is not a valid SYCL kernel name type; a non-union class type is required}} + [[clang::sycl_kernel_entry_point(KNT)]] + friend void bad19() {} +}; +// expected-note@+1 {{in instantiation of template class 'B19' requested here}} +B19 b19; diff --git a/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-sfinae.cpp b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-sfinae.cpp new file mode 100644 index 00000000000000..4c615704196299 --- /dev/null +++ b/clang/test/SemaSYCL/sycl-kernel-entry-point-attr-sfinae.cpp @@ -0,0 +1,65 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fsyntax-only -fsycl-is-device -verify %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fsyntax-only -fsycl-is-device -verify %s + +// These tests are intended to validate that a sycl_kernel_entry_point attribute +// appearing in the declaration of a function template does not affect overload +// resolution or cause spurious errors during overload resolution due to either +// a substitution failure in the attribute argument or a semantic check of the +// attribute during instantiation of a specialization unless that specialization +// is selected by overload resolution. + +// FIXME: C++23 [temp.expl.spec]p12 states: +// FIXME: ... Similarly, attributes appearing in the declaration of a template +// FIXME: have no effect on an explicit specialization of that template. +// FIXME: Clang currently instantiates and propagates attributes from a function +// FIXME: template to its explicit specializations resulting in the following +// FIXME: spurious error. +struct S1; // #S1-decl +// expected-error@+4 {{incomplete type 'S1' named in nested name specifier}} +// expected-note@+5 {{in instantiation of function template specialization 'ok1' requested here}} +// expected-note@#S1-decl {{forward declaration of 'S1'}} +template +[[clang::sycl_kernel_entry_point(typename T::invalid)]] void ok1() {} +template<> +void ok1() {} +void test_ok1() { + // ok1() is not a call to a SYCL kernel entry point function. + ok1(); +} + +// FIXME: The sycl_kernel_entry_point attribute should not be instantiated +// FIXME: until after overload resolution has completed. +struct S2; // #S2-decl +// expected-error@+6 {{incomplete type 'S2' named in nested name specifier}} +// expected-note@+10 {{in instantiation of function template specialization 'ok2' requested here}} +// expected-note@#S2-decl {{forward declaration of 'S2'}} +template +[[clang::sycl_kernel_entry_point(T)]] void ok2(int) {} +template +[[clang::sycl_kernel_entry_point(typename T::invalid)]] void ok2(long) {} +void test_ok2() { + // ok2(int) is a better match and is therefore selected by overload + // resolution; the attempted instantiation of ok2(long) should not produce + // an error for the substitution failure into the attribute argument. + ok2(2); +} + +// FIXME: The sycl_kernel_entry_point attribute should not be instantiated +// FIXME: until after overload resolution has completed. +struct S3; +struct Select3 { + using bad_type = int; + using good_type = S3; +}; +// expected-error@+5 {{'typename Select3::bad_type' (aka 'int') is not a valid SYCL kernel name type; a non-union class type is required}} +// expected-note@+9 {{in instantiation of function template specialization 'ok3' requested here}} +template +[[clang::sycl_kernel_entry_point(typename T::good_type)]] void ok3(int) {} +template +[[clang::sycl_kernel_entry_point(typename T::bad_type)]] void ok3(long) {} +void test_ok3() { + // ok3(int) is a better match and is therefore selected by overload + // resolution; the attempted instantiation of ok3(long) should not produce + // an error for the invalid kernel name provided as the attribute argument. + ok3(2); +}