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 load elimination #4265

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
134 changes: 134 additions & 0 deletions tests/unit/compiler/venom/test_load_elimination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from vyper.venom.analysis.analysis import IRAnalysesCache
from vyper.venom.basicblock import IRLiteral, IRVariable
from vyper.venom.context import IRContext
from vyper.venom.passes.load_elimination import LoadElimination


def test_simple_load_elimination():
ctx = IRContext()
fn = ctx.create_function("test")

bb = fn.get_basic_block()

ptr = IRLiteral(11)
bb.append_instruction("mload", ptr)
bb.append_instruction("mload", ptr)
bb.append_instruction("stop")

ac = IRAnalysesCache(fn)
LoadElimination(ac, fn).run_pass()

assert len([inst for inst in bb.instructions if inst.opcode == "mload"]) == 1

inst0, inst1, inst2 = bb.instructions

assert inst0.opcode == "mload"
assert inst1.opcode == "store"
assert inst1.operands[0] == inst0.output
assert inst2.opcode == "stop"


def test_equivalent_var_elimination():
ctx = IRContext()
fn = ctx.create_function("test")

bb = fn.get_basic_block()

ptr1 = bb.append_instruction("store", IRLiteral(11))
ptr2 = bb.append_instruction("store", ptr1)
bb.append_instruction("mload", ptr1)
bb.append_instruction("mload", ptr2)
bb.append_instruction("stop")

ac = IRAnalysesCache(fn)
LoadElimination(ac, fn).run_pass()

assert len([inst for inst in bb.instructions if inst.opcode == "mload"]) == 1

inst0, inst1, inst2, inst3, inst4 = bb.instructions

assert inst0.opcode == "store"
assert inst1.opcode == "store"
assert inst2.opcode == "mload"
assert inst2.operands[0] == inst0.output
assert inst3.opcode == "store"
assert inst3.operands[0] == inst2.output
assert inst4.opcode == "stop"


def test_elimination_barrier():
ctx = IRContext()
fn = ctx.create_function("test")

bb = fn.get_basic_block()

ptr = IRLiteral(11)
bb.append_instruction("mload", ptr)

arbitrary = IRVariable("%100")
# fence, writes to memory
bb.append_instruction("staticcall", arbitrary, arbitrary, arbitrary, arbitrary)

bb.append_instruction("mload", ptr)
bb.append_instruction("stop")

ac = IRAnalysesCache(fn)

instructions = bb.instructions.copy()
LoadElimination(ac, fn).run_pass()

assert instructions == bb.instructions # no change


def test_store_load_elimination():
ctx = IRContext()
fn = ctx.create_function("test")

bb = fn.get_basic_block()

val = IRLiteral(55)
ptr1 = bb.append_instruction("store", IRLiteral(11))
ptr2 = bb.append_instruction("store", ptr1)
bb.append_instruction("mstore", val, ptr1)
bb.append_instruction("mload", ptr2)
bb.append_instruction("stop")

ac = IRAnalysesCache(fn)
LoadElimination(ac, fn).run_pass()

assert len([inst for inst in bb.instructions if inst.opcode == "mload"]) == 0

inst0, inst1, inst2, inst3, inst4 = bb.instructions

assert inst0.opcode == "store"
assert inst1.opcode == "store"
assert inst2.opcode == "mstore"
assert inst3.opcode == "store"
assert inst3.operands[0] == inst2.operands[0]
assert inst4.opcode == "stop"


def test_store_load_barrier():
ctx = IRContext()
fn = ctx.create_function("test")

bb = fn.get_basic_block()

val = IRLiteral(55)
ptr1 = bb.append_instruction("store", IRLiteral(11))
ptr2 = bb.append_instruction("store", ptr1)
bb.append_instruction("mstore", val, ptr1)

arbitrary = IRVariable("%100")
# fence, writes to memory
bb.append_instruction("staticcall", arbitrary, arbitrary, arbitrary, arbitrary)

bb.append_instruction("mload", ptr2)
bb.append_instruction("stop")

ac = IRAnalysesCache(fn)

instructions = bb.instructions.copy()
LoadElimination(ac, fn).run_pass()

assert instructions == bb.instructions
4 changes: 4 additions & 0 deletions vyper/venom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
AlgebraicOptimizationPass,
BranchOptimizationPass,
DFTPass,
LoadElimination,
MakeSSA,
Mem2Var,
RemoveUnusedVariablesPass,
Expand Down Expand Up @@ -52,8 +53,11 @@
Mem2Var(ac, fn).run_pass()
MakeSSA(ac, fn).run_pass()
SCCP(ac, fn).run_pass()

