Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8334141
more
harkal Sep 10, 2025
b1ae3f6
tests
harkal Sep 10, 2025
e24df15
basic test
harkal Sep 10, 2025
ddf2fcd
lint
harkal Sep 10, 2025
cfac25b
parser
harkal Sep 10, 2025
0fd525c
type checks
harkal Sep 10, 2025
58671c0
test
harkal Sep 11, 2025
023e14e
lint
harkal Sep 11, 2025
622682c
dft update
harkal Sep 11, 2025
712c0a2
handle ret
harkal Sep 11, 2025
0e7efc8
scratch pad
harkal Sep 11, 2025
afc1f59
fix sccp TOP eval case
harkal Sep 11, 2025
2afd4d5
handle TOP condition in jnz and djmp operations
harkal Sep 11, 2025
ecd2f2d
remove test that was not failing with old code
harkal Sep 11, 2025
b08f177
Merge branch 'fix/venom/sccp_defer_eval_top' into feat/venom/call_con…
harkal Sep 11, 2025
b417939
mutli return support in sccp
harkal Sep 11, 2025
1e807c2
Merge branch 'master' into feat/venom/call_conv_stack_return
harkal Sep 18, 2025
212cf86
refactor `append_invoke_instruction`
harkal Sep 19, 2025
fef2c5a
update
harkal Sep 19, 2025
5f5f09c
lint
harkal Sep 19, 2025
0563a51
Merge branch 'master' into feat/venom/call_conv_stack_return
harkal Sep 19, 2025
50c2f9f
convert to parsed venom now parser works
harkal Sep 19, 2025
20e4ceb
lint
harkal Sep 19, 2025
ea85e0c
clone outputs
harkal Sep 28, 2025
a57a11c
lint
harkal Sep 29, 2025
15d4d92
Merge branch 'master' into feat/venom/call_conv_stack_return
harkal Oct 9, 2025
2094374
fix
harkal Oct 9, 2025
6d8614d
Merge remote-tracking branch 'origin/master' into feat/venom/call_con…
harkal Oct 21, 2025
9f31ce8
use non numerals
harkal Oct 29, 2025
86f661d
var counting fix
harkal Oct 29, 2025
0329d45
squash
harkal Oct 29, 2025
45c1882
lint
harkal Oct 29, 2025
c92a2eb
`get_outputs()` return list of variables
harkal Oct 30, 2025
87a6dc8
wip
harkal Oct 30, 2025
3ffb340
get_output()
harkal Oct 30, 2025
4dd3d81
cleanups
harkal Oct 30, 2025
8292ff1
clean
harkal Oct 30, 2025
91db439
CSE updates
harkal Oct 30, 2025
f23577f
optimization
harkal Oct 30, 2025
476c511
comments
harkal Oct 30, 2025
db400ff
full dead pop
harkal Oct 30, 2025
1bb2d38
review comments
harkal Nov 5, 2025
794577a
remove ENABLE_NEW_CALL_CONV
harkal Nov 5, 2025
02a4f6f
`output` property
harkal Nov 6, 2025
b204293
review items
harkal Nov 6, 2025
fbd7108
Merge remote-tracking branch 'origin-vyper/master' into feat/venom/ca…
harkal Nov 6, 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
31 changes: 31 additions & 0 deletions tests/functional/venom/parser/test_multi_output_invoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from tests.venom_utils import parse_venom
from vyper.venom.basicblock import IRInstruction


def _make_src(a: int, b: int) -> str:
return f"""
function main {{
main:
%x, %y = invoke @f
sink %x, %y
}}

function f {{
f:
%retpc = param
%v0 = assign {a}
%v1 = assign {b}
ret %v0, %v1, %retpc
}}
"""


def test_parse_multi_output_invoke_builds_two_outputs():
src = _make_src(7, 9)
ctx = parse_venom(src)
fn = ctx.get_function(next(iter(ctx.functions.keys())))
main_bb = fn.get_basic_block("main")
inst = next(inst for inst in main_bb.instructions if inst.opcode == "invoke")
assert isinstance(inst, IRInstruction)
outs = inst.get_outputs()
assert len(outs) == 2
38 changes: 36 additions & 2 deletions tests/functional/venom/parser/test_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def test_multi_function():
expected_ctx.add_function(entry_fn := IRFunction(IRLabel("entry")))

entry_bb = entry_fn.get_basic_block("entry")
entry_bb.append_invoke_instruction([IRLabel("check_cv")], returns=False)
entry_bb.append_invoke_instruction([IRLabel("check_cv")], returns=0)
entry_bb.append_instruction("jmp", IRLabel("wow"))

entry_fn.append_basic_block(wow_bb := IRBasicBlock(IRLabel("wow"), entry_fn))
Expand Down Expand Up @@ -213,7 +213,7 @@ def test_multi_function_and_data():
expected_ctx.add_function(entry_fn := IRFunction(IRLabel("entry")))

entry_bb = entry_fn.get_basic_block("entry")
entry_bb.append_invoke_instruction([IRLabel("check_cv")], returns=False)
entry_bb.append_invoke_instruction([IRLabel("check_cv")], returns=0)
entry_bb.append_instruction("jmp", IRLabel("wow"))

entry_fn.append_basic_block(wow_bb := IRBasicBlock(IRLabel("wow"), entry_fn))
Expand Down Expand Up @@ -366,3 +366,37 @@ def test_phis():

parsed_fn = next(iter(ctx.functions.values()))
assert_bb_eq(parsed_fn.get_basic_block(expect_bb.label.name), expect_bb)


def test_multi_output_last_var():
source = """
function main {
main:
%1, %2 = invoke @f
%3, %4, %5 = invoke @g
sink %1, %2, %3, %4, %5
}

function f {
f:
%retpc = param
ret 10, 20, %retpc
}

function g {
g:
%retpc = param
ret 30, 40, 50, %retpc
}
"""

parsed_ctx = parse_venom(source)

main_fn = parsed_ctx.get_function(IRLabel("main"))
assert main_fn.last_variable == 5

f_fn = parsed_ctx.get_function(IRLabel("f"))
assert f_fn.last_variable == 0

g_fn = parsed_ctx.get_function(IRLabel("g"))
assert g_fn.last_variable == 0
134 changes: 134 additions & 0 deletions tests/unit/compiler/venom/test_calling_convention.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import pytest

from tests.venom_utils import parse_venom
from vyper.venom.check_venom import (
InconsistentReturnArity,
InvokeArityMismatch,
MultiOutputNonInvoke,
check_calling_convention,
)


def _assert_raises(exc_group, exc_type):
assert any(isinstance(err, exc_type) for err in exc_group.exceptions)


def test_invoke_arity_match_zero():
src = """
function main {
main:
%p = source
invoke @f, %p
}

function f {
main:
%p = param
ret @retpc
}
"""
ctx = parse_venom(src)
# Should not raise: callee returns 0, call site binds 0
check_calling_convention(ctx)


def test_invoke_arity_match_one():
src = """
function main {
main:
%p = source
%ret = invoke @f, %p
sink %ret
}

function f {
main:
%p = param
%one = add %p, 1
ret %one, @retpc
}
"""
ctx = parse_venom(src)
# Should not raise: callee returns 1, call site binds 1
check_calling_convention(ctx)


def test_invoke_arity_mismatch_too_few_outputs():
src = """
function main {
main:
%p = source
invoke @f, %p
}

function f {
main:
%p = param
%one = add %p, 1
ret %one, @retpc
}
"""
ctx = parse_venom(src)
with pytest.raises(ExceptionGroup) as excinfo:
check_calling_convention(ctx)
_assert_raises(excinfo.value, InvokeArityMismatch)


def test_invoke_arity_mismatch_too_many_outputs():
src = """
function main {
main:
%p = source
%ret = invoke @f, %p
sink %ret
}

function f {
main:
%p = param
ret @retpc
}
"""
ctx = parse_venom(src)
with pytest.raises(ExceptionGroup) as excinfo:
check_calling_convention(ctx)
_assert_raises(excinfo.value, InvokeArityMismatch)


def test_inconsistent_callee_return_arity():
src = """
function main {
main:
%p = source
invoke @f, %p
}

function f {
entry:
%p = param
jnz %p, @then, @else
then:
%one = add %p, 1
ret %one, @retpc
else:
ret @retpc
}
"""
ctx = parse_venom(src)
with pytest.raises(ExceptionGroup) as excinfo:
check_calling_convention(ctx)
_assert_raises(excinfo.value, InconsistentReturnArity)


def test_multi_lhs_non_invoke_rejected():
src = """
function main {
main:
%x, %y = add 1, 2
sink %x, %y
}
"""
ctx = parse_venom(src)
with pytest.raises(ExceptionGroup) as excinfo:
check_calling_convention(ctx)
_assert_raises(excinfo.value, MultiOutputNonInvoke)
4 changes: 2 additions & 2 deletions tests/unit/compiler/venom/test_dominator_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ def test_phi_placement():
bb1, bb2, bb3, bb4, bb5, bb6, bb7 = [fn.get_basic_block(str(i)) for i in range(1, 8)]

x = IRVariable("%x")
bb1.insert_instruction(IRInstruction("mload", [IRLiteral(0)], x), 0)
bb2.insert_instruction(IRInstruction("add", [x, IRLiteral(1)], x), 0)
bb1.insert_instruction(IRInstruction("mload", [IRLiteral(0)], [x]), 0)
bb2.insert_instruction(IRInstruction("add", [x, IRLiteral(1)], [x]), 0)
bb7.insert_instruction(IRInstruction("mstore", [x, IRLiteral(0)]), 0)

ac = IRAnalysesCache(fn)
Expand Down
38 changes: 38 additions & 0 deletions tests/unit/compiler/venom/test_invoke_multi_return.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import pytest

from tests.hevm import hevm_check_venom_ctx
from vyper.venom.parser import parse_venom


@pytest.mark.hevm
def test_invoke_two_returns_executes_correctly():
a, b = 7, 9

pre = parse_venom(
f"""
function main {{
main:
%a, %b = invoke @f
sink %a, %b
}}

function f {{
f:
%retpc = param
%v0 = {a}
%v1 = {b}
ret %v0, %v1, %retpc
}}
"""
)

post = parse_venom(
f"""
function main {{
main:
sink {a}, {b}
}}
"""
)

hevm_check_venom_ctx(pre, post)
28 changes: 28 additions & 0 deletions tests/unit/compiler/venom/test_venom_to_assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,31 @@ def test_optimistic_swap_params():

asm = VenomCompiler(ctx).generate_evm_assembly()
assert asm == ["SWAP2", "PUSH1", 117, "POP", "MSTORE", "MSTORE", "JUMP"]


def test_invoke_middle_output_unused():
code = """
function main {
main:
%a, %b, %c = invoke @callee
return %a, %c
}

function callee {
callee:
%retpc = param
%x = 1
%y = 2
%z = 3
ret %x, %y, %z, %retpc
}
"""
ctx = parse_venom(code)
asm = VenomCompiler(ctx).generate_evm_assembly()

assert "POP" in asm, f"expected POP to remove dead output, got {asm}"
pop_idx = asm.index("POP")
assert pop_idx > 0 and asm[pop_idx - 1] == "SWAP1", asm
assert "RETURN" in asm, asm
return_idx = asm.index("RETURN")
assert return_idx > pop_idx and asm[return_idx - 1] == "SWAP1", asm
6 changes: 5 additions & 1 deletion tests/venom_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ def parse_from_basic_block(source: str, funcname="_global"):


def instructions_eq(i1: IRInstruction, i2: IRInstruction) -> bool:
return i1.output == i2.output and i1.opcode == i2.opcode and i1.operands == i2.operands
return (
i1.get_outputs() == i2.get_outputs()
and i1.opcode == i2.opcode
and i1.operands == i2.operands
)


def assert_bb_eq(bb1: IRBasicBlock, bb2: IRBasicBlock):
Expand Down
3 changes: 3 additions & 0 deletions vyper/venom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from vyper.venom.analysis import MemSSA
from vyper.venom.analysis.analysis import IRAnalysesCache
from vyper.venom.basicblock import IRLabel, IRLiteral
from vyper.venom.check_venom import check_calling_convention
from vyper.venom.context import IRContext
from vyper.venom.function import IRFunction
from vyper.venom.ir_node_to_venom import ir_node_to_venom
Expand Down Expand Up @@ -121,6 +122,8 @@ def _run_global_passes(ctx: IRContext, optimize: OptimizationLevel, ir_analyses:

def run_passes_on(ctx: IRContext, optimize: OptimizationLevel) -> None:
ir_analyses = {}
# Validate calling convention invariants before running passes
check_calling_convention(ctx)
for fn in ctx.functions.values():
ir_analyses[fn] = IRAnalysesCache(fn)

Expand Down
4 changes: 4 additions & 0 deletions vyper/venom/analysis/available_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ def _handle_bb(self, bb: IRBasicBlock) -> bool:
for inst in bb.instructions:
if inst.opcode == "assign" or inst.is_pseudo or inst.is_bb_terminator:
continue
if inst.num_outputs > 1:
continue

if (
inst not in self.inst_to_available
Expand Down Expand Up @@ -341,6 +343,8 @@ def _get_operand(
# source is a magic opcode for tests
if inst.opcode == "source":
return op
if inst.num_outputs > 1:
return op

assert inst in self.inst_to_expr, f"operand source was not handled, ({op}, {inst})"
return self.inst_to_expr[inst]
Expand Down
6 changes: 2 additions & 4 deletions vyper/venom/analysis/dfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ def analyze(self):
inputs = self._dfg_inputs.setdefault(op, OrderedSet())
inputs.add(inst)

for op in res: # type: ignore
assert isinstance(op, IRVariable)
for op in res:
self._dfg_outputs[op] = inst

def as_graph(self) -> str:
Expand All @@ -96,8 +95,7 @@ def as_graph(self) -> str:
for var, inputs in self._dfg_inputs.items():
for input in inputs:
for op in input.get_outputs():
if isinstance(op, IRVariable):
lines.append(f' " {var.name} " -> " {op.name} "')
lines.append(f' " {var.name} " -> " {op.name} "')

lines.append("}")
return "\n".join(lines)
Expand Down
5 changes: 2 additions & 3 deletions vyper/venom/analysis/stack_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ def analyze_bb(self, bb: IRBasicBlock) -> Needed:
inst.operands,
)
self.stack = self.stack[: -len(inst.operands)]
if inst.output is not None:
self.stack.append(inst.output)
self.stack.extend(inst.get_outputs())

for pred in self.cfg.cfg_in(bb):
self._from_to[(pred, bb)] = self.needed.copy()
Expand All @@ -97,7 +96,7 @@ def from_to(self, origin: IRBasicBlock, successor: IRBasicBlock) -> Needed:

def _handle_assign(self, inst: IRInstruction):
assert inst.opcode == "assign"
assert inst.output is not None
_ = inst.output # Assert single output

index = inst.parent.instructions.index(inst)
next_inst = inst.parent.instructions[index + 1]
Expand Down
Loading