kbuild: distributed build support for Clang ThinLTO

Add distributed ThinLTO build support for the Linux kernel.
This new mode offers several advantages: (1) Increased
flexibility in handling user-specified build options.
(2) Improved user-friendliness for developers. (3) Greater
convenience for integrating with objtool and livepatch.

Note that "distributed" in this context refers to a term
that differentiates in-process ThinLTO builds by invoking
backend compilation through the linker, not necessarily
building in distributed environments.

Distributed ThinLTO is enabled via the
`CONFIG_LTO_CLANG_THIN_DIST` Kconfig option. For example:
 > make LLVM=1 defconfig
 > scripts/config -e LTO_CLANG_THIN_DIST
 > make LLVM=1 oldconfig
 > make LLVM=1 vmlinux -j <..>

The build flow proceeds in four stages:
  1. Perform FE compilation, mirroring the in-process ThinLTO mode.
  2. Thin-link the generated IR files and object files.
  3. Find all IR files and perform BE compilation, using the flags
    stored in the .*.o.cmd files.
  4. Link the BE results to generate the final vmlinux.o.

NOTE: This patch currently implements the build for the main kernel
image (vmlinux) only. Kernel module support is planned for a
subsequent patch.

Tested on the following arch: x86, arm64, loongarch, and
riscv.

The earlier implementation details can be found here:
https://discourse.llvm.org/t/rfc-distributed-thinlto-build-for-kernel/85934

Signed-off-by: Rong Xu <xur@google.com>
Co-developed-by: Masahiro Yamada <masahiroy@kernel.org>
Signed-off-by: Masahiro Yamada <masahiroy@kernel.org>
Tested-by: Piotr Gorski <piotrgorski@cachyos.org>
Tested-by: Nathan Chancellor <nathan@kernel.org>
Link: https://patch.msgid.link/20260529185347.2418373-4-xur@google.com
Signed-off-by: Nathan Chancellor <nathan@kernel.org>
This commit is contained in:
Rong Xu
2026-05-29 11:53:46 -07:00
committed by Nathan Chancellor
parent 9c72d26e9f
commit 9f2aee8f7d
7 changed files with 129 additions and 8 deletions

View File

@@ -249,6 +249,13 @@ ifdef CONFIG_LTO_CLANG
cmd_ld_single = $(if $(objtool-enabled)$(is-single-obj-m), ; $(LD) $(ld_flags) -r -o $(tmp-target) $@; mv $(tmp-target) $@)
endif
ifdef CONFIG_LTO_CLANG_THIN_DIST
# Save the _c_flags, sliently.
quiet_cmd_save_c_flags =
saved_c_flags = $(_c_flags) $(modkern_cflags)
cmd_save_c_flags = printf '\n%s\n' 'saved_c_flags_$@ := $(call escsq,$(saved_c_flags))' >> $(dot-target).cmd
endif
quiet_cmd_cc_o_c = CC $(quiet_modtag) $@
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< \
$(cmd_ld_single) \
@@ -256,6 +263,7 @@ quiet_cmd_cc_o_c = CC $(quiet_modtag) $@
define rule_cc_o_c
$(call cmd_and_fixdep,cc_o_c)
$(call cmd,save_c_flags)
$(call cmd,checksrc)
$(call cmd,checkdoc)
$(call cmd,gen_objtooldep)

40
scripts/Makefile.thinlto Normal file
View File

