diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java index ac042cdee15..7f0722b9e41 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java @@ -133,7 +133,7 @@ public void setUp() { Case scenario = Case.valueOf(caseName); aPool = new Bytes[SAMPLE_SIZE]; bPool = new Bytes[SAMPLE_SIZE]; - cPool = new Bytes[SAMPLE_SIZE]; + mPool = new Bytes[SAMPLE_SIZE]; final ThreadLocalRandom random = ThreadLocalRandom.current(); int aSize; @@ -156,7 +156,7 @@ public void setUp() { random.nextBytes(c); aPool[i] = Bytes.wrap(a); bPool[i] = Bytes.wrap(b); - cPool[i] = Bytes.wrap(c); + mPool[i] = Bytes.wrap(c); } index = 0; } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java index 5821f57e3d6..bf696196ccb 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java @@ -27,7 +27,7 @@ public class MulModOperationBenchmark extends TernaryOperationBenchmark { - // Benches for (a + b) % c + // Benches for (a * b) % m // Define available scenarios public enum Case { @@ -40,7 +40,7 @@ public enum Case { MULMOD_128_64_64(4, 2, 2), MULMOD_128_128_32(4, 4, 1), MULMOD_128_128_64(4, 4, 2), - MULMOD_128_128_128(4, 4, 3), + MULMOD_128_128_128(4, 4, 4), MULMOD_192_32_32(6, 1, 1), MULMOD_192_64_32(6, 2, 1), MULMOD_192_64_64(6, 2, 2), @@ -75,12 +75,12 @@ public enum Case { final int aSize; final int bSize; - final int cSize; + final int mSize; - Case(final int aSize, final int bSize, final int cSize) { + Case(final int aSize, final int bSize, final int mSize) { this.aSize = aSize; this.bSize = bSize; - this.cSize = cSize; + this.mSize = mSize; } } @@ -137,30 +137,30 @@ public void setUp() { Case scenario = Case.valueOf(caseName); aPool = new Bytes[SAMPLE_SIZE]; bPool = new Bytes[SAMPLE_SIZE]; - cPool = new Bytes[SAMPLE_SIZE]; + mPool = new Bytes[SAMPLE_SIZE]; final ThreadLocalRandom random = ThreadLocalRandom.current(); int aSize; int bSize; - int cSize; + int mSize; for (int i = 0; i < SAMPLE_SIZE; i++) { if (scenario.aSize < 0) aSize = random.nextInt(1, 33); else aSize = scenario.aSize * 4; if (scenario.bSize < 0) bSize = random.nextInt(1, 33); else bSize = scenario.bSize * 4; - if (scenario.cSize < 0) cSize = random.nextInt(1, 33); - else cSize = scenario.cSize * 4; + if (scenario.mSize < 0) mSize = random.nextInt(1, 33); + else mSize = scenario.mSize * 4; final byte[] a = new byte[aSize]; final byte[] b = new byte[bSize]; - final byte[] c = new byte[cSize]; + final byte[] m = new byte[mSize]; random.nextBytes(a); random.nextBytes(b); - random.nextBytes(c); + random.nextBytes(m); aPool[i] = Bytes.wrap(a); bPool[i] = Bytes.wrap(b); - cPool[i] = Bytes.wrap(c); + mPool[i] = Bytes.wrap(m); } index = 0; } diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java index 6b300f97e7b..842d4fb9de8 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TernaryOperationBenchmark.java @@ -42,7 +42,7 @@ public abstract class TernaryOperationBenchmark { protected Bytes[] aPool; protected Bytes[] bPool; - protected Bytes[] cPool; + protected Bytes[] mPool; protected int index; protected MessageFrame frame; @@ -51,16 +51,16 @@ public void setUp() { frame = BenchmarkHelper.createMessageCallFrame(); aPool = new Bytes[SAMPLE_SIZE]; bPool = new Bytes[SAMPLE_SIZE]; - cPool = new Bytes[SAMPLE_SIZE]; + mPool = new Bytes[SAMPLE_SIZE]; BenchmarkHelper.fillPool(aPool); BenchmarkHelper.fillPool(bPool); - BenchmarkHelper.fillPool(cPool); + BenchmarkHelper.fillPool(mPool); index = 0; } @Benchmark public void executeOperation(final Blackhole blackhole) { - frame.pushStackItem(cPool[index]); + frame.pushStackItem(mPool[index]); frame.pushStackItem(bPool[index]); frame.pushStackItem(aPool[index]); diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java new file mode 100644 index 00000000000..179cf51f686 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/MulModOperationBenchmarkV2.java @@ -0,0 +1,172 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.operation.MulModOperationV2; + +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + +public class MulModOperationBenchmarkV2 extends TernaryOperationBenchmarkV2 { + + // Benches for (a * b) % m + + // Define available scenarios + public enum Case { + MULMOD_32_32_32(1, 1, 1), + MULMOD_64_32_32(2, 1, 1), + MULMOD_64_64_32(2, 2, 1), + MULMOD_64_64_64(2, 2, 2), + MULMOD_128_32_32(4, 1, 1), + MULMOD_128_64_32(4, 2, 1), + MULMOD_128_64_64(4, 2, 2), + MULMOD_128_128_32(4, 4, 1), + MULMOD_128_128_64(4, 4, 2), + MULMOD_128_128_128(4, 4, 4), + MULMOD_192_32_32(6, 1, 1), + MULMOD_192_64_32(6, 2, 1), + MULMOD_192_64_64(6, 2, 2), + MULMOD_192_128_32(6, 4, 1), + MULMOD_192_128_64(6, 4, 2), + MULMOD_192_128_128(6, 4, 4), + MULMOD_192_192_32(6, 6, 1), + MULMOD_192_192_64(6, 6, 2), + MULMOD_192_192_128(6, 6, 4), + MULMOD_192_192_192(6, 6, 6), + MULMOD_256_32_32(8, 1, 1), + MULMOD_256_64_32(8, 2, 1), + MULMOD_256_64_64(8, 2, 2), + MULMOD_256_64_128(8, 2, 4), + MULMOD_256_64_192(8, 2, 6), + MULMOD_256_128_32(8, 4, 1), + MULMOD_256_128_64(8, 4, 2), + MULMOD_256_128_128(8, 4, 4), + MULMOD_256_192_32(8, 6, 1), + MULMOD_256_192_64(8, 6, 2), + MULMOD_256_192_128(8, 6, 4), + MULMOD_256_192_192(8, 6, 6), + MULMOD_256_256_32(8, 8, 1), + MULMOD_256_256_64(8, 8, 2), + MULMOD_256_256_128(8, 8, 4), + MULMOD_256_256_192(8, 8, 6), + MULMOD_256_256_256(8, 8, 8), + LARGER_MULMOD_64_64_128(2, 2, 4), + LARGER_MULMOD_192_192_256(6, 6, 8), + ZERO_MULMOD_128_256_0(4, 8, 0), + FULL_RANDOM(-1, -1, -1); + + final int aSize; + final int bSize; + final int mSize; + + Case(final int aSize, final int bSize, final int mSize) { + this.aSize = aSize; + this.bSize = bSize; + this.mSize = mSize; + } + } + + @Param({ + "MULMOD_32_32_32", + "MULMOD_64_32_32", + "MULMOD_64_64_32", + "MULMOD_64_64_64", + "MULMOD_128_32_32", + "MULMOD_128_64_32", + "MULMOD_128_64_64", + "MULMOD_128_128_32", + "MULMOD_128_128_64", + "MULMOD_128_128_128", + "MULMOD_192_32_32", + "MULMOD_192_64_32", + "MULMOD_192_64_64", + "MULMOD_192_128_32", + "MULMOD_192_128_64", + "MULMOD_192_128_128", + "MULMOD_192_192_32", + "MULMOD_192_192_64", + "MULMOD_192_192_128", + "MULMOD_192_192_192", + "MULMOD_256_32_32", + "MULMOD_256_64_32", + "MULMOD_256_64_64", + "MULMOD_256_64_128", + "MULMOD_256_64_192", + "MULMOD_256_128_32", + "MULMOD_256_128_64", + "MULMOD_256_128_128", + "MULMOD_256_192_32", + "MULMOD_256_192_64", + "MULMOD_256_192_128", + "MULMOD_256_192_192", + "MULMOD_256_256_32", + "MULMOD_256_256_64", + "MULMOD_256_256_128", + "MULMOD_256_256_192", + "MULMOD_256_256_256", + "LARGER_MULMOD_64_64_128", + "LARGER_MULMOD_192_192_256", + "ZERO_MULMOD_128_256_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + + MulModOperationBenchmarkV2.Case scenario = MulModOperationBenchmarkV2.Case.valueOf(caseName); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + mPool = new UInt256[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + int mSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.aSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.aSize * 4; + if (scenario.bSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.bSize * 4; + if (scenario.mSize < 0) mSize = random.nextInt(1, 33); + else mSize = scenario.mSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + final byte[] m = new byte[mSize]; + random.nextBytes(a); + random.nextBytes(b); + random.nextBytes(m); + aPool[i] = BenchmarkHelperV2.bytesToUInt256(a); + bPool[i] = BenchmarkHelperV2.bytesToUInt256(b); + mPool[i] = BenchmarkHelperV2.bytesToUInt256(m); + } + index = 0; + } + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return MulModOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java new file mode 100644 index 00000000000..16a5d63203d --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/TernaryOperationBenchmarkV2.java @@ -0,0 +1,75 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public abstract class TernaryOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + + protected UInt256[] aPool; + protected UInt256[] bPool; + protected UInt256[] mPool; + protected int index; + protected MessageFrame frame; + + @Setup() + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + mPool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelperV2.fillUInt256Pool(aPool); + BenchmarkHelperV2.fillUInt256Pool(bPool); + BenchmarkHelperV2.fillUInt256Pool(mPool); + index = 0; + } + + @Benchmark + public void executeOperation(final Blackhole blackhole) { + BenchmarkHelperV2.pushUInt256(frame, mPool[index]); + BenchmarkHelperV2.pushUInt256(frame, bPool[index]); + BenchmarkHelperV2.pushUInt256(frame, aPool[index]); + + blackhole.consume(invoke(frame)); + + frame.setTopV2(frame.stackTopV2() - 1); + + index = (index + 1) % SAMPLE_SIZE; + } + + protected abstract Operation.OperationResult invoke(MessageFrame frame); +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index 97577d59572..84252ef803c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -88,6 +88,7 @@ import org.hyperledger.besu.evm.operation.XorOperationOptimized; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.v2.operation.AddOperationV2; +import org.hyperledger.besu.evm.v2.operation.MulModOperationV2; import org.hyperledger.besu.evm.v2.operation.SarOperationV2; import org.hyperledger.besu.evm.v2.operation.ShlOperationV2; import org.hyperledger.besu.evm.v2.operation.ShrOperationV2; @@ -488,6 +489,7 @@ private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing result = switch (opcode) { case 0x01 -> AddOperationV2.staticOperation(frame, frame.stackDataV2()); + case 0x09 -> MulModOperationV2.staticOperation(frame, frame.stackDataV2()); case 0x1b -> enableConstantinople ? ShlOperationV2.staticOperation(frame) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java new file mode 100644 index 00000000000..203601408db --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2.java @@ -0,0 +1,85 @@ +/* + * 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.v2.operation; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +/** The Mul mod operation. */ +public class MulModOperationV2 extends AbstractFixedCostOperationV2 { + + private static final OperationResult mulModSuccess = new OperationResult(8, null); + + /** + * Instantiates a new Mul mod operation. + * + * @param gasCalculator the gas calculator + */ + public MulModOperationV2(final GasCalculator gasCalculator) { + super(0x09, "MULMOD", 3, 1, gasCalculator, gasCalculator.getMidTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Performs MulMod operation. + * + * @param frame the frame + * @param stack the v2 operand stack ({@code long[]} in big-endian limb order) + * @return the operation result + */ + public static OperationResult staticOperation(final MessageFrame frame, final long[] stack) { + if (!frame.stackHasItems(3)) return UNDERFLOW_RESPONSE; + int top = frame.stackTopV2(); + mulMod(stack, top); + // consumed three items and produced one item + frame.setTopV2(top - 2); + return mulModSuccess; + } + + /** + * Performs EVM MULMOD (modular multiplication) on the three top stack items. + * + *
MULMOD: mulmod(a,b,m) = (a * b) mod m + * + *
MULMOD: stack[top-3] = (stack[top-1] * stack[top-2]) mod stack[top-3]. + * + * @param stack the flat limb array + * @param top current stack-top (item count) + */ + private static void mulMod(final long[] stack, final int top) { + final int aOffset = (top - 1) << 2; + final int bOffset = (top - 2) << 2; + final int mOffset = (top - 3) << 2; + final UInt256 valueA = + new UInt256(stack[aOffset], stack[aOffset + 1], stack[aOffset + 2], stack[aOffset + 3]); + final UInt256 valueB = + new UInt256(stack[bOffset], stack[bOffset + 1], stack[bOffset + 2], stack[bOffset + 3]); + final UInt256 modulus = + new UInt256(stack[mOffset], stack[mOffset + 1], stack[mOffset + 2], stack[mOffset + 3]); + final UInt256 r = modulus.isZero() ? UInt256.ZERO : valueA.mulMod(valueB, modulus); + stack[mOffset] = r.u3(); + stack[mOffset + 1] = r.u2(); + stack[mOffset + 2] = r.u1(); + stack[mOffset + 3] = r.u0(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java new file mode 100644 index 00000000000..de8bddd6e58 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/v2/operation/MulModOperationV2Test.java @@ -0,0 +1,127 @@ +/* + * 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.v2.operation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.v2.testutils.TestMessageFrameBuilderV2; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class MulModOperationV2Test { + + private final GasCalculator gasCalculator = new FrontierGasCalculator(); + private final MulModOperationV2 operation = new MulModOperationV2(gasCalculator); + + /** + * Test data for mulmod(a, b, m) = expected. + * + *
Push order when building the frame: m first (deepest), then b, then a (top).
+ */
+ static Iterable