Profiling Nethermind's ZisK stateless guest (mainnet block 25,532,382, 470M ziskemu steps after
the client-side optimization rounds in NethermindEth/nethermind#13090 / #13091) shows a set of
recurring codegen patterns that together account for a double-digit share of executed instructions.
On this target every retired instruction is proving cost (MAIN is ~60 % of total cost at 68/step),
so backend quality translates 1:1 into proof time. Numbers below are from full-binary capstone
disassembly weighted by ziskemu --trace-from/--trace-to samples (7×500k-step windows).
1. 64-bit constant materialization at every inlined use (largest single item)
Every 64-bit literal is synthesized with a ~5-instruction lui/slli/addi chain at each inlined
use site; nothing is CSE'd across inlined copies and there is no constant-pool fallback for
multi-use constants. A trace-weighted scan attributed 4.4 % of all executed guest steps to
rebuilding the same handful of byte-swap masks and hash primes. We recovered most of it in C# by
moving hot constants into frozen arrays (MemoryMarshal.GetArrayDataReference loads,
NethermindEth/nethermind@18dc415cad, −4.1 % steps in one commit) — but that is a workaround the
backend could make unnecessary, and the same pattern presumably costs every other bflat riscv64
workload. Note static readonly primitive fields do not help: ILC preinit folds them back into
literals.
2. Redundant width-conversion pairs
sext.w is 2.57 % of executed instructions; a large share follows W-form producers
(addiw, lw) whose results are already sign-extended by definition.
- Explicit
slli 32; srli 32 zero-extension pairs on values already zero-extended (e.g. straight
after lbu) measured 2.13 % of executed steps on an earlier build of the same guest.
Both look like peephole candidates in the emitter (track the extension state of the def, elide the
conversion when provably redundant).
3. Struct composition through memory
Composing multi-field structs (e.g. Vector256.Create on a target without SIMD, 48-byte struct
assignments) emits store-then-immediately-reload pairs on the same stack slot — visible as
sd a2, -0xf8(s0); ld a2, -0xf8(s0) sequences in every hot function we disassembled, and as
memcpy calls for 48-byte struct copies on frame transitions.
4. Instruction mix summary (whole run, trace-weighted)
| group |
share |
memory (ld/sd/lw/sw/lbu/...) |
~32 % |
addi (address arithmetic + immediates) |
13.9 % |
shifts (slli/srli, incl. zext artifacts) |
10.8 % |
mv (register shuffles) |
3.7 % |
auipc (address materialization) |
3.8 % |
sext.w |
2.6 % |
A conservative estimate for items 1–3 addressed in the backend is another 5–8 % fewer executed
instructions on this workload, on top of what C#-level workarounds already claw back.
Happy to share the exact scan scripts (capstone + trace-window weighting) and reproduction steps —
the guest measurement loop is deterministic and byte-exact, so peephole changes are cheap to
validate against it.
🤖 Generated with Claude Code
Profiling Nethermind's ZisK stateless guest (mainnet block 25,532,382, 470M
ziskemusteps afterthe client-side optimization rounds in NethermindEth/nethermind#13090 / #13091) shows a set of
recurring codegen patterns that together account for a double-digit share of executed instructions.
On this target every retired instruction is proving cost (MAIN is ~60 % of total cost at 68/step),
so backend quality translates 1:1 into proof time. Numbers below are from full-binary capstone
disassembly weighted by
ziskemu --trace-from/--trace-tosamples (7×500k-step windows).1. 64-bit constant materialization at every inlined use (largest single item)
Every 64-bit literal is synthesized with a ~5-instruction
lui/slli/addichain at each inlineduse site; nothing is CSE'd across inlined copies and there is no constant-pool fallback for
multi-use constants. A trace-weighted scan attributed 4.4 % of all executed guest steps to
rebuilding the same handful of byte-swap masks and hash primes. We recovered most of it in C# by
moving hot constants into frozen arrays (
MemoryMarshal.GetArrayDataReferenceloads,NethermindEth/nethermind@18dc415cad, −4.1 % steps in one commit) — but that is a workaround the
backend could make unnecessary, and the same pattern presumably costs every other bflat riscv64
workload. Note
static readonlyprimitive fields do not help: ILC preinit folds them back intoliterals.
2. Redundant width-conversion pairs
sext.wis 2.57 % of executed instructions; a large share follows W-form producers(
addiw,lw) whose results are already sign-extended by definition.slli 32; srli 32zero-extension pairs on values already zero-extended (e.g. straightafter
lbu) measured 2.13 % of executed steps on an earlier build of the same guest.Both look like peephole candidates in the emitter (track the extension state of the def, elide the
conversion when provably redundant).
3. Struct composition through memory
Composing multi-field structs (e.g.
Vector256.Createon a target without SIMD, 48-byte structassignments) emits store-then-immediately-reload pairs on the same stack slot — visible as
sd a2, -0xf8(s0); ld a2, -0xf8(s0)sequences in every hot function we disassembled, and asmemcpycalls for 48-byte struct copies on frame transitions.4. Instruction mix summary (whole run, trace-weighted)
ld/sd/lw/sw/lbu/...)addi(address arithmetic + immediates)slli/srli, incl. zext artifacts)mv(register shuffles)auipc(address materialization)sext.wA conservative estimate for items 1–3 addressed in the backend is another 5–8 % fewer executed
instructions on this workload, on top of what C#-level workarounds already claw back.
Happy to share the exact scan scripts (capstone + trace-window weighting) and reproduction steps —
the guest measurement loop is deterministic and byte-exact, so peephole changes are cheap to
validate against it.
🤖 Generated with Claude Code