Skip to content

[SYCL] Add support for free function kernel builtins #15938

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 11, 2024
Merged
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
1 change: 1 addition & 0 deletions clang/include/clang/Basic/Builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ enum LanguageID : uint16_t {
OCL_DSE = 0x400, // builtin requires OpenCL device side enqueue.
ALL_OCL_LANGUAGES = 0x800, // builtin for OCL languages.
HLSL_LANG = 0x1000, // builtin requires HLSL.
SYCL_LANG = 0x2000, // builtin requires SYCL.
ALL_LANGUAGES = C_LANG | CXX_LANG | OBJC_LANG, // builtin for all languages.
ALL_GNU_LANGUAGES = ALL_LANGUAGES | GNU_LANG, // builtin requires GNU mode.
ALL_MS_LANGUAGES = ALL_LANGUAGES | MS_LANG // builtin requires MS mode.
Expand Down
19 changes: 19 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -4731,6 +4731,25 @@ def GetDeviceSideMangledName : LangBuiltin<"CUDA_LANG"> {
let Prototype = "char const*(...)";
}

// SYCL
def SYCLIsKernel : LangBuiltin<"SYCL_LANG"> {
let Spellings = ["__builtin_sycl_is_kernel"];
let Attributes = [NoThrow, Const, Constexpr, CustomTypeChecking];
let Prototype = "bool(...)";
}

def SYCLIsSingleTaskKernel : LangBuiltin<"SYCL_LANG"> {
let Spellings = ["__builtin_sycl_is_single_task_kernel"];
let Attributes = [NoThrow, Const, Constexpr, CustomTypeChecking];
let Prototype = "bool(...)";
}

def SYCLIsNDRangeKernel : LangBuiltin<"SYCL_LANG"> {
let Spellings = ["__builtin_sycl_is_nd_range_kernel"];
let Attributes = [NoThrow, Const, Constexpr, CustomTypeChecking];
let Prototype = "bool(...)";
}

// HLSL
def HLSLAll : LangBuiltin<"HLSL_LANG"> {
let Spellings = ["__builtin_hlsl_all"];
Expand Down
9 changes: 8 additions & 1 deletion clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,9 @@ def warn_unreachable_association : Warning<
InGroup<UnreachableCodeGenericAssoc>;

/// Built-in functions.
def err_builtin_invalid_argument_count : Error<
"builtin %plural{0:takes no arguments|1:takes one argument|"
":requires exactly %0 arguments}0">;
def ext_implicit_lib_function_decl : ExtWarn<
"implicitly declaring library function '%0' with type %1">,
InGroup<ImplicitFunctionDeclare>;
Expand Down Expand Up @@ -12388,7 +12391,11 @@ def err_builtin_invalid_arg_type: Error <
"a vector of integers|"
"an unsigned integer|"
"an 'int'|"
"a vector of floating points}1 (was %2)">;
"a vector of floating points|"
"a function pointer}1 (was %2)">;

def err_builtin_invalid_arg_value: Error<
"%ordinal0 argument must be a strictly positive value (was %1)">;

def err_builtin_matrix_disabled: Error<
"matrix types extension is disabled. Pass -fenable-matrix to enable it">;
Expand Down
52 changes: 52 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12726,6 +12726,45 @@ static bool getBuiltinAlignArguments(const CallExpr *E, EvalInfo &Info,
return true;
}

static bool isSYCLFreeFunctionKernel(IntExprEvaluator &IEV,
const EvalInfo &Info, const CallExpr *E,
StringRef NameStr1, StringRef NameStr2,
bool CheckNDRangeKernelDim = false) {
const Expr *ArgExpr = E->getArg(0)->IgnoreParenImpCasts();
while (isa<CastExpr>(ArgExpr))
ArgExpr = cast<CastExpr>(ArgExpr)->getSubExpr();
auto *DRE = dyn_cast<DeclRefExpr>(ArgExpr);
if (DRE) {
const FunctionDecl *FD = dyn_cast<FunctionDecl>(DRE->getDecl());
if (FD) {
auto *SAIRAttr = FD->getAttr<SYCLAddIRAttributesFunctionAttr>();
if (!SAIRAttr)
return IEV.Success(false, E);
SmallVector<std::pair<std::string, std::string>, 4> NameValuePairs =
SAIRAttr->getFilteredAttributeNameValuePairs(Info.Ctx);
for (const auto &NVPair : NameValuePairs) {
if (!NVPair.first.compare(NameStr1) ||
(!NameStr2.empty() && !NVPair.first.compare(NameStr2))) {
if (CheckNDRangeKernelDim) {
uint64_t Dim =
E->getArg(1)->EvaluateKnownConstInt(Info.Ctx).getZExtValue();
// Return true only if the dimensions match.
if (std::stoul(NVPair.second) == Dim)
return IEV.Success(true, E);
else
return IEV.Success(false, E);
}
// Return true if it has the sycl-single-task-kernel or the
// sycl-nd-range-kernel attribute.
return IEV.Success(true, E);
}
}
return IEV.Success(false, E);
}
}
return false;
}

bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
unsigned BuiltinOp) {
switch (BuiltinOp) {
Expand Down Expand Up @@ -13671,6 +13710,19 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
Result.setBitVal(P++, Val[I]);
return Success(Result, E);
}

case Builtin::BI__builtin_sycl_is_kernel: {
return isSYCLFreeFunctionKernel(*this, Info, E, "sycl-single-task-kernel",
"sycl-nd-range-kernel");
}
case Builtin::BI__builtin_sycl_is_single_task_kernel: {
return isSYCLFreeFunctionKernel(*this, Info, E, "sycl-single-task-kernel",
"");
}
case Builtin::BI__builtin_sycl_is_nd_range_kernel: {
return isSYCLFreeFunctionKernel(*this, Info, E, "sycl-nd-range-kernel", "",
/*CheckNDRangeDim=*/true);
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Basic/Builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ static bool builtinIsSupported(const Builtin::Info &BuiltinInfo,
/* CUDA Unsupported */
if (!LangOpts.CUDA && BuiltinInfo.Langs == CUDA_LANG)
return false;
/* SYCL Unsupported */
if (!LangOpts.isSYCL() && BuiltinInfo.Langs == SYCL_LANG)
return false;
/* CPlusPlus Unsupported */
if (!LangOpts.CPlusPlus && BuiltinInfo.Langs == CXX_LANG)
return false;
Expand Down
59 changes: 59 additions & 0 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2642,6 +2642,53 @@ static RValue EmitHipStdParUnsupportedBuiltin(CodeGenFunction *CGF,
return RValue::get(CGF->Builder.CreateCall(UBF, Args));
}

static RValue EmitSYCLFreeFunctionKernelBuiltin(CodeGenFunction &CGF,
const CallExpr *E,
StringRef NameStr1,
StringRef NameStr2,
bool CheckNDRangeDim = false) {
const Expr *ArgExpr = E->getArg(0)->IgnoreImpCasts();
auto *UO = dyn_cast<clang::UnaryOperator>(ArgExpr);
// If this is of the form &function or *function, get to the function
// sub-expression.
if (UO && (UO->getOpcode() == UO_AddrOf || UO->getOpcode() == UO_Deref))
ArgExpr = UO->getSubExpr()->IgnoreParenImpCasts();
while (isa<CastExpr>(ArgExpr))
ArgExpr = cast<CastExpr>(ArgExpr)->getSubExpr();
auto *DRE = dyn_cast<DeclRefExpr>(ArgExpr);
if (DRE) {
const FunctionDecl *FD = dyn_cast<FunctionDecl>(DRE->getDecl());
if (FD && FD->hasAttr<SYCLAddIRAttributesFunctionAttr>()) {
auto *SAIRAttr = FD->getAttr<SYCLAddIRAttributesFunctionAttr>();
SmallVector<std::pair<std::string, std::string>, 4> NameValuePairs =
SAIRAttr->getFilteredAttributeNameValuePairs(CGF.CGM.getContext());
for (const auto &NVPair : NameValuePairs) {
if (!NVPair.first.compare(NameStr1) ||
(!NameStr2.empty() && !!NVPair.first.compare(NameStr2))) {
if (CheckNDRangeDim) {
uint64_t Dim = E->getArg(1)
->EvaluateKnownConstInt(CGF.CGM.getContext())
.getZExtValue();
// Return true only if the dimensions match.
if (std::stoul(NVPair.second) == Dim)
return RValue::get(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you explain what passing the type to the getTrue( ) API does here? Is it possible to just return 0 or 1 if you are converting to ConstantInt anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not that familiar with this - but passing the type makes the function return a true value in the context of the type. There is another interface for getTrue, but it takes the LLVM context as its argument and returns a true value in that context. Passing the type made more sense and that is what other builtins seem to do too.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for explaining

llvm::ConstantInt::getTrue(CGF.ConvertType(E->getType())));
else
return RValue::get(
llvm::ConstantInt::getFalse(CGF.ConvertType(E->getType())));
}
// Return true if the kernel type matches.
return RValue::get(
llvm::ConstantInt::getTrue(CGF.ConvertType(E->getType())));
}
}
}
}
// Return false otherwise.
return RValue::get(
llvm::ConstantInt::getFalse(CGF.ConvertType(E->getType())));
}

RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
const CallExpr *E,
ReturnValueSlot ReturnValue) {
Expand Down Expand Up @@ -6282,6 +6329,18 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
auto Str = CGM.GetAddrOfConstantCString(Name, "");
return RValue::get(Str.getPointer());
}
case Builtin::BI__builtin_sycl_is_kernel: {
return EmitSYCLFreeFunctionKernelBuiltin(
*this, E, "sycl-single-task-kernel", "sycl-nd-range-kernel");
}
case Builtin::BI__builtin_sycl_is_single_task_kernel: {
return EmitSYCLFreeFunctionKernelBuiltin(*this, E,
"sycl-single-task-kernel", "");
}
case Builtin::BI__builtin_sycl_is_nd_range_kernel: {
return EmitSYCLFreeFunctionKernelBuiltin(*this, E, "sycl-nd-range-kernel",
"", /*CheckNDRangeDim=*/true);
}
}

// If this is an alias for a lib function (e.g. __builtin_sin), emit
Expand Down
34 changes: 34 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3024,6 +3024,40 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
}
break;
}

