Summary
PhiEliminationPass can treat a phi over two different outputs of the same multi-output invoke as trivial because _get_phi_origins_r keys origins by producing instruction, not by produced variable.
For this shape:
%r1, %r2 = invoke @f, ...
%z = phi @t, %r1, @e, %r2
both operands trace to the same invoke instruction. _process_phi then sees a single origin and tries to assign from src.output. In normal Python this raises because the invoke has multiple outputs; under python -O it would silently pick the first output and miscompile the %r2 path.
Reproducer
This reproduces from plain Vyper with experimental codegen and -O gas. The callee needs to stay uninlined, and there need to be two call sites so the invoke survives.
import vyper
from vyper.compiler.settings import Settings, OptimizationLevel
body = "\n".join(f" x{i}: uint256 = p + {i}" for i in range(40))
src = f"""
@internal
def two(p: uint256) -> (uint256, uint256):
{body}
return p + x0, p * 2
@external
def f(c: bool, p: uint256) -> uint256:
a: uint256 = 0
b: uint256 = 0
a, b = self.two(p)
x: uint256 = b
if c:
x = a
return x
@external
def g(p: uint256) -> uint256:
a: uint256 = 0
b: uint256 = 0
a, b = self.two(p)
return a + b
"""
vyper.compile_code(src, settings=Settings(experimental_codegen=True, optimize=OptimizationLevel.GAS))
Observed failure:
AssertionError: expected single output for %24, %25 = invoke @"internal 0 two(uint256)_runtime", %19
Root cause
vyper/venom/passes/phi_elimination.py:_get_phi_origins_r returns origins by instruction. For multi-output producers, instruction identity is not enough to distinguish %r1 from %r2.
Fix direction
Key phi origins by (producer_instruction, produced_variable), or conservatively bail out of _process_phi when the apparent single source instruction has num_outputs != 1.
Summary
PhiEliminationPasscan treat a phi over two different outputs of the same multi-outputinvokeas trivial because_get_phi_origins_rkeys origins by producing instruction, not by produced variable.For this shape:
both operands trace to the same invoke instruction.
_process_phithen sees a single origin and tries to assign fromsrc.output. In normal Python this raises because the invoke has multiple outputs; underpython -Oit would silently pick the first output and miscompile the%r2path.Reproducer
This reproduces from plain Vyper with experimental codegen and
-O gas. The callee needs to stay uninlined, and there need to be two call sites so theinvokesurvives.Observed failure:
Root cause
vyper/venom/passes/phi_elimination.py:_get_phi_origins_rreturns origins by instruction. For multi-output producers, instruction identity is not enough to distinguish%r1from%r2.Fix direction
Key phi origins by
(producer_instruction, produced_variable), or conservatively bail out of_process_phiwhen the apparent single source instruction hasnum_outputs != 1.