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]: mark loads as non-volatile #4388

Open
wants to merge 49 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
51cbd35
feat[venom]: mark loads as non-volatile
charles-cooper Dec 4, 2024
9befa12
handle msize
charles-cooper Dec 7, 2024
863869e
Merge branch 'master' into feat/improve-volatile
charles-cooper Dec 7, 2024
fa85193
fix msize effects
charles-cooper Dec 7, 2024
ea2ba1d
fix typo
charles-cooper Dec 7, 2024
e3575a0
add a note
charles-cooper Dec 7, 2024
12b9604
improve the analysis
charles-cooper Dec 7, 2024
0baece8
typo
charles-cooper Dec 7, 2024
00d4f6f
fix: defaultdict
charles-cooper Dec 7, 2024
d8f6a2c
fix the analysis and the pass
charles-cooper Dec 7, 2024
2b95c71
basic tests
HodanPlodky Dec 9, 2024
22de2e3
loop test
HodanPlodky Dec 9, 2024
14894a3
unused msize
HodanPlodky Dec 9, 2024
f071b11
test without the msize
HodanPlodky Dec 9, 2024
6cef92b
fix of the order msize
HodanPlodky Dec 9, 2024
b9027fc
Merge pull request #53 from HodanPlodky/feat/improve-volatile
charles-cooper Dec 9, 2024
799964f
clean up for range
charles-cooper Dec 9, 2024
2433a4a
fix: remove msize from index
charles-cooper Dec 9, 2024
7235a13
swipe mark_for_removal
charles-cooper Dec 9, 2024
0fe354a
small fixes
charles-cooper Dec 9, 2024
7e8aae5
handle memory effect in loop
charles-cooper Dec 9, 2024
3b68860
Fix type hint
charles-cooper Dec 9, 2024
7196c56
test for msize in branches
HodanPlodky Dec 9, 2024
4ffdf9d
fix jmp => jnz
HodanPlodky Dec 9, 2024
2beca35
Merge pull request #54 from HodanPlodky/feat/improve-volatile
charles-cooper Dec 9, 2024
b35d0ea
fix some bad logic
charles-cooper Dec 9, 2024
a0d1b3b
Merge branch 'master' into feat/improve-volatile
charles-cooper Dec 18, 2024
303414e
update tests with new machinery
charles-cooper Dec 18, 2024
04c31d6
fix typos
charles-cooper Dec 18, 2024
d10df16
fix lint
charles-cooper Dec 18, 2024
08bec15
update comments
charles-cooper Dec 18, 2024
578abe4
rename a variable and add more comments
charles-cooper Dec 18, 2024
ebbde05
add another test
charles-cooper Dec 18, 2024
0171aab
move around tests, add test comments
charles-cooper Dec 18, 2024
79eb372
Merge branch 'master' into feat/improve-volatile
charles-cooper Dec 18, 2024
ae59fee
fix lint
charles-cooper Dec 20, 2024
d15e8a7
Merge branch 'master' into feat/improve-volatile
charles-cooper Dec 20, 2024
0223869
add another test
charles-cooper Dec 20, 2024
69ac671
Merge branch 'master' into feat/improve-volatile
charles-cooper Dec 20, 2024
476507c
Merge branch 'master' into feat/improve-volatile
charles-cooper Dec 22, 2024
d580116
simplify remove_unused_variables, add itouch instruction
charles-cooper Dec 22, 2024
e487909
fix lint
charles-cooper Dec 30, 2024
7544544
rename a variable
charles-cooper Dec 30, 2024
0ad9a33
update tests - don't need as many tricky mload/msize test cases
charles-cooper Dec 30, 2024
353eb7c
Merge branch 'master' into feat/improve-volatile
charles-cooper Dec 30, 2024
8944ba1
add itouch to PASS_THRU_INSTRUCTIONS
charles-cooper Dec 31, 2024
c8290ae
Merge branch 'master' into feat/improve-volatile
charles-cooper Jan 6, 2025
d1151a8
fix lint
charles-cooper Jan 6, 2025
9f8f9f9
Merge branch 'master' into feat/improve-volatile
charles-cooper Jan 9, 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
211 changes: 211 additions & 0 deletions tests/unit/compiler/venom/test_removeunused.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
from tests.venom_utils import assert_ctx_eq, parse_from_basic_block, parse_venom
from vyper.venom.analysis.analysis import IRAnalysesCache
from vyper.venom.passes import RemoveUnusedVariablesPass


def _check_pre_post(pre, post, scope="basicblock"):
if scope == "basicblock":
parse = parse_from_basic_block
else:
parse = parse_venom

ctx = parse(pre)
for fn in ctx.functions.values():
ac = IRAnalysesCache(fn)
RemoveUnusedVariablesPass(ac, fn).run_pass()

assert_ctx_eq(ctx, parse(post))


def _check_no_change(pre, scope="basicblock"):
_check_pre_post(pre, pre, scope=scope)


def test_removeunused_basic():
"""
Check basic unused variable removal
"""
pre = """
main:
%1 = add 10, 20
%2_unused = add 10, %1
mstore 20, %1
stop
"""
post = """
main:
%1 = add 10, 20
mstore 20, %1
stop
"""
_check_pre_post(pre, post)


def test_removeunused_chain():
"""
Check removal of unused variable dependency chain
"""
pre = """
main:
%1 = add 10, 20
%2_unused = add 10, %1
%3_unused = add 10, %2_unused
mstore 20, %1
stop
"""
post = """
main:
%1 = add 10, 20
mstore 20, %1
stop
"""
_check_pre_post(pre, post)


def test_removeunused_loop():
"""
Test unused variable removal in loop
"""
pre = """
main:
%1 = 10
jmp @after
after:
%p = phi @main, %1, @after, %2
%2 = add %p, 1
%3_unused = add %2, %p
mstore 10, %2
jmp @after
"""
post = """
main:
%1 = 10
jmp @after
after:
%p = phi @main, %1, @after, %2
%2 = add %p, 1
mstore 10, %2
jmp @after
"""
_check_pre_post(pre, post)


def test_removeunused_mload_basic():
pre = """
main:
%a = mload 32
%b = msize
%c_unused = mload 64 # safe to remove
return %b, %b
"""
post = """
main:
%a = mload 32
%b = msize
return %b, %b
"""
_check_pre_post(pre, post)


def test_removeunused_mload_two_msizes():
pre = """
main:
%a = mload 32
%b = msize
%c = mload 64 # not safe to remove - has MSIZE effect
%d = msize
return %b, %d
"""
_check_no_change(pre)


def test_removeunused_msize_loop():
pre = """
main:
%1 = msize

# not safe to remove because the previous instruction is
# still reachable
%2 = mload %1

jmp @main
"""
_check_no_change(pre)


def test_removeunused_msize_reachable():
pre = """
main:
# not safe to remove because there is an msize in a reachable
# basic block
%1 = mload 0

jmp @next
next:
jmp @last
last:
%2 = msize
return %2, %2
"""
_check_no_change(pre)


def test_removeunused_msize_branches():
"""
Test that mload removal is blocked by msize in a downstream basic
block
"""
pre = """
function global {
main:
%1 = param
%2 = mload 10 ; looks unused, but has MSIZE effect
jnz %1, @branch1, @branch2
branch1:
%3 = msize ; blocks removal of `%2 = mload 10`
mstore 10, %3
jmp @end
branch2:
jmp @end
end:
stop
}
"""
_check_no_change(pre, scope="function")


def test_remove_unused_mload_msize_chain():
"""
Test effect chain removal - remove mload which is initially blocked by
an msize but is free to be removed after the msize is removed.
"""
pre = """
main:
%1_unused = mload 10
%2_unused = msize
stop
"""
post = """
main:
stop
"""
_check_pre_post(pre, post)


def test_remove_unused_mload_msize_chain_loop():
"""
Test effect chain removal - remove mload which is initially blocked by
an msize but is free to be removed after the msize is removed.
Loop version.
"""
pre = """
main:
%1_unused = msize
%2_unused = mload 10
jmp @main
"""
post = """
main:
jmp @main
"""
_check_pre_post(pre, post)
2 changes: 1 addition & 1 deletion vyper/codegen/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@
# assumption in general: (mload X) => msize == ceil32(X + 32)
# see py-evm extend_memory: after_size = ceil32(start_position + size)
if immutables_len > 0:
deploy_code.append(["iload", max(0, immutables_len - 32)])
deploy_code.append(["itouch", max(0, immutables_len - 32)])

