[llvm] [Offload] Retain the offloading entries section under --gc-sections (PR #212614)
Joseph Huber via llvm-commits
llvm-commits at lists.llvm.org
Tue Jul 28 13:47:42 PDT 2026
https://github.com/jhuber6 created https://github.com/llvm/llvm-project/pull/212614
## Summary
The offloading entries section (`llvm_offload_entries`) is bounded by *hidden*
`__start_`/`__stop_` symbols and kept alive only by a `__dummy` anchor placed in
`llvm.compiler.used`. That blocks compiler dead-code elimination but not linker
garbage collection: under `--gc-sections`, lld's
[start-stop-gc](https://lld.llvm.org/ELF/start-stop-gc) rule does not treat a
*hidden* encapsulation symbol as a reason to retain its section, so the section
is collected and the boundary symbols are left undefined.
This surfaces with `clang --offload-link -r` (severing RDC into a host-only
relocatable object). `relocateOffloadSection` renames the section to a private,
hashed name (`llvm_offload_entries_<hash>`) which is later linked into a final
application. A downstream link with `--gc-sections` then fails with:
```
ld.lld: error: undefined hidden symbol: __stop_llvm_offload_entries_<hash>
>>> referenced by ...
>>> device.o:(.cuda.globals_reg) in archive device.a
```
Fix: anchor the dummy in `llvm.used` instead of `llvm.compiler.used`, so the
section is emitted with `SHF_GNU_RETAIN` and survives `--gc-sections`.
## Test plan
- Updated `llvm/test/tools/llvm-offload-wrapper/offload-wrapper.ll` to expect
`@llvm.used` and to verify (via `llc` + `llvm-readelf`) that the emitted
`llvm_offload_entries` section carries the retain flag (`R`).
- Verified end-to-end: a CUDA RDC sever (`--offload-new-driver -fgpu-rdc`,
`--offload-link -r`, archived, then linked with `-Wl,--gc-sections`) now links
cleanly; without the fix it fails with the undefined hidden `__stop_` symbol.
- `ninja check` for the affected offload tests passes.
Made with [Cursor](https://cursor.com)
>From 896672eac0d15bdd7ad869a30bfd22a93c570f21 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Tue, 28 Jul 2026 15:47:12 -0500
Subject: [PATCH] [Offload] Retain the offloading entries section under
--gc-sections
The offloading entries section is bounded by hidden `__start_`/`__stop_`
symbols and kept alive only by a dummy anchor placed in `llvm.compiler.used`.
That prevents compiler dead-code elimination but not linker garbage
collection: under `--gc-sections`, lld's start-stop-gc rule does not treat a
hidden encapsulation symbol as a reason to retain its section, so the section
is collected and the boundary symbols are left undefined.
This surfaces with `clang --offload-link -r` (severing RDC), where
`relocateOffloadSection` renames the section to a private, hashed name that is
later linked into a final application. A downstream link with `--gc-sections`
then fails with an undefined hidden `__stop_llvm_offload_entries_<hash>`
referenced from `.cuda.globals_reg`.
Anchor the dummy in `llvm.used` instead so the section is emitted with
SHF_GNU_RETAIN and survives `--gc-sections`.
Co-authored-by: Cursor <cursoragent at cursor.com>
---
llvm/lib/Frontend/Offloading/Utility.cpp | 12 ++++++++----
.../tools/llvm-offload-wrapper/offload-wrapper.ll | 15 +++++++++++----
2 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/Frontend/Offloading/Utility.cpp b/llvm/lib/Frontend/Offloading/Utility.cpp
index c07d276244ee1..fb34046a9a1e5 100644
--- a/llvm/lib/Frontend/Offloading/Utility.cpp
+++ b/llvm/lib/Frontend/Offloading/Utility.cpp
@@ -154,22 +154,26 @@ offloading::getOffloadEntryArray(Module &M) {
// We assume that external begin/end symbols that we have created above will
// be defined by the linker. This is done whenever a section name with a
// valid C-identifier is present. We define a dummy variable here to force
- // the linker to always provide these symbols.
+ // the linker to always provide these symbols. It is added to 'llvm.used'
+ // (rather than 'llvm.compiler.used') so the section is emitted with
+ // SHF_GNU_RETAIN; the boundary symbols are hidden, so under --gc-sections
+ // the section would otherwise be collected and the symbols left undefined.
auto *DummyEntry = new GlobalVariable(
M, ZeroInitilaizer->getType(), true, GlobalVariable::InternalLinkage,
ZeroInitilaizer, "__dummy." + SectionName);
DummyEntry->setSection(SectionName);
DummyEntry->setAlignment(Align(object::OffloadBinary::getAlignment()));
- appendToCompilerUsed(M, DummyEntry);
+ appendToUsed(M, DummyEntry);
} else if (Triple.isOSBinFormatMachO()) {
// Mach-O needs a dummy variable in the section (like ELF) to ensure the
- // linker provides the section boundary symbols.
+ // linker provides the section boundary symbols. Mark it used so the
+ // section survives dead-stripping.
auto *DummyEntry = new GlobalVariable(
M, ZeroInitilaizer->getType(), true, GlobalVariable::InternalLinkage,
ZeroInitilaizer, "__dummy." + SectionName);
DummyEntry->setSection(SectionName);
DummyEntry->setAlignment(Align(object::OffloadBinary::getAlignment()));
- appendToCompilerUsed(M, DummyEntry);
+ appendToUsed(M, DummyEntry);
} else {
// The COFF linker will merge sections containing a '$' together into a
// single section. The order of entries in this section will be sorted
diff --git a/llvm/test/tools/llvm-offload-wrapper/offload-wrapper.ll b/llvm/test/tools/llvm-offload-wrapper/offload-wrapper.ll
index 699f8a69175a4..b2ca6eb4fe255 100644
--- a/llvm/test/tools/llvm-offload-wrapper/offload-wrapper.ll
+++ b/llvm/test/tools/llvm-offload-wrapper/offload-wrapper.ll
@@ -1,10 +1,17 @@
; RUN: llvm-offload-wrapper --triple=x86_64-unknown-linux-gnu -kind=openmp %s -o %t.bc
; RUN: llvm-dis %t.bc -o - | FileCheck %s --check-prefix=OMP
+; RUN: llc --filetype=obj %t.bc -o %t.o
+; RUN: llvm-readelf --sections %t.o | FileCheck %s --check-prefix=OMP-SECTION
+
+; The offloading entries section must be retained (SHF_GNU_RETAIN, shown as 'R')
+; so that --gc-sections does not collect it. Its boundary symbols are hidden,
+; which would otherwise leave the section unreferenced and dead-stripped.
+; OMP-SECTION: llvm_offload_entries PROGBITS {{.*}} AR
; OMP: @__start_llvm_offload_entries = external hidden constant [0 x %struct.__tgt_offload_entry]
; OMP-NEXT: @__stop_llvm_offload_entries = external hidden constant [0 x %struct.__tgt_offload_entry]
; OMP-NEXT: @__dummy.llvm_offload_entries = internal constant [0 x %struct.__tgt_offload_entry] zeroinitializer, section "llvm_offload_entries", align 8
-; OMP-NEXT: @llvm.compiler.used = appending global [1 x ptr] [ptr @__dummy.llvm_offload_entries], section "llvm.metadata"
+; OMP-NEXT: @llvm.used = appending global [1 x ptr] [ptr @__dummy.llvm_offload_entries], section "llvm.metadata"
; OMP-NEXT: @.omp_offloading.device_image = internal unnamed_addr constant [[[SIZE:[0-9]+]] x i8] c"{{.*}}", section ".llvm.offloading", align 8
; OMP-NEXT: @.omp_offloading.device_images = internal unnamed_addr constant [1 x %__tgt_device_image] [%__tgt_device_image { ptr @.omp_offloading.device_image, ptr getelementptr ([[[SIZE]] x i8], ptr @.omp_offloading.device_image, i64 0, i64 [[SIZE]]), ptr @__start_llvm_offload_entries, ptr @__stop_llvm_offload_entries }]
; OMP-NEXT: @.omp_offloading.descriptor = internal constant %__tgt_bin_desc { i32 1, ptr @.omp_offloading.device_images, ptr @__start_llvm_offload_entries, ptr @__stop_llvm_offload_entries }
@@ -29,7 +36,7 @@
; HIP: @__start_llvm_offload_entries = external hidden constant [0 x %struct.__tgt_offload_entry]
; HIP-NEXT: @__stop_llvm_offload_entries = external hidden constant [0 x %struct.__tgt_offload_entry]
; HIP-NEXT: @__dummy.llvm_offload_entries = internal constant [0 x %struct.__tgt_offload_entry] zeroinitializer, section "llvm_offload_entries", align 8
-; HIP-NEXT: @llvm.compiler.used = appending global [1 x ptr] [ptr @__dummy.llvm_offload_entries], section "llvm.metadata"
+; HIP-NEXT: @llvm.used = appending global [1 x ptr] [ptr @__dummy.llvm_offload_entries], section "llvm.metadata"
; HIP-NEXT: @.fatbin_image = internal constant {{.*}}, section ".hip_fatbin"
; HIP-NEXT: @.fatbin_wrapper = internal constant %fatbin_wrapper { i32 1212764230, i32 1, ptr @.fatbin_image, ptr null }, section ".hipFatBinSegment", no_sanitize_address, no_sanitize_hwaddress, align 8
; HIP-NEXT: @.hip.binary_handle = internal global ptr null
@@ -57,7 +64,7 @@
; HIP-MACHO: @"\01section$start$__LLVM$offload_entries" = external hidden constant [0 x %struct.__tgt_offload_entry]
; HIP-MACHO-NEXT: @"\01section$end$__LLVM$offload_entries" = external hidden constant [0 x %struct.__tgt_offload_entry]
; HIP-MACHO-NEXT: @"__dummy.__LLVM,offload_entries" = internal constant [0 x %struct.__tgt_offload_entry] zeroinitializer, section "__LLVM,offload_entries", align 8
-; HIP-MACHO-NEXT: @llvm.compiler.used = appending global [1 x ptr] [ptr @"__dummy.__LLVM,offload_entries"], section "llvm.metadata"
+; HIP-MACHO-NEXT: @llvm.used = appending global [1 x ptr] [ptr @"__dummy.__LLVM,offload_entries"], section "llvm.metadata"
; HIP-MACHO-NEXT: @.fatbin_image = internal constant {{.*}}, section "__HIP,__hip_fatbin"
; HIP-MACHO-NEXT: @.fatbin_wrapper = internal constant %fatbin_wrapper { i32 1212764230, i32 1, ptr @.fatbin_image, ptr null }, section "__HIP,__fatbin", no_sanitize_address, no_sanitize_hwaddress, align 8
; HIP-MACHO-NEXT: @.hip.binary_handle = internal global ptr null
@@ -85,7 +92,7 @@
; CUDA: @__start_llvm_offload_entries = external hidden constant [0 x %struct.__tgt_offload_entry]
; CUDA-NEXT: @__stop_llvm_offload_entries = external hidden constant [0 x %struct.__tgt_offload_entry]
; CUDA-NEXT: @__dummy.llvm_offload_entries = internal constant [0 x %struct.__tgt_offload_entry] zeroinitializer, section "llvm_offload_entries", align 8
-; CUDA-NEXT: @llvm.compiler.used = appending global [1 x ptr] [ptr @__dummy.llvm_offload_entries], section "llvm.metadata"
+; CUDA-NEXT: @llvm.used = appending global [1 x ptr] [ptr @__dummy.llvm_offload_entries], section "llvm.metadata"
; CUDA-NEXT: @.fatbin_image = internal constant {{.*}}, section ".nv_fatbin"
; CUDA-NEXT: @.fatbin_wrapper = internal constant %fatbin_wrapper { i32 1180844977, i32 1, ptr @.fatbin_image, ptr null }, section ".nvFatBinSegment", no_sanitize_address, no_sanitize_hwaddress, align 8
; CUDA-NEXT: @.cuda.binary_handle = internal global ptr null
More information about the llvm-commits
mailing list