Skip to content

Commit 03182e5

Browse files
authored
Improve Add OpCode performance (#9477)
* Improve ADD performance using new UInt256 implementation Signed-off-by: Ameziane H. <[email protected]>
1 parent a4de105 commit 03182e5

File tree

6 files changed

+316
-4
lines changed

6 files changed

+316
-4
lines changed

ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddOperationBenchmark.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
package org.hyperledger.besu.ethereum.vm.operations;
1616

1717
import org.hyperledger.besu.evm.frame.MessageFrame;
18-
import org.hyperledger.besu.evm.operation.AddOperation;
18+
import org.hyperledger.besu.evm.operation.AddOperationOptimized;
1919
import org.hyperledger.besu.evm.operation.Operation;
2020

2121
public class AddOperationBenchmark extends BinaryOperationBenchmark {
2222

2323
@Override
2424
protected Operation.OperationResult invoke(final MessageFrame frame) {
25-
return AddOperation.staticOperation(frame);
25+
return AddOperationOptimized.staticOperation(frame);
2626
}
2727
}

evm/src/main/java/org/hyperledger/besu/evm/EVM.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.hyperledger.besu.evm.operation.AddModOperation;
3131
import org.hyperledger.besu.evm.operation.AddModOperationOptimized;
3232
import org.hyperledger.besu.evm.operation.AddOperation;
33+
import org.hyperledger.besu.evm.operation.AddOperationOptimized;
3334
import org.hyperledger.besu.evm.operation.AndOperation;
3435
import org.hyperledger.besu.evm.operation.AndOperationOptimized;
3536
import org.hyperledger.besu.evm.operation.ByteOperation;
@@ -225,7 +226,10 @@ public void runToHalt(final MessageFrame frame, final OperationTracer tracing) {
225226
result =
226227
switch (opcode) {
227228
case 0x00 -> StopOperation.staticOperation(frame);
228-
case 0x01 -> AddOperation.staticOperation(frame);
229+
case 0x01 ->
230+
evmConfiguration.enableOptimizedOpcodes()
231+
? AddOperationOptimized.staticOperation(frame)
232+
: AddOperation.staticOperation(frame);
229233
case 0x02 -> MulOperation.staticOperation(frame);
230234
case 0x03 -> SubOperation.staticOperation(frame);
231235
case 0x04 -> DivOperation.staticOperation(frame);

evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.hyperledger.besu.evm.operation.AddModOperation;
3434
import org.hyperledger.besu.evm.operation.AddModOperationOptimized;
3535
import org.hyperledger.besu.evm.operation.AddOperation;
36+
import org.hyperledger.besu.evm.operation.AddOperationOptimized;
3637
import org.hyperledger.besu.evm.operation.AddressOperation;
3738
import org.hyperledger.besu.evm.operation.AndOperation;
3839
import org.hyperledger.besu.evm.operation.AndOperationOptimized;
@@ -194,12 +195,12 @@ private static void registerFrontierOperations(
194195
for (int i = 0; i < 255; i++) {
195196
registry.put(new InvalidOperation(i, gasCalculator));
196197
}
197-
registry.put(new AddOperation(gasCalculator));
198198
registry.put(new MulOperation(gasCalculator));
199199
registry.put(new SubOperation(gasCalculator));
200200
registry.put(new DivOperation(gasCalculator));
201201
registry.put(new SDivOperation(gasCalculator));
202202
if (evmConfiguration.enableOptimizedOpcodes()) {
203+
registry.put(new AddOperationOptimized(gasCalculator));
203204
registry.put(new ModOperationOptimized(gasCalculator));
204205
registry.put(new SModOperationOptimized(gasCalculator));
205206
registry.put(new AddModOperationOptimized(gasCalculator));
@@ -209,6 +210,7 @@ private static void registerFrontierOperations(
209210
registry.put(new OrOperationOptimized(gasCalculator));
210211
registry.put(new NotOperationOptimized(gasCalculator));
211212
} else {
213+
registry.put(new AddOperation(gasCalculator));
212214
registry.put(new ModOperation(gasCalculator));
213215
registry.put(new SModOperation(gasCalculator));
214216
registry.put(new AddModOperation(gasCalculator));

evm/src/main/java/org/hyperledger/besu/evm/UInt256.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,17 @@ public UInt256 not() {
441441
// --------------------------------------------------------------------------
442442
// endregion
443443

444+
/**
445+
* Simple addition
446+
*
447+
* @param other The UInt256 to add to this.
448+
* @return The UInt256 result from the addition
449+
*/
450+
public UInt256 add(final UInt256 other) {
451+
return new UInt256(
452+
addWithCarry(this.limbs, this.limbs.length, other.limbs, other.limbs.length));
453+
}
454+
444455
// region Support (private) Algorithms
445456
// --------------------------------------------------------------------------
446457
private static int nSetLimbs(final int[] x) {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright contributors to Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.evm.operation;
16+
17+
import org.hyperledger.besu.evm.EVM;
18+
import org.hyperledger.besu.evm.UInt256;
19+
import org.hyperledger.besu.evm.frame.MessageFrame;
20+
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
21+
22+
import org.apache.tuweni.bytes.Bytes;
23+
24+
/** The Add operation. */
25+
public class AddOperationOptimized extends AbstractFixedCostOperation {
26+
27+
/** The Add operation success result. */
28+
static final OperationResult addSuccess = new OperationResult(3, null);
29+
30+
/**
31+
* Instantiates a new Add operation.
32+
*
33+
* @param gasCalculator the gas calculator
34+
*/
35+
public AddOperationOptimized(final GasCalculator gasCalculator) {
36+
super(0x01, "ADD", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost());
37+
}
38+
39+
@Override
40+
public Operation.OperationResult executeFixedCostOperation(
41+
final MessageFrame frame, final EVM evm) {
42+
return staticOperation(frame);
43+
}
44+
45+
/**
46+
* Static operation.
47+
*
48+
* @param frame the frame
49+
* @return the operation result
50+
*/
51+
public static OperationResult staticOperation(final MessageFrame frame) {
52+
53+
final Bytes value0 = frame.popStackItem();
54+
final Bytes value1 = frame.popStackItem();
55+
56+
UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe());
57+
UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe());
58+
59+
UInt256 result = b0.add(b1);
60+
byte[] resultArray = result.toBytesBE();
61+
frame.pushStackItem(Bytes.wrap(resultArray));
62+
63+
return addSuccess;
64+
}
65+
}

evm/src/test/java/org/hyperledger/besu/evm/UInt256PropertyBasedTest.java

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,6 +1061,236 @@ void property_not_sum_with_original(@ForAll("unsigned1to32") final byte[] a) {
10611061

10621062
// endregion
10631063

1064+
// Simple ADD tests
1065+
// --------------------------------------------------------------------------
1066+
@Property
1067+
void property_add_matchesBigInteger(
1068+
@ForAll("unsigned1to32") final byte[] a, @ForAll("unsigned1to32") final byte[] b) {
1069+
// Arrange
1070+
final UInt256 ua = UInt256.fromBytesBE(a);
1071+
final UInt256 ub = UInt256.fromBytesBE(b);
1072+
1073+
// Act
1074+
final byte[] got = ua.add(ub).toBytesBE();
1075+
1076+
// Assert
1077+
BigInteger A = toBigUnsigned(a);
1078+
BigInteger B = toBigUnsigned(b);
1079+
// Addition wraps around at 2^256
1080+
byte[] expected = bigUnsignedToBytes32(A.add(B));
1081+
assertThat(got).containsExactly(expected);
1082+
}
1083+
1084+
@Property
1085+
void property_add_commutative(
1086+
@ForAll("unsigned1to32") final byte[] a, @ForAll("unsigned1to32") final byte[] b) {
1087+
// Arrange
1088+
final UInt256 ua = UInt256.fromBytesBE(a);
1089+
final UInt256 ub = UInt256.fromBytesBE(b);
1090+
1091+
// Act & Assert
1092+
assertThat(ua.add(ub)).isEqualTo(ub.add(ua));
1093+
}
1094+
1095+
@Property
1096+
void property_add_associative(
1097+
@ForAll("unsigned1to32") final byte[] a,
1098+
@ForAll("unsigned1to32") final byte[] b,
1099+
@ForAll("unsigned1to32") final byte[] c) {
1100+
// Arrange
1101+
final UInt256 ua = UInt256.fromBytesBE(a);
1102+
final UInt256 ub = UInt256.fromBytesBE(b);
1103+
final UInt256 uc = UInt256.fromBytesBE(c);
1104+
1105+
// Act & Assert
1106+
assertThat(ua.add(ub).add(uc)).isEqualTo(ua.add(ub.add(uc)));
1107+
}
1108+
1109+
@Property
1110+
void property_add_identity(@ForAll("unsigned1to32") final byte[] a) {
1111+
// Arrange
1112+
final UInt256 ua = UInt256.fromBytesBE(a);
1113+
final UInt256 zero = UInt256.ZERO;
1114+
1115+
// Act & Assert
1116+
assertThat(ua.add(zero)).isEqualTo(ua);
1117+
assertThat(zero.add(ua)).isEqualTo(ua);
1118+
}
1119+
1120+
@Property
1121+
void property_add_singleLimb_matchesBigInteger(
1122+
@ForAll("singleLimbUnsigned1to4") final byte[] a,
1123+
@ForAll("singleLimbUnsigned1to4") final byte[] b) {
1124+
// Arrange
1125+
final UInt256 ua = UInt256.fromBytesBE(a);
1126+
final UInt256 ub = UInt256.fromBytesBE(b);
1127+
1128+
// Act
1129+
final byte[] got = ua.add(ub).toBytesBE();
1130+
1131+
// Assert
1132+
BigInteger A = toBigUnsigned(a);
1133+
BigInteger B = toBigUnsigned(b);
1134+
byte[] expected = bigUnsignedToBytes32(A.add(B));
1135+
assertThat(got).containsExactly(expected);
1136+
}
1137+
1138+
@Property
1139+
void property_add_one_increment(@ForAll("unsigned1to32") final byte[] a) {
1140+
// Arrange
1141+
final UInt256 ua = UInt256.fromBytesBE(a);
1142+
final UInt256 one = UInt256.fromBytesBE(new byte[] {1});
1143+
1144+
// Act
1145+
final byte[] got = ua.add(one).toBytesBE();
1146+
1147+
// Assert
1148+
BigInteger A = toBigUnsigned(a);
1149+
byte[] expected = bigUnsignedToBytes32(A.add(BigInteger.ONE));
1150+
assertThat(got).containsExactly(expected);
1151+
}
1152+
1153+
@Property
1154+
void property_add_self_doubles(@ForAll("unsigned1to32") final byte[] a) {
1155+
// Arrange
1156+
final UInt256 ua = UInt256.fromBytesBE(a);
1157+
1158+
// Act
1159+
final byte[] got = ua.add(ua).toBytesBE();
1160+
1161+
// Assert - verify A + A = 2 * A using BigInteger
1162+
BigInteger A = toBigUnsigned(a);
1163+
byte[] expected = bigUnsignedToBytes32(A.multiply(BigInteger.TWO));
1164+
assertThat(got).containsExactly(expected);
1165+
}
1166+
1167+
@Property
1168+
void property_add_max_values() {
1169+
// Arrange - MAX_UINT256 = 2^256 - 1 (all bits set to 1)
1170+
final UInt256 max =
1171+
UInt256.fromBytesBE(
1172+
new byte[] {
1173+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1174+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1175+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1176+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1177+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1178+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1179+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1180+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF
1181+
});
1182+
final UInt256 one = UInt256.fromBytesBE(new byte[] {1});
1183+
final UInt256 zero = UInt256.ZERO;
1184+
1185+
// Act & Assert - MAX + 1 should wrap to 0
1186+
assertThat(max.add(one)).isEqualTo(zero);
1187+
1188+
// MAX + MAX should wrap to MAX - 1 (i.e., 2^256 - 2)
1189+
BigInteger maxBig = BigInteger.ONE.shiftLeft(256).subtract(BigInteger.ONE);
1190+
byte[] expMaxPlusMax = bigUnsignedToBytes32(maxBig.add(maxBig));
1191+
assertThat(max.add(max).toBytesBE()).containsExactly(expMaxPlusMax);
1192+
1193+
// Verify MAX + MAX = 2^256 - 2 (wraps around)
1194+
final UInt256 expectedMaxPlusMax =
1195+
UInt256.fromBytesBE(
1196+
new byte[] {
1197+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1198+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1199+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1200+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1201+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1202+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1203+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1204+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFE // Last byte is 0xFE
1205+
});
1206+
assertThat(max.add(max)).isEqualTo(expectedMaxPlusMax);
1207+
}
1208+
1209+
@Property
1210+
void property_add_max_with_random(@ForAll("unsigned1to32") final byte[] a) {
1211+
// Arrange - MAX_UINT256
1212+
final UInt256 max =
1213+
UInt256.fromBytesBE(
1214+
new byte[] {
1215+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1216+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1217+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1218+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1219+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1220+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1221+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1222+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF
1223+
});
1224+
final UInt256 ua = UInt256.fromBytesBE(a);
1225+
1226+
// Act
1227+
final byte[] got = max.add(ua).toBytesBE();
1228+
1229+
// Assert - verify MAX + A wraps correctly
1230+
BigInteger maxBig = BigInteger.ONE.shiftLeft(256).subtract(BigInteger.ONE);
1231+
BigInteger A = toBigUnsigned(a);
1232+
byte[] exp = bigUnsignedToBytes32(maxBig.add(A));
1233+
assertThat(got).containsExactly(exp);
1234+
1235+
// Also verify: MAX + A = A - 1 (due to wrapping)
1236+
BigInteger expectedWrapped = A.subtract(BigInteger.ONE);
1237+
if (expectedWrapped.signum() < 0) {
1238+
// If A is 0, then MAX + 0 = MAX
1239+
expectedWrapped = maxBig;
1240+
}
1241+
byte[] expWrapped = bigUnsignedToBytes32(expectedWrapped);
1242+
assertThat(got).containsExactly(expWrapped);
1243+
}
1244+
1245+
@Property
1246+
void property_add_near_max_boundary() {
1247+
// Arrange - test values near the max boundary
1248+
final UInt256 max =
1249+
UInt256.fromBytesBE(
1250+
new byte[] {
1251+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1252+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1253+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1254+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1255+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1256+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1257+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1258+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF
1259+
});
1260+
1261+
// MAX - 1
1262+
final UInt256 maxMinusOne =
1263+
UInt256.fromBytesBE(
1264+
new byte[] {
1265+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1266+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1267+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1268+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1269+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1270+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1271+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
1272+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFE
1273+
});
1274+
1275+
final UInt256 one = UInt256.fromBytesBE(new byte[] {1});
1276+
final UInt256 two = UInt256.fromBytesBE(new byte[] {2});
1277+
1278+
// Act & Assert
1279+
// (MAX - 1) + 1 = MAX
1280+
assertThat(maxMinusOne.add(one)).isEqualTo(max);
1281+
1282+
// (MAX - 1) + 2 = 0 (wraps)
1283+
assertThat(maxMinusOne.add(two)).isEqualTo(UInt256.ZERO);
1284+
1285+
// (MAX - 1) + (MAX - 1) should wrap correctly
1286+
BigInteger maxMinusOneBig = BigInteger.ONE.shiftLeft(256).subtract(BigInteger.TWO);
1287+
byte[] exp = bigUnsignedToBytes32(maxMinusOneBig.add(maxMinusOneBig));
1288+
assertThat(maxMinusOne.add(maxMinusOne).toBytesBE()).containsExactly(exp);
1289+
}
1290+
1291+
// --------------------------------------------------------------------------
1292+
// endregion
1293+
10641294
// region Utility Methods
10651295

10661296
private static byte[] clampUnsigned32(final byte[] any) {

0 commit comments

Comments
 (0)