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

Reply via email to