diff --git a/CHANGELOG.md b/CHANGELOG.md index 80f3542e8cf..7ceb8a817de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ #### Performance - EVM optimisations - Improves 70% of EEST benchmarks [#9775](https://github.com/hyperledger/besu/pull/9775) +- EVM optimisations - Improve SAR, SHR and SHL opcodes performance [#9796](https://github.com/hyperledger/besu/pull/9796) ### Bug fixes - Fix QBFT Shanghai support by reintroducing NotApplicableWithdrawals withdrawals validator [#9830](https://github.com/hyperledger/besu/pull/9830) diff --git a/build.gradle b/build.gradle index 909ee98f7e9..e91a23e24d3 100644 --- a/build.gradle +++ b/build.gradle @@ -656,6 +656,7 @@ subprojects { excludes = _strListCmdArg('excludes', []) var asyncProfiler = _strCmdArg('asyncProfiler') var asyncProfilerOptions = _strCmdArg('asyncProfilerOptions', 'output=flamegraph') + zip64.set(true) jvmArgs = [ '-XX:+EnableDynamicAgentLoading' ] @@ -666,6 +667,10 @@ subprojects { // Force debug symbols of stack traces to the profiler jvmArgs.addAll("-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints") } + var gcProfiler = _strCmdArg('gcProfiler') + if (gcProfiler != null && gcProfiler.toBoolean()) { + profilers.add('gc') + } duplicateClassesStrategy = DuplicatesStrategy.INCLUDE } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractSarOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractSarOperationBenchmark.java new file mode 100644 index 00000000000..786fd254408 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractSarOperationBenchmark.java @@ -0,0 +1,161 @@ +/* + * 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; + +import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomNegativeValue; +import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomPositiveValue; +import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomValue; + +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.tuweni.bytes.Bytes; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +/** + * Abstract base class for SAR (Shift Arithmetic Right) operation benchmarks. + * + *
SAR has additional test cases for negative/positive values to test sign extension behavior. + */ +public abstract class AbstractSarOperationBenchmark extends BinaryOperationBenchmark { + + /** Test cases covering different execution paths for SAR operations. */ + public enum Case { + /** Shift by 0 - early return path. */ + SHIFT_0, + /** Negative number (ALL_BITS) with shift=1 - tests sign extension OR path. */ + NEGATIVE_SHIFT_1, + /** value with all bits to 1 with shift=1 * */ + ALL_BITS_SHIFT_1, + /** Positive number with shift=1 - no sign extension needed. */ + POSITIVE_SHIFT_1, + /** Negative number with medium shift. */ + NEGATIVE_SHIFT_128, + /** Negative number with max shift. */ + NEGATIVE_SHIFT_255, + /** Positive number with medium shift. */ + POSITIVE_SHIFT_128, + /** positive number with max shift. */ + POSITIVE_SHIFT_255, + /** Overflow: shift >= 256. */ + OVERFLOW_SHIFT_256, + /** Overflow: shift amount > 4 bytes. */ + OVERFLOW_LARGE_SHIFT, + /** Random values (original behavior). */ + FULL_RANDOM + } + + /** All bits set (32 bytes of 0xFF) - represents -1 in two's complement. */ + protected static final Bytes ALL_BITS = + Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + + @Param({ + "SHIFT_0", + "NEGATIVE_SHIFT_1", + "POSITIVE_SHIFT_1", + "ALL_BITS_SHIFT_1", + "NEGATIVE_SHIFT_128", + "NEGATIVE_SHIFT_255", + "POSITIVE_SHIFT_128", + "POSITIVE_SHIFT_255", + "OVERFLOW_SHIFT_256", + "OVERFLOW_LARGE_SHIFT", + "FULL_RANDOM" + }) + protected String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelper.createMessageCallFrame(); + + final Case scenario = Case.valueOf(caseName); + aPool = new Bytes[SAMPLE_SIZE]; // shift amount (pushed second, popped first) + bPool = new Bytes[SAMPLE_SIZE]; // value (pushed first, popped second) + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + + for (int i = 0; i < SAMPLE_SIZE; i++) { + switch (scenario) { + case SHIFT_0: + aPool[i] = Bytes.of(0); + bPool[i] = randomValue(random); + break; + + case NEGATIVE_SHIFT_1: + // shiftAmount = 0x1, value = 0xfff...fff (negative, tests OR path) + aPool[i] = Bytes.of(1); + bPool[i] = randomNegativeValue(random); + break; + + case ALL_BITS_SHIFT_1: + // shiftAmount = 0x1, value = 0xfff...fff (negative, tests OR path) + aPool[i] = Bytes.of(1); + bPool[i] = ALL_BITS; + break; + + case POSITIVE_SHIFT_1: + // shiftAmount = 0x1, random positive value (no sign extension) + aPool[i] = Bytes.of(1); + bPool[i] = randomPositiveValue(random); + break; + + case NEGATIVE_SHIFT_128: + aPool[i] = Bytes.of(128); + bPool[i] = randomNegativeValue(random); + break; + + case NEGATIVE_SHIFT_255: + aPool[i] = Bytes.of(255); + bPool[i] = randomNegativeValue(random); + break; + + case POSITIVE_SHIFT_128: + aPool[i] = Bytes.of(128); + bPool[i] = randomPositiveValue(random); + break; + case POSITIVE_SHIFT_255: + aPool[i] = Bytes.of(255); + bPool[i] = randomPositiveValue(random); + break; + + case OVERFLOW_SHIFT_256: + // Shift of exactly 256 - overflow path + aPool[i] = Bytes.fromHexString("0x0100"); // 256 + bPool[i] = randomValue(random); + break; + + case OVERFLOW_LARGE_SHIFT: + // Shift amount > 4 bytes - overflow path + aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes + bPool[i] = randomValue(random); + break; + + case FULL_RANDOM: + default: + // Original random behavior + final byte[] shift = new byte[1 + random.nextInt(2)]; + final byte[] value = new byte[1 + random.nextInt(32)]; + random.nextBytes(shift); + random.nextBytes(value); + aPool[i] = Bytes.wrap(shift); + bPool[i] = Bytes.wrap(value); + break; + } + } + index = 0; + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractShiftOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractShiftOperationBenchmark.java new file mode 100644 index 00000000000..7ccd39cfec2 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractShiftOperationBenchmark.java @@ -0,0 +1,118 @@ +/* + * 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; + +import static org.hyperledger.besu.ethereum.vm.operations.BenchmarkHelper.randomValue; + +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.tuweni.bytes.Bytes; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +/** + * Abstract base class for shift operation benchmarks (SHL, SHR, SAR). + * + *
Provides shared test case definitions and setup logic. + */ +public abstract class AbstractShiftOperationBenchmark extends BinaryOperationBenchmark { + + /** Test cases covering different execution paths for shift operations. */ + public enum Case { + /** Shift by 0 - no shift needed. */ + SHIFT_0, + /** Small shift by 1 bit. */ + SHIFT_1, + /** Medium shift by 128 bits (half word). */ + SHIFT_128, + /** Large shift by 255 bits (max valid). */ + SHIFT_255, + /** Overflow: shift of exactly 256. */ + OVERFLOW_SHIFT_256, + /** Overflow: shift amount > 4 bytes. */ + OVERFLOW_LARGE_SHIFT, + /** Random values (original behavior). */ + FULL_RANDOM + } + + @Param({ + "SHIFT_0", + "SHIFT_1", + "SHIFT_128", + "SHIFT_255", + "OVERFLOW_SHIFT_256", + "OVERFLOW_LARGE_SHIFT", + "FULL_RANDOM" + }) + protected String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelper.createMessageCallFrame(); + + final Case scenario = Case.valueOf(caseName); + aPool = new Bytes[SAMPLE_SIZE]; // shift amount (pushed second, popped first) + bPool = new Bytes[SAMPLE_SIZE]; // value (pushed first, popped second) + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + + for (int i = 0; i < SAMPLE_SIZE; i++) { + switch (scenario) { + case SHIFT_0: + aPool[i] = Bytes.of(0); + bPool[i] = randomValue(random); + break; + + case SHIFT_1: + aPool[i] = Bytes.of(1); + bPool[i] = randomValue(random); + break; + + case SHIFT_128: + aPool[i] = Bytes.of(128); + bPool[i] = randomValue(random); + break; + + case SHIFT_255: + aPool[i] = Bytes.of(255); + bPool[i] = randomValue(random); + break; + + case OVERFLOW_SHIFT_256: + aPool[i] = Bytes.fromHexString("0x0100"); // 256 + bPool[i] = randomValue(random); + break; + + case OVERFLOW_LARGE_SHIFT: + aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes + bPool[i] = randomValue(random); + break; + + case FULL_RANDOM: + default: + final byte[] shift = new byte[1 + random.nextInt(4)]; + final byte[] value = new byte[1 + random.nextInt(32)]; + random.nextBytes(shift); + random.nextBytes(value); + aPool[i] = Bytes.wrap(shift); + bPool[i] = Bytes.wrap(value); + break; + } + } + index = 0; + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BenchmarkHelper.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BenchmarkHelper.java index fffa86ee124..cda01bfb05f 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BenchmarkHelper.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/BenchmarkHelper.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import java.util.function.BiPredicate; import java.util.function.Predicate; import java.util.function.Supplier; @@ -34,6 +35,13 @@ import org.apache.tuweni.units.bigints.UInt256; public class BenchmarkHelper { + /** + * Creates a minimal {@link MessageFrame} suitable for opcode benchmarks. + * + *
The frame is configured with mocked dependencies and deterministic zero/default values. + * + * @return a message-call frame ready to use in benchmark setup + */ public static MessageFrame createMessageCallFrame() { return MessageFrame.builder() .worldUpdater(mock(WorldUpdater.class)) @@ -56,6 +64,12 @@ public static MessageFrame createMessageCallFrame() { .build(); } + /** + * Creates a minimal {@link MessageFrame} with custom call data for benchmarks. + * + * @param callData the input data to attach to the frame + * @return a message-call frame initialized with {@code callData} + */ public static MessageFrame createMessageCallFrameWithCallData(final Bytes callData) { return MessageFrame.builder() .worldUpdater(mock(WorldUpdater.class)) @@ -164,6 +178,13 @@ public static void fillPools( } } + /** + * Creates call data payload for benchmarks. + * + * @param size size of the payload in bytes + * @param nonZero whether to fill payload with deterministic non-zero bytes + * @return call data payload + */ static Bytes createCallData(final int size, final boolean nonZero) { byte[] data = new byte[size]; if (nonZero) { @@ -174,6 +195,15 @@ static Bytes createCallData(final int size, final boolean nonZero) { return Bytes.wrap(data); } + /** + * Fills COPY-like benchmark pools for call-data operations. + * + * @param sizePool destination pool for copy sizes + * @param destOffsetPool destination pool for destination offsets + * @param srcOffsetPool destination pool for source offsets + * @param dataSize call-data size used to populate the size/source ranges + * @param fixedSrcDst whether to use fixed zero source/destination offsets + */ static void fillPoolsForCallData( final Bytes[] sizePool, final Bytes[] destOffsetPool, @@ -192,4 +222,42 @@ static void fillPoolsForCallData( } } } + + /** + * Generates a random 32-byte value. + * + * @param random thread-local random source + * @return random 32-byte value + */ + static Bytes randomValue(final ThreadLocalRandom random) { + final byte[] value = new byte[32]; + random.nextBytes(value); + return Bytes.wrap(value); + } + + /** + * Generates a random positive signed 256-bit value (sign bit cleared). + * + * @param random thread-local random source + * @return random positive 32-byte value + */ + static Bytes randomPositiveValue(final ThreadLocalRandom random) { + final byte[] value = new byte[32]; + random.nextBytes(value); + value[0] = (byte) (value[0] & 0x7F); + return Bytes.wrap(value); + } + + /** + * Generates a random negative signed 256-bit value (sign bit set). + * + * @param random thread-local random source + * @return random negative 32-byte value + */ + static Bytes randomNegativeValue(final ThreadLocalRandom random) { + final byte[] value = new byte[32]; + random.nextBytes(value); + value[0] = (byte) (value[0] | 0x80); + return Bytes.wrap(value); + } } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationBenchmark.java new file mode 100644 index 00000000000..83e6db2ed45 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationBenchmark.java @@ -0,0 +1,28 @@ +/* + * 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; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.SarOperation; + +/** JMH benchmark for the original SAR (Shift Arithmetic Right) operation. */ +public class SarOperationBenchmark extends AbstractSarOperationBenchmark { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return SarOperation.staticOperation(frame); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationOptimizedBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationOptimizedBenchmark.java new file mode 100644 index 00000000000..ceea8d402f4 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationOptimizedBenchmark.java @@ -0,0 +1,28 @@ +/* + * 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; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.SarOperationOptimized; + +/** JMH benchmark for the optimized SAR (Shift Arithmetic Right) operation. */ +public class SarOperationOptimizedBenchmark extends AbstractSarOperationBenchmark { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return SarOperationOptimized.staticOperation(frame); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationBenchmark.java new file mode 100644 index 00000000000..791cc6a95dd --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationBenchmark.java @@ -0,0 +1,28 @@ +/* + * 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; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.ShlOperation; + +/** JMH benchmark for the original SHL (Shift Left) operation. */ +public class ShlOperationBenchmark extends AbstractShiftOperationBenchmark { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ShlOperation.staticOperation(frame); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationOptimizedBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationOptimizedBenchmark.java new file mode 100644 index 00000000000..aa88abe98de --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationOptimizedBenchmark.java @@ -0,0 +1,28 @@ +/* + * 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; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.ShlOperationOptimized; + +/** JMH benchmark for the optimized SHL (Shift Left) operation. */ +public class ShlOperationOptimizedBenchmark extends AbstractShiftOperationBenchmark { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ShlOperationOptimized.staticOperation(frame); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationBenchmark.java new file mode 100644 index 00000000000..f4bcb2c28d7 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationBenchmark.java @@ -0,0 +1,28 @@ +/* + * 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; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.ShrOperation; + +/** JMH benchmark for the original SHR (Shift Right Logical) operation. */ +public class ShrOperationBenchmark extends AbstractShiftOperationBenchmark { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ShrOperation.staticOperation(frame); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationOptimizedBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationOptimizedBenchmark.java new file mode 100644 index 00000000000..2cf2a31bba3 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationOptimizedBenchmark.java @@ -0,0 +1,28 @@ +/* + * 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; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.ShrOperationOptimized; + +/** JMH benchmark for the optimized SHR (Shift Right Logical) operation. */ +public class ShrOperationOptimizedBenchmark extends AbstractShiftOperationBenchmark { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return ShrOperationOptimized.staticOperation(frame); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index 6b173b78ead..95d3dcc7285 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -68,6 +68,12 @@ import org.hyperledger.besu.evm.operation.SLtOperation; import org.hyperledger.besu.evm.operation.SModOperation; import org.hyperledger.besu.evm.operation.SModOperationOptimized; +import org.hyperledger.besu.evm.operation.SarOperation; +import org.hyperledger.besu.evm.operation.SarOperationOptimized; +import org.hyperledger.besu.evm.operation.ShlOperation; +import org.hyperledger.besu.evm.operation.ShlOperationOptimized; +import org.hyperledger.besu.evm.operation.ShrOperation; +import org.hyperledger.besu.evm.operation.ShrOperationOptimized; import org.hyperledger.besu.evm.operation.SignExtendOperation; import org.hyperledger.besu.evm.operation.StopOperation; import org.hyperledger.besu.evm.operation.SubOperation; @@ -280,6 +286,18 @@ public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { ? NotOperationOptimized.staticOperation(frame) : NotOperation.staticOperation(frame); case 0x1a -> ByteOperation.staticOperation(frame); + case 0x1b -> + evmConfiguration.enableOptimizedOpcodes() + ? ShlOperationOptimized.staticOperation(frame) + : ShlOperation.staticOperation(frame); + case 0x1c -> + evmConfiguration.enableOptimizedOpcodes() + ? ShrOperationOptimized.staticOperation(frame) + : ShrOperation.staticOperation(frame); + case 0x1d -> + evmConfiguration.enableOptimizedOpcodes() + ? SarOperationOptimized.staticOperation(frame) + : SarOperation.staticOperation(frame); case 0x1e -> enableOsaka ? CountLeadingZerosOperation.staticOperation(frame) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java index 9a584fde496..120893515f7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -116,10 +116,13 @@ import org.hyperledger.besu.evm.operation.SModOperationOptimized; import org.hyperledger.besu.evm.operation.SStoreOperation; import org.hyperledger.besu.evm.operation.SarOperation; +import org.hyperledger.besu.evm.operation.SarOperationOptimized; import org.hyperledger.besu.evm.operation.SelfBalanceOperation; import org.hyperledger.besu.evm.operation.SelfDestructOperation; import org.hyperledger.besu.evm.operation.ShlOperation; +import org.hyperledger.besu.evm.operation.ShlOperationOptimized; import org.hyperledger.besu.evm.operation.ShrOperation; +import org.hyperledger.besu.evm.operation.ShrOperationOptimized; import org.hyperledger.besu.evm.operation.SignExtendOperation; import org.hyperledger.besu.evm.operation.SlotNumOperation; import org.hyperledger.besu.evm.operation.StaticCallOperation; @@ -493,9 +496,15 @@ private static void registerConstantinopleOperations( final EvmConfiguration evmConfiguration) { registerByzantiumOperations(registry, gasCalculator, evmConfiguration); registry.put(new Create2Operation(gasCalculator)); - registry.put(new SarOperation(gasCalculator)); - registry.put(new ShlOperation(gasCalculator)); - registry.put(new ShrOperation(gasCalculator)); + if (evmConfiguration.enableOptimizedOpcodes()) { + registry.put(new ShlOperationOptimized(gasCalculator)); + registry.put(new ShrOperationOptimized(gasCalculator)); + registry.put(new SarOperationOptimized(gasCalculator)); + } else { + registry.put(new ShlOperation(gasCalculator)); + registry.put(new ShrOperation(gasCalculator)); + registry.put(new SarOperation(gasCalculator)); + } registry.put(new ExtCodeHashOperation(gasCalculator)); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java new file mode 100644 index 00000000000..adbea0cb47a --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -0,0 +1,123 @@ +/* + * 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.evm.operation; + +import static org.hyperledger.besu.evm.operation.Shift256Operations.ALL_ONES; +import static org.hyperledger.besu.evm.operation.Shift256Operations.ALL_ONES_BYTES; +import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import java.util.Arrays; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** The Sar operation. */ +public class SarOperationOptimized extends AbstractFixedCostOperation { + + /** The Sar operation success result. */ + static final OperationResult sarSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Sar operation. + * + * @param gasCalculator the gas calculator + */ + public SarOperationOptimized(final GasCalculator gasCalculator) { + super(0x1d, "SAR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame); + } + + /** + * Performs sar operation. + * + * @param frame the frame + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame) { + final Bytes shiftAmount = frame.popStackItem(); + final Bytes value = frame.popStackItem(); + byte[] valueBytes = value.toArrayUnsafe(); + if (Arrays.equals(valueBytes, ALL_ONES_BYTES)) { + frame.pushStackItem(ALL_ONES); + return sarSuccess; + } + valueBytes = Bytes32.leftPad(value).toArrayUnsafe(); + final byte[] shiftBytes = shiftAmount.toArrayUnsafe(); + final boolean negative = (valueBytes[0] & 0x80) != 0; + + // shift >= 256, push All 1s if negative, All 0s otherwise + if (isShiftOverflow(shiftBytes)) { + frame.pushStackItem(negative ? ALL_ONES : Bytes.EMPTY); + return sarSuccess; + } + final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); + + frame.pushStackItem(sar256(valueBytes, shift, negative)); + return sarSuccess; + } + + /** + * Performs a 256-bit arithmetic right shift (EVM SAR). + * + *
The input value is treated as a signed 256-bit integer in two's complement representation. + * The shift amount is in the range {@code [0..255]} and is assumed to have been validated by the + * caller. + * + *
For shift values greater than or equal to 256, the result is fully sign-extended and handled + * by the caller. + * + * @param in the raw 32-byte array of the input value + * @param shift the right shift amount in bits (0–255) + * @param negative whether the input value is negative (sign bit set) + * @return the shifted 256-bit value + */ + private static Bytes sar256(final byte[] in, final int shift, final boolean negative) { + if (shift == 0) return Bytes.wrap(in); + + final int shiftBytes = shift >>> 3; // /8 + final int shiftBits = shift & 7; // %8 + final int fill = negative ? 0xFF : 0x00; + + final byte[] out = new byte[32]; + + // Pre-fill sign-extended bytes (indices below shiftBytes are fully sign-extended) + if (negative && shiftBytes > 0) { + Arrays.fill(out, 0, shiftBytes, (byte) 0xFF); + } + + // Only iterate bytes that receive shifted data from the input + for (int i = 31; i >= shiftBytes; i--) { + final int srcIndex = i - shiftBytes; + final int curr = in[srcIndex] & 0xFF; + if (shiftBits == 0) { + out[i] = (byte) curr; + } else { + final int prev = (srcIndex - 1 >= 0) ? (in[srcIndex - 1] & 0xFF) : fill; + out[i] = (byte) ((curr >>> shiftBits) | (prev << (8 - shiftBits))); + } + } + + return Bytes.wrap(out); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java new file mode 100644 index 00000000000..edfdab6be03 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java @@ -0,0 +1,53 @@ +/* + * 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.evm.operation; + +import java.util.Arrays; + +import org.apache.tuweni.bytes.Bytes; + +/** + * Utility class for shared constants and helpers used by optimized 256-bit shift operations (SHL, + * SHR, SAR). + */ +public final class Shift256Operations { + + /** An array of 31 0 bytes */ + private static final byte[] ZERO_31 = new byte[31]; + + /** All ones (0xFF repeated 32 times). */ + public static final Bytes ALL_ONES = Bytes.repeat((byte) 0xFF, 32); + + /** Raw byte array of ALL_ONES for use with {@code Arrays.equals} (JVM intrinsic). */ + static final byte[] ALL_ONES_BYTES = ALL_ONES.toArrayUnsafe(); + + private Shift256Operations() { + // Utility class - prevent instantiation + } + + /** + * Checks whether the EVM shift amount overflows (shift >= 256). + * + *
If any byte except the last byte is non-zero, the value is >= 256. + * + * @param shiftBytes the raw byte array of the shift amount + * @return {@code true} if the shift amount is >= 256, {@code false} otherwise + */ + public static boolean isShiftOverflow(final byte[] shiftBytes) { + final int len = shiftBytes.length - 1; + if (len <= 0) return false; + return !Arrays.equals(shiftBytes, 0, len, ZERO_31, 0, len); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java new file mode 100644 index 00000000000..a691636e060 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java @@ -0,0 +1,117 @@ +/* + * 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.evm.operation; + +import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * The optimized SHL (Shift Left) operation. + * + *
This implementation uses direct byte[] manipulation instead of Tuweni's Bytes.shiftLeft() to + * avoid intermediate object allocation and improve performance. + */ +public class ShlOperationOptimized extends AbstractFixedCostOperation { + + /** The Shl operation success result. */ + static final OperationResult shlSuccess = new OperationResult(3, null); + + /** + * Instantiates a new optimized Shl operation. + * + * @param gasCalculator the gas calculator + */ + public ShlOperationOptimized(final GasCalculator gasCalculator) { + super(0x1b, "SHL", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame); + } + + /** + * Performs optimized Shift Left operation. + * + * @param frame the frame + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame) { + final Bytes shiftAmount = frame.popStackItem(); + final Bytes value = frame.popStackItem(); + if (value.isZero()) { + frame.pushStackItem(Bytes.EMPTY); + return shlSuccess; + } + + final byte[] valueBytes = Bytes32.leftPad(value).toArrayUnsafe(); + final byte[] shiftBytes = shiftAmount.toArrayUnsafe(); + + // shift >= 256, push All 0s + if (isShiftOverflow(shiftBytes)) { + frame.pushStackItem(Bytes.EMPTY); + return shlSuccess; + } + + final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); + + frame.pushStackItem(shl256(valueBytes, shift)); + return shlSuccess; + } + + /** + * Performs a 256-bit logical left shift (EVM SHL). + * + *
The shift amount is in the range {@code [0..255]} and is assumed to have been validated by + * the caller. For shift values >= 256, zero is returned by the caller. + * + * @param in the raw 32-byte array of the input value + * @param shift the left shift amount in bits (0–255) + * @return the shifted 256-bit value + */ + private static Bytes shl256(final byte[] in, final int shift) { + if (shift == 0) { + return Bytes.wrap(in); + } + + final int shiftBytes = shift >>> 3; // /8 + final int shiftBits = shift & 7; // %8 + + final byte[] out = new byte[32]; + + // Shift left: bytes move to lower indices (towards index 0) + // Bytes at index >= (32 - shiftBytes) are guaranteed zero (already from new byte[32]) + final int limit = 32 - shiftBytes; + for (int i = 0; i < limit; i++) { + final int srcIndex = i + shiftBytes; + final int curr = in[srcIndex] & 0xFF; + if (shiftBits == 0) { + out[i] = (byte) curr; + } else { + final int next = (srcIndex + 1 < 32) ? (in[srcIndex + 1] & 0xFF) : 0; + out[i] = (byte) ((curr << shiftBits) | (next >>> (8 - shiftBits))); + } + } + + return Bytes.wrap(out); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java new file mode 100644 index 00000000000..1eeb1b1ca5c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java @@ -0,0 +1,116 @@ +/* + * 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.evm.operation; + +import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * The optimized SHR (Shift Right Logical) operation. + * + *
This implementation uses direct byte[] manipulation instead of Tuweni's Bytes.shiftRight() to + * avoid intermediate object allocation and improve performance. + */ +public class ShrOperationOptimized extends AbstractFixedCostOperation { + + /** The Shr operation success result. */ + static final OperationResult shrSuccess = new OperationResult(3, null); + + /** + * Instantiates a new optimized Shr operation. + * + * @param gasCalculator the gas calculator + */ + public ShrOperationOptimized(final GasCalculator gasCalculator) { + super(0x1c, "SHR", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame); + } + + /** + * Performs optimized Shift Right Logical operation. + * + * @param frame the frame + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame) { + final Bytes shiftAmount = frame.popStackItem(); + final Bytes value = frame.popStackItem(); + if (value.isZero()) { + frame.pushStackItem(Bytes.EMPTY); + return shrSuccess; + } + + final byte[] valueBytes = Bytes32.leftPad(value).toArrayUnsafe(); + final byte[] shiftBytes = shiftAmount.toArrayUnsafe(); + + // shift >= 256, push All 0s + if (isShiftOverflow(shiftBytes)) { + frame.pushStackItem(Bytes.EMPTY); + return shrSuccess; + } + + final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); + + frame.pushStackItem(shr256(valueBytes, shift)); + return shrSuccess; + } + + /** + * Performs a 256-bit logical right shift (EVM SHR). + * + *
The shift amount is in the range {@code [0..255]} and is assumed to have been validated by + * the caller. For shift values >= 256, zero is returned by the caller. + * + * @param in the raw 32-byte array of the input value + * @param shift the right shift amount in bits (0–255) + * @return the shifted 256-bit value + */ + private static Bytes shr256(final byte[] in, final int shift) { + if (shift == 0) { + return Bytes.wrap(in); + } + + final int shiftBytes = shift >>> 3; // /8 + final int shiftBits = shift & 7; // %8 + + final byte[] out = new byte[32]; + + // Shift right: bytes move to higher indices (towards index 31) + // Bytes below shiftBytes are guaranteed zero (already from new byte[32]) + for (int i = 31; i >= shiftBytes; i--) { + final int srcIndex = i - shiftBytes; + final int curr = in[srcIndex] & 0xFF; + if (shiftBits == 0) { + out[i] = (byte) curr; + } else { + final int prev = (srcIndex - 1 >= 0) ? (in[srcIndex - 1] & 0xFF) : 0; + out[i] = (byte) ((curr >>> shiftBits) | (prev << (8 - shiftBits))); + } + } + + return Bytes.wrap(out); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java new file mode 100644 index 00000000000..8f89f50bbeb --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java @@ -0,0 +1,405 @@ +/* + * 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.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.evm.frame.MessageFrame; + +import java.util.ArrayDeque; +import java.util.Deque; + +import net.jqwik.api.Arbitraries; +import net.jqwik.api.Arbitrary; +import net.jqwik.api.ForAll; +import net.jqwik.api.Property; +import net.jqwik.api.Provide; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * Property-based tests comparing original SAR operation with the optimized version. + * + *
Tests verify that SarOperationOptimized produces identical results to SarOperation for all
+ * possible inputs, including edge cases for negative values and sign extension.
+ */
+public class SarOperationPropertyBasedTest {
+
+ // region Arbitrary Providers
+
+ @Provide
+ Arbitrary Tests verify that SHL/SHR optimized implementations produce identical results to the original
+ * implementations for all possible inputs.
+ */
+public class ShiftOperationsPropertyBasedTest {
+
+ // region Arbitrary Providers
+
+ @Provide
+ Arbitrary