From 68910161491cdd161bff29a32032e52301831164 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 15 May 2026 20:46:54 +0000 Subject: [PATCH] meson: shrink developer-mode build artifacts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two complementary changes in the developer-mode branch of meson.build: 1. -ffunction-sections -fdata-sections: pair with the existing -Wl,--gc-sections so the linker can drop unused individual functions and data instead of being forced to pull whole .o files into each binary. Biggest impact on statically-linked NSS/PAM modules (a single call into creds-util.c used to drag in the entire creds-util translation unit, which transitively pulled TPM2, OpenSSL, PKCS11 and KDF helpers via tpm2-util.c / openssl-util.c) and on tests that embed daemon objects via meson's objects: extraction. 2. -gz=zstd + -fdebug-types-section + -Wl,--compress-debug-sections=zstd: compress every .debug_* section with zstd, and move type DIEs into a COMDAT-mergeable section so identical types described across many TUs land once. Both are transparent to GDB / readelf / addr2line. Gated to mode == 'developer' for now: no major distro (Fedora, Debian/ Ubuntu, Arch, Alpine, Gentoo, openSUSE, Yocto) enables -ffunction-sections in their system-wide default CFLAGS, and the interaction with -flto=auto + -ffat-lto-objects (which Fedora et al. ship by default) deserves a broader evaluation before turning it on for release builds. Developer mode benefits straightforwardly: smaller plugins, smaller tests, smaller libraries, no interference with the hardening/LTO flag combinations distros pin. Size impact on a clean developer-mode build, 626 ELF objects: | Category | n | Before | After | Δ | % | | -------------------------- | --: | ----------: | ----------: | --------------: | ----: | | NSS plugins | 4 | 22,163,272 | 6,681,120 | −15,482,152 | −69.9 | | PAM plugins | 3 | 18,132,160 | 5,764,880 | −12,367,280 | −68.2 | | libsystemd.so (public ABI) | 1 | 6,009,360 | 3,350,168 | −2,659,192 | −44.3 | | libudev.so (public ABI) | 1 | 4,379,336 | 1,441,256 | −2,938,080 | −67.1 | | libsystemd-shared-261.so | 1 | 15,130,264 | 11,293,952 | −3,836,312 | −25.4 | | libsystemd-core-261.so | 1 | 7,356,408 | 4,992,600 | −2,363,808 | −32.1 | | cryptsetup-token plugins | 3 | 139,800 | 113,344 | −26,456 | −18.9 | | daemons / CLI tools | 178 | 43,581,288 | 31,911,368 | −11,669,920 | −26.8 | | test binaries | 386 | 124,717,072 | 60,185,904 | −64,531,168 | −51.7 | | fuzzers | 48 | 33,138,056 | 13,864,952 | −19,273,104 | −58.2 | | **TOTAL** | 626 | 274,747,016 | 139,599,544 | **−135,147,472** | **−49.2** | Biggest individual wins: | Binary | Before | After | Δ | | ------------------------ | -------: | -------: | -----: | | test-networkd-address | 6.21 MB | 0.82 MB | −86.8% | | test-network-tables | 6.23 MB | 0.85 MB | −86.4% | | test-networkd-conf | 6.27 MB | 1.32 MB | −78.9% | | libnss_myhostname.so.2 | 6.40 MB | 1.53 MB | −76.1% | | fuzz-netdev-parser | 6.22 MB | 1.51 MB | −75.7% | | fuzz-network-parser | 6.22 MB | 1.70 MB | −72.6% | | libnss_systemd.so.2 | 6.90 MB | 2.04 MB | −70.4% | | libnss_resolve.so.2 | 4.42 MB | 1.38 MB | −68.8% | | pam_systemd_home.so | 6.91 MB | 2.22 MB | −67.9% | | libudev.so.1.7.13 | 4.18 MB | 1.37 MB | −67.1% | | pam_systemd.so | 7.02 MB | 2.55 MB | −63.6% | | libsystemd-shared-261.so | 14.43 MB | 10.77 MB | −25.4% | The big test wins come from the ~30 daemons (systemd-networkd, systemd-resolved, systemd-journald, systemd-logind, systemd-homed, systemd-importd, systemd-machined, …) whose compiled .o files are embedded directly into their unit tests via meson's objects: extraction mechanism. With per-function sections on the daemon sources, the test binary can GC the bulk of code it never exercises; the remaining DWARF is then shared zstd-compressed across every .o. Build-speed cost is below noise on a 24-core build: across four clean builds (with-flags / sections-only / baseline / with-flags rerun) the range was 23.6–26.0 s real time and 7m39s–7m48s user time, with the two with-flags runs faster than the baseline by a couple of seconds — overhead from per-function-section bookkeeping and zstd compression disappears into parallel-build noise. --- meson.build | 6 ++++++ .../build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/meson.build b/meson.build index 5b880cd95a..98dfb67840 100644 --- a/meson.build +++ b/meson.build @@ -496,6 +496,12 @@ possible_cc_flags = [ if get_option('mode') == 'developer' possible_cc_flags += '-fno-omit-frame-pointer' + # Let -Wl,--gc-sections drop unused functions/data instead of whole + # .o files; biggest win on statically-linked NSS/PAM modules. + possible_cc_flags += ['-ffunction-sections', '-fdata-sections'] + # Compress and dedupe DWARF; transparent to GDB / readelf. + possible_cc_flags += ['-gz=zstd', '-fdebug-types-section'] + possible_link_flags += '-Wl,--compress-debug-sections=zstd' endif add_project_arguments( diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot index 02d4c3e5cb..3db6028903 100755 --- a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot +++ b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot @@ -40,6 +40,12 @@ if [[ -d "$SRCDIR/debian/patches" ]]; then mount --bind /tmp/patches "$SRCDIR/debian/patches" fi +# mode=developer enables -Wl,--compress-debug-sections=zstd in meson.build, which dwz cannot process. +cp "$SRCDIR/debian/rules" /tmp/rules +chmod +x /tmp/rules +printf '\noverride_dh_dwz:\n' >>/tmp/rules +mount --bind /tmp/rules "$SRCDIR/debian/rules" + # While the build directory can be specified through DH_OPTIONS, the default one is hardcoded everywhere so # we have to use that. Because it is architecture dependent, we query it using dpkg-architecture first. DEB_HOST_GNU_TYPE="$(dpkg-architecture --query DEB_HOST_GNU_TYPE)"