Skip to content

Commit 1a59087

Browse files
authored
[SYCL] The sycl_kernel_entry_point attribute. (#111389)
The `sycl_kernel_entry_point` attribute is used to declare a function that defines a pattern for an offload kernel to be emitted. The attribute requires a single type argument that specifies the type used as a SYCL kernel name as described in section 5.2, "Naming of kernels", of the SYCL 2020 specification. Properties of the offload kernel are collected when a function declared with the `sycl_kernel_entry_point` attribute is parsed or instantiated. These properties, such as the kernel name type, are stored in the AST context where they are (or will be) used for diagnostic purposes and to facilitate reflection to a SYCL run-time library. These properties are not serialized with the AST but are recreated upon deserialization. The `sycl_kernel_entry_point` attribute is intended to replace the existing `sycl_kernel` attribute which is intended to be deprecated in a future change and removed following an appropriate deprecation period. The new attribute differs in that it is enabled for both SYCL host and device compilation, may be used with non-template functions, explicitly indicates the type used as the kernel name type, and will impact AST generation. This change adds the basic infrastructure for the new attribute. Future changes will add diagnostics and new AST support that will be used to drive generation of the corresponding offload kernel.
1 parent 8b7af60 commit 1a59087

14 files changed

+591
-3
lines changed

clang/include/clang/AST/ASTContext.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "clang/AST/ExternalASTSource.h"
2424
#include "clang/AST/PrettyPrinter.h"
2525
#include "clang/AST/RawCommentList.h"
26+
#include "clang/AST/SYCLKernelInfo.h"
2627
#include "clang/AST/TemplateName.h"
2728
#include "clang/Basic/LLVM.h"
2829
#include "clang/Basic/PartialDiagnostic.h"
@@ -1239,6 +1240,11 @@ class ASTContext : public RefCountedBase<ASTContext> {
12391240
/// in device compilation.
12401241
llvm::DenseSet<const FunctionDecl *> CUDAImplicitHostDeviceFunUsedByDevice;
12411242

1243+
/// Map of SYCL kernels indexed by the unique type used to name the kernel.
1244+
/// Entries are not serialized but are recreated on deserialization of a
1245+
/// sycl_kernel_entry_point attributed function declaration.
1246+
llvm::DenseMap<CanQualType, SYCLKernelInfo> SYCLKernels;
1247+
12421248
/// For capturing lambdas with an explicit object parameter whose type is
12431249
/// derived from the lambda type, we need to perform derived-to-base
12441250
/// conversion so we can access the captures; the cast paths for that
@@ -3340,6 +3346,14 @@ class ASTContext : public RefCountedBase<ASTContext> {
33403346
void getFunctionFeatureMap(llvm::StringMap<bool> &FeatureMap,
33413347
GlobalDecl GD) const;
33423348

3349+
/// Generates and stores SYCL kernel metadata for the provided
3350+
/// SYCL kernel entry point function. The provided function must have
3351+
/// an attached sycl_kernel_entry_point attribute that specifies a unique
3352+
/// type for the name of a SYCL kernel. Callers are required to detect
3353+
/// conflicting SYCL kernel names and issue a diagnostic prior to calling
3354+
/// this function.
3355+
void registerSYCLEntryPointFunction(FunctionDecl *FD);
3356+
33433357
//===--------------------------------------------------------------------===//
33443358
// Statistics
33453359
//===--------------------------------------------------------------------===//
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===--- SYCLKernelInfo.h --- Information about SYCL kernels --------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
/// \file
9+
/// This file declares types used to describe SYCL kernels.
10+
///
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef LLVM_CLANG_AST_SYCLKERNELINFO_H
14+
#define LLVM_CLANG_AST_SYCLKERNELINFO_H
15+
16+
#include "clang/AST/Decl.h"
17+
#include "clang/AST/Type.h"
18+
19+
namespace clang {
20+
21+
class SYCLKernelInfo {
22+
public:
23+
SYCLKernelInfo(CanQualType KernelNameType,
24+
const FunctionDecl *KernelEntryPointDecl)
25+
: KernelNameType(KernelNameType),
26+
KernelEntryPointDecl(KernelEntryPointDecl) {}
27+
28+
CanQualType getKernelNameType() const { return KernelNameType; }
29+
30+
const FunctionDecl *getKernelEntryPointDecl() const {
31+
return KernelEntryPointDecl;
32+
}
33+
34+
private:
35+
CanQualType KernelNameType;
36+
const FunctionDecl *KernelEntryPointDecl;
37+
};
38+
39+
} // namespace clang
40+
41+
#endif // LLVM_CLANG_AST_SYCLKERNELINFO_H

clang/include/clang/Basic/Attr.td

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,8 @@ def MicrosoftExt : LangOpt<"MicrosoftExt">;
407407
def Borland : LangOpt<"Borland">;
408408
def CUDA : LangOpt<"CUDA">;
409409
def HIP : LangOpt<"HIP">;
410-
def SYCL : LangOpt<"SYCLIsDevice">;
410+
def SYCLHost : LangOpt<"SYCLIsHost">;
411+
def SYCLDevice : LangOpt<"SYCLIsDevice">;
411412
def COnly : LangOpt<"", "!LangOpts.CPlusPlus">;
412413
def CPlusPlus : LangOpt<"CPlusPlus">;
413414
def OpenCL : LangOpt<"OpenCL">;
@@ -1493,14 +1494,23 @@ def : MutualExclusions<[CUDAConstant, CUDAShared, HIPManaged]>;
14931494
def SYCLKernel : InheritableAttr {
14941495
let Spellings = [Clang<"sycl_kernel">];
14951496
let Subjects = SubjectList<[FunctionTmpl]>;
1496-
let LangOpts = [SYCL];
1497+
let LangOpts = [SYCLDevice];
14971498
let Documentation = [SYCLKernelDocs];
14981499
}
14991500

1501+
def SYCLKernelEntryPoint : InheritableAttr {
1502+
let Spellings = [Clang<"sycl_kernel_entry_point">];
1503+
let Args = [TypeArgument<"KernelName">];
1504+
let Subjects = SubjectList<[Function], ErrorDiag>;
1505+
let TemplateDependent = 1;
1506+
let LangOpts = [SYCLHost, SYCLDevice];
1507+
let Documentation = [SYCLKernelEntryPointDocs];
1508+
}
1509+
15001510
def SYCLSpecialClass: InheritableAttr {
15011511
let Spellings = [Clang<"sycl_special_class">];
15021512
let Subjects = SubjectList<[CXXRecord]>;
1503-
let LangOpts = [SYCL];
1513+
let LangOpts = [SYCLDevice];
15041514
let Documentation = [SYCLSpecialClassDocs];
15051515
}
15061516

clang/include/clang/Basic/AttrDocs.td

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,180 @@ The SYCL kernel in the previous code sample meets these expectations.
455455
}];
456456
}
457457