case Builtin::BI__builtin_sycl_is_kernel:
case Builtin::BI__builtin_sycl_is_single_task_kernel:
case Builtin::BI__builtin_sycl_is_nd_range_kernel: {
unsigned int ExpNumArgs =
BuiltinID == Builtin::BI__builtin_sycl_is_nd_range_kernel ? 2 : 1;
// Builtin takes either 1 or 2 arguments.
if (TheCall->getNumArgs() != ExpNumArgs) {
Diag(TheCall->getBeginLoc(), diag::err_builtin_invalid_argument_count)
<< ExpNumArgs;
return ExprError();
}

const Expr *Arg = TheCall->getArg(0);
QualType ArgTy = Arg->getType();

if (!ArgTy->isFunctionProtoType() && !ArgTy->isFunctionPointerType()) {
Diag(Arg->getBeginLoc(), diag::err_builtin_invalid_arg_type)
<< 1 << /* pointer to function type */ 10 << ArgTy;
return ExprError();
}

if (ExpNumArgs == 2) {
int64_t DimArg =
TheCall->getArg(1)->EvaluateKnownConstInt(Context).getSExtValue();
if (DimArg <= 0) {
Diag(Arg->getBeginLoc(), diag::err_builtin_invalid_arg_value)
<< 2 << DimArg;
return ExprError();
}
}

break;
}
}

if (getLangOpts().HLSL && HLSL().CheckBuiltinFunctionCall(BuiltinID, TheCall))
Expand Down
79 changes: 79 additions & 0 deletions clang/test/CodeGenSYCL/builtin_sycl_kernel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// RUN: %clang_cc1 -internal-isystem %S/Inputs -fsycl-is-device -triple spir64-unknown-unknown -disable-llvm-passes -emit-llvm %s -o - | FileCheck %s

// This test tests the builtin __builtin_sycl_is_kernel

#include "sycl.hpp"

using namespace sycl;
queue q;

__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-single-task-kernel", 0)]]
void sstk_free_func() {
}

template <typename T>
[[__sycl_detail__::add_ir_attributes_function("sycl-single-task-kernel", 4)]]
void sstk_free_func_tmpl(T *ptr) {
for (int i = 0; i <= 7; i++)
ptr[i] = i + 11;
}

__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-single-task-kernel", 1)]]
void ovl_free_func(int *ptr) {
for (int i = 0; i <= 7; i++)
ptr[i] = i;
}

__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-nd-range-kernel", 1)]]
void ovl_free_func(int *ptr, int val) {
for (int i = 0; i <= 7; i++)
ptr[i] = i + val;
}

__attribute__((sycl_device))
[[__sycl_detail__::add_ir_attributes_function("sycl-nd-range-kernel", 4)]]
void sndrk_free_func() {
}

void func() {}

void foo() {
bool b1 = __builtin_sycl_is_kernel(sstk_free_func);
// CHECK: store i8 1, ptr addrspace(4) %b1{{.*}}, align 1
bool b2 = __builtin_sycl_is_kernel(*sstk_free_func);
// CHECK: store i8 1, ptr addrspace(4) %b2{{.*}}, align 1
bool b3 = __builtin_sycl_is_kernel(&sstk_free_func);
// CHECK: store i8 1, ptr addrspace(4) %b3{{.*}}, align 1
bool b4 = __builtin_sycl_is_kernel(func);
// CHECK: store i8 0, ptr addrspace(4) %b4{{.*}}, align 1
bool b5 = __builtin_sycl_is_kernel(sndrk_free_func);
// CHECK: store i8 1, ptr addrspace(4) %b5{{.*}}, align 1

// Constexpr forms of the valid cases.
constexpr bool b6 = __builtin_sycl_is_kernel(sstk_free_func); // Okay and true.
// CHECK: store i8 1, ptr addrspace(4) %b6{{.*}}, align 1
constexpr bool b7 = __builtin_sycl_is_kernel(func); // Okay, but false.
// CHECK: store i8 0, ptr addrspace(4) %b7{{.*}}, align 1
constexpr bool b8 = __builtin_sycl_is_kernel(sndrk_free_func); // Okay, but false.
// CHECK: store i8 1, ptr addrspace(4) %b8{{.*}}, align 1

// Test function template.
constexpr bool b9 = __builtin_sycl_is_kernel((void(*)(int *))sstk_free_func_tmpl<int>); // Okay
// CHECK: store i8 1, ptr addrspace(4) %b9{{.*}}, align 1

// Test overloaded functions.
constexpr bool b10 = __builtin_sycl_is_kernel((void(*)(int *))ovl_free_func); // Okay
// CHECK: store i8 1, ptr addrspace(4) %b10{{.*}}, align 1
constexpr bool b11 = __builtin_sycl_is_kernel((void(*)(int *, int))ovl_free_func); // Okay
// CHECK: store i8 1, ptr addrspace(4) %b11{{.*}}, align 1
}

void f() {
auto L = []() { foo(); };
q.submit([&](handler &h) {
h.single_task<class kernel_name_1>(L);
});
}
Loading
Loading