On 3/6/26 10:32 AM, Josh Poimboeuf wrote:
On Thu, Mar 05, 2026 at 07:43:25PM -0800, Yonghong Song wrote:
The current clang thin-lto build often produces lots of symbols with
suffix. The following is a partial list of such function call symbols:
...
ethnl_module_fw_flash_ntf.llvm.7631589765585346066
__nf_conntrack_alloc.llvm.6438426151906658917
tcp_can_early_drop.llvm.11937612064648250727
tcp_print_conntrack.llvm.11937612064648250727
...
In my particular build with current bpf-next, the number of '*.llvm.<hash>'
function calls is 1212. Such symbols make kernel live patching
difficult since
- a minor code change will change the hash and then the '*.llvm.<hash>'
symbol becomes another one with a different hash or no hash, and
- a previous source-level symbol may become an one with suffix after live
patching code.
In [1], Song Liu suggested to reduce the number of '*.llvm.<hash>' functions
to make live patch easier. In respond of this, I implemented this
in llvm ([2]). The same thin-lto build with [2] only has two symbols with
suffix:
m_stop.llvm.14460341347352036579
m_next.llvm.14460341347352036579
This should make live patch much easier.
To support suffix symbol reduction, a new config
LTO_CLANG_THIN_SUFFIX_REDUCTION
is introduced and the config depends on thin-lto and llvm23 or higher.
Two lld flags are necessary to enable this feature in kernel:
- Flag '--lto-whole-program-visibility' is needed as it ensures that all
modules are available in the same process, which is true for kernel at
thin-lto lld.
- Flag '-mllvm -always-rename-promoted-locals=false' is needed to enable
suffix reduction. Currently in llvm [1], only process mode is supported.
There is another distributed mode (across different processes or even
different machines) which is not supported yet ([2]).
[1] https://lpc.events/event/19/contributions/2212
[2] https://github.com/llvm/llvm-project/pull/178587
Signed-off-by: Yonghong Song <[email protected]>
---
Makefile | 3 +++
arch/Kconfig | 15 +++++++++++++++
2 files changed, 18 insertions(+)
diff --git a/Makefile b/Makefile
index e944c6e71e81..9d6033595615 100644
--- a/Makefile
+++ b/Makefile
@@ -1034,6 +1034,9 @@ endif
ifdef CONFIG_LTO_CLANG
ifdef CONFIG_LTO_CLANG_THIN
CC_FLAGS_LTO := -flto=thin -fsplit-lto-unit
+ifdef CONFIG_LTO_CLANG_THIN_SUFFIX_REDUCTION
+KBUILD_LDFLAGS += --lto-whole-program-visibility -mllvm
-always-rename-promoted-locals=false
+endif
else
CC_FLAGS_LTO := -flto
endif
diff --git a/arch/Kconfig b/arch/Kconfig
index 102ddbd4298e..e1db64a3284e 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -861,8 +861,23 @@ config LTO_CLANG_THIN
https://clang.llvm.org/docs/ThinLTO.html
If unsure, say Y.
+
endchoice
+config LTO_CLANG_THIN_SUFFIX_REDUCTION
+ bool "Clang ThinLTO Suffix Reduction (EXPERIMENTAL)"
+ depends on LTO_CLANG_THIN
+ depends on CLANG_VERSION >= 230000
+ default y
+ help
+ This option allows to reduce the number of symbols with
+ '.llvm.<hash' suffixes. This can help KLP (kernel living
+ patch) as symbol name can stay stable in most cases.
+
+ See https://github.com/llvm/llvm-project/pull/178587
+
+ If unsure, say N.
+
Thanks! Would there be any downsides to enabling this feature
unconditionally in the kernel when the compiler supports it?
The only downside is for the following case:
C file: static function foo()
Asm file: global function foo()
The thin-lto will collect all C files and with the above llvm patch,
the static function foo() may be promoted to global function foo()
if there is no other pre-existing global function foo() in all C files.
In such cases, there will be a conflict since
there are two global function foo() (one from C file, another from Asm file).
In such cases, the build will fail.
How do you think we could hit such issues in linux kernel?
Maybe should have default no for the new config?
I think the chance should be very low. The following is a grab for x86
for global symbols in asm code:
[~/work/others/linux/arch/x86 (master)]$ egrep -r globl
egrep: warning: egrep is obsolescent; using grep -E
boot/compressed/mkpiggy.c: printf(".globl z_input_len\n");
boot/compressed/mkpiggy.c: printf(".globl z_output_len\n");
boot/compressed/mkpiggy.c: printf(".globl input_data, input_data_end\n");
boot/compressed/mkpiggy.c: printf(".globl input_len\n");
boot/compressed/mkpiggy.c: printf(".globl output_len\n");
boot/compressed/head_64.S: .globl verify_cpu
boot/bioscall.S: .globl intcall
boot/header.S: .globl pecompat_fstart
boot/header.S: .globl sentinel
boot/header.S: .globl hdr
boot/header.S: .globl _start
boot/header.S: .globl realmode_swtch
boot/header.S: .globl die
entry/vdso/vdso32/sigreturn.S: .globl __kernel_sigreturn
entry/vdso/vdso32/sigreturn.S: .globl __kernel_rt_sigreturn
entry/vdso/vdso32/system_call.S: .globl __kernel_vsyscall
entry/vsyscall/vsyscall_emu_64.S: .globl __vsyscall_page
entry/entry_32.S: .globl __irqentry_text_start
entry/entry_32.S: .globl __irqentry_text_end
entry/entry_64.S: .globl __irqentry_text_start
entry/entry_64.S: .globl __irqentry_text_end
include/asm/paravirt_types.h: ".globl " PV_THUNK_NAME(func) ";"
\
include/asm/static_call.h: ".globl " STATIC_CALL_TRAMP_STR(name) "
\n" \
kernel/cpu/amd.c: ".globl vide\n"
kernel/ftrace_32.S:.globl ftrace_call
kernel/ftrace_32.S:.globl ftrace_graph_call
kernel/ftrace_32.S:.globl return_to_handler
kernel/relocate_kernel_32.S: .globl kexec_control_code_size
kernel/head_32.S:.globl initial_pg_pmd
kernel/head_32.S:.globl initial_page_table
kernel/head_32.S:.globl swapper_pg_dir
kernel/head_32.S:.globl empty_zero_page
lib/error-inject.c: ".globl just_return_func\n"
math-emu/reg_round.S:.globl fpu_reg_round
math-emu/reg_round.S:.globl fpu_Arith_exit
purgatory/kexec-purgatory.S: .globl kexec_purgatory
purgatory/kexec-purgatory.S: .globl kexec_purgatory_size
um/setjmp_32.S: .globl kernel_setjmp
um/setjmp_32.S: .globl kernel_longjmp
um/setjmp_64.S: .globl kernel_setjmp
um/setjmp_64.S: .globl kernel_longjmp
xen/xen-head.S: ELFNOTE(Xen, XEN_ELFNOTE_ENTRY, .globl
xen_elfnote_entry;
[~/work/others/linux/arch/x86 (master)]$
Maybe we could collect all global symbols in asm codes before lld,
and then we add an option in lld to feed those global symbols (with a file?),
then we can be sure there won't be any conflict?