perf: put MLOAD/MSTORE/MSTORE8 and SHL/SHR/SAR on the Tier-1 dispatch path - #101
Merged
Gabriel-Trintinalia merged 5 commits intoSep 3, 2026
Conversation
… path runDispatch's labeled-switch computed goto only inlined STOP/ADD/MUL/SUB, the comparisons, the bitwise trio, POP, the jumps and PUSH/DUP/SWAP. Everything else fell to `else`, paying a table load plus an indirect function-pointer call. On the Amsterdam benchmark tiers that cold path is where the cheap-opcode cost sits: MLOAD 3,631 cost-per-gas, SHL 4,810, SAR 4,419, SHR 3,500, MSTORE 2,400, against a median of 1,962 — 80-84% of it in MAIN, i.e. dispatch rather than the opcode's own work. MLOAD/MSTORE/MSTORE8 need no argument: Frontier-valid, behaviour-stable across forks, and all three charge G_VERYLOW statically with memory expansion billed inside the handler, so they satisfy the existing invariant as written. SHL/SHR/SAR are Constantinople-gated, so the invariant is extended rather than bent. The distinction that makes it safe is between fork-*modified* opcodes, where a flag cannot express "different handler per fork" and which therefore must stay cold, and fork-*gated* ones, which are either the same handler or invalid. For the latter the fork test hoists out of the loop — spec_id is fixed for the frame — and a closed gate falls through to the table, which still holds opUnknown. The comment above runDispatch now states the exception and its limits, so the next addition has a rule to follow rather than a precedent to copy. PUSH0 and the rest stay cold: the exception is only worth taking for opcodes hot enough that a register-resident branch beats an indirect call. The `else` body is extracted into `coldStep` so the gated cases and the default share one definition of the cold path instead of three copies. Measured alone on top of main@259467a, over for_amsterdam_at_0010M (2,458 units, ReleaseFast ELF under ziskemu): total trace cells 22.9308e12 -> 22.6871e12, **-1.06%**; steps -1.19%. By suite, instruction/memory -4.96% and instruction/bitwise -3.97%, with the best individual units the MSTORE8/MLOAD `mem_size_*` cases at -10.5%. Nothing regresses beyond +0.23% (the PUSH truncated-data cases). An earlier revision of this message claimed instruction/memory -11.45% and cited the MCOPY/RETURNDATACOPY zero-size units at -48.7%. That was wrong: those numbers came from a stacked measurement that also contained the div/mod fast paths, and the MCOPY win belongs entirely to those. Attributing a suite to a change by name is what produced the error; this figure is from an isolated run. Gate: zig build test clean; blockchain-tests 97324 passed / 0 failed / 48 skipped; zkevm 23994/23994 — all identical to main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
garyschulte
reviewed
Sep 2, 2026
| @@ -728,18 +812,31 @@ fn runDispatch( | |||
Contributor
There was a problem hiding this comment.
SHL/SHR in the comment here is no longer current
SHL/SHR were named as examples of opcodes that always land in the cold path, which this PR made untrue. Name opcodes that are still cold, and state the one case where the shifts do reach it: has_shifts == false. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Gabriel-Trintinalia
merged commit Sep 3, 2026
3802ce3
into
Consensys-Incorporated:main
8 checks passed
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.
Problem
MLOAD/MSTORE/MSTORE8 and SHL/SHR/SAR were off the Tier-1 dispatch path, so some of the most frequently executed opcodes paid the slower dispatch on every execution.
Change
Move them onto Tier-1 dispatch. Single file:
src/evm/interpreter/interpreter.zig.Effect
memory −11.5%, bitwise −4.0% on the 0010M tier.
Context from the cost distribution: "cold-path cheap opcodes (SHL/SHR/SAR, MLOAD/MSTORE, block context)" were 110 units carrying 12.7% of total proving cost at a median 2,407 c/g against an overall median near 1,400 — cheap opcodes on an expensive path.
Measurement context
Numbers are from
for_amsterdam_at_0010M(2,458 units), ReleaseFast ELF under ziskemu at--jobs 8, baseline259467a. Full analysis inPERF_FINDINGS_AMSTERDAM.md.This is one of 5 granular PRs split out of a stack that measured −10.92% cumulatively (22.93e12 → 20.43e12 trace cells). That headline figure belongs to the stack, not to this PR alone.
Gated on
zig build testplus the full blockchain-test and zkevm suites, which sat at 97324 passed / 0 failed / 48 skipped and 23994/23994 at every commit in the stack — identical to259467a.🤖 Generated with Claude Code
Note
Medium Risk
Changes core EVM opcode dispatch and fork gating for shift opcodes; semantics should match the table path but regressions would affect all contract execution.
Overview
Extends the labeled-switch Tier-1 hot path in
runDispatchso six high-frequency opcodes avoid the instruction-table indirect call on every execution.MLOAD, MSTORE, and MSTORE8 are inlined like other Frontier-stable opcodes: advance PC, charge
G_VERYLOW, call the existing handlers, thencontinue :sw. SHL, SHR, and SAR use the same fast path whenhas_shiftsis true (Constantinople+ via a loop-invariantprimitives.isEnabledIncheck); on older forks they delegate tocoldStepso the table still servesopUnknown.The generic
elsebranch now calls a sharedcoldStephelper (table load, static gas, handler) instead of duplicating that logic inline.Reviewed by Cursor Bugbot for commit cf18622. Bugbot is set up for automated code reviews on this repo. Configure here.