https://github.com/koparasy updated https://github.com/llvm/llvm-project/pull/213771
>From cd92614a8023553deed153e9bcd43c5256ac0bf6 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Mon, 3 Aug 2026 14:33:04 -0700 Subject: [PATCH 1/4] [CIR][SYCL] Emit SYCL kernel caller offload entry point in device codegen --- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 9 +- clang/lib/CIR/CodeGen/CIRGenFunction.h | 8 ++ clang/lib/CIR/CodeGen/CIRGenModule.cpp | 10 +- clang/lib/CIR/CodeGen/CIRGenModule.h | 5 + clang/lib/CIR/CodeGen/CIRGenSYCL.cpp | 130 ++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenTypes.cpp | 13 ++ clang/lib/CIR/CodeGen/CIRGenTypes.h | 7 + clang/lib/CIR/CodeGen/TargetInfo.h | 6 + clang/lib/CIR/CodeGen/Targets/SPIRV.cpp | 4 + .../CodeGenSYCL/kernel-caller-entry-point.cpp | 60 ++++++-- 10 files changed, 238 insertions(+), 14 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 66e7b6d5061df..5c476ebe668da 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -369,14 +369,17 @@ void CIRGenFunction::LexicalScope::emitImplicitReturn() { CIRGenBuilderTy &builder = cgf.getBuilder(); LexicalScope *localScope = cgf.curLexScope; - const auto *fd = cast<clang::FunctionDecl>(cgf.curGD.getDecl()); + // Synthesized functions (e.g. SYCL kernel caller offload entry points) are + // emitted without an associated FunctionDecl. They return void, so the + // non-void C++ flow-off-the-end handling below does not apply to them. + const auto *fd = dyn_cast_or_null<clang::FunctionDecl>(cgf.curGD.getDecl()); // In C++, flowing off the end of a non-void function is always undefined // behavior. In C, flowing off the end of a non-void function is undefined // behavior only if the non-existent return value is used by the caller. // That influences whether the terminating op is trap, unreachable, or // return. - if (cgf.getLangOpts().CPlusPlus && !fd->hasImplicitReturnZero() && + if (fd && cgf.getLangOpts().CPlusPlus && !fd->hasImplicitReturnZero() && !cgf.sawAsmBlock && !fd->getReturnType()->isVoidType() && builder.getInsertionBlock() && !previousOpIsNonYieldingCleanup(builder.getInsertionBlock())) { @@ -651,7 +654,7 @@ mlir::LogicalResult CIRGenFunction::emitFunctionBody(const clang::Stmt *body) { return emitStmt(body, /*useCurrentScope=*/true); } -static void eraseEmptyAndUnusedBlocks(cir::FuncOp func) { +void CIRGenFunction::eraseEmptyAndUnusedBlocks(cir::FuncOp func) { // Remove any leftover blocks that are unreachable and empty, since they do // not represent unreachable code useful for warnings nor anything deemed // useful in general. diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 9f8454309f13a..071b28fc33282 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -49,6 +49,7 @@ class LoopOp; } // namespace mlir namespace clang { +class OutlinedFunctionDecl; class SYCLKernelCallStmt; } // namespace clang @@ -2307,6 +2308,13 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::LogicalResult emitSYCLKernelCallStmt(const SYCLKernelCallStmt &s); + void emitSYCLKernelCaller(const clang::OutlinedFunctionDecl *outlinedFnDecl, + cir::FuncOp funcOp, cir::FuncType funcType, + FunctionArgList &args); + + /// Remove leftover empty and unreachable blocks from an emitted function. + static void eraseEmptyAndUnusedBlocks(cir::FuncOp func); + std::optional<mlir::Value> emitTargetBuiltinExpr(unsigned builtinID, const clang::CallExpr *e, ReturnValueSlot &returnValue); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index ae9cad0b7c30f..4c47b9eb8be3d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -441,9 +441,13 @@ void CIRGenModule::emitDeferred() { fd->isDefined()) { // Functions with an invalid sycl_kernel_entry_point attribute are // ignored during device compilation. - if (!fd->getAttr<SYCLKernelEntryPointAttr>()->isInvalidAttr()) - errorNYI(fd->getSourceRange(), - "SYCL kernel caller offload entry point"); + if (!fd->getAttr<SYCLKernelEntryPointAttr>()->isInvalidAttr()) { + // Generate and emit the SYCL kernel caller function. + emitSYCLKernelCaller(fd, getASTContext()); + // Recurse to emit any symbols directly or indirectly referenced + // by the SYCL kernel caller function. + emitDeferred(); + } // Do not emit the sycl_kernel_entry_point attributed function. continue; } diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index ba7b20b91cc03..24648605d8719 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -654,6 +654,11 @@ class CIRGenModule : public CIRGenTypeCache { void emitGlobalDefinition(clang::GlobalDecl gd, mlir::Operation *op = nullptr); void emitGlobalFunctionDefinition(clang::GlobalDecl gd, mlir::Operation *op); + + /// Emit the SYCL kernel caller offload entry point function generated for a + /// function declared with the sycl_kernel_entry_point attribute. + void emitSYCLKernelCaller(const clang::FunctionDecl *kernelEntryPointFn, + clang::ASTContext &ctx); void emitGlobalVarDefinition(const clang::VarDecl *vd, bool isTentative = false); diff --git a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp index 9308b1fe4189f..ba694e086ff41 100644 --- a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp @@ -11,8 +11,13 @@ //===----------------------------------------------------------------------===// #include "CIRGenFunction.h" +#include "CIRGenModule.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" #include "clang/AST/StmtSYCL.h" +#include "clang/AST/SYCLKernelInfo.h" using namespace clang; using namespace clang::CIRGen; @@ -33,3 +38,128 @@ CIRGenFunction::emitSYCLKernelCallStmt(const SYCLKernelCallStmt &s) { // of the original function body. return emitStmt(s.getKernelLaunchStmt(), /*useCurrentScope=*/true); } + +// Emit the body of a SYCL kernel caller offload entry point function. The body +// is the transformed body held by the OutlinedFunctionDecl associated with the +// sycl_kernel_entry_point attributed function. This mirrors the tail of +// CIRGenFunction::generateCode, but is driven by an OutlinedFunctionDecl and an +// explicit argument list rather than a FunctionDecl. +void CIRGenFunction::emitSYCLKernelCaller( + const OutlinedFunctionDecl *outlinedFnDecl, cir::FuncOp funcOp, + cir::FuncType funcType, FunctionArgList &args) { + const Stmt *body = outlinedFnDecl->getBody(); + SourceLocation loc = outlinedFnDecl->getLocation(); + SourceRange bodyRange = body->getSourceRange(); + + // The offload entry point is synthesized and has no FunctionDecl of its own. + // As in classic CodeGen's EmitSYCLKernelCaller, it is emitted with an empty + // GlobalDecl: it is a free function (never an implicit-object member) and + // must not run a C++ instance-function prologue. LexicalScope's implicit + // return handles a null curGD (the entry point returns void). + curGD = GlobalDecl(); + + // Establish a source location for the function so that the prologue can + // inherit one (see CIRGenFunction::getLoc / currSrcLoc). + SourceLocRAIIObject fnLoc{*this, loc.isValid() ? getLoc(loc) + : builder.getUnknownLoc()}; + + mlir::Location fusedLoc = getLoc(bodyRange); + mlir::Block *entryBB = funcOp.addEntryBlock(); + + SymTableScopeTy varScope(symbolTable); + { + LexicalScope lexScope(*this, fusedLoc, entryBB); + startFunction(GlobalDecl(), getContext().VoidTy, funcOp, funcType, args, + loc, bodyRange.getBegin()); + if (mlir::failed(emitFunctionBody(body))) + return; + if (mlir::failed(funcOp.verifyBody())) + return; + finishFunction(body->getEndLoc()); + } + + // Mirror the tail of generateCode: drop leftover empty/unreachable blocks the + // lexical-scope machinery may have created. + eraseEmptyAndUnusedBlocks(funcOp); +} + +void CIRGenModule::emitSYCLKernelCaller(const FunctionDecl *kernelEntryPointFn, + ASTContext &ctx) { + assert(ctx.getLangOpts().SYCLIsDevice && + "SYCL kernel caller offload entry point functions can only be emitted" + " during device compilation"); + + const auto *kernelEntryPointAttr = + kernelEntryPointFn->getAttr<SYCLKernelEntryPointAttr>(); + assert(kernelEntryPointAttr && "Missing sycl_kernel_entry_point attribute"); + assert(!kernelEntryPointAttr->isInvalidAttr() && + "sycl_kernel_entry_point attribute is invalid"); + + // Find the SYCLKernelCallStmt. + SYCLKernelCallStmt *kernelCallStmt = + cast<SYCLKernelCallStmt>(kernelEntryPointFn->getBody()); + + // Retrieve the SYCL kernel caller parameters from the OutlinedFunctionDecl. + FunctionArgList args; + const OutlinedFunctionDecl *outlinedFnDecl = + kernelCallStmt->getOutlinedFunctionDecl(); + args.append(outlinedFnDecl->param_begin(), outlinedFnDecl->param_end()); + + // Compute the function info and CIR function type. + const CIRGenFunctionInfo &fnInfo = + getTypes().arrangeSYCLKernelCallerDeclaration(ctx.VoidTy, args); + cir::FuncType funcType = getTypes().getFunctionType(fnInfo); + + // Retrieve the generated name for the SYCL kernel caller function. + CanQualType kernelNameType = + ctx.getCanonicalType(kernelEntryPointAttr->getKernelName()); + const SYCLKernelInfo &kernelInfo = ctx.getSYCLKernelInfo(kernelNameType); + + // Create the SYCL kernel caller function. Unlike an ordinary function, this + // offload entry point is synthesized from the OutlinedFunctionDecl held by + // the SYCLKernelCallStmt rather than emitted from a FunctionDecl, so it is + // constructed with an empty GlobalDecl. + cir::FuncOp funcOp = getOrCreateCIRFunction( + kernelInfo.GetKernelName(), funcType, GlobalDecl(), /*forVTable=*/false, + /*dontDefer=*/true, /*isThunk=*/false, ForDefinition); + + // The kernel caller offload entry point has external linkage. Classic + // CodeGen creates it with ExternalLinkage explicitly (EmitSYCLKernelCaller); + // createCIRFunction already applies ExternalLinkage by default, so set it + // explicitly here to make the contract clear rather than rely on the default. + funcOp.setLinkage(cir::GlobalLinkageKind::ExternalLinkage); + + // Set the device kernel calling convention so the entry point is emitted as + // a kernel (e.g. spir_kernel) rather than an ordinary device function. + // Classic CodeGen derives this from the CC_DeviceKernel function info via + // SetLLVMFunctionAttributes; CIR does not yet route the function-info calling + // convention onto the FuncOp (opFuncCallingConv), so set it directly from the + // target hook, matching how CIRGen sets kernel calling conventions elsewhere. + funcOp.setCallingConv(getTargetCIRGenInfo().getDeviceKernelCallingConv()); + + // TODO: The following attributes applied by classic CodeGen's + // EmitSYCLKernelCaller are not yet applied in CIR: + // - SetSYCLKernelAttributes: norecurse and mustprogress. + // - addSYCLModuleIdAttr: the "sycl-module-id" attribute. + // - setDSOLocal. + assert(!cir::MissingFeatures::setLLVMFunctionFEnvAttributes()); + assert(!cir::MissingFeatures::setDSOLocal()); + + // Emit the SYCL kernel caller function. + CIRGenFunction cgf(*this, builder); + curCGF = &cgf; + { + mlir::OpBuilder::InsertionGuard guard(builder); + cgf.emitSYCLKernelCaller(outlinedFnDecl, funcOp, funcType, args); + } + curCGF = nullptr; + + setNonAliasAttributes(GlobalDecl(), funcOp); + // The SYCL kernel caller is synthesized from an OutlinedFunctionDecl rather + // than a FunctionDecl. Classic CodeGen passes the OutlinedFunctionDecl to + // SetLLVMFunctionAttributesForDefinition, but CIR's setter takes a + // FunctionDecl; passing nullptr here skips OutlinedFunctionDecl-derived + // attributes (e.g. inline hints), which are not yet handled. + assert(!cir::MissingFeatures::opFuncExtraAttrs()); + setCIRFunctionAttributesForDefinition(/*fd=*/nullptr, funcOp); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp index e5af4eec7720f..34f5a2993101d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp @@ -1,6 +1,7 @@ #include "CIRGenTypes.h" #include "CIRGenCXXABI.h" +#include "CIRGenCall.h" #include "CIRGenFunctionInfo.h" #include "CIRGenModule.h" #include "mlir/IR/BuiltinTypes.h" @@ -783,6 +784,18 @@ const CIRGenFunctionInfo &CIRGenTypes::arrangeCIRFunctionInfo( return *fi; } +const CIRGenFunctionInfo &CIRGenTypes::arrangeSYCLKernelCallerDeclaration( + QualType resultType, const FunctionArgList &args) { + SmallVector<CanQualType, 16> argTypes; + for (const VarDecl *arg : args) + argTypes.push_back(astContext.getCanonicalParamType(arg->getType())); + + assert(!cir::MissingFeatures::opCallFnInfoOpts()); + return arrangeCIRFunctionInfo( + resultType->getCanonicalTypeUnqualified(), /*isInstanceMethod=*/false, + argTypes, FunctionType::ExtInfo(CC_DeviceKernel), RequiredArgs::All); +} + const CIRGenFunctionInfo &CIRGenTypes::arrangeGlobalDeclaration(GlobalDecl gd) { assert(!dyn_cast<ObjCMethodDecl>(gd.getDecl()) && "This is reported as a FIXME in LLVM codegen"); diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h index a7827f76bd5f2..bc2a1d599cd8a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenTypes.h +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h @@ -44,6 +44,7 @@ class CallArgList; class CIRGenBuilderTy; class CIRGenCXXABI; class CIRGenModule; +class FunctionArgList; /// This class organizes the cross-module state that is used while lowering /// AST types to CIR types. @@ -186,6 +187,12 @@ class CIRGenTypes { const CIRGenFunctionInfo & arrangeFunctionDeclaration(const clang::FunctionDecl *fd); + /// Arrange the function info for the offload kernel entry point function + /// generated for a SYCL kernel caller function. + const CIRGenFunctionInfo & + arrangeSYCLKernelCallerDeclaration(clang::QualType resultType, + const FunctionArgList &args); + /// A builtin function is a freestanding function using the default /// C conventions. const CIRGenFunctionInfo &arrangeBuiltinFunctionCall(QualType resultType, diff --git a/clang/lib/CIR/CodeGen/TargetInfo.h b/clang/lib/CIR/CodeGen/TargetInfo.h index e720a4ad2ec5c..39fd3daf4b3db 100644 --- a/clang/lib/CIR/CodeGen/TargetInfo.h +++ b/clang/lib/CIR/CodeGen/TargetInfo.h @@ -141,6 +141,12 @@ class TargetCIRGenInfo { mlir::Operation *global, CIRGenModule &module) const {} + /// Get the CIR calling convention to use for a device kernel entry point + /// (e.g. an OpenCL/SYCL or CUDA/HIP kernel) on this target. + virtual cir::CallingConv getDeviceKernelCallingConv() const { + return cir::CallingConv::C; + } + virtual bool isScalarizableAsmOperand(CIRGenFunction &cgf, mlir::Type ty) const { return false; diff --git a/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp b/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp index 643c635128d09..f2d9810b36061 100644 --- a/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp +++ b/clang/lib/CIR/CodeGen/Targets/SPIRV.cpp @@ -48,6 +48,10 @@ class SPIRVTargetCIRGenInfo : public TargetCIRGenInfo { func.setCallingConv(cir::CallingConv::SpirKernel); } } + + cir::CallingConv getDeviceKernelCallingConv() const override { + return cir::CallingConv::SpirKernel; + } }; } // namespace diff --git a/clang/test/CIR/CodeGenSYCL/kernel-caller-entry-point.cpp b/clang/test/CIR/CodeGenSYCL/kernel-caller-entry-point.cpp index c6522064f2fdc..d6d8006d09cc8 100644 --- a/clang/test/CIR/CodeGenSYCL/kernel-caller-entry-point.cpp +++ b/clang/test/CIR/CodeGenSYCL/kernel-caller-entry-point.cpp @@ -1,10 +1,15 @@ -// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spir64-unknown-unknown \ -// RUN: -fclangir -emit-cir -verify %s +// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spirv64-unknown-unknown -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR +// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spirv64-unknown-unknown -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM +// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spirv64-unknown-unknown -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG -// During device compilation, a SYCL kernel caller offload entry point is -// emitted in place of each sycl_kernel_entry_point attributed function. That -// lowering is not yet implemented in CIR, so it must be reported as a clean -// "Not Yet Implemented" diagnostic rather than crashing. +// During device compilation, an offload kernel caller entry point is emitted +// in place of each sycl_kernel_entry_point attributed function. The entry +// point is named after the kernel name type and its body is the transformed +// body held by the OutlinedFunctionDecl (which invokes the kernel functor). +// The sycl_kernel_entry_point attributed function itself is not emitted. // Required by sycl_kernel_entry_point semantics. template <typename KernelName, typename... Ts> @@ -12,12 +17,51 @@ void sycl_kernel_launch(const char *, Ts...) {} template <typename KernelName, typename KernelType> [[clang::sycl_kernel_entry_point(KernelName)]] -// expected-error@+1 {{ClangIR code gen Not Yet Implemented: SYCL kernel caller offload entry point}} void kernel_single_task(KernelType kf) { kf(); } struct KN; +struct MemberKN; struct K { void operator()() const {} }; -void test() { kernel_single_task<KN>(K{}); } +// A sycl_kernel_entry_point function may also be a non-static member function +// (Sema only rejects explicit-object members, ctors and dtors). The offload +// entry point is still a free function and must not run an instance-function +// prologue. +struct Invoker { + template <typename KernelName, typename KernelType> + [[clang::sycl_kernel_entry_point(KernelName)]] + void kernel_single_task(KernelType kf) { kf(); } +}; + +void test() { + kernel_single_task<KN>(K{}); + Invoker{}.kernel_single_task<MemberKN>(K{}); +} + +// The kernel caller entry point is named after the kernel name type (KN), is +// emitted with the spir_kernel calling convention, and its body calls the +// kernel functor's operator(). The sycl_kernel_entry_point function and its +// launch call are not emitted during device compilation. +// CIR-LABEL: cir.func {{.*}}@_ZTS2KN{{.*}}cc(spir_kernel) +// CIR: cir.call @_ZNK1KclEv +// CIR: cir.return +// CIR-NOT: cir.func {{.*}}@_Z18kernel_single_task +// CIR-NOT: cir.call {{.*}}@_Z17sycl_kernel_launch + +// The member-function entry point is emitted the same way, as a free function +// (no implicit `this` parameter). +// CIR-LABEL: cir.func {{.*}}@_ZTS8MemberKN{{.*}}cc(spir_kernel) +// CIR: cir.call @_ZNK1KclEv +// CIR: cir.return + +// LLVM-LABEL: define {{.*}}spir_kernel void @_ZTS2KN +// LLVM: call {{.*}}void @_ZNK1KclEv +// LLVM: ret void +// LLVM-NOT: define {{.*}}@_Z18kernel_single_task + +// OGCG-LABEL: define {{.*}}spir_kernel void @_ZTS2KN +// OGCG: call {{.*}}spir_func void @_ZNK1KclEv +// OGCG: ret void +// OGCG-NOT: define {{.*}}@_Z18kernel_single_task >From e9daf2b5bce98b06f5437833881eb273b729ac3d Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Wed, 5 Aug 2026 18:32:29 -0700 Subject: [PATCH 2/4] Align with classic codegen, concise comments --- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 5 +-- clang/lib/CIR/CodeGen/CIRGenSYCL.cpp | 55 +++++++----------------- clang/lib/CIR/CodeGen/CIRGenTypes.cpp | 2 +- clang/lib/CIR/CodeGen/CIRGenTypes.h | 8 ++-- 4 files changed, 22 insertions(+), 48 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 5c476ebe668da..0578931fe9419 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -369,9 +369,8 @@ void CIRGenFunction::LexicalScope::emitImplicitReturn() { CIRGenBuilderTy &builder = cgf.getBuilder(); LexicalScope *localScope = cgf.curLexScope; - // Synthesized functions (e.g. SYCL kernel caller offload entry points) are - // emitted without an associated FunctionDecl. They return void, so the - // non-void C++ flow-off-the-end handling below does not apply to them. + // Synthesized functions (e.g. SYCL kernel caller entry points) have no + // FunctionDecl; the non-void flow-off-the-end handling below is guarded on fd. const auto *fd = dyn_cast_or_null<clang::FunctionDecl>(cgf.curGD.getDecl()); // In C++, flowing off the end of a non-void function is always undefined diff --git a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp index ba694e086ff41..e474f1c42b3a2 100644 --- a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp @@ -39,11 +39,9 @@ CIRGenFunction::emitSYCLKernelCallStmt(const SYCLKernelCallStmt &s) { return emitStmt(s.getKernelLaunchStmt(), /*useCurrentScope=*/true); } -// Emit the body of a SYCL kernel caller offload entry point function. The body -// is the transformed body held by the OutlinedFunctionDecl associated with the -// sycl_kernel_entry_point attributed function. This mirrors the tail of -// CIRGenFunction::generateCode, but is driven by an OutlinedFunctionDecl and an -// explicit argument list rather than a FunctionDecl. +// Emit the body of a SYCL kernel caller offload entry point. Mirrors the tail +// of generateCode, but is driven by an OutlinedFunctionDecl and an explicit +// argument list rather than a FunctionDecl. void CIRGenFunction::emitSYCLKernelCaller( const OutlinedFunctionDecl *outlinedFnDecl, cir::FuncOp funcOp, cir::FuncType funcType, FunctionArgList &args) { @@ -51,15 +49,9 @@ void CIRGenFunction::emitSYCLKernelCaller( SourceLocation loc = outlinedFnDecl->getLocation(); SourceRange bodyRange = body->getSourceRange(); - // The offload entry point is synthesized and has no FunctionDecl of its own. - // As in classic CodeGen's EmitSYCLKernelCaller, it is emitted with an empty - // GlobalDecl: it is a free function (never an implicit-object member) and - // must not run a C++ instance-function prologue. LexicalScope's implicit - // return handles a null curGD (the entry point returns void). + // Synthesized entry point: no FunctionDecl, emitted with an empty GlobalDecl. curGD = GlobalDecl(); - // Establish a source location for the function so that the prologue can - // inherit one (see CIRGenFunction::getLoc / currSrcLoc). SourceLocRAIIObject fnLoc{*this, loc.isValid() ? getLoc(loc) : builder.getUnknownLoc()}; @@ -78,8 +70,6 @@ void CIRGenFunction::emitSYCLKernelCaller( finishFunction(body->getEndLoc()); } - // Mirror the tail of generateCode: drop leftover empty/unreachable blocks the - // lexical-scope machinery may have created. eraseEmptyAndUnusedBlocks(funcOp); } @@ -107,7 +97,7 @@ void CIRGenModule::emitSYCLKernelCaller(const FunctionDecl *kernelEntryPointFn, // Compute the function info and CIR function type. const CIRGenFunctionInfo &fnInfo = - getTypes().arrangeSYCLKernelCallerDeclaration(ctx.VoidTy, args); + getTypes().arrangeDeviceKernelCallerDeclaration(ctx.VoidTy, args); cir::FuncType funcType = getTypes().getFunctionType(fnInfo); // Retrieve the generated name for the SYCL kernel caller function. @@ -115,33 +105,21 @@ void CIRGenModule::emitSYCLKernelCaller(const FunctionDecl *kernelEntryPointFn, ctx.getCanonicalType(kernelEntryPointAttr->getKernelName()); const SYCLKernelInfo &kernelInfo = ctx.getSYCLKernelInfo(kernelNameType); - // Create the SYCL kernel caller function. Unlike an ordinary function, this - // offload entry point is synthesized from the OutlinedFunctionDecl held by - // the SYCLKernelCallStmt rather than emitted from a FunctionDecl, so it is - // constructed with an empty GlobalDecl. + // Synthesized from the OutlinedFunctionDecl, not a FunctionDecl, so create it + // with an empty GlobalDecl. cir::FuncOp funcOp = getOrCreateCIRFunction( kernelInfo.GetKernelName(), funcType, GlobalDecl(), /*forVTable=*/false, /*dontDefer=*/true, /*isThunk=*/false, ForDefinition); - - // The kernel caller offload entry point has external linkage. Classic - // CodeGen creates it with ExternalLinkage explicitly (EmitSYCLKernelCaller); - // createCIRFunction already applies ExternalLinkage by default, so set it - // explicitly here to make the contract clear rather than rely on the default. funcOp.setLinkage(cir::GlobalLinkageKind::ExternalLinkage); - // Set the device kernel calling convention so the entry point is emitted as - // a kernel (e.g. spir_kernel) rather than an ordinary device function. - // Classic CodeGen derives this from the CC_DeviceKernel function info via - // SetLLVMFunctionAttributes; CIR does not yet route the function-info calling - // convention onto the FuncOp (opFuncCallingConv), so set it directly from the - // target hook, matching how CIRGen sets kernel calling conventions elsewhere. + // Emit as a device kernel (e.g. spir_kernel). Classic CodeGen derives this + // from CC_DeviceKernel via SetLLVMFunctionAttributes; CIR does not yet route + // opFuncCallingConv onto the FuncOp, so set it from the target hook. funcOp.setCallingConv(getTargetCIRGenInfo().getDeviceKernelCallingConv()); - // TODO: The following attributes applied by classic CodeGen's - // EmitSYCLKernelCaller are not yet applied in CIR: - // - SetSYCLKernelAttributes: norecurse and mustprogress. - // - addSYCLModuleIdAttr: the "sycl-module-id" attribute. - // - setDSOLocal. + // TODO: attributes applied by classic CodeGen not yet handled in CIR: + // SetSYCLKernelAttributes (norecurse, mustprogress), addSYCLModuleIdAttr, + // setDSOLocal. assert(!cir::MissingFeatures::setLLVMFunctionFEnvAttributes()); assert(!cir::MissingFeatures::setDSOLocal()); @@ -155,11 +133,8 @@ void CIRGenModule::emitSYCLKernelCaller(const FunctionDecl *kernelEntryPointFn, curCGF = nullptr; setNonAliasAttributes(GlobalDecl(), funcOp); - // The SYCL kernel caller is synthesized from an OutlinedFunctionDecl rather - // than a FunctionDecl. Classic CodeGen passes the OutlinedFunctionDecl to - // SetLLVMFunctionAttributesForDefinition, but CIR's setter takes a - // FunctionDecl; passing nullptr here skips OutlinedFunctionDecl-derived - // attributes (e.g. inline hints), which are not yet handled. + // CIR's setter takes a FunctionDecl; nullptr skips OutlinedFunctionDecl- + // derived attributes (e.g. inline hints), not yet handled. assert(!cir::MissingFeatures::opFuncExtraAttrs()); setCIRFunctionAttributesForDefinition(/*fd=*/nullptr, funcOp); } diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp index 34f5a2993101d..3707455a79b21 100644 --- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp @@ -784,7 +784,7 @@ const CIRGenFunctionInfo &CIRGenTypes::arrangeCIRFunctionInfo( return *fi; } -const CIRGenFunctionInfo &CIRGenTypes::arrangeSYCLKernelCallerDeclaration( +const CIRGenFunctionInfo &CIRGenTypes::arrangeDeviceKernelCallerDeclaration( QualType resultType, const FunctionArgList &args) { SmallVector<CanQualType, 16> argTypes; for (const VarDecl *arg : args) diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h index bc2a1d599cd8a..c5f8b521ed888 100644 --- a/clang/lib/CIR/CodeGen/CIRGenTypes.h +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h @@ -187,11 +187,11 @@ class CIRGenTypes { const CIRGenFunctionInfo & arrangeFunctionDeclaration(const clang::FunctionDecl *fd); - /// Arrange the function info for the offload kernel entry point function - /// generated for a SYCL kernel caller function. + /// Arrange the function info for a device kernel caller entry point (e.g. a + /// SYCL kernel caller). const CIRGenFunctionInfo & - arrangeSYCLKernelCallerDeclaration(clang::QualType resultType, - const FunctionArgList &args); + arrangeDeviceKernelCallerDeclaration(clang::QualType resultType, + const FunctionArgList &args); /// A builtin function is a freestanding function using the default /// C conventions. >From 0fdafb9f874be50079d31e453078deccf773e80d Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Mon, 10 Aug 2026 13:37:15 -0700 Subject: [PATCH 3/4] Address comments --- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 3 ++- clang/lib/CIR/CodeGen/CIRGenSYCL.cpp | 23 ++++++++++++----------- clang/lib/CIR/CodeGen/CIRGenTypes.cpp | 5 +++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 0578931fe9419..39cf3ee20d52b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -370,7 +370,8 @@ void CIRGenFunction::LexicalScope::emitImplicitReturn() { LexicalScope *localScope = cgf.curLexScope; // Synthesized functions (e.g. SYCL kernel caller entry points) have no - // FunctionDecl; the non-void flow-off-the-end handling below is guarded on fd. + // FunctionDecl; the non-void flow-off-the-end handling below is guarded on + // fd. const auto *fd = dyn_cast_or_null<clang::FunctionDecl>(cgf.curGD.getDecl()); // In C++, flowing off the end of a non-void function is always undefined diff --git a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp index e474f1c42b3a2..2f4d908d5c3e1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp @@ -16,8 +16,10 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" -#include "clang/AST/StmtSYCL.h" #include "clang/AST/SYCLKernelInfo.h" +#include "clang/AST/StmtSYCL.h" + +#include "llvm/Support/SaveAndRestore.h" using namespace clang; using namespace clang::CIRGen; @@ -105,32 +107,31 @@ void CIRGenModule::emitSYCLKernelCaller(const FunctionDecl *kernelEntryPointFn, ctx.getCanonicalType(kernelEntryPointAttr->getKernelName()); const SYCLKernelInfo &kernelInfo = ctx.getSYCLKernelInfo(kernelNameType); - // Synthesized from the OutlinedFunctionDecl, not a FunctionDecl, so create it - // with an empty GlobalDecl. - cir::FuncOp funcOp = getOrCreateCIRFunction( - kernelInfo.GetKernelName(), funcType, GlobalDecl(), /*forVTable=*/false, - /*dontDefer=*/true, /*isThunk=*/false, ForDefinition); + // Synthesized from the OutlinedFunctionDecl, not a FunctionDecl, so create + // the function directly with a null FunctionDecl (mirrors classic CodeGen's + // llvm::Function::Create). + cir::FuncOp funcOp = createCIRFunction( + getLoc(kernelEntryPointFn->getSourceRange()), kernelInfo.GetKernelName(), + funcType, /*funcDecl=*/nullptr); funcOp.setLinkage(cir::GlobalLinkageKind::ExternalLinkage); // Emit as a device kernel (e.g. spir_kernel). Classic CodeGen derives this // from CC_DeviceKernel via SetLLVMFunctionAttributes; CIR does not yet route // opFuncCallingConv onto the FuncOp, so set it from the target hook. funcOp.setCallingConv(getTargetCIRGenInfo().getDeviceKernelCallingConv()); + setDSOLocal(static_cast<mlir::Operation *>(funcOp)); // TODO: attributes applied by classic CodeGen not yet handled in CIR: - // SetSYCLKernelAttributes (norecurse, mustprogress), addSYCLModuleIdAttr, - // setDSOLocal. + // SetSYCLKernelAttributes (norecurse, mustprogress), addSYCLModuleIdAttr. assert(!cir::MissingFeatures::setLLVMFunctionFEnvAttributes()); - assert(!cir::MissingFeatures::setDSOLocal()); // Emit the SYCL kernel caller function. CIRGenFunction cgf(*this, builder); - curCGF = &cgf; + llvm::SaveAndRestore<CIRGenFunction *> savedCGF(curCGF, &cgf); { mlir::OpBuilder::InsertionGuard guard(builder); cgf.emitSYCLKernelCaller(outlinedFnDecl, funcOp, funcType, args); } - curCGF = nullptr; setNonAliasAttributes(GlobalDecl(), funcOp); // CIR's setter takes a FunctionDecl; nullptr skips OutlinedFunctionDecl- diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp index 3707455a79b21..6273c463e85f1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp @@ -784,8 +784,9 @@ const CIRGenFunctionInfo &CIRGenTypes::arrangeCIRFunctionInfo( return *fi; } -const CIRGenFunctionInfo &CIRGenTypes::arrangeDeviceKernelCallerDeclaration( - QualType resultType, const FunctionArgList &args) { +const CIRGenFunctionInfo & +CIRGenTypes::arrangeDeviceKernelCallerDeclaration(QualType resultType, + const FunctionArgList &args) { SmallVector<CanQualType, 16> argTypes; for (const VarDecl *arg : args) argTypes.push_back(astContext.getCanonicalParamType(arg->getType())); >From 0495382d44892246aa19d6e9ab5c1a27a9204fdb Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Tue, 11 Aug 2026 15:05:22 -0700 Subject: [PATCH 4/4] Fix comments --- clang/lib/CIR/CodeGen/CIRGenSYCL.cpp | 9 ++++++++- .../CodeGenSYCL/kernel-caller-entry-point.cpp | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp index 2f4d908d5c3e1..b873c2d6f24b7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenSYCL.cpp @@ -119,7 +119,12 @@ void CIRGenModule::emitSYCLKernelCaller(const FunctionDecl *kernelEntryPointFn, // from CC_DeviceKernel via SetLLVMFunctionAttributes; CIR does not yet route // opFuncCallingConv onto the FuncOp, so set it from the target hook. funcOp.setCallingConv(getTargetCIRGenInfo().getDeviceKernelCallingConv()); - setDSOLocal(static_cast<mlir::Operation *>(funcOp)); + + // Route through the shared attribute path so generic function attributes + // (e.g. convergent) are applied, matching classic CodeGen's + // SetLLVMFunctionAttributes. There is no FunctionDecl, so pass an empty + // GlobalDecl. + setCIRFunctionAttributes(GlobalDecl(), fnInfo, funcOp, /*isThunk=*/false); // TODO: attributes applied by classic CodeGen not yet handled in CIR: // SetSYCLKernelAttributes (norecurse, mustprogress), addSYCLModuleIdAttr. @@ -133,6 +138,8 @@ void CIRGenModule::emitSYCLKernelCaller(const FunctionDecl *kernelEntryPointFn, cgf.emitSYCLKernelCaller(outlinedFnDecl, funcOp, funcType, args); } + setDSOLocal(static_cast<mlir::Operation *>(funcOp)); + setNonAliasAttributes(GlobalDecl(), funcOp); // CIR's setter takes a FunctionDecl; nullptr skips OutlinedFunctionDecl- // derived attributes (e.g. inline hints), not yet handled. diff --git a/clang/test/CIR/CodeGenSYCL/kernel-caller-entry-point.cpp b/clang/test/CIR/CodeGenSYCL/kernel-caller-entry-point.cpp index d6d8006d09cc8..25981f7df76e1 100644 --- a/clang/test/CIR/CodeGenSYCL/kernel-caller-entry-point.cpp +++ b/clang/test/CIR/CodeGenSYCL/kernel-caller-entry-point.cpp @@ -5,6 +5,16 @@ // RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spirv64-unknown-unknown -emit-llvm %s -o %t.ll // RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG +// On an ELF target such as spir64, the kernel caller entry point definition is +// dso_local. dso_local is only attached to a definition, so this also verifies +// that setDSOLocal() runs after body emission. +// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spir64-unknown-unknown -fclangir -emit-cir %s -o %t-elf.cir +// RUN: FileCheck --input-file=%t-elf.cir %s -check-prefix=CIR-ELF +// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spir64-unknown-unknown -fclangir -emit-llvm %s -o %t-elf-cir.ll +// RUN: FileCheck --input-file=%t-elf-cir.ll %s -check-prefix=LLVM-ELF +// RUN: %clang_cc1 -std=c++20 -fsycl-is-device -triple spir64-unknown-unknown -emit-llvm %s -o %t-elf.ll +// RUN: FileCheck --input-file=%t-elf.ll %s -check-prefix=OGCG-ELF + // During device compilation, an offload kernel caller entry point is emitted // in place of each sycl_kernel_entry_point attributed function. The entry // point is named after the kernel name type and its body is the transformed @@ -65,3 +75,9 @@ void test() { // OGCG: call {{.*}}spir_func void @_ZNK1KclEv // OGCG: ret void // OGCG-NOT: define {{.*}}@_Z18kernel_single_task + +// On ELF, the kernel caller entry point definition is dso_local in CIR, +// CIR-lowered LLVM IR, and classic CodeGen alike. +// CIR-ELF: cir.func {{.*}}dso_local {{.*}}@_ZTS2KN +// LLVM-ELF: define {{.*}}dso_local {{.*}}void @_ZTS2KN +// OGCG-ELF: define {{.*}}dso_local {{.*}}void @_ZTS2KN _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
