Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.vm.operations.v2;

import static org.mockito.Mockito.mock;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.frame.BlockValues;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.operation.PopOperation;
import org.hyperledger.besu.evm.v2.operation.PopOperationV2;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.util.concurrent.TimeUnit;

import org.apache.tuweni.bytes.Bytes;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

/**
* JMH benchmark comparing v1 and v2 POP operations.
*
* <p>Each iteration pushes a value then pops it, measuring the pop cost. The push is needed to
* ensure the stack is non-empty.
*/
@State(Scope.Thread)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public class PopOperationBenchmarkV2 {

private static final Bytes STACK_VALUE =
Bytes.fromHexString("0x3232323232323232323232323232323232323232323232323232323232323232");

private MessageFrame v1Frame;
private MessageFrame v2Frame;

@Setup(Level.Iteration)
public void setUp() {
v1Frame = createFrame(false);
v2Frame = createFrame(true);
}

@Benchmark
public void v1Pop(final Blackhole blackhole) {
v1Frame.pushStackItem(STACK_VALUE);
blackhole.consume(PopOperation.staticOperation(v1Frame));
}

@Benchmark
public void v2Pop(final Blackhole blackhole) {
// Push one item onto v2 stack so POP has something to remove
final long[] s = v2Frame.stackDataV2();
final int top = v2Frame.stackTopV2();
final int dst = top << 2;
s[dst] = 0x3232323232323232L;
s[dst + 1] = 0x3232323232323232L;
s[dst + 2] = 0x3232323232323232L;
s[dst + 3] = 0x3232323232323232L;
v2Frame.setTopV2(top + 1);

blackhole.consume(PopOperationV2.staticOperation(v2Frame));
}

private static MessageFrame createFrame(final boolean enableV2) {
return MessageFrame.builder()
.enableEvmV2(enableV2)
.worldUpdater(mock(WorldUpdater.class))
.originator(Address.ZERO)
.gasPrice(Wei.ONE)
.blobGasPrice(Wei.ONE)
.blockValues(mock(BlockValues.class))
.miningBeneficiary(Address.ZERO)
.blockHashLookup((__, ___) -> Hash.ZERO)
.type(MessageFrame.Type.MESSAGE_CALL)
.initialGas(Long.MAX_VALUE)
.address(Address.ZERO)
.contract(Address.ZERO)
.inputData(Bytes.EMPTY)
.sender(Address.ZERO)
.value(Wei.ZERO)
.apparentValue(Wei.ZERO)
.code(Code.EMPTY_CODE)
.completer(__ -> {})
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.vm.operations.v2;

import static org.mockito.Mockito.mock;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.frame.BlockValues;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.operation.PushOperation;
import org.hyperledger.besu.evm.v2.operation.PushOperationV2;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.apache.tuweni.bytes.Bytes;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

/**
* JMH benchmark comparing v1 and v2 PUSH operations.
*
* <p>Each iteration pushes a value from random bytecode onto the stack, then resets the stack
* pointer to avoid overflow. Parameterized by push size to cover different limb-filling code paths.
*/
@State(Scope.Thread)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public class PushOperationBenchmarkV2 {

private static final int SAMPLE_SIZE = 30_000;

@Param({"1", "2", "8", "16", "32"})
private int pushSize;

private byte[][] codePool;
private int index;

private MessageFrame v1Frame;
private MessageFrame v2Frame;

@Setup(Level.Iteration)
public void setUp() {
v1Frame = createFrame(false);
v2Frame = createFrame(true);

final Random random = new Random();
codePool = new byte[SAMPLE_SIZE][];
for (int i = 0; i < SAMPLE_SIZE; i++) {
// bytecode: opcode byte + pushSize random immediate bytes
final byte[] code = new byte[1 + pushSize];
code[0] = (byte) (0x5F + pushSize); // PUSH opcode
for (int j = 1; j <= pushSize; j++) {
code[j] = (byte) random.nextInt(256);
}
codePool[i] = code;
}
index = 0;
}

@Benchmark
public void v1Push(final Blackhole blackhole) {
final byte[] code = codePool[index];
blackhole.consume(PushOperation.staticOperation(v1Frame, code, 0, pushSize));
v1Frame.popStackItem();
index = (index + 1) % SAMPLE_SIZE;
}

@Benchmark
public void v2Push(final Blackhole blackhole) {
final byte[] code = codePool[index];
// Use specialized paths for PUSH1/PUSH2, matching the EVM dispatch
blackhole.consume(
switch (pushSize) {
case 1 -> PushOperationV2.staticPush1(v2Frame, v2Frame.stackDataV2(), code, 0);
case 2 -> PushOperationV2.staticPush2(v2Frame, v2Frame.stackDataV2(), code, 0);
default ->
PushOperationV2.staticOperation(v2Frame, v2Frame.stackDataV2(), code, 0, pushSize);
});
v2Frame.setTopV2(v2Frame.stackTopV2() - 1); // reset stack
v2Frame.setPC(0); // reset PC
index = (index + 1) % SAMPLE_SIZE;
}

private static MessageFrame createFrame(final boolean enableV2) {
return MessageFrame.builder()
.enableEvmV2(enableV2)
.worldUpdater(mock(WorldUpdater.class))
.originator(Address.ZERO)
.gasPrice(Wei.ONE)
.blobGasPrice(Wei.ONE)
.blockValues(mock(BlockValues.class))
.miningBeneficiary(Address.ZERO)
.blockHashLookup((__, ___) -> Hash.ZERO)
.type(MessageFrame.Type.MESSAGE_CALL)
.initialGas(Long.MAX_VALUE)
.address(Address.ZERO)
.contract(Address.ZERO)
.inputData(Bytes.EMPTY)
.sender(Address.ZERO)
.value(Wei.ZERO)
.apparentValue(Wei.ZERO)
.code(Code.EMPTY_CODE)
.completer(__ -> {})
.build();
}
}
44 changes: 44 additions & 0 deletions evm/src/main/java/org/hyperledger/besu/evm/EVM.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@
import org.hyperledger.besu.evm.operation.XorOperationOptimized;
import org.hyperledger.besu.evm.tracing.OperationTracer;
import org.hyperledger.besu.evm.v2.operation.AddOperationV2;
import org.hyperledger.besu.evm.v2.operation.PopOperationV2;
import org.hyperledger.besu.evm.v2.operation.Push0OperationV2;
import org.hyperledger.besu.evm.v2.operation.PushOperationV2;
import org.hyperledger.besu.evm.v2.operation.SarOperationV2;
import org.hyperledger.besu.evm.v2.operation.ShlOperationV2;
import org.hyperledger.besu.evm.v2.operation.ShrOperationV2;
Expand Down Expand Up @@ -500,6 +503,47 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing
enableConstantinople
? SarOperationV2.staticOperation(frame, frame.stackDataV2())
: InvalidOperation.invalidOperationResult(opcode);
case 0x50 -> PopOperationV2.staticOperation(frame);
case 0x5f ->
enableShanghai
? Push0OperationV2.staticOperation(frame, frame.stackDataV2())
: InvalidOperation.invalidOperationResult(opcode);
case 0x60 -> // PUSH1 — specialized for the most frequent opcode
PushOperationV2.staticPush1(frame, frame.stackDataV2(), code, pc);
case 0x61 -> // PUSH2 — specialized for jump destinations
PushOperationV2.staticPush2(frame, frame.stackDataV2(), code, pc);
case 0x62, // PUSH3-32
0x63,
0x64,
0x65,
0x66,
0x67,
0x68,
0x69,
0x6a,
0x6b,
0x6c,
0x6d,
0x6e,
0x6f,
0x70,
0x71,
0x72,
0x73,
0x74,
0x75,
0x76,
0x77,
0x78,
0x79,
0x7a,
0x7b,
0x7c,
0x7d,
0x7e,
0x7f ->
PushOperationV2.staticOperation(
frame, frame.stackDataV2(), code, pc, opcode - PushOperationV2.PUSH_BASE);
// TODO: implement remaining opcodes in v2; until then fall through to v1
default -> {
frame.setCurrentOperation(currentOperation);
Expand Down
10 changes: 10 additions & 0 deletions evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,16 @@ public boolean stackHasItems(final int n) {
return stackTopV2 >= n;
}

/**
* Returns true if the v2 stack has space for at least {@code n} more items.
*
* @param n the number of items to push
* @return true if the stack can accommodate n more items
*/
public boolean stackHasSpace(final int n) {
return stackTopV2 + n <= getMaxStackSize();
}

// ---------------------------------------------------------------------------
// endregion

Expand Down
Loading