From 4fdfdafb644d94660c47deb7fc933c549bbb27b3 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Mon, 9 Nov 2026 21:32:37 +0200 Subject: [PATCH] build: shrink the release binary with fat LTO and one codegen unit Cargo.toml had no [profile.release], so release builds used 16 codegen units and no cross-crate LTO (release.yml's comment claiming LTO was on by default was wrong). rustc gives each codegen unit its own copy of every closure and async body it uses, which left about a fifth of .text as duplicates: `main`'s async body alone appeared three times. Stripped macOS arm64 binary: 13458360 -> 9181064 bytes (-32%), .text 9353156 -> 6844104. Threaded wasm: 4911967 -> 4153657 bytes (it gets the single codegen unit but no LTO, since cargo can't LTO a cdylib+rlib lib). Panics stay `unwind`. `abort` would save another ~2 MiB, but it would turn a panic in one peer session's task into the end of the whole process. release.yml now builds only `--bin hoverfly`, so the unshipped sigcheck doesn't cost a second fat-LTO link. --- .github/workflows/release.yml | 11 +++++++---- AGENTS.md | 5 +++++ Cargo.toml | 11 +++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85c167d..97abdbd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -131,14 +131,17 @@ jobs: - name: Build release binary env: - # Smaller, more portable binaries. LTO is on by default in - # the `release` profile; this just makes binaries strip-friendly. + # LTO and codegen units come from `[profile.release]` in + # Cargo.toml. Symbols are left in here and removed by the + # target's own `strip` in the next step. CARGO_PROFILE_RELEASE_STRIP: none + # `--bin hoverfly`: sigcheck isn't shipped, so don't pay for a + # second fat-LTO link. run: | if [ "${{ matrix.use-cross }}" = "true" ]; then - cross build --release --locked --target ${{ matrix.target }} + cross build --release --locked --target ${{ matrix.target }} --bin hoverfly else - cargo build --release --locked --target ${{ matrix.target }} + cargo build --release --locked --target ${{ matrix.target }} --bin hoverfly fi - name: Strip binary diff --git a/AGENTS.md b/AGENTS.md index a67d8a4..6b9fc8f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,6 +70,11 @@ are in flight at once. Public API (`Behaviour`, `Control`, `IncomingStreams`, - Native: `cargo build` / `cargo build --release`. Release is ~2-5× faster on crypto paths but only ~10-15% end-to-end (network dominates). +- `[profile.release]` is tuned for binary size: fat LTO, `codegen-units = 1`. + A release build is a full slow rebuild; iterate on `cargo build`. Don't + switch it to `panic = "abort"` for the extra ~2 MiB: unwinding is what + confines a panic to the one tokio task (peer session, daemon request, + relay connection) it happened in. - Edition **2024**; crate version tracked in `Cargo.toml` (`0.x`, currently `0.3.0`). - Default features are `cli`, `bridge`, `pusher`. `--no-default-features diff --git a/Cargo.toml b/Cargo.toml index 60952ac..10ab81e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -189,3 +189,14 @@ tokio-test = "0.4" # from `proto/*.proto`. Not pulled into normal `cargo build` — invoke # explicitly via `scripts/regen-protos.sh`. prost-build = "0.13" + +# Binary size over build time. Without LTO each of the default 16 codegen +# units carries its own copy of every async body and closure it touches; +# fat LTO with a single unit merges them. cargo can't LTO the cdylib+rlib lib +# target, so the wasm build only gets the single unit. Panics stay `unwind` +# despite the unwind tables: each peer session drives its swarm in its own +# task, and a panic there (say, a dependency choking on a peer's bytes) must +# drop that session, not the whole upload, daemon or relay. +[profile.release] +lto = "fat" +codegen-units = 1