@@ -0,0 +1,40 @@
PHONY := __default
__default:
include include/config/auto.conf
include $(srctree)/scripts/Kbuild.include
include $(srctree)/scripts/Makefile.lib
native-objs := $(patsubst %.o,%.thinlto-native.o,$(call read-file, vmlinux.thinlto-index))
__default: $(native-objs)
# Generate .thinlto-native.o (obj) from .o (bitcode) and .thinlto.bc (summary) files
# ---------------------------------------------------------------------------
quiet_cmd_cc_o_bc = CC $(quiet_modtag) $@
be_flags = $(shell sed -n '/saved_c_flags_/s/.*:= //p' \
$(dir $(<)).$(notdir $(<)).cmd)
cmd_cc_o_bc = \
$(CC) $(be_flags) -x ir -fno-lto -Wno-unused-command-line-argument \
-fthinlto-index=$(word 2, $^) -c -o $@ $<
targets += $(native-objs)
$(native-objs): %.thinlto-native.o: %.o %.o.thinlto.bc FORCE
$(call if_changed,cc_o_bc)
# Add FORCE to the prerequisites of a target to force it to be always rebuilt.
# ---------------------------------------------------------------------------
PHONY += FORCE
FORCE:
# Read all saved command lines and dependencies for the $(targets) we
# may be building above, using $(if_changed{,_dep}). As an
# optimization, we don't need to read them if the target does not
# exist, we will rebuild anyway in that case.
existing-targets := $(wildcard $(sort $(targets)))
-include $(foreach f, $(existing-targets),$(dir $(f)).$(notdir $(f)).cmd)
.PHONY: $(PHONY)

View File

@@ -21,6 +21,41 @@ targets += built-in-fixup.a
built-in-fixup.a: $(KBUILD_VMLINUX_OBJS) scripts/head-object-list.txt FORCE
$(call if_changed,ar_builtin_fixup)
ifdef CONFIG_LTO_CLANG_THIN_DIST
quiet_cmd_builtin.order = GEN $@
cmd_builtin.order = $(AR) t $< > $@
targets += builtin.order
builtin.order: built-in-fixup.a FORCE
$(call if_changed,builtin.order)
quiet_cmd_ld_thinlto_index = LD $@
cmd_ld_thinlto_index = \
$(LD) $(KBUILD_LDFLAGS) -r --thinlto-index-only=$@ @$<
targets += vmlinux.thinlto-index
vmlinux.thinlto-index: builtin.order FORCE
$(call if_changed,ld_thinlto_index)
quiet_cmd_ar_vmlinux.a = GEN $@
cmd_ar_vmlinux.a = \
rm -f $@; \
while read -r obj; do \
if grep -Fqx $${obj} $(word 2, $^); then \
echo $${obj%.o}.thinlto-native.o; \
else \
echo $${obj}; \
fi; \
done < $< | xargs $(AR) cDPrS --thin $@
targets += vmlinux.a
vmlinux.a: builtin.order vmlinux.thinlto-index FORCE
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.thinlto
$(call if_changed,ar_vmlinux.a)
else
# vmlinux.a
# ---------------------------------------------------------------------------
@@ -28,6 +63,8 @@ targets += vmlinux.a
vmlinux.a: built-in-fixup.a FORCE
$(call if_changed,copy)
endif
# Add FORCE to the prerequisites of a target to force it to be always rebuilt.
# ---------------------------------------------------------------------------

View File

@@ -1487,13 +1487,22 @@ static void extract_crcs_for_object(const char *object, struct module *mod)
char cmd_file[PATH_MAX];
char *buf, *p;
const char *base;
int dirlen, ret;
int dirlen, baselen_without_suffix, ret;
base = get_basename(object);
dirlen = base - object;
ret = snprintf(cmd_file, sizeof(cmd_file), "%.*s.%s.cmd",
dirlen, object, base);
baselen_without_suffix = strlen(object) - dirlen - strlen(".o");
/*
* When CONFIG_LTO_CLANG_THIN_DIST=y, the ELF is *.thinlto-native.o
* but the symbol CRCs are recorded in *.o.cmd file.
*/
if (strends(object, ".thinlto-native.o"))
baselen_without_suffix -= strlen(".thinlto-native");
ret = snprintf(cmd_file, sizeof(cmd_file), "%.*s.%.*s.o.cmd",
dirlen, object, baselen_without_suffix, base);
if (ret >= sizeof(cmd_file)) {
error("%s: too long path was truncated\n", cmd_file);
return;