458+
def SYCLKernelEntryPointDocs : Documentation {
459+
let Category = DocCatFunction;
460+
let Content = [{
461+
The ``sycl_kernel_entry_point`` attribute facilitates the generation of an
462+
offload kernel entry point, sometimes called a SYCL kernel caller function,
463+
suitable for invoking a SYCL kernel on an offload device. The attribute is
464+
intended for use in the implementation of SYCL kernel invocation functions
465+
like the ``single_task`` and ``parallel_for`` member functions of the
466+
``sycl::handler`` class specified in section 4.9.4, "Command group ``handler``
467+
class", of the SYCL 2020 specification.
468+
469+
The attribute requires a single type argument that specifies a class type that
470+
meets the requirements for a SYCL kernel name as described in section 5.2,
471+
"Naming of kernels", of the SYCL 2020 specification. A unique kernel name type
472+
is required for each function declared with the attribute. The attribute may
473+
not first appear on a declaration that follows a definition of the function.
474+
475+
The attribute only appertains to functions and only those that meet the
476+
following requirements.
477+
478+
* Has a ``void`` return type.
479+
* Is not a non-static member function, constructor, or destructor.
480+
* Is not a C variadic function.
481+
* Is not a coroutine.
482+
* Is not defined as deleted or as defaulted.
483+
* Is not declared with the ``constexpr`` or ``consteval`` specifiers.
484+
* Is not declared with the ``[[noreturn]]`` attribute.
485+
486+
Use in the implementation of a SYCL kernel invocation function might look as
487+
follows.
488+
489+
.. code-block:: c++
490+
491+
namespace sycl {
492+
class handler {
493+
template<typename KernelNameType, typename KernelType>
494+
[[ clang::sycl_kernel_entry_point(KernelNameType) ]]
495+
static void kernel_entry_point(KernelType kernel) {
496+
kernel();
497+
}
498+
499+
public:
500+
template<typename KernelNameType, typename KernelType>
501+
void single_task(KernelType kernel) {
502+
// Call kernel_entry_point() to trigger generation of an offload
503+
// kernel entry point.
504+
kernel_entry_point<KernelNameType>(kernel);
505+
// Call functions appropriate for the desired offload backend
506+
// (OpenCL, CUDA, HIP, Level Zero, etc...).
507+
}
508+
};
509+
} // namespace sycl
510+
511+
A SYCL kernel is a callable object of class type that is constructed on a host,
512+
often via a lambda expression, and then passed to a SYCL kernel invocation
513+
function to be executed on an offload device. A SYCL kernel invocation function
514+
is responsible for copying the provided SYCL kernel object to an offload
515+
device and initiating a call to it. The SYCL kernel object and its data members
516+
constitute the parameters of an offload kernel.
517+
518+
A SYCL kernel type is required to satisfy the device copyability requirements
519+
specified in section 3.13.1, "Device copyable", of the SYCL 2020 specification.
520+
Additionally, any data members of the kernel object type are required to satisfy
521+
section 4.12.4, "Rules for parameter passing to kernels". For most types, these
522+
rules require that the type is trivially copyable. However, the SYCL
523+
specification mandates that certain special SYCL types, such as
524+
``sycl::accessor`` and ``sycl::stream`` be device copyable even if they are not
525+
trivially copyable. These types require special handling because they cannot
526+
be copied to device memory as if by ``memcpy()``. Additionally, some offload
527+
backends, OpenCL for example, require objects of some of these types to be
528+
passed as individual arguments to the offload kernel.
529+
530+
An offload kernel consists of an entry point function that declares the
531+
parameters of the offload kernel and the set of all functions and variables that
532+
are directly or indirectly used by the entry point function.
533+
534+
A SYCL kernel invocation function invokes a SYCL kernel on a device by
535+
performing the following tasks (likely with the help of an offload backend
536+
like OpenCL):
537+
538+
#. Identifying the offload kernel entry point to be used for the SYCL kernel.
539+
540+
#. Deconstructing the SYCL kernel object, if necessary, to produce the set of
541+
offload kernel arguments required by the offload kernel entry point.
542+
543+
#. Copying the offload kernel arguments to device memory.
544+
545+
#. Initiating execution of the offload kernel entry point.
546+
547+
The offload kernel entry point for a SYCL kernel performs the following tasks:
548+
549+
#. Reconstituting the SYCL kernel object, if necessary, using the offload
550+
kernel parameters.
551+
552+
#. Calling the ``operator()`` member function of the (reconstituted) SYCL kernel
553+
object.
554+
555+
The ``sycl_kernel_entry_point`` attribute automates generation of an offload
556+
kernel entry point that performs those latter tasks. The parameters and body of
557+
a function declared with the ``sycl_kernel_entry_point`` attribute specify a
558+
pattern from which the parameters and body of the entry point function are
559+
derived. Consider the following call to a SYCL kernel invocation function.
560+
561+
.. code-block:: c++
562+
563+
struct S { int i; };
564+
void f(sycl::handler &handler, sycl::stream &sout, S s) {
565+
handler.single_task<struct KN>([=] {
566+
sout << "The value of s.i is " << s.i << "\n";
567+
});
568+
}
569+
570+
The SYCL kernel object is the result of the lambda expression. It has two
571+
data members corresponding to the captures of ``sout`` and ``s``. Since one
572+
of these data members corresponds to a special SYCL type that must be passed
573+
individually as an offload kernel parameter, it is necessary to decompose the
574+
SYCL kernel object into its constituent parts; the offload kernel will have
575+
two kernel parameters. Given a SYCL implementation that uses a
576+
``sycl_kernel_entry_point`` attributed function like the one shown above, an
577+
offload kernel entry point function will be generated that looks approximately
578+
as follows.
579+
580+
.. code-block:: c++
581+
582+
void sycl-kernel-caller-for-KN(sycl::stream sout, S s) {
583+
kernel-type kernel = { sout, s );
584+
kernel();
585+
}
586+
587+
There are a few items worthy of note:
588+
589+
#. The name of the generated function incorporates the SYCL kernel name,
590+
``KN``, that was passed as the ``KernelNameType`` template parameter to
591+
``kernel_entry_point()`` and provided as the argument to the
592+
``sycl_kernel_entry_point`` attribute. There is a one-to-one correspondence
593+
between SYCL kernel names and offload kernel entry points.
594+
595+
#. The SYCL kernel is a lambda closure type and therefore has no name;
596+
``kernel-type`` is substituted above and corresponds to the ``KernelType``
597+
template parameter deduced in the call to ``kernel_entry_point()``.
598+
Lambda types cannot be declared and initialized using the aggregate
599+
initialization syntax used above, but the intended behavior should be clear.
600+
601+
#. ``S`` is a device copyable type that does not directly or indirectly contain
602+
a data member of a SYCL special type. It therefore does not need to be
603+
decomposed into its constituent members to be passed as a kernel argument.
604+
605+
#. The depiction of the ``sycl::stream`` parameter as a single self contained
606+
kernel parameter is an oversimplification. SYCL special types may require
607+
additional decomposition such that the generated function might have three
608+
or more parameters depending on how the SYCL library implementation defines
609+
these types.
610+
611+
#. The call to ``kernel_entry_point()`` has no effect other than to trigger
612+
emission of the entry point function. The statments that make up the body
613+
of the function are not executed when the function is called; they are
614+
only used in the generation of the entry point function.
615+
616+
It is not necessary for a function declared with the ``sycl_kernel_entry_point``
617+
attribute to be called for the offload kernel entry point to be emitted. For
618+
inline functions and function templates, any ODR-use will suffice. For other
619+
functions, an ODR-use is not required; the offload kernel entry point will be
620+
emitted if the function is defined.
621+
622+
Functions declared with the ``sycl_kernel_entry_point`` attribute are not
623+
limited to the simple example shown above. They may have additional template
624+
parameters, declare additional function parameters, and have complex control
625+
flow in the function body. Function parameter decomposition and reconstitution
626+
is performed for all function parameters. The function must abide by the
627+
language feature restrictions described in section 5.4, "Language restrictions
628+
for device functions" in the SYCL 2020 specification.
629+
}];
630+
}
631+
458632
def SYCLSpecialClassDocs : Documentation {
459633
let Category = DocCatStmt;
460634
let Content = [{

clang/include/clang/Sema/SemaSYCL.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class SemaSYCL : public SemaBase {
6262
ParsedType ParsedTy);
6363

6464
void handleKernelAttr(Decl *D, const ParsedAttr &AL);
65+
void handleKernelEntryPointAttr(Decl *D, const ParsedAttr &AL);
6566
};
6667

6768
} // namespace clang

clang/lib/AST/ASTContext.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14411,6 +14411,32 @@ void ASTContext::getFunctionFeatureMap(llvm::StringMap<bool> &FeatureMap,
1441114411
}
1441214412
}
1441314413

