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]: add codesize optimization pass #4333

Open
wants to merge 3 commits into
base: master
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
5 changes: 5 additions & 0 deletions vyper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,11 @@ def evm_twos_complement(x: int) -> int:
return ((2**256 - 1) ^ x) + 1


def evm_not(val: int) -> int:
assert 0 <= val <= SizeLimits.MAX_UINT256, "Value out of bounds"
return SizeLimits.MAX_UINT256 ^ val


# EVM div semantics as a python function
def evm_div(x, y):
if y == 0:
Expand Down
5 changes: 5 additions & 0 deletions vyper/venom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
DFTPass,
MakeSSA,
Mem2Var,
ReduceLiteralsCodesize,
RemoveUnusedVariablesPass,
SimplifyCFGPass,
StoreElimination,
Expand Down Expand Up @@ -66,6 +67,10 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None:
RemoveUnusedVariablesPass(ac, fn).run_pass()

StoreExpansionPass(ac, fn).run_pass()

if optimize == OptimizationLevel.CODESIZE:
ReduceLiteralsCodesize(ac, fn).run_pass()

DFTPass(ac, fn).run_pass()


Expand Down
1 change: 1 addition & 0 deletions vyper/venom/passes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .algebraic_optimization import AlgebraicOptimizationPass
from .branch_optimization import BranchOptimizationPass
from .dft import DFTPass
from .literals_codesize import ReduceLiteralsCodesize
from .make_ssa import MakeSSA
from .mem2var import Mem2Var
from .normalization import NormalizationPass
Expand Down
47 changes: 47 additions & 0 deletions vyper/venom/passes/literals_codesize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from vyper.utils import evm_not
from vyper.venom.basicblock import IRLiteral
from vyper.venom.passes.base_pass import IRPass


class ReduceLiteralsCodesize(IRPass):
def run_pass(self):
for bb in self.function.get_basic_blocks():
self._process_bb(bb)

def _process_bb(self, bb):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For readability, it might be a good thing to add comments to "separate" the three rules the processing applies, or make them separate methods?

i = 0
while i < len(bb.instructions):
inst = bb.instructions[i]
i += 1
Comment on lines +12 to +15
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
i = 0
while i < len(bb.instructions):
inst = bb.instructions[i]
i += 1
for inst in bb.instructions:

if inst.opcode != "store":
continue

op = inst.operands[0]
if not isinstance(op, IRLiteral):
continue

val = op.value

if val == (2**256 - 1):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make use of constants that we have for numbers like these

inst.opcode = "not"
op.value = 0
continue

# TODO: fuse these two rules?

# transform things like 0xffff...01 to (not 0xfe)
binz = bin(val)[2:]
if (ix := binz.find("0")) > 8: # `not` is 1 byte
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, extract 8 and 24 to constants like BITS_FOR_SHL and BITS_FOR_NOT?

inst.opcode = "not"
op.value = evm_not(val)
continue

if (ix := len(binz) - binz.rfind("1")) > 24: # shl is 3 bytes
ix -= 1
inst.opcode = "shl"
# sanity check
assert (val >> ix) << ix == val, val
assert (val >> ix) & 1 == 1

inst.operands = [IRLiteral(val >> ix), IRLiteral(ix)]
Comment on lines +41 to +46
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inst.opcode = "shl"
# sanity check
assert (val >> ix) << ix == val, val
assert (val >> ix) & 1 == 1
inst.operands = [IRLiteral(val >> ix), IRLiteral(ix)]
# sanity check
assert (val >> ix) << ix == val, val
assert (val >> ix) & 1 == 1
inst.opcode = "shl"
inst.operands = [IRLiteral(val >> ix), IRLiteral(ix)]

continue
8 changes: 2 additions & 6 deletions vyper/venom/passes/sccp/eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
SizeLimits,
evm_div,
evm_mod,
evm_not,
evm_pow,
signed_to_unsigned,
unsigned_to_signed,
Expand Down Expand Up @@ -95,11 +96,6 @@ def _evm_sar(shift_len: int, value: int) -> int:
return value >> shift_len


def _evm_not(value: int) -> int:
assert 0 <= value <= SizeLimits.MAX_UINT256, "Value out of bounds"
return SizeLimits.MAX_UINT256 ^ value


ARITHMETIC_OPS: dict[str, Callable[[list[IROperand]], int]] = {
"add": _wrap_binop(operator.add),
"sub": _wrap_binop(operator.sub),
Expand All @@ -122,7 +118,7 @@ def _evm_not(value: int) -> int:
"or": _wrap_binop(operator.or_),
"and": _wrap_binop(operator.and_),
"xor": _wrap_binop(operator.xor),
"not": _wrap_unop(_evm_not),
"not": _wrap_unop(evm_not),
"signextend": _wrap_binop(_evm_signextend),
"iszero": _wrap_unop(_evm_iszero),
"shr": _wrap_binop(_evm_shr),
Expand Down
Loading