Skip to content

Commit b0c9ced

Browse files
authored
feat(tests): add benchmarks with mass shift opcodes (#1625)
This adds the benchmarks for EVM execution focused on shift instructions. Shift instructions are executed in left-right pairs and the shift amount is taken from the value pool on the stack.
1 parent d9f5eab commit b0c9ced

File tree

1 file changed

+91
-0
lines changed

1 file changed

+91
-0
lines changed

tests/zkevm/test_worst_compute.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
import math
9+
import random
910

1011
import pytest
1112

@@ -21,6 +22,7 @@
2122
While,
2223
)
2324
from ethereum_test_tools.vm.opcode import Opcodes as Op
25+
from ethereum_test_vm import Opcode
2426

2527
REFERENCE_SPEC_GIT_PATH = "TODO"
2628
REFERENCE_SPEC_VERSION = "TODO"
@@ -553,3 +555,92 @@ def test_worst_unop(
553555
post={},
554556
blocks=[Block(txs=[tx])],
555557
)
558+
559+
560+
@pytest.mark.valid_from("Cancun")
561+
@pytest.mark.parametrize("shift_right", [Op.SHR, Op.SAR])
562+
def test_worst_shifts(
563+
blockchain_test: BlockchainTestFiller,
564+
pre: Alloc,
565+
shift_right: Op,
566+
):
567+
"""
568+
Test running a block with as many shift instructions with non-trivial arguments.
569+
This test generates left-right pairs of shifts to avoid zeroing the argument.
570+
The shift amounts are randomly pre-selected from the constant pool of 15 values on the stack.
571+
"""
572+
573+
def to_signed(x):
574+
return x if x < 2**255 else x - 2**256
575+
576+
def to_unsigned(x):
577+
return x if x >= 0 else x + 2**256
578+
579+
def shr(x, s):
580+
return x >> s
581+
582+
def shl(x, s):
583+
return x << s
584+
585+
def sar(x, s):
586+
return to_unsigned(to_signed(x) >> s)
587+
588+
match shift_right:
589+
case Op.SHR:
590+
shift_right_fn = shr
591+
case Op.SAR:
592+
shift_right_fn = sar
593+
case _:
594+
raise ValueError(f"Unexpected shift op: {shift_right}")
595+
596+
rng = random.Random(1) # Use random with a fixed seed.
597+
initial_value = 2**256 - 1 # The initial value to be shifted; should be negative for SAR.
598+
599+
# Create the list of shift amounts if length 15 (max reachable by DUPs instructions).
600+
# For the worst case keep the values small and omit values divisible by 8.
601+
shift_amounts = [x + (x >= 8) + (x >= 15) for x in range(1, 16)]
602+
603+
code_prefix = sum(Op.PUSH1[sh] for sh in shift_amounts) + Op.JUMPDEST + Op.CALLDATALOAD(0)
604+
code_suffix = Op.POP + Op.JUMP(len(shift_amounts) * 2)
605+
code_body_len = MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)
606+
607+
def select_shift_amount(shift_fn, v):
608+
"""Select a shift amount that will produce a non-zero result."""
609+
while True:
610+
index = rng.randint(0, len(shift_amounts) - 1)
611+
sh = shift_amounts[index]
612+
new_v = shift_fn(v, sh) % 2**256
613+
if new_v != 0:
614+
return new_v, index
615+
616+
def make_dup(i):
617+
"""Create a DUP instruction to get the i-th shift amount constant from the stack."""
618+
# TODO: Create a global helper for this.
619+
return Opcode(0x80 + (len(shift_amounts) - i))
620+
621+
code_body = Bytecode()
622+
v = initial_value
623+
while len(code_body) <= code_body_len - 4:
624+
v, i = select_shift_amount(shl, v)
625+
code_body += make_dup(i) + Op.SHL
626+
v, i = select_shift_amount(shift_right_fn, v)
627+
code_body += make_dup(i) + shift_right
628+
629+
code = code_prefix + code_body + code_suffix
630+
assert len(code) == MAX_CODE_SIZE - 2
631+
632+
env = Environment()
633+
634+
tx = Transaction(
635+
to=pre.deploy_contract(code=code),
636+
data=initial_value.to_bytes(32, byteorder="big"),
637+
gas_limit=env.gas_limit,
638+
sender=pre.fund_eoa(),
639+
)
640+
641+
blockchain_test(
642+
env=env,
643+
pre=pre,
644+
post={},
645+
blocks=[Block(txs=[tx])],
646+
)

0 commit comments

Comments
 (0)