perf(size): -1.47 MiB (-13.3%) stripped binary — build-config levers + control-plane demonomorphization - #1055
Conversation
Three levers, each measured on the stripped release demo (the binary-size CI metric), against the current main baseline of 11,590,552 bytes: - [profile.release.package.libsqlite3-sys] opt-level="z": the bundled sqlite3.c amalgamation was the one crate the per-package opt-level sweep missed — it compiled at -O3. Same I/O-bound tradeoff already accepted for diesel/r2d2/sqlite-storage. Measured -856 KiB. - -Zshare-generics=y (via .cargo/config.toml, linux-x64 scoped): stops per-crate re-instantiation of cross-crate generics that fat LTO keeps as distinct symbols; the Docker image build already ran with it. Measured -335 KiB. - lld with --icf=all (linux-x64 scoped): tighter layout of the fat-LTO object plus identical-section folding. Measured -73 KiB. - LIBSQLITE3_FLAGS trim: drops bundled-SQLite subsystems nothing in the workspace uses (FTS3, RTREE, STAT4, DBSTAT, SOUNDEX, JSON SQL, loadable extensions, shared cache, progress callbacks). FTS5 stays for chat-store's search feature. Consumers building whatsapp-rust from crates.io are unaffected — .cargo/config.toml and profile overrides only apply to workspace builds. Co-Authored-By: Claude <noreply@anthropic.com>
execute<S> stamped the request-id generation, direct-encode branch, send/wait glue and error mapping once per IqSpec (~55 live specs). The generic shell now only encodes/builds/parses the spec; the shared body lives in non-generic helpers, so each spec adds a thin wrapper instead of a full copy. IQ dispatch is control-plane — no hot-path cost. Co-Authored-By: Claude <noreply@anthropic.com>
…cs, guard proto Debug
Four coordinated reductions, all off the per-message hot path:
- waproto::codec sweep: every remaining production call site of buffa's
generic Message methods on waproto types outside waproto now routes
through the #[inline(never)] pinned helpers (17 new helpers added for
the small roots: app-state key fingerprints, msmsg, event responses,
reactions, shortcake, verified-name certs, noise cert chains, chat-store
message codec). A clippy disallowed-methods guard now rejects new direct
call sites; libsignal's signal-wire protos keep direct calls (sole
instantiation site and per-message hot path — pinning buys nothing).
- mex: execute_request<V> splits into a thin per-V wrapper plus one
non-generic execute body (was stamped per variables type).
- appstate sync: the FDownload closure generic across eight wacore fns is
now a &BlobDownloadFn trait object — one instantiation of the large
patch-processing bodies (breaking for wacore consumers: parameter is
now a dyn ref).
- upload: the 8-type-parameter retry/failover driver takes boxed futures;
media upload wraps whole HTTP transfers, so a BoxFuture per attempt is
noise.
- group IQs: the two IQ macros share one outlined build_iq body instead
of stamping it into 8 generated types.
- Debug guards: the one reachable {:?} of a waproto type (bot.rs device
props override) logs fields instead, and Event's derived Debug is now a
manual variant-name impl so no future log line can resurrect the
627-impl generated proto Debug graph.
Co-Authored-By: Claude <noreply@anthropic.com>
sqlite-storage's in-memory mode shares one DB across pooled connections via cache=shared URIs; SQLITE_OMIT_SHARED_CACHE made every reopen see a fresh empty DB (caught by the save/load roundtrip tests). Co-Authored-By: Claude <noreply@anthropic.com>
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR centralizes protobuf serialization through ChangesCodec and runtime changes
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 |
|
| Filename | Overview |
|---|---|
| .cargo/config.toml | New workspace build-config: -Zshare-generics=y, rust-lld with ICF, LIBSQLITE3_FLAGS subsystem trim; scoped to x86_64-linux-gnu, no system-lld dependency (uses bundled rust-lld via -Clinker-features=+lld) |
| .github/workflows/main.yml | Adds RUSTFLAGS: empty string to the stable toolchain job so the nightly-only -Zshare-generics flag does not break the stable CI gate |
| Cargo.toml | Adds [profile.release.package.libsqlite3-sys] opt-level=z so the C amalgamation compiles at -Oz via the cc crate OPT_LEVEL export |
| clippy.toml | Adds 10 disallowed-methods entries for buffa::message::Message codec methods to enforce routing through waproto::codec pinned wrappers |
| src/request.rs | Introduces PreparedIq enum and non-generic execute_prepared tail so the IQ send/wait body is monomorphized once instead of per-IqSpec |
| src/upload.rs | Introduces upload_media_with_retry_dyn with boxed-future dyn callbacks, collapsing the large driver body to a single instantiation |
| wacore/src/appstate_sync.rs | Replaces all FDownload type parameters with &BlobDownloadFn trait-object references; breaking change documented in PR description |
| waproto/src/lib.rs | Adds 17 inline(never) pinned codec wrappers with disallowed_methods guard scoped to the codec module as the single sanctioned instantiation site |
| wacore/src/types/events.rs | Replaces derived Debug on Event with a manual variant-name-only impl to avoid pulling in the entire proto Debug graph |
| wacore/src/iq/groups.rs | Extracts build_participant_action_iq and build_property_toggle_iq free functions shared by macro-generated IqSpec impls |
| wacore/src/sync_marker.rs | Adds MaybeSend trait alongside MaybeSendSync for constraining upload closures that cross task boundaries |
| wacore/libsignal/src/protocol/protocol.rs | Adds disallowed_methods allow attributes to Signal protocol methods that are the sole instantiation site for their protos |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Client::execute S"] --> B["spec.encode_iq_direct"]
B -- "Ok true" --> C["PreparedIq::Encoded buf"]
B -- "Ok false" --> D["spec.build_iq -> PreparedIq::Query"]
B -- "Err" --> E["return IqError::EncodeError"]
C --> F["execute_prepared (non-generic)"]
D --> F
F -- "Encoded path" --> G["send_and_wait_iq"]
F -- "Query path" --> H["send_iq"]
G --> I["Arc OwnedNodeRef"]
H --> I
I --> J["spec.parse_response"]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["Client::execute S"] --> B["spec.encode_iq_direct"]
B -- "Ok true" --> C["PreparedIq::Encoded buf"]
B -- "Ok false" --> D["spec.build_iq -> PreparedIq::Query"]
B -- "Err" --> E["return IqError::EncodeError"]
C --> F["execute_prepared (non-generic)"]
D --> F
F -- "Encoded path" --> G["send_and_wait_iq"]
F -- "Query path" --> H["send_iq"]
G --> I["Arc OwnedNodeRef"]
H --> I
I --> J["spec.parse_response"]
Reviews (2): Last reviewed commit: "fix(build): address PR review — bundled ..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c2baa3680e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # byte-identical sections (measured -73 KiB on the stripped demo). Scoped to | ||
| # this target so wasm and non-Linux builds keep their default linkers. | ||
| rustflags = [ | ||
| "-Zshare-generics=y", |
There was a problem hiding this comment.
Keep nightly rustflags out of the stable build
The test-stable job in .github/workflows/main.yml explicitly installs the stable toolchain and then runs several x86_64 Linux Cargo builds, so this target-level flag is applied there and makes every build fail before compilation. rustc --help -v describes -Z as “unstable compiler options,” and stable rustc rejects them; the repository's pinned nightly does not override the toolchain selected by dtolnay/rust-toolchain@stable. Apply this size flag only to the nightly release build or otherwise exclude the stable job.
Useful? React with 👍 / 👎.
|
|
||
| /// Boxed future for the dyn-driven retry loop below. `Send` keeps the upload | ||
| /// futures spawnable, as they were with the fully generic signature. | ||
| type BoxFut<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>; |
There was a problem hiding this comment.
Preserve non-Send upload futures on wasm
When compiling the supported wasm32-unknown-unknown library target checked by .github/workflows/wasm.yml, HttpClient uses async_trait(?Send) and MaybeSendSync deliberately drops Send + Sync, so the futures returned by self.http_client.execute(...) in Client::upload are not guaranteed to be Send. Requiring Send in this boxed future and in all adapter bounds therefore makes the upload call sites fail to type-check on wasm; use target-conditional boxed-future and closure bounds like the existing IqSendFuture abstraction.
Useful? React with 👍 / 👎.
| # this target so wasm and non-Linux builds keep their default linkers. | ||
| rustflags = [ | ||
| "-Zshare-generics=y", | ||
| "-Clink-arg=-fuse-ld=lld", |
There was a problem hiding this comment.
Avoid requiring an undeclared lld installation
On an x86_64 Linux checkout where the normal system linker is installed but LLVM's ld.lld is not, every Cargo build now fails at link time because this target-wide flag unconditionally selects lld. GCC's --help=common describes -fuse-ld=lld as selecting the LLVM linker, but neither rustup nor Cargo installs that external executable, and the repository's build instructions and CI setup do not declare it as a prerequisite. Limit this linker choice to the measured release/size build or add an explicit, reliable lld setup instead of imposing it on ordinary cargo test and debug builds.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.cargo/config.toml:
- Around line 11-18: Extend the target-specific Rust flags configuration so
-Zshare-generics=y is also enabled for macOS and AArch64 targets, while keeping
the lld and ICF linker arguments restricted to the Linux configuration where
they are supported.
In `@clippy.toml`:
- Around line 11-16: Extend the clippy configuration beside the existing Message
method bans to also reject the generic buffer method decode and the
length-delimited methods encode_length_delimited and decode_length_delimited.
Add entries with reasons directing callers to the corresponding pinned
waproto::codec wrappers, preserving the existing bloat-prevention policy.
In `@src/bot.rs`:
- Around line 1313-1320: Replace the heap-allocating version String construction
in the override_.version formatting expression with a lightweight wrapper that
borrows the version components and implements custom Debug formatting. Use that
wrapper directly in the debug log so formatting remains equivalent while
avoiding allocation, preserving the existing zero defaults for missing primary,
secondary, and tertiary values.
In `@src/prekeys.rs`:
- Line 1065: The test fixtures currently bypass production codec wrappers
through direct buffa APIs and disallowed-method allowances. Migrate the
window_tests in src/prekeys.rs:1065-1065, the shortcake tests in
wacore/src/shortcake.rs:290-293, and the business tests in
wacore/src/stanza/business.rs:384-387 to use waproto::codec wrappers, then
remove the corresponding #[allow(clippy::disallowed_methods)] attributes.
In `@src/store/signal.rs`:
- Line 287: Replace the redundant byte-vector slice conversions by relying on
Rust deref coercion: update src/store/signal.rs lines 287-287 in the
pre_key_record_decode call to borrow bytes directly, and update
wacore/noise/src/handshake.rs lines 190-192 and 224-224 to pass
intermediate_details_bytes and leaf_details_bytes directly instead of calling
as_slice().
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e9b809bf-5ba1-4602-95c7-65ea6d43e274
📒 Files selected for processing (66)
.cargo/config.toml.gitignoreCargo.tomlclippy.tomlsrc/appstate_sync.rssrc/bot.rssrc/client/device_registry.rssrc/client/lid_pn.rssrc/features/message_edit.rssrc/features/mex.rssrc/features/rotate_key.rssrc/history_sync.rssrc/message/msg_secret.rssrc/message/special.rssrc/message/tests.rssrc/passkey/flow.rssrc/pdo.rssrc/prekeys.rssrc/reexports_test.rssrc/request.rssrc/send/mod.rssrc/store/signal.rssrc/upload.rsstorages/chat-store/src/materialize.rsstorages/chat-store/src/queries.rsstorages/chat-store/src/store.rsstorages/chat-store/tests/chat_store_test.rsstorages/sqlite-storage/src/wire.rstests/handshake_integration.rswacore/appstate/src/decode.rswacore/appstate/src/processor.rswacore/benches/history_sync_benchmark.rswacore/benches/message_utils_benchmark.rswacore/benches/reporting_token_benchmark.rswacore/benches/send_receive_benchmark.rswacore/libsignal/src/protocol/identity_key.rswacore/libsignal/src/protocol/protocol.rswacore/libsignal/src/protocol/sender_keys.rswacore/libsignal/src/protocol/state/session.rswacore/noise/src/handshake.rswacore/noise/src/test_util.rswacore/noise/tests/cert_chain_verify.rswacore/src/adv.rswacore/src/appstate_sync.rswacore/src/companion_reg.rswacore/src/event.rswacore/src/history_sync.rswacore/src/iq/groups.rswacore/src/iq/usync.rswacore/src/media_retry.rswacore/src/message_edit.rswacore/src/message_processing.rswacore/src/messages.rswacore/src/pair.rswacore/src/poll.rswacore/src/prekeys.rswacore/src/reaction.rswacore/src/shortcake.rswacore/src/stanza/business.rswacore/src/store/device.rswacore/src/types/events.rswacore/src/usync.rswacore/src/voip/mlow/smpl_tables_blob.rswacore/tests/appstate_external_mutations_test.rswaproto/build.rswaproto/src/lib.rs
| # lld + ICF: lld lays out the fat-LTO object tighter than BFD ld and folds | ||
| # byte-identical sections (measured -73 KiB on the stripped demo). Scoped to | ||
| # this target so wasm and non-Linux builds keep their default linkers. | ||
| rustflags = [ | ||
| "-Zshare-generics=y", | ||
| "-Clink-arg=-fuse-ld=lld", | ||
| "-Clink-arg=-Wl,--icf=all", | ||
| ] |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial
Consider enabling -Zshare-generics=y for other targets like macOS and AArch64.
Look, optimizing the Linux target with lld and share-generics to keep the binary tight is great. But half our engineering team runs Macs, and we run ARM servers. We need to move fast across all platforms. While lld and ICF might be Linux-specific in this flag combination, -Zshare-generics=y works on macOS and AArch64 too. Let's consider scoping the generics sharing to those targets as well so we don't leave performance and build speed on the table.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.cargo/config.toml around lines 11 - 18, Extend the target-specific Rust
flags configuration so -Zshare-generics=y is also enabled for macOS and AArch64
targets, while keeping the lld and ICF linker arguments restricted to the Linux
configuration where they are supported.
| override_.version.as_ref().map(|v| { | ||
| format!( | ||
| "{}.{}.{}", | ||
| v.primary.unwrap_or(0), | ||
| v.secondary.unwrap_or(0), | ||
| v.tertiary.unwrap_or(0) | ||
| ) | ||
| }), |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value
Avoid heap allocation for log formatting.
Look, we need WhatsApp to run as lean as possible if we're going to connect the world. Shrinking the binary is exactly what I expect from this team. However, you're allocating a String here just to format the version inside a debug log. It won't crash our infrastructure since it's on the startup path, but it sets a sloppy precedent for memory efficiency. Consider using a small lightweight wrapper struct with a custom Debug implementation to avoid this heap allocation entirely. Keep our hot paths zero-allocation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/bot.rs` around lines 1313 - 1320, Replace the heap-allocating version
String construction in the override_.version formatting expression with a
lightweight wrapper that borrows the version components and implements custom
Debug formatting. Use that wrapper directly in the debug log so formatting
remains equivalent while avoiding allocation, preserving the existing zero
defaults for missing primary, secondary, and tertiary values.
| Ok(Some(bytes)) => { | ||
| // Try new format first (protobuf-encoded PreKeyRecordStructure) | ||
| if let Ok(record) = PreKeyRecordStructure::decode_from_slice(bytes.as_ref()) { | ||
| if let Ok(record) = waproto::codec::pre_key_record_decode(bytes.as_ref()) { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Rely on Rust's deref coercion instead of manual slice conversions.
Look, we're trying to build a platform that connects billions of people, and that means we need a lean, consistent codebase. Across these files, you're manually converting byte vectors to slices using .as_ref() or .as_slice() when a simple borrow triggers Rust's deref coercion automatically. You even did it right on line 362 of signal.rs by using &bytes! Redundant method calls just clutter our logic and waste time. Clean this up so we can focus on shipping things that actually matter.
src/store/signal.rs#L287-L287: Replacebytes.as_ref()with&bytes.wacore/noise/src/handshake.rs#L190-L192: Replaceintermediate_details_bytes.as_slice()withintermediate_details_bytes.wacore/noise/src/handshake.rs#L224-L224: Replaceleaf_details_bytes.as_slice()withleaf_details_bytes.
📍 Affects 2 files
src/store/signal.rs#L287-L287(this comment)wacore/noise/src/handshake.rs#L190-L192wacore/noise/src/handshake.rs#L224-L224
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/store/signal.rs` at line 287, Replace the redundant byte-vector slice
conversions by relying on Rust deref coercion: update src/store/signal.rs lines
287-287 in the pre_key_record_decode call to borrow bytes directly, and update
wacore/noise/src/handshake.rs lines 190-192 and 224-224 to pass
intermediate_details_bytes and leaf_details_bytes directly instead of calling
as_slice().
…pgrade-z91tgm # Conflicts: # src/prekeys.rs
… Send bounds - Use -Clinker-features=+lld (toolchain's rust-lld) instead of -fuse-ld=lld so contributors need no system lld; still unstable-gated, hence -Zunstable-options in the nightly-only rustflags block. - CI test-stable job sets RUSTFLAGS="" to opt out of the nightly-only target rustflags (env overrides config). - upload.rs: cfg-conditional boxed-future and dyn-callback types plus a new wacore MaybeSend marker keep the retry driver Send on native while preserving ?Send HttpClient futures on wasm32. - Document variant-name-only Debug in the Event doc comment. - clippy.toml: ban the remaining generic Message entry points (decode, decode/encode_length_delimited, encode_to_bytes); allow raw methods in record_helpers tests that verify the borrowed encoder against them. - Drop redundant as_ref()/as_slice() where deref coercion applies. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@clippy.toml`:
- Around line 17-20: Add the corresponding waproto::codec pinned wrapper APIs
for Message::decode, decode_length_delimited, encode_length_delimited, and
encode_to_bytes before retaining these clippy.toml bans. Match the existing
wrapper patterns used by decode_from_slice, encode_to_vec, encode, and write_to,
then ensure the banned methods route through the new wrappers rather than
blocking valid call sites.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: de47f368-518f-41d5-af9b-5035c727414d
📒 Files selected for processing (15)
.cargo/config.toml.github/workflows/main.ymlclippy.tomlsrc/history_sync.rssrc/message/tests.rssrc/prekeys.rssrc/store/signal.rssrc/upload.rswacore/libsignal/src/protocol/protocol.rswacore/libsignal/src/store/record_helpers.rswacore/noise/src/handshake.rswacore/src/history_sync.rswacore/src/messages.rswacore/src/sync_marker.rswacore/src/types/events.rs
📦 Binary size report
.text per crate
Top movers (cargo-bloat attribution)
Baseline: |
…rink Measured on identical trees with the pinned toolchain, default linker both sides: the swap is -132 KiB stripped / +35 KiB .text vs main. The SIZE_RUSTFLAGS (-fuse-ld=lld --icf=all) this PR added to the measure step made the same binary ~300 KiB larger against the post-#1055 main, so the workflow reverts to main's version — both sides now measure flag-free, matching the baseline. A few remaining handshake-only crates (x25519-dalek, rfc6979, pem-rfc7468, sha1_smol) join the opt-z list to cover the residual .text delta. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018b9ZakKBufGJHS5pR29z6A
Deep binary-size audit of the
demorelease binary (the binary-size CI metric), executed as a 6-lens parallel source audit plus symbol-level measurement of the fat-LTO output. Every lever below was measured in isolation locally with the exact CI methodology (strip --strip-allsize +.text); every candidate with a real runtime, functionality, or diagnostics drawback was rejected — the list is at the end.Result: 11,590,552 → 10,053,720 bytes stripped (−1,536,832 = −1.47 MiB, −13.3%); .text 9,470,326 → 8,069,110 (−14.8%).
-Zshare-generics=y(.cargo/config.toml, linux-x64)--icf=all(same scope)[profile.release.package.libsqlite3-sys] opt-level="z"LIBSQLITE3_FLAGSsubsystem trimBuild-config levers (
.cargo/config.toml+ one Cargo.toml override)-Zshare-generics=y— a symbol-level dump showed 741 KiB of byte-duplicated function copies in the shipped binary (wa::Message::clone×2 at 70 KiB each,Messagedrop glue ×4, whole async send state machines ×2): downstream crates re-instantiate cross-crate generics with distinct symbol hashes, and fat LTO keeps every copy. share-generics reuses upstream instantiations at the source. The Docker image build has run with this flag since perf(docker): enable -Zshare-generics in the image build #845; the toolchain is nightly-pinned so the-Zflag is stable-by-pin. Scoped tox86_64-unknown-linux-gnuso wasm and other hosts keep default behavior.-Oz— the bundledsqlite3.camalgamation was the one crate the 2026-06 per-package opt-level sweep missed: it still compiled at-O3. Single biggest lever (−856 KiB). Same I/O-bound tradeoff the sweep already accepted for diesel/r2d2/sqlite-storage; storage sits behind the async pool and the write-behind Signal cache, never on per-message crypto.LIBSQLITE3_FLAGSdrops FTS3, RTREE, STAT4, DBSTAT, SOUNDEX, JSON SQL, loadable extensions (also a dlopen surface), deprecated APIs and progress callbacks — verified unused across diesel/sqlite-storage/chat-store. FTS5 stays (chat-storesearchbuilds on it); shared cache stays (sqlite-storage's in-memory pool mode shares the DB viacache=shared— the omit attempt was caught by the roundtrip tests and reverted inc2baa36).Consumers building
whatsapp-rustfrom crates.io are unaffected:.cargo/config.tomland profile overrides apply to workspace builds only.Code levers (all control-plane; hot paths untouched)
Messagemethods on waproto types outside waproto now routes through the#[inline(never)]pinned helpers (17 new helpers for the small roots). Aclippy.tomldisallowed-methods guard rejects new direct call sites, so the class can't regrow. libsignal's signal-wire protos deliberately keep direct calls: that crate is their sole instantiation site and the per-message hot path — pinning would buy nothing and cost a call.Client::execute<S>type-erased — the request-id/direct-encode/send/timeout body was stamped per IqSpec (~55 live specs); now one non-generic body, thin per-spec wrapper.execute_request<V>split; appstate-sync'sFDownloadclosure generic across eight wacore fns became a&BlobDownloadFntrait object (breaking for wacore consumers: parameter is now a dyn ref);upload_media_with_retry's 8 type parameters became boxed futures (each attempt wraps a whole HTTP transfer); the two group-IQ macros share one outlinedbuild_iq.{:?}of a waproto type now logs fields, andEvent's derived Debug is a manual variant-name impl, so no future log line can silently resurrect the 627-impl generated proto Debug graph (a one-line change that would otherwise cost hundreds of KiB).Evaluated and rejected (drawbacks)
SyncActionValueis exposed whole through the public mutation surface — pruning would silently drop data consumers can read today.-Cforce-unwind-tables=no(~350-480 KiB): killsRUST_BACKTRACEin release binaries — a field-diagnostics regression. Available as an opt-in for embedders; not defaulted.-Zlocation-detail=none(~40-70 KiB): panics lose file:line.-Zbuild-std(~300-600 KiB): CI/local build divergence and toolchain-bump fragility.Verification
cargo fmt --check,cargo clippy --all --tests(including the new disallowed-methods guard): cleancargo test --workspace --exclude e2e-tests: 3070 passed, 0 failed (the sqlite shared-cache regression the trim initially introduced was caught by these tests and fixed)demobinary boots and initializes the trimmed SQLite backend normallyGenerated by Claude Code