14414+
static SYCLKernelInfo BuildSYCLKernelInfo(CanQualType KernelNameType,
14415+
const FunctionDecl *FD) {
14416+
return {KernelNameType, FD};
14417+
}
14418+
14419+
void ASTContext::registerSYCLEntryPointFunction(FunctionDecl *FD) {
14420+
// If the function declaration to register is invalid or dependent, the
14421+
// registration attempt is ignored.
14422+
if (FD->isInvalidDecl() || FD->isTemplated())
14423+
return;
14424+
14425+
const auto *SKEPAttr = FD->getAttr<SYCLKernelEntryPointAttr>();
14426+
assert(SKEPAttr && "Missing sycl_kernel_entry_point attribute");
14427+
14428+
// Be tolerant of multiple registration attempts so long as each attempt
14429+
// is for the same entity. Callers are obligated to detect and diagnose
14430+
// conflicting kernel names prior to calling this function.
14431+
CanQualType KernelNameType = getCanonicalType(SKEPAttr->getKernelName());
14432+
auto IT = SYCLKernels.find(KernelNameType);
14433+
assert((IT == SYCLKernels.end() ||
14434+
declaresSameEntity(FD, IT->second.getKernelEntryPointDecl())) &&
14435+
"SYCL kernel name conflict");
14436+
SYCLKernels.insert(
14437+
std::make_pair(KernelNameType, BuildSYCLKernelInfo(KernelNameType, FD)));
14438+
}
14439+
1441414440
OMPTraitInfo &ASTContext::getNewOMPTraitInfo() {
1441514441
OMPTraitInfoVector.emplace_back(new OMPTraitInfo());
1441614442
return *OMPTraitInfoVector.back();

clang/lib/Sema/SemaDecl.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12094,6 +12094,9 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD,
1209412094
if (LangOpts.OpenMP)
1209512095
OpenMP().ActOnFinishedFunctionDefinitionInOpenMPAssumeScope(NewFD);
1209612096

12097+
if (LangOpts.isSYCL() && NewFD->hasAttr<SYCLKernelEntryPointAttr>())
12098+
getASTContext().registerSYCLEntryPointFunction(NewFD);
12099+
1209712100
// Semantic checking for this function declaration (in isolation).
1209812101

1209912102
if (getLangOpts().CPlusPlus) {

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6620,6 +6620,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
66206620
case ParsedAttr::AT_SYCLKernel:
66216621
S.SYCL().handleKernelAttr(D, AL);
66226622
break;
6623+
case ParsedAttr::AT_SYCLKernelEntryPoint:
6624+
S.SYCL().handleKernelEntryPointAttr(D, AL);
6625+
break;
66236626
case ParsedAttr::AT_SYCLSpecialClass:
66246627
handleSimpleAttribute<SYCLSpecialClassAttr>(S, D, AL);
66256628
break;

clang/lib/Sema/SemaSYCL.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,12 @@ void SemaSYCL::handleKernelAttr(Decl *D, const ParsedAttr &AL) {
198198

199199
handleSimpleAttribute<SYCLKernelAttr>(*this, D, AL);
200200
}
201+
202+
void SemaSYCL::handleKernelEntryPointAttr(Decl *D, const ParsedAttr &AL) {
203+
ParsedType PT = AL.getTypeArg();
204+
TypeSourceInfo *TSI = nullptr;
205+
(void)SemaRef.GetTypeFromParser(PT, &TSI);
206+
assert(TSI && "no type source info for attribute argument");
207+
D->addAttr(::new (SemaRef.Context)
208+
SYCLKernelEntryPointAttr(SemaRef.Context, AL, TSI));
209+
}

clang/lib/Serialization/ASTReaderDecl.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,14 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) {
11551155
for (unsigned I = 0; I != NumParams; ++I)
11561156
Params.push_back(readDeclAs<ParmVarDecl>());
11571157
FD->setParams(Reader.getContext(), Params);
1158+
1159+
// If the declaration is a SYCL kernel entry point function as indicated by
1160+
// the presence of a sycl_kernel_entry_point attribute, register it so that
1161+
// associated metadata is recreated.
1162+
if (FD->hasAttr<SYCLKernelEntryPointAttr>()) {
1163+
ASTContext &C = Reader.getContext();
1164+
C.registerSYCLEntryPointFunction(FD);
1165+
}
11581166
}
11591167

11601168
void ASTDeclReader::VisitObjCMethodDecl(ObjCMethodDecl *MD) {

0 commit comments

Comments
 (0)