Commit 68a120a
authored
perf(syscalls,crypto): in-place keccak sponge + direct finalize + fixed-shape parents (−40% recursion-verifier cycles at real query counts) (#847)
* perf(syscalls): absorb keccak sponge input in place
The state is the only buffer: input bytes XOR directly into the rate
lanes at a running offset (tiny-keccak's design), padding lands in
place, and the digest is read straight out of the state. This removes
the staging buffer and its zeroing, the full-block copy, the 17-lane
byte re-extraction, and the separate pad block — the largest slice of
the ~1,322 cycles of software plumbing measured per 1-cycle
keccak_permute ecall (measured for this commit alone: −1.545B cycles
on the 219-query ethrex-block recursion benchmark; the residual is
addressed by the follow-up commits).
The whole-lane fast path gates on the input pointer's runtime
alignment (the VM traps on unaligned doubleword loads); misaligned
input falls back to byte-wise absorption, so correctness never
depends on alignment. Digests are byte-identical to the previous
implementation.
* perf(crypto): finalize keccak Merkle hashes straight into the node array
The field-element Merkle backends finalized every leaf and parent hash
through the `Digest::finalize` blanket: it allocates a zeroed `GenericArray`,
the riscv64 keccak adapter fills a local `[u8; 32]` and copies it into that
`Output`, then the caller copies the `Output` once more into its own node
array. That is two 32-byte memcpys plus a 32-byte memset of pure plumbing
around the single permutation each hash actually costs.
Route the leaf (`hash_data`, `hash_data_from_slices`, `hash_bytes`) and
parent (`hash_new_parent`) hashes of `FieldElementPairBackend` and
`FieldElementVectorBackend` through a shared `hash_streamed` helper. On the
riscv64 guest, when `D` is the platform keccak digest and nodes are 32 bytes,
it drives the syscall sponge directly and squeezes straight into the result
array — no GenericArray, no intermediate buffer, no double copy. Every other
digest / node size and the entire host build keep the generic `Digest` path,
so output stays byte-identical (blobs still verify).
Recursion verifier guest cycles: min 42,185,756 -> 41,991,672 (-0.46%),
blowup8 291,147,000 -> 273,789,625 (-5.96%); keccak permutation counts
unchanged (3,025 / 134,173).
* perf(crypto,syscalls): fixed-shape keccak256_pair for Merkle parents
Every Merkle parent hash is exactly 64 bytes (two concatenated 32-byte nodes),
which fits the keccak rate in a single block. Add `keccak256_pair` to the
syscall sponge: it loads the eight data lanes straight from the two nodes, XORs
the pad10*1 bits in place, runs one permutation, and squeezes four lanes —
skipping the incremental sponge's per-byte absorb, running-offset bookkeeping,
and separate padding pass. Route `hash_new_parent` of both keccak field-element
backends to it on the riscv64 guest via the same TypeId + node-size dispatch
used for the direct finalize; every other digest / node size and the host build
keep the generic streaming path, so output stays byte-identical (blobs verify,
keccak counts unchanged at 3,025 / 134,173).
Cumulative recursion verifier guest cycles after this commit: min 41,973,461,
blowup8 271,979,593. C's isolated effect vs the prior commit: min -266,369,
blowup8 -14,646,310. (The prior commit is a measured regression DROP-candidate;
A and C together, with B dropped, are the recommended stack.)
* test(syscalls): differential-test the keccak sponge against sha3 on host
The sponge's absorption chunking, padding, and squeezing had no unit
coverage — its only oracle was end-to-end proof-blob acceptance. Host
tests now inject a software Keccak-f[1600] in place of the ecall and
check digest byte-identity against sha3::Keccak256: every input length
through three rate blocks (all padding boundaries), 300 randomized
misaligned-chunking cases, and the fixed-shape pair path against both
the reference and the streaming sponge.
The guest global allocator registration is now gated to riscv64 so a
host `cargo test` of this crate uses the system allocator instead of
aborting on the never-initialized guest heap. Guest builds unchanged
(cycle counts bit-identical).
* test(syscalls): wire keccak differential tests into make; drop unused critical-section dev-dep
The syscalls crate is excluded from the workspace (riscv-only bare-metal
allocator/entrypoints that assemble only for the guest target), so the root
`cargo test` never reached its host keccak-vs-sha3 differential tests and CI
couldn't run them. Add a `test-syscalls` target that runs `cargo test` in the
crate dir and make `test` depend on it.
Drop the `critical-section` dev-dependency: the embedded_alloc global allocator
is gated to `target_arch = "riscv64"` (src/allocator.rs), so on host test builds
it is never the active allocator and its critical-section path is never linked.
A clean `cargo test` links and passes without it. critical-section stays in the
lock transitively (via riscv/embedded-alloc) but needs no host impl.
Commit syscalls/Cargo.lock (now that CI builds this crate standalone) to match
the repo convention for excluded crates and pin dev-deps for reproducible tests.
* refactor(syscalls): keccak review fixes — LE guard, shared squeeze, StdRng tests
- update(): gate the whole-lane fast path on cfg!(target_endian = "little").
The raw *const u64 lane read equals the required little-endian value only on
LE targets; the cfg! folds to a compile-time constant (codegen unchanged on
every real target) and the byte fallback is endian-correct everywhere.
- Deduplicate the squeeze: extract squeeze32_into(state, out), shared by
Keccak256::finalize and keccak256_pair. Write-into (rather than returning
[u8; 32]) so finalize fills its output reference in place — verified
guest-codegen and cycle-identical to the pre-dedup loops.
- Tests: replace the hand-rolled xorshift RNG with rand's StdRng + SeedableRng
(matching src/random.rs), keeping fixed seeds for reproducibility.
- Document at the byte-wise fallback that a from_le_bytes lane-assembly middle
path was measured at +4.7% cycles (dropped commit f6d575ed) so it is not
re-proposed on this 1-cycle-per-instruction VM.
Digests remain byte-identical to sha3 Keccak-256 (host differential tests);
guest cycles unchanged (recursion-min 41,747,766 / recursion-blowup8
260,967,578, keccak call counts identical).
* docs(crypto,syscalls): record measured dead-end optimizations at their code sites
Two refactors that reviewers (and optimization-hunting agents) will
keep re-proposing were implemented and measured slower on the guest;
pin the numbers where the edit would happen so the effort isn't
repeated:
- replacing the TypeId dispatch in hash_streamed with a generic
Digest::finalize_into route: +0.5% guest cycles at blowup8 across
every formulation (by-value sponge through the trait layer, not
elidable cross-crate without LTO)
- return-value squeeze32: +81k cycles from the extra stack temporary
The misaligned lane-assembly absorb path (+4.7%) is already documented
at the byte fallback in update().
* fix(ci,test,docs): close the review gaps on the keccak sponge PR
- CI never invokes 'make test', so the syscalls differential tests
still did not run in CI despite the Makefile wiring; run
'make test-syscalls' as a dedicated step in the cli-test job.
- Swap the test RNG from StdRng (algorithm unstable across rand
releases) to ChaCha8Rng so the fixed seeds pin the exact fuzz case
streams across versions and checkouts.
- Correct the finalize_into dead-end comment to give both preset
percentages (+0.14% min / +0.48% blowup8) instead of one figure.
- Cite PR #847 instead of a commit hash unreachable from any ref for
the dropped lane-assembly measurement.
* fix(syscalls): gate the guest entrypoint module to riscv64
The _start entry symbol (and its imported main) only exist for the
guest; on a Linux host they collide with the C runtime / test harness
entry and 'cargo test' fails with "entry symbol `main` declared
multiple times" (macOS tolerates the duplicate, which is why local
host tests passed). Same treatment as the global-allocator gating:
guest builds are unchanged.
* test(syscalls),docs(crypto): make absorb-path coverage structural; pin the adapter passthrough invariant
Review feedback (two reviewers independently): the whole-lane fast path
was only exercised because Vec<u8> bases happen to be 8-aligned on
current platforms. A repr(align(8)) buffer now guarantees the aligned
path and a +1-offset view guarantees the byte fallback, across all
padding boundaries.
Also documents two load-bearing implicit facts: the TypeId
specializations depend on PlatformKeccak256 remaining a pure
passthrough of the syscall sponge (INVARIANT note in the adapter), and
the host tests' coverage boundary (the ecall and riscv64 branches are
validated only by the proof-blob oracle).1 parent 3be1eed commit 68a120a
9 files changed
Lines changed: 814 additions & 82 deletions
File tree
- .github/workflows
- crypto/crypto/src
- hash
- merkle_tree/backends
- syscalls
- src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
131 | 131 | | |
132 | 132 | | |
133 | 133 | | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
134 | 137 | | |
135 | 138 | | |
136 | 139 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
293 | 293 | | |
294 | 294 | | |
295 | 295 | | |
296 | | - | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
297 | 306 | | |
298 | 307 | | |
299 | 308 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
14 | 23 | | |
15 | 24 | | |
16 | 25 | | |
| |||
Lines changed: 98 additions & 34 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
12 | 94 | | |
13 | 95 | | |
14 | 96 | | |
| |||
27 | 109 | | |
28 | 110 | | |
29 | 111 | | |
30 | | - | |
| 112 | + | |
31 | 113 | | |
32 | 114 | | |
33 | 115 | | |
| |||
38 | 120 | | |
39 | 121 | | |
40 | 122 | | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
47 | 127 | | |
48 | 128 | | |
49 | 129 | | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
| 130 | + | |
56 | 131 | | |
57 | 132 | | |
58 | 133 | | |
| |||
71 | 146 | | |
72 | 147 | | |
73 | 148 | | |
74 | | - | |
| 149 | + | |
75 | 150 | | |
76 | 151 | | |
77 | 152 | | |
| |||
80 | 155 | | |
81 | 156 | | |
82 | 157 | | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
87 | | - | |
| 158 | + | |
88 | 159 | | |
89 | 160 | | |
90 | 161 | | |
91 | | - | |
| 162 | + | |
92 | 163 | | |
93 | 164 | | |
94 | 165 | | |
| |||
100 | 171 | | |
101 | 172 | | |
102 | 173 | | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
110 | 179 | | |
111 | 180 | | |
112 | 181 | | |
113 | | - | |
| 182 | + | |
114 | 183 | | |
115 | 184 | | |
116 | 185 | | |
| |||
129 | 198 | | |
130 | 199 | | |
131 | 200 | | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
| 201 | + | |
138 | 202 | | |
139 | 203 | | |
140 | 204 | | |
| |||
0 commit comments