Skip to content

Put each function in its own section so ICF can fold more - #6550

Merged
Fedr merged 1 commit into
masterfrom
linker-function-sections
Aug 7, 2026
Merged

Put each function in its own section so ICF can fold more#6550
Fedr merged 1 commit into
masterfrom
linker-function-sections

Conversation

@Fedr

@Fedr Fedr commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #6547. --icf=safe can only fold what the compiler put in its own section: template instantiations and inlines get COMDAT sections automatically, but plain out-of-line functions share one .text per translation unit and are invisible to folding. -ffunction-sections gives each of them its own section.

Same gate as the ICF flag — inside the MESHLIB_HAVE_LLD block, non-Debug only — so nothing changes for GCC rows, macOS, emscripten or Debug builds. Restricted to C/C++ via $<COMPILE_LANGUAGE:C,CXX>, because add_compile_options would otherwise hand -ffunction-sections to nvcc, which does not accept it. -fdata-sections is deliberately absent: lld's ELF ICF folds executable sections only.

Sizes: −2.0% once stripped, on both arches

Release / Clang 21 / lld, all 14 .so, baseline = master (--icf=safe already in):

baseline -ffunction-sections delta
x64, unstripped 62,285,048 61,363,448 −921,600 (−1.48%)
x64, stripped 46,020,984 45,099,384 −921,600 (−2.00%)
arm64, unstripped 64,009,152 65,737,856 +1,728,704 (+2.70%)
arm64, stripped 46,360,888 45,443,384 −917,504 (−1.98%)

The arm64 growth in unstripped builds is entirely symbol tables — per-function sections mean per-function section symbols:

arm64 libMeshLibC2.so baseline variant delta
.symtab 2,476,176 4,485,936 +2,009,760
.strtab 4,250,085 4,501,305 +251,220
.gcc_except_table 1,087,764 195,492 −892,272

.symtab/.strtab are not SEC_ALLOC: never loaded at runtime, and removed by strip. Everything we ship is stripped — wheels via #6525, the AppImage by linuxdeploy (which strips unless NO_STRIP is set) — so the +2.70% never reaches a user.

Where the win comes from

Not .text. Summed over all .so (x64 / arm64):

section delta
.gcc_except_table −845,124 / −916,720
.text −55,192 / −58,592
.eh_frame + .eh_frame_hdr −18,292 / −20,348

The folding is mostly exception-handling tables. libMeshLibC2.so alone drops .gcc_except_table from 1,045,892 to 220,576 — −79% — while its .text is byte-identical: the generated C bindings are thousands of near-identical try/catch wrappers, so their LSDAs are duplicates even where the surrounding code is not.

Caveat before merging

scripts/distribution_vcpkg.sh does not strip (cmake --install without --strip), so the linux-vcpkg tar.xz is the one artifact that would carry the extra symbol tables. Its compressed delta is unmeasured and could go either way: the added .symtab and the removed .gcc_except_table both compress well. Two ways to settle it — measure the package both ways, or strip in the packaging script, which makes the question moot and shrinks that artifact regardless of this PR.

Testing

Linux legs only; Windows and macOS disabled as unreachable behind UNIX AND NOT APPLE. All four linux-vcpkg legs green across every measurement run.

--icf=safe only sees COMDAT functions without this: plain out-of-line
functions share one .text per translation unit and never fold. CUDA is
excluded -- nvcc does not take -ffunction-sections.
@Fedr Fedr changed the title Put each function in its own section so ICF can fold more Put each function in its own section so ICF can fold more (measurement: arm64 regresses) Aug 7, 2026
@Fedr Fedr changed the title Put each function in its own section so ICF can fold more (measurement: arm64 regresses) Put each function in its own section so ICF can fold more Aug 7, 2026
@Fedr
Fedr merged commit 9c63862 into master Aug 7, 2026
45 checks passed
@Fedr
Fedr deleted the linker-function-sections branch August 7, 2026 14:08
Fedr added a commit that referenced this pull request Aug 7, 2026
The linux-vcpkg tar.xz was the last artifact shipping unstripped libraries: the wheels (#6525) and the AppImage (linuxdeploy strips unless NO_STRIP is set) have always been stripped. `.symtab`/`.strtab` are not SEC_ALLOC, are never loaded at runtime, and linking against the package needs only `.dynsym`, which strip keeps.

The archive shrinks 1,504,076 B on x64 (-2.19%) and 2,131,340 B on arm64 (-3.26%); an unpacked install shrinks by the full 16.3 MB / 17.6 MB of symbol tables. This also removes the only place where `-ffunction-sections` (#6550) inflated `.symtab` visibly.

Third-party libraries copied from vcpkg are left alone: blanket-stripping that tree would hit `.a` archives, where it breaks linking. Backtraces inside the shipped libraries now lose the names of non-exported functions, as is already the case for every wheel and AppImage we publish.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants