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

Merged
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
52 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
032f160
test fix
HodanPlodky Jan 16, 2025
2bf3d88
Merge branch 'master' into feat/advanced-load-elimination
charles-cooper Jan 20, 2025
7f57ba0
test fix since the static asserts could be found in compile time
HodanPlodky Jan 27, 2025
6b2a36f
test fix since the static asserts could be found in compile time
HodanPlodky Jan 27, 2025
4d586cb
Merge branch 'feat/advanced-load-elimination' into feat/advanced-load…
HodanPlodky Jan 28, 2025
4e7638d
additional tests + lint
HodanPlodky Jan 28, 2025
2455eef
store store overlap test
HodanPlodky Jan 28, 2025
8797c9c
mstore non overlap test
HodanPlodky Jan 31, 2025
f302804
mstore barrier with unknown ptr
HodanPlodky Jan 31, 2025
04915aa
Merge pull request #58 from HodanPlodky/feat/advanced-load-elimination
charles-cooper Jan 31, 2025
25bd912
add test comments
charles-cooper Jan 31, 2025
d7b72f7
Merge branch 'master' into feat/advanced-load-elimination
charles-cooper Jan 31, 2025
26ae754
remove redundant branch
charles-cooper Jan 31, 2025
35d6a6f
Merge branch 'master' into feat/advanced-load-elimination
charles-cooper Feb 7, 2025
8bd079a
add calldataload elimination
charles-cooper Feb 7, 2025
1700185
add hevm harness for load elimination
charles-cooper Feb 7, 2025
50ebb78
use make_nop
charles-cooper Feb 7, 2025
0f664f4
Fix a comment
charles-cooper Feb 11, 2025
8ddea05
refactor load/store handling into functions
charles-cooper Feb 12, 2025
55d4de0
Merge branch 'master' into feat/advanced-load-elimination
charles-cooper Feb 12, 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
18 changes: 13 additions & 5 deletions tests/hevm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from tests.venom_utils import parse_from_basic_block
from vyper.ir.compile_ir import assembly_to_evm
from vyper.venom import StoreExpansionPass, VenomCompiler
from vyper.venom import LowerDloadPass, StoreExpansionPass, VenomCompiler
from vyper.venom.analysis import IRAnalysesCache
from vyper.venom.basicblock import IRInstruction, IRLiteral

Expand All @@ -29,20 +29,28 @@ def _prep_hevm_venom(venom_source_code):
term = bb.instructions[-1]
# test convention, terminate by `return`ing the variables
# you want to check
assert term.opcode == "sink"
if term.opcode != "sink":
continue

# testing convention: first 256 bytes can be symbolically filled
# with calldata
RETURN_START = 256

num_return_values = 0
for op in term.operands:
ptr = IRLiteral(num_return_values * 32)
ptr = IRLiteral(RETURN_START + num_return_values * 32)
new_inst = IRInstruction("mstore", [op, ptr])
bb.insert_instruction(new_inst, index=-1)
num_return_values += 1

# return 0, 32 * num_variables
term.opcode = "return"
term.operands = [IRLiteral(num_return_values * 32), IRLiteral(0)]
term.operands = [IRLiteral(num_return_values * 32), IRLiteral(RETURN_START)]

ac = IRAnalysesCache(fn)
# requirement for venom_to_assembly

# requirements for venom_to_assembly
LowerDloadPass(ac, fn).run_pass()
StoreExpansionPass(ac, fn).run_pass()

compiler = VenomCompiler([ctx])
Expand Down
250 changes: 219 additions & 31 deletions tests/unit/compiler/venom/test_load_elimination.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,104 @@
import pytest

from tests.hevm import hevm_check_venom
from tests.venom_utils import assert_ctx_eq, parse_from_basic_block
from vyper.evm.address_space import CALLDATA, DATA, MEMORY, STORAGE, TRANSIENT
from vyper.venom.analysis.analysis import IRAnalysesCache
from vyper.venom.passes.load_elimination import LoadElimination
from vyper.venom.passes import LoadElimination, StoreElimination

pytestmark = pytest.mark.hevm


def _check_pre_post(pre, post):
def _check_pre_post(pre, post, hevm=True):
ctx = parse_from_basic_block(pre)

post_ctx = parse_from_basic_block(post)
for fn in post_ctx.functions.values():
ac = IRAnalysesCache(fn)
# this store elim is used for
# proper equivalence of the post
# and pre results
StoreElimination(ac, fn).run_pass()

for fn in ctx.functions.values():
ac = IRAnalysesCache(fn)
# store elim is needed for variable equivalence
StoreElimination(ac, fn).run_pass()
LoadElimination(ac, fn).run_pass()
# this store elim is used for
# proper equivalence of the post
# and pre results
StoreElimination(ac, fn).run_pass()

assert_ctx_eq(ctx, parse_from_basic_block(post))
assert_ctx_eq(ctx, post_ctx)

if hevm:
hevm_check_venom(pre, post)


def _check_no_change(pre):
_check_pre_post(pre, pre)
_check_pre_post(pre, pre, hevm=False)


def test_simple_load_elimination():
pre = """
# fill memory with symbolic data for hevm
def _fill_symbolic(addrspace):
if addrspace == MEMORY:
return "calldatacopy 0, 0, 256"

return ""


ADDRESS_SPACES = (MEMORY, STORAGE, TRANSIENT, CALLDATA, DATA)
RW_ADDRESS_SPACES = (MEMORY, STORAGE, TRANSIENT)


@pytest.mark.parametrize("addrspace", ADDRESS_SPACES)
def test_simple_load_elimination(addrspace):
LOAD = addrspace.load_op
pre = f"""
main:
%ptr = 11
%1 = mload %ptr
%2 = mload %ptr
%1 = {LOAD} %ptr
%2 = {LOAD} %ptr
stop
sink %1, %2
"""
post = """
post = f"""
main:
%ptr = 11
%1 = mload %ptr
%1 = {LOAD} %ptr
%2 = %1
stop
sink %1, %2
"""
_check_pre_post(pre, post)


def test_equivalent_var_elimination():
@pytest.mark.parametrize("addrspace", ADDRESS_SPACES)
def test_equivalent_var_elimination(addrspace):
"""
Test that the lattice can "peer through" equivalent vars
"""
pre = """
LOAD = addrspace.load_op
pre = f"""
main:
%1 = 11
%2 = %1
%3 = mload %1
%4 = mload %2
%3 = {LOAD} %1
%4 = {LOAD} %2
stop
sink %3, %4
"""
post = """
post = f"""
main:
%1 = 11
%2 = %1
%3 = mload %1
%3 = {LOAD} %1
%4 = %3 # %2 == %1
stop
sink %3, %4
"""
_check_pre_post(pre, post)

Expand All @@ -82,32 +119,35 @@ def test_elimination_barrier():
_check_no_change(pre)


def test_store_load_elimination():
@pytest.mark.parametrize("addrspace", RW_ADDRESS_SPACES)
def test_store_load_elimination(addrspace):
"""
Check that lattice stores the result of mstores (even through
Check that lattice stores the result of stores (even through
equivalent variables)
"""
pre = """
LOAD = addrspace.load_op
STORE = addrspace.store_op
pre = f"""
main:
%val = 55
%ptr1 = 11
%ptr2 = %ptr1
mstore %ptr1, %val
{STORE} %ptr1, %val
%3 = mload %ptr2
%3 = {LOAD} %ptr2
stop
sink %3
"""
post = """
post = f"""
main:
%val = 55
%ptr1 = 11
%ptr2 = %ptr1
mstore %ptr1, %val
{STORE} %ptr1, %val
%3 = %val
stop
sink %3
"""
_check_pre_post(pre, post)

Expand All @@ -127,3 +167,151 @@ def test_store_load_barrier():
%4 = mload %ptr
"""
_check_no_change(pre)


def test_store_load_overlap_barrier():
"""
Check for barrier between store/load done
by overlap of the mstore and mload
"""

pre = """
main:
%ptr_mload = 10
%ptr_mstore = 20
%tmp01 = mload %ptr_mload
# barrier created with overlap
mstore %ptr_mstore, 11
%tmp02 = mload %ptr_mload
return %tmp01, %tmp02
"""

_check_no_change(pre)


def test_store_store_overlap_barrier():
"""
Check for barrier between store/load done
by overlap of the mstore and mload
"""

pre = """
main:
%ptr_mstore01 = 10
%ptr_mstore02 = 20
mstore %ptr_mstore01, 10
# barrier created with overlap
mstore %ptr_mstore02, 11
mstore %ptr_mstore01, 10
stop
"""

_check_no_change(pre)


def test_store_load_no_overlap_different_store():
"""
Check for barrier between store/load done
by overlap of the mstore and mload
"""

pre = f"""
main:
{_fill_symbolic(MEMORY)}
%ptr_mload = 10
%tmp01 = mload %ptr_mload
# this should not create barrier
sstore %ptr_mload, 11
%tmp02 = mload %ptr_mload
sink %tmp01, %tmp02
"""

post = f"""
main:
{_fill_symbolic(MEMORY)}
%ptr_mload = 10
%tmp01 = mload %ptr_mload
# this should not create barrier
sstore %ptr_mload, 11
%tmp02 = %tmp01 ; mload optimized out
sink %tmp01, %tmp02
"""

_check_pre_post(pre, post)


@pytest.mark.parametrize("addrspace", RW_ADDRESS_SPACES)
def test_store_store_no_overlap(addrspace):
"""
Test that if the mstores do not overlap it can still
eliminate any possible repeated mstores
"""
LOAD = addrspace.load_op
STORE = addrspace.store_op

pre = f"""
main:
{_fill_symbolic(addrspace)}
%ptr_mstore01 = 10
%ptr_mstore02 = 42
{STORE} %ptr_mstore01, 10
{STORE} %ptr_mstore02, 11
{STORE} %ptr_mstore01, 10
%val1 = {LOAD} %ptr_mstore01
%val2 = {LOAD} %ptr_mstore02
sink %val1, %val2
"""

post = f"""
main:
{_fill_symbolic(addrspace)}
%ptr_mstore01 = 10
%ptr_mstore02 = 42
{STORE} %ptr_mstore01, 10
{STORE} %ptr_mstore02, 11
nop ; repeated store
sink 10, 11
"""

_check_pre_post(pre, post)


def test_store_store_unknown_ptr_barrier():
"""
Check for barrier between store/load done
by overlap of the mstore and mload
"""

pre = """
main:
%ptr_mstore01 = 10
%ptr_mstore02 = param
mstore %ptr_mstore01, 10
# barrier created with overlap
mstore %ptr_mstore02, 11
mstore %ptr_mstore01, 10
stop
"""

_check_no_change(pre)
Loading