Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e3c8523
test that disables optional passes
HodanPlodky Aug 13, 2026
2b6d944
fix for remove unused variables (removing insts with read effect)
HodanPlodky Aug 13, 2026
7809019
fix for the case sccp does not guarantee that
HodanPlodky Aug 14, 2026
06ff659
test with proper fuzz
HodanPlodky Aug 14, 2026
e42e9a1
remove poors man fuzzing from the normal tests
HodanPlodky Aug 14, 2026
038cd56
lint
HodanPlodky Aug 14, 2026
84c1a5b
check for spaces that do not have alias analysis
HodanPlodky Aug 17, 2026
26a8a63
removed check if it ran (does not change the validity of the test)
HodanPlodky Aug 17, 2026
4a8f25f
lint
HodanPlodky Aug 17, 2026
73f093c
monkey patch in context to remove interference
HodanPlodky Aug 25, 2026
da07d23
Merge branch 'master' into fix/venom/pass-disable-testing
HodanPlodky Aug 26, 2026
6da9c9f
added RemoveUnusedVariablesPass pass to the fuzzed passes and fixed bugs
HodanPlodky Aug 27, 2026
c3159ac
removed temp test
HodanPlodky Aug 27, 2026
4ef25da
lint
HodanPlodky Aug 27, 2026
770ee21
Merge branch 'master' into fix/venom/pass-disable-testing
HodanPlodky Aug 28, 2026
73616d0
Merge branch 'master' into fix/venom/pass-disable-testing
HodanPlodky Aug 31, 2026
64a85d4
lint
HodanPlodky Aug 31, 2026
3d4fc91
fix for insts that have effects outside of the alias
HodanPlodky Aug 31, 2026
ad1f1f6
assert for the retpc in inliner and correct format in tests
HodanPlodky Sep 1, 2026
79130e8
used retpc_param instead of param in dalloca test
HodanPlodky Sep 1, 2026
7b90af6
another param -> retpc_param
HodanPlodky Sep 1, 2026
a4ba5ba
cleanup few asserts
HodanPlodky Sep 1, 2026
0262903
removed test
HodanPlodky Sep 1, 2026
0dc1f89
test for venom emit with unuses vars
HodanPlodky Sep 2, 2026
00f04b9
check that the dead items are used
HodanPlodky Sep 2, 2026
ba12d1b
removed test change (done in test PR)
HodanPlodky Sep 3, 2026
c061354
Merge branch 'master' into fix/venom/pass-disable-testing-fixes
harkal Sep 3, 2026
9efa351
added the inliner test to show the error clearly
HodanPlodky Sep 4, 2026
73a88e0
Merge branch 'fix/venom/pass-disable-testing-fixes' of github.com:Hod…
HodanPlodky Sep 4, 2026
99b2fa8
Merge branch 'master' into fix/venom/pass-disable-testing-fixes
HodanPlodky Sep 4, 2026
9e10c9e
used `FunctionCallLayout` in inliner
HodanPlodky Sep 4, 2026
5dc0451
Merge branch 'master' into fix/venom/pass-disable-testing-fixes
HodanPlodky Sep 7, 2026
4bd07cf
Merge branch 'master' into fix/venom/pass-disable-testing-fixes
HodanPlodky Sep 9, 2026
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: 1 addition & 1 deletion tests/unit/compiler/venom/test_calling_convention.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ def test_dret_rejects_mixing_ret_and_dret():
main:
%p1 = param
%p2 = param
%retpc = param
%retpc = retpc_param
%x = %p1
%x = %p2
mstore 0, %x
Expand Down
8 changes: 4 additions & 4 deletions tests/unit/compiler/venom/test_dalloca.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ def test_dret_full_pipeline_with_and_without_inlining(env):

function callee {
callee:
%retpc = param
%retpc = retpc_param
%p = dalloca 32
mstore %p, 123
dret 1, %p, 32, %retpc
Expand Down Expand Up @@ -928,7 +928,7 @@ def test_unreachable_raw_dret_does_not_block_inlining():

function dret_callee {
dret_callee:
%retpc = param
%retpc = retpc_param
%p = dalloca 32
mstore %p, 7
dret 1, %p, 32, %retpc
Expand Down Expand Up @@ -1133,7 +1133,7 @@ def test_inlined_publishing_callee_host_does_not_publish(env):

function callee {
callee:
%retpc = param
%retpc = retpc_param
%p = dalloca 32
mstore %p, 123
dret 1, %p, 32, %retpc
Expand Down Expand Up @@ -1758,7 +1758,7 @@ def test_escaped_getfmp_capture_pins_reclaim(env):

function callee {
callee:
%retpc = param
%retpc = retpc_param
%s1 = dalloca 32
%s2 = dalloca 32
mstore %s1, 11
Expand Down
49 changes: 44 additions & 5 deletions tests/unit/compiler/venom/test_inliner.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from tests.venom_utils import parse_venom
from vyper.compiler.settings import OptimizationLevel, VenomOptimizationFlags
from vyper.evm.assembler.core import assembly_to_evm
from vyper.evm.assembler.instructions import PUSHLABEL, Label
from vyper.venom.analysis.analysis import IRAnalysesCache
from vyper.venom.analysis.fcg import FCGGlobalAnalysis
from vyper.venom.basicblock import IRLabel
from vyper.venom.check_venom import check_venom_ctx
from vyper.venom.passes import FunctionInlinerPass, SimplifyCFGPass
from vyper.venom.venom_to_assembly import VenomCompiler


def test_inliner_phi_invalidation():
Expand Down Expand Up @@ -35,9 +38,10 @@ def test_inliner_phi_invalidation():

function f {
main:
%retpc = retpc_param
%p = source
%1 = add %p, 1
ret %1
ret %retpc, %1
}
"""

Expand Down Expand Up @@ -91,9 +95,10 @@ def test_inliner_phi_invalidation_inner():

function f {
main:
%retpc = retpc_param
%p = source
%1 = add %p, 1
ret %1
ret %retpc, %1
}
"""

Expand Down Expand Up @@ -152,7 +157,7 @@ def test_inliner_preserves_memory_read_max_size():
%dst = param
%src = param
%size = param
%retpc = param
%retpc = retpc_param
mcopy %dst, %src, %size
ret %retpc
}
Expand Down Expand Up @@ -184,9 +189,9 @@ def test_noinline_annotation():

function f [noinline] {
f:
%retpc = param
%retpc = retpc_param
%1 = 42
ret %1, %retpc
ret %retpc, %1
}
"""

Expand All @@ -203,3 +208,37 @@ def run_inliner(source):

# sanity check: without the annotation, the function gets inlined
assert IRLabel("f") not in run_inliner(src.replace(" [noinline]", "")).functions


def test_inliner_retpc_retain():
code = """
function main {
main:
%1 = invoke @f
%ptr = alloca 32
mstore %ptr, %1
return %ptr, 32
}

function f {
f:
%retpc = retpc_param
%1 = 42
ret %retpc, %1
}
"""

flags = VenomOptimizationFlags(level=OptimizationLevel.CODESIZE)

def run_inliner(source):
ctx = parse_venom(source)
analyses = {fn: IRAnalysesCache(fn) for fn in ctx.functions.values()}
FunctionInlinerPass(analyses, ctx, flags).run_pass()
for fn in ctx.functions.values():
SimplifyCFGPass(analyses[fn], fn).run_pass()
return ctx

ctx = run_inliner(code)
asm = VenomCompiler(ctx).generate_evm_assembly()
assert PUSHLABEL(Label("f")) not in asm
_ = assembly_to_evm(asm)
18 changes: 18 additions & 0 deletions tests/unit/compiler/venom/test_removeunused.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from tests.venom_utils import assert_ctx_eq, parse_from_basic_block
from vyper.evm.address_space import MEMORY
from vyper.venom.analysis import MemSSA
Expand Down Expand Up @@ -112,3 +114,19 @@ def test_removeunused_invalidates_memory_ssa():
# with fresh MemorySSA the store has no reader left, so DSE removes it
DeadStoreElimination(ac, fn).run_pass(MEMORY)
assert not any(inst.opcode == "mstore" for inst in fn.entry.instructions)


@pytest.mark.parametrize(
"opcode", ["balance", "selfbalance", "extcodesize", "extcodehash", "returndatasize"]
)
def test_removeunused_non_alias_supported_opcodes(opcode):
pre = f"""
main:
%1 = {opcode}
stop
"""
post = """
main:
stop
"""
_check_pre_post(pre, post)
24 changes: 24 additions & 0 deletions tests/unit/compiler/venom/test_venom_to_assembly.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from tests.venom_utils import parse_from_basic_block
from vyper.compiler import compile_code
from vyper.compiler.settings import Settings
from vyper.venom.basicblock import IRVariable
Expand Down Expand Up @@ -77,6 +78,29 @@ def test_invoke_middle_output_unused():
assert pop_idx > 0 and asm[pop_idx - 1] == "SWAP1", asm


def test_unused_clean_stack_from_cfg_in():
code = """
main:
%unused = offset @else, 32
%cond = calldataload 0
jnz %cond, @then, @else
then:
%a = calldataload 0
jmp @after
else:
jmp @after
after:
%x = phi @else, %cond, @then, %a
%ptr = alloca 32
mstore %ptr, %x
return %ptr, 32
"""

ctx = parse_from_basic_block(code)
asm = VenomCompiler(ctx).generate_evm_assembly()
assert "POP" not in asm


def test_popmany_bulk_removal_of_suffix():
compiler = VenomCompiler(IRContext())
stack = StackModel()
Expand Down
4 changes: 4 additions & 0 deletions vyper/venom/analysis/mem_alias.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,7 @@ def mem_alias_type_factory(addr_space: AddrSpace) -> type[MemoryAliasAnalysisAbs
return TransientAliasAnalysis
else: # pragma: nocover
raise ValueError(f"Invalid address space: {addr_space}")


def can_create_mem_alias(addr_space: AddrSpace) -> bool:
return addr_space in (MEMORY, STORAGE, TRANSIENT)
10 changes: 9 additions & 1 deletion vyper/venom/passes/branch_optimization.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from vyper.utils import OrderedSet
from vyper.venom.analysis import CFGAnalysis, DFGAnalysis, LivenessAnalysis
from vyper.venom.basicblock import COMPARATOR_INSTRUCTIONS, IRBasicBlock, IRInstruction, IRLiteral
from vyper.venom.basicblock import (
COMPARATOR_INSTRUCTIONS,
IRBasicBlock,
IRInstruction,
IRLiteral,
IRVariable,
)
from vyper.venom.passes.base_pass import InstUpdater, IRPass


Expand Down Expand Up @@ -45,6 +51,8 @@ def _optimize_branches(self) -> None:
cost_a, cost_b = len(fst_liveness), len(snd_liveness)

cond = term_inst.operands[0]
if not isinstance(cond, IRVariable):
continue
prev_inst = self.dfg.get_producing_instruction(cond)
assert prev_inst is not None

Expand Down
11 changes: 9 additions & 2 deletions vyper/venom/passes/function_inliner.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from vyper.venom.analysis.fcg import FCGGlobalAnalysis
from vyper.venom.analysis.readonly_memory_args import ReadonlyMemoryArgsGlobalAnalysis
from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IROperand, IRVariable
from vyper.venom.call_layout import InvokeLayout, has_dret
from vyper.venom.call_layout import FunctionCallLayout, InvokeLayout, has_dret
from vyper.venom.context import IRContext
from vyper.venom.function import IRFunction
from vyper.venom.passes.base_pass import IRGlobalPass
Expand Down Expand Up @@ -132,13 +132,18 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None:
# operands[1:] + [operands[0]] reorder for raw IR.
binding_ops = InvokeLayout(self.ctx, call_site).bound_params

layout = FunctionCallLayout(func_copy)
retpc_inst = layout.retpc_param_opcode_inst
assert retpc_inst is not None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow inlining callees with no return-PC parameter

When valid raw Venom invokes a non-returning callee such as a parameterless function ending in stop or revert, its FunctionCallLayout legitimately has no return-PC parameter, yet a single call site selects it for inlining and this assertion aborts compilation. The calling-convention validator explicitly permits callees with no return instructions and expects zero invoke outputs, and the previous inliner could inline this shape because it never required a return-PC binding; only require/remove the parameter when one exists.

Useful? React with 👍 / 👎.

retpc_op = retpc_inst.output
retpc_inst.make_nop()
for bb in func_copy.get_basic_blocks():
bb.parent = call_site_func
call_site_func.append_basic_block(bb)
param_idx = 0
for inst in bb.instructions:
if inst.is_param:
# NOTE: one of these params is the return pc.
assert inst.opcode != "retpc_param"
inst.opcode = "assign"
val = binding_ops[param_idx]
inst.operands = [val]
Expand All @@ -156,6 +161,7 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None:
# host's own reclaim governs the inlined data. Whether the
# host publishes is determined solely by the host's own
# terminators (plain `ret` is callee-save).
assert retpc_op in inst.operands, retpc_op

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve plain return-PC params during inlining

When a valid raw Venom callee uses a plain param as its return PC, retpc_op remains None, so every ret reaches this assertion and aborts compilation. FunctionCallLayout explicitly supports discovering plain return-PC params in raw IR, and the full pipeline runs this inliner before FmpLoweringPass; therefore changing the tests to retpc_param does not eliminate the accepted input path. Resolve the return-PC through the call layout or preserve the previous binding behavior instead of requiring the dedicated opcode here.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve return-PC aliases before removing the parameter

When a callee declares %retpc = retpc_param but returns through an accepted alias such as %copy = %retpc; ret %copy, this assertion fails because it requires the original variable to occur directly in every return. FunctionCallLayout.param_for_alias and the calling-convention validator explicitly accept assign/phi aliases of the return PC, so valid raw Venom still aborts during inlining even though the dedicated opcode is present. Identify return operands through the layout or rewrite aliases before deleting the parameter.

Useful? React with 👍 / 👎.

ret_values = [op for op in inst.operands[:-1] if not isinstance(op, IRLabel)]

# Map each returned value to corresponding callsite outputs
Expand All @@ -173,6 +179,7 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None:

for inst in bb.instructions:
if not inst.annotation:
assert retpc_op not in inst.operands, (inst, retpc_op)
inst.annotation = f"from {func.name}"

call_site_bb.instructions = call_site_bb.instructions[:call_idx]
Expand Down
30 changes: 28 additions & 2 deletions vyper/venom/passes/remove_unused_variables.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from vyper.evm import address_space
from vyper.utils import OrderedSet, uniq
from vyper.venom import effects
from vyper.venom.analysis import BasePtrAnalysis, DFGAnalysis, LivenessAnalysis
from vyper.venom.analysis.load_analysis import LoadAnalysis
from vyper.venom.analysis.mem_alias import (
MemoryAliasAnalysis,
StorageAliasAnalysis,
TransientAliasAnalysis,
can_create_mem_alias,
mem_alias_type_factory,
)
from vyper.venom.analysis.mem_ssa import MemSSA, StorageSSA, TransientSSA
from vyper.venom.basicblock import IRInstruction
from vyper.venom.effects import EMPTY, FMP
from vyper.venom.passes.base_pass import IRPass


Expand All @@ -19,8 +23,11 @@ class RemoveUnusedVariablesPass(IRPass):
dfg: DFGAnalysis
work_list: OrderedSet[IRInstruction]

invalidate_alias: set[address_space.AddrSpace]

def run_pass(self):
self.dfg = self.analyses_cache.request_analysis(DFGAnalysis)
self.invalidate_alias = set()

work_list = OrderedSet()
self.work_list = work_list
Expand All @@ -45,14 +52,16 @@ def run_pass(self):
# invalidations below only cascade to them when the parent is
# actually cached, but the alias analyses can also be requested
# (and cached) on their own
self.analyses_cache.invalidate_analysis(MemoryAliasAnalysis)
self.analyses_cache.invalidate_analysis(StorageAliasAnalysis)
self.analyses_cache.invalidate_analysis(TransientAliasAnalysis)
self.analyses_cache.invalidate_analysis(LoadAnalysis)
self.analyses_cache.invalidate_analysis(MemSSA)
self.analyses_cache.invalidate_analysis(StorageSSA)
self.analyses_cache.invalidate_analysis(TransientSSA)
self.analyses_cache.invalidate_analysis(LivenessAnalysis)
for space in self.invalidate_alias:
alias_analysis = mem_alias_type_factory(space)
self.analyses_cache.invalidate_analysis(alias_analysis)
Comment on lines +62 to +64

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate memory aliases after every IR deletion

When this pass removes only pure dead instructions, such as an unused add, invalidate_alias remains empty and a directly cached MemoryAliasAnalysis survives. However, that analysis retains the DFGAnalysis and BasePtrAnalysis instances invalidated above (DFG invalidation even deletes its internal maps), so a subsequent query such as pointer_uses_may_touch can crash or use stale facts. Preserve the previous unconditional memory-alias invalidation whenever the pass changes IR.

Useful? React with 👍 / 👎.


def _process_instruction(self, inst) -> bool:
outputs = inst.get_outputs()
Expand All @@ -72,5 +81,22 @@ def _process_instruction(self, inst) -> bool:
new_uses = self.dfg.get_uses(operand)
self.work_list.addmany(new_uses)

# instructions that handle FMP can be removed if there is no use for the
# since they either bump or set only at the end of the function so the
# removal of the instruction will not effect other instructions.
# Other write effect should not be removed by this analysis
assert inst.get_write_effects() == EMPTY or inst.get_write_effects() == FMP

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle dead dload before asserting write effects

When RemoveUnusedVariablesPass is run before LowerDloadPass on valid raw Venom containing an unused %value = dload ..., the instruction is non-volatile and has no uses, but its declared write effect is MEMORY, so this new assertion aborts the pass. The pass previously removed this dead pseudo-load safely, and it has no ordering constraint requiring LowerDloadPass first; either retain that behavior or explicitly handle dload rather than asserting.

Useful? React with 👍 / 👎.

effs = inst.get_read_effects()
if effs != EMPTY:
for eff in effs:
space = effects.to_addr_space(eff)
if space is None:
# sanity check
continue

# Mem alias does not use all address spaces
if can_create_mem_alias(space):
self.invalidate_alias.add(space)

inst.make_nop()
return True
8 changes: 7 additions & 1 deletion vyper/venom/venom_to_assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,13 @@ def clean_stack_from_cfg_in(
# for making sure its output stack layout works no matter which
# bb it jumps into).
layout = self.liveness.out_vars(in_bb)
to_pop = list(layout.difference(inputs))
to_pop = []
for item in stack._stack:
if not isinstance(item, IRVariable):
continue
if item not in inputs and item not in layout:
to_pop.append(item)
to_pop.extend(list(layout.difference(inputs)))
self._assert_dead_stack_prefix(stack)
physical_to_pop = [
var for var in to_pop if stack.get_depth(var) is not StackModel.NOT_IN_STACK
Expand Down
Loading