Check warning on line 503 in vyper/codegen/module.py

View check run for this annotation

Codecov / codecov/patch

vyper/codegen/module.py#L503

Added line #L503 was not covered by tests

deploy_code.append(init_func_ir)
deploy_code.append(["deploy", init_mem_used, runtime, immutables_len])
Expand Down
1 change: 1 addition & 0 deletions vyper/evm/opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
"DEBUGGER": (None, 0, 0, 0),
"ILOAD": (None, 1, 1, 6),
"ISTORE": (None, 2, 0, 6),
"ITOUCH": (None, 1, 0, 6),
"DLOAD": (None, 1, 1, 9),
"DLOADBYTES": (None, 3, 0, 3),
}
Expand Down
11 changes: 11 additions & 0 deletions vyper/ir/compile_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,17 @@

return o

elif code.value == "itouch":
loc = code.args[0]

Check warning on line 337 in vyper/ir/compile_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/ir/compile_ir.py#L337

Added line #L337 was not covered by tests

o = []
o.extend(_data_ofst_of("_mem_deploy_end", loc, height))
o.append("MLOAD")
o.append("POP")

Check warning on line 342 in vyper/ir/compile_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/ir/compile_ir.py#L339-L342

Added lines #L339 - L342 were not covered by tests

return o

Check warning on line 344 in vyper/ir/compile_ir.py

View check run for this annotation

Codecov / codecov/patch

vyper/ir/compile_ir.py#L344

Added line #L344 was not covered by tests


# "mstore" to the data section of (to-be-deployed) runtime code
elif code.value == "istore":
loc = code.args[0]
Expand Down
3 changes: 3 additions & 0 deletions vyper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
for item in iterable:
self._data.pop(item, None)

def clear(self):
self._data.clear()

Check warning on line 79 in vyper/utils.py

View check run for this annotation

Codecov / codecov/patch

vyper/utils.py#L79

Added line #L79 was not covered by tests

def difference(self, other):
ret = self.copy()
ret.dropmany(other)
Expand Down
1 change: 1 addition & 0 deletions vyper/venom/analysis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from .dominators import DominatorTreeAnalysis
from .equivalent_vars import VarEquivalenceAnalysis
from .liveness import LivenessAnalysis
from .reachable import ReachableAnalysis
8 changes: 7 additions & 1 deletion vyper/venom/analysis/cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,16 @@
return iter(self._dfs)

def invalidate(self):
from vyper.venom.analysis import DFGAnalysis, DominatorTreeAnalysis, LivenessAnalysis
from vyper.venom.analysis import (

Check warning on line 54 in vyper/venom/analysis/cfg.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/cfg.py#L54

Added line #L54 was not covered by tests
DFGAnalysis,
DominatorTreeAnalysis,
LivenessAnalysis,
ReachableAnalysis,
)

self.analyses_cache.invalidate_analysis(DominatorTreeAnalysis)
self.analyses_cache.invalidate_analysis(LivenessAnalysis)
self.analyses_cache.invalidate_analysis(ReachableAnalysis)

Check warning on line 63 in vyper/venom/analysis/cfg.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/cfg.py#L63

Added line #L63 was not covered by tests

self._dfs = None

Expand Down
42 changes: 42 additions & 0 deletions vyper/venom/analysis/reachable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from collections import defaultdict

from vyper.utils import OrderedSet
from vyper.venom.analysis import IRAnalysis
from vyper.venom.analysis.cfg import CFGAnalysis
from vyper.venom.basicblock import IRBasicBlock


class ReachableAnalysis(IRAnalysis):
"""
Compute control flow graph information for each basic block in the function.
"""

reachable: dict[IRBasicBlock, OrderedSet[IRBasicBlock]]

def analyze(self) -> None:
self.analyses_cache.request_analysis(CFGAnalysis)
self.reachable = defaultdict(OrderedSet)

Check warning on line 18 in vyper/venom/analysis/reachable.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/reachable.py#L17-L18

Added lines #L17 - L18 were not covered by tests

self._compute_reachable_r(self.function.entry)

Check warning on line 20 in vyper/venom/analysis/reachable.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/reachable.py#L20

Added line #L20 was not covered by tests

def _compute_reachable_r(self, bb):
if bb in self.reachable:
return

Check warning on line 24 in vyper/venom/analysis/reachable.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/reachable.py#L24

