wasm_usage.md 3.6 KB

WASM Binary Size Analysis

Analysis of cdk-wasm release build (cargo build --target wasm32-unknown-unknown --release).

Binary size: ~6.6 MB raw (before gzip/brotli compression).

Tooling used: twiggy for symbol-level profiling.

Per-component breakdown

Component Size (KB) % Notes
[data/rodata] 1554.8 23.1% String literals, lookup tables, constants
cdk (wallet core) 944.1 14.0% Wallet sagas, swap, receive, restore, OIDC
core/alloc/std 841.9 12.5% Rust stdlib (formatting, collections, etc.)
[function names] 793.3 11.8% Debug symbol names — stripped by wasm-pack
[other] 597.9 8.9% Mostly secp256k1 C symbols + serde monomorphs
serde (all) 425.9 6.3% JSON/CBOR deserialization monomorphization
tracing 251.7 3.7% Logging framework
cashu 223.9 3.3% Protocol types + their serde impls
secp256k1 221.8 3.3% Elliptic curve crypto (C code compiled to WASM)
wasm-bindgen 189.6 2.8% JS glue layer
cdk_wasm + cdk_common 191.7 2.9% FFI wrappers + shared types
url/idna 66.3 1.0% URL parsing + international domain names
lightning 50.5 0.8% Invoice parsing
bitcoin_hashes 31.2 0.5% SHA256, SHA512, RIPEMD160
Everything else ~110 1.6% bip39, cbor, encoding, tokio, etc.

Key observations

  1. Biggest wins would come from: reducing cdk code size (14%), trimming serde monomorphization (6.3%), and evaluating if tracing (3.7%) can be compiled out for WASM.

  2. .rodata at 23% is large — this is string literals (error messages, format strings, tracing span names) and lookup tables (unicode normalization, secp256k1 precomputed tables). Hard to reduce without removing features.

  3. [function names] (11.8%) disappears when wasm-pack uses --release with name stripping, so the real production binary is closer to ~5.9 MB raw (before gzip).

  4. secp256k1 + bitcoin_hashes (3.8% combined) is the cost of doing cryptography in WASM — this is the C secp256k1 library compiled to WASM. Can't avoid it.

  5. serde monomorphization (6.3%) is the classic Rust WASM bloat problem — every Deserialize impl for each type generates a separate copy of the JSON parser. miniserde or hand-written parsing would shrink this but at massive effort.

  6. jsonwebtoken/ring is completely gone from the WASM build — zero cost, replaced by the browser's built-in Web Crypto API via SubtleCrypto.

Potential size reduction strategies

  • Strip tracing on WASM: feature-gate tracing instrumentation behind cfg(not(target_arch = "wasm32")) or use console.log directly. Saves ~250 KB.
  • wasm-opt: run wasm-opt -Oz on the output. Typically 10-20% reduction.
  • gzip/brotli: the final served .wasm file compresses well (~60-70% reduction).
  • Reduce serde monomorphization: use #[serde(deserialize_with)] to share deserializer instances across similar types, or use serde_json::Value as an intermediate.

Reproducing this analysis

# Install twiggy
cargo install twiggy

# Build release WASM
cargo build --target wasm32-unknown-unknown -p cdk-wasm --release

# Analyze
twiggy top target/wasm32-unknown-unknown/release/cdk_wasm.wasm -n 50

# Dominator tree (what retains what)
twiggy dominators target/wasm32-unknown-unknown/release/cdk_wasm.wasm -d 2 | head -80

# CSV export for scripting
twiggy top target/wasm32-unknown-unknown/release/cdk_wasm.wasm -n 30000 --format csv > twiggy.csv