Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat[venom]: possibility of combination of both optimizations #56

Open
wants to merge 4 commits into
base: feat/venom-codesize
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions tests/unit/compiler/venom/test_literals_codesize.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
]


Expand All @@ -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 = (
Expand All @@ -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)] + [
Expand All @@ -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"
34 changes: 27 additions & 7 deletions vyper/venom/passes/literals_codesize.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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]
Expand All @@ -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
Expand Down
Loading