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

refactor[venom]: introduce IRContext and IRAnalysisCache #3983

Merged
merged 10 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
4 changes: 3 additions & 1 deletion tests/unit/compiler/venom/test_convert_basicblock_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ def test_simple():
venom = ir_node_to_venom(ir_node)
assert venom is not None

bb = venom.basic_blocks[0]
fn = list(venom.functions.values())[0]

bb = fn.entry
assert bb.instructions[0].opcode == "calldatasize"
assert bb.instructions[1].opcode == "calldatacopy"

Expand Down
46 changes: 24 additions & 22 deletions tests/unit/compiler/venom/test_dominator_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

from vyper.exceptions import CompilerPanic
from vyper.utils import OrderedSet
from vyper.venom.analysis import calculate_cfg
from vyper.venom.analysis.dominators import DominatorTreeAnalysis
from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRLiteral, IRVariable
from vyper.venom.dominators import DominatorTree
from vyper.venom.context import IRContext
from vyper.venom.function import IRFunction
from vyper.venom.passes.make_ssa import MakeSSA
from vyper.venom.passes.pass_manager import IRPassManager


def _add_bb(
ctx: IRFunction, label: IRLabel, cfg_outs: [IRLabel], bb: Optional[IRBasicBlock] = None
fn: IRFunction, label: IRLabel, cfg_outs: list[IRLabel], bb: Optional[IRBasicBlock] = None
) -> IRBasicBlock:
bb = bb if bb is not None else IRBasicBlock(label, ctx)
ctx.append_basic_block(bb)
bb = bb if bb is not None else IRBasicBlock(label, fn)
fn.append_basic_block(bb)
cfg_outs_len = len(cfg_outs)
if cfg_outs_len == 0:
bb.append_instruction("stop")
Expand All @@ -29,27 +30,27 @@ def _add_bb(
def _make_test_ctx():
lab = [IRLabel(str(i)) for i in range(0, 9)]

ctx = IRFunction(lab[1])
ctx = IRContext()
fn = ctx.create_function(lab[1].value)

bb1 = ctx.basic_blocks[0]
bb1.append_instruction("jmp", lab[2])
fn.entry.append_instruction("jmp", lab[2])

_add_bb(ctx, lab[7], [])
_add_bb(ctx, lab[6], [lab[7], lab[2]])
_add_bb(ctx, lab[5], [lab[6], lab[3]])
_add_bb(ctx, lab[4], [lab[6]])
_add_bb(ctx, lab[3], [lab[5]])
_add_bb(ctx, lab[2], [lab[3], lab[4]])
_add_bb(fn, lab[7], [])
_add_bb(fn, lab[6], [lab[7], lab[2]])
_add_bb(fn, lab[5], [lab[6], lab[3]])
_add_bb(fn, lab[4], [lab[6]])
_add_bb(fn, lab[3], [lab[5]])
_add_bb(fn, lab[2], [lab[3], lab[4]])

return ctx
return fn


def test_deminator_frontier_calculation():
ctx = _make_test_ctx()
bb1, bb2, bb3, bb4, bb5, bb6, bb7 = [ctx.get_basic_block(str(i)) for i in range(1, 8)]
fn = _make_test_ctx()
bb1, bb2, bb3, bb4, bb5, bb6, bb7 = [fn.get_basic_block(str(i)) for i in range(1, 8)]

calculate_cfg(ctx)
dom = DominatorTree.build_dominator_tree(ctx, bb1)
pm = IRPassManager(fn)
dom = pm.request_analysis(DominatorTreeAnalysis)
df = dom.dominator_frontiers

assert len(df[bb1]) == 0, df[bb1]
Expand All @@ -62,12 +63,13 @@ def test_deminator_frontier_calculation():


def test_phi_placement():
ctx = _make_test_ctx()
bb1, bb2, bb3, bb4, bb5, bb6, bb7 = [ctx.get_basic_block(str(i)) for i in range(1, 8)]
fn = _make_test_ctx()
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)
bb7.insert_instruction(IRInstruction("mstore", [x, IRLiteral(0)]), 0)

MakeSSA().run_pass(ctx, bb1)
pm = IRPassManager(fn)
MakeSSA(pm).run_pass()
7 changes: 4 additions & 3 deletions tests/unit/compiler/venom/test_duplicate_operands.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from vyper.compiler.settings import OptimizationLevel
from vyper.venom import generate_assembly_experimental
from vyper.venom.function import IRFunction
from vyper.venom.context import IRContext


def test_duplicate_operands():
Expand All @@ -15,8 +15,9 @@ def test_duplicate_operands():

Should compile to: [PUSH1, 10, DUP1, DUP1, DUP1, ADD, MUL, STOP]
"""
ctx = IRFunction()
bb = ctx.get_basic_block()
ctx = IRContext()
fn = ctx.create_function("test")
bb = fn.get_basic_block()
op = bb.append_instruction("store", 10)
sum_ = bb.append_instruction("add", op, op)
bb.append_instruction("mul", sum_, op)
Expand Down
32 changes: 16 additions & 16 deletions tests/unit/compiler/venom/test_make_ssa.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
from vyper.venom.analysis import calculate_cfg, calculate_liveness
from vyper.venom.basicblock import IRBasicBlock, IRLabel
from vyper.venom.function import IRFunction
from vyper.venom.context import IRContext
from vyper.venom.passes.make_ssa import MakeSSA
from vyper.venom.passes.pass_manager import IRPassManager


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

bb = ctx.get_basic_block()
bb = fn.get_basic_block()

bb_cont = IRBasicBlock(IRLabel("condition"), ctx)
bb_then = IRBasicBlock(IRLabel("then"), ctx)
bb_else = IRBasicBlock(IRLabel("else"), ctx)
bb_if_exit = IRBasicBlock(IRLabel("if_exit"), ctx)
ctx.append_basic_block(bb_cont)
ctx.append_basic_block(bb_then)
ctx.append_basic_block(bb_else)
ctx.append_basic_block(bb_if_exit)
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)
Expand All @@ -30,11 +31,10 @@ def test_phi_case():

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

calculate_cfg(ctx)
MakeSSA().run_pass(ctx, ctx.basic_blocks[0])
calculate_liveness(ctx)
pm = IRPassManager(fn)
MakeSSA(pm).run_pass()

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

phi_inst = condition_block.instructions[0]
Expand Down
102 changes: 55 additions & 47 deletions tests/unit/compiler/venom/test_multi_entry_block.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,48 @@
from vyper.venom.analysis import calculate_cfg
from vyper.venom.function import IRBasicBlock, IRFunction, IRLabel
from vyper.venom.analysis.cfg import CFGAnalysis
from vyper.venom.context import IRContext
from vyper.venom.function import IRBasicBlock, IRLabel
from vyper.venom.passes.normalization import NormalizationPass
from vyper.venom.passes.pass_manager import IRPassManager


def test_multi_entry_block_1():
ctx = IRFunction()
ctx = IRContext()
fn = ctx.create_function("__global")

finish_label = IRLabel("finish")
target_label = IRLabel("target")
block_1_label = IRLabel("block_1", ctx)
block_1_label = IRLabel("block_1", fn)

bb = ctx.get_basic_block()
bb = fn.get_basic_block()
op = bb.append_instruction("store", 10)
acc = bb.append_instruction("add", op, op)
bb.append_instruction("jnz", acc, finish_label, block_1_label)

block_1 = IRBasicBlock(block_1_label, ctx)
ctx.append_basic_block(block_1)
block_1 = IRBasicBlock(block_1_label, fn)
fn.append_basic_block(block_1)
acc = block_1.append_instruction("add", acc, op)
op = block_1.append_instruction("store", 10)
block_1.append_instruction("mstore", acc, op)
block_1.append_instruction("jnz", acc, finish_label, target_label)

target_bb = IRBasicBlock(target_label, ctx)
ctx.append_basic_block(target_bb)
target_bb = IRBasicBlock(target_label, fn)
fn.append_basic_block(target_bb)
target_bb.append_instruction("mul", acc, acc)
target_bb.append_instruction("jmp", finish_label)

finish_bb = IRBasicBlock(finish_label, ctx)
ctx.append_basic_block(finish_bb)
finish_bb = IRBasicBlock(finish_label, fn)
fn.append_basic_block(finish_bb)
finish_bb.append_instruction("stop")

calculate_cfg(ctx)
assert not ctx.normalized, "CFG should not be normalized"
pm = IRPassManager(fn)
pm.request_analysis(CFGAnalysis)
assert not fn.normalized, "CFG should not be normalized"

NormalizationPass().run_pass(ctx)
NormalizationPass(pm).run_pass()

assert ctx.normalized, "CFG should be normalized"
assert fn.normalized, "CFG should be normalized"

finish_bb = ctx.get_basic_block(finish_label.value)
finish_bb = fn.get_basic_block(finish_label.value)
cfg_in = list(finish_bb.cfg_in)
assert cfg_in[0].label.value == "target", "Should contain target"
assert cfg_in[1].label.value == "__global_split_finish", "Should contain __global_split_finish"
Expand All @@ -47,91 +51,95 @@ def test_multi_entry_block_1():

# more complicated one
def test_multi_entry_block_2():
ctx = IRFunction()
ctx = IRContext()
fn = ctx.create_function("__global")

finish_label = IRLabel("finish")
target_label = IRLabel("target")
block_1_label = IRLabel("block_1", ctx)
block_2_label = IRLabel("block_2", ctx)
block_1_label = IRLabel("block_1", fn)
block_2_label = IRLabel("block_2", fn)

bb = ctx.get_basic_block()
bb = fn.get_basic_block()
op = bb.append_instruction("store", 10)
acc = bb.append_instruction("add", op, op)
bb.append_instruction("jnz", acc, finish_label, block_1_label)

block_1 = IRBasicBlock(block_1_label, ctx)
ctx.append_basic_block(block_1)
block_1 = IRBasicBlock(block_1_label, fn)
fn.append_basic_block(block_1)
acc = block_1.append_instruction("add", acc, op)
op = block_1.append_instruction("store", 10)
block_1.append_instruction("mstore", acc, op)
block_1.append_instruction("jnz", acc, target_label, finish_label)

block_2 = IRBasicBlock(block_2_label, ctx)
ctx.append_basic_block(block_2)
block_2 = IRBasicBlock(block_2_label, fn)
fn.append_basic_block(block_2)
acc = block_2.append_instruction("add", acc, op)
op = block_2.append_instruction("store", 10)
block_2.append_instruction("mstore", acc, op)
# switch the order of the labels, for fun and profit
block_2.append_instruction("jnz", acc, finish_label, target_label)

target_bb = IRBasicBlock(target_label, ctx)
ctx.append_basic_block(target_bb)
target_bb = IRBasicBlock(target_label, fn)
fn.append_basic_block(target_bb)
target_bb.append_instruction("mul", acc, acc)
target_bb.append_instruction("jmp", finish_label)

finish_bb = IRBasicBlock(finish_label, ctx)
ctx.append_basic_block(finish_bb)
finish_bb = IRBasicBlock(finish_label, fn)
fn.append_basic_block(finish_bb)
finish_bb.append_instruction("stop")

calculate_cfg(ctx)
assert not ctx.normalized, "CFG should not be normalized"
pm = IRPassManager(fn)
pm.request_analysis(CFGAnalysis)
assert not fn.normalized, "CFG should not be normalized"

NormalizationPass().run_pass(ctx)
NormalizationPass(pm).run_pass()

assert ctx.normalized, "CFG should be normalized"
assert fn.normalized, "CFG should be normalized"

finish_bb = ctx.get_basic_block(finish_label.value)
finish_bb = fn.get_basic_block(finish_label.value)
cfg_in = list(finish_bb.cfg_in)
assert cfg_in[0].label.value == "target", "Should contain target"
assert cfg_in[1].label.value == "__global_split_finish", "Should contain __global_split_finish"
assert cfg_in[2].label.value == "block_1_split_finish", "Should contain block_1_split_finish"


def test_multi_entry_block_with_dynamic_jump():
ctx = IRFunction()
ctx = IRContext()
fn = ctx.create_function("__global")

finish_label = IRLabel("finish")
target_label = IRLabel("target")
block_1_label = IRLabel("block_1", ctx)
block_1_label = IRLabel("block_1", fn)

bb = ctx.get_basic_block()
bb = fn.get_basic_block()
op = bb.append_instruction("store", 10)
acc = bb.append_instruction("add", op, op)
bb.append_instruction("djmp", acc, finish_label, block_1_label)

block_1 = IRBasicBlock(block_1_label, ctx)
ctx.append_basic_block(block_1)
block_1 = IRBasicBlock(block_1_label, fn)
fn.append_basic_block(block_1)
acc = block_1.append_instruction("add", acc, op)
op = block_1.append_instruction("store", 10)
block_1.append_instruction("mstore", acc, op)
block_1.append_instruction("jnz", acc, finish_label, target_label)

target_bb = IRBasicBlock(target_label, ctx)
ctx.append_basic_block(target_bb)
target_bb = IRBasicBlock(target_label, fn)
fn.append_basic_block(target_bb)
target_bb.append_instruction("mul", acc, acc)
target_bb.append_instruction("jmp", finish_label)

finish_bb = IRBasicBlock(finish_label, ctx)
ctx.append_basic_block(finish_bb)
finish_bb = IRBasicBlock(finish_label, fn)
fn.append_basic_block(finish_bb)
finish_bb.append_instruction("stop")

calculate_cfg(ctx)
assert not ctx.normalized, "CFG should not be normalized"
pm = IRPassManager(fn)
pm.request_analysis(CFGAnalysis)
assert not fn.normalized, "CFG should not be normalized"

NormalizationPass().run_pass(ctx)
assert ctx.normalized, "CFG should be normalized"
NormalizationPass(pm).run_pass()
assert fn.normalized, "CFG should be normalized"

finish_bb = ctx.get_basic_block(finish_label.value)
finish_bb = fn.get_basic_block(finish_label.value)
cfg_in = list(finish_bb.cfg_in)
assert cfg_in[0].label.value == "target", "Should contain target"
assert cfg_in[1].label.value == "__global_split_finish", "Should contain __global_split_finish"
Expand Down
Loading
Loading