Skip to content

Commit

Permalink
Merge branch 'master' into refactor/equivalent-vars
Browse files Browse the repository at this point in the history
  • Loading branch information
charles-cooper authored Dec 26, 2024
2 parents 0ade317 + 7caa055 commit 7daec7f
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 82 deletions.
121 changes: 120 additions & 1 deletion tests/functional/venom/parser/test_parsing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from tests.venom_utils import assert_ctx_eq
from tests.venom_utils import assert_bb_eq, assert_ctx_eq
from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRLiteral, IRVariable
from vyper.venom.context import DataItem, DataSection, IRContext
from vyper.venom.function import IRFunction
Expand Down Expand Up @@ -231,3 +231,122 @@ def test_multi_function_and_data():
]

assert_ctx_eq(parsed_ctx, expected_ctx)


def test_phis():
# @external
# def _loop() -> uint256:
# res: uint256 = 9
# for i: uint256 in range(res, bound=10):
# res = res + i
# return res
source = """
function __main_entry {
__main_entry: ; IN=[] OUT=[fallback, 1_then] => {}
%27 = 0
%1 = calldataload %27
%28 = %1
%29 = 224
%2 = shr %29, %28
%31 = %2
%30 = 1729138561
%4 = xor %30, %31
%32 = %4
jnz %32, @fallback, @1_then
; (__main_entry)
1_then: ; IN=[__main_entry] OUT=[4_condition] => {%11, %var8_0}
%6 = callvalue
%33 = %6
%7 = iszero %33
%34 = %7
assert %34
%var8_0 = 9
%11 = 0
nop
jmp @4_condition
; (__main_entry)
4_condition: ; IN=[1_then, 5_body] OUT=[5_body, 7_exit] => {%11:3, %var8_0:2}
%var8_0:2 = phi @1_then, %var8_0, @5_body, %var8_0:3
%11:3 = phi @1_then, %11, @5_body, %11:4
%35 = %11:3
%36 = 9
%15 = xor %36, %35
%37 = %15
jnz %37, @5_body, @7_exit
; (__main_entry)
5_body: ; IN=[4_condition] OUT=[4_condition] => {%11:4, %var8_0:3}
%38 = %11:3
%39 = %var8_0:2
%22 = add %39, %38
%41 = %22
%40 = %var8_0:2
%24 = gt %40, %41
%42 = %24
%25 = iszero %42
%43 = %25
assert %43
%var8_0:3 = %22
%44 = %11:3
%45 = 1
%11:4 = add %45, %44
jmp @4_condition
; (__main_entry)
7_exit: ; IN=[4_condition] OUT=[] => {}
%46 = %var8_0:2
%47 = 64
mstore %47, %46
%48 = 32
%49 = 64
return %49, %48
; (__main_entry)
fallback: ; IN=[__main_entry] OUT=[] => {}
%50 = 0
%51 = 0
revert %51, %50
stop
; (__main_entry)
} ; close function __main_entry
"""
ctx = parse_venom(source)

expected_ctx = IRContext()
expected_ctx.add_function(entry_fn := IRFunction(IRLabel("__main_entry")))

expect_bb = IRBasicBlock(IRLabel("4_condition"), entry_fn)
entry_fn.append_basic_block(expect_bb)

expect_bb.append_instruction(
"phi",
IRLabel("1_then"),
IRVariable("%var8_0"),
IRLabel("5_body"),
IRVariable("%var8_0:3"),
ret=IRVariable("var8_0:2"),
)
expect_bb.append_instruction(
"phi",
IRLabel("1_then"),
IRVariable("%11"),
IRLabel("5_body"),
IRVariable("%11:4"),
ret=IRVariable("11:3"),
)
expect_bb.append_instruction("store", IRVariable("11:3"), ret=IRVariable("%35"))
expect_bb.append_instruction("store", IRLiteral(9), ret=IRVariable("%36"))
expect_bb.append_instruction("xor", IRVariable("%35"), IRVariable("%36"), ret=IRVariable("%15"))
expect_bb.append_instruction("store", IRVariable("%15"), ret=IRVariable("%37"))
expect_bb.append_instruction("jnz", IRVariable("%37"), IRLabel("5_body"), IRLabel("7_exit"))
# other basic blocks omitted for brevity

