Skip to content

riscv64 codegen: constant rematerialization, redundant width conversions, and memory round-trips measured on the ZisK guest #40

Description

@LukaszRozmej

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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions