Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix[venom]: liveness analysis in some loops #3732

Merged
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
51 changes: 51 additions & 0 deletions tests/unit/compiler/venom/test_convert_basicblock_simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from vyper.codegen.ir_node import IRnode
from vyper.compiler.settings import OptimizationLevel
from vyper.venom import generate_ir


def test_simple():
ir = ["calldatacopy", 32, 0, ["calldatasize"]]
ir_node = IRnode.from_list(ir)
deploy, runtime = generate_ir(ir_node, OptimizationLevel.NONE)
assert deploy is None
assert runtime is not None

bb = runtime.basic_blocks[0]
assert bb.instructions[0].opcode == "calldatasize"
assert bb.instructions[1].opcode == "calldatacopy"


def test_simple_2():
ir = [
"seq",
[
"seq",
[
"mstore",
["add", 64, 0],
[
"with",
"x",
["calldataload", ["add", 4, 0]],
[
"with",
"ans",
["add", "x", 1],
["seq", ["assert", ["ge", "ans", "x"]], "ans"],
],
],
],
],
32,
]
ir_node = IRnode.from_list(ir)
deploy, runtime = generate_ir(ir_node, OptimizationLevel.NONE)
assert deploy is None
assert runtime is not None

print(runtime)


if __name__ == "__main__":
test_simple()
test_simple_2()
16 changes: 16 additions & 0 deletions tests/unit/compiler/venom/test_liveness_simple_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import vyper

source = """
@external
def foo(a: uint256):
_numBids: uint256 = 20
b: uint256 = 10

for i: uint256 in range(128):
b = 1 + _numBids
"""


def test_liveness_simple_loop():
vyper.compile_code(source, ["opcodes"])
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
assert True
10 changes: 7 additions & 3 deletions vyper/venom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,15 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None:
break


def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> tuple[IRFunction, IRFunction]:
def generate_ir(
ir: IRnode, optimize: OptimizationLevel
) -> tuple[Optional[IRFunction], Optional[IRFunction]]:
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
# Convert "old" IR to "new" IR
ctx, ctx_runtime = convert_ir_basicblock(ir)

_run_passes(ctx, optimize)
_run_passes(ctx_runtime, optimize)
if ctx:
_run_passes(ctx, optimize)
if ctx_runtime:
_run_passes(ctx_runtime, optimize)

return ctx, ctx_runtime
36 changes: 20 additions & 16 deletions vyper/venom/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ def _reset_liveness(ctx: IRFunction) -> None:
inst.liveness = OrderedSet()


def _calculate_liveness_bb(bb: IRBasicBlock) -> None:
def _calculate_liveness(bb: IRBasicBlock) -> bool:
"""
Compute liveness of each instruction in the basic block.
Returns True if liveness changed
"""
orig_liveness = bb.instructions[0].liveness.copy()
liveness = bb.out_vars.copy()
for instruction in reversed(bb.instructions):
ops = instruction.get_inputs()
Expand All @@ -60,29 +62,31 @@ def _calculate_liveness_bb(bb: IRBasicBlock) -> None:
liveness.remove(out)
instruction.liveness = liveness

return orig_liveness != bb.instructions[0].liveness

def _calculate_liveness_r(bb: IRBasicBlock, visited: dict) -> None:
assert isinstance(visited, dict)
for out_bb in bb.cfg_out:
if visited.get(bb) == out_bb:
continue
visited[bb] = out_bb

# recurse
_calculate_liveness_r(out_bb, visited)

def _calculate_out_vars(bb: IRBasicBlock) -> bool:
"""
Compute out_vars of basic block.
Returns True if out_vars changed
"""
out_vars = bb.out_vars.copy()
for out_bb in bb.cfg_out:
target_vars = input_vars_from(bb, out_bb)

# the output stack layout for bb. it produces a stack layout
# which works for all possible cfg_outs from the bb.
bb.out_vars = bb.out_vars.union(target_vars)

_calculate_liveness_bb(bb)
return out_vars != bb.out_vars


def calculate_liveness(ctx: IRFunction) -> None:
_reset_liveness(ctx)
_calculate_liveness_r(ctx.basic_blocks[0], dict())
while True:
changed = False
for bb in ctx.basic_blocks:
changed |= _calculate_out_vars(bb)
changed |= _calculate_liveness(bb)
harkal marked this conversation as resolved.
Show resolved Hide resolved

if changed is False:
harkal marked this conversation as resolved.
Show resolved Hide resolved
break


# calculate the input variables into self from source
Expand Down
4 changes: 2 additions & 2 deletions vyper/venom/basicblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,8 @@ def copy(self):
def __repr__(self) -> str:
s = (
f"{repr(self.label)}: IN={[bb.label for bb in self.cfg_in]}"
f" OUT={[bb.label for bb in self.cfg_out]} => {self.out_vars} \n"
f" OUT={[bb.label for bb in self.cfg_out]} => {self.out_vars}\n"
)
for instruction in self.instructions:
s += f" {instruction}\n"
s += f" {str(instruction).strip()}\n"
return s
2 changes: 1 addition & 1 deletion vyper/venom/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,4 @@ def __repr__(self) -> str:
str += "Data segment:\n"
for inst in self.data_segment:
str += f"{inst}\n"
return str
return str.strip()
Loading
Loading