parsed_fn = next(iter(ctx.functions.values()))
assert_bb_eq(parsed_fn.get_basic_block(expect_bb.label.name), expect_bb)
86 changes: 85 additions & 1 deletion tests/functional/venom/test_venom_repr.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import copy
import glob
import textwrap

import pytest

from tests.venom_utils import assert_ctx_eq, parse_venom
from vyper.compiler import compile_code
from vyper.compiler.phases import generate_bytecode
from vyper.venom import generate_assembly_experimental, run_passes_on
from vyper.venom.context import IRContext

"""
Expand All @@ -16,15 +20,95 @@ def get_example_vy_filenames():


@pytest.mark.parametrize("vy_filename", get_example_vy_filenames())
def test_round_trip(vy_filename, optimize, request):
def test_round_trip_examples(vy_filename, optimize, compiler_settings):
"""
Check all examples round trip
"""
path = f"examples/{vy_filename}"
with open(path) as f:
vyper_source = f.read()

_round_trip_helper(vyper_source, optimize, compiler_settings)


# pure vyper sources
vyper_sources = [
"""
@external
def _loop() -> uint256:
res: uint256 = 9
for i: uint256 in range(res, bound=10):
res = res + i
return res
"""
]


@pytest.mark.parametrize("vyper_source", vyper_sources)
def test_round_trip_sources(vyper_source, optimize, compiler_settings):
"""
Test vyper_sources round trip
"""
vyper_source = textwrap.dedent(vyper_source)
_round_trip_helper(vyper_source, optimize, compiler_settings)


def _round_trip_helper(vyper_source, optimize, compiler_settings):
# helper function to test venom round-tripping thru the parser
# use two helpers because run_passes_on and
# generate_assembly_experimental are both destructive (mutating) on
# the IRContext
_helper1(vyper_source, optimize)
_helper2(vyper_source, optimize, compiler_settings)


def _helper1(vyper_source, optimize):
"""
Check that we are able to run passes on the round-tripped venom code
and that it is valid (generates bytecode)
"""
# note: compiling any later stage than bb_runtime like `asm` or
# `bytecode` modifies the bb_runtime data structure in place and results
# in normalization of the venom cfg (which breaks again make_ssa)
out = compile_code(vyper_source, output_formats=["bb_runtime"])

bb_runtime = out["bb_runtime"]
venom_code = IRContext.__repr__(bb_runtime)

ctx = parse_venom(venom_code)

assert_ctx_eq(bb_runtime, ctx)

# check it's valid to run venom passes+analyses
# (note this breaks bytecode equality, in the future we should
# test that separately)
run_passes_on(ctx, optimize)

# test we can generate assembly+bytecode
asm = generate_assembly_experimental(ctx)
generate_bytecode(asm, compiler_metadata=None)


def _helper2(vyper_source, optimize, compiler_settings):
"""
Check that we can compile to bytecode, and without running venom passes,
that the output bytecode is equal to going through the normal vyper pipeline
"""
settings = copy.copy(compiler_settings)
# bytecode equivalence only makes sense if we use venom pipeline
settings.experimental_codegen = True

out = compile_code(vyper_source, settings=settings, output_formats=["bb_runtime"])
bb_runtime = out["bb_runtime"]
venom_code = IRContext.__repr__(bb_runtime)

ctx = parse_venom(venom_code)

assert_ctx_eq(bb_runtime, ctx)

# test we can generate assembly+bytecode
asm = generate_assembly_experimental(ctx, optimize=optimize)
bytecode = generate_bytecode(asm, compiler_metadata=None)

out = compile_code(vyper_source, settings=settings, output_formats=["bytecode_runtime"])
assert "0x" + bytecode.hex() == out["bytecode_runtime"]
88 changes: 46 additions & 42 deletions tests/unit/compiler/venom/test_make_ssa.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,52 @@
from tests.venom_utils import assert_ctx_eq, parse_venom
from vyper.venom.analysis import IRAnalysesCache
from vyper.venom.basicblock import IRBasicBlock, IRLabel
from vyper.venom.context import IRContext
from vyper.venom.passes import MakeSSA


def test_phi_case():
ctx = IRContext()
fn = ctx.create_function("_global")

bb = fn.get_basic_block()

bb_cont = IRBasicBlock(IRLabel("condition"), fn)
bb_then = IRBasicBlock(IRLabel("then"), fn)
bb_else = IRBasicBlock(IRLabel("else"), fn)
bb_if_exit = IRBasicBlock(IRLabel("if_exit"), fn)
fn.append_basic_block(bb_cont)
fn.append_basic_block(bb_then)
fn.append_basic_block(bb_else)
fn.append_basic_block(bb_if_exit)

v = bb.append_instruction("mload", 64)
bb_cont.append_instruction("jnz", v, bb_then.label, bb_else.label)

bb_if_exit.append_instruction("add", v, 1, ret=v)
bb_if_exit.append_instruction("jmp", bb_cont.label)
def _check_pre_post(pre, post):
ctx = parse_venom(pre)
for fn in ctx.functions.values():
ac = IRAnalysesCache(fn)
MakeSSA(ac, fn).run_pass()
assert_ctx_eq(ctx, parse_venom(post))

bb_then.append_instruction("assert", bb_then.append_instruction("mload", 96))
bb_then.append_instruction("jmp", bb_if_exit.label)
bb_else.append_instruction("jmp", bb_if_exit.label)

bb.append_instruction("jmp", bb_cont.label)

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

condition_block = fn.get_basic_block("condition")
assert len(condition_block.instructions) == 2

phi_inst = condition_block.instructions[0]
assert phi_inst.opcode == "phi"
assert phi_inst.operands[0].name == "_global"
assert phi_inst.operands[1].name == "%1"
assert phi_inst.operands[2].name == "if_exit"
assert phi_inst.operands[3].name == "%1"
assert phi_inst.output.name == "%1"
assert phi_inst.output.value != phi_inst.operands[1].value
assert phi_inst.output.value != phi_inst.operands[3].value
def test_phi_case():
pre = """
function loop {
main:
%v = mload 64
jmp @test
test:
jnz %v, @then, @else
then:
%t = mload 96
assert %t
jmp @if_exit
else:
jmp @if_exit
if_exit:
%v = add %v, 1
jmp @test
}
"""
post = """
function loop {
main:
%v = mload 64
jmp @test
test:
%v:1 = phi @main, %v, @if_exit, %v:2
jnz %v:1, @then, @else
then:
%t = mload 96
assert %t
jmp @if_exit
else:
jmp @if_exit
if_exit:
%v:2 = add %v:1, 1
jmp @test
}
"""
_check_pre_post(pre, post)
17 changes: 8 additions & 9 deletions vyper/venom/basicblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class IROperand:
"""

value: Any
_hash: Optional[int]
_hash: Optional[int] = None

def __init__(self, value: Any) -> None:
self.value = value
Expand Down Expand Up @@ -149,9 +149,8 @@ class IRLiteral(IROperand):
value: int

def __init__(self, value: int) -> None:
super().__init__(value)
assert isinstance(value, int), "value must be an int"
self.value = value
super().__init__(value)


class IRVariable(IROperand):
Expand All @@ -163,17 +162,17 @@ class IRVariable(IROperand):
version: Optional[int]

def __init__(self, name: str, version: int = 0) -> None:
super().__init__(name)
assert isinstance(name, str)
assert isinstance(version, int | None)
# TODO: allow version to be None
assert isinstance(version, int)
if not name.startswith("%"):
name = f"%{name}"
self._name = name
self.version = version
value = name
if version > 0:
self.value = f"{name}:{version}"
else:
self.value = name
value = f"{name}:{version}"
super().__init__(value)

@property
def name(self) -> str:
Expand All @@ -193,8 +192,8 @@ class IRLabel(IROperand):
def __init__(self, value: str, is_symbol: bool = False) -> None:
assert isinstance(value, str), f"not a str: {value} ({type(value)})"
assert len(value) > 0
super().__init__(value)
self.is_symbol = is_symbol
super().__init__(value)

_IS_IDENTIFIER = re.compile("[0-9a-zA-Z_]*")

Expand Down
Loading

0 comments on commit 7daec7f

Please sign in to comment.