Skip to content
Merged
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
63 changes: 63 additions & 0 deletions tests/functional/codegen/features/iteration/test_for_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,3 +507,66 @@ def foo() -> (uint256, DynArray[uint256, 3]):
length, res = c.foo()

assert (length, res) == (1, [0, 1, 2])


def test_bubble_sort(get_contract):
"""
Test vyper implementation of bubble sort. Good functional test as it
stresses the code generator and optimizer a little.
"""
code = """
MAX_DATA_SIZE: constant(uint256) = 100

data: DynArray[uint256, MAX_DATA_SIZE]

@internal
@view
def _validate_index(idx: uint256):
assert idx < len(self.data), "Index out of bounds"

@internal
def _swap(i: uint256, j: uint256):
self._validate_index(i)
self._validate_index(j)
temp: uint256 = self.data[i]
self.data[i] = self.data[j]
self.data[j] = temp

@internal
def _bubble_sort():
n: uint256 = len(self.data)
for i: uint256 in range(n, bound=MAX_DATA_SIZE):
for j: uint256 in range(n - i - 1, bound=MAX_DATA_SIZE):
if self.data[j] > self.data[j + 1]:
self._swap(j, j + 1)

@external
def add(val: uint256):
self.data.append(val)

@external
def sort_data():
self._bubble_sort()

@external
@view
def get(idx: uint256) -> uint256:
self._validate_index(idx)
return self.data[idx]
"""

c = get_contract(code)

# add unsorted data
c.add(5)
c.add(2)
c.add(8)
c.add(1)

# sort
c.sort_data()

# check sorted
results = [c.get(i) for i in range(4)]

assert results == [1, 2, 5, 8]
4 changes: 4 additions & 0 deletions tests/functional/codegen/features/test_constructor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import pytest

from tests.evm_backends.base_env import _compile
from vyper.exceptions import StackTooDeep
from vyper.utils import method_id


Expand Down Expand Up @@ -211,6 +214,7 @@ def get_foo() -> DynArray[DynArray[uint256, 3], 3]:
assert c.get_foo() == [[37, 41, 73], [37041, 41073, 73037], [146, 123, 148]]


@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression")
def test_initialise_nested_dynamic_array_2(env, get_contract):
code = """
foo: DynArray[DynArray[DynArray[int128, 3], 3], 3]
Expand Down
2 changes: 2 additions & 0 deletions tests/functional/codegen/features/test_immutable.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from vyper.compiler.settings import OptimizationLevel
from vyper.exceptions import StackTooDeep


@pytest.mark.parametrize(
Expand Down Expand Up @@ -198,6 +199,7 @@ def get_idx_two() -> uint256:
assert c.get_idx_two() == expected_values[2][2]


@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression")
def test_nested_dynarray_immutable(get_contract):
code = """
my_list: immutable(DynArray[DynArray[DynArray[int128, 3], 3], 3])
Expand Down
3 changes: 2 additions & 1 deletion tests/functional/codegen/features/test_transient.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from tests.utils import ZERO_ADDRESS
from vyper.compiler import compile_code
from vyper.exceptions import EvmVersionException, VyperException
from vyper.exceptions import EvmVersionException, StackTooDeep, VyperException

pytestmark = pytest.mark.requires_evm_version("cancun")

Expand Down Expand Up @@ -343,6 +343,7 @@ def get_idx_two(_a: uint256, _b: uint256, _c: uint256) -> uint256:
assert c.get_idx_two(*values) == expected_values[2][2]


@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression")
def test_nested_dynarray_transient(get_contract, tx_failed, env):
set_list = """
self.my_list = [
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 @@ -597,6 +597,7 @@ def bar(_baz: Foo[3]) -> String[96]:
assert c.bar(c_input) == "Hello world!!!!"


@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression")
def test_list_of_nested_struct_arrays(get_contract):
code = """
struct Ded:
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/compiler/venom/test_dft.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from tests.venom_utils import PrePostChecker
from vyper.venom.passes import DFTPass

_check_pre_post = PrePostChecker([DFTPass])


def _check_no_change(pre):
_check_pre_post(pre, pre)


def test_dft():
"""
Basic test for dft pass
"""
pre = """
main:
%x = 1
%y = 2
return %x, %y
"""
post = """
main:
%y = 2
%x = 1
return %x, %y
"""
_check_pre_post(pre, post)
2 changes: 2 additions & 0 deletions vyper/venom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache
SimplifyCFGPass(ac, fn).run_pass()
AssignElimination(ac, fn).run_pass()
AlgebraicOptimizationPass(ac, fn).run_pass()

LoadElimination(ac, fn).run_pass()

SCCP(ac, fn).run_pass()
Expand All @@ -104,6 +105,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache
PhiEliminationPass(ac, fn).run_pass()
AssignElimination(ac, fn).run_pass()
CSE(ac, fn).run_pass()

AssignElimination(ac, fn).run_pass()
RemoveUnusedVariablesPass(ac, fn).run_pass()
SingleUseExpansion(ac, fn).run_pass()
Expand Down
3 changes: 2 additions & 1 deletion vyper/venom/basicblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,8 @@ def is_pseudo(self) -> bool:
Check if instruction is pseudo, i.e. not an actual instruction but
a construct for intermediate representation like phi and param.
"""
return self.is_phi or self.is_param
# do not reorder `source` instructions in dft pass - for testing
return self.is_phi or self.is_param or self.opcode == "source"

def get_read_effects(self) -> effects.Effects:
return effects.reads.get(self.opcode, effects.EMPTY)
Expand Down
16 changes: 12 additions & 4 deletions vyper/venom/passes/dft.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def _calculate_dependency_graphs(self, bb: IRBasicBlock) -> None:
# Compute dependency graph
#
last_write_effects: dict[effects.Effects, IRInstruction] = {}
last_read_effects: dict[effects.Effects, IRInstruction] = {}
all_read_effects: dict[effects.Effects, list[IRInstruction]] = defaultdict(list)

for inst in non_phis:
for op in inst.operands:
Expand All @@ -106,14 +106,22 @@ def _calculate_dependency_graphs(self, bb: IRBasicBlock) -> None:
read_effects = inst.get_read_effects()

for write_effect in write_effects:
if write_effect in last_read_effects:
self.eda[inst].add(last_read_effects[write_effect])
# ALL reads must happen before this write
if write_effect in all_read_effects:
for read_inst in all_read_effects[write_effect]:
self.eda[inst].add(read_inst)
# prevent reordering write-after-write for the same effect
if write_effect in last_write_effects:
self.eda[inst].add(last_write_effects[write_effect])
last_write_effects[write_effect] = inst
# clear previous read effects after a write
if write_effect in all_read_effects:
all_read_effects[write_effect] = []

for read_effect in read_effects:
if read_effect in last_write_effects and last_write_effects[read_effect] != inst:
self.eda[inst].add(last_write_effects[read_effect])
last_read_effects[read_effect] = inst
all_read_effects[read_effect].append(inst)

def _calculate_data_offspring(self, inst: IRInstruction):
if inst in self.data_offspring:
Expand Down