Skip to content

chacha20: add WebAssembly SIMD (simd128) backend - #589

Open
TheUltDev wants to merge 2 commits into
RustCrypto:masterfrom
TheUltDev:chacha20-simd128
Open

TheUltDev wants to merge 2 commits into
RustCrypto:masterfrom
TheUltDev:chacha20-simd128

Conversation

@TheUltDev

@TheUltDev TheUltDev commented Nov 1, 2026 •

Copy link
Copy Markdown

Adds a backend for WebAssembly's 128-bit SIMD, selected at compile time when the crate is built with the simd128 target feature (-C target-feature=+simd128), as the NEON backend is selected by target_feature = "neon" and as sha2 selects its wasm32_simd128 backend. Today wasm32 uses the software backend, a block at a time; simd128 is supported by every major WebAssembly engine (V8, SpiderMonkey, JavaScriptCore, Wasmtime, Wasmer).

The backend computes four blocks at a time with each vector holding one state word of the four blocks, so the rounds need no lane rotations (the layout of libsodium's u4); the blocks are transposed into rows once at the end. A single block (the tail of a message) is computed as its four rows, as the NEON backend does. The 16- and 8-bit rotations are byte shuffles (i8x16.shuffle), the 12- and 7-bit ones shift pairs. It supports both counter widths (Ietf's 32-bit and Legacy's 64-bit) and the RNG path, and chacha20_backend="soft" still forces the software backend. Only v128_store needs unsafe; the other intrinsics are safe with the target feature enabled.

Performance

The crate's own benches (cargo +nightly bench --target wasm32-wasip1 with RUSTFLAGS='-C target-feature=+simd128', the runner wasmtime run or Node 24's WASI) on an i9-14900K's performance cores, MB/s, the best of two runs; the software backend is the same build with --cfg chacha20_backend="soft":

soft, wasmtime simd128, wasmtime soft, Node 24 (V8) simd128, Node 24 (V8)
chacha20 16 B 800 727 (941 with RustCrypto/traits#2505) 800 666 (888 with RustCrypto/traits#2505)
chacha20 256 B 1,003 1,684 898 2,031
chacha20 1 KiB 1,015 1,729 899 2,098
chacha20 16 KiB 1,014 1,748 885 2,100
chacha12 16 KiB 1,611 2,855 1,594 3,399
chacha8 16 KiB 2,365 4,165 2,379 4,898

From four blocks on, 1.7 times the software backend's throughput under wasmtime and 2.4 times under V8. A single block takes 62 ns straight from the core against the software backend's 70, but the 16 B bench shows the opposite with cipher 0.5.2: its wrapper enters the backend on every call, including the three of four that only read cached keystream, and zeroes a buffer of four blocks each time, which the software backend, with one parallel block, never does. RustCrypto/traits#2505 skips both; with it the 16 B case is 941 MB/s under wasmtime and 888 under V8. ChaCha20Rng::fill_bytes over 64 MiB: 950 → 1,500 MB/s under wasmtime, 900 → 2,000 under V8.

Testing

  • The crate's tests pass with +simd128 on wasm32-wasip1 for every feature combination (cargo hack test --feature-powerset under wasmtime), and with chacha20_backend="soft" forced. The backend's output equals the software backend's over every length from 0 to 600 bytes as a stream, over 1 MiB + 77 bytes from nine seek positions, for XChaCha20, ChaCha8 and ChaCha12, and for ChaCha20Legacy and ChaCha20Rng across the 64-bit counter's carry at 2^32 blocks, on wasmtime, Wasmer 6.0.1 and Node 24.
  • cargo clippy --all-features --lib --tests -D warnings and cargo fmt --check are clean, and the crate builds on the MSRV (1.85) with +simd128.
  • Adds a CI job that runs cargo hack test --feature-powerset on wasm32-wasip1 under wasmtime, on the MSRV and stable, with and without simd128 (the matrix sha2's workflow runs).

@tarcieri

tarcieri commented Nov 1, 2026

Copy link
Copy Markdown
Member

Looks good at first glance. I can do a more detailed review later.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants