]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
meson: shrink developer-mode build artifacts
authorDaan De Meyer <daan@amutable.com>
Fri, 15 May 2026 20:46:54 +0000 (20:46 +0000)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 20 May 2026 14:27:10 +0000 (16:27 +0200)
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
mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot

index 5b880cd95a7e1cdf1934d034205f2addc1f4b217..98dfb6784049dfde28715e67cfc72b5c71a78968 100644 (file)
@@ -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(
index 02d4c3e5cb9cff052ab4ac30d88bc12f704ca34e..3db60289037ac11e17f63d0565a0c69d46c08066 100755 (executable)
@@ -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)"