Skip to content

Add POP, PUSH0 and PUSH1-32 operations for EVM v2 (#10131)#10209

Open
vivek-0509 wants to merge 1 commit intobesu-eth:mainfrom
vivek-0509:feat/evm-v2-pop-push-operations
Open

Add POP, PUSH0 and PUSH1-32 operations for EVM v2 (#10131)#10209
vivek-0509 wants to merge 1 commit intobesu-eth:mainfrom
vivek-0509:feat/evm-v2-pop-push-operations

Conversation

@vivek-0509
Copy link
Copy Markdown
Contributor

Fixed Issue(s)

Partially addresses #10131 (Category 6: Stack manipulation ops)

Summary

Implements POP (0x50), PUSH0 (0x5F), and PUSH1-PUSH32 (0x60-0x7F) operations for the EVM v2 long[] stack, covering 34 opcodes that account for ~30% of all EVM instructions executed on mainnet.

Changes

New operations:

  • PopOperationV2 - decrements stack pointer (gas: 2, baseTier)
  • Push0OperationV2 - pushes 4 zero longs (gas: 2, baseTier, Shanghai-gated)
  • PushOperationV2 - reads immediate bytes from bytecode into 4-limb long[] representation (gas: 3, veryLowTier)

Performance optimizations (inspired by Geth's #19210, #31267):

  • Specialized staticPush1/staticPush2 methods with dedicated dispatch cases, avoiding the generic pushFromBytes path for the two most frequently executed PUSH variants (~21% of all instructions combined)
  • PUSH32 uses 4x VarHandle bytesToLong reads (no buildLong loop)
  • Redundant zeroing pass eliminated, each branch writes only the limbs it needs

Infrastructure additions:

  • MessageFrame.stackHasSpace(int n) - overflow check for push operations
  • AbstractFixedCostOperationV2.OVERFLOW_RESPONSE - static shared response for overflow
  • StackArithmetic.push1(), push2(), pushZero(), pushFromBytes() - stack manipulation utilities with VarHandle for fast big-endian byte reads

Benchmark Results

Operation v1 (ns/op) v2 (ns/op) Speedup
PUSH1 14.889 9.393 1.59x
PUSH2 14.383 9.335 1.54x
PUSH8 14.658 11.433 1.28x
PUSH16 14.554 12.613 1.15x
PUSH32 10.023 9.336 1.07x
POP 4.820 3.051 1.58x

Note: Benchmarks run with -f 0 (non-forked, same VM). The v1 PUSH32 number appears fast because Bytes.wrap is a zero-copy reference that defers byte interpretation to arithmetic time - v2 pays the conversion cost upfront at push time, yielding zero-allocation arithmetic downstream.

Known optimization opportunity

stackHasSpace() calls getMaxStackSize() -> txValues.maxStackSize() on every PUSH. This indirection was flagged by @daniellehrner in #10105 as a potential JIT inlining concern. Caching maxStackSize as a field on MessageFrame would reduce this, but is left for the maintainers to decide as it affects the broader v2 architecture.

Thanks for sending a pull request! Have you done the following?

  • Checked out our contribution guidelines?
  • Considered documentation and added the doc-change-required label to this PR if updates are required.
  • Considered the changelog and included an update if required.
  • For database changes (e.g. KeyValueSegmentIdentifier) considered compatibility and performed forwards and backwards compatibility tests

Locally, you can run these tests to catch failures early:

  • spotless: ./gradlew spotlessApply
  • unit tests: ./gradlew build
  • acceptance tests: ./gradlew acceptanceTest
  • integration tests: ./gradlew integrationTest
  • reference tests: ./gradlew ethereum:referenceTests:referenceTests
  • hive tests: Engine or other RPCs modified?

Signed-off-by: Vivek Singh Solanki <viveksolanki0509@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant