Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -5317,6 +5317,21 @@ def SYCLIsNDRangeKernel : LangBuiltin<"SYCL_LANG"> {
let Prototype = "bool(...)";
}

// Resolves a (possibly overloaded / templated) free-function-kernel name
// against the provided argument types using ordinary C++ overload resolution
// and template-argument deduction, and evaluates to a pointer to the resolved
// specialization. Used by the SYCL_KERNEL(name, args...) launch macro to fill
// the `kernel_function<Func>` non-type template parameter of the enqueue
// functions from a bare kernel name, e.g.
// nd_launch(q, range, SYCL_KERNEL(iota, 3.14f, p));
// Arg 0 is the unresolved kernel name; the remaining args are the launch
// arguments (consumed only to drive deduction, never evaluated).
def SYCLLaunchKernel : LangBuiltin<"SYCL_LANG"> {
let Spellings = ["__builtin_sycl_launch_kernel"];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a good name for what this builtin does. It doesn't actually launch a kernel; all it does is select an overload from an overload set based on a set of arguments. Other name suggestions:

let Attributes = [NoThrow, CustomTypeChecking];
let Prototype = "void(...)";
}

// HLSL
def HLSLAddUint64: LangBuiltin<"HLSL_LANG"> {
let Spellings = ["__builtin_hlsl_adduint64"];
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -13955,6 +13955,12 @@ def err_sycl_add_ir_attribute_invalid_value : Error<
def err_sycl_add_ir_attribute_invalid_filter : Error<
"initializer list in the first argument of %0 must contain only string "
"literals">;
// Bare-name free-function-kernel launch (__builtin_sycl_launch_kernel, used by
// the SYCL_EXT_ONEAPI_KERNEL_FUNCTION macro).
def err_sycl_launch_kernel_not_free_function : Error<
"the function resolved by '__builtin_sycl_launch_kernel' is not a SYCL free "
"function kernel; it must be declared with 'sycl-nd-range-kernel' or "
"'sycl-single-task-kernel' compile time properties">;
def warn_sycl_old_and_new_kernel_attributes : Warning<
"kernel has both attribute %0 and kernel properties; if the kernel "
"properties contains the property \"%1\" it will be ignored">,
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Sema/SemaSYCL.h
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,16 @@ class SemaSYCL : public SemaBase {
void ProcessFreeFunction(FunctionDecl *FD);
void finalizeFreeFunctionKernels();

/// Handle the '__builtin_sycl_launch_kernel' builtin. Arg 0 of \p TheCall is
/// a (possibly overloaded / templated) SYCL free-function-kernel name; the
/// remaining args are the launch arguments. Resolves the name against those
/// argument types using ordinary C++ overload resolution / template argument
/// deduction, verifies the result is a free function kernel, and evaluates to
/// a pointer to the resolved specialization (usable as the `auto *Func`
/// non-type template parameter of the enqueue-function launch path). Used by
/// the SYCL_KERNEL(name, args...) launch macro.
ExprResult BuildSYCLLaunchKernelCall(CallExpr *TheCall);

/// Get the number of fields or captures within the parsed type.
ExprResult ActOnSYCLBuiltinNumFieldsExpr(ParsedType PT);
ExprResult BuildSYCLBuiltinNumFieldsExpr(SourceLocation Loc,
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4190,6 +4190,9 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,

break;
}

case Builtin::BI__builtin_sycl_launch_kernel:
return SYCL().BuildSYCLLaunchKernelCall(TheCall);
}

if (getLangOpts().HLSL && HLSL().CheckBuiltinFunctionCall(BuiltinID, TheCall))
Expand Down
100 changes: 100 additions & 0 deletions clang/lib/Sema/SemaSYCL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5917,6 +5917,106 @@ void SemaSYCL::processFreeFunctionDeclaration(const FunctionDecl *FD) {
FreeFunctionDeclarations.insert(FD->getCanonicalDecl());
}

// Handle __builtin_sycl_launch_kernel(name, launch-args...). The
// SYCL_EXT_ONEAPI_KERNEL_FUNCTION launch macro expands to
//
// kernel_function<__builtin_sycl_launch_kernel(name, args...)>, args...
//
// so this builtin sits in the `kernel_function<Func>` non-type template
// parameter slot of an enqueue function. Its job is purely to resolve `name`
// (a bare, possibly overloaded / templated free-function-kernel name) to a
// concrete specialization using the launch argument types, and to evaluate to
// a pointer to it — a constant expression usable as the `auto *Func` NTTP. The
// same launch arguments are passed positionally to the enqueue function to
// perform the actual launch; the builtin never evaluates them. Mirrors the
// CUDA `<<<>>>` front-end approach (build a synthetic call, read the resolved
// callee), and reuses the real overload-resolution machinery so a bad launch
// produces ordinary diagnostics at the call site.
ExprResult SemaSYCL::BuildSYCLLaunchKernelCall(CallExpr *TheCall) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise with respect to my comment about the name of the builtin function, this name doesn't reflect what the function really does. It is misleadingly similar to other functions in this same file that actually are involved in launching a kernel (e.g., BuildSYCLKernelLaunchCallArgs()).

I find the comparison with CUDA misleading as well. The CUDA kernel call expression does actually launch a kernel (by implicitly calling cudaConfigureCall() or whatever the current name of that function is).

if (TheCall->getNumArgs() < 1) {
// The builtin needs at least the kernel-name argument. This is unreachable
// through the SYCL_EXT_ONEAPI_KERNEL_FUNCTION macro (NAME is mandatory);
// the guard only protects the getArg(0) below for a direct zero-argument
// call. Reuse the generic builtin arg-count diagnostic (as the sibling
// __builtin_sycl_is_kernel family does) rather than a bespoke one.
Diag(TheCall->getBeginLoc(), diag::err_builtin_invalid_argument_count) << 1;
return ExprError();
}

Expr *KernelNameExpr = TheCall->getArg(0);
SmallVector<Expr *, 8> LaunchArgs(TheCall->arguments().begin() + 1,
TheCall->arguments().end());

// Depend on template parameters: defer until instantiation. When any launch
// argument (or the kernel name) is dependent, Sema::BuildCallExpr has already
// created this builtin call with a dependent result type and skipped custom
// type checking, so we simply return it; this handler re-runs on the
// instantiated, concrete-typed call. The deferred call keeps its BuiltinFn
// placeholder callee; the SYCL_EXT_ONEAPI_KERNEL_FUNCTION macro wraps the
// builtin in a unary plus so the enclosing kernel_function<...> NTTP argument
// is a UnaryOperator (a plain prvalue that does not recurse into this
// dependent CallExpr) rather than the CallExpr itself, which is what keeps
// classification off CallExpr::getCallReturnType and avoids a front-end
// crash.
if (KernelNameExpr->isTypeDependent() || KernelNameExpr->isValueDependent() ||
Expr::hasAnyTypeDependentArguments(LaunchArgs))
return TheCall;

// Build the synthetic call and let overload resolution + deduction run. This
// reuses the real C++ machinery so diagnostics (no viable overload,
// ambiguity, non-deducible template parameter) are emitted at the launch
// site for free.
ExprResult Call = SemaRef.BuildCallExpr(/*Scope=*/nullptr, KernelNameExpr,
KernelNameExpr->getBeginLoc(),
LaunchArgs, TheCall->getRParenLoc());
if (Call.isInvalid())
return ExprError();

auto *ResolvedCall = dyn_cast<CallExpr>(Call.get()->IgnoreImpCasts());
FunctionDecl *ResolvedFn =
ResolvedCall ? ResolvedCall->getDirectCallee() : nullptr;
if (!ResolvedFn)
// Overload resolution / deduction failed and already emitted diagnostics
// (BuildCallExpr can return a recovery expression rather than an invalid
// one), or the name did not resolve to a direct callee. Nothing to add.
return ExprError();

// Free-function-kernel verification is only meaningful during device
// compilation. On the host pass the SYCL driver includes the integration
// footer, whose attribute-less redeclaration of the kernel would make
// isFreeFunction() spuriously fail; the host pass only needs the resolved
// function pointer for the runtime launch. The device pass performs the real
// validation.
if (getLangOpts().SYCLIsDevice) {
if (!isFreeFunction(ResolvedFn)) {
Diag(KernelNameExpr->getBeginLoc(),
diag::err_sycl_launch_kernel_not_free_function);
Diag(ResolvedFn->getLocation(), diag::note_previous_declaration);
return ExprError();
}

// Force instantiation of the specialization so its definition is emitted
// and routed into the free-function-kernel collection/emission path exactly
// as the explicit kernel_function<Func> form does today. The synthetic Call
// above is discarded; no host call to the kernel is emitted.
SemaRef.MarkFunctionReferenced(KernelNameExpr->getBeginLoc(), ResolvedFn);
}

// Evaluate to &ResolvedFn: an ordinary function-pointer constant expression
// (the builtin CallExpr itself is not constant-foldable), usable directly as
// the `auto *Func` non-type template parameter of kernel_function<Func>. The
// emitted SPIR-V kernel is therefore the real user function (no wrapper) and
// the runtime launch path is unchanged.
QualType FnPtrTy = getASTContext().getPointerType(ResolvedFn->getType());
ExprResult FnRef =
SemaRef.BuildDeclRefExpr(ResolvedFn, ResolvedFn->getType(), VK_LValue,
KernelNameExpr->getBeginLoc());
if (FnRef.isInvalid())
return ExprError();
return SemaRef.ImpCastExprToType(FnRef.get(), FnPtrTy,
CK_FunctionToPointerDecay);
}

void SemaSYCL::ProcessFreeFunction(FunctionDecl *FD) {
if (isFreeFunction(FD)) {
if (CheckFreeFunctionDiagnostics(SemaRef, FD))
Expand Down
65 changes: 65 additions & 0 deletions clang/test/SemaSYCL/builtin_sycl_launch_kernel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// RUN: %clang_cc1 -internal-isystem %S/Inputs -fsycl-is-device %s -verify

// Tests __builtin_sycl_launch_kernel: a bare (overloaded / templated) SYCL free
// function kernel name is resolved against the launch argument types via
// ordinary C++ overload resolution / template argument deduction, and the
// builtin evaluates to a pointer to the resolved specialization — usable as the
// `auto *Func` non-type template parameter of the enqueue-function launch path.
// This is the front-end seam the SYCL_EXT_ONEAPI_KERNEL_FUNCTION launch macro is built on.

#include "sycl.hpp"

template <typename T>
__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-nd-range-kernel", 1)]]
void axpy(T *y, const T *x, T a, int n) {
for (int i = 0; i < n; ++i)
y[i] = a * x[i] + y[i];
}

__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-nd-range-kernel", 1)]]
void ovl(int *p) {} // expected-note {{candidate function not viable}}

__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-nd-range-kernel", 1)]]
void ovl(float *p) {} // expected-note {{candidate function not viable}}

// A plain function that is NOT a free-function kernel.
__attribute__((sycl_device))
void not_a_kernel(int *p) {} // expected-note {{previous declaration is here}}

// The builtin evaluates to a pointer to the resolved specialization, usable as
// an `auto *Func` non-type template parameter.
template <auto *Func> struct kernel_function_s {};

void test() {
float *y = nullptr;
const float *x = nullptr;
int *ip = nullptr;
float *fp = nullptr;

// Deduce T = float from the pointer arguments; result folds to '&axpy<float>'
// and is usable as a non-type template argument.
kernel_function_s<__builtin_sycl_launch_kernel(axpy, y, x, 1.0f, 8)> k1;
(void)k1;

// Pick the right overload by argument type.
kernel_function_s<__builtin_sycl_launch_kernel(ovl, ip)> k2;
(void)k2;
kernel_function_s<__builtin_sycl_launch_kernel(ovl, fp)> k3;
(void)k3;

// No viable overload: no ovl(double*).
double *dp = nullptr;
// expected-error@+1 {{no matching function for call to 'ovl'}}
(void)__builtin_sycl_launch_kernel(ovl, dp);

// Resolves to a real function that is not a free-function kernel.
// expected-error@+1 {{is not a SYCL free function kernel}}
(void)__builtin_sycl_launch_kernel(not_a_kernel, ip);

// Missing kernel-name argument.
// expected-error@+1 {{builtin takes one argument}}
(void)__builtin_sycl_launch_kernel();
}
90 changes: 90 additions & 0 deletions clang/test/SemaSYCL/builtin_sycl_launch_kernel_adversarial.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// RUN: %clang_cc1 -internal-isystem %S/Inputs -fsycl-is-device %s -verify

// Adversarial coverage for __builtin_sycl_launch_kernel name resolution: the
// kernel-name operand must survive the full range of C++ ways a function name
// can be spelled, both non-dependent and (via the '+' form the SYCL_EXT_ONEAPI_KERNEL_FUNCTION
// macro emits) inside templates with dependent arguments.
//
// These lock in the behaviors that were only checked manually before:
// - qualified names (ns::ft)
// - explicit template-ids (ns::ft<float>)
// - overloaded names resolved by argument type, including in a dependent
// context
// - mixed template parameters: a leading non-deducible NTTP given explicitly
// (Dim) plus a trailing deducible type parameter (T)
// - zero launch arguments

#include "sycl.hpp"

// expected-no-diagnostics

template <auto *Func> struct kernel_function_s {};
template <auto *Func> constexpr kernel_function_s<Func> kernel_function{};

namespace ns {
template <typename T>
__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-nd-range-kernel", 1)]]
void ft(T *p, int n) {}
} // namespace ns

// Overload set, distinguished by argument type.
__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-nd-range-kernel", 1)]]
void ovl(int *p) {}
__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-nd-range-kernel", 1)]]
void ovl(float *p) {}

// Mixed: Dim is a non-deducible NTTP (appears in no parameter), T is deducible.
template <int Dim, typename T>
__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-nd-range-kernel", 1)]]
void mix(T *p, int n) {}

// Zero-argument kernel.
__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-single-task-kernel", "")]]
void noop() {}

// Non-dependent uses.
void non_dependent() {
float *p = nullptr;
int *ip = nullptr;

// Qualified name.
(void)kernel_function<+__builtin_sycl_launch_kernel(ns::ft, p, 8)>;
// Qualified explicit template-id.
(void)kernel_function<+__builtin_sycl_launch_kernel(ns::ft<float>, p, 8)>;
// Overload resolved by argument type.
(void)kernel_function<+__builtin_sycl_launch_kernel(ovl, ip)>;
(void)kernel_function<+__builtin_sycl_launch_kernel(ovl, p)>;
// Mixed: Dim=2 explicit (leading, non-deducible), T deduced from p.
(void)kernel_function<+__builtin_sycl_launch_kernel(mix<2>, p, 8)>;
// Zero launch arguments.
(void)kernel_function<+__builtin_sycl_launch_kernel(noop)>;
}

// Dependent uses (the '+' form the macro emits): resolution deferred to
// instantiation. These previously risked the front-end classification crash.
template <typename T>
auto dep_qualified(T *p) {
return kernel_function<+__builtin_sycl_launch_kernel(ns::ft, p, 8)>;
}
template <typename T>
auto dep_overload(T *p) {
return kernel_function<+__builtin_sycl_launch_kernel(ovl, p)>;
}
template <int Dim, typename T>
auto dep_mixed(T *p) {
return kernel_function<+__builtin_sycl_launch_kernel(mix<Dim>, p, 8)>;
}

void dependent() {
int *ip = nullptr;
float *fp = nullptr;
(void)dep_qualified(fp); // ns::ft<float>
(void)dep_overload(ip); // ovl(int*)
(void)dep_overload(fp); // ovl(float*)
(void)dep_mixed<3>(fp); // mix<3, float>
}
40 changes: 40 additions & 0 deletions clang/test/SemaSYCL/builtin_sycl_launch_kernel_dependent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// RUN: %clang_cc1 -internal-isystem %S/Inputs -fsycl-is-device %s -verify

// Regression test: __builtin_sycl_launch_kernel used inside a template, with
// dependent launch arguments, must be deferred to instantiation rather than
// resolved eagerly. Using the raw builtin call as the `auto *Func` non-type
// template argument of kernel_function<...> would crash the front end while
// dependent: deducing the auto* parameter runs Expr::Classify on the builtin
// CallExpr, whose callee has the BuiltinFn placeholder type, and
// CallExpr::getCallReturnType asserts trying to castAs<FunctionType>.
//
// The SYCL_EXT_ONEAPI_KERNEL_FUNCTION macro wraps the builtin in a unary plus, so the non-type
// template argument is a UnaryOperator (whose classification is a plain
// prvalue and does not recurse into the dependent CallExpr) instead of the
// CallExpr itself. That is what makes the dependent case well-formed with no
// front-end change; this test spells the same `+` form the macro emits.
// (Unary plus on a function pointer is the identity, so the deduced Func value
// is unchanged.)

#include "sycl.hpp"

template <auto *Func> struct kernel_function_s {};
template <auto *Func> constexpr kernel_function_s<Func> kernel_function{};

template <typename T>
__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-nd-range-kernel", 1)]]
void axpy(T *y, const T *x, T a, int n) {}

// Dependent-argument use inside a function template: deferred to instantiation.
template <typename T>
auto select(T *y, const T *x, T a, int n) {
return kernel_function<+__builtin_sycl_launch_kernel(axpy, y, x, a, n)>;
}

void ok() {
// expected-no-diagnostics
float *y = nullptr;
const float *x = nullptr;
(void)select(y, x, 1.0f, 8); // instantiates select<float>, resolves axpy<float>
}
Loading