]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
kbuild: distributed build support for Clang ThinLTO
authorRong Xu <xur@google.com>
Fri, 29 May 2026 18:53:46 +0000 (11:53 -0700)
committerNathan Chancellor <nathan@kernel.org>
Fri, 29 May 2026 20:44:29 +0000 (13:44 -0700)
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>
.gitignore
Makefile
arch/Kconfig
scripts/Makefile.lib
scripts/Makefile.thinlto [new file with mode: 0644]
scripts/Makefile.vmlinux_a
scripts/mod/modpost.c

index 3044b9590f058f94fb5334d75765444066488b8f..1cc34c9a552344b87e80474240bf237ac4791d07 100644 (file)
@@ -57,6 +57,7 @@
 *.zst
 Module.symvers
 dtbs-list
+builtin.order
 modules.order
 
 #
@@ -68,6 +69,7 @@ modules.order
 /vmlinux.32
 /vmlinux.map
 /vmlinux.symvers
+/vmlinux.thinlto-index
 /vmlinux.unstripped
 /vmlinux-gdb.py
 /vmlinuz
index 0fce9557a115733c7c68caea07eb0c0f3642dd29..857f08dcc952382672c71b714a46d4d1698961d8 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1071,11 +1071,16 @@ export CC_FLAGS_SCS
 endif
 
 ifdef CONFIG_LTO_CLANG
-ifdef CONFIG_LTO_CLANG_THIN
+ifdef CONFIG_LTO_CLANG_FULL
+CC_FLAGS_LTO   := -flto
+else
 CC_FLAGS_LTO   := -flto=thin -fsplit-lto-unit
+
+# These LLVM options were initially added with only in-process ThinLTO
+# support, so avoid distributed ThinLTO support for now.
+ifdef CONFIG_LTO_CLANG_THIN
 KBUILD_LDFLAGS += $(call ld-option,--lto-whole-program-visibility -mllvm -always-rename-promoted-locals=false)
-else
-CC_FLAGS_LTO   := -flto
+endif
 endif
 CC_FLAGS_LTO   += -fvisibility=hidden
 
@@ -1684,6 +1689,7 @@ endif # CONFIG_MODULES
 CLEAN_FILES += vmlinux.symvers modules-only.symvers \
               modules.builtin modules.builtin.modinfo modules.nsdeps \
               modules.builtin.ranges vmlinux.o.map vmlinux.unstripped \
+              vmlinux.thinlto-index builtin.order \
               compile_commands.json rust/test \
               rust-project.json .vmlinux.objs .vmlinux.export.c \
                .builtin-dtbs-list .builtin-dtbs.S
@@ -2145,7 +2151,7 @@ clean: $(clean-dirs)
        $(call cmd,rmfiles)
        @find . $(RCS_FIND_IGNORE) \
                \( -name '*.[aios]' -o -name '*.rsi' -o -name '*.ko' -o -name '.*.cmd' \
-               -o -name '*.ko.*' \
+               -o -name '*.ko.*' -o -name '*.o.thinlto.bc' \
                -o -name '*.dtb' -o -name '*.dtbo' \
                -o -name '*.dtb.S' -o -name '*.dtbo.S' \
                -o -name '*.dt.yaml' -o -name 'dtbs-list' \
index 5d6e9f56210bd8ba8566f6d42212452bb369d65c..0848932d1c8e894f13bef907e0f762a15c3931b4 100644 (file)
@@ -858,6 +858,25 @@ config LTO_CLANG_THIN
            https://clang.llvm.org/docs/ThinLTO.html
 
          If unsure, say Y.
+
+config LTO_CLANG_THIN_DIST
+       bool "Clang ThinLTO in distributed mode (EXPERIMENTAL)"
+       depends on HAS_LTO_CLANG && ARCH_SUPPORTS_LTO_CLANG_THIN
+       select LTO_CLANG
+       help
+         This option enables Clang's ThinLTO in distributed build mode.
+         In this mode, the linker performs the thin-link, generating
+         ThinLTO index files. Subsequently, the build system explicitly
+         invokes ThinLTO backend compilation using these index files
+         and pre-linked IR objects. The resulting native object files
+         are with the .thinlto-native.o suffix.
+
+         This build mode offers improved visibility into the ThinLTO
+         process through explicit subcommand exposure. It also makes
+         final native object files directly available, benefiting
+         tools like objtool and kpatch. Additionally, it provides
+         crucial granular control over back-end options, enabling
+         module-specific compiler options, and simplifies debugging.
 endchoice
 
 config ARCH_SUPPORTS_AUTOFDO_CLANG
index 0718e39cedda2a3d2605c71822b20bf7aa922918..86e1428cc55d6f0c1c070ac0fd0cc70b546f227b 100644 (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)
diff --git a/scripts/Makefile.thinlto b/scripts/Makefile.thinlto
new file mode 100644 (file)
index 0000000..bb83f13
--- /dev/null
@@ -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)
index 650d44330d1f381f22110fa5660b8445bc50ebbf..bd141b8937480c59a2fa1618018af5caa34578af 100644 (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.
 # ---------------------------------------------------------------------------
 
index 0d2f1f09019bafc31d291daad91b4b9c285b7ddf..da0f4bc755094d0b34c480aa0adf359980f4ef0f 100644 (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;