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.
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(
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)"