diff --git a/tests/unit/compiler/venom/test_literals_codesize.py b/tests/unit/compiler/venom/test_literals_codesize.py index 53a519b70f..c659bc791d 100644 --- a/tests/unit/compiler/venom/test_literals_codesize.py +++ b/tests/unit/compiler/venom/test_literals_codesize.py @@ -22,10 +22,14 @@ def test_literal_codesize_ff_inversion(orig_value): assert bb.instructions[0].opcode == "not" assert evm_not(bb.instructions[0].operands[0].value) == orig_value + assert bb.instructions[1].opcode == "stop" -should_not_invert = [1, 0xFE << 248 | (2**248 - 1)] + [ - ((2**255 - 1) >> i) << i for i in range(0, 3 * 8) +should_not_invert = [ + # goes from one since the first one can be optimized with + # combination of two optimizations + ((2**255 - 1) >> i) << i + for i in range(1, 3 * 8) ] @@ -42,6 +46,7 @@ def test_literal_codesize_no_inversion(orig_value): assert bb.instructions[0].opcode == "store" assert bb.instructions[0].operands[0].value == orig_value + assert bb.instructions[1].opcode == "stop" should_shl = ( @@ -65,6 +70,7 @@ def test_literal_codesize_shl(orig_value): assert bb.instructions[0].opcode == "shl" op0, op1 = bb.instructions[0].operands assert op0.value << op1.value == orig_value + assert bb.instructions[1].opcode == "stop" should_not_shl = [1 << i for i in range(0, 3 * 8)] + [ @@ -86,3 +92,47 @@ def test_literal_codesize_no_shl(orig_value): assert bb.instructions[0].opcode == "store" assert bb.instructions[0].operands[0].value == orig_value + assert bb.instructions[1].opcode == "stop" + + +should_do_both = [(2**256 - 1) ^ (1 << i) for i in range(4 * 8, 256)] + + +@pytest.mark.parametrize("orig_value", should_do_both) +def test_literal_codesize_both(orig_value): + ctx = IRContext() + fn = ctx.create_function("_global") + bb = fn.get_basic_block() + + bb.append_instruction("store", IRLiteral(orig_value)) + bb.append_instruction("stop") + + ac = IRAnalysesCache(fn) + ReduceLiteralsCodesize(ac, fn).run_pass() + + assert bb.instructions[0].opcode == "shl" + op0, op1 = bb.instructions[0].operands + assert evm_not(op0.value << op1.value) == orig_value + assert bb.instructions[1].opcode == "not" + assert bb.instructions[1].operands[0] == bb.instructions[0].output + assert bb.instructions[2].opcode == "stop" + + +should_not_do_any = [(2**256 - 1) ^ (1 << i) ^ 1 ^ 2**255 for i in range(4 * 8, 255)] + + +@pytest.mark.parametrize("orig_value", should_not_do_any) +def test_literal_codesize_nothing(orig_value): + ctx = IRContext() + fn = ctx.create_function("_global") + bb = fn.get_basic_block() + + bb.append_instruction("store", IRLiteral(orig_value)) + bb.append_instruction("stop") + + ac = IRAnalysesCache(fn) + ReduceLiteralsCodesize(ac, fn).run_pass() + + assert bb.instructions[0].opcode == "store" + assert bb.instructions[0].operands[0].value == orig_value + assert bb.instructions[1].opcode == "stop" diff --git a/vyper/venom/passes/literals_codesize.py b/vyper/venom/passes/literals_codesize.py index 5bdc1f0986..808433f418 100644 --- a/vyper/venom/passes/literals_codesize.py +++ b/vyper/venom/passes/literals_codesize.py @@ -1,5 +1,5 @@ from vyper.utils import evm_not -from vyper.venom.basicblock import IRLiteral +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLiteral from vyper.venom.passes.base_pass import IRPass # not takes 1 byte1, so it makes sense to use it when we can save at least @@ -16,7 +16,7 @@ def run_pass(self): for bb in self.function.get_basic_blocks(): self._process_bb(bb) - def _process_bb(self, bb): + def _process_bb(self, bb: IRBasicBlock): i = 0 while i < len(bb.instructions): inst = bb.instructions[i] @@ -38,18 +38,38 @@ def _process_bb(self, bb): ix = len(binz) - binz.rfind("1") shl_benefit = ix - SHL_THRESHOLD * 8 - if not_benefit <= 0 and shl_benefit <= 0: + # calculate amount of bits saved by both optimizations put together + negated = evm_not(val) + negated_binz = bin(negated)[2:] + negated_ix = len(negated_binz) - negated_binz.rfind("1") + not_then_shl_size = ( + (len(hex(negated)) - 2) * 4 - negated_ix + 1 + NOT_THRESHOLD * 8 + SHL_THRESHOLD * 8 + ) + not_the_shl_benefit = (len(hex(val)) - 2) * 4 - not_then_shl_size + + if not_benefit <= 0 and shl_benefit <= 0 and not_the_shl_benefit <= 0: # no optimization can be done here continue - - if not_benefit >= shl_benefit: - assert not_benefit > 0 # implied by previous conditions + if not_the_shl_benefit > not_benefit and not_the_shl_benefit > shl_benefit: + negated_ix -= 1 + # sanity check + assert (negated >> negated_ix) << negated_ix == negated, negated + assert (negated >> negated_ix) & 1 == 1, negated + index = bb.instructions.index(inst) + var = bb.parent.get_next_variable() + new_inst = IRInstruction( + "shl", [IRLiteral(negated >> negated_ix), IRLiteral(negated_ix)], output=var + ) + bb.insert_instruction(new_inst, index) + inst.opcode = "not" + inst.operands = [var] + continue + elif not_benefit >= shl_benefit: # transform things like 0xffff...01 to (not 0xfe) inst.opcode = "not" op.value = evm_not(val) continue else: - assert shl_benefit > 0 # implied by previous conditions # transform things like 0x123400....000 to 0x1234 << ... ix -= 1 # sanity check