perf(link): stop exporting every symbol from native macOS executables#6533
Conversation
A perry-compiled executable carried a dynamic export trie with every external Rust/codegen symbol — 300,281 exports on a large app binary — with MH_WEAK_DEFINES/MH_BINDS_TO_WEAK set, so dyld spent ~0.9s of EVERY launch on weak-def coalescing and bind resolution against it. Profiling a large compiled app's --version: ~80% of the 1.17s wall time was inside dyld (trieWalk / resolveSymbol / weak-def map inserts). Nothing consumes those exports: plugins dlopen their own dylibs and resolve symbols from their own handle (never dlsym(RTLD_DEFAULT/SELF) into the host), and the plugin-host mode — which does need exports — force-exports its API via -u and keeps the trie (gated on ctx.needs_plugins, unchanged here). Pass -Wl,-no_exported_symbols on the native macOS/iOS clang link when plugins are off. Exports: 300,281 -> 3; WEAK_DEFINES/BINDS_TO_WEAK cleared; binary 5.1MB -> 4.9MB on a small program. Measured (large app binary, hyperfine): --version 1.168s of which ~0.9s was dyld — the dyld share drops to near zero (numbers for the full binary in the PR body).
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughApple-platform non-plugin builds now pass ChangesPlatform linker export control
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Full-app numbers (197MB binary, the large esbuild-bundled TUI app, hyperfine, 10 runs, isolated HOME):
The compiled binary's Verification on this branch's toolchain: perry-runtime suite 1327/0 (single-threaded), gap shards 5/8 and 7/8 locally green (only reds: pre-triaged entries + the known local zlib |
Problem
A perry-compiled executable exports every external Rust/codegen symbol — 300,281 exports on a large app binary — with
MH_WEAK_DEFINES/MH_BINDS_TO_WEAKset. dyld pays for that at every launch: weak-def coalescing plus bind-target resolution against a 300k-entry export trie.Measured on a large compiled TUI app:
--versiontakes 1.168s wall, and sampling shows ~80% of it inside dyld (trieWalk,resolveSymbol,hasExportedSymbol, weak-def map inserts) — ~0.9s of pure dynamic-linker overhead on every single launch, before any app code runs. node runs the same app's--versionin 328ms.Fix
Pass
-Wl,-no_exported_symbolson the native macOS/iOS clang link when plugins are off.Safety audit: nothing consumes the exports —
dlopentheir own dylibs anddlsymfrom their own handle (plugin.rsusesRTLD_NOW | RTLD_LOCAL, neverdlopen(NULL)/RTLD_DEFAULT/RTLD_SELFinto the host);-uand is explicitly excluded (!ctx.needs_plugins, unchanged behavior).Numbers
WEAK_DEFINES/BINDS_TO_WEAKheader flags cleared.Summary by CodeRabbit