[clang] [llvm] [clang][SYCL] Align device binary (un)registration with CUDA/HIP/OpenMP (PR #217173)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 18 17:10:49 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-codegen
Author: Yury Plyakhin (YuriPlyakhin)
<details>
<summary>Changes</summary>
The SYCL offload wrapper still registers and unregisters the device binary the way the offloading runtimes did before 421085fd740d (#<!-- -->86830): a constructor and a destructor, both at priority 1. That commit moved OpenMP/CUDA/HIP off a destructor and priority 1, and the reasons apply to SYCL equally.
With no destructor left to emit, wrapSYCLBinaries() only ever hands back one function, so shrink its out-parameter to a single Function *. This also lets CodeGenModule::Release() emit the SYCL constructor next to the CUDA one instead of ahead of registerGlobalDtorsWithAtExit(), where it had to sit only because AddGlobalDtor() additions are dropped after that call.
---
Full diff: https://github.com/llvm/llvm-project/pull/217173.diff
8 Files Affected:
- (modified) clang/lib/CodeGen/CodeGenModule.cpp (+6-9)
- (modified) clang/lib/CodeGen/CodeGenModule.h (+3-3)
- (modified) clang/lib/CodeGen/CodeGenSYCL.cpp (+6-7)
- (modified) clang/test/CodeGenSYCL/offload-include-binary.cpp (+10-8)
- (modified) clang/test/OffloadTools/clang-linker-wrapper/linker-wrapper-image.c (+1)
- (modified) llvm/include/llvm/Frontend/Offloading/OffloadWrapper.h (+8-8)
- (modified) llvm/lib/Frontend/Offloading/OffloadWrapper.cpp (+19-10)
- (modified) llvm/test/tools/llvm-offload-wrapper/offload-wrapper.ll (+2-2)
``````````diff
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index b171418f5d51d..d348e80780ee9 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1177,15 +1177,6 @@ void CodeGenModule::Release() {
else
EmitCXXGlobalInitFunc();
EmitCXXGlobalCleanUpFunc();
- if (LangOpts.SYCLIsHost && !CodeGenOpts.OffloadBinaryToEmbedFile.empty()) {
- auto [SYCLCtorFunction, SYCLDtorFunction] = embedSYCLDeviceBinary();
- if (SYCLCtorFunction) {
- // A static initializer may launch a kernel, so the device binaries have
- // to be registered before any of them run, hence a priority.
- AddGlobalCtor(SYCLCtorFunction, /*Priority=*/1);
- AddGlobalDtor(SYCLDtorFunction, /*Priority=*/1);
- }
- }
registerGlobalDtorsWithAtExit();
EmitCXXThreadLocalInitFunc();
if (ObjCRuntime)
@@ -1195,6 +1186,12 @@ void CodeGenModule::Release() {
if (llvm::Function *CudaCtorFunction = CUDARuntime->finalizeModule())
AddGlobalCtor(CudaCtorFunction);
}
+ if (LangOpts.SYCLIsHost && !CodeGenOpts.OffloadBinaryToEmbedFile.empty()) {
+ if (llvm::Function *SYCLCtorFunction = embedSYCLDeviceBinary())
+ // A static initializer may launch a kernel, so the device binary has to
+ // be registered before any of them run, hence a priority.
+ AddGlobalCtor(SYCLCtorFunction, /*Priority=*/101);
+ }
if (OpenMPRuntime) {
OpenMPRuntime->createOffloadEntriesAndInfoMetadata();
OpenMPRuntime->clear();
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index f3e0541636638..1f5ecf734c528 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2181,9 +2181,9 @@ class CodeGenModule : public CodeGenTypeCache {
/// Embed the finalized SYCL device binary named by -foffload-include-binary
/// into the host module.
- /// \return the functions that register and unregister the binary with the
- /// runtime, both null if the binary could not be read.
- std::pair<llvm::Function *, llvm::Function *> embedSYCLDeviceBinary();
+ /// \return the function that registers the binary with the runtime, or null
+ /// if the binary could not be read.
+ llvm::Function *embedSYCLDeviceBinary();
/// Determine whether the definition must be emitted; if this returns \c
/// false, the definition can be emitted lazily if it's used.
diff --git a/clang/lib/CodeGen/CodeGenSYCL.cpp b/clang/lib/CodeGen/CodeGenSYCL.cpp
index ba7b246df9869..a8c36fb7824be 100644
--- a/clang/lib/CodeGen/CodeGenSYCL.cpp
+++ b/clang/lib/CodeGen/CodeGenSYCL.cpp
@@ -89,24 +89,23 @@ void CodeGenModule::EmitSYCLKernelCaller(const FunctionDecl *KernelEntryPointFn,
CGF.FinishFunction();
}
-std::pair<llvm::Function *, llvm::Function *>
-CodeGenModule::embedSYCLDeviceBinary() {
+llvm::Function *CodeGenModule::embedSYCLDeviceBinary() {
StringRef FileName = getCodeGenOpts().OffloadBinaryToEmbedFile;
auto BufferOrErr = getFileSystem()->getBufferForFile(FileName);
if (std::error_code EC = BufferOrErr.getError()) {
getDiags().Report(diag::err_cannot_open_file) << FileName << EC.message();
- return {nullptr, nullptr};
+ return nullptr;
}
std::unique_ptr<llvm::MemoryBuffer> Buffer = std::move(BufferOrErr.get());
- std::pair<llvm::Function *, llvm::Function *> RegistrationFuncs;
+ llvm::Function *RegistrationFunc = nullptr;
if (llvm::Error Err = llvm::offloading::wrapSYCLBinaries(
getModule(),
ArrayRef<char>(Buffer->getBufferStart(), Buffer->getBufferSize()),
llvm::offloading::SYCLJITOptions(), /*IsFinalizedImage=*/true,
- &RegistrationFuncs)) {
+ &RegistrationFunc)) {
getDiags().Report(diag::err_fe_error_backend)
<< llvm::toString(std::move(Err));
- return {nullptr, nullptr};
+ return nullptr;
}
- return RegistrationFuncs;
+ return RegistrationFunc;
}
diff --git a/clang/test/CodeGenSYCL/offload-include-binary.cpp b/clang/test/CodeGenSYCL/offload-include-binary.cpp
index 02c92328d97ae..fada4a303042d 100644
--- a/clang/test/CodeGenSYCL/offload-include-binary.cpp
+++ b/clang/test/CodeGenSYCL/offload-include-binary.cpp
@@ -1,18 +1,20 @@
// REQUIRES: x86-registered-target
// Verify that -foffload-include-binary embeds the finalized SYCL device
-// binary into the host module and emits the registration/unregistration
-// constructors and destructors expected by the SYCL runtime.
+// binary into the host module and emits the constructor that registers it with
+// the SYCL runtime. Unregistration is done from 'atexit', so no global
+// destructor is emitted for it.
// The binary is already finalized, so it must not land in ".llvm.offloading".
// RUN: echo -n 'FAKE_SYCL_DEVICE_IMAGE' > %t.bin
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsycl-is-host \
// RUN: -foffload-include-binary %t.bin -emit-llvm %s -o - \
// RUN: | FileCheck %s --implicit-check-not='.llvm.offloading' \
-// RUN: --implicit-check-not='llvm.global_ctors.'
+// RUN: --implicit-check-not='llvm.global_ctors.' \
+// RUN: --implicit-check-not='llvm.global_dtors'
-// The registration functions have to merge into the constructor and destructor
-// lists the rest of the translation unit contributes to, so object emission
-// must succeed for a translation unit that has its own static initializers.
+// The registration function has to merge into the constructor list the rest of
+// the translation unit contributes to, so object emission must succeed for a
+// translation unit that has its own static initializers.
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fsycl-is-host \
// RUN: -foffload-include-binary %t.bin -emit-obj %s -o %t.o
@@ -39,11 +41,11 @@ void f() {}
// CHECK: @.sycl_offloading.binary = internal unnamed_addr constant [22 x i8] c"FAKE_SYCL_DEVICE_IMAGE", section ".sycl_fatbin"
// CHECK: @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }]
// CHECK-SAME: i32 65535, ptr @_GLOBAL__sub_I_
-// CHECK-SAME: i32 1, ptr @sycl.descriptor_reg
-// CHECK: @llvm.global_dtors = {{.*}}@sycl.descriptor_unreg
+// CHECK-SAME: i32 101, ptr @sycl.descriptor_reg
// CHECK: define internal void @sycl.descriptor_reg()
// CHECK-NEXT: entry:
// CHECK-NEXT: call void @__sycl_register_lib(ptr @.sycl_offloading.binary, i64 22)
+// CHECK-NEXT: call i32 @atexit(ptr @sycl.descriptor_unreg)
// CHECK-NEXT: ret void
// CHECK: define internal void @sycl.descriptor_unreg()
// CHECK-NEXT: entry:
diff --git a/clang/test/OffloadTools/clang-linker-wrapper/linker-wrapper-image.c b/clang/test/OffloadTools/clang-linker-wrapper/linker-wrapper-image.c
index 1f6cc9cc32e8e..238645aace5cf 100644
--- a/clang/test/OffloadTools/clang-linker-wrapper/linker-wrapper-image.c
+++ b/clang/test/OffloadTools/clang-linker-wrapper/linker-wrapper-image.c
@@ -316,6 +316,7 @@
// SYCL: define internal void @sycl.descriptor_reg() section ".text.startup" {
// SYCL-NEXT: entry:
// SYCL-NEXT: call void @__sycl_register_lib(ptr @.sycl_offloading.binary, i64 0)
+// SYCL-NEXT: %0 = call i32 @atexit(ptr @sycl.descriptor_unreg)
// SYCL-NEXT: ret void
// SYCL-NEXT: }
diff --git a/llvm/include/llvm/Frontend/Offloading/OffloadWrapper.h b/llvm/include/llvm/Frontend/Offloading/OffloadWrapper.h
index bf78372168298..8d0c98f12b24d 100644
--- a/llvm/include/llvm/Frontend/Offloading/OffloadWrapper.h
+++ b/llvm/include/llvm/Frontend/Offloading/OffloadWrapper.h
@@ -70,14 +70,14 @@ struct SYCLJITOptions {
/// use by a runtime for JIT compilation. Not used for AOT.
/// \param IsFinalizedImage True when \p Buffer holds an already finalized
/// device image, which must not be device-linked again.
-/// \param RegistrationFuncs When given, receives the functions that register
-/// and unregister the binary with the runtime instead of them being appended
-/// to llvm.global_ctors and llvm.global_dtors. A caller has to add them
-/// to those lists itself.
-LLVM_ABI llvm::Error wrapSYCLBinaries(
- llvm::Module &M, llvm::ArrayRef<char> Buffer,
- SYCLJITOptions Options = SYCLJITOptions(), bool IsFinalizedImage = false,
- std::pair<llvm::Function *, llvm::Function *> *RegistrationFuncs = nullptr);
+/// \param RegistrationFunc When given, receives the function that registers the
+/// binary with the runtime instead of it being appended to llvm.global_ctors.
+/// A caller has to add it to that list itself.
+LLVM_ABI llvm::Error
+wrapSYCLBinaries(llvm::Module &M, llvm::ArrayRef<char> Buffer,
+ SYCLJITOptions Options = SYCLJITOptions(),
+ bool IsFinalizedImage = false,
+ llvm::Function **RegistrationFunc = nullptr);
} // namespace offloading
} // namespace llvm
diff --git a/llvm/lib/Frontend/Offloading/OffloadWrapper.cpp b/llvm/lib/Frontend/Offloading/OffloadWrapper.cpp
index 594f0b70b31b2..135d3095ab42a 100644
--- a/llvm/lib/Frontend/Offloading/OffloadWrapper.cpp
+++ b/llvm/lib/Frontend/Offloading/OffloadWrapper.cpp
@@ -676,13 +676,25 @@ class SYCLWrapper {
FunctionCallee RegFuncC =
M.getOrInsertFunction("__sycl_register_lib", RegFuncTy);
+ FunctionType *AtExitTy =
+ FunctionType::get(Type::getInt32Ty(C), PtrTy, /*isVarArg=*/false);
+ FunctionCallee AtExit = M.getOrInsertFunction("atexit", AtExitTy);
+
+ Function *UnregFunc = createUnregisterFunction(Start, Size);
+
IRBuilder<> Builder(BasicBlock::Create(C, "entry", Func));
Builder.CreateCall(RegFuncC, {Start, Size});
+
+ // Unregister with 'atexit'. The handler is installed after
+ // __sycl_register_lib has brought the runtime's own exit-time cleanup into
+ // the atexit chain, so it is ordered ahead of that cleanup.
+ Builder.CreateCall(AtExit, UnregFunc);
Builder.CreateRetVoid();
return Func;
}
+private:
Function *createUnregisterFunction(Constant *Start, Constant *Size) {
FunctionType *FuncTy =
FunctionType::get(Type::getVoidTy(C), /*isVarArg*/ false);
@@ -705,7 +717,6 @@ class SYCLWrapper {
return Func;
}
-private:
Module &M;
LLVMContext &C;
SYCLJITOptions Options;
@@ -753,20 +764,18 @@ Error offloading::wrapHIPBinary(Module &M, ArrayRef<char> Image,
return Error::success();
}
-Error llvm::offloading::wrapSYCLBinaries(
- llvm::Module &M, ArrayRef<char> Buffer, SYCLJITOptions Options,
- bool IsFinalizedImage,
- std::pair<Function *, Function *> *RegistrationFuncs) {
+Error llvm::offloading::wrapSYCLBinaries(llvm::Module &M, ArrayRef<char> Buffer,
+ SYCLJITOptions Options,
+ bool IsFinalizedImage,
+ Function **RegistrationFunc) {
SYCLWrapper W(M, Options, IsFinalizedImage);
auto [Start, Size] = W.embedBinary(Buffer);
Function *RegisterFunc = W.createRegisterFatbinFunction(Start, Size);
- Function *UnregisterFunc = W.createUnregisterFunction(Start, Size);
- if (RegistrationFuncs) {
- *RegistrationFuncs = {RegisterFunc, UnregisterFunc};
+ if (RegistrationFunc) {
+ *RegistrationFunc = RegisterFunc;
return Error::success();
}
- appendToGlobalCtors(M, RegisterFunc, /*Priority*/ 1);
- appendToGlobalDtors(M, UnregisterFunc, /*Priority*/ 1);
+ appendToGlobalCtors(M, RegisterFunc, /*Priority=*/101);
return Error::success();
}
diff --git a/llvm/test/tools/llvm-offload-wrapper/offload-wrapper.ll b/llvm/test/tools/llvm-offload-wrapper/offload-wrapper.ll
index cc053642761e1..420e41adbd115 100644
--- a/llvm/test/tools/llvm-offload-wrapper/offload-wrapper.ll
+++ b/llvm/test/tools/llvm-offload-wrapper/offload-wrapper.ll
@@ -125,12 +125,12 @@
; RUN: llvm-dis %t.bc -o - | FileCheck %s --check-prefix=SYCL
; SYCL: @.sycl_offloading.binary = internal unnamed_addr constant [[[SIZE:[0-9]+]] x i8] c"{{.*}}", section ".llvm.offloading"
-; SYCL-NEXT: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 1, ptr @sycl.descriptor_reg, ptr null }]
-; SYCL-NEXT: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 1, ptr @sycl.descriptor_unreg, ptr null }]
+; SYCL-NEXT: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 101, ptr @sycl.descriptor_reg, ptr null }]
; SYCL: define internal void @sycl.descriptor_reg() section ".text.startup" {
; SYCL-NEXT: entry:
; SYCL-NEXT: call void @__sycl_register_lib(ptr @.sycl_offloading.binary, i64 [[SIZE]])
+; SYCL-NEXT: %0 = call i32 @atexit(ptr @sycl.descriptor_unreg)
; SYCL-NEXT: ret void
; SYCL-NEXT: }
``````````
</details>
https://github.com/llvm/llvm-project/pull/217173
More information about the llvm-commits
mailing list