Added line #L24 was not covered by tests

s = bb.cfg_out.copy()
self.reachable[bb] = s

Check warning on line 27 in vyper/venom/analysis/reachable.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/reachable.py#L26-L27

Added lines #L26 - L27 were not covered by tests

for out_bb in bb.cfg_out:
self._compute_reachable_r(out_bb)
s.update(self.reachable[out_bb])

Check warning on line 31 in vyper/venom/analysis/reachable.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/reachable.py#L30-L31

Added lines #L30 - L31 were not covered by tests

def invalidate(self):
from vyper.venom.analysis import DFGAnalysis, DominatorTreeAnalysis, LivenessAnalysis

Check warning on line 34 in vyper/venom/analysis/reachable.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/reachable.py#L34

Added line #L34 was not covered by tests

self.analyses_cache.invalidate_analysis(DominatorTreeAnalysis)
self.analyses_cache.invalidate_analysis(LivenessAnalysis)

Check warning on line 37 in vyper/venom/analysis/reachable.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/reachable.py#L36-L37

Added lines #L36 - L37 were not covered by tests

self._dfs = None

Check warning on line 39 in vyper/venom/analysis/reachable.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/reachable.py#L39

Added line #L39 was not covered by tests

# be conservative - assume cfg invalidation invalidates dfg
self.analyses_cache.invalidate_analysis(DFGAnalysis)

Check warning on line 42 in vyper/venom/analysis/reachable.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/reachable.py#L42

Added line #L42 was not covered by tests
5 changes: 1 addition & 4 deletions vyper/venom/basicblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,11 @@
"create",
"create2",
"invoke",
"sload",
"sstore",
"iload",
"istore",
"tload",
"itouch",
"tstore",
"mstore",
"mload",
"calldatacopy",
"mcopy",
"extcodecopy",
Expand Down
5 changes: 3 additions & 2 deletions vyper/venom/effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __iter__(self):
"tstore": TRANSIENT,
"mstore": MEMORY,
"istore": IMMUTABLES,
"itouch": MSIZE,
"call": ALL ^ IMMUTABLES,
"delegatecall": ALL ^ IMMUTABLES,
"staticcall": MEMORY | RETURNDATA,
Expand Down Expand Up @@ -84,11 +85,11 @@ def __iter__(self):
writes = _writes.copy()

for k, v in reads.items():
if MEMORY in v:
if MEMORY in v or IMMUTABLES in v:
if k not in writes:
writes[k] = EMPTY
writes[k] |= MSIZE

for k, v in writes.items():
if MEMORY in v:
if MEMORY in v or IMMUTABLES in v:
writes[k] |= MSIZE
15 changes: 12 additions & 3 deletions vyper/venom/passes/remove_unused_variables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from collections import defaultdict

from vyper.utils import OrderedSet
from vyper.venom.analysis import DFGAnalysis, LivenessAnalysis
from vyper.venom.basicblock import IRInstruction
from vyper.venom import effects
Fixed Show fixed Hide fixed
from vyper.venom.analysis import DFGAnalysis, LivenessAnalysis, ReachableAnalysis
Fixed Show fixed Hide fixed
from vyper.venom.basicblock import IRBasicBlock, IRInstruction
Fixed Show fixed Hide fixed
from vyper.venom.passes.base_pass import IRPass


Expand All @@ -25,6 +28,9 @@
inst = work_list.pop()
self._process_instruction(inst)

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

Check warning on line 32 in vyper/venom/passes/remove_unused_variables.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/remove_unused_variables.py#L32

Added line #L32 was not covered by tests

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

Expand All @@ -33,6 +39,9 @@
return
if inst.is_volatile or inst.is_bb_terminator:
return

bb = inst.parent

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

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/remove_unused_variables.py#L43

Added line #L43 was not covered by tests

uses = self.dfg.get_uses(inst.output)
if len(uses) > 0:
return
Expand All @@ -42,4 +51,4 @@
new_uses = self.dfg.get_uses(operand)
self.work_list.addmany(new_uses)

inst.parent.remove_instruction(inst)
bb.mark_for_removal(inst)

Check warning on line 54 in vyper/venom/passes/remove_unused_variables.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/passes/remove_unused_variables.py#L54

Added line #L54 was not covered by tests
Loading
Loading