Skip to content

perf(zkvm): W-aware right-shift lookup tables — SRLW/SRAW 5->2 rows, SRLIW/SRAIW 3->1 - #1753

Draft
0xAndoroid wants to merge 3 commits into
mainfrom
vseq-wshift-tables
Draft

perf(zkvm): W-aware right-shift lookup tables — SRLW/SRAW 5->2 rows, SRLIW/SRAIW 3->1#1753
0xAndoroid wants to merge 3 commits into
mainfrom
vseq-wshift-tables

Conversation

@0xAndoroid

Copy link
Copy Markdown
Collaborator

What

Adds three W-aware lookup tables so the word right-shifts stop lifting through the 64-bit shift machinery:

Instruction Before After New sequence
SRLW / SRAW 5 2 ShiftRightBitmaskW + VirtualSRLW/VirtualSRAW
SRLIW / SRAIW 3 1 single row, imm = mask32(shamt)

These are hot in real RV64 guests (u32 arithmetic codegen).

New tables + shape budget

  • ShiftRightBitmaskW: combine = 2^32·One − Pow2W·pow2w — 2 suffixes, reuses the shipped Pow2W machinery including its final-phase absorption.
  • VirtualSRAW: exact arity mirror of the shipped VirtualSRA — 3 prefixes / 4 suffixes, fill term x31·(2^64 − 2^(32−s')).
  • VirtualSRLW: 2 prefixes / 4 suffixes; the result's bit-31 sign-extension stays live at s' = 0 via a dedicated y₀ term (validated first, before anything else, by the phase-boundary-sweeping prefix_suffix harness at both geometries).

All three are bilinear and within the MAX_SUFFIXES = 4 budget.

Why new tables instead of reusing the 64-bit SRL/SRA tables with a 32-bit mask: that shortcut is unsound — the existing tables' prefix/suffix closed forms (leading_ones/trailing_zeros) assume ones-prefix mask shapes and give wrong values on 32-bit-confined masks (counterexample-checked during design). The W tables exist because the mask algebra genuinely differs.

Read-raf cost, priced: +3 used tables ≈ 120–230K field mults/proof (~0.01% at 2^22; present-table-gated, zero cost in guests without W-shifts), verifier +~900 mults, proof +96 B. This is a small fixed set serving four architectural instructions in every RV64 guest — not a per-constant family.

Testing

  • Per-table: mle_random + phased prefix_suffix (both geometries) + mle_full_hypercube::<8> with 32-bit-mask index override (ROTRW precedent); exhaustive host-side materialize_entry vs w >> s over all s' ∈ [0,32) × edge operands (sign bit, garbage upper word) — both stacks; ABI append tests
  • Tracer differential torture: all four kinds × s ∈ {0,1,31} × ±low-word × garbage-high, register and immediate forms
  • Guest-level modular proving coverage: the muldiv guest gains 8 raw W-shift ops (objdump-verified at 0x8000138a..0x800013a8); both modular acceptance arms assert ≥2 traced rows per new virtual kind before proving — the new tables are provably in-trace for the clear byte-diff and ZK e2e (modular clear 20/20, ZK 13/13; legacy muldiv 3/3 both modes)
  • Expansion parity fixture: exactly 20 hashes moved (the four W-shift kinds), unrelated entries byte-identical
  • Clippy both feature modes -D warnings; fmt

Rebased on current main (post-#1748).

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Warning

This PR has more than 500 changed lines and does not include a spec.

Large features and architectural changes benefit from a spec-driven workflow.
See CONTRIBUTING.md for details on how to create a spec.

If this PR is a bug fix, refactor, or doesn't warrant a spec, feel free to ignore this message.

@github-actions github-actions Bot added the no-spec PR has no spec file label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Benchmark comparison (crates)

group                           main_run                               pr_run
-----                           --------                               ------
EqPolynomial::evaluations/14    1.00   536.6±21.17µs        ? ?/sec    1.45   778.3±23.14µs        ? ?/sec

The instruction read-raf kernel parity fixture assigned arbitrary random
lookup indices to whatever tables sit at fixed enum positions. With the
W-aware right-shift tables appended, the last variant (VirtualSRAW) has a
constrained operand domain: its prefix-suffix decomposition only matches
materialize_entry when the right operand is bitmask-shaped, so the
first-principles input claim diverged from the kernels' round sums
(RoundCheckFailed at round 0).

Expose per-table in-domain index generation behind a new
jolt-lookup-tables test-utils feature (random_lookup_index takes &self and
dispatches through LookupTableKind; the bitmask generators move to an
index_gen module shared between cfg(test) and the feature). The fixture
now draws each table row's index from that table's domain and keeps the
edge indices (0, all-ones, all-ones upper half) on table-less rows, since
the RAF operand paths they target are table-independent. This also lets
the fixture exercise constrained tables at all — previously any fixture
position landing on SRA/SRL/ROTR would have failed the same way.

Regenerate the registered-inline expansion parity hashes: SHA-256 inlines
expand SRLIW/SRAIW sites, so the shrunk sequences change row counts
(2436 -> 2244 per block) and row-stream hashes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-spec PR has no spec file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant