diff --git a/CHANGELOG.md b/CHANGELOG.md index a68b742a8c1..466628ede96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,11 @@ are provided with different values, using input as per the execution-apis spec i - Implement `txpool_status` RPC method [#10002](https://github.com/hyperledger/besu/pull/10002) - Support [EIP-7975](https://eips.ethereum.org/EIPS/eip-7975): eth/70 - partial block receipt lists - Limit pooled tx requests by size and remove pre-eth/68 transaction announcement support [#9990](https://github.com/besu-eth/besu/pull/9990) +<<<<<<< optimize/register-based-shift +- Use cache locality to improve Shift opcodes [#9878](https://github.com/besu-eth/besu/pull/9878) +======= - Plugin API: pass pending block header when creating selectors [#10034](https://github.com/besu-eth/besu/pull/10034) +>>>>>>> main ## 26.2.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 adbea0cb47a..70815987f84 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,10 @@ 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.getLong; import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; +import static org.hyperledger.besu.evm.operation.Shift256Operations.putLong; +import static org.hyperledger.besu.evm.operation.Shift256Operations.shiftRight; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -94,30 +97,46 @@ public static OperationResult staticOperation(final MessageFrame frame) { */ private static Bytes sar256(final byte[] in, final int shift, final boolean negative) { if (shift == 0) return Bytes.wrap(in); - - final int shiftBytes = shift >>> 3; // /8 - final int shiftBits = shift & 7; // %8 - final int fill = negative ? 0xFF : 0x00; - - final byte[] out = new byte[32]; - - // Pre-fill sign-extended bytes (indices below shiftBytes are fully sign-extended) - if (negative && shiftBytes > 0) { - Arrays.fill(out, 0, shiftBytes, (byte) 0xFF); + long w0 = getLong(in, 0); + long w1 = getLong(in, 8); + long w2 = getLong(in, 16); + long w3 = getLong(in, 24); + final long fill = negative ? -1L : 0L; + // Number of whole 64-bit words to shift (shift / 64). + final int wordShift = shift >>> 6; + // Remaining intra-word bit shift (shift % 64). + final int bitShift = shift & 63; + switch (wordShift) { + case 0: + w3 = shiftRight(w3, w2, bitShift); + w2 = shiftRight(w2, w1, bitShift); + w1 = shiftRight(w1, w0, bitShift); + w0 = shiftRight(w0, fill, bitShift); + break; + case 1: + w3 = shiftRight(w2, w1, bitShift); + w2 = shiftRight(w1, w0, bitShift); + w1 = shiftRight(w0, fill, bitShift); + w0 = fill; + break; + case 2: + w3 = shiftRight(w1, w0, bitShift); + w2 = shiftRight(w0, fill, bitShift); + w1 = fill; + w0 = fill; + break; + case 3: + w3 = shiftRight(w0, fill, bitShift); + w2 = fill; + w1 = fill; + w0 = fill; + break; } - - // Only iterate bytes that receive shifted data from the input - for (int i = 31; i >= shiftBytes; i--) { - final int srcIndex = i - shiftBytes; - final int curr = in[srcIndex] & 0xFF; - if (shiftBits == 0) { - out[i] = (byte) curr; - } else { - final int prev = (srcIndex - 1 >= 0) ? (in[srcIndex - 1] & 0xFF) : fill; - out[i] = (byte) ((curr >>> shiftBits) | (prev << (8 - shiftBits))); - } - } - + final byte[] out = new byte[32]; + putLong(out, 0, w0); + putLong(out, 8, w1); + putLong(out, 16, w2); + putLong(out, 24, w3); return Bytes.wrap(out); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Shift256Operations.java index edfdab6be03..8e4075bb3b7 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,6 +14,9 @@ */ package org.hyperledger.besu.evm.operation; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; import java.util.Arrays; import org.apache.tuweni.bytes.Bytes; @@ -24,6 +27,9 @@ */ public final class Shift256Operations { + private static final VarHandle LONG_HANDLE = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + /** An array of 31 0 bytes */ private static final byte[] ZERO_31 = new byte[31]; @@ -50,4 +56,42 @@ public static boolean isShiftOverflow(final byte[] shiftBytes) { if (len <= 0) return false; return !Arrays.equals(shiftBytes, 0, len, ZERO_31, 0, len); } + + /** + * Reads a big-endian long from a byte array at the given offset. + * + * @param arr the byte array (must have at least offset + 8 bytes) + * @param offset the byte offset to read from + * @return the long value + */ + static long getLong(final byte[] arr, final int offset) { + return (long) LONG_HANDLE.get(arr, offset); + } + + /** + * Writes a big-endian long to a byte array at the given offset. + * + * @param arr the byte array (must have at least offset + 8 bytes) + * @param offset the byte offset to write to + * @param value the long value to write + */ + static void putLong(final byte[] arr, final int offset, final long value) { + LONG_HANDLE.set(arr, offset, value); + } + + /** + * Shifts a 64-bit word right and carries in bits from the previous more-significant word. + * + *

The {@code bitShift == 0} fast path avoids Java long-shift masking, where a shift by 64 is + * treated as a shift by 0. + * + * @param value the current word + * @param prevValue the previous more-significant word + * @param bitShift the intra-word shift amount in the range {@code [0..63]} + * @return the shifted word + */ + static long shiftRight(final long value, final long prevValue, final int bitShift) { + if (bitShift == 0) return value; + return (value >>> bitShift) | (prevValue << (64 - bitShift)); + } } 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 a691636e060..0b9804ae6ee 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,9 @@ */ package org.hyperledger.besu.evm.operation; +import static org.hyperledger.besu.evm.operation.Shift256Operations.getLong; import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; +import static org.hyperledger.besu.evm.operation.Shift256Operations.putLong; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -89,29 +91,63 @@ public static OperationResult staticOperation(final MessageFrame frame) { * @return the shifted 256-bit value */ private static Bytes shl256(final byte[] in, final int shift) { - if (shift == 0) { - return Bytes.wrap(in); + if (shift == 0) return Bytes.wrap(in); + long w0 = getLong(in, 0); + long w1 = getLong(in, 8); + long w2 = getLong(in, 16); + long w3 = getLong(in, 24); + + // Number of whole 64-bit words to shift (shift / 64). + final int wordShift = shift >>> 6; + // Remaining intra-word bit shift (shift % 64). + final int bitShift = shift & 63; + switch (wordShift) { + case 0: + w0 = shiftLeft(w0, w1, bitShift); + w1 = shiftLeft(w1, w2, bitShift); + w2 = shiftLeft(w2, w3, bitShift); + w3 = shiftLeft(w3, 0, bitShift); + break; + case 1: + w0 = shiftLeft(w1, w2, bitShift); + w1 = shiftLeft(w2, w3, bitShift); + w2 = shiftLeft(w3, 0, bitShift); + w3 = 0; + break; + case 2: + w0 = shiftLeft(w2, w3, bitShift); + w1 = shiftLeft(w3, 0, bitShift); + w2 = 0; + w3 = 0; + break; + case 3: + w0 = shiftLeft(w3, 0, bitShift); + w1 = 0; + w2 = 0; + w3 = 0; + break; } - - final int shiftBytes = shift >>> 3; // /8 - final int shiftBits = shift & 7; // %8 - final byte[] out = new byte[32]; - - // Shift left: bytes move to lower indices (towards index 0) - // Bytes at index >= (32 - shiftBytes) are guaranteed zero (already from new byte[32]) - final int limit = 32 - shiftBytes; - for (int i = 0; i < limit; i++) { - final int srcIndex = i + shiftBytes; - final int curr = in[srcIndex] & 0xFF; - if (shiftBits == 0) { - out[i] = (byte) curr; - } else { - final int next = (srcIndex + 1 < 32) ? (in[srcIndex + 1] & 0xFF) : 0; - out[i] = (byte) ((curr << shiftBits) | (next >>> (8 - shiftBits))); - } - } - + putLong(out, 0, w0); + putLong(out, 8, w1); + putLong(out, 16, w2); + putLong(out, 24, w3); return Bytes.wrap(out); } + + /** + * Shifts a 64-bit word left and carries in bits from the next less-significant word. + * + *

The {@code bitShift == 0} fast path avoids Java long-shift masking, where a shift by 64 is + * treated as a shift by 0. + * + * @param value the current word + * @param nextValue the next less-significant word + * @param bitShift the intra-word shift amount in the range {@code [0..63]} + * @return the shifted word + */ + private static long shiftLeft(final long value, final long nextValue, final int bitShift) { + if (bitShift == 0) return value; + return (value << bitShift) | (nextValue >>> (64 - bitShift)); + } } 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 1eeb1b1ca5c..e32bd22b875 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,10 @@ */ package org.hyperledger.besu.evm.operation; +import static org.hyperledger.besu.evm.operation.Shift256Operations.getLong; import static org.hyperledger.besu.evm.operation.Shift256Operations.isShiftOverflow; +import static org.hyperledger.besu.evm.operation.Shift256Operations.putLong; +import static org.hyperledger.besu.evm.operation.Shift256Operations.shiftRight; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -93,24 +96,48 @@ private static Bytes shr256(final byte[] in, final int shift) { return Bytes.wrap(in); } - final int shiftBytes = shift >>> 3; // /8 - final int shiftBits = shift & 7; // %8 - - final byte[] out = new byte[32]; - - // Shift right: bytes move to higher indices (towards index 31) - // Bytes below shiftBytes are guaranteed zero (already from new byte[32]) - for (int i = 31; i >= shiftBytes; i--) { - final int srcIndex = i - shiftBytes; - final int curr = in[srcIndex] & 0xFF; - if (shiftBits == 0) { - out[i] = (byte) curr; - } else { - final int prev = (srcIndex - 1 >= 0) ? (in[srcIndex - 1] & 0xFF) : 0; - out[i] = (byte) ((curr >>> shiftBits) | (prev << (8 - shiftBits))); - } + long w0 = getLong(in, 0); + long w1 = getLong(in, 8); + long w2 = getLong(in, 16); + long w3 = getLong(in, 24); + + // Number of whole 64-bit words to shift (shift / 64). + final int wordShift = shift >>> 6; + // Remaining intra-word bit shift (shift % 64). + final int bitShift = shift & 63; + + switch (wordShift) { + case 0: + w3 = shiftRight(w3, w2, bitShift); + w2 = shiftRight(w2, w1, bitShift); + w1 = shiftRight(w1, w0, bitShift); + w0 = shiftRight(w0, 0, bitShift); + break; + case 1: + w3 = shiftRight(w2, w1, bitShift); + w2 = shiftRight(w1, w0, bitShift); + w1 = shiftRight(w0, 0, bitShift); + w0 = 0; + break; + case 2: + w3 = shiftRight(w1, w0, bitShift); + w2 = shiftRight(w0, 0, bitShift); + w1 = 0; + w0 = 0; + break; + case 3: + w3 = shiftRight(w0, 0, bitShift); + w2 = 0; + w1 = 0; + w0 = 0; + break; } + final byte[] out = new byte[32]; + putLong(out, 0, w0); + putLong(out, 8, w1); + putLong(out, 16, w2); + putLong(out, 24, w3); return Bytes.wrap(out); } }