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]: improve load elimination #4407

Draft
wants to merge 33 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f76b812
add load elimination pass
charles-cooper Sep 29, 2024
9b105d4
handle effects
charles-cooper Sep 29, 2024
f7035ce
fix analysis invalidation, add tests
charles-cooper Sep 29, 2024
e448d7c
add a note
charles-cooper Sep 29, 2024
6923199
fix lint
charles-cooper Oct 19, 2024
4834318
Merge branch 'master' into feat/load-elimination
harkal Oct 29, 2024
729bd19
upgrade to latest effects lib
harkal Oct 29, 2024
9d10858
Merge branch 'master' into feat/load-elimination
charles-cooper Nov 23, 2024
ce5fe9a
Update vyper/venom/passes/load_elimination.py
charles-cooper Nov 26, 2024
59a7230
Merge branch 'master' into feat/load-elimination
charles-cooper Nov 26, 2024
6faffba
rewrite some assignments
charles-cooper Dec 18, 2024
7e67157
Merge branch 'master' into feat/load-elimination
charles-cooper Dec 18, 2024
fdc8eaa
rewrite tests using new machinery
charles-cooper Dec 18, 2024
740b18c
Merge branch 'master' into feat/load-elimination
charles-cooper Dec 18, 2024
0f03a49
simplify code
charles-cooper Dec 18, 2024
cac7f8a
reorder passes
charles-cooper Dec 20, 2024
31fd7bf
Merge branch 'master' into feat/load-elimination
charles-cooper Dec 20, 2024
3ca7198
Merge branch 'master' into feat/load-elimination
charles-cooper Dec 20, 2024
a67683d
Merge branch 'master' into feat/load-elimination
charles-cooper Dec 20, 2024
c4aa761
lint
charles-cooper Dec 20, 2024
012c62f
add some comments
charles-cooper Dec 20, 2024
8193417
Merge branch 'master' into feat/load-elimination
charles-cooper Dec 20, 2024
352b10f
fix lint
charles-cooper Dec 20, 2024
c4e7026
allow peering through to original value
charles-cooper Dec 18, 2024
0155857
use proper lattice
charles-cooper Dec 18, 2024
a9d1725
Merge branch 'master' into feat/advanced-load-elimination
charles-cooper Dec 20, 2024
f4a827a
fix lint, tests
charles-cooper Dec 20, 2024
d2bcb09
fix another test
charles-cooper Dec 20, 2024
d6741a1
add dload elimination
charles-cooper Dec 21, 2024
aee9e76
Merge branch 'master' into feat/advanced-load-elimination
charles-cooper Jan 12, 2025
226b9c9
use store elim instead of var equivalence
charles-cooper Jan 12, 2025
55cbf90
roll back var equivalence changes
charles-cooper Jan 12, 2025
2bf3d88
Merge branch 'master' into feat/advanced-load-elimination
charles-cooper Jan 20, 2025
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
2 changes: 2 additions & 0 deletions tests/functional/codegen/types/test_dynamic_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
CompilerPanic,
ImmutableViolation,
OverflowException,
StackTooDeep,
StateAccessViolation,
StaticAssertionException,
TypeMismatch,
Expand Down Expand Up @@ -290,6 +291,7 @@ def test_array(x: int128, y: int128, z: int128, w: int128) -> int128:
assert c.test_array(2, 7, 1, 8) == -5454


@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression")
def test_four_d_array_accessor(get_contract):
four_d_array_accessor = """
@external
Expand Down
3 changes: 2 additions & 1 deletion tests/functional/codegen/types/test_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from tests.utils import check_precompile_asserts, decimal_to_int
from vyper.compiler.settings import OptimizationLevel
from vyper.evm.opcodes import version_check
from vyper.exceptions import ArrayIndexException, OverflowException, TypeMismatch
from vyper.exceptions import ArrayIndexException, OverflowException, StackTooDeep, TypeMismatch


def _map_nested(f, xs):
Expand Down Expand Up @@ -193,6 +193,7 @@ def test_array(x: int128, y: int128, z: int128, w: int128) -> int128:
assert c.test_array(2, 7, 1, 8) == -5454


@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression")
def test_four_d_array_accessor(get_contract):
four_d_array_accessor = """
@external
Expand Down
83 changes: 66 additions & 17 deletions vyper/venom/passes/load_elimination.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
from typing import Optional

from vyper.venom.analysis import DFGAnalysis, LivenessAnalysis, VarEquivalenceAnalysis
from vyper.venom.basicblock import IRLiteral
from vyper.venom.effects import Effects
from vyper.venom.passes.base_pass import IRPass


def _conflict(store_opcode: str, k1: IRLiteral, k2: IRLiteral):
ptr1, ptr2 = k1.value, k2.value
# hardcode the size of store opcodes for now. maybe refactor to use
# vyper.evm.address_space
if store_opcode == "mstore":
return abs(ptr1 - ptr2) < 32
assert store_opcode in ("sstore", "tstore"), "unhandled store opcode"
return abs(ptr1 - ptr2) < 1


class LoadElimination(IRPass):
"""
Eliminate sloads, mloads and tloads
Expand All @@ -11,40 +24,76 @@ class LoadElimination(IRPass):
# should this be renamed to EffectsElimination?

def run_pass(self):
self.equivalence = self.analyses_cache.request_analysis(VarEquivalenceAnalysis)

for bb in self.function.get_basic_blocks():
self._process_bb(bb, Effects.MEMORY, "mload", "mstore")
self._process_bb(bb, Effects.TRANSIENT, "tload", "tstore")
self._process_bb(bb, Effects.STORAGE, "sload", "sstore")
self._process_bb(bb, None, "dload", None)

self.analyses_cache.invalidate_analysis(LivenessAnalysis)
self.analyses_cache.invalidate_analysis(DFGAnalysis)
self.analyses_cache.invalidate_analysis(VarEquivalenceAnalysis)

def equivalent(self, op1, op2):
return op1 == op2 or self.equivalence.equivalent(op1, op2)
return op1 == op2

def get_literal(self, op):
if isinstance(op, IRLiteral):
return op
return None

def _process_bb(self, bb, eff, load_opcode, store_opcode):
# not really a lattice even though it is not really inter-basic block;
# we may generalize in the future
lattice = ()
self._lattice = {}

for inst in bb.instructions:
if eff in inst.get_write_effects():
lattice = ()

if inst.opcode == store_opcode:
# mstore [val, ptr]
val, ptr = inst.operands
lattice = (ptr, val)

if inst.opcode == load_opcode:
prev_lattice = lattice
known_ptr: Optional[IRLiteral] = self.get_literal(ptr)
if known_ptr is None:
# flush the lattice
self._lattice = {ptr: val}
else:
# we found a redundant store, eliminate it
existing_val = self._lattice.get(known_ptr)
if self.equivalent(val, existing_val):
inst.opcode = "nop"
inst.output = None
inst.operands = []
continue

self._lattice[known_ptr] = val

# kick out any conflicts
for existing_key in self._lattice.copy().keys():
if not isinstance(existing_key, IRLiteral):
# flush the whole thing
self._lattice = {known_ptr: val}
break

if _conflict(store_opcode, known_ptr, existing_key):
del self._lattice[existing_key]
self._lattice[known_ptr] = val

elif eff is not None and eff in inst.get_write_effects():
self._lattice = {}

elif inst.opcode == load_opcode:
(ptr,) = inst.operands
lattice = (ptr, inst.output)
if not prev_lattice:
continue
if not self.equivalent(ptr, prev_lattice[0]):
continue
inst.opcode = "store"
inst.operands = [prev_lattice[1]]
known_ptr = self.get_literal(ptr)
if known_ptr is not None:
ptr = known_ptr

existing_value = self._lattice.get(ptr)

assert inst.output is not None # help mypy

# "cache" the value for future load instructions
self._lattice[ptr] = inst.output

if existing_value is not None:
inst.opcode = "store"
inst.operands = [existing_value]
Loading