Repository navigation
chacha20: add WebAssembly SIMD (simd128) backend - #589
Merged
Merged
Conversation
…sm on the MSRV and without SIMD
Member
|
Looks good at first glance. I can do a more detailed review later. |
newpavlov
pushed a commit
to RustCrypto/traits
that referenced
this pull request
Nov 2, 2026
`StreamCipherCoreWrapper::unchecked_apply_keystream_inout` and `unchecked_write_keystream` enter the core's backend on every call, even when the data has no whole block (a call that only consumes cached keystream, or one shorter than a block), and `ApplyBlocksCtx::call` zeroes a buffer of `ParBlocksSize` blocks before computing a tail that may have zero blocks. A backend with parallel blocks (`chacha20`'s SSE2, AVX2 and NEON backends, and the WebAssembly SIMD one of RustCrypto/stream-ciphers#589) pays its setup and that zeroing on every call, so small calls run slower with those backends than with the software backend, whose `ParBlocksSize` is one. This skips the backend when there are no whole blocks and the tail buffer when the tail is empty. The keystream and the counter are unchanged: the skipped calls computed nothing.
tarcieri
approved these changes
Nov 2, 2026
newpavlov
reviewed
Nov 3, 2026
The counter row is incremented by one block in a method of the backend, after each of the four blocks takes the state and after the single block, instead of a free function adding a block count to the row. The loop over the four blocks needs no index, so no `usize` to `u32` cast. The crate's benches on wasm32-wasip1 under wasmtime and Node 24, with `cipher` 0.5.2 and with `cipher` from master, are the same as before within the run-to-run noise of about 4%.
Contributor
Author
|
Re-ran on |
newpavlov
approved these changes
Nov 6, 2026
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Adds a backend for WebAssembly's 128-bit SIMD, selected at compile time when the crate is built with the
simd128target feature (-C target-feature=+simd128), as the NEON backend is selected bytarget_feature = "neon"and assha2selects itswasm32_simd128backend. Todaywasm32uses the software backend, a block at a time;simd128is 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 andLegacy's 64-bit) and the RNG path, andchacha20_backend="soft"still forces the software backend. Onlyv128_storeneedsunsafe; the other intrinsics are safe with the target feature enabled.Performance
The crate's own benches (
cargo +nightly bench --target wasm32-wasip1withRUSTFLAGS='-C target-feature=+simd128', the runnerwasmtime runor Node 24's WASI) againstcipherfrom master (ec30ea6), on an i9-14900K's performance cores, MB/s, the best of four runs; the software backend is the same build with--cfg chacha20_backend="soft":simd128, wasmtimesimd128, Node 24 (V8)From four blocks on, 1.6 times the software backend's throughput under wasmtime and 2.3 times under V8; runs differ by about 4%.
ChaCha20Rng::fill_bytesover 64 MiB: 1,006 → 1,538 MB/s under wasmtime, 942 → 2,086 under V8.The 16 B case depends on
cipher's wrapper: 0.5.2 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, so with 0.5.2 the 16 B case is 727 MB/s under wasmtime and 666 under V8 against the software backend's 800. RustCrypto/traits#2505 skips both, and with it a single block takes 62 ns straight from the core against the software backend's 70.Testing
+simd128onwasm32-wasip1for every feature combination (cargo hack test --feature-powersetunder wasmtime), againstcipher0.5.2 and againstcipherfrom master, and withchacha20_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 forChaCha20LegacyandChaCha20Rngacross 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 warningsandcargo fmt --checkare clean, and the crate builds on the MSRV (1.85) with+simd128.cargo hack test --feature-powersetonwasm32-wasip1under wasmtime, on the MSRV and stable, with and withoutsimd128(the matrixsha2's workflow runs).