Skip to content

Commit 642ee82

Browse files
author
Erich Keane
authored
[SYCL] Implement a builtin to mark a sycl kernel (#3894)
The unique-stable-name constraint that you can't look up the name in a constant expression before instantiating the kernel is causing issues. This provides a constexpr builtin so that you can mark the kernel without having to instantiate the kernel.
1 parent db31088 commit 642ee82

File tree

14 files changed

+176
-21
lines changed

14 files changed

+176
-21
lines changed

clang/docs/LanguageExtensions.rst

+24
Original file line numberDiff line numberDiff line change
@@ -2452,6 +2452,30 @@ their usual pattern without any special treatment.
24522452
// Computes a unique stable name for the given type.
24532453
constexpr const char * __builtin_sycl_unique_stable_name( type-id );
24542454
2455+
``__builtin_sycl_mark_kernel_name``
2456+
-----------------------------------
2457+
2458+
``__builtin_sycl_mark_kernel_name`` is a builtin that can be used with
2459+
``__builtin_sycl_unique_stable_name`` to make sure a kernel is properly 'marked'
2460+
as a kernel without having to instantiate a sycl_kernel function. Typically,
2461+
``__builtin_sycl_unique_stable_name`` can only be called in a constant expression
2462+
context after any kernels that would change the output have been instantiated.
2463+
This is necessary, as changing the answer to the constant expression after
2464+
evaluation isn't permitted. However, in some cases it can be useful to query the
2465+
result of ``__builtin_unique_stable_name`` after we know that the name is a kernel
2466+
name, but before we are able to instantiate the kernel itself (such as when trying
2467+
to decide between two signatures at compile time). In these cases,
2468+
``__builtin_sycl_mark_kernel_name`` can be used to mark the type as a kernel name,
2469+
ensuring that ``__builtin_unique_stable_name`` gives the correct result despite the
2470+
kernel not yet being instantiated.
2471+
2472+
**Syntax**:
2473+
2474+
.. code-block:: c++
2475+
2476+
// Marks a type as the name of a sycl kernel.
2477+
constexpr bool __builtin_sycl_mark_kernel_name( type-id );
2478+
24552479
Multiprecision Arithmetic Builtins
24562480
----------------------------------
24572481

clang/include/clang/Basic/DiagnosticSemaKinds.td

+2-2
Original file line numberDiff line numberDiff line change
@@ -6408,8 +6408,8 @@ def warn_gnu_null_ptr_arith : Warning<
64086408
"arithmetic on a null pointer treated as a cast from integer to pointer is a GNU extension">,
64096409
InGroup<NullPointerArithmetic>, DefaultIgnore;
64106410
def err_kernel_invalidates_sycl_unique_stable_name
6411-
: Error<"kernel instantiation changes the result of an evaluated "
6412-
"'__builtin_sycl_unique_stable_name'">;
6411+
: Error<"kernel %select{naming|instantiation}0 changes the result of an "
6412+
"evaluated '__builtin_sycl_unique_stable_name'">;
64136413
def note_sycl_unique_stable_name_evaluated_here
64146414
: Note<"'__builtin_sycl_unique_stable_name' evaluated here">;
64156415

clang/include/clang/Basic/TokenKinds.def

+2
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,8 @@ KEYWORD(__builtin_bit_cast , KEYALL)
710710
KEYWORD(__builtin_available , KEYALL)
711711
KEYWORD(__builtin_sycl_unique_stable_name, KEYSYCL)
712712

713+
TYPE_TRAIT_1(__builtin_sycl_mark_kernel_name, SYCLMarkKernelName, KEYSYCL)
714+
713715
// Clang-specific keywords enabled only in testing.
714716
TESTING_KEYWORD(__unknown_anytype , KEYALL)
715717

clang/include/clang/Sema/Sema.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -1069,8 +1069,13 @@ class Sema final {
10691069
OpaqueParser = P;
10701070
}
10711071

1072+
// Marks a type as a SYCL Kernel without necessarily adding it. Additionally,
1073+
// it diagnoses if this causes any of the evaluated
1074+
// __builtin_sycl_unique_stable_name values to change.
1075+
void MarkSYCLKernel(SourceLocation NewLoc, QualType Ty, bool IsInstantiation);
10721076
// Does the work necessary to deal with a SYCL kernel lambda. At the moment,
1073-
// this just marks the list of lambdas required to name the kernel.
1077+
// this just marks the list of lambdas required to name the kernel. It does
1078+
// this by dispatching to MarkSYCLKernel, so it also does the diagnostics.
10741079
void AddSYCLKernelLambda(const FunctionDecl *FD);
10751080

10761081
class DelayedDiagnostics;

clang/lib/Parse/ParseExpr.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,7 @@ class CastExpressionIdValidator final : public CorrectionCandidateCallback {
893893
/// [Clang] unary-type-trait:
894894
/// '__is_aggregate'
895895
/// '__trivially_copyable'
896+
/// '__builtin_sycl_mark_kernel_name'
896897
///
897898
/// binary-type-trait:
898899
/// [GNU] '__is_base_of'

clang/lib/Sema/SemaExprCXX.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -4733,6 +4733,10 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
47334733

47344734
return !S.RequireCompleteType(
47354735
Loc, ArgTy, diag::err_incomplete_type_used_in_type_trait_expr);
4736+
4737+
// Only the type name matters, not the completeness, so always return true.
4738+
case UTT_SYCLMarkKernelName:
4739+
return true;
47364740
}
47374741
}
47384742

@@ -5169,6 +5173,9 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
51695173
return !T->isIncompleteType();
51705174
case UTT_HasUniqueObjectRepresentations:
51715175
return C.hasUniqueObjectRepresentations(T);
5176+
case UTT_SYCLMarkKernelName:
5177+
Self.MarkSYCLKernel(KeyLoc, T, /*IsInstantiation*/ false);
5178+
return true;
51725179
}
51735180
}
51745181

clang/lib/Sema/SemaSYCL.cpp

+21-2
Original file line numberDiff line numberDiff line change
@@ -5222,7 +5222,8 @@ static QualType GetSYCLKernelObjectType(const FunctionDecl *KernelCaller) {
52225222
return KernelParamTy;
52235223
}
52245224

5225-
void Sema::AddSYCLKernelLambda(const FunctionDecl *FD) {
5225+
void Sema::MarkSYCLKernel(SourceLocation NewLoc, QualType Ty,
5226+
bool IsInstantiation) {
52265227
auto MangleCallback = [](ASTContext &Ctx,
52275228
const NamedDecl *ND) -> llvm::Optional<unsigned> {
52285229
if (const auto *RD = dyn_cast<CXXRecordDecl>(ND))
@@ -5232,9 +5233,27 @@ void Sema::AddSYCLKernelLambda(const FunctionDecl *FD) {
52325233
return 1;
52335234
};
52345235

5235-
QualType Ty = GetSYCLKernelObjectType(FD);
52365236
std::unique_ptr<MangleContext> Ctx{ItaniumMangleContext::create(
52375237
Context, Context.getDiagnostics(), MangleCallback)};
52385238
llvm::raw_null_ostream Out;
52395239
Ctx->mangleTypeName(Ty, Out);
5240+
5241+
// Evaluate whether this would change any of the already evaluated
5242+
// __builtin_sycl_unique_stable_name values.
5243+
for (auto &Itr : Context.SYCLUniqueStableNameEvaluatedValues) {
5244+
const std::string &CurName = Itr.first->ComputeName(Context);
5245+
if (Itr.second != CurName) {
5246+
Diag(NewLoc, diag::err_kernel_invalidates_sycl_unique_stable_name)
5247+
<< IsInstantiation;
5248+
Diag(Itr.first->getLocation(),
5249+
diag::note_sycl_unique_stable_name_evaluated_here);
5250+
// Update this so future diagnostics work correctly.
5251+
Itr.second = CurName;
5252+
}
5253+
}
5254+
}
5255+
5256+
void Sema::AddSYCLKernelLambda(const FunctionDecl *FD) {
5257+
QualType Ty = GetSYCLKernelObjectType(FD);
5258+
MarkSYCLKernel(FD->getLocation(), Ty, /*IsInstantiation*/ true);
52405259
}

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

-14
Original file line numberDiff line numberDiff line change
@@ -773,20 +773,6 @@ static void instantiateDependentSYCLKernelAttr(
773773
// instantiation of a kernel.
774774
S.AddSYCLKernelLambda(cast<FunctionDecl>(New));
775775

776-
// Evaluate whether this would change any of the already evaluated
777-
// __builtin_sycl_unique_stable_name values.
778-
for (auto &Itr : S.Context.SYCLUniqueStableNameEvaluatedValues) {
779-
const std::string &CurName = Itr.first->ComputeName(S.Context);
780-
if (Itr.second != CurName) {
781-
S.Diag(New->getLocation(),
782-
diag::err_kernel_invalidates_sycl_unique_stable_name);
783-
S.Diag(Itr.first->getLocation(),
784-
diag::note_sycl_unique_stable_name_evaluated_here);
785-
// Update this so future diagnostics work correctly.
786-
Itr.second = CurName;
787-
}
788-
}
789-
790776
New->addAttr(Attr.clone(S.getASTContext()));
791777
}
792778

clang/test/CodeGenSYCL/Inputs/sycl.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ class accessor {
187187
template <int dimensions, access::mode accessmode, access::target accesstarget>
188188
struct opencl_image_type;
189189

190+
#ifdef __SYCL_DEVICE_ONLY__
190191
#define IMAGETY_DEFINE(dim, accessmode, amsuffix, Target, ifarray_) \
191192
template <> \
192193
struct opencl_image_type<dim, access::mode::accessmode, \
@@ -218,6 +219,8 @@ IMAGETY_WRITE_3_DIM_IMAGE
218219
IMAGETY_READ_2_DIM_IARRAY
219220
IMAGETY_WRITE_2_DIM_IARRAY
220221

222+
#endif
223+
221224
template <int dim, access::mode accessmode, access::target accesstarget>
222225
struct _ImageImplT {
223226
#ifdef __SYCL_DEVICE_ONLY__
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: %clang_cc1 -triple x86_64-linux-pc -fsycl-is-host -disable-llvm-passes -fdeclare-spirv-builtins -emit-llvm %s -o - | FileCheck %s
2+
// RUN: %clang_cc1 -triple spir64 -aux-triple x86_64-linux-pc -fsycl-is-device -disable-llvm-passes -emit-llvm %s -o - | FileCheck %s
3+
4+
#include "Inputs/sycl.hpp"
5+
6+
// This test validates that the use of __builtin_sycl_mark_kernel_name alters
7+
// the code-gen'ed value of __builtin_unique_stable_name. In this case, lambda1
8+
// emits the unmodified version like we do typically, while lambda2 is 'marked',
9+
// so it should follow kernel naming (that is, using the E10000 naming). Note
10+
// that the top level kernel lambda (the E10000 in common) is automatically part
11+
// of a kernel name, since it is passed to the kernel function (which is
12+
// necessary so that the 'device' build actually emits the builtins.
13+
14+
int main() {
15+
16+
cl::sycl::kernel_single_task<class K>([]() {
17+
auto lambda1 = []() {};
18+
auto lambda2 = []() {};
19+
20+
(void)__builtin_sycl_unique_stable_name(decltype(lambda1));
21+
// CHECK: [35 x i8] c"_ZTSZZ4mainENKUlvE10000_clEvEUlvE_\00"
22+
23+
// Should change the unique-stable-name of the lambda.
24+
(void)__builtin_sycl_mark_kernel_name(decltype(lambda2));
25+
(void)__builtin_sycl_unique_stable_name(decltype(lambda2));
26+
// CHECK: [40 x i8] c"_ZTSZZ4mainENKUlvE10000_clEvEUlvE10000_\00"
27+
});
28+
}

clang/test/SemaSYCL/Inputs/sycl.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ template <typename Type> struct get_kernel_wrapper_name_t {
228228

229229
#define ATTR_SYCL_KERNEL __attribute__((sycl_kernel))
230230
template <typename KernelName = auto_name, typename KernelType>
231-
ATTR_SYCL_KERNEL void kernel_single_task(const KernelType &kernelFunc) {
231+
ATTR_SYCL_KERNEL void kernel_single_task(const KernelType &kernelFunc) { // #KernelSingleTaskFunc
232232
kernelFunc(); // #KernelSingleTaskKernelFuncCall
233233
}
234234
template <typename KernelName = auto_name, typename KernelType>
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// RUN: %clang_cc1 %s -std=c++17 -triple x86_64-linux-gnu -fsycl-is-device -verify -fsyntax-only
2+
3+
#include "Inputs/sycl.hpp"
4+
5+
// Test to validate that __builtin_sycl_mark_kernel_name properly updates the
6+
// constexpr checking for __builtin_sycl_unique_stable_name. We need to make
7+
// sure that the KernelInfo change in the library both still stays broken, and
8+
// is then 'fixed', so the definitions below help ensure that is the case.
9+
// We also validate that this works in the event that we have a wrapper that
10+
// first calls for the KernelInfo type, then instantiates a kernel.
11+
12+
template <typename KN>
13+
struct KernelInfo {
14+
static constexpr const char *c = __builtin_sycl_unique_stable_name(KN); // #KI_USN
15+
};
16+
17+
template <typename KN>
18+
struct FixedKernelInfo {
19+
static constexpr bool b = __builtin_sycl_mark_kernel_name(KN);
20+
// making 'c' dependent on 'b' is necessary to ensure 'b' gets called first.
21+
static constexpr const char *c = b
22+
? __builtin_sycl_unique_stable_name(KN)
23+
: nullptr;
24+
};
25+
26+
template <template <typename> class KI,
27+
typename KernelName,
28+
typename KernelType>
29+
void wrapper(KernelType KernelFunc) {
30+
(void)KI<KernelName>::c;
31+
cl::sycl::kernel_single_task<KernelName>(KernelFunc); // #SingleTaskInst
32+
}
33+
34+
int main() {
35+
[]() {
36+
class KernelName1;
37+
constexpr const char *C = __builtin_sycl_unique_stable_name(KernelName1);
38+
// expected-error@+2 {{kernel naming changes the result of an evaluated '__builtin_sycl_unique_stable_name'}}
39+
// expected-note@-2 {{'__builtin_sycl_unique_stable_name' evaluated here}}
40+
(void)__builtin_sycl_mark_kernel_name(KernelName1);
41+
}();
42+
43+
[]() {
44+
// expected-error@#KernelSingleTaskFunc {{kernel instantiation changes the result of an evaluated '__builtin_sycl_unique_stable_name'}}
45+
// expected-note@#SingleTaskInst {{in instantiation of function template}}
46+
// expected-note@+2 {{in instantiation of function template}}
47+
// expected-note@#KI_USN {{'__builtin_sycl_unique_stable_name' evaluated here}}
48+
wrapper<KernelInfo, class KernelName2>([]() {});
49+
}();
50+
51+
[]() {
52+
wrapper<FixedKernelInfo, class KernelName3>([]() {});
53+
}();
54+
}

sycl/include/CL/sycl/detail/kernel_desc.hpp

+11-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,17 @@ using make_index_sequence =
105105

106106
template <typename T> struct KernelInfoImpl {
107107
private:
108-
static constexpr auto n = __builtin_sycl_unique_stable_name(T);
108+
// This is necessary to ensure that any kernels we get info for are properly
109+
// labeled as such before we call __builtin_sycl_unique_stable_name in a
110+
// constant expression, otherwise subsequent calls to a sycl_kernel function
111+
// could cause the kernel name to be altered, and change the result of the
112+
// builtin.
113+
// Additionally, we make this a dependency of 'n' so that we can guarantee
114+
// that this is evaluated first. The builtin always returns 'true', so the
115+
// 'else' branch of 'n's ternary is never evaluated.
116+
static constexpr bool b = __builtin_sycl_mark_kernel_name(T);
117+
static constexpr auto n = b ? __builtin_sycl_unique_stable_name(T)
118+
: __builtin_sycl_unique_stable_name(T);
109119
template <unsigned long long... I>
110120
static KernelInfoData<n[I]...> impl(index_sequence<I...>) {
111121
return {};
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %clangxx -fsycl -fsycl-unnamed-lambda -fsycl-device-only -c %s -o %t.temp
2+
3+
// This validates that the unnamed lambda logic in the library correctly works
4+
// with a new implementation of __builtin_unique_stable_name, where
5+
// instantiation order matters. parallel_for instantiates the KernelInfo before
6+
// the kernel itself, so this checks that example, which only happens when the
7+
// named kernel is inside another lambda.
8+
9+
#include "CL/sycl.hpp"
10+
11+
void foo(cl::sycl::queue queue) {
12+
cl::sycl::event queue_event2 = queue.submit([&](cl::sycl::handler &cgh) {
13+
cgh.parallel_for<class K1>(cl::sycl::range<1>{1},
14+
[=](cl::sycl::item<1> id) {});
15+
});
16+
}

0 commit comments

Comments
 (0)