From ab9e3cd9a55a1c4ec3d7b12c00de2b1bbad1b4d4 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Wed, 28 Jan 2026 17:30:46 +0100 Subject: [PATCH 01/27] Improve SAR opcode - first round Signed-off-by: Ameziane H. --- .../vm/operations/SarOperationBenchmark.java | 138 +++++++++++++++++ .../SarOperationOptimizedBenchmark.java | 139 ++++++++++++++++++ .../evm/operation/SarOperationOptimized.java | 134 +++++++++++++++++ 3 files changed, 411 insertions(+) create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationBenchmark.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationOptimizedBenchmark.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java 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..566a7338cf6 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationBenchmark.java @@ -0,0 +1,138 @@ +/* + * 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; + +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; + +public class SarOperationBenchmark extends BinaryOperationBenchmark { + + // Define available scenarios to test different execution paths + 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, + // Positive number with shift=1 - no sign extension needed + POSITIVE_SHIFT_1, + // Negative number with medium shift + NEGATIVE_SHIFT_128, + // Positive number with medium shift + POSITIVE_SHIFT_128, + // Overflow: shift >= 256 + OVERFLOW_SHIFT_256, + // Overflow: shift amount > 4 bytes + OVERFLOW_LARGE_SHIFT, + // Random values (original behavior) + FULL_RANDOM + } + + private static final Bytes ALL_BITS = + Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + private static final Bytes POSITIVE_VALUE = + Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + + @Param({ + "SHIFT_0", + "NEGATIVE_SHIFT_1", + "POSITIVE_SHIFT_1", + "NEGATIVE_SHIFT_128", + "POSITIVE_SHIFT_128", + "OVERFLOW_SHIFT_256", + "OVERFLOW_LARGE_SHIFT", + "FULL_RANDOM" + }) + private 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] = ALL_BITS; + break; + + case NEGATIVE_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, value = 0x7ff...fff (positive, no sign extension) + aPool[i] = Bytes.of(1); + bPool[i] = POSITIVE_VALUE; + break; + + case NEGATIVE_SHIFT_128: + aPool[i] = Bytes.of(128); + bPool[i] = ALL_BITS; + break; + + case POSITIVE_SHIFT_128: + aPool[i] = Bytes.of(128); + bPool[i] = POSITIVE_VALUE; + break; + + case OVERFLOW_SHIFT_256: + // Shift of exactly 256 - overflow path + aPool[i] = Bytes.fromHexString("0x0100"); // 256 + bPool[i] = ALL_BITS; + break; + + case OVERFLOW_LARGE_SHIFT: + // Shift amount > 4 bytes - overflow path + aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes + bPool[i] = ALL_BITS; + break; + + case FULL_RANDOM: + default: + // Original random behavior + 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; + } + + @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..6300fcef8bd --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SarOperationOptimizedBenchmark.java @@ -0,0 +1,139 @@ +/* + * 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; + +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.evm.operation.SarOperationOptimized2; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +public class SarOperationOptimizedBenchmark extends BinaryOperationBenchmark { + + // Define available scenarios to test different execution paths + 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, + // Positive number with shift=1 - no sign extension needed + POSITIVE_SHIFT_1, + // Negative number with medium shift + NEGATIVE_SHIFT_128, + // Positive number with medium shift + POSITIVE_SHIFT_128, + // Overflow: shift >= 256 + OVERFLOW_SHIFT_256, + // Overflow: shift amount > 4 bytes + OVERFLOW_LARGE_SHIFT, + // Random values (original behavior) + FULL_RANDOM + } + + private static final Bytes ALL_BITS = + Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + private static final Bytes POSITIVE_VALUE = + Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + + @Param({ + "SHIFT_0", + "NEGATIVE_SHIFT_1", + "POSITIVE_SHIFT_1", + "NEGATIVE_SHIFT_128", + "POSITIVE_SHIFT_128", + "OVERFLOW_SHIFT_256", + "OVERFLOW_LARGE_SHIFT", + "FULL_RANDOM" + }) + private 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] = ALL_BITS; + break; + + case NEGATIVE_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, value = 0x7ff...fff (positive, no sign extension) + aPool[i] = Bytes.of(1); + bPool[i] = POSITIVE_VALUE; + break; + + case NEGATIVE_SHIFT_128: + aPool[i] = Bytes.of(128); + bPool[i] = ALL_BITS; + break; + + case POSITIVE_SHIFT_128: + aPool[i] = Bytes.of(128); + bPool[i] = POSITIVE_VALUE; + break; + + case OVERFLOW_SHIFT_256: + // Shift of exactly 256 - overflow path + aPool[i] = Bytes.fromHexString("0x0100"); // 256 + bPool[i] = ALL_BITS; + break; + + case OVERFLOW_LARGE_SHIFT: + // Shift amount > 4 bytes - overflow path + aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes + bPool[i] = ALL_BITS; + break; + + case FULL_RANDOM: + default: + // Original random behavior + 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; + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return SarOperationOptimized.staticOperation(frame); + } +} 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..e1ca5695a6b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -0,0 +1,134 @@ +/* + * Copyright ConsenSys AG. + * + * 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.apache.tuweni.bytes.Bytes32.leftPad; + +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; + +/** 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); + } + + private static final Bytes[] SIGN_EXTENSION_MASKS = new Bytes[256]; + /** All ones (0xFF repeated 32 times). */ + public static final Bytes ALL_ONES = Bytes.repeat((byte) 0xFF, 32); + + /** Zero value (32 zero bytes). */ + public static final Bytes ZERO_32 = Bytes.wrap(new byte[32]); + static { + // shift = 0 → no sign extension (never used, but keep ZERO) + SIGN_EXTENSION_MASKS[0] = ZERO_32; + + for (int s = 1; s < 256; s++) { + // mask with top s bits set + SIGN_EXTENSION_MASKS[s] = ALL_ONES.shiftLeft(256 - s); + } + } + + + /** + * 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 = leftPad(frame.popStackItem()); // ideally eliminate, see below + + final boolean negative = (value.get(0) & 0x80) != 0; + +// detect shift >= 256 cheaply (check high bytes) + if (isShiftOverflow(shiftAmount)) { + frame.pushStackItem(negative ? ALL_ONES : ZERO_32); + return sarSuccess; + } + final int shift = (shiftAmount.get(shiftAmount.size() - 1) & 0xFF); + + frame.pushStackItem(sar256(value, shift, negative)); + return sarSuccess; + + } + + private static Bytes sar256(final Bytes value32, final int shift, final boolean negative) { + // shift is assumed 0..255, value32 is 32 bytes. + if (shift == 0) return value32; + + final int shiftBytes = shift >>> 3; // /8 + final int shiftBits = shift & 7; // %8 + final int fill = negative ? 0xFF : 0x00; + + final byte[] out = new byte[32]; + + // If shiftBytes >= 32, result is all fill (but you should have handled shift>=256 earlier) + if (shiftBytes >= 32) { + if (negative) java.util.Arrays.fill(out, (byte) 0xFF); + return Bytes.wrap(out); + } + + final byte[] in = value32.toArrayUnsafe(); + + for (int i = 31; i >= 0; i--) { + final int src = i - shiftBytes; + + final int hi = (src >= 0) ? (in[src] & 0xFF) : fill; + if (shiftBits == 0) { + out[i] = (byte) hi; + } else { + final int lo = (src - 1 >= 0) ? (in[src - 1] & 0xFF) : fill; + out[i] = (byte) ((hi >>> shiftBits) | ((lo << (8 - shiftBits)) & 0xFF)); + } + } + + return Bytes.wrap(out); + } + + private static boolean isShiftOverflow(final Bytes shiftAmount) { + final byte[] a = shiftAmount.toArrayUnsafe(); + final int n = a.length; + if (n <= 1) return false; + + int acc = 0; + for (int i = 0; i < n - 1; i++) { + acc |= a[i]; // OR-reduce all high bytes + } + return acc != 0; + } + + + +} From c6e21bdd3f9d07f7bd703fd03145c24370777dac Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Wed, 28 Jan 2026 17:39:48 +0100 Subject: [PATCH 02/27] Improve SAR opcode - first round Signed-off-by: Ameziane H. --- .../SarOperationOptimizedBenchmark.java | 1 - .../main/java/org/hyperledger/besu/evm/EVM.java | 6 ++++++ .../org/hyperledger/besu/evm/MainnetEVMs.java | 7 ++++++- .../evm/operation/SarOperationOptimized.java | 17 +++++++---------- 4 files changed, 19 insertions(+), 12 deletions(-) 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 index 6300fcef8bd..f54161dce35 100644 --- 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 @@ -21,7 +21,6 @@ import java.util.concurrent.ThreadLocalRandom; import org.apache.tuweni.bytes.Bytes; -import org.hyperledger.besu.evm.operation.SarOperationOptimized2; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; 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..6c46e832866 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,8 @@ 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.SignExtendOperation; import org.hyperledger.besu.evm.operation.StopOperation; import org.hyperledger.besu.evm.operation.SubOperation; @@ -280,6 +282,10 @@ public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { ? NotOperationOptimized.staticOperation(frame) : NotOperation.staticOperation(frame); case 0x1a -> ByteOperation.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 d6a798e48da..55443b98aeb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -116,6 +116,7 @@ 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; @@ -492,7 +493,11 @@ private static void registerConstantinopleOperations( final EvmConfiguration evmConfiguration) { registerByzantiumOperations(registry, gasCalculator, evmConfiguration); registry.put(new Create2Operation(gasCalculator)); - registry.put(new SarOperation(gasCalculator)); + if (evmConfiguration.enableOptimizedOpcodes()) { + registry.put(new SarOperationOptimized(gasCalculator)); + } else { + registry.put(new SarOperation(gasCalculator)); + } registry.put(new ShlOperation(gasCalculator)); registry.put(new ShrOperation(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 index e1ca5695a6b..06a8ffc1d84 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -39,16 +39,18 @@ public SarOperationOptimized(final GasCalculator gasCalculator) { @Override public Operation.OperationResult executeFixedCostOperation( - final MessageFrame frame, final EVM evm) { + final MessageFrame frame, final EVM evm) { return staticOperation(frame); } private static final Bytes[] SIGN_EXTENSION_MASKS = new Bytes[256]; + /** All ones (0xFF repeated 32 times). */ public static final Bytes ALL_ONES = Bytes.repeat((byte) 0xFF, 32); /** Zero value (32 zero bytes). */ public static final Bytes ZERO_32 = Bytes.wrap(new byte[32]); + static { // shift = 0 → no sign extension (never used, but keep ZERO) SIGN_EXTENSION_MASKS[0] = ZERO_32; @@ -59,7 +61,6 @@ public Operation.OperationResult executeFixedCostOperation( } } - /** * Performs sar operation. * @@ -72,7 +73,7 @@ public static OperationResult staticOperation(final MessageFrame frame) { final boolean negative = (value.get(0) & 0x80) != 0; -// detect shift >= 256 cheaply (check high bytes) + // detect shift >= 256 cheaply (check high bytes) if (isShiftOverflow(shiftAmount)) { frame.pushStackItem(negative ? ALL_ONES : ZERO_32); return sarSuccess; @@ -81,15 +82,14 @@ public static OperationResult staticOperation(final MessageFrame frame) { frame.pushStackItem(sar256(value, shift, negative)); return sarSuccess; - } private static Bytes sar256(final Bytes value32, final int shift, final boolean negative) { // shift is assumed 0..255, value32 is 32 bytes. if (shift == 0) return value32; - final int shiftBytes = shift >>> 3; // /8 - final int shiftBits = shift & 7; // %8 + final int shiftBytes = shift >>> 3; // /8 + final int shiftBits = shift & 7; // %8 final int fill = negative ? 0xFF : 0x00; final byte[] out = new byte[32]; @@ -124,11 +124,8 @@ private static boolean isShiftOverflow(final Bytes shiftAmount) { int acc = 0; for (int i = 0; i < n - 1; i++) { - acc |= a[i]; // OR-reduce all high bytes + acc |= a[i]; // OR-reduce all high bytes } return acc != 0; } - - - } From 969167023aa75571284e12cf03d564c95ed437f6 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Wed, 28 Jan 2026 21:46:49 +0100 Subject: [PATCH 03/27] Improve isShiftOverflow Signed-off-by: Ameziane H. --- .../besu/evm/operation/SarOperationOptimized.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) 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 index 06a8ffc1d84..7c9f0bd459d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -69,12 +69,12 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); - final Bytes value = leftPad(frame.popStackItem()); // ideally eliminate, see below + final Bytes value = leftPad(frame.popStackItem()); final boolean negative = (value.get(0) & 0x80) != 0; // detect shift >= 256 cheaply (check high bytes) - if (isShiftOverflow(shiftAmount)) { + if (isShiftOverflowEarlyExit(shiftAmount)) { frame.pushStackItem(negative ? ALL_ONES : ZERO_32); return sarSuccess; } @@ -85,7 +85,6 @@ public static OperationResult staticOperation(final MessageFrame frame) { } private static Bytes sar256(final Bytes value32, final int shift, final boolean negative) { - // shift is assumed 0..255, value32 is 32 bytes. if (shift == 0) return value32; final int shiftBytes = shift >>> 3; // /8 @@ -117,15 +116,12 @@ private static Bytes sar256(final Bytes value32, final int shift, final boolean return Bytes.wrap(out); } - private static boolean isShiftOverflow(final Bytes shiftAmount) { + private static boolean isShiftOverflowEarlyExit(final Bytes shiftAmount) { final byte[] a = shiftAmount.toArrayUnsafe(); final int n = a.length; - if (n <= 1) return false; - - int acc = 0; for (int i = 0; i < n - 1; i++) { - acc |= a[i]; // OR-reduce all high bytes + if (a[i] != 0) return true; } - return acc != 0; + return false; } } From 9b20601ab6adbffe4586fdc3b6adc67b0b4b4e7a Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Wed, 4 Feb 2026 18:34:29 +0100 Subject: [PATCH 04/27] Small refactoring Signed-off-by: Ameziane H. --- .../evm/operation/SarOperationOptimized.java | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) 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 index 7c9f0bd459d..d941eb306ee 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -14,8 +14,7 @@ */ package org.hyperledger.besu.evm.operation; -import static org.apache.tuweni.bytes.Bytes32.leftPad; - +import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -42,25 +41,12 @@ public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { return staticOperation(frame); } - - private static final Bytes[] SIGN_EXTENSION_MASKS = new Bytes[256]; - /** All ones (0xFF repeated 32 times). */ public static final Bytes ALL_ONES = Bytes.repeat((byte) 0xFF, 32); /** Zero value (32 zero bytes). */ public static final Bytes ZERO_32 = Bytes.wrap(new byte[32]); - static { - // shift = 0 → no sign extension (never used, but keep ZERO) - SIGN_EXTENSION_MASKS[0] = ZERO_32; - - for (int s = 1; s < 256; s++) { - // mask with top s bits set - SIGN_EXTENSION_MASKS[s] = ALL_ONES.shiftLeft(256 - s); - } - } - /** * Performs sar operation. * @@ -69,12 +55,12 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); - final Bytes value = leftPad(frame.popStackItem()); + final Bytes value = Bytes32.leftPad(frame.popStackItem()); final boolean negative = (value.get(0) & 0x80) != 0; // detect shift >= 256 cheaply (check high bytes) - if (isShiftOverflowEarlyExit(shiftAmount)) { + if (isShiftOverflow(shiftAmount)) { frame.pushStackItem(negative ? ALL_ONES : ZERO_32); return sarSuccess; } @@ -84,6 +70,23 @@ public static OperationResult staticOperation(final MessageFrame frame) { 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 value32 a 32-byte value representing a signed 256-bit integer + * @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 Bytes value32, final int shift, final boolean negative) { if (shift == 0) return value32; @@ -92,19 +95,10 @@ private static Bytes sar256(final Bytes value32, final int shift, final boolean final int fill = negative ? 0xFF : 0x00; final byte[] out = new byte[32]; - - // If shiftBytes >= 32, result is all fill (but you should have handled shift>=256 earlier) - if (shiftBytes >= 32) { - if (negative) java.util.Arrays.fill(out, (byte) 0xFF); - return Bytes.wrap(out); - } - final byte[] in = value32.toArrayUnsafe(); for (int i = 31; i >= 0; i--) { - final int src = i - shiftBytes; - - final int hi = (src >= 0) ? (in[src] & 0xFF) : fill; + final int src = i - shiftBytes; final int hi = (src >= 0) ? (in[src] & 0xFF) : fill; if (shiftBits == 0) { out[i] = (byte) hi; } else { @@ -116,7 +110,13 @@ private static Bytes sar256(final Bytes value32, final int shift, final boolean return Bytes.wrap(out); } - private static boolean isShiftOverflowEarlyExit(final Bytes shiftAmount) { + /** + * Checks whether the EVM SAR shift amount overflows (shift ≥ 256). + * + * @param shiftAmount the shift amount as a stack value + * @return {@code true} if the shift amount is ≥ 256, {@code false} otherwise + */ + private static boolean isShiftOverflow(final Bytes shiftAmount) { final byte[] a = shiftAmount.toArrayUnsafe(); final int n = a.length; for (int i = 0; i < n - 1; i++) { From 9ada695b82cd272b5fe19f4a64ac68e8b648b3c0 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 5 Feb 2026 15:55:51 +0100 Subject: [PATCH 05/27] Fix an issue with empty shift Signed-off-by: Ameziane H. --- .../evm/operation/SarOperationOptimized.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) 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 index d941eb306ee..cdd2ced38e3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -14,12 +14,12 @@ */ package org.hyperledger.besu.evm.operation; -import org.apache.tuweni.bytes.Bytes32; 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 Sar operation. */ public class SarOperationOptimized extends AbstractFixedCostOperation { @@ -41,6 +41,7 @@ public Operation.OperationResult executeFixedCostOperation( final MessageFrame frame, final EVM evm) { return staticOperation(frame); } + /** All ones (0xFF repeated 32 times). */ public static final Bytes ALL_ONES = Bytes.repeat((byte) 0xFF, 32); @@ -56,7 +57,6 @@ public Operation.OperationResult executeFixedCostOperation( public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); final Bytes value = Bytes32.leftPad(frame.popStackItem()); - final boolean negative = (value.get(0) & 0x80) != 0; // detect shift >= 256 cheaply (check high bytes) @@ -64,29 +64,27 @@ public static OperationResult staticOperation(final MessageFrame frame) { frame.pushStackItem(negative ? ALL_ONES : ZERO_32); return sarSuccess; } - final int shift = (shiftAmount.get(shiftAmount.size() - 1) & 0xFF); + final int shift = shiftAmount.isEmpty() ? 0 : (shiftAmount.get(shiftAmount.size() - 1) & 0xFF); frame.pushStackItem(sar256(value, 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.

+ *

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.

+ *

For shift values greater than or equal to 256, the result is fully sign-extended and handled + * by the caller. * * @param value32 a 32-byte value representing a signed 256-bit integer * @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 Bytes value32, final int shift, final boolean negative) { if (shift == 0) return value32; @@ -98,7 +96,8 @@ private static Bytes sar256(final Bytes value32, final int shift, final boolean final byte[] in = value32.toArrayUnsafe(); for (int i = 31; i >= 0; i--) { - final int src = i - shiftBytes; final int hi = (src >= 0) ? (in[src] & 0xFF) : fill; + final int src = i - shiftBytes; + final int hi = (src >= 0) ? (in[src] & 0xFF) : fill; if (shiftBits == 0) { out[i] = (byte) hi; } else { From d4363f47592d008578855ecfec36c0f491ad915a Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Fri, 6 Feb 2026 15:22:02 +0100 Subject: [PATCH 06/27] Optimize Sar, Shl and Shr performances Signed-off-by: Ameziane H. --- .../AbstractSarOperationBenchmark.java | 139 ++++++ .../AbstractShiftOperationBenchmark.java | 139 ++++++ .../vm/operations/SarOperationBenchmark.java | 114 +---- .../SarOperationOptimizedBenchmark.java | 114 +---- .../vm/operations/ShlOperationBenchmark.java | 28 ++ .../ShlOperationOptimizedBenchmark.java | 28 ++ .../vm/operations/ShrOperationBenchmark.java | 28 ++ .../ShrOperationOptimizedBenchmark.java | 28 ++ .../evm/operation/SarOperationOptimized.java | 27 +- .../evm/operation/Shift256Operations.java | 56 +++ .../evm/operation/ShlOperationOptimized.java | 110 +++++ .../evm/operation/ShrOperationOptimized.java | 110 +++++ .../evm/operation/Shift256OperationsTest.java | 83 ++++ .../ShiftOperationsPerformanceTest.java | 430 ++++++++++++++++++ .../ShiftOperationsPropertyBasedTest.java | 292 ++++++++++++ 15 files changed, 1480 insertions(+), 246 deletions(-) create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractSarOperationBenchmark.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractShiftOperationBenchmark.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationBenchmark.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShlOperationOptimizedBenchmark.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationBenchmark.java create mode 100644 ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ShrOperationOptimizedBenchmark.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPropertyBasedTest.java 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..48bd384ecb8 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractSarOperationBenchmark.java @@ -0,0 +1,139 @@ +/* + * 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 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, + /** Positive number with shift=1 - no sign extension needed. */ + POSITIVE_SHIFT_1, + /** Negative number with medium shift. */ + NEGATIVE_SHIFT_128, + /** Positive number with medium shift. */ + POSITIVE_SHIFT_128, + /** 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"); + + /** Max positive value (sign bit not set). */ + protected static final Bytes POSITIVE_VALUE = + Bytes.fromHexString( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + + @Param({ + "SHIFT_0", + "NEGATIVE_SHIFT_1", + "POSITIVE_SHIFT_1", + "NEGATIVE_SHIFT_128", + "POSITIVE_SHIFT_128", + "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] = ALL_BITS; + break; + + case NEGATIVE_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, value = 0x7ff...fff (positive, no sign extension) + aPool[i] = Bytes.of(1); + bPool[i] = POSITIVE_VALUE; + break; + + case NEGATIVE_SHIFT_128: + aPool[i] = Bytes.of(128); + bPool[i] = ALL_BITS; + break; + + case POSITIVE_SHIFT_128: + aPool[i] = Bytes.of(128); + bPool[i] = POSITIVE_VALUE; + break; + + case OVERFLOW_SHIFT_256: + // Shift of exactly 256 - overflow path + aPool[i] = Bytes.fromHexString("0x0100"); // 256 + bPool[i] = ALL_BITS; + break; + + case OVERFLOW_LARGE_SHIFT: + // Shift amount > 4 bytes - overflow path + aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes + bPool[i] = ALL_BITS; + break; + + case FULL_RANDOM: + default: + // Original random behavior + 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/AbstractShiftOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractShiftOperationBenchmark.java new file mode 100644 index 00000000000..9ef14d6079a --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AbstractShiftOperationBenchmark.java @@ -0,0 +1,139 @@ +/* + * 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 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 + } + + /** All bits set (32 bytes of 0xFF). */ + protected static final Bytes ALL_BITS = + Bytes.fromHexString( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + + /** Max positive value (sign bit not set). */ + protected static final Bytes POSITIVE_VALUE = + Bytes.fromHexString( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + + @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] = getValueForCase(scenario); + break; + + case SHIFT_1: + aPool[i] = Bytes.of(1); + bPool[i] = getValueForCase(scenario); + break; + + case SHIFT_128: + aPool[i] = Bytes.of(128); + bPool[i] = getValueForCase(scenario); + break; + + case SHIFT_255: + aPool[i] = Bytes.of(255); + bPool[i] = getValueForCase(scenario); + break; + + case OVERFLOW_SHIFT_256: + aPool[i] = Bytes.fromHexString("0x0100"); // 256 + bPool[i] = getValueForCase(scenario); + break; + + case OVERFLOW_LARGE_SHIFT: + aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes + bPool[i] = getValueForCase(scenario); + 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; + } + + /** + * Returns the value to use for the given test case. + * + *

Subclasses can override this to use different values for specific cases (e.g., SAR uses + * negative values to test sign extension). + * + * @param scenario the test case + * @return the value to use + */ + protected Bytes getValueForCase(final Case scenario) { + return ALL_BITS; + } +} 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 index 566a7338cf6..83e6db2ed45 100644 --- 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 @@ -18,118 +18,8 @@ import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.operation.SarOperation; -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; - -public class SarOperationBenchmark extends BinaryOperationBenchmark { - - // Define available scenarios to test different execution paths - 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, - // Positive number with shift=1 - no sign extension needed - POSITIVE_SHIFT_1, - // Negative number with medium shift - NEGATIVE_SHIFT_128, - // Positive number with medium shift - POSITIVE_SHIFT_128, - // Overflow: shift >= 256 - OVERFLOW_SHIFT_256, - // Overflow: shift amount > 4 bytes - OVERFLOW_LARGE_SHIFT, - // Random values (original behavior) - FULL_RANDOM - } - - private static final Bytes ALL_BITS = - Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - private static final Bytes POSITIVE_VALUE = - Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - - @Param({ - "SHIFT_0", - "NEGATIVE_SHIFT_1", - "POSITIVE_SHIFT_1", - "NEGATIVE_SHIFT_128", - "POSITIVE_SHIFT_128", - "OVERFLOW_SHIFT_256", - "OVERFLOW_LARGE_SHIFT", - "FULL_RANDOM" - }) - private 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] = ALL_BITS; - break; - - case NEGATIVE_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, value = 0x7ff...fff (positive, no sign extension) - aPool[i] = Bytes.of(1); - bPool[i] = POSITIVE_VALUE; - break; - - case NEGATIVE_SHIFT_128: - aPool[i] = Bytes.of(128); - bPool[i] = ALL_BITS; - break; - - case POSITIVE_SHIFT_128: - aPool[i] = Bytes.of(128); - bPool[i] = POSITIVE_VALUE; - break; - - case OVERFLOW_SHIFT_256: - // Shift of exactly 256 - overflow path - aPool[i] = Bytes.fromHexString("0x0100"); // 256 - bPool[i] = ALL_BITS; - break; - - case OVERFLOW_LARGE_SHIFT: - // Shift amount > 4 bytes - overflow path - aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes - bPool[i] = ALL_BITS; - break; - - case FULL_RANDOM: - default: - // Original random behavior - 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; - } +/** JMH benchmark for the original SAR (Shift Arithmetic Right) operation. */ +public class SarOperationBenchmark extends AbstractSarOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame 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 index f54161dce35..ceea8d402f4 100644 --- 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 @@ -18,118 +18,8 @@ import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.operation.SarOperationOptimized; -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; - -public class SarOperationOptimizedBenchmark extends BinaryOperationBenchmark { - - // Define available scenarios to test different execution paths - 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, - // Positive number with shift=1 - no sign extension needed - POSITIVE_SHIFT_1, - // Negative number with medium shift - NEGATIVE_SHIFT_128, - // Positive number with medium shift - POSITIVE_SHIFT_128, - // Overflow: shift >= 256 - OVERFLOW_SHIFT_256, - // Overflow: shift amount > 4 bytes - OVERFLOW_LARGE_SHIFT, - // Random values (original behavior) - FULL_RANDOM - } - - private static final Bytes ALL_BITS = - Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - private static final Bytes POSITIVE_VALUE = - Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - - @Param({ - "SHIFT_0", - "NEGATIVE_SHIFT_1", - "POSITIVE_SHIFT_1", - "NEGATIVE_SHIFT_128", - "POSITIVE_SHIFT_128", - "OVERFLOW_SHIFT_256", - "OVERFLOW_LARGE_SHIFT", - "FULL_RANDOM" - }) - private 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] = ALL_BITS; - break; - - case NEGATIVE_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, value = 0x7ff...fff (positive, no sign extension) - aPool[i] = Bytes.of(1); - bPool[i] = POSITIVE_VALUE; - break; - - case NEGATIVE_SHIFT_128: - aPool[i] = Bytes.of(128); - bPool[i] = ALL_BITS; - break; - - case POSITIVE_SHIFT_128: - aPool[i] = Bytes.of(128); - bPool[i] = POSITIVE_VALUE; - break; - - case OVERFLOW_SHIFT_256: - // Shift of exactly 256 - overflow path - aPool[i] = Bytes.fromHexString("0x0100"); // 256 - bPool[i] = ALL_BITS; - break; - - case OVERFLOW_LARGE_SHIFT: - // Shift amount > 4 bytes - overflow path - aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes - bPool[i] = ALL_BITS; - break; - - case FULL_RANDOM: - default: - // Original random behavior - 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; - } +/** JMH benchmark for the optimized SAR (Shift Arithmetic Right) operation. */ +public class SarOperationOptimizedBenchmark extends AbstractSarOperationBenchmark { @Override protected Operation.OperationResult invoke(final MessageFrame 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/operation/SarOperationOptimized.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java index cdd2ced38e3..8dfe3feda0c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -14,6 +14,10 @@ */ package org.hyperledger.besu.evm.operation; +import static org.hyperledger.besu.evm.operation.Shift256Operations.ALL_ONES; +import static org.hyperledger.besu.evm.operation.Shift256Operations.ZERO_32; +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; @@ -42,12 +46,6 @@ public Operation.OperationResult executeFixedCostOperation( return staticOperation(frame); } - /** All ones (0xFF repeated 32 times). */ - public static final Bytes ALL_ONES = Bytes.repeat((byte) 0xFF, 32); - - /** Zero value (32 zero bytes). */ - public static final Bytes ZERO_32 = Bytes.wrap(new byte[32]); - /** * Performs sar operation. * @@ -73,7 +71,7 @@ public static OperationResult staticOperation(final MessageFrame frame) { /** * 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 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. * @@ -108,19 +106,4 @@ private static Bytes sar256(final Bytes value32, final int shift, final boolean return Bytes.wrap(out); } - - /** - * Checks whether the EVM SAR shift amount overflows (shift ≥ 256). - * - * @param shiftAmount the shift amount as a stack value - * @return {@code true} if the shift amount is ≥ 256, {@code false} otherwise - */ - private static boolean isShiftOverflow(final Bytes shiftAmount) { - final byte[] a = shiftAmount.toArrayUnsafe(); - final int n = a.length; - for (int i = 0; i < n - 1; i++) { - if (a[i] != 0) return true; - } - return false; - } } 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..d9b06ded122 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java @@ -0,0 +1,56 @@ +/* + * Copyright ConsenSys AG. + * + * 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 org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * Utility class for shared constants and helpers used by optimized 256-bit shift operations (SHL, + * SHR, SAR). + */ +public final class Shift256Operations { + + /** Zero value (32 zero bytes). */ + public static final Bytes ZERO_32 = Bytes32.ZERO; + + /** All ones (0xFF repeated 32 times). */ + public static final Bytes ALL_ONES = Bytes.repeat((byte) 0xFF, 32); + + 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 shiftAmount the shift amount as a stack value + * @return {@code true} if the shift amount is >= 256, {@code false} otherwise + */ + public static boolean isShiftOverflow(final Bytes shiftAmount) { + final int n = shiftAmount.size(); + if (n == 0) { + return false; + } + for (int i = 0; i < n - 1; i++) { + if (shiftAmount.get(i) != 0) { + return true; + } + } + return false; + } +} 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..019e16bf8e2 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java @@ -0,0 +1,110 @@ +/* + * Copyright ConsenSys AG. + * + * 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.ZERO_32; +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 = Bytes32.leftPad(frame.popStackItem()); + + // Detect shift >= 256 cheaply (check high bytes) + if (isShiftOverflow(shiftAmount)) { + frame.pushStackItem(ZERO_32); + return shlSuccess; + } + + final int shift = shiftAmount.isEmpty() ? 0 : (shiftAmount.get(shiftAmount.size() - 1) & 0xFF); + + frame.pushStackItem(shl256(value, 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 value32 a 32-byte value + * @param shift the left shift amount in bits (0–255) + * @return the shifted 256-bit value + */ + private static Bytes shl256(final Bytes value32, final int shift) { + if (shift == 0) { + return value32; + } + + final int shiftBytes = shift >>> 3; // /8 + final int shiftBits = shift & 7; // %8 + + final byte[] out = new byte[32]; + final byte[] in = value32.toArrayUnsafe(); + + // Shift left: bytes move to lower indices (towards index 0) + for (int i = 0; i < 32; i++) { + final int src = i + shiftBytes; + final int lo = (src < 32) ? (in[src] & 0xFF) : 0; + if (shiftBits == 0) { + out[i] = (byte) lo; + } else { + final int hi = (src + 1 < 32) ? (in[src + 1] & 0xFF) : 0; + out[i] = (byte) ((lo << shiftBits) | (hi >>> (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..53e46db7275 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java @@ -0,0 +1,110 @@ +/* + * Copyright ConsenSys AG. + * + * 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.ZERO_32; +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 = Bytes32.leftPad(frame.popStackItem()); + + // Detect shift >= 256 cheaply (check high bytes) + if (isShiftOverflow(shiftAmount)) { + frame.pushStackItem(ZERO_32); + return shrSuccess; + } + + final int shift = shiftAmount.isEmpty() ? 0 : (shiftAmount.get(shiftAmount.size() - 1) & 0xFF); + + frame.pushStackItem(shr256(value, 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 value32 a 32-byte value + * @param shift the right shift amount in bits (0–255) + * @return the shifted 256-bit value + */ + private static Bytes shr256(final Bytes value32, final int shift) { + if (shift == 0) { + return value32; + } + + final int shiftBytes = shift >>> 3; // /8 + final int shiftBits = shift & 7; // %8 + + final byte[] out = new byte[32]; + final byte[] in = value32.toArrayUnsafe(); + + // Shift right: bytes move to higher indices (towards index 31) + for (int i = 31; i >= 0; i--) { + final int src = i - shiftBytes; + final int hi = (src >= 0) ? (in[src] & 0xFF) : 0; + if (shiftBits == 0) { + out[i] = (byte) hi; + } else { + final int lo = (src - 1 >= 0) ? (in[src - 1] & 0xFF) : 0; + out[i] = (byte) ((hi >>> shiftBits) | ((lo << (8 - shiftBits)) & 0xFF)); + } + } + + return Bytes.wrap(out); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java new file mode 100644 index 00000000000..bdc4d7d0e37 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java @@ -0,0 +1,83 @@ +/* + * 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.hyperledger.besu.evm.operation.Shift256Operations.ALL_ONES; +import static org.hyperledger.besu.evm.operation.Shift256Operations.ZERO_32; +import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; + +/** Unit tests for the {@link Shift256Operations} utility class. */ +class Shift256OperationsTest { + + // region Constants Tests + + @Test + void constants_areCorrect() { + assertThat(ZERO_32).isEqualTo(Bytes32.ZERO); + assertThat(ALL_ONES.size()).isEqualTo(32); + assertThat(ALL_ONES.toHexString()) + .isEqualTo("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + } + + // endregion + + // region isShiftOverflow Tests + + @Test + void isShiftOverflow_empty_returnsFalse() { + assertThat(isShiftOverflow(Bytes.EMPTY)).isFalse(); + } + + @Test + void isShiftOverflow_singleByte_returnsFalse() { + assertThat(isShiftOverflow(Bytes.of(0))).isFalse(); + assertThat(isShiftOverflow(Bytes.of(1))).isFalse(); + assertThat(isShiftOverflow(Bytes.of(128))).isFalse(); + assertThat(isShiftOverflow(Bytes.of(255))).isFalse(); + } + + @Test + void isShiftOverflow_multiByteWithZeroPrefix_returnsFalse() { + assertThat(isShiftOverflow(Bytes.of(0, 0, 42))).isFalse(); + assertThat(isShiftOverflow(Bytes.of(0, 0, 0, 0, 200))).isFalse(); + } + + @Test + void isShiftOverflow_256_returnsTrue() { + // 0x0100 = 256 + assertThat(isShiftOverflow(Bytes.fromHexString("0x0100"))).isTrue(); + } + + @Test + void isShiftOverflow_nonZeroHighByte_returnsTrue() { + assertThat(isShiftOverflow(Bytes.of(1, 0))).isTrue(); + assertThat(isShiftOverflow(Bytes.of(0, 1, 0))).isTrue(); + assertThat(isShiftOverflow(Bytes.fromHexString("0x010000000000"))).isTrue(); + } + + @Test + void isShiftOverflow_largeValue_returnsTrue() { + // Large shift amount should overflow + assertThat(isShiftOverflow(Bytes.fromHexString("0xff00"))).isTrue(); + assertThat(isShiftOverflow(Bytes.fromHexString("0x0200"))).isTrue(); // 512 + } + + // endregion +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java new file mode 100644 index 00000000000..851626ad105 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java @@ -0,0 +1,430 @@ +/* + * 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.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.worldstate.WorldUpdater; + +import java.io.PrintStream; +import java.util.Random; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Performance test comparing shift operations (SHL/SHR) with their optimized versions. + * + *

Measures throughput in Mgas/s (million gas per second). All shift operations cost 3 gas. + */ +public class ShiftOperationsPerformanceTest { + + private static final PrintStream OUT = System.out; + private static final long SHIFT_GAS_COST = 3L; + private static final int WARMUP_ITERATIONS = 10_000; + private static final int MEASURE_ITERATIONS = 100_000; + private static final int SAMPLE_SIZE = 10_000; + + private MessageFrame frame; + private Bytes[] valuePool; + private Bytes[] shiftPool; + private Random random; + + @BeforeEach + void setUp() { + frame = createMessageFrame(); + random = new Random(42); + valuePool = new Bytes[SAMPLE_SIZE]; + shiftPool = new Bytes[SAMPLE_SIZE]; + } + + // region SHL Performance Tests + + @Test + void measureShlPerformance_smallShifts() { + OUT.println("\n=== SHL Performance: Small Shifts (0-255) ==="); + fillPoolsSmallShifts(); + runShlComparison("SHL_SMALL_SHIFTS"); + } + + @Test + void measureShlPerformance_shift1() { + OUT.println("\n=== SHL Performance: Shift by 1 ==="); + fillPoolsShift1(); + runShlComparison("SHL_SHIFT_1"); + } + + @Test + void measureShlPerformance_overflowShifts() { + OUT.println("\n=== SHL Performance: Overflow Shifts (>= 256) ==="); + fillPoolsOverflowShifts(); + runShlComparison("SHL_OVERFLOW"); + } + + @Test + void measureShlPerformance_random() { + OUT.println("\n=== SHL Performance: Random Inputs ==="); + fillPoolsRandom(); + runShlComparison("SHL_RANDOM"); + } + + // endregion + + // region SHR Performance Tests + + @Test + void measureShrPerformance_smallShifts() { + OUT.println("\n=== SHR Performance: Small Shifts (0-255) ==="); + fillPoolsSmallShifts(); + runShrComparison("SHR_SMALL_SHIFTS"); + } + + @Test + void measureShrPerformance_shift1() { + OUT.println("\n=== SHR Performance: Shift by 1 ==="); + fillPoolsShift1(); + runShrComparison("SHR_SHIFT_1"); + } + + @Test + void measureShrPerformance_overflowShifts() { + OUT.println("\n=== SHR Performance: Overflow Shifts (>= 256) ==="); + fillPoolsOverflowShifts(); + runShrComparison("SHR_OVERFLOW"); + } + + @Test + void measureShrPerformance_random() { + OUT.println("\n=== SHR Performance: Random Inputs ==="); + fillPoolsRandom(); + runShrComparison("SHR_RANDOM"); + } + + // endregion + + // region Pool Initialization + + private void fillPoolsSmallShifts() { + for (int i = 0; i < SAMPLE_SIZE; i++) { + byte[] valueBytes = new byte[32]; + random.nextBytes(valueBytes); + valuePool[i] = Bytes.wrap(valueBytes); + shiftPool[i] = Bytes.of(random.nextInt(256)); + } + } + + private void fillPoolsShift1() { + for (int i = 0; i < SAMPLE_SIZE; i++) { + byte[] valueBytes = new byte[32]; + random.nextBytes(valueBytes); + valuePool[i] = Bytes.wrap(valueBytes); + shiftPool[i] = Bytes.of(1); + } + } + + private void fillPoolsOverflowShifts() { + for (int i = 0; i < SAMPLE_SIZE; i++) { + byte[] valueBytes = new byte[32]; + random.nextBytes(valueBytes); + valuePool[i] = Bytes.wrap(valueBytes); + int shift = 256 + random.nextInt(1000); + shiftPool[i] = intToMinimalBytes(shift); + } + } + + private void fillPoolsRandom() { + for (int i = 0; i < SAMPLE_SIZE; i++) { + int valueSize = 1 + random.nextInt(32); + byte[] valueBytes = new byte[valueSize]; + random.nextBytes(valueBytes); + valuePool[i] = Bytes.wrap(valueBytes); + + int shiftSize = random.nextInt(5); + if (shiftSize == 0) { + shiftPool[i] = Bytes.EMPTY; + } else { + byte[] shiftBytes = new byte[shiftSize]; + random.nextBytes(shiftBytes); + shiftPool[i] = Bytes.wrap(shiftBytes); + } + } + } + + // endregion + + // region Performance Measurement + + private void runShlComparison(final String testName) { + OUT.println("Warming up..."); + warmupShl(false); + warmupShl(true); + + OUT.println("Measuring original ShlOperation..."); + PerformanceResult original = measureShl(false); + + OUT.println("Measuring optimized ShlOperationOptimized..."); + PerformanceResult optimized = measureShl(true); + + printResults(testName, original, optimized); + verifyShlCorrectness(); + } + + private void runShrComparison(final String testName) { + OUT.println("Warming up..."); + warmupShr(false); + warmupShr(true); + + OUT.println("Measuring original ShrOperation..."); + PerformanceResult original = measureShr(false); + + OUT.println("Measuring optimized ShrOperationOptimized..."); + PerformanceResult optimized = measureShr(true); + + printResults(testName, original, optimized); + verifyShrCorrectness(); + } + + private void warmupShl(final boolean useOptimized) { + int index = 0; + for (int i = 0; i < WARMUP_ITERATIONS; i++) { + frame.pushStackItem(valuePool[index]); + frame.pushStackItem(shiftPool[index]); + if (useOptimized) { + ShlOperationOptimized.staticOperation(frame); + } else { + ShlOperation.staticOperation(frame); + } + frame.popStackItem(); + index = (index + 1) % SAMPLE_SIZE; + } + } + + private void warmupShr(final boolean useOptimized) { + int index = 0; + for (int i = 0; i < WARMUP_ITERATIONS; i++) { + frame.pushStackItem(valuePool[index]); + frame.pushStackItem(shiftPool[index]); + if (useOptimized) { + ShrOperationOptimized.staticOperation(frame); + } else { + ShrOperation.staticOperation(frame); + } + frame.popStackItem(); + index = (index + 1) % SAMPLE_SIZE; + } + } + + private PerformanceResult measureShl(final boolean useOptimized) { + int index = 0; + long startTime = System.nanoTime(); + for (int i = 0; i < MEASURE_ITERATIONS; i++) { + frame.pushStackItem(valuePool[index]); + frame.pushStackItem(shiftPool[index]); + if (useOptimized) { + ShlOperationOptimized.staticOperation(frame); + } else { + ShlOperation.staticOperation(frame); + } + frame.popStackItem(); + index = (index + 1) % SAMPLE_SIZE; + } + long endTime = System.nanoTime(); + return new PerformanceResult(MEASURE_ITERATIONS, endTime - startTime, SHIFT_GAS_COST); + } + + private PerformanceResult measureShr(final boolean useOptimized) { + int index = 0; + long startTime = System.nanoTime(); + for (int i = 0; i < MEASURE_ITERATIONS; i++) { + frame.pushStackItem(valuePool[index]); + frame.pushStackItem(shiftPool[index]); + if (useOptimized) { + ShrOperationOptimized.staticOperation(frame); + } else { + ShrOperation.staticOperation(frame); + } + frame.popStackItem(); + index = (index + 1) % SAMPLE_SIZE; + } + long endTime = System.nanoTime(); + return new PerformanceResult(MEASURE_ITERATIONS, endTime - startTime, SHIFT_GAS_COST); + } + + private void verifyShlCorrectness() { + OUT.println("Verifying SHL correctness..."); + int sampleCount = Math.min(1000, SAMPLE_SIZE); + for (int i = 0; i < sampleCount; i++) { + Bytes value = valuePool[i]; + Bytes shift = shiftPool[i]; + + frame.pushStackItem(value); + frame.pushStackItem(shift); + ShlOperation.staticOperation(frame); + Bytes originalResult = frame.popStackItem(); + + frame.pushStackItem(value); + frame.pushStackItem(shift); + ShlOperationOptimized.staticOperation(frame); + Bytes optimizedResult = frame.popStackItem(); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SHL mismatch at index %d", i) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + OUT.println("SHL correctness verified for " + sampleCount + " samples"); + } + + private void verifyShrCorrectness() { + OUT.println("Verifying SHR correctness..."); + int sampleCount = Math.min(1000, SAMPLE_SIZE); + for (int i = 0; i < sampleCount; i++) { + Bytes value = valuePool[i]; + Bytes shift = shiftPool[i]; + + frame.pushStackItem(value); + frame.pushStackItem(shift); + ShrOperation.staticOperation(frame); + Bytes originalResult = frame.popStackItem(); + + frame.pushStackItem(value); + frame.pushStackItem(shift); + ShrOperationOptimized.staticOperation(frame); + Bytes optimizedResult = frame.popStackItem(); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SHR mismatch at index %d", i) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + OUT.println("SHR correctness verified for " + sampleCount + " samples"); + } + + private void printResults( + final String testName, + final PerformanceResult original, + final PerformanceResult optimized) { + + double speedup = original.nsPerOp() / optimized.nsPerOp(); + double percentFaster = (1 - optimized.nsPerOp() / original.nsPerOp()) * 100; + + OUT.println(); + OUT.println("╔═══════════════════════════════════════════════════════════════════╗"); + OUT.println("║ Shift Operation Performance Results: " + testName); + OUT.println("╠═══════════════════════════════════════════════════════════════════╣"); + OUT.println("║ Original Operation:"); + OUT.printf("║ - Operations: %,d%n", original.operations); + OUT.printf("║ - Total time: %.2f ms%n", original.elapsedMs()); + OUT.printf("║ - Throughput: %,.0f ops/sec%n", original.opsPerSecond()); + OUT.printf("║ - Latency: %.2f ns/op%n", original.nsPerOp()); + OUT.printf("║ - Gas throughput: %.3f Mgas/s%n", original.mgasPerSecond()); + OUT.println("╠═══════════════════════════════════════════════════════════════════╣"); + OUT.println("║ Optimized Operation:"); + OUT.printf("║ - Operations: %,d%n", optimized.operations); + OUT.printf("║ - Total time: %.2f ms%n", optimized.elapsedMs()); + OUT.printf("║ - Throughput: %,.0f ops/sec%n", optimized.opsPerSecond()); + OUT.printf("║ - Latency: %.2f ns/op%n", optimized.nsPerOp()); + OUT.printf("║ - Gas throughput: %.3f Mgas/s%n", optimized.mgasPerSecond()); + OUT.println("╠═══════════════════════════════════════════════════════════════════╣"); + OUT.printf("║ Speedup: %.2fx (%.1f%% faster)%n", speedup, percentFaster); + OUT.println("╚═══════════════════════════════════════════════════════════════════╝"); + OUT.println(); + } + + // endregion + + // region Helper Classes + + private static class PerformanceResult { + final long operations; + final long elapsedNanos; + final long gasPerOp; + + PerformanceResult(final long operations, final long elapsedNanos, final long gasPerOp) { + this.operations = operations; + this.elapsedNanos = elapsedNanos; + this.gasPerOp = gasPerOp; + } + + double elapsedMs() { + return elapsedNanos / 1_000_000.0; + } + + double nsPerOp() { + return (double) elapsedNanos / operations; + } + + double opsPerSecond() { + return operations / (elapsedNanos / 1_000_000_000.0); + } + + double mgasPerSecond() { + long totalGas = operations * gasPerOp; + double seconds = elapsedNanos / 1_000_000_000.0; + return (totalGas / 1_000_000.0) / seconds; + } + } + + // endregion + + // region Helper Methods + + private MessageFrame createMessageFrame() { + return MessageFrame.builder() + .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(Bytes32.ZERO) + .sender(Address.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(Code.EMPTY_CODE) + .completer(__ -> {}) + .build(); + } + + private Bytes intToMinimalBytes(final int value) { + if (value == 0) { + return Bytes.EMPTY; + } + if (value <= 0xFF) { + return Bytes.of(value); + } + if (value <= 0xFFFF) { + return Bytes.of(value >> 8, value & 0xFF); + } + if (value <= 0xFFFFFF) { + return Bytes.of(value >> 16, (value >> 8) & 0xFF, value & 0xFF); + } + return Bytes.of(value >> 24, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF); + } + + // endregion +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPropertyBasedTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPropertyBasedTest.java new file mode 100644 index 00000000000..004acfd7ba6 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPropertyBasedTest.java @@ -0,0 +1,292 @@ +/* + * 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 shift operations with their optimized versions. + * + *

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 values1to32() { + return Arbitraries.bytes().array(byte[].class).ofMinSize(1).ofMaxSize(32); + } + + @Provide + Arbitrary shiftAmounts() { + return Arbitraries.bytes().array(byte[].class).ofMinSize(0).ofMaxSize(32); + } + + @Provide + Arbitrary smallShifts() { + return Arbitraries.integers().between(0, 255); + } + + @Provide + Arbitrary overflowShifts() { + return Arbitraries.integers().between(256, 1024); + } + + // endregion + + // region SHL Property Tests + + @Property(tries = 10000) + void property_shlOptimized_matchesOriginal_randomInputs( + @ForAll("values1to32") final byte[] valueBytes, + @ForAll("shiftAmounts") final byte[] shiftBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.wrap(shiftBytes); + + final Bytes originalResult = runShlOperation(shift, value); + final Bytes optimizedResult = runShlOperationOptimized(shift, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as( + "SHL mismatch for shift=%s, value=%s", + shift.toHexString(), Bytes32.leftPad(value).toHexString()) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + @Property(tries = 5000) + void property_shlOptimized_matchesOriginal_smallShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes originalResult = runShlOperation(shiftBytes, value); + final Bytes optimizedResult = runShlOperationOptimized(shiftBytes, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SHL mismatch for shift=%d", shift) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + @Property(tries = 1000) + void property_shlOptimized_matchesOriginal_overflowShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes originalResult = runShlOperation(shiftBytes, value); + final Bytes optimizedResult = runShlOperationOptimized(shiftBytes, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SHL overflow mismatch for shift=%d", shift) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + // endregion + + // region SHR Property Tests + + @Property(tries = 10000) + void property_shrOptimized_matchesOriginal_randomInputs( + @ForAll("values1to32") final byte[] valueBytes, + @ForAll("shiftAmounts") final byte[] shiftBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.wrap(shiftBytes); + + final Bytes originalResult = runShrOperation(shift, value); + final Bytes optimizedResult = runShrOperationOptimized(shift, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as( + "SHR mismatch for shift=%s, value=%s", + shift.toHexString(), Bytes32.leftPad(value).toHexString()) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + @Property(tries = 5000) + void property_shrOptimized_matchesOriginal_smallShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes originalResult = runShrOperation(shiftBytes, value); + final Bytes optimizedResult = runShrOperationOptimized(shiftBytes, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SHR mismatch for shift=%d", shift) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + @Property(tries = 1000) + void property_shrOptimized_matchesOriginal_overflowShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes originalResult = runShrOperation(shiftBytes, value); + final Bytes optimizedResult = runShrOperationOptimized(shiftBytes, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SHR overflow mismatch for shift=%d", shift) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + // endregion + + // region Edge Case Tests + + @Property(tries = 1000) + void property_shl_shiftByZero_returnsOriginalValue( + @ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(0); + + final Bytes originalResult = runShlOperation(shift, value); + final Bytes optimizedResult = runShlOperationOptimized(shift, value); + + assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.leftPad(value)); + assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 1000) + void property_shr_shiftByZero_returnsOriginalValue( + @ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(0); + + final Bytes originalResult = runShrOperation(shift, value); + final Bytes optimizedResult = runShrOperationOptimized(shift, value); + + assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.leftPad(value)); + assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 500) + void property_shl_largeShift_returnsZero(@ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes originalResult = runShlOperation(largeShift, value); + final Bytes optimizedResult = runShlOperationOptimized(largeShift, value); + + assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.ZERO); + assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.ZERO); + } + + @Property(tries = 500) + void property_shr_largeShift_returnsZero(@ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes originalResult = runShrOperation(largeShift, value); + final Bytes optimizedResult = runShrOperationOptimized(largeShift, value); + + assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.ZERO); + assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.ZERO); + } + + // endregion + + // region Helper Methods + + private Bytes runShlOperation(final Bytes shift, final Bytes value) { + return runOperation(shift, value, ShlOperation::staticOperation); + } + + private Bytes runShlOperationOptimized(final Bytes shift, final Bytes value) { + return runOperation(shift, value, ShlOperationOptimized::staticOperation); + } + + private Bytes runShrOperation(final Bytes shift, final Bytes value) { + return runOperation(shift, value, ShrOperation::staticOperation); + } + + private Bytes runShrOperationOptimized(final Bytes shift, final Bytes value) { + return runOperation(shift, value, ShrOperationOptimized::staticOperation); + } + + @FunctionalInterface + interface OperationExecutor { + Operation.OperationResult execute(MessageFrame frame); + } + + private Bytes runOperation( + final Bytes shift, final Bytes value, final OperationExecutor executor) { + final MessageFrame frame = mock(MessageFrame.class); + final Deque stack = new ArrayDeque<>(); + stack.push(value); + stack.push(shift); + + when(frame.popStackItem()).thenAnswer(invocation -> stack.pop()); + + final Bytes[] result = new Bytes[1]; + doAnswer( + invocation -> { + result[0] = invocation.getArgument(0); + return null; + }) + .when(frame) + .pushStackItem(any(Bytes.class)); + + executor.execute(frame); + return result[0]; + } + + private Bytes intToMinimalBytes(final int value) { + if (value == 0) { + return Bytes.EMPTY; + } + if (value <= 0xFF) { + return Bytes.of(value); + } + if (value <= 0xFFFF) { + return Bytes.of(value >> 8, value & 0xFF); + } + if (value <= 0xFFFFFF) { + return Bytes.of(value >> 16, (value >> 8) & 0xFF, value & 0xFF); + } + return Bytes.of(value >> 24, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF); + } + + // endregion +} From c721e1553c9da7975186d2c103b3e0a837005e47 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Fri, 6 Feb 2026 15:46:15 +0100 Subject: [PATCH 07/27] Add the configuration for block processing Signed-off-by: Ameziane H. --- evm/src/main/java/org/hyperledger/besu/evm/EVM.java | 12 ++++++++++++ .../java/org/hyperledger/besu/evm/MainnetEVMs.java | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) 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 6c46e832866..95d3dcc7285 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -70,6 +70,10 @@ 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; @@ -282,6 +286,14 @@ 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) 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 f99481d5e14..120893515f7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -120,7 +120,9 @@ 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; @@ -495,12 +497,14 @@ private static void registerConstantinopleOperations( registerByzantiumOperations(registry, gasCalculator, evmConfiguration); registry.put(new Create2Operation(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 ShlOperation(gasCalculator)); - registry.put(new ShrOperation(gasCalculator)); registry.put(new ExtCodeHashOperation(gasCalculator)); } From b3cd5331e3cead9eb9f8e6c0da5e87082dbf2197 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Fri, 6 Feb 2026 17:36:24 +0100 Subject: [PATCH 08/27] Optimize isShiftOverflow method Signed-off-by: Ameziane H. --- .../besu/evm/operation/Shift256Operations.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) 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 index d9b06ded122..5a50360199b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java @@ -42,14 +42,10 @@ private Shift256Operations() { * @return {@code true} if the shift amount is >= 256, {@code false} otherwise */ public static boolean isShiftOverflow(final Bytes shiftAmount) { - final int n = shiftAmount.size(); - if (n == 0) { - return false; - } + final byte[] a = shiftAmount.toArrayUnsafe(); + final int n = a.length; for (int i = 0; i < n - 1; i++) { - if (shiftAmount.get(i) != 0) { - return true; - } + if (a[i] != 0) return true; } return false; } From aee6907057cb40ddb29fe12227e3072dd881dc92 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Fri, 6 Feb 2026 18:37:53 +0100 Subject: [PATCH 09/27] Fix "Archive contains more than 65535 entries" error with jmh Signed-off-by: Ameziane H. --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 558e55555f5..51bb1329c37 100644 --- a/build.gradle +++ b/build.gradle @@ -650,6 +650,7 @@ subprojects { excludes = _strListCmdArg('excludes', []) var asyncProfiler = _strCmdArg('asyncProfiler') var asyncProfilerOptions = _strCmdArg('asyncProfilerOptions', 'output=flamegraph') + zip64.set(true) jvmArgs = [ '-XX:+EnableDynamicAgentLoading' ] From f030d939bd9197ed9f414250ea451a7350d80f35 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 9 Feb 2026 17:18:07 +0100 Subject: [PATCH 10/27] Add SAR property based testing Signed-off-by: Ameziane H. --- .../SarOperationPropertyBasedTest.java | 385 ++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java 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..256818524c8 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java @@ -0,0 +1,385 @@ +/* + * 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 values1to32() { + return Arbitraries.bytes().array(byte[].class).ofMinSize(1).ofMaxSize(32); + } + + @Provide + Arbitrary shiftAmounts() { + return Arbitraries.bytes().array(byte[].class).ofMinSize(0).ofMaxSize(32); + } + + @Provide + Arbitrary smallShifts() { + return Arbitraries.integers().between(0, 255); + } + + @Provide + Arbitrary overflowShifts() { + return Arbitraries.integers().between(256, 1024); + } + + @Provide + Arbitrary negativeValues() { + // Generate values with sign bit set (first byte >= 0x80) + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(1) + .ofMaxSize(32) + .map( + bytes -> { + if (bytes.length > 0) { + // Ensure sign bit is set by ORing with 0x80 + bytes[0] = (byte) (bytes[0] | 0x80); + } + return bytes; + }); + } + + @Provide + Arbitrary positiveValues() { + // Generate values with sign bit clear (first byte < 0x80) + return Arbitraries.bytes() + .array(byte[].class) + .ofMinSize(1) + .ofMaxSize(32) + .map( + bytes -> { + if (bytes.length > 0) { + // Ensure sign bit is clear by ANDing with 0x7F + bytes[0] = (byte) (bytes[0] & 0x7F); + } + return bytes; + }); + } + + // endregion + + // region SAR Property Tests - Random Inputs + + @Property(tries = 10000) + void property_sarOptimized_matchesOriginal_randomInputs( + @ForAll("values1to32") final byte[] valueBytes, + @ForAll("shiftAmounts") final byte[] shiftBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.wrap(shiftBytes); + + final Bytes originalResult = runSarOperation(shift, value); + final Bytes optimizedResult = runSarOperationOptimized(shift, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as( + "SAR mismatch for shift=%s, value=%s", + shift.toHexString(), Bytes32.leftPad(value).toHexString()) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + @Property(tries = 5000) + void property_sarOptimized_matchesOriginal_smallShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes originalResult = runSarOperation(shiftBytes, value); + final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SAR mismatch for shift=%d", shift) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + @Property(tries = 1000) + void property_sarOptimized_matchesOriginal_overflowShifts( + @ForAll("values1to32") final byte[] valueBytes, @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes originalResult = runSarOperation(shiftBytes, value); + final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SAR overflow mismatch for shift=%d", shift) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + // endregion + + // region SAR Property Tests - Negative Values (Sign Extension) + + @Property(tries = 5000) + void property_sarOptimized_matchesOriginal_negativeValues_smallShifts( + @ForAll("negativeValues") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes originalResult = runSarOperation(shiftBytes, value); + final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SAR negative mismatch for shift=%d, value=%s", shift, value.toHexString()) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + @Property(tries = 1000) + void property_sarOptimized_matchesOriginal_negativeValues_overflowShifts( + @ForAll("negativeValues") final byte[] valueBytes, + @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes originalResult = runSarOperation(shiftBytes, value); + final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); + + // For negative values with overflow shift, result should be all ones + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SAR negative overflow mismatch for shift=%d", shift) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + // endregion + + // region SAR Property Tests - Positive Values + + @Property(tries = 5000) + void property_sarOptimized_matchesOriginal_positiveValues_smallShifts( + @ForAll("positiveValues") final byte[] valueBytes, @ForAll("smallShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes originalResult = runSarOperation(shiftBytes, value); + final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SAR positive mismatch for shift=%d, value=%s", shift, value.toHexString()) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + @Property(tries = 1000) + void property_sarOptimized_matchesOriginal_positiveValues_overflowShifts( + @ForAll("positiveValues") final byte[] valueBytes, + @ForAll("overflowShifts") final int shift) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shiftBytes = intToMinimalBytes(shift); + + final Bytes originalResult = runSarOperation(shiftBytes, value); + final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); + + // For positive values with overflow shift, result should be all zeros + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SAR positive overflow mismatch for shift=%d", shift) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + // endregion + + // region Edge Case Tests + + @Property(tries = 1000) + void property_sar_shiftByZero_returnsOriginalValue( + @ForAll("values1to32") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(0); + + final Bytes originalResult = runSarOperation(shift, value); + final Bytes optimizedResult = runSarOperationOptimized(shift, value); + + assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.leftPad(value)); + assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 500) + void property_sar_negativeValue_largeShift_returnsAllOnes( + @ForAll("negativeValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes originalResult = runSarOperation(largeShift, value); + final Bytes optimizedResult = runSarOperationOptimized(largeShift, value); + + // Both should return all ones for negative value with large shift + final Bytes32 allOnes = + Bytes32.fromHexString( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(allOnes); + assertThat(Bytes32.leftPad(originalResult)).isEqualTo(allOnes); + } + + @Property(tries = 500) + void property_sar_positiveValue_largeShift_returnsZero( + @ForAll("positiveValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes largeShift = Bytes.fromHexString("0x010000000000"); + + final Bytes originalResult = runSarOperation(largeShift, value); + final Bytes optimizedResult = runSarOperationOptimized(largeShift, value); + + // Both should return zero for positive value with large shift + assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.ZERO); + assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.ZERO); + } + + @Property(tries = 500) + void property_sar_allOnes_anyShift_returnsAllOnes(@ForAll("smallShifts") final int shift) { + + // -1 in two's complement (all bits set) + final Bytes value = + Bytes.fromHexString( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + final Bytes shiftBytes = Bytes.of(shift); + + final Bytes originalResult = runSarOperation(shiftBytes, value); + final Bytes optimizedResult = runSarOperationOptimized(shiftBytes, value); + + // SAR of -1 by any amount should still be -1 (all ones) + assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.leftPad(value)); + assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.leftPad(value)); + } + + @Property(tries = 500) + void property_sar_minValue_shift255_returnsAllOnes() { + + // MIN_VALUE: 0x8000...0000 (only sign bit set) + final Bytes value = + Bytes.fromHexString( + "0x8000000000000000000000000000000000000000000000000000000000000000"); + final Bytes shift = Bytes.of(255); + + final Bytes originalResult = runSarOperation(shift, value); + final Bytes optimizedResult = runSarOperationOptimized(shift, value); + + // SAR of MIN_VALUE by 255 should be all ones (-1) + final Bytes32 allOnes = + Bytes32.fromHexString( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(allOnes); + assertThat(Bytes32.leftPad(originalResult)).isEqualTo(allOnes); + } + + @Property(tries = 500) + void property_sar_maxPositive_shift255_returnsZero() { + + // MAX_VALUE: 0x7fff...ffff (all bits except sign bit set) + final Bytes value = + Bytes.fromHexString( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + final Bytes shift = Bytes.of(255); + + final Bytes originalResult = runSarOperation(shift, value); + final Bytes optimizedResult = runSarOperationOptimized(shift, value); + + // SAR of MAX_VALUE by 255 should be 0 + assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(Bytes32.ZERO); + assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.ZERO); + } + + // endregion + + // region Helper Methods + + private Bytes runSarOperation(final Bytes shift, final Bytes value) { + return runOperation(shift, value, SarOperation::staticOperation); + } + + private Bytes runSarOperationOptimized(final Bytes shift, final Bytes value) { + return runOperation(shift, value, SarOperationOptimized::staticOperation); + } + + @FunctionalInterface + interface OperationExecutor { + Operation.OperationResult execute(MessageFrame frame); + } + + private Bytes runOperation( + final Bytes shift, final Bytes value, final OperationExecutor executor) { + final MessageFrame frame = mock(MessageFrame.class); + final Deque stack = new ArrayDeque<>(); + stack.push(value); + stack.push(shift); + + when(frame.popStackItem()).thenAnswer(invocation -> stack.pop()); + + final Bytes[] result = new Bytes[1]; + doAnswer( + invocation -> { + result[0] = invocation.getArgument(0); + return null; + }) + .when(frame) + .pushStackItem(any(Bytes.class)); + + executor.execute(frame); + return result[0]; + } + + private Bytes intToMinimalBytes(final int value) { + if (value == 0) { + return Bytes.EMPTY; + } + if (value <= 0xFF) { + return Bytes.of(value); + } + if (value <= 0xFFFF) { + return Bytes.of(value >> 8, value & 0xFF); + } + if (value <= 0xFFFFFF) { + return Bytes.of(value >> 16, (value >> 8) & 0xFF, value & 0xFF); + } + return Bytes.of(value >> 24, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF); + } + + // endregion +} From cf20a2c9139e26c36f8d08fda5b37a70ca6353dc Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 12 Feb 2026 11:40:00 +0100 Subject: [PATCH 11/27] Handle some specific cases Signed-off-by: Ameziane H. --- .../besu/evm/operation/SarOperationOptimized.java | 9 +++++++-- .../besu/evm/operation/ShlOperationOptimized.java | 9 +++++++-- .../besu/evm/operation/ShrOperationOptimized.java | 9 +++++++-- .../operation/SarOperationPropertyBasedTest.java | 15 +++++---------- 4 files changed, 26 insertions(+), 16 deletions(-) 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 index 8dfe3feda0c..f81dfe847fa 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -54,10 +54,15 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); - final Bytes value = Bytes32.leftPad(frame.popStackItem()); + Bytes value = frame.popStackItem(); + if (value.equals(ALL_ONES)) { + frame.pushStackItem(ALL_ONES); + return sarSuccess; + } + value = Bytes32.leftPad(value); final boolean negative = (value.get(0) & 0x80) != 0; - // detect shift >= 256 cheaply (check high bytes) + // shift >= 256, push All 1s if negative, All 0s otherwise if (isShiftOverflow(shiftAmount)) { frame.pushStackItem(negative ? ALL_ONES : ZERO_32); return sarSuccess; 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 index 019e16bf8e2..9bcfadc543f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java @@ -58,9 +58,14 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); - final Bytes value = Bytes32.leftPad(frame.popStackItem()); + Bytes value = frame.popStackItem(); + if (value.isZero()) { + frame.pushStackItem(ZERO_32); + return shlSuccess; + } + value = Bytes32.leftPad(value); - // Detect shift >= 256 cheaply (check high bytes) + // shift >= 256, push All 0s if (isShiftOverflow(shiftAmount)) { frame.pushStackItem(ZERO_32); return shlSuccess; 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 index 53e46db7275..dbb3ec27ac5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java @@ -58,9 +58,14 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); - final Bytes value = Bytes32.leftPad(frame.popStackItem()); + Bytes value = frame.popStackItem(); + if (value.isZero()) { + frame.pushStackItem(ZERO_32); + return shrSuccess; + } + value = Bytes32.leftPad(value); - // Detect shift >= 256 cheaply (check high bytes) + // shift >= 256, push All 0s if (isShiftOverflow(shiftAmount)) { frame.pushStackItem(ZERO_32); return shrSuccess; 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 index 256818524c8..092f53eb7f6 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java @@ -65,21 +65,16 @@ Arbitrary overflowShifts() { @Provide Arbitrary negativeValues() { - // Generate values with sign bit set (first byte >= 0x80) return Arbitraries.bytes() - .array(byte[].class) - .ofMinSize(1) - .ofMaxSize(32) - .map( - bytes -> { - if (bytes.length > 0) { - // Ensure sign bit is set by ORing with 0x80 - bytes[0] = (byte) (bytes[0] | 0x80); - } + .array(byte[].class) + .ofSize(32) + .map(bytes -> { + bytes[0] = (byte) (bytes[0] | 0x80); // set sign bit of 256-bit word return bytes; }); } + @Provide Arbitrary positiveValues() { // Generate values with sign bit clear (first byte < 0x80) From 705b033d83c86f29d4976434d881a4f63b6daadc Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 12 Feb 2026 12:57:08 +0100 Subject: [PATCH 12/27] Use Arrays.equals Signed-off-by: Ameziane H. --- .../evm/operation/SarOperationOptimized.java | 27 +++++++++++-------- .../evm/operation/Shift256Operations.java | 13 ++++----- .../evm/operation/ShlOperationOptimized.java | 19 ++++++------- .../evm/operation/ShrOperationOptimized.java | 19 ++++++------- .../evm/operation/Shift256OperationsTest.java | 26 +++++++++--------- 5 files changed, 56 insertions(+), 48 deletions(-) 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 index f81dfe847fa..d8f5802a33c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -15,6 +15,7 @@ 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.ZERO_32; import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; @@ -22,6 +23,8 @@ 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; @@ -54,22 +57,25 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); - Bytes value = frame.popStackItem(); - if (value.equals(ALL_ONES)) { + final Bytes value = Bytes32.leftPad(frame.popStackItem()); + final byte[] valueBytes = value.toArrayUnsafe(); + if (Arrays.equals(valueBytes, ALL_ONES_BYTES)) { frame.pushStackItem(ALL_ONES); return sarSuccess; } - value = Bytes32.leftPad(value); - final boolean negative = (value.get(0) & 0x80) != 0; + + final byte[] shiftBytes = shiftAmount.toArrayUnsafe(); + final boolean negative = (valueBytes[0] & 0x80) != 0; // shift >= 256, push All 1s if negative, All 0s otherwise - if (isShiftOverflow(shiftAmount)) { + if (isShiftOverflow(shiftBytes)) { frame.pushStackItem(negative ? ALL_ONES : ZERO_32); return sarSuccess; } - final int shift = shiftAmount.isEmpty() ? 0 : (shiftAmount.get(shiftAmount.size() - 1) & 0xFF); + final int shift = + shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); - frame.pushStackItem(sar256(value, shift, negative)); + frame.pushStackItem(sar256(valueBytes, shift, negative)); return sarSuccess; } @@ -83,20 +89,19 @@ public static OperationResult staticOperation(final MessageFrame frame) { *

For shift values greater than or equal to 256, the result is fully sign-extended and handled * by the caller. * - * @param value32 a 32-byte value representing a signed 256-bit integer + * @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 Bytes value32, final int shift, final boolean negative) { - if (shift == 0) return value32; + 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]; - final byte[] in = value32.toArrayUnsafe(); for (int i = 31; i >= 0; i--) { final int src = i - shiftBytes; 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 index 5a50360199b..8d0e825d923 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java @@ -29,6 +29,9 @@ public final class Shift256Operations { /** 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 } @@ -38,14 +41,12 @@ private Shift256Operations() { * *

If any byte except the last byte is non-zero, the value is >= 256. * - * @param shiftAmount the shift amount as a stack value + * @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 Bytes shiftAmount) { - final byte[] a = shiftAmount.toArrayUnsafe(); - final int n = a.length; - for (int i = 0; i < n - 1; i++) { - if (a[i] != 0) return true; + public static boolean isShiftOverflow(final byte[] shiftBytes) { + for (int i = 0; i < shiftBytes.length - 1; i++) { + if (shiftBytes[i] != 0) return true; } return false; } 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 index 9bcfadc543f..c32d0ebcc47 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java @@ -58,22 +58,24 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); - Bytes value = frame.popStackItem(); + final Bytes value = frame.popStackItem(); if (value.isZero()) { frame.pushStackItem(ZERO_32); return shlSuccess; } - value = Bytes32.leftPad(value); + + final byte[] valueBytes = Bytes32.leftPad(value).toArrayUnsafe(); + final byte[] shiftBytes = shiftAmount.toArrayUnsafe(); // shift >= 256, push All 0s - if (isShiftOverflow(shiftAmount)) { + if (isShiftOverflow(shiftBytes)) { frame.pushStackItem(ZERO_32); return shlSuccess; } - final int shift = shiftAmount.isEmpty() ? 0 : (shiftAmount.get(shiftAmount.size() - 1) & 0xFF); + final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); - frame.pushStackItem(shl256(value, shift)); + frame.pushStackItem(shl256(valueBytes, shift)); return shlSuccess; } @@ -83,20 +85,19 @@ public static OperationResult staticOperation(final MessageFrame frame) { *

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 value32 a 32-byte value + * @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 Bytes value32, final int shift) { + private static Bytes shl256(final byte[] in, final int shift) { if (shift == 0) { - return value32; + return Bytes.wrap(in); } final int shiftBytes = shift >>> 3; // /8 final int shiftBits = shift & 7; // %8 final byte[] out = new byte[32]; - final byte[] in = value32.toArrayUnsafe(); // Shift left: bytes move to lower indices (towards index 0) for (int i = 0; i < 32; i++) { 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 index dbb3ec27ac5..f0e98ae9345 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java @@ -58,22 +58,24 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); - Bytes value = frame.popStackItem(); + final Bytes value = frame.popStackItem(); if (value.isZero()) { frame.pushStackItem(ZERO_32); return shrSuccess; } - value = Bytes32.leftPad(value); + + final byte[] valueBytes = Bytes32.leftPad(value).toArrayUnsafe(); + final byte[] shiftBytes = shiftAmount.toArrayUnsafe(); // shift >= 256, push All 0s - if (isShiftOverflow(shiftAmount)) { + if (isShiftOverflow(shiftBytes)) { frame.pushStackItem(ZERO_32); return shrSuccess; } - final int shift = shiftAmount.isEmpty() ? 0 : (shiftAmount.get(shiftAmount.size() - 1) & 0xFF); + final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); - frame.pushStackItem(shr256(value, shift)); + frame.pushStackItem(shr256(valueBytes, shift)); return shrSuccess; } @@ -83,20 +85,19 @@ public static OperationResult staticOperation(final MessageFrame frame) { *

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 value32 a 32-byte value + * @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 Bytes value32, final int shift) { + private static Bytes shr256(final byte[] in, final int shift) { if (shift == 0) { - return value32; + return Bytes.wrap(in); } final int shiftBytes = shift >>> 3; // /8 final int shiftBits = shift & 7; // %8 final byte[] out = new byte[32]; - final byte[] in = value32.toArrayUnsafe(); // Shift right: bytes move to higher indices (towards index 31) for (int i = 31; i >= 0; i--) { diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java index bdc4d7d0e37..9596ad75c0a 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java @@ -42,41 +42,41 @@ void constants_areCorrect() { @Test void isShiftOverflow_empty_returnsFalse() { - assertThat(isShiftOverflow(Bytes.EMPTY)).isFalse(); + assertThat(isShiftOverflow(Bytes.EMPTY.toArrayUnsafe())).isFalse(); } @Test void isShiftOverflow_singleByte_returnsFalse() { - assertThat(isShiftOverflow(Bytes.of(0))).isFalse(); - assertThat(isShiftOverflow(Bytes.of(1))).isFalse(); - assertThat(isShiftOverflow(Bytes.of(128))).isFalse(); - assertThat(isShiftOverflow(Bytes.of(255))).isFalse(); + assertThat(isShiftOverflow(Bytes.of(0).toArrayUnsafe())).isFalse(); + assertThat(isShiftOverflow(Bytes.of(1).toArrayUnsafe())).isFalse(); + assertThat(isShiftOverflow(Bytes.of(128).toArrayUnsafe())).isFalse(); + assertThat(isShiftOverflow(Bytes.of(255).toArrayUnsafe())).isFalse(); } @Test void isShiftOverflow_multiByteWithZeroPrefix_returnsFalse() { - assertThat(isShiftOverflow(Bytes.of(0, 0, 42))).isFalse(); - assertThat(isShiftOverflow(Bytes.of(0, 0, 0, 0, 200))).isFalse(); + assertThat(isShiftOverflow(Bytes.of(0, 0, 42).toArrayUnsafe())).isFalse(); + assertThat(isShiftOverflow(Bytes.of(0, 0, 0, 0, 200).toArrayUnsafe())).isFalse(); } @Test void isShiftOverflow_256_returnsTrue() { // 0x0100 = 256 - assertThat(isShiftOverflow(Bytes.fromHexString("0x0100"))).isTrue(); + assertThat(isShiftOverflow(Bytes.fromHexString("0x0100").toArrayUnsafe())).isTrue(); } @Test void isShiftOverflow_nonZeroHighByte_returnsTrue() { - assertThat(isShiftOverflow(Bytes.of(1, 0))).isTrue(); - assertThat(isShiftOverflow(Bytes.of(0, 1, 0))).isTrue(); - assertThat(isShiftOverflow(Bytes.fromHexString("0x010000000000"))).isTrue(); + assertThat(isShiftOverflow(Bytes.of(1, 0).toArrayUnsafe())).isTrue(); + assertThat(isShiftOverflow(Bytes.of(0, 1, 0).toArrayUnsafe())).isTrue(); + assertThat(isShiftOverflow(Bytes.fromHexString("0x010000000000").toArrayUnsafe())).isTrue(); } @Test void isShiftOverflow_largeValue_returnsTrue() { // Large shift amount should overflow - assertThat(isShiftOverflow(Bytes.fromHexString("0xff00"))).isTrue(); - assertThat(isShiftOverflow(Bytes.fromHexString("0x0200"))).isTrue(); // 512 + assertThat(isShiftOverflow(Bytes.fromHexString("0xff00").toArrayUnsafe())).isTrue(); + assertThat(isShiftOverflow(Bytes.fromHexString("0x0200").toArrayUnsafe())).isTrue(); // 512 } // endregion From bf918a88593e6dab16aae5d97368d229854cef6b Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 12 Feb 2026 16:57:31 +0100 Subject: [PATCH 13/27] Add GC/memory allocations profiling Signed-off-by: Ameziane H. --- build.gradle | 4 ++++ .../besu/evm/operation/SarOperationOptimized.java | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 51bb1329c37..ff9a9766699 100644 --- a/build.gradle +++ b/build.gradle @@ -661,6 +661,10 @@ subprojects { // Force debug symbols of stack traces to the profiler jvmArgs.addAll("-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints") } + var gcProfiler = _strCmdArg('gcProfiler') + if (gcProfiler != null) { + profilers.add('gc') + } duplicateClassesStrategy = DuplicatesStrategy.INCLUDE } 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 index d8f5802a33c..e57a3c1d9bb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -57,13 +57,13 @@ public Operation.OperationResult executeFixedCostOperation( */ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); - final Bytes value = Bytes32.leftPad(frame.popStackItem()); - final byte[] valueBytes = value.toArrayUnsafe(); + 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; From 0ca7aa0db19bc13c5eccda86fbb90f91ec7dd9fc Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 12 Feb 2026 20:27:52 +0100 Subject: [PATCH 14/27] Improve SHIFT_255 use case Signed-off-by: Ameziane H. --- .../besu/evm/operation/SarOperationOptimized.java | 10 ++++++++-- .../besu/evm/operation/ShlOperationOptimized.java | 6 ++++-- .../besu/evm/operation/ShrOperationOptimized.java | 5 +++-- 3 files changed, 15 insertions(+), 6 deletions(-) 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 index e57a3c1d9bb..f79d6324ba7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -103,9 +103,15 @@ private static Bytes sar256(final byte[] in, final int shift, final boolean nega final byte[] out = new byte[32]; - for (int i = 31; i >= 0; i--) { + // 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 src = i - shiftBytes; - final int hi = (src >= 0) ? (in[src] & 0xFF) : fill; + final int hi = in[src] & 0xFF; if (shiftBits == 0) { out[i] = (byte) hi; } else { 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 index c32d0ebcc47..d315e6630ad 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java @@ -100,9 +100,11 @@ private static Bytes shl256(final byte[] in, final int shift) { final byte[] out = new byte[32]; // Shift left: bytes move to lower indices (towards index 0) - for (int i = 0; i < 32; i++) { + // 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 src = i + shiftBytes; - final int lo = (src < 32) ? (in[src] & 0xFF) : 0; + final int lo = in[src] & 0xFF; if (shiftBits == 0) { out[i] = (byte) lo; } else { 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 index f0e98ae9345..f7deec6207a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java @@ -100,9 +100,10 @@ private static Bytes shr256(final byte[] in, final int shift) { final byte[] out = new byte[32]; // Shift right: bytes move to higher indices (towards index 31) - for (int i = 31; i >= 0; i--) { + // Bytes below shiftBytes are guaranteed zero (already from new byte[32]) + for (int i = 31; i >= shiftBytes; i--) { final int src = i - shiftBytes; - final int hi = (src >= 0) ? (in[src] & 0xFF) : 0; + final int hi = in[src] & 0xFF; if (shiftBits == 0) { out[i] = (byte) hi; } else { From 06587d20b82148618ebab4e3c43d95e8af44e66c Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 12 Feb 2026 21:22:49 +0100 Subject: [PATCH 15/27] spotless + fix gcProfiler flag issue Signed-off-by: Ameziane H. --- CHANGELOG.md | 1 + build.gradle | 2 +- .../AbstractSarOperationBenchmark.java | 6 ++--- .../AbstractShiftOperationBenchmark.java | 6 ++--- .../evm/operation/SarOperationOptimized.java | 3 +-- .../SarOperationPropertyBasedTest.java | 23 ++++++++----------- .../ShiftOperationsPerformanceTest.java | 4 +--- 7 files changed, 17 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f79623a9a2f..eaffa2b026a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ - Add ability to pass a custom tracer to block simulation [#9708](https://github.com/hyperledger/besu/pull/9708) - Add support for `4byteTracer` in `debug_trace*` methods to collect function selectors from internal calls via PR [#9642](https://github.com/hyperledger/besu/pull/9642). Thanks to [@JukLee0ira](https://github.com/JukLee0ira). - Update assertj to v3.27.7 [#9710](https://github.com/hyperledger/besu/pull/9710) +- Improve SAR, SHR and SHL opcodes performance [#9796](https://github.com/hyperledger/besu/pull/9796) ### Bug fixes diff --git a/build.gradle b/build.gradle index ff9a9766699..a7340705f2a 100644 --- a/build.gradle +++ b/build.gradle @@ -662,7 +662,7 @@ subprojects { jvmArgs.addAll("-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints") } var gcProfiler = _strCmdArg('gcProfiler') - if (gcProfiler != null) { + 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 index 48bd384ecb8..83628e57209 100644 --- 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 @@ -50,13 +50,11 @@ public enum Case { /** All bits set (32 bytes of 0xFF) - represents -1 in two's complement. */ protected static final Bytes ALL_BITS = - Bytes.fromHexString( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); /** Max positive value (sign bit not set). */ protected static final Bytes POSITIVE_VALUE = - Bytes.fromHexString( - "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @Param({ "SHIFT_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 index 9ef14d6079a..ce50d7a0ed7 100644 --- 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 @@ -48,13 +48,11 @@ public enum Case { /** All bits set (32 bytes of 0xFF). */ protected static final Bytes ALL_BITS = - Bytes.fromHexString( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); /** Max positive value (sign bit not set). */ protected static final Bytes POSITIVE_VALUE = - Bytes.fromHexString( - "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @Param({ "SHIFT_0", 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 index f79d6324ba7..bc563632d69 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -72,8 +72,7 @@ public static OperationResult staticOperation(final MessageFrame frame) { frame.pushStackItem(negative ? ALL_ONES : ZERO_32); return sarSuccess; } - final int shift = - shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); + final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); frame.pushStackItem(sar256(valueBytes, shift, negative)); return sarSuccess; 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 index 092f53eb7f6..cd1fae3e751 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java @@ -66,15 +66,15 @@ Arbitrary overflowShifts() { @Provide Arbitrary negativeValues() { return Arbitraries.bytes() - .array(byte[].class) - .ofSize(32) - .map(bytes -> { + .array(byte[].class) + .ofSize(32) + .map( + bytes -> { bytes[0] = (byte) (bytes[0] | 0x80); // set sign bit of 256-bit word return bytes; }); } - @Provide Arbitrary positiveValues() { // Generate values with sign bit clear (first byte < 0x80) @@ -246,8 +246,7 @@ void property_sar_negativeValue_largeShift_returnsAllOnes( // Both should return all ones for negative value with large shift final Bytes32 allOnes = - Bytes32.fromHexString( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + Bytes32.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(allOnes); assertThat(Bytes32.leftPad(originalResult)).isEqualTo(allOnes); } @@ -272,8 +271,7 @@ void property_sar_allOnes_anyShift_returnsAllOnes(@ForAll("smallShifts") final i // -1 in two's complement (all bits set) final Bytes value = - Bytes.fromHexString( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); final Bytes shiftBytes = Bytes.of(shift); final Bytes originalResult = runSarOperation(shiftBytes, value); @@ -289,8 +287,7 @@ void property_sar_minValue_shift255_returnsAllOnes() { // MIN_VALUE: 0x8000...0000 (only sign bit set) final Bytes value = - Bytes.fromHexString( - "0x8000000000000000000000000000000000000000000000000000000000000000"); + Bytes.fromHexString("0x8000000000000000000000000000000000000000000000000000000000000000"); final Bytes shift = Bytes.of(255); final Bytes originalResult = runSarOperation(shift, value); @@ -298,8 +295,7 @@ void property_sar_minValue_shift255_returnsAllOnes() { // SAR of MIN_VALUE by 255 should be all ones (-1) final Bytes32 allOnes = - Bytes32.fromHexString( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + Bytes32.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); assertThat(Bytes32.leftPad(optimizedResult)).isEqualTo(allOnes); assertThat(Bytes32.leftPad(originalResult)).isEqualTo(allOnes); } @@ -309,8 +305,7 @@ void property_sar_maxPositive_shift255_returnsZero() { // MAX_VALUE: 0x7fff...ffff (all bits except sign bit set) final Bytes value = - Bytes.fromHexString( - "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); final Bytes shift = Bytes.of(255); final Bytes originalResult = runSarOperation(shift, value); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java index 851626ad105..361b9328cd4 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java @@ -320,9 +320,7 @@ private void verifyShrCorrectness() { } private void printResults( - final String testName, - final PerformanceResult original, - final PerformanceResult optimized) { + final String testName, final PerformanceResult original, final PerformanceResult optimized) { double speedup = original.nsPerOp() / optimized.nsPerOp(); double percentFaster = (1 - optimized.nsPerOp() / original.nsPerOp()) * 100; From e4b8bbe6d6890a2012930de13686d1cba71413fb Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 12 Feb 2026 21:27:23 +0100 Subject: [PATCH 16/27] Delete a debug class Signed-off-by: Ameziane H. --- .../ShiftOperationsPerformanceTest.java | 428 ------------------ 1 file changed, 428 deletions(-) delete mode 100644 evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java deleted file mode 100644 index 361b9328cd4..00000000000 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/ShiftOperationsPerformanceTest.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * 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.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.worldstate.WorldUpdater; - -import java.io.PrintStream; -import java.util.Random; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * Performance test comparing shift operations (SHL/SHR) with their optimized versions. - * - *

Measures throughput in Mgas/s (million gas per second). All shift operations cost 3 gas. - */ -public class ShiftOperationsPerformanceTest { - - private static final PrintStream OUT = System.out; - private static final long SHIFT_GAS_COST = 3L; - private static final int WARMUP_ITERATIONS = 10_000; - private static final int MEASURE_ITERATIONS = 100_000; - private static final int SAMPLE_SIZE = 10_000; - - private MessageFrame frame; - private Bytes[] valuePool; - private Bytes[] shiftPool; - private Random random; - - @BeforeEach - void setUp() { - frame = createMessageFrame(); - random = new Random(42); - valuePool = new Bytes[SAMPLE_SIZE]; - shiftPool = new Bytes[SAMPLE_SIZE]; - } - - // region SHL Performance Tests - - @Test - void measureShlPerformance_smallShifts() { - OUT.println("\n=== SHL Performance: Small Shifts (0-255) ==="); - fillPoolsSmallShifts(); - runShlComparison("SHL_SMALL_SHIFTS"); - } - - @Test - void measureShlPerformance_shift1() { - OUT.println("\n=== SHL Performance: Shift by 1 ==="); - fillPoolsShift1(); - runShlComparison("SHL_SHIFT_1"); - } - - @Test - void measureShlPerformance_overflowShifts() { - OUT.println("\n=== SHL Performance: Overflow Shifts (>= 256) ==="); - fillPoolsOverflowShifts(); - runShlComparison("SHL_OVERFLOW"); - } - - @Test - void measureShlPerformance_random() { - OUT.println("\n=== SHL Performance: Random Inputs ==="); - fillPoolsRandom(); - runShlComparison("SHL_RANDOM"); - } - - // endregion - - // region SHR Performance Tests - - @Test - void measureShrPerformance_smallShifts() { - OUT.println("\n=== SHR Performance: Small Shifts (0-255) ==="); - fillPoolsSmallShifts(); - runShrComparison("SHR_SMALL_SHIFTS"); - } - - @Test - void measureShrPerformance_shift1() { - OUT.println("\n=== SHR Performance: Shift by 1 ==="); - fillPoolsShift1(); - runShrComparison("SHR_SHIFT_1"); - } - - @Test - void measureShrPerformance_overflowShifts() { - OUT.println("\n=== SHR Performance: Overflow Shifts (>= 256) ==="); - fillPoolsOverflowShifts(); - runShrComparison("SHR_OVERFLOW"); - } - - @Test - void measureShrPerformance_random() { - OUT.println("\n=== SHR Performance: Random Inputs ==="); - fillPoolsRandom(); - runShrComparison("SHR_RANDOM"); - } - - // endregion - - // region Pool Initialization - - private void fillPoolsSmallShifts() { - for (int i = 0; i < SAMPLE_SIZE; i++) { - byte[] valueBytes = new byte[32]; - random.nextBytes(valueBytes); - valuePool[i] = Bytes.wrap(valueBytes); - shiftPool[i] = Bytes.of(random.nextInt(256)); - } - } - - private void fillPoolsShift1() { - for (int i = 0; i < SAMPLE_SIZE; i++) { - byte[] valueBytes = new byte[32]; - random.nextBytes(valueBytes); - valuePool[i] = Bytes.wrap(valueBytes); - shiftPool[i] = Bytes.of(1); - } - } - - private void fillPoolsOverflowShifts() { - for (int i = 0; i < SAMPLE_SIZE; i++) { - byte[] valueBytes = new byte[32]; - random.nextBytes(valueBytes); - valuePool[i] = Bytes.wrap(valueBytes); - int shift = 256 + random.nextInt(1000); - shiftPool[i] = intToMinimalBytes(shift); - } - } - - private void fillPoolsRandom() { - for (int i = 0; i < SAMPLE_SIZE; i++) { - int valueSize = 1 + random.nextInt(32); - byte[] valueBytes = new byte[valueSize]; - random.nextBytes(valueBytes); - valuePool[i] = Bytes.wrap(valueBytes); - - int shiftSize = random.nextInt(5); - if (shiftSize == 0) { - shiftPool[i] = Bytes.EMPTY; - } else { - byte[] shiftBytes = new byte[shiftSize]; - random.nextBytes(shiftBytes); - shiftPool[i] = Bytes.wrap(shiftBytes); - } - } - } - - // endregion - - // region Performance Measurement - - private void runShlComparison(final String testName) { - OUT.println("Warming up..."); - warmupShl(false); - warmupShl(true); - - OUT.println("Measuring original ShlOperation..."); - PerformanceResult original = measureShl(false); - - OUT.println("Measuring optimized ShlOperationOptimized..."); - PerformanceResult optimized = measureShl(true); - - printResults(testName, original, optimized); - verifyShlCorrectness(); - } - - private void runShrComparison(final String testName) { - OUT.println("Warming up..."); - warmupShr(false); - warmupShr(true); - - OUT.println("Measuring original ShrOperation..."); - PerformanceResult original = measureShr(false); - - OUT.println("Measuring optimized ShrOperationOptimized..."); - PerformanceResult optimized = measureShr(true); - - printResults(testName, original, optimized); - verifyShrCorrectness(); - } - - private void warmupShl(final boolean useOptimized) { - int index = 0; - for (int i = 0; i < WARMUP_ITERATIONS; i++) { - frame.pushStackItem(valuePool[index]); - frame.pushStackItem(shiftPool[index]); - if (useOptimized) { - ShlOperationOptimized.staticOperation(frame); - } else { - ShlOperation.staticOperation(frame); - } - frame.popStackItem(); - index = (index + 1) % SAMPLE_SIZE; - } - } - - private void warmupShr(final boolean useOptimized) { - int index = 0; - for (int i = 0; i < WARMUP_ITERATIONS; i++) { - frame.pushStackItem(valuePool[index]); - frame.pushStackItem(shiftPool[index]); - if (useOptimized) { - ShrOperationOptimized.staticOperation(frame); - } else { - ShrOperation.staticOperation(frame); - } - frame.popStackItem(); - index = (index + 1) % SAMPLE_SIZE; - } - } - - private PerformanceResult measureShl(final boolean useOptimized) { - int index = 0; - long startTime = System.nanoTime(); - for (int i = 0; i < MEASURE_ITERATIONS; i++) { - frame.pushStackItem(valuePool[index]); - frame.pushStackItem(shiftPool[index]); - if (useOptimized) { - ShlOperationOptimized.staticOperation(frame); - } else { - ShlOperation.staticOperation(frame); - } - frame.popStackItem(); - index = (index + 1) % SAMPLE_SIZE; - } - long endTime = System.nanoTime(); - return new PerformanceResult(MEASURE_ITERATIONS, endTime - startTime, SHIFT_GAS_COST); - } - - private PerformanceResult measureShr(final boolean useOptimized) { - int index = 0; - long startTime = System.nanoTime(); - for (int i = 0; i < MEASURE_ITERATIONS; i++) { - frame.pushStackItem(valuePool[index]); - frame.pushStackItem(shiftPool[index]); - if (useOptimized) { - ShrOperationOptimized.staticOperation(frame); - } else { - ShrOperation.staticOperation(frame); - } - frame.popStackItem(); - index = (index + 1) % SAMPLE_SIZE; - } - long endTime = System.nanoTime(); - return new PerformanceResult(MEASURE_ITERATIONS, endTime - startTime, SHIFT_GAS_COST); - } - - private void verifyShlCorrectness() { - OUT.println("Verifying SHL correctness..."); - int sampleCount = Math.min(1000, SAMPLE_SIZE); - for (int i = 0; i < sampleCount; i++) { - Bytes value = valuePool[i]; - Bytes shift = shiftPool[i]; - - frame.pushStackItem(value); - frame.pushStackItem(shift); - ShlOperation.staticOperation(frame); - Bytes originalResult = frame.popStackItem(); - - frame.pushStackItem(value); - frame.pushStackItem(shift); - ShlOperationOptimized.staticOperation(frame); - Bytes optimizedResult = frame.popStackItem(); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SHL mismatch at index %d", i) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - OUT.println("SHL correctness verified for " + sampleCount + " samples"); - } - - private void verifyShrCorrectness() { - OUT.println("Verifying SHR correctness..."); - int sampleCount = Math.min(1000, SAMPLE_SIZE); - for (int i = 0; i < sampleCount; i++) { - Bytes value = valuePool[i]; - Bytes shift = shiftPool[i]; - - frame.pushStackItem(value); - frame.pushStackItem(shift); - ShrOperation.staticOperation(frame); - Bytes originalResult = frame.popStackItem(); - - frame.pushStackItem(value); - frame.pushStackItem(shift); - ShrOperationOptimized.staticOperation(frame); - Bytes optimizedResult = frame.popStackItem(); - - assertThat(Bytes32.leftPad(optimizedResult)) - .as("SHR mismatch at index %d", i) - .isEqualTo(Bytes32.leftPad(originalResult)); - } - OUT.println("SHR correctness verified for " + sampleCount + " samples"); - } - - private void printResults( - final String testName, final PerformanceResult original, final PerformanceResult optimized) { - - double speedup = original.nsPerOp() / optimized.nsPerOp(); - double percentFaster = (1 - optimized.nsPerOp() / original.nsPerOp()) * 100; - - OUT.println(); - OUT.println("╔═══════════════════════════════════════════════════════════════════╗"); - OUT.println("║ Shift Operation Performance Results: " + testName); - OUT.println("╠═══════════════════════════════════════════════════════════════════╣"); - OUT.println("║ Original Operation:"); - OUT.printf("║ - Operations: %,d%n", original.operations); - OUT.printf("║ - Total time: %.2f ms%n", original.elapsedMs()); - OUT.printf("║ - Throughput: %,.0f ops/sec%n", original.opsPerSecond()); - OUT.printf("║ - Latency: %.2f ns/op%n", original.nsPerOp()); - OUT.printf("║ - Gas throughput: %.3f Mgas/s%n", original.mgasPerSecond()); - OUT.println("╠═══════════════════════════════════════════════════════════════════╣"); - OUT.println("║ Optimized Operation:"); - OUT.printf("║ - Operations: %,d%n", optimized.operations); - OUT.printf("║ - Total time: %.2f ms%n", optimized.elapsedMs()); - OUT.printf("║ - Throughput: %,.0f ops/sec%n", optimized.opsPerSecond()); - OUT.printf("║ - Latency: %.2f ns/op%n", optimized.nsPerOp()); - OUT.printf("║ - Gas throughput: %.3f Mgas/s%n", optimized.mgasPerSecond()); - OUT.println("╠═══════════════════════════════════════════════════════════════════╣"); - OUT.printf("║ Speedup: %.2fx (%.1f%% faster)%n", speedup, percentFaster); - OUT.println("╚═══════════════════════════════════════════════════════════════════╝"); - OUT.println(); - } - - // endregion - - // region Helper Classes - - private static class PerformanceResult { - final long operations; - final long elapsedNanos; - final long gasPerOp; - - PerformanceResult(final long operations, final long elapsedNanos, final long gasPerOp) { - this.operations = operations; - this.elapsedNanos = elapsedNanos; - this.gasPerOp = gasPerOp; - } - - double elapsedMs() { - return elapsedNanos / 1_000_000.0; - } - - double nsPerOp() { - return (double) elapsedNanos / operations; - } - - double opsPerSecond() { - return operations / (elapsedNanos / 1_000_000_000.0); - } - - double mgasPerSecond() { - long totalGas = operations * gasPerOp; - double seconds = elapsedNanos / 1_000_000_000.0; - return (totalGas / 1_000_000.0) / seconds; - } - } - - // endregion - - // region Helper Methods - - private MessageFrame createMessageFrame() { - return MessageFrame.builder() - .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(Bytes32.ZERO) - .sender(Address.ZERO) - .value(Wei.ZERO) - .apparentValue(Wei.ZERO) - .code(Code.EMPTY_CODE) - .completer(__ -> {}) - .build(); - } - - private Bytes intToMinimalBytes(final int value) { - if (value == 0) { - return Bytes.EMPTY; - } - if (value <= 0xFF) { - return Bytes.of(value); - } - if (value <= 0xFFFF) { - return Bytes.of(value >> 8, value & 0xFF); - } - if (value <= 0xFFFFFF) { - return Bytes.of(value >> 16, (value >> 8) & 0xFF, value & 0xFF); - } - return Bytes.of(value >> 24, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF); - } - - // endregion -} From 78126d89ee6b608d4e7e36a4907e7d565d486691 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Fri, 13 Feb 2026 13:48:43 +0100 Subject: [PATCH 17/27] Add randomness to JMH benchmarks Signed-off-by: Ameziane H. --- .../AbstractSarOperationBenchmark.java | 31 ++++++--- .../AbstractShiftOperationBenchmark.java | 35 +++------- .../vm/operations/BenchmarkHelper.java | 68 +++++++++++++++++++ 3 files changed, 96 insertions(+), 38 deletions(-) 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 index 83628e57209..9594a0049a0 100644 --- 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 @@ -14,6 +14,10 @@ */ 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; @@ -34,6 +38,8 @@ public enum Case { 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. */ @@ -52,14 +58,11 @@ public enum Case { protected static final Bytes ALL_BITS = Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - /** Max positive value (sign bit not set). */ - protected static final Bytes POSITIVE_VALUE = - Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - @Param({ "SHIFT_0", "NEGATIVE_SHIFT_1", "POSITIVE_SHIFT_1", + "ALL_BITS_SHIFT_1", "NEGATIVE_SHIFT_128", "POSITIVE_SHIFT_128", "OVERFLOW_SHIFT_256", @@ -83,41 +86,47 @@ public void setUp() { switch (scenario) { case SHIFT_0: aPool[i] = Bytes.of(0); - bPool[i] = ALL_BITS; + 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, value = 0x7ff...fff (positive, no sign extension) + // shiftAmount = 0x1, random positive value (no sign extension) aPool[i] = Bytes.of(1); - bPool[i] = POSITIVE_VALUE; + bPool[i] = randomPositiveValue(random); break; case NEGATIVE_SHIFT_128: aPool[i] = Bytes.of(128); - bPool[i] = ALL_BITS; + bPool[i] = randomNegativeValue(random); break; case POSITIVE_SHIFT_128: aPool[i] = Bytes.of(128); - bPool[i] = POSITIVE_VALUE; + bPool[i] = randomPositiveValue(random); break; case OVERFLOW_SHIFT_256: // Shift of exactly 256 - overflow path aPool[i] = Bytes.fromHexString("0x0100"); // 256 - bPool[i] = ALL_BITS; + bPool[i] = randomValue(random); break; case OVERFLOW_LARGE_SHIFT: // Shift amount > 4 bytes - overflow path aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes - bPool[i] = ALL_BITS; + bPool[i] = randomValue(random); break; case FULL_RANDOM: 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 index ce50d7a0ed7..7ccd39cfec2 100644 --- 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 @@ -14,6 +14,8 @@ */ 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; @@ -46,14 +48,6 @@ public enum Case { FULL_RANDOM } - /** All bits set (32 bytes of 0xFF). */ - protected static final Bytes ALL_BITS = - Bytes.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - - /** Max positive value (sign bit not set). */ - protected static final Bytes POSITIVE_VALUE = - Bytes.fromHexString("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - @Param({ "SHIFT_0", "SHIFT_1", @@ -80,32 +74,32 @@ public void setUp() { switch (scenario) { case SHIFT_0: aPool[i] = Bytes.of(0); - bPool[i] = getValueForCase(scenario); + bPool[i] = randomValue(random); break; case SHIFT_1: aPool[i] = Bytes.of(1); - bPool[i] = getValueForCase(scenario); + bPool[i] = randomValue(random); break; case SHIFT_128: aPool[i] = Bytes.of(128); - bPool[i] = getValueForCase(scenario); + bPool[i] = randomValue(random); break; case SHIFT_255: aPool[i] = Bytes.of(255); - bPool[i] = getValueForCase(scenario); + bPool[i] = randomValue(random); break; case OVERFLOW_SHIFT_256: aPool[i] = Bytes.fromHexString("0x0100"); // 256 - bPool[i] = getValueForCase(scenario); + bPool[i] = randomValue(random); break; case OVERFLOW_LARGE_SHIFT: aPool[i] = Bytes.fromHexString("0x010000000000"); // > 4 bytes - bPool[i] = getValueForCase(scenario); + bPool[i] = randomValue(random); break; case FULL_RANDOM: @@ -121,17 +115,4 @@ public void setUp() { } index = 0; } - - /** - * Returns the value to use for the given test case. - * - *

Subclasses can override this to use different values for specific cases (e.g., SAR uses - * negative values to test sign extension). - * - * @param scenario the test case - * @return the value to use - */ - protected Bytes getValueForCase(final Case scenario) { - return ALL_BITS; - } } 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); + } } From ca544de07a7771301248e7c2648b4e0c6a8126f7 Mon Sep 17 00:00:00 2001 From: ahamlat Date: Fri, 13 Feb 2026 21:27:04 +0100 Subject: [PATCH 18/27] Update evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java Co-authored-by: Luis Pinto Signed-off-by: ahamlat --- .../hyperledger/besu/evm/operation/SarOperationOptimized.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index bc563632d69..1851a1f6329 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -115,7 +115,7 @@ private static Bytes sar256(final byte[] in, final int shift, final boolean nega out[i] = (byte) hi; } else { final int lo = (src - 1 >= 0) ? (in[src - 1] & 0xFF) : fill; - out[i] = (byte) ((hi >>> shiftBits) | ((lo << (8 - shiftBits)) & 0xFF)); + out[i] = (byte) ((hi >>> shiftBits) | (lo << (8 - shiftBits))); } } From 268036a9a7eb336c1c1369c3d9db94a31983f132 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Fri, 13 Feb 2026 21:29:21 +0100 Subject: [PATCH 19/27] Remove redundant & 0xFF Signed-off-by: Ameziane H. --- .../hyperledger/besu/evm/operation/ShrOperationOptimized.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index f7deec6207a..90eafa171e1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java @@ -108,7 +108,7 @@ private static Bytes shr256(final byte[] in, final int shift) { out[i] = (byte) hi; } else { final int lo = (src - 1 >= 0) ? (in[src - 1] & 0xFF) : 0; - out[i] = (byte) ((hi >>> shiftBits) | ((lo << (8 - shiftBits)) & 0xFF)); + out[i] = (byte) ((hi >>> shiftBits) | (lo << (8 - shiftBits))); } } From f95e54954eca14371eecc572e7f050102d314f5f Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 16 Feb 2026 10:29:11 +0100 Subject: [PATCH 20/27] Review variables naming for better readability Signed-off-by: Ameziane H. --- .../besu/evm/operation/SarOperationOptimized.java | 8 ++++---- .../besu/evm/operation/ShlOperationOptimized.java | 8 ++++---- .../besu/evm/operation/ShrOperationOptimized.java | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) 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 index 1851a1f6329..abd9275f8a8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -110,12 +110,12 @@ private static Bytes sar256(final byte[] in, final int shift, final boolean nega // Only iterate bytes that receive shifted data from the input for (int i = 31; i >= shiftBytes; i--) { final int src = i - shiftBytes; - final int hi = in[src] & 0xFF; + final int curr = in[src] & 0xFF; if (shiftBits == 0) { - out[i] = (byte) hi; + out[i] = (byte) curr; } else { - final int lo = (src - 1 >= 0) ? (in[src - 1] & 0xFF) : fill; - out[i] = (byte) ((hi >>> shiftBits) | (lo << (8 - shiftBits))); + final int prev = (src - 1 >= 0) ? (in[src - 1] & 0xFF) : fill; + out[i] = (byte) ((curr >>> shiftBits) | (prev << (8 - shiftBits))); } } 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 index d315e6630ad..b68a2ea473f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java @@ -104,12 +104,12 @@ private static Bytes shl256(final byte[] in, final int shift) { final int limit = 32 - shiftBytes; for (int i = 0; i < limit; i++) { final int src = i + shiftBytes; - final int lo = in[src] & 0xFF; + final int curr = in[src] & 0xFF; if (shiftBits == 0) { - out[i] = (byte) lo; + out[i] = (byte) curr; } else { - final int hi = (src + 1 < 32) ? (in[src + 1] & 0xFF) : 0; - out[i] = (byte) ((lo << shiftBits) | (hi >>> (8 - shiftBits))); + final int next = (src + 1 < 32) ? (in[src + 1] & 0xFF) : 0; + out[i] = (byte) ((curr << shiftBits) | (next >>> (8 - shiftBits))); } } 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 index 90eafa171e1..71bf0240892 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java @@ -103,12 +103,12 @@ private static Bytes shr256(final byte[] in, final int shift) { // Bytes below shiftBytes are guaranteed zero (already from new byte[32]) for (int i = 31; i >= shiftBytes; i--) { final int src = i - shiftBytes; - final int hi = in[src] & 0xFF; + final int curr = in[src] & 0xFF; if (shiftBits == 0) { - out[i] = (byte) hi; + out[i] = (byte) curr; } else { - final int lo = (src - 1 >= 0) ? (in[src - 1] & 0xFF) : 0; - out[i] = (byte) ((hi >>> shiftBits) | (lo << (8 - shiftBits))); + final int prev = (src - 1 >= 0) ? (in[src - 1] & 0xFF) : 0; + out[i] = (byte) ((curr >>> shiftBits) | (prev << (8 - shiftBits))); } } From 83c7a1725e4770d2cdb2fa13621aaacecfc8428c Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Mon, 16 Feb 2026 17:28:32 +0100 Subject: [PATCH 21/27] Add worst case on SAR benchmarks Signed-off-by: Ameziane H. --- .../operations/AbstractSarOperationBenchmark.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 index 9594a0049a0..58a6e496c45 100644 --- 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 @@ -44,8 +44,12 @@ public enum Case { 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. */ @@ -64,7 +68,9 @@ public enum Case { "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" @@ -112,10 +118,19 @@ public void setUp() { 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 From 8c1d5c105e6a9a68fc17eaef9632550cde3f581f Mon Sep 17 00:00:00 2001 From: ahamlat Date: Tue, 17 Feb 2026 15:01:37 +0100 Subject: [PATCH 22/27] Apply suggestions from code review Co-authored-by: Simon Dudley Signed-off-by: ahamlat --- .../org/hyperledger/besu/evm/operation/Shift256Operations.java | 2 +- .../hyperledger/besu/evm/operation/ShlOperationOptimized.java | 2 +- .../hyperledger/besu/evm/operation/ShrOperationOptimized.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 index 8d0e825d923..ce2b51738f1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * 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 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 index b68a2ea473f..fb46f2109ed 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * 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 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 index 71bf0240892..ae81f5d77e3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * 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 From a7514b1b50b773a551023b1e2217ea5b2290f0f9 Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Tue, 17 Feb 2026 16:16:42 +0100 Subject: [PATCH 23/27] Address review comments Signed-off-by: Ameziane H. --- CHANGELOG.md | 2 +- .../AbstractSarOperationBenchmark.java | 2 +- .../evm/operation/SarOperationOptimized.java | 3 +- .../evm/operation/Shift256Operations.java | 14 ++++----- .../evm/operation/ShlOperationOptimized.java | 5 ++-- .../evm/operation/ShrOperationOptimized.java | 5 ++-- .../SarOperationPropertyBasedTest.java | 30 +++++++++++++++++++ 7 files changed, 44 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09acebbba42..db836175f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,12 +40,12 @@ - Add ability to pass a custom tracer to block simulation [#9708](https://github.com/hyperledger/besu/pull/9708) - Add support for `4byteTracer` in `debug_trace*` methods to collect function selectors from internal calls via PR [#9642](https://github.com/hyperledger/besu/pull/9642). Thanks to [@JukLee0ira](https://github.com/JukLee0ira). - Update assertj to v3.27.7 [#9710](https://github.com/hyperledger/besu/pull/9710) -- Improve SAR, SHR and SHL opcodes performance [#9796](https://github.com/hyperledger/besu/pull/9796) - Update vertx to 4.5.24 [#9645](https://github.com/hyperledger/besu/pull/9645) - Add byte-level metrics for P2P message exchange [#9666](https://github.com/hyperledger/besu/pull/9666) #### 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 callTracer handling of failed CREATE operations, including correct input field extraction and proper error reporting for both soft failures and revert reasons 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 index 58a6e496c45..786fd254408 100644 --- 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 @@ -147,7 +147,7 @@ public void setUp() { case FULL_RANDOM: default: // Original random behavior - final byte[] shift = new byte[1 + random.nextInt(4)]; + final byte[] shift = new byte[1 + random.nextInt(2)]; final byte[] value = new byte[1 + random.nextInt(32)]; random.nextBytes(shift); random.nextBytes(value); 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 index abd9275f8a8..88a4146210b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -16,7 +16,6 @@ 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.ZERO_32; import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; import org.hyperledger.besu.evm.EVM; @@ -69,7 +68,7 @@ public static OperationResult staticOperation(final MessageFrame frame) { // shift >= 256, push All 1s if negative, All 0s otherwise if (isShiftOverflow(shiftBytes)) { - frame.pushStackItem(negative ? ALL_ONES : ZERO_32); + frame.pushStackItem(negative ? ALL_ONES : Bytes.EMPTY); return sarSuccess; } final int shift = shiftBytes.length == 0 ? 0 : (shiftBytes[shiftBytes.length - 1] & 0xFF); 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 index ce2b51738f1..edfdab6be03 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java @@ -14,8 +14,9 @@ */ package org.hyperledger.besu.evm.operation; +import java.util.Arrays; + import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; /** * Utility class for shared constants and helpers used by optimized 256-bit shift operations (SHL, @@ -23,8 +24,8 @@ */ public final class Shift256Operations { - /** Zero value (32 zero bytes). */ - public static final Bytes ZERO_32 = Bytes32.ZERO; + /** 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); @@ -45,9 +46,8 @@ private Shift256Operations() { * @return {@code true} if the shift amount is >= 256, {@code false} otherwise */ public static boolean isShiftOverflow(final byte[] shiftBytes) { - for (int i = 0; i < shiftBytes.length - 1; i++) { - if (shiftBytes[i] != 0) return true; - } - return false; + 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 index fb46f2109ed..16ebb8f7c1b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.operation.Shift256Operations.ZERO_32; import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; import org.hyperledger.besu.evm.EVM; @@ -60,7 +59,7 @@ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); final Bytes value = frame.popStackItem(); if (value.isZero()) { - frame.pushStackItem(ZERO_32); + frame.pushStackItem(Bytes.EMPTY); return shlSuccess; } @@ -69,7 +68,7 @@ public static OperationResult staticOperation(final MessageFrame frame) { // shift >= 256, push All 0s if (isShiftOverflow(shiftBytes)) { - frame.pushStackItem(ZERO_32); + frame.pushStackItem(Bytes.EMPTY); return shlSuccess; } 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 index ae81f5d77e3..1ae7d451443 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.operation.Shift256Operations.ZERO_32; import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; import org.hyperledger.besu.evm.EVM; @@ -60,7 +59,7 @@ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes shiftAmount = frame.popStackItem(); final Bytes value = frame.popStackItem(); if (value.isZero()) { - frame.pushStackItem(ZERO_32); + frame.pushStackItem(Bytes.EMPTY); return shrSuccess; } @@ -69,7 +68,7 @@ public static OperationResult staticOperation(final MessageFrame frame) { // shift >= 256, push All 0s if (isShiftOverflow(shiftBytes)) { - frame.pushStackItem(ZERO_32); + frame.pushStackItem(Bytes.EMPTY); return shrSuccess; } 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 index cd1fae3e751..8f89f50bbeb 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationPropertyBasedTest.java @@ -316,6 +316,36 @@ void property_sar_maxPositive_shift255_returnsZero() { assertThat(Bytes32.leftPad(originalResult)).isEqualTo(Bytes32.ZERO); } + @Property(tries = 3000) + void property_sarOptimized_matchesOriginal_negativeValues_shift255( + @ForAll("negativeValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(255); + + final Bytes originalResult = runSarOperation(shift, value); + final Bytes optimizedResult = runSarOperationOptimized(shift, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SAR negative shift=255 mismatch for value=%s", value.toHexString()) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + + @Property(tries = 3000) + void property_sarOptimized_matchesOriginal_positiveValues_shift255( + @ForAll("positiveValues") final byte[] valueBytes) { + + final Bytes value = Bytes.wrap(valueBytes); + final Bytes shift = Bytes.of(255); + + final Bytes originalResult = runSarOperation(shift, value); + final Bytes optimizedResult = runSarOperationOptimized(shift, value); + + assertThat(Bytes32.leftPad(optimizedResult)) + .as("SAR positive shift=255 mismatch for value=%s", value.toHexString()) + .isEqualTo(Bytes32.leftPad(originalResult)); + } + // endregion // region Helper Methods From a2d7ffd0e27925b7e7d9ecc14efea82bda3e053c Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Tue, 17 Feb 2026 16:49:52 +0100 Subject: [PATCH 24/27] Fix test issue. Signed-off-by: Ameziane H. --- .../hyperledger/besu/evm/operation/Shift256OperationsTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java index 9596ad75c0a..333f6aa2d38 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java @@ -16,7 +16,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.operation.Shift256Operations.ALL_ONES; -import static org.hyperledger.besu.evm.operation.Shift256Operations.ZERO_32; import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; import org.apache.tuweni.bytes.Bytes; @@ -30,7 +29,6 @@ class Shift256OperationsTest { @Test void constants_areCorrect() { - assertThat(ZERO_32).isEqualTo(Bytes32.ZERO); assertThat(ALL_ONES.size()).isEqualTo(32); assertThat(ALL_ONES.toHexString()) .isEqualTo("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); From 31d06694dce456f36b083e7d57b57353ba2e360b Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Tue, 17 Feb 2026 17:12:08 +0100 Subject: [PATCH 25/27] spotless. Signed-off-by: Ameziane H. --- .../hyperledger/besu/evm/operation/Shift256OperationsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java index 333f6aa2d38..4e8ec768f6f 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/Shift256OperationsTest.java @@ -19,7 +19,6 @@ import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; /** Unit tests for the {@link Shift256Operations} utility class. */ From 63eb9f2bb822af743a24638118f85a0f9fd0d29a Mon Sep 17 00:00:00 2001 From: ahamlat Date: Tue, 17 Feb 2026 17:22:10 +0100 Subject: [PATCH 26/27] Update evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java Co-authored-by: Simon Dudley Signed-off-by: ahamlat --- .../hyperledger/besu/evm/operation/SarOperationOptimized.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 88a4146210b..6cc3cc995cb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * 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 From 8b30c597bd991f868a1427f0efb1198b2a6e579b Mon Sep 17 00:00:00 2001 From: "Ameziane H." Date: Thu, 19 Feb 2026 12:36:23 +0100 Subject: [PATCH 27/27] Renaming Signed-off-by: Ameziane H. --- .../besu/evm/operation/SarOperationOptimized.java | 6 +++--- .../besu/evm/operation/ShlOperationOptimized.java | 6 +++--- .../besu/evm/operation/ShrOperationOptimized.java | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) 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 index 6cc3cc995cb..adbea0cb47a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SarOperationOptimized.java @@ -108,12 +108,12 @@ private static Bytes sar256(final byte[] in, final int shift, final boolean nega // Only iterate bytes that receive shifted data from the input for (int i = 31; i >= shiftBytes; i--) { - final int src = i - shiftBytes; - final int curr = in[src] & 0xFF; + final int srcIndex = i - shiftBytes; + final int curr = in[srcIndex] & 0xFF; if (shiftBits == 0) { out[i] = (byte) curr; } else { - final int prev = (src - 1 >= 0) ? (in[src - 1] & 0xFF) : fill; + final int prev = (srcIndex - 1 >= 0) ? (in[srcIndex - 1] & 0xFF) : fill; out[i] = (byte) ((curr >>> shiftBits) | (prev << (8 - shiftBits))); } } 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 index 16ebb8f7c1b..a691636e060 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShlOperationOptimized.java @@ -102,12 +102,12 @@ private static Bytes shl256(final byte[] in, final int shift) { // 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 src = i + shiftBytes; - final int curr = in[src] & 0xFF; + final int srcIndex = i + shiftBytes; + final int curr = in[srcIndex] & 0xFF; if (shiftBits == 0) { out[i] = (byte) curr; } else { - final int next = (src + 1 < 32) ? (in[src + 1] & 0xFF) : 0; + final int next = (srcIndex + 1 < 32) ? (in[srcIndex + 1] & 0xFF) : 0; out[i] = (byte) ((curr << shiftBits) | (next >>> (8 - shiftBits))); } } 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 index 1ae7d451443..1eeb1b1ca5c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ShrOperationOptimized.java @@ -101,12 +101,12 @@ private static Bytes shr256(final byte[] in, final int shift) { // 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 src = i - shiftBytes; - final int curr = in[src] & 0xFF; + final int srcIndex = i - shiftBytes; + final int curr = in[srcIndex] & 0xFF; if (shiftBits == 0) { out[i] = (byte) curr; } else { - final int prev = (src - 1 >= 0) ? (in[src - 1] & 0xFF) : 0; + final int prev = (srcIndex - 1 >= 0) ? (in[srcIndex - 1] & 0xFF) : 0; out[i] = (byte) ((curr >>> shiftBits) | (prev << (8 - shiftBits))); } }