Skip to content

perf: put MLOAD/MSTORE/MSTORE8 and SHL/SHR/SAR on the Tier-1 dispatch path - #101

Merged
Gabriel-Trintinalia merged 5 commits into
Consensys-Incorporated:mainfrom
Gabriel-Trintinalia:perf/tier1-dispatch
Sep 3, 2026
Merged

perf: put MLOAD/MSTORE/MSTORE8 and SHL/SHR/SAR on the Tier-1 dispatch path#101
Gabriel-Trintinalia merged 5 commits into
Consensys-Incorporated:mainfrom
Gabriel-Trintinalia:perf/tier1-dispatch

Conversation

@Gabriel-Trintinalia

@Gabriel-Trintinalia Gabriel-Trintinalia commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

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, baseline 259467a. Full analysis in PERF_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 test plus 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 to 259467a.

🤖 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 runDispatch so 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, then continue :sw. SHL, SHR, and SAR use the same fast path when has_shifts is true (Constantinople+ via a loop-invariant primitives.isEnabledIn check); on older forks they delegate to coldStep so the table still serves opUnknown.

The generic else branch now calls a shared coldStep helper (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.

… 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>
Comment thread src/evm/interpreter/interpreter.zig Outdated
@@ -728,18 +812,31 @@ fn runDispatch(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHL/SHR in the comment here is no longer current

@garyschulte garyschulte left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢

garyschulte and others added 4 commits September 2, 2026 16:39
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
Gabriel-Trintinalia merged commit 3802ce3 into Consensys-Incorporated:main Sep 3, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants