Fold identical functions at link time with --icf=safe - #6547
Merged
Conversation
Enable lld/gold/mold ICF for non-Debug Linux builds of the MeshLib libraries. Guarded by check_linker_flag, so GNU ld (no ICF) is a no-op.
check_linker_flag replaces CMAKE_REQUIRED_LINK_OPTIONS with the flag under test, so the -fuse-ld=lld it needed was dropped and the check probed the image's default ld.bfd -- it failed on every row.
Grantim
approved these changes
Aug 7, 2026
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Identical Code Folding merges byte-identical function bodies at link time. #6535 turned it on for the Python bindings (
--icf=all); this enables it for the MeshLib libraries themselves (libMRMesh.so& co) in non-Debug Linux builds, in the conservativesafemode.safefolds only functions whose address is provably insignificant, so&SomeClass::method/ callback pointers keep comparing unequal — the guarantee a public C++ library owes its users.allstays reserved for the bindings, where nothing observes wrapper addresses.Which linker actually implements it
ICF is a linker feature, not a compiler one — Clang only forwards the option:
lld--icf={none,safe,all}gold--icf=[none,all,safe]— "folds ctors, dtors and functions whose pointers are definitely not taken"mold--icf=[none,all,safe]ld(bfd)ld-no_deduplicate("don't run deduplication pass in linker"), i.e. it already folds by defaultNOT APPLEwasm-ldduplicate-function-eliminationNOT MR_EMSCRIPTENlink/OPT:ICFSo the answer for the GNU toolchain:
ld.bfdhas no ICF at all;goldandmoldaccept the very same spelling. GCC's-fipa-icf(on at-O2) is a compile-time analogue that folds within a translation unit only — it is not a substitute for cross-object folding of template instantiations.There is a second, subtler reason the win is Clang-only:
safemode decides what may be folded from the address-significance table (.llvm_addrsig), which Clang emits by default while GCC emits nothing of the kind. So evengcc + mold --icf=safewould fold almost nothing; it takes--icf=allto fold GCC output, and that is exactly the mode we do not want here.That is why the flag goes inside the existing
MESHLIB_HAVE_LLDblock instead of getting a probe of its own: there lld is already established as the linker by its owncheck_linker_flag, and no row outside it can use the option anyway. Debug is excluded through the same$<$<NOT:$<CONFIG:Debug>>:…>genex that the neighbouring-Wl,-xuses.A guarding
check_linker_flag(CXX "-Wl,--icf=safe" …)was the first attempt and is a trap worth recording: the module replacesCMAKE_REQUIRED_LINK_OPTIONSwith the flag under test (set(CMAKE_REQUIRED_LINK_OPTIONS "${_flag}")inInternal/CheckLinkerFlag.cmake), so the-fuse-ld=lldpassed alongside it is dropped and the probe tests the image's default linker —ld.bfd, which has no ICF. The check therefore reportedFailedon the Clang 21 rows as well, and the flag was silently never applied while CI stayed green.Practically this means the Clang-built artifacts — the
linux-vcpkgpackage, the AppImage and the manylinux wheels, all built withclang++in therockylinux8-vcpkgimage — get smaller, while the GCC-built.debs are bit-for-bit unaffected until they too move to lld/mold (possible follow-up).Measured effect
A/B on the same commit, same image,
Release/Clang 21/ lld, the flag being the only difference (throwaway branch, both runs measured identically). Raw sizes inbuild/Release/bin:libMeshLibC2.solibMRMesh.solibMRViewer.solibMRVoxels.solibMRCommonPlugins.soMRTest.soThe compressed package moves much less:
meshlib_linux-vcpkg-x64.tar.xz68,720,612 → 68,665,336 B (−0.08%), arm64 65,517,604 → 65,005,316 B (−0.78%). That is expected — what ICF removes is duplicated code, which xz was already encoding as back-references, so the win is in installed footprint and resident pages rather than in download size. (Unlike #6535, where the bindings' zip win tracked the raw win; those duplicates were bulkier and more numerous.)Two caveats so the per-file numbers aren't over-read:
-z separate-loadable-segments(the patchelf work-around directly above) pads each loadable segment to max-page-size, so file sizes quantize. Several small arm64 libraries show a flat 0 while having shrunk by less than one page. The aggregate row is the number to trust.0.0.0— a few bytes of difference, but it makes the sub-percent package figures less airtight than the raw ones.Link time was not measured; I have timings only for the ICF side, so there is no honest comparison to quote yet.
Testing
Unit, C-unit, Python sanity and regression tests run in the Linux legs with folding active. Windows and macOS are disabled for this PR: the code path is guarded by
UNIX AND NOT APPLE, so neither can be reached.