Repository navigation
fix(uniffi): link-time optimisation reaches the libraries the release ships - #579
Merged
Merged
Conversation
…ibrary tools/capi-fuzz depended on offline-protocol-uniffi by path, which needs the crate to build a Rust library. It now compiles the crate's own lib.rs under its own package, generates the scaffolding from the crate's interface definition in its own build script, and declares the crate's dependencies and features itself. The fuzz target is unchanged. Because the fuzz build no longer links the crate's cdylib, whose coverage hooks only the fuzz binary defined, the same command now runs on macOS with `--sanitizer none`. Checked there: every seed runs, and a 60 second session ran 1.4 million inputs with coverage growing to 2015 edges and no crash.
… ships Cargo runs LTO on a library only when every crate type in its list can take it. The FFI crate listed `lib` beside `cdylib` and `staticlib`, and an rlib cannot, so `lto = true` in the release profile never reached a shipped library from the UniFFI migration to 0.29.0, with no warning. Nothing links the crate as Rust: the one consumer, the fuzzer, now compiles its source. Measured on macOS arm64 at the same commit, fresh target directories: the static library falls from 137,949,536 to 23,393,288 bytes and the shared library from 11,534,416 to 10,932,208, with the same 482 exported functions. The release build passes `-C lto`; the C consumer check, the release C library check and the Python suite (1124 passed) run against the result. Two guards: `the_ffi_crate_builds_no_rust_library` refuses a crate type that turns LTO off, and `the_fuzzer_compiles_the_ffi_crate_with_its_dependencies` holds the fuzzer's copy of the dependencies and features to the crate's. Each was negative-controlled (rlib, lib, a dropped dependency, a dropped feature of a dependency, a new crate feature, a moved interface definition).
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
What was wrong
The release profile sets
lto = true, but cargo runs link-time optimisation on a library only when every crate type in its list can take it. The FFI crate listed["cdylib", "staticlib", "lib"], and an rlib cannot, so cargo passed no LTO flag at all. Every native library from the UniFFI migration (2025-11-07) to 0.29.0 was built without it, on every platform, and no build said so.The rule is in cargo's source (
CrateType::can_ltoand the comment incompiler/lto.rs: "a cdylib/rlib combination won't allow LTO") and is cargo issue rust-lang/cargo#2301, closed in 2017 with a workaround and no warning.The change
cdylibandstaticlib. The release build now passes-C lto.tools/capi-fuzzwas the only thing linking the crate as Rust. It now compiles the crate's ownlib.rsunder its own package, with its own build script generating the scaffolding from the same interface definition. The fuzz target is unchanged.the_ffi_crate_builds_no_rust_libraryrefuses a crate type that turns LTO off;the_fuzzer_compiles_the_ffi_crate_with_its_dependenciesholds the fuzzer's dependencies and features to the crate's, since the fuzz build only runs nightly.Measured
macOS arm64, same commit, fresh target directory each,
capion:liboffline_protocol_uniffi.aliboffline_protocol_uniffi.dylibThe static library is what the iOS framework and the npm package carry, so that is where nearly all of the win lands. The iOS and Android numbers will be read off the first release artifacts built this way.
Verified locally
cargo fmt,cargo clippy --workspace -- -D warnings, clippy without the C binding,cargo test --workspace --lib, rustdoc under-D warnings.scripts/check-c-library.sh macos-arm64against the LTO release dylib.cargo +nightly fuzz run --sanitizer nonefor 60 s on macOS ran 1.4 million inputs with coverage rising to 2015 edges. Fuzzing on macOS did not link before this change;fuzz.ymlanddocs/bridges/c.mdnow say so.Not covered here
fuzz.ymlhas never run. I will dispatch it on this branch.minisize, while six documents say the mobile artifact shipspanic = "abort".