StoreElimination(ac, fn).run_pass()
SimplifyCFGPass(ac, fn).run_pass()
LoadElimination(ac, fn).run_pass()

Check warning on line 59 in vyper/venom/__init__.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/__init__.py#L59

Added line #L59 was not covered by tests

AlgebraicOptimizationPass(ac, fn).run_pass()
# NOTE: MakeSSA is after algebraic optimization it currently produces
# smaller code by adding some redundant phi nodes. This is not a
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 .load_elimination import LoadElimination
from .make_ssa import MakeSSA
from .mem2var import Mem2Var
from .normalization import NormalizationPass
Expand Down
73 changes: 73 additions & 0 deletions vyper/venom/passes/load_elimination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from vyper.venom.analysis import DFGAnalysis, LivenessAnalysis, VarEquivalenceAnalysis
from vyper.venom.passes.base_pass import IRPass


class LoadElimination(IRPass):
"""
Eliminate sloads, mloads and tloads
"""

# should this be renamed to EffectsElimination?

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

Check warning on line 13 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L13

Added line #L13 was not covered by tests

for bb in self.function.get_basic_blocks():
self._process_bb(bb)

Check warning on line 16 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L16

Added line #L16 was not covered by tests

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

Check warning on line 19 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L18-L19

Added lines #L18 - L19 were not covered by tests

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

Check warning on line 22 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L22

Added line #L22 was not covered by tests

def _process_bb(self, bb):
transient = ()
storage = ()
memory = ()

Check warning on line 27 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L25-L27

Added lines #L25 - L27 were not covered by tests

for inst in bb.instructions:
if "memory" in inst.get_write_effects():
memory = ()

Check warning on line 31 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L31

Added line #L31 was not covered by tests
if "storage" in inst.get_write_effects():
storage = ()

Check warning on line 33 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L33

Added line #L33 was not covered by tests
if "transient" in inst.get_write_effects():
transient = ()

Check warning on line 35 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L35

Added line #L35 was not covered by tests

if inst.opcode == "mstore":
# mstore [val, ptr]
memory = (inst.operands[1], inst.operands[0])

Check warning on line 39 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L39

Added line #L39 was not covered by tests
if inst.opcode == "sstore":
storage = (inst.operands[1], inst.operands[0])

Check warning on line 41 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L41

Added line #L41 was not covered by tests
if inst.opcode == "tstore":
transient = (inst.operands[1], inst.operands[0])

Check warning on line 43 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L43

Added line #L43 was not covered by tests

if inst.opcode == "mload":
prev_memory = memory
memory = (inst.operands[0], inst.output)

Check warning on line 47 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L46-L47

Added lines #L46 - L47 were not covered by tests
if not prev_memory:
continue

Check warning on line 49 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L49

Added line #L49 was not covered by tests
if not self.equivalent(inst.operands[0], prev_memory[0]):
continue
inst.opcode = "store"
inst.operands = [prev_memory[1]]

Check warning on line 53 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L51-L53

Added lines #L51 - L53 were not covered by tests

if inst.opcode == "sload":
prev_storage = storage
storage = (inst.operands[0], inst.output)

Check warning on line 57 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L56-L57

Added lines #L56 - L57 were not covered by tests
if not prev_storage:
continue

Check warning on line 59 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L59

Added line #L59 was not covered by tests
if not self.equivalent(inst.operands[0], prev_storage[0]):
continue
inst.opcode = "store"
inst.operands = [prev_storage[1]]

Check warning on line 63 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L61-L63

Added lines #L61 - L63 were not covered by tests

if inst.opcode == "tload":
prev_transient = transient
transient = (inst.operands[0], inst.output)

Check warning on line 67 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L66-L67

Added lines #L66 - L67 were not covered by tests
if not prev_transient:
continue

Check warning on line 69 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L69

Added line #L69 was not covered by tests
if not self.equivalent(inst.operands[0], prev_transient[0]):
continue
inst.opcode = "store"
inst.operands = [prev_transient[1]]

Check warning on line 73 in vyper/venom/passes/load_elimination.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/load_elimination.py#L71-L73

Added lines #L71 - L73 were not covered by tests
Loading