From bee2416a32bdd377cda1132810f353e67460477b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 15 Jan 2024 08:40:24 +0200 Subject: [PATCH 001/322] Generalize the conversion of IR nodes to venom Make the functions handle the case there is no deploy instructions --- vyper/venom/__init__.py | 6 ++++-- vyper/venom/ir_node_to_venom.py | 13 ++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 570aba771a..07c2011e22 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -65,7 +65,9 @@ def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> tuple[IRFunction, IR # 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 diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c86d3a3d67..10a994b5ba 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -99,14 +99,17 @@ def _findIRnode(ir: IRnode, value: str) -> Optional[IRnode]: def convert_ir_basicblock(ir: IRnode) -> tuple[IRFunction, IRFunction]: + runtime_ir = ir deploy_ir = _findIRnode(ir, "deploy") - assert deploy_ir is not None - deploy_venom = IRFunction() - _convert_ir_basicblock(deploy_venom, ir, {}, OrderedSet(), {}) - deploy_venom.get_basic_block().append_instruction("stop") + deploy_venom = None + if deploy_ir is not None: + deploy_venom = IRFunction() + _convert_ir_basicblock(deploy_venom, ir, {}, OrderedSet(), {}) + deploy_venom.get_basic_block().append_instruction("stop") + + runtime_ir = deploy_ir.args[1] - runtime_ir = deploy_ir.args[1] runtime_venom = IRFunction() _convert_ir_basicblock(runtime_venom, runtime_ir, {}, OrderedSet(), {}) From 2dc412c0d677d938af248b3faa1473c7f4a965f5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 15 Jan 2024 08:43:54 +0200 Subject: [PATCH 002/322] Fix unterminated basic blocks in IR to Venom conversion --- vyper/venom/ir_node_to_venom.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 10a994b5ba..f66db7e924 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -115,8 +115,11 @@ def convert_ir_basicblock(ir: IRnode) -> tuple[IRFunction, IRFunction]: # Connect unterminated blocks to the next with a jump for i, bb in enumerate(runtime_venom.basic_blocks): - if not bb.is_terminated and i < len(runtime_venom.basic_blocks) - 1: - bb.append_instruction("jmp", runtime_venom.basic_blocks[i + 1].label) + if not bb.is_terminated: + if i < len(runtime_venom.basic_blocks) - 1: + bb.append_instruction("jmp", runtime_venom.basic_blocks[i + 1].label) + else: + bb.append_instruction("stop") return deploy_venom, runtime_venom From 5dd5fe65af431946152b72f8c417bba5d059f903 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 15 Jan 2024 08:52:29 +0200 Subject: [PATCH 003/322] Update IRFunction's __str__ method to remove trailing whitespace --- vyper/venom/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 9f26fa8ec0..771dcf73ce 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -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() From f338abe5734b6e8c2e98e4e07390f27a5675e30b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 15 Jan 2024 08:52:56 +0200 Subject: [PATCH 004/322] Add unit test for converting basic block in Venom compiler --- .../venom/test_convert_basicblock_simple.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/unit/compiler/venom/test_convert_basicblock_simple.py diff --git a/tests/unit/compiler/venom/test_convert_basicblock_simple.py b/tests/unit/compiler/venom/test_convert_basicblock_simple.py new file mode 100644 index 0000000000..f98a660545 --- /dev/null +++ b/tests/unit/compiler/venom/test_convert_basicblock_simple.py @@ -0,0 +1,23 @@ +from vyper.codegen.ir_node import IRnode +from vyper.compiler.settings import OptimizationLevel +from vyper.venom import generate_ir + + +def test_simple(get_contract_from_ir): + ir = ["calldatacopy", 32, 0, ["calldatasize"]] + ir = IRnode.from_list(ir) + print(ir) + deploy, runtime = generate_ir(ir, OptimizationLevel.NONE) + assert deploy is None + assert runtime is not None + assert len(runtime.basic_blocks) == 1 + bb = runtime.basic_blocks[0] + assert len(bb.instructions) == 3 + + correct_venom = """IRFunction: __global +__global: IN=[] OUT=[] => {} + %1 = calldatasize + calldatacopy %1, 0, 32 + stop""" + + assert str(runtime) == correct_venom From bcc63adcc58f4678dd7c8872dc3738f3c65cbcbe Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 15 Jan 2024 08:59:46 +0200 Subject: [PATCH 005/322] Refactor code for cleaner venom output and lint stuff --- vyper/venom/__init__.py | 4 +++- vyper/venom/basicblock.py | 4 ++-- vyper/venom/ir_node_to_venom.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 07c2011e22..1a9b7cf172 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -61,7 +61,9 @@ 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]]: # Convert "old" IR to "new" IR ctx, ctx_runtime = convert_ir_basicblock(ir) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 598b8af7d5..f86e9b330c 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -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 diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index f66db7e924..517a2569df 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -98,7 +98,7 @@ def _findIRnode(ir: IRnode, value: str) -> Optional[IRnode]: return None -def convert_ir_basicblock(ir: IRnode) -> tuple[IRFunction, IRFunction]: +def convert_ir_basicblock(ir: IRnode) -> tuple[Optional[IRFunction], Optional[IRFunction]]: runtime_ir = ir deploy_ir = _findIRnode(ir, "deploy") From d6af7fe12ae858029bf0fbe4a36073e2ce37b6c5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 15 Jan 2024 09:00:03 +0200 Subject: [PATCH 006/322] Clean up whitespace test_convert_basicblock_simple.py --- .../venom/test_convert_basicblock_simple.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/unit/compiler/venom/test_convert_basicblock_simple.py b/tests/unit/compiler/venom/test_convert_basicblock_simple.py index f98a660545..adfa49c163 100644 --- a/tests/unit/compiler/venom/test_convert_basicblock_simple.py +++ b/tests/unit/compiler/venom/test_convert_basicblock_simple.py @@ -3,20 +3,16 @@ from vyper.venom import generate_ir -def test_simple(get_contract_from_ir): +def test_simple(): ir = ["calldatacopy", 32, 0, ["calldatasize"]] - ir = IRnode.from_list(ir) - print(ir) - deploy, runtime = generate_ir(ir, OptimizationLevel.NONE) + ir_node = IRnode.from_list(ir) + deploy, runtime = generate_ir(ir_node, OptimizationLevel.NONE) assert deploy is None assert runtime is not None - assert len(runtime.basic_blocks) == 1 - bb = runtime.basic_blocks[0] - assert len(bb.instructions) == 3 correct_venom = """IRFunction: __global -__global: IN=[] OUT=[] => {} - %1 = calldatasize +__global: IN=[] OUT=[] => {} + %1 = calldatasize calldatacopy %1, 0, 32 stop""" From 33e5b5a7bab62b338b80b1084dbdbc3d9e1fed19 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 15 Jan 2024 14:11:15 +0200 Subject: [PATCH 007/322] Add new test cases for simple conversion in test_convert_basicblock_simple.py --- .../venom/test_convert_basicblock_simple.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/unit/compiler/venom/test_convert_basicblock_simple.py b/tests/unit/compiler/venom/test_convert_basicblock_simple.py index adfa49c163..be379b133f 100644 --- a/tests/unit/compiler/venom/test_convert_basicblock_simple.py +++ b/tests/unit/compiler/venom/test_convert_basicblock_simple.py @@ -17,3 +17,39 @@ def test_simple(): stop""" assert str(runtime) == correct_venom + + +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() From 3ec3a51a367c2b8a83671c80d34a3d9a0623d7f5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 15 Jan 2024 14:11:49 +0200 Subject: [PATCH 008/322] clean up ir to venom --- vyper/venom/ir_node_to_venom.py | 152 +++++++++++++------------------- 1 file changed, 63 insertions(+), 89 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 517a2569df..51f9acf251 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -102,18 +102,20 @@ def convert_ir_basicblock(ir: IRnode) -> tuple[Optional[IRFunction], Optional[IR runtime_ir = ir deploy_ir = _findIRnode(ir, "deploy") + # 1. Convert deploy IR to Venom IR deploy_venom = None if deploy_ir is not None: deploy_venom = IRFunction() - _convert_ir_basicblock(deploy_venom, ir, {}, OrderedSet(), {}) + _convert_ir_bb(deploy_venom, ir, {}, OrderedSet(), {}) deploy_venom.get_basic_block().append_instruction("stop") runtime_ir = deploy_ir.args[1] + # 2. Convert runtime IR to Venom IR runtime_venom = IRFunction() - _convert_ir_basicblock(runtime_venom, runtime_ir, {}, OrderedSet(), {}) + _convert_ir_bb(runtime_venom, runtime_ir, {}, OrderedSet(), {}) - # Connect unterminated blocks to the next with a jump + # 3. Patch up basic blocks. Connect unterminated blocks to the next with a jump for i, bb in enumerate(runtime_venom.basic_blocks): if not bb.is_terminated: if i < len(runtime_venom.basic_blocks) - 1: @@ -133,8 +135,7 @@ def _convert_binary_op( swap: bool = False, ) -> Optional[IRVariable]: ir_args = ir.args[::-1] if swap else ir.args - arg_0 = _convert_ir_basicblock(ctx, ir_args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir_args[1], symbols, variables, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir_args, symbols, variables, allocated_variables) return ctx.get_basic_block().append_instruction(str(ir.value), arg_1, arg_0) @@ -171,14 +172,12 @@ def _handle_self_call( if arg.is_literal: sym = symbols.get(f"&{arg.value}", None) if sym is None: - ret = _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) + ret = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) ret_args.append(ret) else: ret_args.append(sym) # type: ignore else: - ret = _convert_ir_basicblock( - ctx, arg._optimized, symbols, variables, allocated_variables - ) + ret = _convert_ir_bb(ctx, arg._optimized, symbols, variables, allocated_variables) if arg.location and arg.location.load_op == "calldataload": bb = ctx.get_basic_block() ret = bb.append_instruction(arg.location.load_op, ret) @@ -231,9 +230,7 @@ def _convert_ir_simple_node( variables: OrderedSet, allocated_variables: dict[str, IRVariable], ) -> Optional[IRVariable]: - args = [ - _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args - ] + args = [_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args] return ctx.get_basic_block().append_instruction(ir.value, *args) # type: ignore @@ -272,7 +269,16 @@ def _append_return_for_stack_operand( bb.append_instruction("return", last_ir, new_var) # type: ignore -def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): +def _convert_ir_bb_list(ctx, ir, symbols, variables, allocated_variables): + ret = [] + for ir_node in ir: + venom = _convert_ir_bb(ctx, ir_node, symbols, variables, allocated_variables) + ret.append(venom) + return ret + + +def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): + assert isinstance(ir, IRnode) assert isinstance(variables, OrderedSet) global _break_target, _continue_target @@ -320,35 +326,22 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ret = None for ir_node in ir.args: # NOTE: skip the last one - ret = _convert_ir_basicblock(ctx, ir_node, symbols, variables, allocated_variables) + ret = _convert_ir_bb(ctx, ir_node, symbols, variables, allocated_variables) return ret elif ir.value in ["staticcall", "call"]: # external call idx = 0 - gas = _convert_ir_basicblock(ctx, ir.args[idx], symbols, variables, allocated_variables) - address = _convert_ir_basicblock( - ctx, ir.args[idx + 1], symbols, variables, allocated_variables - ) + gas = _convert_ir_bb(ctx, ir.args[idx], symbols, variables, allocated_variables) + address = _convert_ir_bb(ctx, ir.args[idx + 1], symbols, variables, allocated_variables) value = None if ir.value == "call": - value = _convert_ir_basicblock( - ctx, ir.args[idx + 2], symbols, variables, allocated_variables - ) + value = _convert_ir_bb(ctx, ir.args[idx + 2], symbols, variables, allocated_variables) else: idx -= 1 - argsOffset = _convert_ir_basicblock( - ctx, ir.args[idx + 3], symbols, variables, allocated_variables - ) - argsSize = _convert_ir_basicblock( - ctx, ir.args[idx + 4], symbols, variables, allocated_variables - ) - retOffset = _convert_ir_basicblock( - ctx, ir.args[idx + 5], symbols, variables, allocated_variables - ) - retSize = _convert_ir_basicblock( - ctx, ir.args[idx + 6], symbols, variables, allocated_variables + argsOffset, argsSize, retOffset, retSize = _convert_ir_bb_list( + ctx, ir.args[idx + 3 : idx + 7], symbols, variables, allocated_variables ) if isinstance(argsOffset, IRLiteral): @@ -383,7 +376,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): current_bb = ctx.get_basic_block() # convert the condition - cont_ret = _convert_ir_basicblock(ctx, cond, symbols, variables, allocated_variables) + cont_ret = _convert_ir_bb(ctx, cond, symbols, variables, allocated_variables) else_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(else_block) @@ -392,7 +385,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): else_ret_val = None else_syms = symbols.copy() if len(ir.args) == 3: - else_ret_val = _convert_ir_basicblock( + else_ret_val = _convert_ir_bb( ctx, ir.args[2], else_syms, variables, allocated_variables.copy() ) if isinstance(else_ret_val, IRLiteral): @@ -404,9 +397,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): then_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(then_block) - then_ret_val = _convert_ir_basicblock( - ctx, ir.args[1], symbols, variables, allocated_variables - ) + then_ret_val = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) if isinstance(then_ret_val, IRLiteral): then_ret_val = ctx.get_basic_block().append_instruction("store", then_ret_val) @@ -444,7 +435,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return if_ret elif ir.value == "with": - ret = _convert_ir_basicblock( + ret = _convert_ir_bb( ctx, ir.args[1], symbols, variables, allocated_variables ) # initialization @@ -458,27 +449,25 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): else: with_symbols[sym.value] = ret # type: ignore - return _convert_ir_basicblock( - ctx, ir.args[2], with_symbols, variables, allocated_variables - ) # body + return _convert_ir_bb(ctx, ir.args[2], with_symbols, variables, allocated_variables) # body elif ir.value == "goto": _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "djump": - args = [_convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables)] + args = [_convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables)] for target in ir.args[1:]: args.append(IRLabel(target.value)) ctx.get_basic_block().append_instruction("djmp", *args) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) new_var = ctx.get_basic_block().append_instruction("store", arg_1) # type: ignore symbols[sym.value] = new_var elif ir.value == "calldatacopy": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + arg_0, arg_1, size = _convert_ir_bb_list( + ctx, ir.args, symbols, variables, allocated_variables + ) new_v = arg_0 var = ( @@ -498,9 +487,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return new_v elif ir.value == "codecopy": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + arg_0, arg_1, size = _convert_ir_bb_list( + ctx, ir.args, symbols, variables, allocated_variables + ) ctx.get_basic_block().append_instruction("codecopy", size, arg_1, arg_0) # type: ignore elif ir.value == "symbol": @@ -515,10 +504,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif isinstance(c, bytes): ctx.append_data("db", [c]) # type: ignore elif isinstance(c, IRnode): - data = _convert_ir_basicblock(ctx, c, symbols, variables, allocated_variables) + data = _convert_ir_bb(ctx, c, symbols, variables, allocated_variables) ctx.append_data("db", [data]) # type: ignore elif ir.value == "assert": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) current_bb = ctx.get_basic_block() current_bb.append_instruction("assert", arg_0) elif ir.value == "label": @@ -528,7 +517,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("jmp", label) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) - _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + _convert_ir_bb(ctx, ir.args[2], symbols, variables, allocated_variables) elif ir.value == "exit_to": func_t = ir.passthrough_metadata.get("func_t", None) assert func_t is not None, "exit_to without func_t" @@ -551,15 +540,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): deleted = symbols[f"&{ret_var.value}"] del symbols[f"&{ret_var.value}"] for arg in ir.args[2:]: - last_ir = _convert_ir_basicblock( - ctx, arg, symbols, variables, allocated_variables - ) + last_ir = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) if deleted is not None: symbols[f"&{ret_var.value}"] = deleted - ret_ir = _convert_ir_basicblock( - ctx, ret_var, symbols, variables, allocated_variables - ) + ret_ir = _convert_ir_bb(ctx, ret_var, symbols, variables, allocated_variables) bb = ctx.get_basic_block() @@ -618,12 +603,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("ret", ret_by_value, symbols["return_pc"]) elif ir.value == "revert": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) ctx.get_basic_block().append_instruction("revert", arg_1, arg_0) elif ir.value == "dload": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) bb = ctx.get_basic_block() src = bb.append_instruction("add", arg_0, IRLabel("code_end")) @@ -631,11 +615,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return bb.append_instruction("mload", MemoryPositions.FREE_VAR_SPACE) elif ir.value == "dloadbytes": - dst = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - src_offset = _convert_ir_basicblock( - ctx, ir.args[1], symbols, variables, allocated_variables + dst, src_offset, len_ = _convert_ir_bb_list( + ctx, ir.args, symbols, variables, allocated_variables ) - len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + bb = ctx.get_basic_block() src = bb.append_instruction("add", src_offset, IRLabel("code_end")) bb.append_instruction("dloadbytes", len_, src, dst) @@ -684,9 +667,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): else: return bb.append_instruction("mload", sym_ir.value) else: - new_var = _convert_ir_basicblock( - ctx, sym_ir, symbols, variables, allocated_variables - ) + new_var = _convert_ir_bb(ctx, sym_ir, symbols, variables, allocated_variables) # # Old IR gets it's return value as a reference in the stack # New IR gets it's return value in stack in case of 32 bytes or less @@ -698,8 +679,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return bb.append_instruction("mload", new_var) elif ir.value == "mstore": - sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + sym_ir, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) bb = ctx.get_basic_block() @@ -748,11 +728,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return arg_1 elif ir.value in ["sload", "iload"]: - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) return ctx.get_basic_block().append_instruction(ir.value, arg_0) elif ir.value in ["sstore", "istore"]: - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) elif ir.value == "unique_symbol": sym = ir.args[0] @@ -773,14 +752,14 @@ def emit_body_block(): global _break_target, _continue_target old_targets = _break_target, _continue_target _break_target, _continue_target = exit_block, increment_block - _convert_ir_basicblock(ctx, body, symbols, variables, allocated_variables) + _convert_ir_bb(ctx, body, symbols, variables, allocated_variables) _break_target, _continue_target = old_targets sym = ir.args[0] - start = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - end = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) - # "bound" is not used - _ = _convert_ir_basicblock(ctx, ir.args[3], symbols, variables, allocated_variables) + start, end, _ = _convert_ir_bb_list( + ctx, ir.args[:3], symbols, variables, allocated_variables + ) + body = ir.args[4] entry_block = ctx.get_basic_block() @@ -857,23 +836,20 @@ def emit_body_block(): return ctx.get_basic_block().append_instruction("returndatasize") elif ir.value == "returndatacopy": assert len(ir.args) == 3, "returndatacopy with wrong number of arguments" - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) + size = _convert_ir_bb(ctx, ir.args[2], symbols, variables, allocated_variables) new_var = ctx.get_basic_block().append_instruction("returndatacopy", arg_1, size) symbols[f"&{arg_0.value}"] = new_var return new_var elif ir.value == "selfdestruct": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) ctx.get_basic_block().append_instruction("selfdestruct", arg_0) elif isinstance(ir.value, str) and ir.value.startswith("log"): args = reversed( - [ - _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) - for arg in ir.args - ] + [_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args] ) topic_count = int(ir.value[3:]) assert topic_count >= 0 and topic_count <= 4, "invalid topic count" @@ -901,9 +877,7 @@ def _convert_ir_opcode( inst_args = [] for arg in ir.args: if isinstance(arg, IRnode): - inst_args.append( - _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) - ) + inst_args.append(_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables)) ctx.get_basic_block().append_instruction(opcode, *inst_args) From 33d1c72d20b6aa38ee5ae92e07f5bfc6373bfe1b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 15 Jan 2024 21:50:59 +0200 Subject: [PATCH 009/322] Fix conditional block termination issue --- vyper/venom/ir_node_to_venom.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 51f9acf251..cdddff0d89 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -377,6 +377,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # convert the condition cont_ret = _convert_ir_bb(ctx, cond, symbols, variables, allocated_variables) + current_bb = ctx.get_basic_block() else_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(else_block) @@ -392,6 +393,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): assert isinstance(else_ret_val.value, int) # help mypy else_ret_val = ctx.get_basic_block().append_instruction("store", else_ret_val) after_else_syms = else_syms.copy() + else_block = ctx.get_basic_block() # convert "then" then_block = IRBasicBlock(ctx.get_next_label(), ctx) @@ -404,21 +406,24 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): current_bb.append_instruction("jnz", cont_ret, then_block.label, else_block.label) after_then_syms = symbols.copy() + then_block = ctx.get_basic_block() # exit bb exit_label = ctx.get_next_label() - bb = IRBasicBlock(exit_label, ctx) - bb = ctx.append_basic_block(bb) + exit_bb = IRBasicBlock(exit_label, ctx) + exit_bb = ctx.append_basic_block(exit_bb) if_ret = None if then_ret_val is not None and else_ret_val is not None: - if_ret = bb.append_instruction( + if_ret = exit_bb.append_instruction( "phi", then_block.label, then_ret_val, else_block.label, else_ret_val ) common_symbols = _get_symbols_common(after_then_syms, after_else_syms) for sym, val in common_symbols.items(): - ret = bb.append_instruction("phi", then_block.label, val[0], else_block.label, val[1]) + ret = exit_bb.append_instruction( + "phi", then_block.label, val[0], else_block.label, val[1] + ) old_var = symbols.get(sym, None) symbols[sym] = ret if old_var is not None: @@ -427,10 +432,10 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): allocated_variables[idx] = ret # type: ignore if not else_block.is_terminated: - else_block.append_instruction("jmp", bb.label) + else_block.append_instruction("jmp", exit_bb.label) if not then_block.is_terminated: - then_block.append_instruction("jmp", bb.label) + then_block.append_instruction("jmp", exit_bb.label) return if_ret @@ -757,7 +762,7 @@ def emit_body_block(): sym = ir.args[0] start, end, _ = _convert_ir_bb_list( - ctx, ir.args[:3], symbols, variables, allocated_variables + ctx, ir.args[1:4], symbols, variables, allocated_variables ) body = ir.args[4] @@ -813,8 +818,9 @@ def emit_body_block(): jump_up_block.append_instruction("jmp", increment_block.label) ctx.append_basic_block(jump_up_block) - increment_block.append_instruction(IRInstruction("add", ret, 1)) - increment_block.insert_instruction[-1].output = counter_inc_var + increment_block.insert_instruction( + IRInstruction("add", [ret, IRLiteral(1)], counter_inc_var), 0 + ) increment_block.append_instruction("jmp", cond_block.label) ctx.append_basic_block(increment_block) @@ -836,9 +842,9 @@ def emit_body_block(): return ctx.get_basic_block().append_instruction("returndatasize") elif ir.value == "returndatacopy": assert len(ir.args) == 3, "returndatacopy with wrong number of arguments" - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) - arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) - size = _convert_ir_bb(ctx, ir.args[2], symbols, variables, allocated_variables) + arg_0, arg_1, size = _convert_ir_bb_list( + ctx, ir.args, symbols, variables, allocated_variables + ) new_var = ctx.get_basic_block().append_instruction("returndatacopy", arg_1, size) From 978316cd94b270c4c562119cde793d584a803e58 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 16 Jan 2024 15:49:11 +0200 Subject: [PATCH 010/322] Add _get_symbols_accessed function to ir_node_to_venom.py --- vyper/venom/ir_node_to_venom.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index cdddff0d89..798885e243 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -87,6 +87,18 @@ def _get_symbols_common(a: dict, b: dict) -> dict: return ret +def _get_symbols_accessed(a: dict, b: dict) -> list: + ret = [] + # preserves the ordering in `a` + for k in a.keys(): + if k not in b: + continue + if a[k] != b[k]: + continue + ret.append(a[k]) + return ret + + def _findIRnode(ir: IRnode, value: str) -> Optional[IRnode]: if ir.value == value: return ir @@ -753,7 +765,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # 5) increment block # 6) exit block # TODO: Add the extra bounds check after clarify - def emit_body_block(): + def emit_body_blocks(): global _break_target, _continue_target old_targets = _break_target, _continue_target _break_target, _continue_target = exit_block, increment_block @@ -789,10 +801,9 @@ def emit_body_block(): cont_ret = cond_block.append_instruction("iszero", xor_ret) ctx.append_basic_block(cond_block) - # Do a dry run to get the symbols needing phi nodes start_syms = symbols.copy() ctx.append_basic_block(body_block) - emit_body_block() + emit_body_blocks() end_syms = symbols.copy() diff_syms = _get_symbols_common(start_syms, end_syms) From 1eae31b9287874c5f2c9e199f03ad297e0c9e09d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 16 Jan 2024 15:49:43 +0200 Subject: [PATCH 011/322] Remove unused function _get_symbols_accessed() --- vyper/venom/ir_node_to_venom.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 798885e243..ff2d1f84bc 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -87,18 +87,6 @@ def _get_symbols_common(a: dict, b: dict) -> dict: return ret -def _get_symbols_accessed(a: dict, b: dict) -> list: - ret = [] - # preserves the ordering in `a` - for k in a.keys(): - if k not in b: - continue - if a[k] != b[k]: - continue - ret.append(a[k]) - return ret - - def _findIRnode(ir: IRnode, value: str) -> Optional[IRnode]: if ir.value == value: return ir From 428c910a4de951768d066272197daf2651935742 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 17 Jan 2024 16:48:19 +0200 Subject: [PATCH 012/322] liveness calculation and fixed a bug * Converted algorithm to iterative * fixed a bug related to simple loops --- vyper/venom/analysis.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index eed579463e..b268d0ba85 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -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() @@ -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) + + if changed is False: + break # calculate the input variables into self from source From e7d2b40635377c662f2dcfef091fbbc1f4370ccb Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 17 Jan 2024 16:49:15 +0200 Subject: [PATCH 013/322] update test --- .../compiler/venom/test_convert_basicblock_simple.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/unit/compiler/venom/test_convert_basicblock_simple.py b/tests/unit/compiler/venom/test_convert_basicblock_simple.py index be379b133f..c3e5bd80c8 100644 --- a/tests/unit/compiler/venom/test_convert_basicblock_simple.py +++ b/tests/unit/compiler/venom/test_convert_basicblock_simple.py @@ -10,13 +10,9 @@ def test_simple(): assert deploy is None assert runtime is not None - correct_venom = """IRFunction: __global -__global: IN=[] OUT=[] => {} - %1 = calldatasize - calldatacopy %1, 0, 32 - stop""" - - assert str(runtime) == correct_venom + bb = runtime.basic_blocks[0] + assert bb.instructions[0].opcode == "calldatasize" + assert bb.instructions[1].opcode == "calldatacopy" def test_simple_2(): From 353fefa599e95e6bcc693e969fb57f11383d08a4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 17 Jan 2024 16:52:58 +0200 Subject: [PATCH 014/322] Add test for liveness in a simple loop --- .../compiler/venom/test_liveness_simple_loop.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/unit/compiler/venom/test_liveness_simple_loop.py diff --git a/tests/unit/compiler/venom/test_liveness_simple_loop.py b/tests/unit/compiler/venom/test_liveness_simple_loop.py new file mode 100644 index 0000000000..822cab5d2e --- /dev/null +++ b/tests/unit/compiler/venom/test_liveness_simple_loop.py @@ -0,0 +1,17 @@ +import pytest +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"]) + assert True From 0cc222ad18df54678a0c084f4f15985168b94002 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 17 Jan 2024 16:54:33 +0200 Subject: [PATCH 015/322] Remove unused import statement --- tests/unit/compiler/venom/test_liveness_simple_loop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/compiler/venom/test_liveness_simple_loop.py b/tests/unit/compiler/venom/test_liveness_simple_loop.py index 822cab5d2e..d92a50596a 100644 --- a/tests/unit/compiler/venom/test_liveness_simple_loop.py +++ b/tests/unit/compiler/venom/test_liveness_simple_loop.py @@ -1,4 +1,3 @@ -import pytest import vyper source = """ From 3697cf5980776e421e2e2701e58da6f2abba009a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 18 Jan 2024 12:57:46 +0200 Subject: [PATCH 016/322] insert_instruction default to insert at the end of the block --- vyper/venom/basicblock.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index f86e9b330c..5ddaa8194f 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -373,8 +373,10 @@ def append_invoke_instruction( self.instructions.append(inst) return ret - def insert_instruction(self, instruction: IRInstruction, index: int) -> None: + def insert_instruction(self, instruction: IRInstruction, index: Optional[int] = None) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" + if index is None: + index = len(self.instructions) instruction.parent = self self.instructions.insert(index, instruction) From cbbe21cdc2ad1e325241576084fe7b9702b00c90 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 18 Jan 2024 12:59:17 +0200 Subject: [PATCH 017/322] fix call return value issue --- vyper/venom/ir_node_to_venom.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index ff2d1f84bc..4cd41aabf8 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -367,10 +367,11 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if ir.value == "call": args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] - return bb.append_instruction(ir.value, *args) else: args = [retSize, retOffset, argsSize, argsOffsetVar, address, gas] - return bb.append_instruction(ir.value, *args) + + bb.insert_instruction(IRInstruction(ir.value, args, retVar)) + return retVar elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() From 6e86f176c05c6b11b3647c17fa538211420ad720 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 18 Jan 2024 14:59:35 +0200 Subject: [PATCH 018/322] assert BasicBlock is not terminated when inserting --- vyper/venom/basicblock.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 5ddaa8194f..f86c004592 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -343,6 +343,8 @@ def append_instruction(self, opcode: str, *args: Union[IROperand, int]) -> Optio Returns the output variable if the instruction supports one """ + assert not self.is_terminated, self + ret = self.parent.get_next_variable() if opcode not in NO_OUTPUT_INSTRUCTIONS else None # Wrap raw integers in IRLiterals @@ -361,6 +363,7 @@ def append_invoke_instruction( Returns the output variable if the instruction supports one """ + assert not self.is_terminated, self ret = None if returns: ret = self.parent.get_next_variable() @@ -375,7 +378,9 @@ def append_invoke_instruction( def insert_instruction(self, instruction: IRInstruction, index: Optional[int] = None) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" + if index is None: + assert not self.is_terminated, self index = len(self.instructions) instruction.parent = self self.instructions.insert(index, instruction) From 3455ef615359c0eb3412ae5c0473313c35cb8e3e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 18 Jan 2024 16:00:56 +0200 Subject: [PATCH 019/322] Invoke is a cfg altering instruction --- vyper/venom/basicblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 872fadae46..1ba910157d 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -55,7 +55,7 @@ ] ) -CFG_ALTERING_INSTRUCTIONS = frozenset(["jmp", "djmp", "jnz"]) +CFG_ALTERING_INSTRUCTIONS = frozenset(["jmp", "djmp", "jnz", "invoke"]) if TYPE_CHECKING: from vyper.venom.function import IRFunction From e0e717707008428acf5daa9ea0976785da2e4fb9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 18 Jan 2024 22:25:43 +0200 Subject: [PATCH 020/322] fix adding instruction to terminated bb add extra instructions --- vyper/venom/ir_node_to_venom.py | 20 +++++++++++++------- vyper/venom/venom_to_assembly.py | 3 +++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 8aa0749f05..96317f7b51 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -27,6 +27,7 @@ "sgt", "shr", "shl", + "sar", "or", "xor", "and", @@ -34,6 +35,8 @@ "sub", "mul", "div", + "smul", + "sdiv", "mod", "exp", "sha3", @@ -340,18 +343,19 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): else: argsOffsetVar = argsOffset - retOffsetValue = int(retOffset.value) if retOffset else 0 - retVar = ctx.get_next_variable(MemType.MEMORY, retOffsetValue) - symbols[f"&{retOffsetValue}"] = retVar - - bb = ctx.get_basic_block() + if isinstance(retOffset, IRLiteral): + retOffsetValue = int(retOffset.value) if retOffset else 0 + retVar = ctx.get_next_variable(MemType.MEMORY, retOffsetValue) + symbols[f"&{retOffsetValue}"] = retVar + else: + retVar = retOffset if ir.value == "call": args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] else: args = [retSize, retOffset, argsSize, argsOffsetVar, address, gas] - bb.insert_instruction(IRInstruction(ir.value, args, retVar)) + ctx.get_basic_block().insert_instruction(IRInstruction(ir.value, args, retVar)) return retVar elif ir.value == "if": cond = ir.args[0] @@ -664,7 +668,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # if sym_ir.is_self_call: return new_var - return bb.append_instruction("mload", new_var) + return ctx.get_basic_block().append_instruction("mload", new_var) elif ir.value == "mstore": sym_ir, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) @@ -819,6 +823,8 @@ def emit_body_blocks(): ctx.append_basic_block(exit_block) cond_block.append_instruction("jnz", cont_ret, exit_block.label, body_block.label) + elif ir.value == "cleanup_repeat": + pass elif ir.value == "break": assert _break_target is not None, "Break with no break target" ctx.get_basic_block().append_instruction("jmp", _break_target.label) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 608e100cd1..8e02a439dd 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -47,6 +47,7 @@ "stop", "shr", "shl", + "sar", "and", "xor", "or", @@ -54,6 +55,8 @@ "sub", "mul", "div", + "smul", + "sdiv", "mod", "exp", "eq", From 5e4fe897b11e2b353b20849748bd179bc56d6cad Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 18 Jan 2024 22:41:57 +0200 Subject: [PATCH 021/322] add call test --- tests/unit/compiler/venom/test_call.py | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/unit/compiler/venom/test_call.py diff --git a/tests/unit/compiler/venom/test_call.py b/tests/unit/compiler/venom/test_call.py new file mode 100644 index 0000000000..524efe921a --- /dev/null +++ b/tests/unit/compiler/venom/test_call.py @@ -0,0 +1,44 @@ +import pytest + + +@pytest.fixture +def market_maker(get_contract): + contract_code = """ +from vyper.interfaces import ERC20 + +unused: public(uint256) +token_address: ERC20 + +@external +@payable +def foo(token_addr: address, token_quantity: uint256): + self.token_address = ERC20(token_addr) + self.token_address.transferFrom(msg.sender, self, token_quantity) +""" + return get_contract(contract_code) + + +TOKEN_NAME = "Vypercoin" +TOKEN_SYMBOL = "FANG" +TOKEN_DECIMALS = 18 +TOKEN_INITIAL_SUPPLY = 21 * 10**6 +TOKEN_TOTAL_SUPPLY = TOKEN_INITIAL_SUPPLY * (10**TOKEN_DECIMALS) + + +@pytest.fixture +def erc20(get_contract): + with open("examples/tokens/ERC20.vy") as f: + contract_code = f.read() + return get_contract( + contract_code, *[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY] + ) + + +def test_call(w3, market_maker, erc20, tx_failed): + a0 = w3.eth.accounts[0] + ether, ethers = w3.to_wei(1, "ether"), w3.to_wei(2, "ether") + erc20.approve(market_maker.address, ethers, transact={}) + assert erc20.name() == TOKEN_NAME + assert erc20.decimals() == TOKEN_DECIMALS + + market_maker.foo(erc20.address, ether, transact={"value": ethers}) From 7c0fe7581ad9aaafe7f12be36a09d92c293e6ae2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 00:36:31 +0200 Subject: [PATCH 022/322] Add iteration limit check in IR pass --- vyper/venom/passes/base_pass.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/venom/passes/base_pass.py b/vyper/venom/passes/base_pass.py index 11da80ac66..649a939677 100644 --- a/vyper/venom/passes/base_pass.py +++ b/vyper/venom/passes/base_pass.py @@ -14,6 +14,8 @@ def run_pass(cls, *args, **kwargs): count += changes_count if changes_count == 0: break + if count > 1000: + raise Exception("Too many iterations in IR pass!", t.__class__) return count From a3b476cdde4582f4cc6d7a97c9885488f4017e09 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 00:54:50 +0200 Subject: [PATCH 023/322] new bb reachability --- vyper/venom/basicblock.py | 8 +++----- vyper/venom/function.py | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 1ba910157d..3761c5b5d6 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -55,7 +55,7 @@ ] ) -CFG_ALTERING_INSTRUCTIONS = frozenset(["jmp", "djmp", "jnz", "invoke"]) +CFG_ALTERING_INSTRUCTIONS = frozenset(["jmp", "djmp", "jnz"]) if TYPE_CHECKING: from vyper.venom.function import IRFunction @@ -306,6 +306,7 @@ class IRBasicBlock: cfg_out: OrderedSet["IRBasicBlock"] # stack items which this basic block produces out_vars: OrderedSet[IRVariable] + is_reachable: bool = False def __init__(self, label: IRLabel, parent: "IRFunction") -> None: assert isinstance(label, IRLabel), "label must be an IRLabel" @@ -315,6 +316,7 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.cfg_in = OrderedSet() self.cfg_out = OrderedSet() self.out_vars = OrderedSet() + self.is_reachable = False def add_cfg_in(self, bb: "IRBasicBlock") -> None: self.cfg_in.add(bb) @@ -333,10 +335,6 @@ def remove_cfg_out(self, bb: "IRBasicBlock") -> None: assert bb in self.cfg_out self.cfg_out.remove(bb) - @property - def is_reachable(self) -> bool: - return len(self.cfg_in) > 0 - def append_instruction(self, opcode: str, *args: Union[IROperand, int]) -> Optional[IRVariable]: """ Append an instruction to the basic block diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 771dcf73ce..3e228874d8 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,6 +1,7 @@ from typing import Optional from vyper.venom.basicblock import ( + CFG_ALTERING_INSTRUCTIONS, IRBasicBlock, IRInstruction, IRLabel, @@ -107,6 +108,8 @@ def get_last_variable(self) -> str: return f"%{self.last_variable}" def remove_unreachable_blocks(self) -> int: + self._compute_reachability() + removed = 0 new_basic_blocks = [] for bb in self.basic_blocks: @@ -117,6 +120,29 @@ def remove_unreachable_blocks(self) -> int: self.basic_blocks = new_basic_blocks return removed + def _compute_reachability(self) -> None: + """ + Compute reachability of basic blocks. + """ + for bb in self.basic_blocks: + bb.is_reachable = False + for entry in self.entry_points: + self._compute_reachability_from(self.get_basic_block(entry.value)) + + def _compute_reachability_from(self, bb: IRBasicBlock) -> None: + """ + Compute reachability of basic blocks from bb. + """ + if bb.is_reachable: + return + bb.is_reachable = True + for inst in bb.instructions: + if inst.opcode in CFG_ALTERING_INSTRUCTIONS or inst.opcode in "invoke": + ops = inst.get_label_operands() + for op in ops: + out_bb = self.get_basic_block(op.value) + self._compute_reachability_from(out_bb) + def append_data(self, opcode: str, args: list[IROperand]) -> None: """ Append data From 5f3b4d237c7e5b9b274d562dc625b13108aebf9a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 01:30:01 +0200 Subject: [PATCH 024/322] New reachability --- vyper/venom/basicblock.py | 3 +++ vyper/venom/function.py | 14 +++++++++----- vyper/venom/venom_to_assembly.py | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 3761c5b5d6..4a92db26b1 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -306,6 +306,8 @@ class IRBasicBlock: cfg_out: OrderedSet["IRBasicBlock"] # stack items which this basic block produces out_vars: OrderedSet[IRVariable] + + reachable: OrderedSet["IRBasicBlock"] is_reachable: bool = False def __init__(self, label: IRLabel, parent: "IRFunction") -> None: @@ -316,6 +318,7 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.cfg_in = OrderedSet() self.cfg_out = OrderedSet() self.out_vars = OrderedSet() + self.reachable = OrderedSet() self.is_reachable = False def add_cfg_in(self, bb: "IRBasicBlock") -> None: diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 3e228874d8..79196c11c8 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,4 +1,5 @@ from typing import Optional +from vyper.utils import OrderedSet from vyper.venom.basicblock import ( CFG_ALTERING_INSTRUCTIONS, @@ -113,7 +114,7 @@ def remove_unreachable_blocks(self) -> int: removed = 0 new_basic_blocks = [] for bb in self.basic_blocks: - if not bb.is_reachable and bb.label not in self.entry_points: + if not bb.is_reachable: removed += 1 else: new_basic_blocks.append(bb) @@ -125,9 +126,12 @@ def _compute_reachability(self) -> None: Compute reachability of basic blocks. """ for bb in self.basic_blocks: + bb.reachable = OrderedSet() bb.is_reachable = False + for entry in self.entry_points: - self._compute_reachability_from(self.get_basic_block(entry.value)) + entry_bb = self.get_basic_block(entry.value) + self._compute_reachability_from(entry_bb) def _compute_reachability_from(self, bb: IRBasicBlock) -> None: """ @@ -137,10 +141,10 @@ def _compute_reachability_from(self, bb: IRBasicBlock) -> None: return bb.is_reachable = True for inst in bb.instructions: - if inst.opcode in CFG_ALTERING_INSTRUCTIONS or inst.opcode in "invoke": - ops = inst.get_label_operands() - for op in ops: + if inst.opcode in CFG_ALTERING_INSTRUCTIONS or inst.opcode == "invoke": + for op in inst.get_label_operands(): out_bb = self.get_basic_block(op.value) + bb.reachable.add(out_bb) self._compute_reachability_from(out_bb) def append_data(self, opcode: str, args: list[IROperand]) -> None: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 8e02a439dd..e0f8d5d648 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -230,7 +230,7 @@ def _generate_evm_for_basicblock_r( for inst in basicblock.instructions: asm = self._generate_evm_for_instruction(asm, inst, stack) - for bb in basicblock.cfg_out: + for bb in basicblock.reachable: self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) # pop values from stack at entry to bb From 33d015cc5a9d7d5761ac566cf242ffc91814373e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 01:31:47 +0200 Subject: [PATCH 025/322] lint --- tests/unit/compiler/venom/test_call.py | 2 +- vyper/venom/function.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/compiler/venom/test_call.py b/tests/unit/compiler/venom/test_call.py index 524efe921a..3e5ad7414e 100644 --- a/tests/unit/compiler/venom/test_call.py +++ b/tests/unit/compiler/venom/test_call.py @@ -35,7 +35,7 @@ def erc20(get_contract): def test_call(w3, market_maker, erc20, tx_failed): - a0 = w3.eth.accounts[0] + # a0 = w3.eth.accounts[0] ether, ethers = w3.to_wei(1, "ether"), w3.to_wei(2, "ether") erc20.approve(market_maker.address, ethers, transact={}) assert erc20.name() == TOKEN_NAME diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 79196c11c8..939ec3f921 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,6 +1,6 @@ from typing import Optional -from vyper.utils import OrderedSet +from vyper.utils import OrderedSet from vyper.venom.basicblock import ( CFG_ALTERING_INSTRUCTIONS, IRBasicBlock, From 96007e3bedd90448184b87aa8fce0a2e27e76a2a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 13:01:19 +0200 Subject: [PATCH 026/322] call staticcall return fix --- vyper/venom/ir_node_to_venom.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 96317f7b51..235a3eff99 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -343,20 +343,12 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): else: argsOffsetVar = argsOffset - if isinstance(retOffset, IRLiteral): - retOffsetValue = int(retOffset.value) if retOffset else 0 - retVar = ctx.get_next_variable(MemType.MEMORY, retOffsetValue) - symbols[f"&{retOffsetValue}"] = retVar - else: - retVar = retOffset - if ir.value == "call": args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] else: args = [retSize, retOffset, argsSize, argsOffsetVar, address, gas] - ctx.get_basic_block().insert_instruction(IRInstruction(ir.value, args, retVar)) - return retVar + return ctx.get_basic_block().append_instruction(ir.value, *args) elif ir.value == "if": cond = ir.args[0] From 6b689987b945a1cddc5b8796e4c036f89eca6101 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 14:19:13 +0200 Subject: [PATCH 027/322] Fix stack reordering multiples of operands case --- vyper/venom/venom_to_assembly.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index e0f8d5d648..e7a59a6a0a 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -152,11 +152,9 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: return top_asm def _stack_reorder( - self, assembly: list, stack: StackModel, _stack_ops: OrderedSet[IRVariable] + self, assembly: list, stack: StackModel, stack_ops: list[IRVariable] ) -> None: - # make a list so we can index it - stack_ops = [x for x in _stack_ops.keys()] - stack_ops_count = len(_stack_ops) + stack_ops_count = len(stack_ops) for i in range(stack_ops_count): op = stack_ops[i] @@ -316,11 +314,15 @@ def _generate_evm_for_instruction( b = next(iter(inst.parent.cfg_out)) target_stack = input_vars_from(inst.parent, b) # TODO optimize stack reordering at entry and exit from basic blocks - self._stack_reorder(assembly, stack, target_stack) + # NOTE: stack in general can contain multiple copies of the same variable, + # however we are safe in the case of jmp/djmp/jnz as it's not going to + # have multiples. + target_stack_list = [x for x in target_stack.keys()] + self._stack_reorder(assembly, stack, target_stack_list) # final step to get the inputs to this instruction ordered # correctly on the stack - self._stack_reorder(assembly, stack, OrderedSet(operands)) + self._stack_reorder(assembly, stack, operands) # type: ignore # some instructions (i.e. invoke) need to do stack manipulations # with the stack model containing the return value(s), so we fiddle From 40d2a0924ab0a22728ce5dd48e3f73c5e8896518 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 15:18:11 +0200 Subject: [PATCH 028/322] Commented out stack reordering code --- vyper/venom/venom_to_assembly.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index e7a59a6a0a..72a3bd2453 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -308,17 +308,17 @@ def _generate_evm_for_instruction( self._emit_input_operands(assembly, inst, operands, stack) # Step 3: Reorder stack - if opcode in ["jnz", "djmp", "jmp"]: - # prepare stack for jump into another basic block - assert inst.parent and isinstance(inst.parent.cfg_out, OrderedSet) - b = next(iter(inst.parent.cfg_out)) - target_stack = input_vars_from(inst.parent, b) - # TODO optimize stack reordering at entry and exit from basic blocks - # NOTE: stack in general can contain multiple copies of the same variable, - # however we are safe in the case of jmp/djmp/jnz as it's not going to - # have multiples. - target_stack_list = [x for x in target_stack.keys()] - self._stack_reorder(assembly, stack, target_stack_list) + # if opcode in ["jnz", "djmp", "jmp"]: + # # prepare stack for jump into another basic block + # assert inst.parent and isinstance(inst.parent.cfg_out, OrderedSet) + # b = next(iter(inst.parent.cfg_out)) + # target_stack = input_vars_from(inst.parent, b) + # # TODO optimize stack reordering at entry and exit from basic blocks + # # NOTE: stack in general can contain multiple copies of the same variable, + # # however we are safe in the case of jmp/djmp/jnz as it's not going to + # # have multiples. + # target_stack_list = [x for x in target_stack.keys()] + # self._stack_reorder(assembly, stack, target_stack_list) # final step to get the inputs to this instruction ordered # correctly on the stack From 128f0736ff7a46c56ea98ab84a9b8b2826670377 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 18:35:19 +0200 Subject: [PATCH 029/322] Multi datacopy demultiplexing --- vyper/venom/ir_node_to_venom.py | 49 +++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 235a3eff99..bffc989ffa 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -233,6 +233,20 @@ def _get_variable_from_address( return None +def _get_variables_from_address_and_size( + variables: OrderedSet[VariableRecord], addr: int, size: int +) -> list[VariableRecord]: + assert isinstance(addr, int), "non-int address" + addr_end = addr + size + ret = [] + for var in variables.keys(): + if var.location.name != "memory": + continue + if var.pos >= addr and var.pos + var.size <= addr_end: # type: ignore + ret.append(var) + return ret + + def _append_return_for_stack_operand( ctx: IRFunction, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable ) -> None: @@ -452,22 +466,20 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ) new_v = arg_0 - var = ( - _get_variable_from_address(variables, int(arg_0.value)) - if isinstance(arg_0, IRLiteral) - else None - ) bb = ctx.get_basic_block() - if var is not None: - if allocated_variables.get(var.name, None) is None: - new_v = bb.append_instruction("alloca", var.size, var.pos) # type: ignore - allocated_variables[var.name] = new_v # type: ignore - bb.append_instruction("calldatacopy", size, arg_1, new_v) # type: ignore - symbols[f"&{var.pos}"] = new_v # type: ignore - else: - bb.append_instruction("calldatacopy", size, arg_1, new_v) # type: ignore + if isinstance(arg_0, IRLiteral) and isinstance(size, IRLiteral): + vars = _get_variables_from_address_and_size( + variables, int(arg_0.value), int(size.value) + ) + for var in vars: + if allocated_variables.get(var.name, None) is None: + new_v = ctx.get_basic_block().append_instruction("alloca", var.size, var.pos) + allocated_variables[var.name] = new_v + symbols[f"&{var.pos}"] = new_v + + bb.append_instruction("calldatacopy", size, arg_1, arg_0) # type: ignore - return new_v + return None elif ir.value == "codecopy": arg_0, arg_1, size = _convert_ir_bb_list( ctx, ir.args, symbols, variables, allocated_variables @@ -796,7 +808,14 @@ def emit_body_blocks(): 1, ) - body_block.replace_operands(replacements) + i = 0 + for bb in ctx.basic_blocks: + if bb.label == body_block.label: + break + i += 1 + + for bb in ctx.basic_blocks[i:]: + bb.replace_operands(replacements) body_end = ctx.get_basic_block() if not body_end.is_terminated: From 185efde3904ce4eb37fd834493aaee7971231a91 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 19:05:55 +0200 Subject: [PATCH 030/322] Update label naming issue in NormalizationPass and recalc reachability --- vyper/venom/passes/normalization.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 26699099b2..963dc08519 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -28,7 +28,7 @@ def _insert_split_basicblock(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> IRB source = in_bb.label.value target = bb.label.value - split_label = IRLabel(f"{target}_split_{source}") + split_label = IRLabel(f"{source}_split_{target}") in_terminal = in_bb.instructions[-1] in_terminal.replace_label_operands({bb.label: split_label}) @@ -55,5 +55,6 @@ def _run_pass(self, ctx: IRFunction) -> int: # If we made changes, recalculate the cfg if self.changes > 0: calculate_cfg(ctx) + ctx.remove_unreachable_blocks() return self.changes From cb119d8b76a010cbc49b59bb1c86cc4794959352 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 19:06:06 +0200 Subject: [PATCH 031/322] Update test_multi_entry_block.py with correct label names --- tests/unit/compiler/venom/test_multi_entry_block.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/compiler/venom/test_multi_entry_block.py b/tests/unit/compiler/venom/test_multi_entry_block.py index 6d8b074994..cc148416a5 100644 --- a/tests/unit/compiler/venom/test_multi_entry_block.py +++ b/tests/unit/compiler/venom/test_multi_entry_block.py @@ -41,8 +41,8 @@ def test_multi_entry_block_1(): finish_bb = ctx.get_basic_block(finish_label.value) cfg_in = list(finish_bb.cfg_in.keys()) assert cfg_in[0].label.value == "target", "Should contain target" - assert cfg_in[1].label.value == "finish_split___global", "Should contain finish_split___global" - assert cfg_in[2].label.value == "finish_split_block_1", "Should contain finish_split_block_1" + 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" # more complicated one @@ -93,8 +93,8 @@ def test_multi_entry_block_2(): finish_bb = ctx.get_basic_block(finish_label.value) cfg_in = list(finish_bb.cfg_in.keys()) assert cfg_in[0].label.value == "target", "Should contain target" - assert cfg_in[1].label.value == "finish_split___global", "Should contain finish_split___global" - assert cfg_in[2].label.value == "finish_split_block_1", "Should contain finish_split_block_1" + 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(): @@ -134,5 +134,5 @@ def test_multi_entry_block_with_dynamic_jump(): finish_bb = ctx.get_basic_block(finish_label.value) cfg_in = list(finish_bb.cfg_in.keys()) assert cfg_in[0].label.value == "target", "Should contain target" - assert cfg_in[1].label.value == "finish_split___global", "Should contain finish_split___global" - assert cfg_in[2].label.value == "finish_split_block_1", "Should contain finish_split_block_1" + 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" From 47a06862ddfa8607542975311ba64c0a19dc020f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 19:48:28 +0200 Subject: [PATCH 032/322] Add daisy chaining of empty basic blocks as an alternative to block removal --- vyper/venom/bb_optimizer.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index 620ee66d15..5174a13d43 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -56,9 +56,31 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> int: return count +def _daisychain_empty_basicblocks(ctx: IRFunction) -> int: + count = 0 + i = 0 + while i < len(ctx.basic_blocks): + bb = ctx.basic_blocks[i] + i += 1 + if bb.is_terminated: + continue + + if i < len(ctx.basic_blocks) - 1: + bb.append_instruction("jmp", ctx.basic_blocks[i + 1].label) + else: + bb.append_instruction("stop") + + count += 1 + + return count + + @ir_pass def ir_pass_optimize_empty_blocks(ctx: IRFunction) -> int: - changes = _optimize_empty_basicblocks(ctx) + # NOTE: Replaced basic block removal for now, as it breaks the optimizer + # the normalization of 2 inputs to phi nodes. It just daisy chains empty + # blocks instead. + changes = _daisychain_empty_basicblocks(ctx) calculate_cfg(ctx) return changes From cad98ed81068e735c3ad2fc711d810b1f081314a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 19 Jan 2024 20:05:28 +0200 Subject: [PATCH 033/322] back to removal of bb --- vyper/venom/bb_optimizer.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index 5174a13d43..60dd8bbee1 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -77,10 +77,7 @@ def _daisychain_empty_basicblocks(ctx: IRFunction) -> int: @ir_pass def ir_pass_optimize_empty_blocks(ctx: IRFunction) -> int: - # NOTE: Replaced basic block removal for now, as it breaks the optimizer - # the normalization of 2 inputs to phi nodes. It just daisy chains empty - # blocks instead. - changes = _daisychain_empty_basicblocks(ctx) + changes = _optimize_empty_basicblocks(ctx) calculate_cfg(ctx) return changes From 45410440a021a744747ce04282b847b6acbe4c27 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 20 Jan 2024 13:25:09 +0200 Subject: [PATCH 034/322] Add Iterator support to OrderedSet class --- vyper/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/utils.py b/vyper/utils.py index 2349731b97..495698c0cd 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -1,4 +1,5 @@ import binascii +from collections.abc import Iterator import contextlib import decimal import functools @@ -58,6 +59,9 @@ def update(self, other): def __or__(self, other): return self.__class__(super().__or__(other)) + def __iter__(self) -> Iterator[_T]: + return iter(self.keys()) + def copy(self): return self.__class__(super().copy()) From e9ec36e426663d3b72a51411807fc74f10bf6b18 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 20 Jan 2024 14:56:35 +0200 Subject: [PATCH 035/322] add sink block at iftheelse code --- vyper/venom/ir_node_to_venom.py | 4 ++++ vyper/venom/venom_to_assembly.py | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index bffc989ffa..adf8d69439 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -428,6 +428,10 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if not then_block.is_terminated: then_block.append_instruction("jmp", exit_bb.label) + sink_block = IRBasicBlock(ctx.get_next_label(), ctx) + ctx.append_basic_block(sink_block) + exit_bb.append_instruction("jmp", sink_block.label) + return if_ret elif ir.value == "with": diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 72a3bd2453..e7a59a6a0a 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -308,17 +308,17 @@ def _generate_evm_for_instruction( self._emit_input_operands(assembly, inst, operands, stack) # Step 3: Reorder stack - # if opcode in ["jnz", "djmp", "jmp"]: - # # prepare stack for jump into another basic block - # assert inst.parent and isinstance(inst.parent.cfg_out, OrderedSet) - # b = next(iter(inst.parent.cfg_out)) - # target_stack = input_vars_from(inst.parent, b) - # # TODO optimize stack reordering at entry and exit from basic blocks - # # NOTE: stack in general can contain multiple copies of the same variable, - # # however we are safe in the case of jmp/djmp/jnz as it's not going to - # # have multiples. - # target_stack_list = [x for x in target_stack.keys()] - # self._stack_reorder(assembly, stack, target_stack_list) + if opcode in ["jnz", "djmp", "jmp"]: + # prepare stack for jump into another basic block + assert inst.parent and isinstance(inst.parent.cfg_out, OrderedSet) + b = next(iter(inst.parent.cfg_out)) + target_stack = input_vars_from(inst.parent, b) + # TODO optimize stack reordering at entry and exit from basic blocks + # NOTE: stack in general can contain multiple copies of the same variable, + # however we are safe in the case of jmp/djmp/jnz as it's not going to + # have multiples. + target_stack_list = [x for x in target_stack.keys()] + self._stack_reorder(assembly, stack, target_stack_list) # final step to get the inputs to this instruction ordered # correctly on the stack From 6ba2e44b5916dc52f8e4ce286d35d06636db9fd3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 20 Jan 2024 18:53:12 +0200 Subject: [PATCH 036/322] dyarray in loops issues --- vyper/venom/function.py | 6 ++++-- vyper/venom/ir_node_to_venom.py | 28 ++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 939ec3f921..ae04114e95 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -95,9 +95,11 @@ def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: """ return [bb for bb in self.basic_blocks if basic_block.label in bb.cfg_in] - def get_next_label(self) -> IRLabel: + def get_next_label(self, suffix: str = "") -> IRLabel: + if suffix != "": + suffix = f"_{suffix}" self.last_label += 1 - return IRLabel(f"{self.last_label}") + return IRLabel(f"{self.last_label}{suffix}") def get_next_variable( self, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: Optional[int] = None diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index adf8d69439..966059b74e 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,6 +1,7 @@ from typing import Optional from vyper.codegen.context import VariableRecord +from vyper.codegen.core import is_array_like from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes from vyper.exceptions import CompilerPanic @@ -630,6 +631,9 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb = ctx.get_basic_block() if var is not None: if var.size and var.size > 32: + if is_array_like(var.typ): + return bb.append_instruction("store", var.pos) + if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = bb.append_instruction( "alloca", var.size, var.pos @@ -771,12 +775,16 @@ def emit_body_blocks(): body = ir.args[4] - entry_block = ctx.get_basic_block() - cond_block = IRBasicBlock(ctx.get_next_label(), ctx) - body_block = IRBasicBlock(ctx.get_next_label(), ctx) - jump_up_block = IRBasicBlock(ctx.get_next_label(), ctx) - increment_block = IRBasicBlock(ctx.get_next_label(), ctx) - exit_block = IRBasicBlock(ctx.get_next_label(), ctx) + entry_block = IRBasicBlock(ctx.get_next_label("repeat"), ctx) + cond_block = IRBasicBlock(ctx.get_next_label("condition"), ctx) + body_block = IRBasicBlock(ctx.get_next_label("body"), ctx) + jump_up_block = IRBasicBlock(ctx.get_next_label("jump_up"), ctx) + increment_block = IRBasicBlock(ctx.get_next_label("increment"), ctx) + exit_block = IRBasicBlock(ctx.get_next_label("exit"), ctx) + + bb = ctx.get_basic_block() + bb.append_instruction("jmp", entry_block.label) + ctx.append_basic_block(entry_block) counter_inc_var = ctx.get_next_variable() @@ -794,6 +802,7 @@ def emit_body_blocks(): ctx.append_basic_block(cond_block) start_syms = symbols.copy() + start_allocated = allocated_variables.copy() ctx.append_basic_block(body_block) emit_body_blocks() end_syms = symbols.copy() @@ -822,6 +831,13 @@ def emit_body_blocks(): bb.replace_operands(replacements) body_end = ctx.get_basic_block() + # for name, var in allocated_variables.items(): + # if start_allocated.get(name) is not None: + # continue + # body_end.append_instruction("dealloca", var) + + allocated_variables = start_allocated + if not body_end.is_terminated: body_end.append_instruction("jmp", jump_up_block.label) From 8dd4635837ad3c3219ea505def98cbf26df951ed Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 21 Jan 2024 00:00:43 +0200 Subject: [PATCH 037/322] Fix unreachable block removal - remove orphan phis --- vyper/venom/function.py | 16 +++++++++++++--- vyper/venom/ir_node_to_venom.py | 16 ++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index ae04114e95..008b4217a9 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -113,15 +113,25 @@ def get_last_variable(self) -> str: def remove_unreachable_blocks(self) -> int: self._compute_reachability() - removed = 0 + removed = [] new_basic_blocks = [] for bb in self.basic_blocks: if not bb.is_reachable: - removed += 1 + removed.append(bb) else: new_basic_blocks.append(bb) self.basic_blocks = new_basic_blocks - return removed + + for bb in removed: + for out_bb in bb.cfg_out: + for inst in out_bb.instructions: + if inst.opcode != "phi": + continue + in_labels = inst.get_label_operands() + if bb.label in in_labels: + out_bb.remove_instruction(inst) + + return len(removed) def _compute_reachability(self) -> None: """ diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 966059b74e..b23723a177 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -371,7 +371,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): cont_ret = _convert_ir_bb(ctx, cond, symbols, variables, allocated_variables) current_bb = ctx.get_basic_block() - else_block = IRBasicBlock(ctx.get_next_label(), ctx) + else_block = IRBasicBlock(ctx.get_next_label("else"), ctx) ctx.append_basic_block(else_block) # convert "else" @@ -388,7 +388,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): else_block = ctx.get_basic_block() # convert "then" - then_block = IRBasicBlock(ctx.get_next_label(), ctx) + then_block = IRBasicBlock(ctx.get_next_label("then"), ctx) ctx.append_basic_block(then_block) then_ret_val = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) @@ -401,7 +401,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): then_block = ctx.get_basic_block() # exit bb - exit_label = ctx.get_next_label() + exit_label = ctx.get_next_label("if_exit") exit_bb = IRBasicBlock(exit_label, ctx) exit_bb = ctx.append_basic_block(exit_bb) @@ -429,7 +429,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if not then_block.is_terminated: then_block.append_instruction("jmp", exit_bb.label) - sink_block = IRBasicBlock(ctx.get_next_label(), ctx) + sink_block = IRBasicBlock(ctx.get_next_label("sink"), ctx) ctx.append_basic_block(sink_block) exit_bb.append_instruction("jmp", sink_block.label) @@ -764,7 +764,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): def emit_body_blocks(): global _break_target, _continue_target old_targets = _break_target, _continue_target - _break_target, _continue_target = exit_block, increment_block + _break_target, _continue_target = exit_block, continue_block _convert_ir_bb(ctx, body, symbols, variables, allocated_variables) _break_target, _continue_target = old_targets @@ -779,6 +779,7 @@ def emit_body_blocks(): cond_block = IRBasicBlock(ctx.get_next_label("condition"), ctx) body_block = IRBasicBlock(ctx.get_next_label("body"), ctx) jump_up_block = IRBasicBlock(ctx.get_next_label("jump_up"), ctx) + continue_block = IRBasicBlock(ctx.get_next_label("continue"), ctx) increment_block = IRBasicBlock(ctx.get_next_label("increment"), ctx) exit_block = IRBasicBlock(ctx.get_next_label("exit"), ctx) @@ -841,9 +842,12 @@ def emit_body_blocks(): if not body_end.is_terminated: body_end.append_instruction("jmp", jump_up_block.label) - jump_up_block.append_instruction("jmp", increment_block.label) + jump_up_block.append_instruction("jmp", continue_block.label) ctx.append_basic_block(jump_up_block) + continue_block.append_instruction("jmp", increment_block.label) + ctx.append_basic_block(continue_block) + increment_block.insert_instruction( IRInstruction("add", [ret, IRLiteral(1)], counter_inc_var), 0 ) From 91f9fd9f65d1aef5123b68720514328d33672bf5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 21 Jan 2024 00:01:34 +0200 Subject: [PATCH 038/322] Add remove_instruction method, is_empty property to IRBasicBlock class --- vyper/venom/basicblock.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 4a92db26b1..4d53d4af54 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -386,6 +386,10 @@ def insert_instruction(self, instruction: IRInstruction, index: Optional[int] = instruction.parent = self self.instructions.insert(index, instruction) + def remove_instruction(self, instruction: IRInstruction) -> None: + assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" + self.instructions.remove(instruction) + def clear_instructions(self) -> None: self.instructions = [] @@ -396,6 +400,13 @@ def replace_operands(self, replacements: dict) -> None: for instruction in self.instructions: instruction.replace_operands(replacements) + @property + def is_empty(self) -> bool: + """ + Check if the basic block is empty, i.e. it has no instructions. + """ + return len(self.instructions) == 0 + @property def is_terminated(self) -> bool: """ From 2c7999df708a8aa4fc7397f6849828adee3974dc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 21 Jan 2024 00:12:08 +0200 Subject: [PATCH 039/322] comment --- vyper/venom/function.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 008b4217a9..2aff8c508b 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -115,6 +115,8 @@ def remove_unreachable_blocks(self) -> int: removed = [] new_basic_blocks = [] + + # Remove unreachable basic blocks for bb in self.basic_blocks: if not bb.is_reachable: removed.append(bb) @@ -122,6 +124,7 @@ def remove_unreachable_blocks(self) -> int: new_basic_blocks.append(bb) self.basic_blocks = new_basic_blocks + # Remove phi instructions that reference removed basic blocks for bb in removed: for out_bb in bb.cfg_out: for inst in out_bb.instructions: From 9f2606be53aa3f0317784fa2edb8502973717db7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 21 Jan 2024 12:42:37 +0200 Subject: [PATCH 040/322] Graphviz output --- vyper/venom/function.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 2aff8c508b..f07501d14a 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -200,6 +200,31 @@ def copy(self): new.last_variable = self.last_variable return new + def as_graph(self) -> str: + import html + + def _make_label(bb): + ret = f'<' + ret += f'' + for inst in bb.instructions: + ret += f'' + ret += "
{html.escape(bb.label.value)}
{html.escape(str(inst))}
>" + + return ret + # return f"{bb.label.value}:\n" + "\n".join([f" {inst}" for inst in bb.instructions]) + + ret = "digraph G {\n" + + for bb in self.basic_blocks: + for out_bb in bb.cfg_out: + ret += f' "{bb.label.value}" -> "{out_bb.label.value}"\n' + + for bb in self.basic_blocks: + ret += f' "{bb.label.value}" [shape=plaintext, label={_make_label(bb)}, fontname="Courier" fontsize="8"]\n' + + ret += "}\n" + return ret + def __repr__(self) -> str: str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: From 5b0a0622d7f920a5251acbea00dbc9820c807909 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 02:14:25 +0200 Subject: [PATCH 041/322] Dominance calculations --- vyper/venom/__init__.py | 11 ++++ vyper/venom/dominators.py | 103 ++++++++++++++++++++++++++++++++++++++ vyper/venom/function.py | 5 +- 3 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 vyper/venom/dominators.py diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index d1c2d0c342..beb2c42ba2 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -11,6 +11,7 @@ ir_pass_optimize_unused_variables, ir_pass_remove_unreachable_blocks, ) +from vyper.venom.dominators import DominatorTree from vyper.venom.function import IRFunction from vyper.venom.ir_node_to_venom import ir_node_to_venom from vyper.venom.passes.constant_propagation import ir_pass_constant_propagation @@ -46,6 +47,16 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: calculate_liveness(ctx) + if len(ctx.basic_blocks) > 3: + calculate_cfg(ctx) + + dom = DominatorTree(ctx, ctx.basic_blocks[0]) + print(dom.as_graph()) + # print(ctx.as_graph()) + import sys + + sys.exit(0) + changes += ir_pass_optimize_unused_variables(ctx) calculate_cfg(ctx) diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py new file mode 100644 index 0000000000..7d3db3a28c --- /dev/null +++ b/vyper/venom/dominators.py @@ -0,0 +1,103 @@ +from vyper.exceptions import CompilerPanic +from vyper.utils import OrderedSet +from vyper.venom.basicblock import IRBasicBlock +from vyper.venom.function import IRFunction + + +class DominatorTree: + """ + Dominator tree. + """ + + def __init__(self, ctx: IRFunction, entry: IRBasicBlock): + self.ctx = ctx + self.entry = entry + self.dfs_order = {} + self.dfs = [] + self.dominators = {} + self.idoms = {} + self._compute() + + def dominates(self, bb1, bb2): + return bb2 in self.dominators[bb1] + + def immediate_dominator(self, bb): + return self.idoms.get(bb) + + def _compute(self): + self._dfs(self.entry, set()) + self._compute_dominators() + self._compute_idoms() + + def _compute_dominators(self): + basic_blocks = list(self.dfs_order.keys()) + self.dominators = {bb: set(basic_blocks) for bb in basic_blocks} + self.dominators[self.entry] = {self.entry} + changed = True + count = len(basic_blocks) ** 2 # TODO: find a proper bound for this + while changed: + count -= 1 + if count < 0: + raise CompilerPanic("Dominators computation failed to converge") + changed = False + for bb in basic_blocks: + if bb == self.entry: + continue + preds = bb.cfg_in + if len(preds) > 0: + new_dominators = set.intersection(*[self.dominators[pred] for pred in preds]) + new_dominators.add(bb) + if new_dominators != self.dominators[bb]: + self.dominators[bb] = new_dominators + changed = True + + # for bb in basic_blocks: + # print(bb.label) + # for dom in self.dominators[bb]: + # print(" ", dom.label) + + def _compute_idoms(self): + """ + Compute immediate dominators + """ + self.idoms = {bb: None for bb in self.dfs_order.keys()} + self.idoms[self.entry] = self.entry + for bb in self.dfs: + if bb == self.entry: + continue + doms = sorted(self.dominators[bb], key=lambda x: self.dfs_order[x]) + self.idoms[bb] = doms[1] + + def _intersect(self, bb1, bb2): + dfs_order = self.dfs_order + while bb1 != bb2: + while dfs_order[bb1] < dfs_order[bb2]: + bb1 = self.idoms[bb1] + while dfs_order[bb1] > dfs_order[bb2]: + bb2 = self.idoms[bb2] + return bb1 + + def _dfs(self, entry: IRBasicBlock, visited): + visited.add(entry) + + for bb in entry.cfg_out: + if bb not in visited: + self._dfs(bb, visited) + + self.dfs.append(entry) + self.dfs_order[entry] = len(self.dfs) + + def as_graph(self) -> str: + """ + Generate a graphviz representation of the dominator tree. + """ + lines = ["digraph dominator_tree {"] + for bb in self.ctx.basic_blocks: + if bb == self.entry: + continue + idom = self.immediate_dominator(bb) + if idom is None: + continue + lines.append(f' "{idom.label}" -> "{bb.label}"') + lines.append("}") + return "\n".join(lines) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index f07501d14a..131c557874 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -205,9 +205,9 @@ def as_graph(self) -> str: def _make_label(bb): ret = f'<' - ret += f'' + ret += f'\n' for inst in bb.instructions: - ret += f'' + ret += f'\n' ret += "
{html.escape(bb.label.value)}
{html.escape(str(bb.label))}
{html.escape(str(inst))}
{html.escape(str(inst))}
>" return ret @@ -226,6 +226,7 @@ def _make_label(bb): return ret def __repr__(self) -> str: + return self.as_graph() str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: str += f"{bb}\n" From 9c294c787b4b0f2d5359348a46c988dc355f042b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 13:10:03 +0200 Subject: [PATCH 042/322] Add dominance frontier computation --- vyper/venom/dominators.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index 7d3db3a28c..164d3454f3 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -16,6 +16,7 @@ def __init__(self, ctx: IRFunction, entry: IRBasicBlock): self.dfs = [] self.dominators = {} self.idoms = {} + self.df = {} self._compute() def dominates(self, bb1, bb2): @@ -28,6 +29,7 @@ def _compute(self): self._dfs(self.entry, set()) self._compute_dominators() self._compute_idoms() + self._compute_df() def _compute_dominators(self): basic_blocks = list(self.dfs_order.keys()) @@ -68,6 +70,26 @@ def _compute_idoms(self): doms = sorted(self.dominators[bb], key=lambda x: self.dfs_order[x]) self.idoms[bb] = doms[1] + def _compute_df(self): + """ + Compute dominance frontier + """ + basic_blocks = self.dfs + self.df = {bb: set() for bb in basic_blocks} + + for bb in self.dfs: + if len(bb.cfg_in) > 1: + for pred in bb.cfg_in: + runner = pred + while runner != self.idoms[bb]: + self.df[runner].add(bb) + runner = self.idoms[runner] + + # for bb in self.dfs: + # print(bb.label) + # for df in self.df[bb]: + # print(" ", df.label) + def _intersect(self, bb1, bb2): dfs_order = self.dfs_order while bb1 != bb2: From 73b81632dd18554797d67b1bc0a385075b5655ed Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 13:52:11 +0200 Subject: [PATCH 043/322] Add test for dominator tree calculation --- .../compiler/venom/test_dominator_tree.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/unit/compiler/venom/test_dominator_tree.py diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py new file mode 100644 index 0000000000..285ef086e9 --- /dev/null +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -0,0 +1,53 @@ +from typing import Optional +from vyper.venom.analysis import calculate_cfg +from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRLiteral +from vyper.venom.dominators import DominatorTree +from vyper.venom.function import IRFunction + + +def _add_bb( + ctx: IRFunction, label: IRLabel, cfg_outs: [IRLabel], bb: Optional[IRBasicBlock] = None +) -> IRBasicBlock: + bb = bb if bb is not None else IRBasicBlock(label, ctx) + ctx.append_basic_block(bb) + cgf_outs_len = len(cfg_outs) + if cgf_outs_len == 0: + bb.append_instruction("stop") + elif cgf_outs_len == 1: + bb.append_instruction("jmp", cfg_outs[0]) + elif cgf_outs_len == 2: + bb.append_instruction("jnz", IRLiteral(1), cfg_outs[0], cfg_outs[1]) + else: + assert False, cgf_outs_len + return bb + + +def test_deminator_frontier_calculation(): + l = [IRLabel(str(i)) for i in range(0, 9)] + + ctx = IRFunction(l[1]) + + bb1 = ctx.basic_blocks[0] + bb1.append_instruction("jmp", l[2]) + + bb7 = _add_bb(ctx, l[7], []) + bb6 = _add_bb(ctx, l[6], [l[7], l[2]]) + bb5 = _add_bb(ctx, l[5], [l[6], l[3]]) + bb4 = _add_bb(ctx, l[4], [l[6]]) + bb3 = _add_bb(ctx, l[3], [l[5]]) + bb2 = _add_bb(ctx, l[2], [l[3], l[4]]) + + calculate_cfg(ctx) + dom = DominatorTree(ctx, bb1) + + assert dom.df[bb1] == set(), dom.df[bb1] + assert dom.df[bb2] == {bb2}, dom.df[bb2] + assert dom.df[bb3] == {bb3, bb6}, dom.df[bb3] + assert dom.df[bb4] == {bb6}, dom.df[bb4] + assert dom.df[bb5] == {bb3, bb6}, dom.df[bb5] + assert dom.df[bb6] == {bb2}, dom.df[bb6] + assert dom.df[bb7] == set(), dom.df[bb7] + + +if __name__ == "__main__": + test_deminator_frontier_calculation() From 495d3f900e9ab630690fff1274f9b0f6909a68c7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 14:12:24 +0200 Subject: [PATCH 044/322] refactor test --- .../compiler/venom/test_dominator_tree.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index 285ef086e9..78c5208915 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -22,7 +22,7 @@ def _add_bb( return bb -def test_deminator_frontier_calculation(): +def _make_test_ctx(): l = [IRLabel(str(i)) for i in range(0, 9)] ctx = IRFunction(l[1]) @@ -30,12 +30,19 @@ def test_deminator_frontier_calculation(): bb1 = ctx.basic_blocks[0] bb1.append_instruction("jmp", l[2]) - bb7 = _add_bb(ctx, l[7], []) - bb6 = _add_bb(ctx, l[6], [l[7], l[2]]) - bb5 = _add_bb(ctx, l[5], [l[6], l[3]]) - bb4 = _add_bb(ctx, l[4], [l[6]]) - bb3 = _add_bb(ctx, l[3], [l[5]]) - bb2 = _add_bb(ctx, l[2], [l[3], l[4]]) + _add_bb(ctx, l[7], []) + _add_bb(ctx, l[6], [l[7], l[2]]) + _add_bb(ctx, l[5], [l[6], l[3]]) + _add_bb(ctx, l[4], [l[6]]) + _add_bb(ctx, l[3], [l[5]]) + _add_bb(ctx, l[2], [l[3], l[4]]) + + return ctx + + +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)] calculate_cfg(ctx) dom = DominatorTree(ctx, bb1) From cbd0fd24717b40252b28688cc91f79bd80c49955 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 14:32:44 +0200 Subject: [PATCH 045/322] Add test for phi placement --- .../compiler/venom/test_dominator_tree.py | 19 +++++++++++++++++-- vyper/venom/passes/make_ssa.py | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 vyper/venom/passes/make_ssa.py diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index 78c5208915..5ae770f23e 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -1,8 +1,9 @@ from typing import Optional from vyper.venom.analysis import calculate_cfg -from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRLiteral +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRLiteral, IRVariable from vyper.venom.dominators import DominatorTree from vyper.venom.function import IRFunction +from vyper.venom.passes.make_ssa import MakeSSA def _add_bb( @@ -56,5 +57,19 @@ def test_deminator_frontier_calculation(): assert dom.df[bb7] == set(), dom.df[bb7] +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)] + + 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) + + print(ctx) + + if __name__ == "__main__": - test_deminator_frontier_calculation() + test_phi_placement() diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py new file mode 100644 index 0000000000..5ca2d1efd2 --- /dev/null +++ b/vyper/venom/passes/make_ssa.py @@ -0,0 +1,15 @@ +from vyper.venom.analysis import calculate_cfg +from vyper.venom.dominators import DominatorTree +from vyper.venom.function import IRFunction +from vyper.venom.passes.base_pass import IRPass + + +class MakeSSA(IRPass): + def _run_pass(self, ctx: IRFunction) -> int: + self.ctx = ctx + + calculate_cfg(ctx) + entry = ctx.get_basic_block(ctx.entry_points[0].value) + dom = DominatorTree(ctx, entry) + + self.changes = 0 From 0be6409a105cb020731133a0685297fde50f10e5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 14:41:04 +0200 Subject: [PATCH 046/322] Remove testing code --- vyper/venom/__init__.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index beb2c42ba2..e36dd34fcd 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -47,16 +47,6 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: calculate_liveness(ctx) - if len(ctx.basic_blocks) > 3: - calculate_cfg(ctx) - - dom = DominatorTree(ctx, ctx.basic_blocks[0]) - print(dom.as_graph()) - # print(ctx.as_graph()) - import sys - - sys.exit(0) - changes += ir_pass_optimize_unused_variables(ctx) calculate_cfg(ctx) From b3a450fcf29fa9e027224c1c240749ad3898d64b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 20:32:17 +0200 Subject: [PATCH 047/322] phi placement --- vyper/venom/dominators.py | 9 +++++++++ vyper/venom/passes/make_ssa.py | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index 164d3454f3..ea1acd2c86 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -90,6 +90,15 @@ def _compute_df(self): # for df in self.df[bb]: # print(" ", df.label) + def dominance_frontier(self, basic_blocks: list[IRBasicBlock]): + """ + Compute dominance frontier of a set of basic blocks. + """ + df = set() + for bb in basic_blocks: + df.update(self.df[bb]) + return df + def _intersect(self, bb1, bb2): dfs_order = self.dfs_order while bb1 != bb2: diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 5ca2d1efd2..3f14d4bf40 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -1,4 +1,5 @@ from vyper.venom.analysis import calculate_cfg +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable from vyper.venom.dominators import DominatorTree from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass @@ -12,4 +13,27 @@ def _run_pass(self, ctx: IRFunction) -> int: entry = ctx.get_basic_block(ctx.entry_points[0].value) dom = DominatorTree(ctx, entry) + defs = {} + for bb in ctx.basic_blocks: + assignments = bb.get_assignments() + for var in assignments: + if var not in defs: + defs[var] = set() + defs[var].add(bb) + + for var, d in defs.items(): + df = dom.dominance_frontier(d) + for bb in df: + self._add_phi(var, bb) + self.changes = 0 + + def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock): + args = [] + for bb in basic_block.cfg_in: + if bb == basic_block: + continue + args.append(var) + args.append(bb.label) + phi = IRInstruction("phi", args, var) + basic_block.instructions.insert(0, phi) From 038caf91f53b4469aeb9d5223a5bb84aa9dd53ef Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 21:13:09 +0200 Subject: [PATCH 048/322] Add support for tracking dominated blocks in DominatorTree --- vyper/venom/dominators.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index ea1acd2c86..685ff525c0 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -16,6 +16,7 @@ def __init__(self, ctx: IRFunction, entry: IRBasicBlock): self.dfs = [] self.dominators = {} self.idoms = {} + self.dominated = {} self.df = {} self._compute() @@ -70,6 +71,15 @@ def _compute_idoms(self): doms = sorted(self.dominators[bb], key=lambda x: self.dfs_order[x]) self.idoms[bb] = doms[1] + self.dominated = {bb: set() for bb in self.dfs} + for dom, target in self.idoms.items(): + self.dominated[target].add(dom) + + # for dom, targets in self.dominated.items(): + # print(dom.label) + # for t in targets: + # print(" ", t.label) + def _compute_df(self): """ Compute dominance frontier From d441c5ed86638aa21071dacfb8dc49450483c232 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 21:13:34 +0200 Subject: [PATCH 049/322] add get_assignments method --- vyper/venom/basicblock.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 4d53d4af54..eaec3cc0ff 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -260,8 +260,8 @@ def __repr__(self) -> str: if self.annotation: s += f" <{self.annotation}>" - # if self.liveness: - # return f"{s: <30} # {self.liveness}" + if self.liveness: + return f"{s: <30} # {self.liveness}" return s @@ -400,6 +400,12 @@ def replace_operands(self, replacements: dict) -> None: for instruction in self.instructions: instruction.replace_operands(replacements) + def get_assignments(self): + """ + Get all assignments in basic block. + """ + return [inst.output for inst in self.instructions if inst.output] + @property def is_empty(self) -> bool: """ From 109c697acedc901830ef8405e7faa23938586c85 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 21:13:54 +0200 Subject: [PATCH 050/322] renaming start --- vyper/venom/passes/make_ssa.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 3f14d4bf40..320b87029e 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -6,28 +6,43 @@ class MakeSSA(IRPass): + dom: DominatorTree + def _run_pass(self, ctx: IRFunction) -> int: self.ctx = ctx calculate_cfg(ctx) entry = ctx.get_basic_block(ctx.entry_points[0].value) dom = DominatorTree(ctx, entry) + self.dom = dom + + # self._dfs_dom(entry, set()) + + print(ctx.as_graph()) + + self.changes = 0 + def _dfs_dom(self, basic_block: IRBasicBlock, visited: set): + visited.add(basic_block) + for bb in self.dom.dominated[basic_block]: + if bb not in visited: + self._dfs_dom(bb, visited) + + self._process_basic_block(basic_block) + + def _process_basic_block(self, basic_block: IRBasicBlock): defs = {} - for bb in ctx.basic_blocks: - assignments = bb.get_assignments() - for var in assignments: - if var not in defs: - defs[var] = set() - defs[var].add(bb) + assignments = basic_block.get_assignments() + for var in assignments: + if var not in defs: + defs[var] = set() + defs[var].add(basic_block) for var, d in defs.items(): - df = dom.dominance_frontier(d) + df = self.dom.dominance_frontier(d) for bb in df: self._add_phi(var, bb) - self.changes = 0 - def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock): args = [] for bb in basic_block.cfg_in: From 4c25296d0fc67065c47a8f87028a9d26d933aa8a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 21:26:05 +0200 Subject: [PATCH 051/322] refactor --- vyper/venom/passes/make_ssa.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 320b87029e..a44593c629 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -7,6 +7,7 @@ class MakeSSA(IRPass): dom: DominatorTree + defs: dict[IRVariable, set[IRBasicBlock]] def _run_pass(self, ctx: IRFunction) -> int: self.ctx = ctx @@ -16,12 +17,20 @@ def _run_pass(self, ctx: IRFunction) -> int: dom = DominatorTree(ctx, entry) self.dom = dom + self._compute_defs() + self._add_phi_nodes() # self._dfs_dom(entry, set()) print(ctx.as_graph()) self.changes = 0 + def _add_phi_nodes(self): + for var, defs in self.defs.items(): + for bb in defs: + for front in self.dom.df[bb]: + self._add_phi(var, front) + def _dfs_dom(self, basic_block: IRBasicBlock, visited: set): visited.add(basic_block) for bb in self.dom.dominated[basic_block]: @@ -30,20 +39,8 @@ def _dfs_dom(self, basic_block: IRBasicBlock, visited: set): self._process_basic_block(basic_block) - def _process_basic_block(self, basic_block: IRBasicBlock): - defs = {} - assignments = basic_block.get_assignments() - for var in assignments: - if var not in defs: - defs[var] = set() - defs[var].add(basic_block) - - for var, d in defs.items(): - df = self.dom.dominance_frontier(d) - for bb in df: - self._add_phi(var, bb) - def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock): + # TODO: check if the phi already exists args = [] for bb in basic_block.cfg_in: if bb == basic_block: @@ -52,3 +49,12 @@ def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock): args.append(bb.label) phi = IRInstruction("phi", args, var) basic_block.instructions.insert(0, phi) + + def _compute_defs(self): + self.defs = {} + for bb in self.dom.dfs: + assignments = bb.get_assignments() + for var in assignments: + if var not in self.defs: + self.defs[var] = set() + self.defs[var].add(bb) From a6d969311abf89289afe1431131e2fd23231c614 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 21:54:04 +0200 Subject: [PATCH 052/322] Add variable name property to IRVariable class and implement variable renaming in MakeSSA pass --- vyper/venom/basicblock.py | 6 +++++ vyper/venom/passes/make_ssa.py | 44 ++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index eaec3cc0ff..cbfeab933c 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -1,4 +1,5 @@ from enum import Enum, auto +import re from typing import TYPE_CHECKING, Any, Iterator, Optional, Union from vyper.utils import OrderedSet @@ -139,6 +140,11 @@ def __init__( self.mem_type = mem_type self.mem_addr = mem_addr + @property + def name(self) -> str: + s = re.split(r"(\d+)", self.value) + return s[0] + def __repr__(self) -> str: return self.value diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index a44593c629..492d2bdd6c 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -19,7 +19,9 @@ def _run_pass(self, ctx: IRFunction) -> int: self._compute_defs() self._add_phi_nodes() - # self._dfs_dom(entry, set()) + + self.var_names = {var.name: 0 for var in self.defs.keys()} + self._rename_vars(entry, set()) print(ctx.as_graph()) @@ -31,13 +33,40 @@ def _add_phi_nodes(self): for front in self.dom.df[bb]: self._add_phi(var, front) - def _dfs_dom(self, basic_block: IRBasicBlock, visited: set): + def _rename_vars(self, basic_block: IRBasicBlock, visited: set): visited.add(basic_block) - for bb in self.dom.dominated[basic_block]: - if bb not in visited: - self._dfs_dom(bb, visited) - self._process_basic_block(basic_block) + for inst in basic_block.instructions: + if inst.output is None: + continue + + inst.replace_operands( + {inst.output: IRVariable(f"{inst.output.name}{self.var_names[inst.output.name]}")} + ) + self.var_names[inst.output.name] += 1 + inst.output = IRVariable(f"{inst.output.value}{self.var_names[inst.output.name]}") + + for bb in basic_block.cfg_out: + for inst in bb.instructions: + if inst.opcode != "phi": + continue + inst.replace_operands( + { + inst.output: IRVariable( + f"{inst.output.value}{self.var_names[inst.output.name]}" + ) + } + ) + + for bb in self.dom.dominated[basic_block]: + if bb in visited: + continue + self._rename_vars(bb, visited) + + for inst in basic_block.instructions: + if inst.output is None: + continue + self.var_names[inst.output.name] -= 1 def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock): # TODO: check if the phi already exists @@ -45,8 +74,9 @@ def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock): for bb in basic_block.cfg_in: if bb == basic_block: continue - args.append(var) args.append(bb.label) + args.append(var) + phi = IRInstruction("phi", args, var) basic_block.instructions.insert(0, phi) From 41b95977edeccc2d3b99002fcf274c6dd470ac0d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 22:07:41 +0200 Subject: [PATCH 053/322] Fix variable renaming --- .../compiler/venom/test_dominator_tree.py | 2 +- vyper/venom/passes/make_ssa.py | 28 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index 5ae770f23e..ea2e0a20d4 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -68,7 +68,7 @@ def test_phi_placement(): MakeSSA.run_pass(ctx) - print(ctx) + # print(ctx) if __name__ == "__main__": diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 492d2bdd6c..8c05eddbee 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -46,22 +46,20 @@ def _rename_vars(self, basic_block: IRBasicBlock, visited: set): self.var_names[inst.output.name] += 1 inst.output = IRVariable(f"{inst.output.value}{self.var_names[inst.output.name]}") - for bb in basic_block.cfg_out: - for inst in bb.instructions: - if inst.opcode != "phi": - continue - inst.replace_operands( - { - inst.output: IRVariable( - f"{inst.output.value}{self.var_names[inst.output.name]}" - ) - } - ) - - for bb in self.dom.dominated[basic_block]: - if bb in visited: + for bb in basic_block.cfg_out: + for inst in bb.instructions: + if inst.opcode != "phi": continue - self._rename_vars(bb, visited) + for i, op in enumerate(inst.operands): + if op == basic_block.label: + inst.operands[i + 1] = IRVariable( + f"{inst.output.name}{self.var_names[inst.output.name]}" + ) + + for bb in self.dom.dominated[basic_block]: + if bb in visited: + continue + self._rename_vars(bb, visited) for inst in basic_block.instructions: if inst.output is None: From f7518f16bd4788688daa27f3a8029e8ae29c4e13 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 22:11:56 +0200 Subject: [PATCH 054/322] lint --- .../compiler/venom/test_dominator_tree.py | 23 ++++++++++--------- vyper/venom/dominators.py | 1 - vyper/venom/function.py | 5 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index ea2e0a20d4..921959fed4 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -1,4 +1,5 @@ from typing import Optional +from vyper.exceptions import CompilerPanic from vyper.venom.analysis import calculate_cfg from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRLiteral, IRVariable from vyper.venom.dominators import DominatorTree @@ -19,24 +20,24 @@ def _add_bb( elif cgf_outs_len == 2: bb.append_instruction("jnz", IRLiteral(1), cfg_outs[0], cfg_outs[1]) else: - assert False, cgf_outs_len + raise CompilerPanic("Invalid number of CFG outs") return bb def _make_test_ctx(): - l = [IRLabel(str(i)) for i in range(0, 9)] + lab = [IRLabel(str(i)) for i in range(0, 9)] - ctx = IRFunction(l[1]) + ctx = IRFunction(lab[1]) bb1 = ctx.basic_blocks[0] - bb1.append_instruction("jmp", l[2]) - - _add_bb(ctx, l[7], []) - _add_bb(ctx, l[6], [l[7], l[2]]) - _add_bb(ctx, l[5], [l[6], l[3]]) - _add_bb(ctx, l[4], [l[6]]) - _add_bb(ctx, l[3], [l[5]]) - _add_bb(ctx, l[2], [l[3], l[4]]) + bb1.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]]) return ctx diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index 685ff525c0..9da5b08cce 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -1,5 +1,4 @@ from vyper.exceptions import CompilerPanic -from vyper.utils import OrderedSet from vyper.venom.basicblock import IRBasicBlock from vyper.venom.function import IRFunction diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 131c557874..4fc490743d 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -204,7 +204,7 @@ def as_graph(self) -> str: import html def _make_label(bb): - ret = f'<' + ret = '<
' ret += f'\n' for inst in bb.instructions: ret += f'\n' @@ -220,7 +220,8 @@ def _make_label(bb): ret += f' "{bb.label.value}" -> "{out_bb.label.value}"\n' for bb in self.basic_blocks: - ret += f' "{bb.label.value}" [shape=plaintext, label={_make_label(bb)}, fontname="Courier" fontsize="8"]\n' + ret += f' "{bb.label.value}" [shape=plaintext, ' + ret += 'label={_make_label(bb)}, fontname="Courier" fontsize="8"]\n' ret += "}\n" return ret From b1990e462dc223ad83d5db86b2451e3e77ce7166 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 22:14:04 +0200 Subject: [PATCH 055/322] Fix formatting --- vyper/venom/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 4fc490743d..51cd79baaf 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -221,7 +221,7 @@ def _make_label(bb): for bb in self.basic_blocks: ret += f' "{bb.label.value}" [shape=plaintext, ' - ret += 'label={_make_label(bb)}, fontname="Courier" fontsize="8"]\n' + ret += f'label={_make_label(bb)}, fontname="Courier" fontsize="8"]\n' ret += "}\n" return ret From 6bb3dc6f93122dd02f1667170eb31576ae488b25 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 23:34:38 +0200 Subject: [PATCH 056/322] more generic --- vyper/venom/basicblock.py | 3 +-- vyper/venom/passes/make_ssa.py | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index cbfeab933c..c30289cf77 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -142,8 +142,7 @@ def __init__( @property def name(self) -> str: - s = re.split(r"(\d+)", self.value) - return s[0] + return self.value.split(":")[0] def __repr__(self) -> str: return self.value diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 8c05eddbee..84ee66a49b 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -23,7 +23,7 @@ def _run_pass(self, ctx: IRFunction) -> int: self.var_names = {var.name: 0 for var in self.defs.keys()} self._rename_vars(entry, set()) - print(ctx.as_graph()) + # print(ctx.as_graph()) self.changes = 0 @@ -37,14 +37,18 @@ def _rename_vars(self, basic_block: IRBasicBlock, visited: set): visited.add(basic_block) for inst in basic_block.instructions: - if inst.output is None: - continue + new_ops = [] + for op in inst.operands: + if not isinstance(op, IRVariable): + new_ops.append(op) + continue + + new_ops.append(IRVariable(f"{op.name}:{self.var_names[op.name]}")) - inst.replace_operands( - {inst.output: IRVariable(f"{inst.output.name}{self.var_names[inst.output.name]}")} - ) - self.var_names[inst.output.name] += 1 - inst.output = IRVariable(f"{inst.output.value}{self.var_names[inst.output.name]}") + inst.operands = new_ops + if inst.output is not None: + self.var_names[inst.output.name] += 1 + inst.output = IRVariable(f"{inst.output.name}:{self.var_names[inst.output.name]}") for bb in basic_block.cfg_out: for inst in bb.instructions: @@ -53,7 +57,7 @@ def _rename_vars(self, basic_block: IRBasicBlock, visited: set): for i, op in enumerate(inst.operands): if op == basic_block.label: inst.operands[i + 1] = IRVariable( - f"{inst.output.name}{self.var_names[inst.output.name]}" + f"{inst.output.name}:{self.var_names[inst.output.name]}" ) for bb in self.dom.dominated[basic_block]: From 9a5513fb4597608219624a3fefc2c2bea40625ba Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 22 Jan 2024 23:56:37 +0200 Subject: [PATCH 057/322] improvements --- vyper/venom/passes/make_ssa.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 84ee66a49b..8127d7a97b 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -1,3 +1,4 @@ +from vyper.exceptions import CompilerPanic from vyper.venom.analysis import calculate_cfg from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable from vyper.venom.dominators import DominatorTree @@ -17,8 +18,10 @@ def _run_pass(self, ctx: IRFunction) -> int: dom = DominatorTree(ctx, entry) self.dom = dom - self._compute_defs() - self._add_phi_nodes() + count = 0 + while self._add_phi_nodes(): + if count := count + 1 > len(ctx.basic_blocks) * 2: + raise CompilerPanic("Failed to add phi nodes") self.var_names = {var.name: 0 for var in self.defs.keys()} self._rename_vars(entry, set()) @@ -27,11 +30,15 @@ def _run_pass(self, ctx: IRFunction) -> int: self.changes = 0 - def _add_phi_nodes(self): + def _add_phi_nodes(self) -> bool: + self._compute_defs() + changed = False for var, defs in self.defs.items(): for bb in defs: for front in self.dom.df[bb]: - self._add_phi(var, front) + changed |= self._add_phi(var, front) + + return changed def _rename_vars(self, basic_block: IRBasicBlock, visited: set): visited.add(basic_block) @@ -70,18 +77,24 @@ def _rename_vars(self, basic_block: IRBasicBlock, visited: set): continue self.var_names[inst.output.name] -= 1 - def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock): - # TODO: check if the phi already exists + def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock) -> bool: + for inst in basic_block.instructions: + if inst.opcode == "phi" and inst.output.name == var.name: + return False + args = [] for bb in basic_block.cfg_in: if bb == basic_block: continue + args.append(bb.label) args.append(var) phi = IRInstruction("phi", args, var) basic_block.instructions.insert(0, phi) + return True + def _compute_defs(self): self.defs = {} for bb in self.dom.dfs: From 070b4760a1e73b0bf765bc922c4c224f139c3fb2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 23 Jan 2024 00:08:11 +0200 Subject: [PATCH 058/322] Add versioning support to IRVariable constructor --- vyper/venom/basicblock.py | 10 +++++++++- vyper/venom/passes/make_ssa.py | 6 +++--- vyper/venom/venom_to_assembly.py | 2 ++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index c30289cf77..12b80cfbd6 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -132,9 +132,17 @@ class IRVariable(IRValue): mem_addr: Optional[int] = None def __init__( - self, value: str, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: int = None + self, + value: str, + mem_type: MemType = MemType.OPERAND_STACK, + mem_addr: int = None, + version: str | int = None, ) -> None: assert isinstance(value, str) + assert ":" not in value, "Variable name cannot contain ':'" + if version: + assert isinstance(value, str) or isinstance(value, int), "value must be an str or int" + value = f"{value}:{version}" self.value = value self.offset = 0 self.mem_type = mem_type diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 8127d7a97b..e17842d2f1 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -50,12 +50,12 @@ def _rename_vars(self, basic_block: IRBasicBlock, visited: set): new_ops.append(op) continue - new_ops.append(IRVariable(f"{op.name}:{self.var_names[op.name]}")) + new_ops.append(IRVariable(op.name, version=self.var_names[op.name])) inst.operands = new_ops if inst.output is not None: self.var_names[inst.output.name] += 1 - inst.output = IRVariable(f"{inst.output.name}:{self.var_names[inst.output.name]}") + inst.output = IRVariable(inst.output.name, version=self.var_names[inst.output.name]) for bb in basic_block.cfg_out: for inst in bb.instructions: @@ -64,7 +64,7 @@ def _rename_vars(self, basic_block: IRBasicBlock, visited: set): for i, op in enumerate(inst.operands): if op == basic_block.label: inst.operands[i + 1] = IRVariable( - f"{inst.output.name}:{self.var_names[inst.output.name]}" + inst.output.name, version=self.var_names[inst.output.name] ) for bb in self.dom.dominated[basic_block]: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index e7a59a6a0a..e7db021bb6 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -13,6 +13,7 @@ MemType, ) from vyper.venom.function import IRFunction +from vyper.venom.passes.make_ssa import MakeSSA from vyper.venom.passes.normalization import NormalizationPass from vyper.venom.stack_model import StackModel @@ -110,6 +111,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: for ctx in self.ctxs: calculate_cfg(ctx) NormalizationPass.run_pass(ctx) + # MakeSSA.run_pass(ctx) calculate_liveness(ctx) assert ctx.normalized, "Non-normalized CFG!" From 911ebb2d2b5aaa376c71bf2b0f8064448ac4b7a2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 23 Jan 2024 11:58:07 +0200 Subject: [PATCH 059/322] IRVariables api updates --- tests/unit/compiler/venom/test_variables.py | 8 ++++++++ vyper/venom/basicblock.py | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 tests/unit/compiler/venom/test_variables.py diff --git a/tests/unit/compiler/venom/test_variables.py b/tests/unit/compiler/venom/test_variables.py new file mode 100644 index 0000000000..cded8d0e1a --- /dev/null +++ b/tests/unit/compiler/venom/test_variables.py @@ -0,0 +1,8 @@ +from vyper.venom.basicblock import IRVariable + + +def test_variable_equality(): + v1 = IRVariable("%x") + v2 = IRVariable("%x") + assert v1 == v2 + assert v1 != IRVariable("%y") diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 12b80cfbd6..bead804c13 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -143,6 +143,8 @@ def __init__( if version: assert isinstance(value, str) or isinstance(value, int), "value must be an str or int" value = f"{value}:{version}" + if value[0] != "%": + value = f"%{value}" self.value = value self.offset = 0 self.mem_type = mem_type @@ -152,6 +154,12 @@ def __init__( def name(self) -> str: return self.value.split(":")[0] + def __hash__(self) -> int: + return self.value.__hash__() + + def __eq__(self, v: object) -> bool: + return self.value == v + def __repr__(self) -> str: return self.value @@ -351,7 +359,9 @@ def remove_cfg_out(self, bb: "IRBasicBlock") -> None: assert bb in self.cfg_out self.cfg_out.remove(bb) - def append_instruction(self, opcode: str, *args: Union[IROperand, int]) -> Optional[IRVariable]: + def append_instruction( + self, opcode: str, *args: Union[IROperand, int], ret: IRVariable = None + ) -> Optional[IRVariable]: """ Append an instruction to the basic block @@ -359,7 +369,8 @@ def append_instruction(self, opcode: str, *args: Union[IROperand, int]) -> Optio """ assert not self.is_terminated, self - ret = self.parent.get_next_variable() if opcode not in NO_OUTPUT_INSTRUCTIONS else None + if ret is None: + ret = self.parent.get_next_variable() if opcode not in NO_OUTPUT_INSTRUCTIONS else None # Wrap raw integers in IRLiterals inst_args = [_ir_operand_from_value(arg) for arg in args] From ff2e2844a2e45ef37dfc0bb9cc97822af834c904 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 23 Jan 2024 11:58:34 +0200 Subject: [PATCH 060/322] Refactor MakeSSA pass to accept entry point --- vyper/venom/passes/make_ssa.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index e17842d2f1..98e629dd4d 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -10,11 +10,10 @@ class MakeSSA(IRPass): dom: DominatorTree defs: dict[IRVariable, set[IRBasicBlock]] - def _run_pass(self, ctx: IRFunction) -> int: + def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self.ctx = ctx calculate_cfg(ctx) - entry = ctx.get_basic_block(ctx.entry_points[0].value) dom = DominatorTree(ctx, entry) self.dom = dom @@ -23,10 +22,13 @@ def _run_pass(self, ctx: IRFunction) -> int: if count := count + 1 > len(ctx.basic_blocks) * 2: raise CompilerPanic("Failed to add phi nodes") - self.var_names = {var.name: 0 for var in self.defs.keys()} + self.var_names = {var.name: -1 for var in self.defs.keys()} self._rename_vars(entry, set()) # print(ctx.as_graph()) + # import sys + + # sys.exit(0) self.changes = 0 From c701de729d6d73319921f8e8186cdc09501a671a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 23 Jan 2024 11:59:22 +0200 Subject: [PATCH 061/322] Test update --- tests/unit/compiler/venom/test_dominator_tree.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index 921959fed4..a9575e5734 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -67,9 +67,7 @@ def test_phi_placement(): bb2.insert_instruction(IRInstruction("add", [x, IRLiteral(1)], x), 0) bb7.insert_instruction(IRInstruction("mstore", [x, IRLiteral(0)]), 0) - MakeSSA.run_pass(ctx) - - # print(ctx) + MakeSSA.run_pass(ctx, bb1) if __name__ == "__main__": From cf8c1f26e004a62e90fb44bbad81dbecde045271 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 11:38:48 +0200 Subject: [PATCH 062/322] Fix variable renaming in MakeSSA pass --- vyper/venom/passes/make_ssa.py | 43 ++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 98e629dd4d..c30d22de17 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -22,8 +22,9 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: if count := count + 1 > len(ctx.basic_blocks) * 2: raise CompilerPanic("Failed to add phi nodes") - self.var_names = {var.name: -1 for var in self.defs.keys()} - self._rename_vars(entry, set()) + self.var_names = {var.name: 0 for var in self.defs.keys()} + self.stacks = {var.name: [0] for var in self.defs.keys()} + self._rename_vars(entry) # print(ctx.as_graph()) # import sys @@ -42,22 +43,26 @@ def _add_phi_nodes(self) -> bool: return changed - def _rename_vars(self, basic_block: IRBasicBlock, visited: set): - visited.add(basic_block) - + def _rename_vars(self, basic_block: IRBasicBlock): + old_assignments = [] for inst in basic_block.instructions: new_ops = [] - for op in inst.operands: - if not isinstance(op, IRVariable): - new_ops.append(op) - continue + if inst.opcode != "phi": + for op in inst.operands: + if not isinstance(op, IRVariable): + new_ops.append(op) + continue + + new_ops.append(IRVariable(op.name, version=self.stacks[op.name][-1])) - new_ops.append(IRVariable(op.name, version=self.var_names[op.name])) + inst.operands = new_ops - inst.operands = new_ops if inst.output is not None: - self.var_names[inst.output.name] += 1 - inst.output = IRVariable(inst.output.name, version=self.var_names[inst.output.name]) + i = self.var_names[inst.output.name] + inst.output = IRVariable(inst.output.name, version=i) + old_assignments.append(inst.output) + self.stacks[inst.output.name].append(i) + self.var_names[inst.output.name] = i + 1 for bb in basic_block.cfg_out: for inst in bb.instructions: @@ -66,18 +71,16 @@ def _rename_vars(self, basic_block: IRBasicBlock, visited: set): for i, op in enumerate(inst.operands): if op == basic_block.label: inst.operands[i + 1] = IRVariable( - inst.output.name, version=self.var_names[inst.output.name] + inst.output.name, version=self.stacks[inst.output.name][-1] ) for bb in self.dom.dominated[basic_block]: - if bb in visited: + if bb == basic_block: continue - self._rename_vars(bb, visited) + self._rename_vars(bb) - for inst in basic_block.instructions: - if inst.output is None: - continue - self.var_names[inst.output.name] -= 1 + for op in old_assignments: + self.stacks[op.name].pop() def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock) -> bool: for inst in basic_block.instructions: From da959693c68f4ed43855aa8521dd2bcf0de34dc8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 11:39:22 +0200 Subject: [PATCH 063/322] Fix variable version check in IRVariable class and remove commented --- vyper/venom/basicblock.py | 8 +++++++- vyper/venom/venom_to_assembly.py | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index bead804c13..9376ff0286 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -140,7 +140,7 @@ def __init__( ) -> None: assert isinstance(value, str) assert ":" not in value, "Variable name cannot contain ':'" - if version: + if version is not None: assert isinstance(value, str) or isinstance(value, int), "value must be an str or int" value = f"{value}:{version}" if value[0] != "%": @@ -154,6 +154,12 @@ def __init__( def name(self) -> str: return self.value.split(":")[0] + @property + def version(self) -> int: + if ":" not in self.value: + return -1 + return int(self.value.split(":")[1]) + def __hash__(self) -> int: return self.value.__hash__() diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index e7db021bb6..8252536633 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -111,7 +111,6 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: for ctx in self.ctxs: calculate_cfg(ctx) NormalizationPass.run_pass(ctx) - # MakeSSA.run_pass(ctx) calculate_liveness(ctx) assert ctx.normalized, "Non-normalized CFG!" From 6af5e70f9ba1d467292a9bf8eae66635e0b7d2c1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 11:40:29 +0200 Subject: [PATCH 064/322] Dont show zero --- vyper/venom/basicblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 9376ff0286..f7bf6c7d4e 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -140,7 +140,7 @@ def __init__( ) -> None: assert isinstance(value, str) assert ":" not in value, "Variable name cannot contain ':'" - if version is not None: + if version: assert isinstance(value, str) or isinstance(value, int), "value must be an str or int" value = f"{value}:{version}" if value[0] != "%": From 0445941fbdccf2aef01bd7c0b9242066c6ede4f6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 12:05:48 +0200 Subject: [PATCH 065/322] Refactor variable renaming --- vyper/venom/passes/make_ssa.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index c30d22de17..565b7b8303 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -44,7 +44,7 @@ def _add_phi_nodes(self) -> bool: return changed def _rename_vars(self, basic_block: IRBasicBlock): - old_assignments = [] + outs = [] for inst in basic_block.instructions: new_ops = [] if inst.opcode != "phi": @@ -58,11 +58,12 @@ def _rename_vars(self, basic_block: IRBasicBlock): inst.operands = new_ops if inst.output is not None: - i = self.var_names[inst.output.name] - inst.output = IRVariable(inst.output.name, version=i) - old_assignments.append(inst.output) - self.stacks[inst.output.name].append(i) - self.var_names[inst.output.name] = i + 1 + v_name = inst.output.name + i = self.var_names[v_name] + inst.output = IRVariable(v_name, version=i) + outs.append(inst.output) + self.stacks[v_name].append(i) + self.var_names[v_name] = i + 1 for bb in basic_block.cfg_out: for inst in bb.instructions: @@ -79,7 +80,7 @@ def _rename_vars(self, basic_block: IRBasicBlock): continue self._rename_vars(bb) - for op in old_assignments: + for op in outs: self.stacks[op.name].pop() def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock) -> bool: From ad0eb26d70ca574d8faf19603fef3b48f085328f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 12:06:17 +0200 Subject: [PATCH 066/322] Refactor conditional block in translator --- vyper/venom/ir_node_to_venom.py | 41 +++++++++++---------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index b23723a177..a00bb020cf 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -369,40 +369,43 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # convert the condition cont_ret = _convert_ir_bb(ctx, cond, symbols, variables, allocated_variables) - current_bb = ctx.get_basic_block() + cond_block = ctx.get_basic_block() + + cond_symbols = symbols.copy() + cond_variables = variables.copy() + cond_allocated_variables = allocated_variables.copy() else_block = IRBasicBlock(ctx.get_next_label("else"), ctx) ctx.append_basic_block(else_block) # convert "else" else_ret_val = None - else_syms = symbols.copy() if len(ir.args) == 3: else_ret_val = _convert_ir_bb( - ctx, ir.args[2], else_syms, variables, allocated_variables.copy() + ctx, ir.args[2], cond_symbols, cond_variables, cond_allocated_variables ) if isinstance(else_ret_val, IRLiteral): assert isinstance(else_ret_val.value, int) # help mypy else_ret_val = ctx.get_basic_block().append_instruction("store", else_ret_val) - after_else_syms = else_syms.copy() + else_block = ctx.get_basic_block() # convert "then" then_block = IRBasicBlock(ctx.get_next_label("then"), ctx) ctx.append_basic_block(then_block) - then_ret_val = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) + then_ret_val = _convert_ir_bb( + ctx, ir.args[1], cond_symbols, cond_variables, cond_allocated_variables + ) if isinstance(then_ret_val, IRLiteral): then_ret_val = ctx.get_basic_block().append_instruction("store", then_ret_val) - current_bb.append_instruction("jnz", cont_ret, then_block.label, else_block.label) + cond_block.append_instruction("jnz", cont_ret, then_block.label, else_block.label) - after_then_syms = symbols.copy() then_block = ctx.get_basic_block() # exit bb - exit_label = ctx.get_next_label("if_exit") - exit_bb = IRBasicBlock(exit_label, ctx) + exit_bb = IRBasicBlock(ctx.get_next_label("if_exit"), ctx) exit_bb = ctx.append_basic_block(exit_bb) if_ret = None @@ -411,28 +414,12 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): "phi", then_block.label, then_ret_val, else_block.label, else_ret_val ) - common_symbols = _get_symbols_common(after_then_syms, after_else_syms) - for sym, val in common_symbols.items(): - ret = exit_bb.append_instruction( - "phi", then_block.label, val[0], else_block.label, val[1] - ) - old_var = symbols.get(sym, None) - symbols[sym] = ret - if old_var is not None: - for idx, var_rec in allocated_variables.items(): # type: ignore - if var_rec.value == old_var.value: - allocated_variables[idx] = ret # type: ignore - if not else_block.is_terminated: else_block.append_instruction("jmp", exit_bb.label) if not then_block.is_terminated: then_block.append_instruction("jmp", exit_bb.label) - sink_block = IRBasicBlock(ctx.get_next_label("sink"), ctx) - ctx.append_basic_block(sink_block) - exit_bb.append_instruction("jmp", sink_block.label) - return if_ret elif ir.value == "with": @@ -707,9 +694,9 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("mstore", arg_1, ptr_var) else: if isinstance(sym_ir, IRLiteral): - new_var = bb.append_instruction("store", arg_1) + new_var = IRVariable(var.name) + bb.append_instruction("store", arg_1, ret=new_var) symbols[f"&{sym_ir.value}"] = new_var - # if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = new_var return new_var else: From b7fd201988100317b168dbd31238568daca9e09f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 12:27:00 +0200 Subject: [PATCH 067/322] Generalize phi to N inputs --- vyper/venom/analysis.py | 22 ++++++++++------------ vyper/venom/basicblock.py | 9 +++++++++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index daebd2560c..66d83e73aa 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -104,20 +104,18 @@ def input_vars_from(source: IRBasicBlock, target: IRBasicBlock) -> OrderedSet[IR # will arbitrarily choose either %12 or %14 to be in the liveness # set, and then during instruction selection, after this instruction, # %12 will be replaced by %56 in the liveness set - source1, source2 = inst.operands[0], inst.operands[2] - phi1, phi2 = inst.operands[1], inst.operands[3] - if source.label == source1: - liveness.add(phi1) - if phi2 in liveness: - liveness.remove(phi2) - elif source.label == source2: - liveness.add(phi2) - if phi1 in liveness: - liveness.remove(phi1) - else: - # bad path into this phi node + + # bad path into this phi node + if source.label not in inst.operands: raise CompilerPanic(f"unreachable: {inst}") + for label, var in inst.phi_operands: + if label == source.label: + liveness.add(var) + else: + if var in liveness: + liveness.remove(var) + return liveness diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index f7bf6c7d4e..e371dcffbd 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -273,6 +273,15 @@ def replace_label_operands(self, replacements: dict) -> None: if isinstance(operand, IRLabel) and operand.value in replacements: self.operands[i] = replacements[operand.value] + @property + def phi_operands(self) -> (IRLabel, IRVariable): + """ + Get phi operands for instruction. + """ + assert self.opcode == "phi", "instruction must be a phi" + for i in range(0, len(self.operands), 2): + yield self.operands[i], self.operands[i + 1] + def __repr__(self) -> str: s = "" if self.output: From efea6ba5b7d8ec257a5bb149be36796f79e12ce3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 12:53:31 +0200 Subject: [PATCH 068/322] Remove commented out code --- vyper/venom/passes/make_ssa.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 565b7b8303..34ea607243 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -26,11 +26,6 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self.stacks = {var.name: [0] for var in self.defs.keys()} self._rename_vars(entry) - # print(ctx.as_graph()) - # import sys - - # sys.exit(0) - self.changes = 0 def _add_phi_nodes(self) -> bool: From c99b88bae98dc08da2227a05f740b58e79ca5e83 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 12:54:39 +0200 Subject: [PATCH 069/322] Refactor repeat with auto phi --- vyper/venom/ir_node_to_venom.py | 40 ++------------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a00bb020cf..9c04df7b78 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -774,55 +774,19 @@ def emit_body_blocks(): bb.append_instruction("jmp", entry_block.label) ctx.append_basic_block(entry_block) - counter_inc_var = ctx.get_next_variable() - counter_var = ctx.get_basic_block().append_instruction("store", start) symbols[sym.value] = counter_var ctx.get_basic_block().append_instruction("jmp", cond_block.label) - ret = cond_block.append_instruction( - "phi", entry_block.label, counter_var, increment_block.label, counter_inc_var - ) - symbols[sym.value] = ret - - xor_ret = cond_block.append_instruction("xor", ret, end) + xor_ret = cond_block.append_instruction("xor", counter_var, end) cont_ret = cond_block.append_instruction("iszero", xor_ret) ctx.append_basic_block(cond_block) - start_syms = symbols.copy() start_allocated = allocated_variables.copy() ctx.append_basic_block(body_block) emit_body_blocks() - end_syms = symbols.copy() - diff_syms = _get_symbols_common(start_syms, end_syms) - - replacements = {} - for sym, val in diff_syms.items(): - new_var = ctx.get_next_variable() - symbols[sym] = new_var - replacements[val[0]] = new_var - replacements[val[1]] = new_var - cond_block.insert_instruction( - IRInstruction( - "phi", [entry_block.label, val[0], increment_block.label, val[1]], new_var - ), - 1, - ) - - i = 0 - for bb in ctx.basic_blocks: - if bb.label == body_block.label: - break - i += 1 - - for bb in ctx.basic_blocks[i:]: - bb.replace_operands(replacements) body_end = ctx.get_basic_block() - # for name, var in allocated_variables.items(): - # if start_allocated.get(name) is not None: - # continue - # body_end.append_instruction("dealloca", var) allocated_variables = start_allocated @@ -836,7 +800,7 @@ def emit_body_blocks(): ctx.append_basic_block(continue_block) increment_block.insert_instruction( - IRInstruction("add", [ret, IRLiteral(1)], counter_inc_var), 0 + IRInstruction("add", [counter_var, IRLiteral(1)], counter_var) ) increment_block.append_instruction("jmp", cond_block.label) From 6fc5a07382a838ffe62ec813588fbb8ee7b54a55 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 18:59:53 +0200 Subject: [PATCH 070/322] more dev --- vyper/venom/basicblock.py | 2 +- vyper/venom/passes/make_ssa.py | 94 ++++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 27 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index e371dcffbd..1a6fe67291 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -157,7 +157,7 @@ def name(self) -> str: @property def version(self) -> int: if ":" not in self.value: - return -1 + return 0 return int(self.value.split(":")[1]) def __hash__(self) -> int: diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 34ea607243..2c3ae9afea 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -1,4 +1,5 @@ from vyper.exceptions import CompilerPanic +from vyper.utils import OrderedSet from vyper.venom.analysis import calculate_cfg from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable from vyper.venom.dominators import DominatorTree @@ -8,7 +9,7 @@ class MakeSSA(IRPass): dom: DominatorTree - defs: dict[IRVariable, set[IRBasicBlock]] + defs: dict[IRVariable, OrderedSet[IRBasicBlock]] def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self.ctx = ctx @@ -17,26 +18,66 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: dom = DominatorTree(ctx, entry) self.dom = dom - count = 0 - while self._add_phi_nodes(): - if count := count + 1 > len(ctx.basic_blocks) * 2: - raise CompilerPanic("Failed to add phi nodes") + self._add_phi_nodes() self.var_names = {var.name: 0 for var in self.defs.keys()} self.stacks = {var.name: [0] for var in self.defs.keys()} - self._rename_vars(entry) + # self._rename_vars(entry) + # self._remove_degenerate_phis(entry) self.changes = 0 def _add_phi_nodes(self) -> bool: self._compute_defs() - changed = False - for var, defs in self.defs.items(): - for bb in defs: - for front in self.dom.df[bb]: - changed |= self._add_phi(var, front) + self.work = {var: 0 for var in self.dom.dfs} + self.has_already = {var: 0 for var in self.dom.dfs} + i = 0 + + # Iterate over all variables + for var, d in self.defs.items(): + i += 1 + defs = list(d) + while len(defs) > 0: + bb = defs.pop() + for dom in self.dom.df[bb]: + if self.has_already[dom] >= i: + continue + + self._place_phi(var, dom) + self.has_already[dom] = i + if self.work[dom] < i: + self.work[dom] = i + defs.append(dom) + + def _place_phi(self, var: IRVariable, basic_block: IRBasicBlock): + args = [] + for bb in basic_block.cfg_in: + if bb == basic_block: + continue + + args.append(bb.label) + args.append(var) - return changed + phi = IRInstruction("phi", args, var) + basic_block.instructions.insert(0, phi) + + def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock) -> bool: + for inst in basic_block.instructions: + if inst.opcode == "phi" and inst.output.name == var.name: + return False + + args = [] + for bb in basic_block.cfg_in: + if bb == basic_block: + continue + + args.append(bb.label) + args.append(var) + + phi = IRInstruction("phi", args, var) + basic_block.instructions.insert(0, phi) + + return True def _rename_vars(self, basic_block: IRBasicBlock): outs = [] @@ -78,23 +119,24 @@ def _rename_vars(self, basic_block: IRBasicBlock): for op in outs: self.stacks[op.name].pop() - def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock) -> bool: - for inst in basic_block.instructions: - if inst.opcode == "phi" and inst.output.name == var.name: - return False - - args = [] - for bb in basic_block.cfg_in: - if bb == basic_block: + def _remove_degenerate_phis(self, entry: IRBasicBlock): + for inst in entry.instructions: + if inst.opcode != "phi": continue - args.append(bb.label) - args.append(var) + remove = False + for _, var in inst.phi_operands: + if var == inst.output: + remove = True + break - phi = IRInstruction("phi", args, var) - basic_block.instructions.insert(0, phi) + if remove: + entry.instructions.remove(inst) - return True + for bb in self.dom.dominated[entry]: + if bb == entry: + continue + self._remove_degenerate_phis(bb) def _compute_defs(self): self.defs = {} @@ -102,5 +144,5 @@ def _compute_defs(self): assignments = bb.get_assignments() for var in assignments: if var not in self.defs: - self.defs[var] = set() + self.defs[var] = OrderedSet() self.defs[var].add(bb) From 35fd6a811ab3bdc8dd257232f4e577ecb92d8ae6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 20:13:44 +0200 Subject: [PATCH 071/322] Update succesor phi instruction label in NormalizationPass --- vyper/venom/passes/normalization.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 963dc08519..9ca8127b2d 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -36,6 +36,13 @@ def _insert_split_basicblock(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> IRB split_bb.append_instruction("jmp", bb.label) self.ctx.append_basic_block(split_bb) + for inst in bb.instructions: + if inst.opcode != "phi": + continue + for i in range(0, len(inst.operands), 2): + if inst.operands[i] == in_bb.label: + inst.operands[i] = split_bb.label + # Update the labels in the data segment for inst in self.ctx.data_segment: if inst.opcode == "db" and inst.operands[0] == bb.label: From 6a3593892308d8bc5d7239a0fe95a28fcc86a346 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 20:13:58 +0200 Subject: [PATCH 072/322] remove phis --- vyper/venom/passes/make_ssa.py | 37 ++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 2c3ae9afea..5f48d6c92c 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -22,8 +22,8 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self.var_names = {var.name: 0 for var in self.defs.keys()} self.stacks = {var.name: [0] for var in self.defs.keys()} - # self._rename_vars(entry) - # self._remove_degenerate_phis(entry) + self._rename_vars(entry) + self._remove_degenerate_phis(entry) self.changes = 0 @@ -97,7 +97,7 @@ def _rename_vars(self, basic_block: IRBasicBlock): v_name = inst.output.name i = self.var_names[v_name] inst.output = IRVariable(v_name, version=i) - outs.append(inst.output) + outs.append(inst.output.name) self.stacks[v_name].append(i) self.var_names[v_name] = i + 1 @@ -116,10 +116,10 @@ def _rename_vars(self, basic_block: IRBasicBlock): continue self._rename_vars(bb) - for op in outs: - self.stacks[op.name].pop() + for op_name in outs: + self.stacks[op_name].pop() - def _remove_degenerate_phis(self, entry: IRBasicBlock): + def __remove_degenerate_phis(self, entry: IRBasicBlock): for inst in entry.instructions: if inst.opcode != "phi": continue @@ -138,6 +138,31 @@ def _remove_degenerate_phis(self, entry: IRBasicBlock): continue self._remove_degenerate_phis(bb) + def _remove_degenerate_phis(self, entry: IRBasicBlock): + for inst in entry.instructions: + if inst.opcode != "phi": + continue + + new_ops = [] + for label, op in inst.phi_operands: + if op == inst.output: + continue + new_ops.extend([label, op]) + l = len(new_ops) + if l == 0: + entry.instructions.remove(inst) + elif l == 2: + inst.opcode = "store" + inst.output = new_ops[1] + inst.operands = [new_ops[0]] + else: + inst.operands = new_ops + + for bb in self.dom.dominated[entry]: + if bb == entry: + continue + self._remove_degenerate_phis(bb) + def _compute_defs(self): self.defs = {} for bb in self.dom.dfs: From 73faa806d9d34bdcd3b5a217482c5ed1943a3fa4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 20:32:35 +0200 Subject: [PATCH 073/322] remove final usage of manual phi --- vyper/venom/ir_node_to_venom.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 9c04df7b78..1ea2895d5a 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -408,11 +408,10 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): exit_bb = IRBasicBlock(ctx.get_next_label("if_exit"), ctx) exit_bb = ctx.append_basic_block(exit_bb) - if_ret = None + if_ret = ctx.get_next_variable("if_ret") if then_ret_val is not None and else_ret_val is not None: - if_ret = exit_bb.append_instruction( - "phi", then_block.label, then_ret_val, else_block.label, else_ret_val - ) + then_block.append_instruction("store", then_ret_val, ret=if_ret) + else_block.append_instruction("store", else_ret_val, ret=if_ret) if not else_block.is_terminated: else_block.append_instruction("jmp", exit_bb.label) From cc45efcafefa6933c1842aa4fe1f44963e9c2ca5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 20:33:21 +0200 Subject: [PATCH 074/322] Add MakeSSA passes to Venom IR --- vyper/venom/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index e36dd34fcd..e564bc6ad6 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -16,6 +16,8 @@ from vyper.venom.ir_node_to_venom import ir_node_to_venom from vyper.venom.passes.constant_propagation import ir_pass_constant_propagation from vyper.venom.passes.dft import DFTPass +from vyper.venom.passes.make_ssa import MakeSSA +from vyper.venom.passes.normalization import NormalizationPass from vyper.venom.venom_to_assembly import VenomCompiler DEFAULT_OPT_LEVEL = OptimizationLevel.default() @@ -39,6 +41,13 @@ def generate_assembly_experimental( def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: # Run passes on Venom IR # TODO: Add support for optimization levels + + ir_pass_optimize_empty_blocks(ctx) + ir_pass_remove_unreachable_blocks(ctx) + MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) + calculate_cfg(ctx) + calculate_liveness(ctx) + while True: changes = 0 From 1b6f136f77fc8d864d289ee31df2f03ee54bcbef Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 20:36:48 +0200 Subject: [PATCH 075/322] simplify repeat --- vyper/venom/ir_node_to_venom.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 1ea2895d5a..ffa75aa3d9 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -750,7 +750,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): def emit_body_blocks(): global _break_target, _continue_target old_targets = _break_target, _continue_target - _break_target, _continue_target = exit_block, continue_block + _break_target, _continue_target = exit_block, increment_block _convert_ir_bb(ctx, body, symbols, variables, allocated_variables) _break_target, _continue_target = old_targets @@ -765,7 +765,6 @@ def emit_body_blocks(): cond_block = IRBasicBlock(ctx.get_next_label("condition"), ctx) body_block = IRBasicBlock(ctx.get_next_label("body"), ctx) jump_up_block = IRBasicBlock(ctx.get_next_label("jump_up"), ctx) - continue_block = IRBasicBlock(ctx.get_next_label("continue"), ctx) increment_block = IRBasicBlock(ctx.get_next_label("increment"), ctx) exit_block = IRBasicBlock(ctx.get_next_label("exit"), ctx) @@ -792,12 +791,9 @@ def emit_body_blocks(): if not body_end.is_terminated: body_end.append_instruction("jmp", jump_up_block.label) - jump_up_block.append_instruction("jmp", continue_block.label) + jump_up_block.append_instruction("jmp", increment_block.label) ctx.append_basic_block(jump_up_block) - continue_block.append_instruction("jmp", increment_block.label) - ctx.append_basic_block(continue_block) - increment_block.insert_instruction( IRInstruction("add", [counter_var, IRLiteral(1)], counter_var) ) From f31cfecbc74d3cdee4827991483e85dfa93ef6e8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 20:39:11 +0200 Subject: [PATCH 076/322] lint --- tests/unit/compiler/venom/test_dominator_tree.py | 1 + vyper/utils.py | 2 +- vyper/venom/basicblock.py | 1 - vyper/venom/passes/make_ssa.py | 7 +++---- vyper/venom/venom_to_assembly.py | 1 - 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index a9575e5734..fc78a4ccee 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -1,4 +1,5 @@ from typing import Optional + from vyper.exceptions import CompilerPanic from vyper.venom.analysis import calculate_cfg from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRLiteral, IRVariable diff --git a/vyper/utils.py b/vyper/utils.py index 495698c0cd..bb50ba01d3 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -1,5 +1,4 @@ import binascii -from collections.abc import Iterator import contextlib import decimal import functools @@ -7,6 +6,7 @@ import time import traceback import warnings +from collections.abc import Iterator from typing import Generic, List, TypeVar, Union from vyper.exceptions import DecimalOverrideException, InvalidLiteral diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 1a6fe67291..c4e57a575b 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -1,5 +1,4 @@ from enum import Enum, auto -import re from typing import TYPE_CHECKING, Any, Iterator, Optional, Union from vyper.utils import OrderedSet diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 5f48d6c92c..d8de8660a0 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -1,4 +1,3 @@ -from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet from vyper.venom.analysis import calculate_cfg from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable @@ -148,10 +147,10 @@ def _remove_degenerate_phis(self, entry: IRBasicBlock): if op == inst.output: continue new_ops.extend([label, op]) - l = len(new_ops) - if l == 0: + new_ops_len = len(new_ops) + if new_ops_len == 0: entry.instructions.remove(inst) - elif l == 2: + elif new_ops_len == 2: inst.opcode = "store" inst.output = new_ops[1] inst.operands = [new_ops[0]] diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 8252536633..e7a59a6a0a 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -13,7 +13,6 @@ MemType, ) from vyper.venom.function import IRFunction -from vyper.venom.passes.make_ssa import MakeSSA from vyper.venom.passes.normalization import NormalizationPass from vyper.venom.stack_model import StackModel From 05f616b7ce76b11fde04ee4fbb776afcd86324eb Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 20:49:44 +0200 Subject: [PATCH 077/322] lint --- vyper/venom/basicblock.py | 12 ++++++++---- vyper/venom/dominators.py | 9 +++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index c4e57a575b..f28b4de2f1 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -1,5 +1,5 @@ from enum import Enum, auto -from typing import TYPE_CHECKING, Any, Iterator, Optional, Union +from typing import TYPE_CHECKING, Any, Generator, Iterator, Optional, Union from vyper.utils import OrderedSet @@ -135,7 +135,7 @@ def __init__( value: str, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: int = None, - version: str | int = None, + version: Optional[str | int] = None, ) -> None: assert isinstance(value, str) assert ":" not in value, "Variable name cannot contain ':'" @@ -273,13 +273,17 @@ def replace_label_operands(self, replacements: dict) -> None: self.operands[i] = replacements[operand.value] @property - def phi_operands(self) -> (IRLabel, IRVariable): + def phi_operands(self) -> Generator[tuple[IRLabel, IRVariable], None, None]: """ Get phi operands for instruction. """ assert self.opcode == "phi", "instruction must be a phi" for i in range(0, len(self.operands), 2): - yield self.operands[i], self.operands[i + 1] + label = self.operands[i] + var = self.operands[i + 1] + assert isinstance(label, IRLabel), "phi operand must be a label" + assert isinstance(var, IRVariable), "phi operand must be a variable" + yield label, var def __repr__(self) -> str: s = "" diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index 9da5b08cce..433f11dad9 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -8,6 +8,15 @@ class DominatorTree: Dominator tree. """ + ctx: IRFunction + entry: IRBasicBlock + dfs_order: dict[IRBasicBlock, int] + dfs: list[IRBasicBlock] + dominators: dict[IRBasicBlock, set[IRBasicBlock]] + idoms: dict[IRBasicBlock, IRBasicBlock] + dominated: dict[IRBasicBlock, set[IRBasicBlock]] + df: dict[IRBasicBlock, set[IRBasicBlock]] + def __init__(self, ctx: IRFunction, entry: IRBasicBlock): self.ctx = ctx self.entry = entry From 38b87ad798fd1b499db8bf6df5dff54c10debc62 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 21:04:44 +0200 Subject: [PATCH 078/322] comment --- vyper/venom/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index e564bc6ad6..ae0c6dc544 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -44,7 +44,7 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: ir_pass_optimize_empty_blocks(ctx) ir_pass_remove_unreachable_blocks(ctx) - MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) + MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) # TODO: do all entries calculate_cfg(ctx) calculate_liveness(ctx) From 4adbf5a42d37e91df339f1f9a9bb9cf20a9e3733 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 24 Jan 2024 23:52:54 +0200 Subject: [PATCH 079/322] Use variable name in allocation --- vyper/venom/ir_node_to_venom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index ffa75aa3d9..1f54cba100 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -464,7 +464,8 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ) for var in vars: if allocated_variables.get(var.name, None) is None: - new_v = ctx.get_basic_block().append_instruction("alloca", var.size, var.pos) + new_v = IRVariable(var.name) + ctx.get_basic_block().append_instruction("alloca", var.size, var.pos, ret=new_v) allocated_variables[var.name] = new_v symbols[f"&{var.pos}"] = new_v From 36220984294d42918516e52d973cfbcb028b65ee Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 25 Jan 2024 00:37:58 +0200 Subject: [PATCH 080/322] translator fix --- vyper/venom/__init__.py | 5 ++++- vyper/venom/ir_node_to_venom.py | 12 ++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index ae0c6dc544..cafa97a550 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -44,7 +44,10 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: ir_pass_optimize_empty_blocks(ctx) ir_pass_remove_unreachable_blocks(ctx) - MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) # TODO: do all entries + internals = [bb for bb in ctx.basic_blocks if bb.label.value.startswith("internal")] + MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) + for entry in internals: + MakeSSA.run_pass(ctx, entry) calculate_cfg(ctx) calculate_liveness(ctx) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 1f54cba100..7baae5b9b4 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -681,9 +681,9 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if var is not None and var.size is not None: if var.size and var.size > 32: if allocated_variables.get(var.name, None) is None: - allocated_variables[var.name] = bb.append_instruction( - "alloca", var.size, var.pos - ) + new_var = IRVariable(var.name) + allocated_variables[var.name] = new_var + bb.append_instruction("alloca", var.size, var.pos, ret=new_var) offset = int(sym_ir.value) - var.pos if offset > 0: @@ -752,7 +752,7 @@ def emit_body_blocks(): global _break_target, _continue_target old_targets = _break_target, _continue_target _break_target, _continue_target = exit_block, increment_block - _convert_ir_bb(ctx, body, symbols, variables, allocated_variables) + _convert_ir_bb(ctx, body, symbols.copy(), variables, allocated_variables.copy()) _break_target, _continue_target = old_targets sym = ir.args[0] @@ -781,14 +781,10 @@ def emit_body_blocks(): cont_ret = cond_block.append_instruction("iszero", xor_ret) ctx.append_basic_block(cond_block) - start_allocated = allocated_variables.copy() ctx.append_basic_block(body_block) emit_body_blocks() - body_end = ctx.get_basic_block() - allocated_variables = start_allocated - if not body_end.is_terminated: body_end.append_instruction("jmp", jump_up_block.label) From 1710e2d871e7ac5babbaa4f1dc5ef2ce1ce470ae Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 25 Jan 2024 00:40:00 +0200 Subject: [PATCH 081/322] Add chainid instruction --- vyper/venom/venom_to_assembly.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index e7a59a6a0a..d655d0c58f 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -27,6 +27,7 @@ "gas", "gasprice", "gaslimit", + "chainid", "address", "origin", "number", From 1781680fa41bc66997075853c0bb30fe5fd79d6c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 25 Jan 2024 00:42:41 +0200 Subject: [PATCH 082/322] change message to be more informative --- vyper/venom/venom_to_assembly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index d655d0c58f..cfddcea7e1 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -456,11 +456,11 @@ def dup_op(self, assembly, stack, op): def _evm_swap_for(depth: int) -> str: swap_idx = -depth - assert 1 <= swap_idx <= 16, "Unsupported swap depth" + assert 1 <= swap_idx <= 16, f"Unsupported swap depth {swap_idx}" return f"SWAP{swap_idx}" def _evm_dup_for(depth: int) -> str: dup_idx = 1 - depth - assert 1 <= dup_idx <= 16, "Unsupported dup depth" + assert 1 <= dup_idx <= 16, f"Unsupported dup depth {dup_idx}" return f"DUP{dup_idx}" From e01e4565904aa43c34d63ef09ca65a000c0c3cdf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 25 Jan 2024 12:40:03 +0200 Subject: [PATCH 083/322] fixes --- vyper/venom/passes/make_ssa.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index d8de8660a0..7d2d88f451 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -24,7 +24,7 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self._rename_vars(entry) self._remove_degenerate_phis(entry) - self.changes = 0 + return 1 def _add_phi_nodes(self) -> bool: self._compute_defs() @@ -54,8 +54,8 @@ def _place_phi(self, var: IRVariable, basic_block: IRBasicBlock): if bb == basic_block: continue - args.append(bb.label) - args.append(var) + args.append(bb.label) # type: ignore + args.append(var) # type: ignore phi = IRInstruction("phi", args, var) basic_block.instructions.insert(0, phi) From b2a1ca8867b659591d3dcb9ba7e1181c74140407 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 25 Jan 2024 12:48:05 +0200 Subject: [PATCH 084/322] mypy --- vyper/venom/basicblock.py | 4 ++++ vyper/venom/passes/make_ssa.py | 11 ++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index f28b4de2f1..df329143a9 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -87,6 +87,10 @@ class IROperand: value: Any + @property + def name(self) -> str: + return self.value + class IRValue(IROperand): """ diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 7d2d88f451..7af49377c8 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -1,6 +1,6 @@ from vyper.utils import OrderedSet from vyper.venom.analysis import calculate_cfg -from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IROperand, IRVariable from vyper.venom.dominators import DominatorTree from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass @@ -26,7 +26,7 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: return 1 - def _add_phi_nodes(self) -> bool: + def _add_phi_nodes(self): self._compute_defs() self.work = {var: 0 for var in self.dom.dfs} self.has_already = {var: 0 for var in self.dom.dfs} @@ -49,7 +49,7 @@ def _add_phi_nodes(self) -> bool: defs.append(dom) def _place_phi(self, var: IRVariable, basic_block: IRBasicBlock): - args = [] + args: list[IROperand] = [] for bb in basic_block.cfg_in: if bb == basic_block: continue @@ -62,10 +62,10 @@ def _place_phi(self, var: IRVariable, basic_block: IRBasicBlock): def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock) -> bool: for inst in basic_block.instructions: - if inst.opcode == "phi" and inst.output.name == var.name: + if inst.opcode == "phi" and inst.output is not None and inst.output.name == var.name: return False - args = [] + args: list[IROperand] = [] for bb in basic_block.cfg_in: if bb == basic_block: continue @@ -104,6 +104,7 @@ def _rename_vars(self, basic_block: IRBasicBlock): for inst in bb.instructions: if inst.opcode != "phi": continue + assert inst.output is not None, "Phi instruction without output" for i, op in enumerate(inst.operands): if op == basic_block.label: inst.operands[i + 1] = IRVariable( From 06b6db1467eca86a824988e49b320c73661c064c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 25 Jan 2024 18:47:10 +0200 Subject: [PATCH 085/322] remove old code --- vyper/venom/passes/make_ssa.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 7af49377c8..49d96dffc7 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -119,25 +119,6 @@ def _rename_vars(self, basic_block: IRBasicBlock): for op_name in outs: self.stacks[op_name].pop() - def __remove_degenerate_phis(self, entry: IRBasicBlock): - for inst in entry.instructions: - if inst.opcode != "phi": - continue - - remove = False - for _, var in inst.phi_operands: - if var == inst.output: - remove = True - break - - if remove: - entry.instructions.remove(inst) - - for bb in self.dom.dominated[entry]: - if bb == entry: - continue - self._remove_degenerate_phis(bb) - def _remove_degenerate_phis(self, entry: IRBasicBlock): for inst in entry.instructions: if inst.opcode != "phi": From eb94c49fe665ff2fe855022de17bb2a9c3e2e25b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 25 Jan 2024 18:48:39 +0200 Subject: [PATCH 086/322] oups --- vyper/venom/passes/make_ssa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 49d96dffc7..b573130604 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -24,7 +24,7 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self._rename_vars(entry) self._remove_degenerate_phis(entry) - return 1 + return 0 def _add_phi_nodes(self): self._compute_defs() From 8854cfd72e74f24d31d9331129405abec1312c4a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 26 Jan 2024 00:53:08 +0200 Subject: [PATCH 087/322] refactor left out --- vyper/venom/venom_to_assembly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index cfddcea7e1..88ddae0d98 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -260,7 +260,7 @@ def clean_stack_from_cfg_in( continue if depth != 0: - stack.swap(depth) + self.swap(asm, stack, depth) self.pop(asm, stack) def _generate_evm_for_instruction( From 0207dea40b0710d3610e4677cdbb168e344a0c1d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 26 Jan 2024 00:57:09 +0200 Subject: [PATCH 088/322] refactor --- vyper/venom/ir_node_to_venom.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 7baae5b9b4..ded5a8a967 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -167,12 +167,10 @@ def _handle_self_call( ret = bb.append_instruction(arg.location.load_op, ret) ret_args.append(ret) - if return_buf.is_literal: - ret_args.append(return_buf.value) # type: ignore - bb = ctx.get_basic_block() do_ret = func_t.return_type is not None if do_ret: + ret_args.append(return_buf.value) # type: ignore invoke_ret = bb.append_invoke_instruction(ret_args, returns=True) # type: ignore allocated_variables["return_buffer"] = invoke_ret # type: ignore return invoke_ret @@ -508,9 +506,9 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): func_t = ir.passthrough_metadata.get("func_t", None) assert func_t is not None, "exit_to without func_t" + bb = ctx.get_basic_block() if func_t.is_external: # Hardcoded contructor special case - bb = ctx.get_basic_block() if func_t.name == "__init__": label = IRLabel(ir.args[0].value, True) bb.append_instruction("jmp", label) @@ -576,8 +574,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) - bb = ctx.get_basic_block() - if func_t.is_internal: + elif func_t.is_internal: assert ir.args[1].value == "return_pc", "return_pc not found" if func_t.return_type is None: bb.append_instruction("ret", symbols["return_pc"]) From f76eeea6cac9f996abacab597409d40555b747c3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 6 Feb 2024 15:40:12 +0200 Subject: [PATCH 089/322] Add is_terminal property to IRBasicBlock and get_terminal_basicblocks method to IRFunction --- vyper/venom/basicblock.py | 7 +++++++ vyper/venom/function.py | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index df329143a9..196c4990cf 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -470,6 +470,13 @@ def is_terminated(self) -> bool: return False return self.instructions[-1].opcode in BB_TERMINATORS + @property + def is_terminal(self) -> bool: + """ + Check if the basic block is terminal. + """ + return len(self.cfg_out) == 0 + def copy(self): bb = IRBasicBlock(self.label, self.parent) bb.instructions = self.instructions.copy() diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 51cd79baaf..a2e541112c 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,3 +1,4 @@ +from collections.abc import Generator from typing import Optional from vyper.utils import OrderedSet @@ -89,6 +90,14 @@ def get_basic_block_after(self, label: IRLabel) -> IRBasicBlock: return self.basic_blocks[i + 1] raise AssertionError(f"Basic block after '{label}' not found") + def get_terminal_basicblocks(self) -> Generator[IRBasicBlock]: + """ + Get basic blocks that are terminal. + """ + for bb in self.basic_blocks: + if bb.is_terminal: + yield bb + def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: """ Get basic blocks that contain label. From 6dd48735fcc8bdb2dd78cd7daac501621c86fca7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 6 Feb 2024 15:40:49 +0200 Subject: [PATCH 090/322] Remove initial liveness --- vyper/venom/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index cafa97a550..8e8cbf5f0c 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -48,8 +48,6 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) for entry in internals: MakeSSA.run_pass(ctx, entry) - calculate_cfg(ctx) - calculate_liveness(ctx) while True: changes = 0 From ba997e32be6a52f3666426b4c647b155292e2e6f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 6 Feb 2024 16:01:54 +0200 Subject: [PATCH 091/322] separate dup requirements --- vyper/venom/analysis.py | 16 ++++++++++++---- vyper/venom/venom_to_assembly.py | 10 ++++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 66d83e73aa..98a4ea0720 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -52,10 +52,6 @@ def _calculate_liveness(bb: IRBasicBlock) -> bool: for instruction in reversed(bb.instructions): ops = instruction.get_inputs() - for op in ops: - if op in liveness: - instruction.dup_requirements.add(op) - liveness = liveness.union(OrderedSet.fromkeys(ops)) out = instruction.get_outputs()[0] if len(instruction.get_outputs()) > 0 else None if out in liveness: @@ -89,6 +85,18 @@ def calculate_liveness(ctx: IRFunction) -> None: break +def calculate_dup_requirements(ctx: IRFunction) -> None: + for bb in ctx.basic_blocks: + last_liveness = bb.out_vars + for inst in reversed(bb.instructions): + inst.dup_requirements = OrderedSet() + ops = inst.get_inputs() + for op in ops: + if op in last_liveness: + inst.dup_requirements.add(op) + last_liveness = inst.liveness + + # calculate the input variables into self from source def input_vars_from(source: IRBasicBlock, target: IRBasicBlock) -> OrderedSet[IRVariable]: liveness = target.instructions[0].liveness.copy() diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 88ddae0d98..573fc777a3 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -2,7 +2,12 @@ from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet -from vyper.venom.analysis import calculate_cfg, calculate_liveness, input_vars_from +from vyper.venom.analysis import ( + calculate_cfg, + calculate_dup_requirements, + calculate_liveness, + input_vars_from, +) from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, @@ -109,9 +114,10 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: # This is a side-effect of how dynamic jumps are temporarily being used # to support the O(1) dispatcher. -> look into calculate_cfg() for ctx in self.ctxs: - calculate_cfg(ctx) NormalizationPass.run_pass(ctx) + calculate_cfg(ctx) calculate_liveness(ctx) + calculate_dup_requirements(ctx) assert ctx.normalized, "Non-normalized CFG!" From 3d6caaa9b1a864f1a448c7279ba010f1d2713d61 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 6 Feb 2024 17:17:35 +0200 Subject: [PATCH 092/322] Add error handling for variable not in stack --- vyper/venom/venom_to_assembly.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 573fc777a3..84123eee32 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,4 +1,5 @@ from typing import Any +from vyper.exceptions import CompilerPanic from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet @@ -168,6 +169,9 @@ def _stack_reorder( final_stack_depth = -(stack_ops_count - i - 1) depth = stack.get_depth(op) # type: ignore + if depth == StackModel.NOT_IN_STACK: + raise CompilerPanic(f"Variable {op} not in stack") + if depth == final_stack_depth: continue From 32cd4b42e8d6b3b325ea338b0968869f9d5903c9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 6 Feb 2024 17:19:04 +0200 Subject: [PATCH 093/322] Update import statement for ERC20 interface --- tests/unit/compiler/venom/test_call.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/compiler/venom/test_call.py b/tests/unit/compiler/venom/test_call.py index 3e5ad7414e..a04a3922a1 100644 --- a/tests/unit/compiler/venom/test_call.py +++ b/tests/unit/compiler/venom/test_call.py @@ -4,7 +4,7 @@ @pytest.fixture def market_maker(get_contract): contract_code = """ -from vyper.interfaces import ERC20 +from ethereum.ercs import ERC20 unused: public(uint256) token_address: ERC20 From 5c8909021a1e5ce8ca3c06b801b8d20094f114a9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 6 Feb 2024 17:41:04 +0200 Subject: [PATCH 094/322] Add stack overflow check in VenomCompiler class --- vyper/venom/venom_to_assembly.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 84123eee32..3d0b9b77f1 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -164,6 +164,11 @@ def _stack_reorder( ) -> None: stack_ops_count = len(stack_ops) + if stack_ops_count > stack.height: + raise CompilerPanic( + f"Stack has {stack.height} items, but {stack_ops_count} are required" + ) + for i in range(stack_ops_count): op = stack_ops[i] final_stack_depth = -(stack_ops_count - i - 1) From ea3d7da9849cc5350e4caee84521d250ed2fcc4f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 7 Feb 2024 00:50:18 +0200 Subject: [PATCH 095/322] pop values that are not used --- vyper/venom/venom_to_assembly.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 3d0b9b77f1..d4bd88d66b 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -164,11 +164,6 @@ def _stack_reorder( ) -> None: stack_ops_count = len(stack_ops) - if stack_ops_count > stack.height: - raise CompilerPanic( - f"Stack has {stack.height} items, but {stack_ops_count} are required" - ) - for i in range(stack_ops_count): op = stack_ops[i] final_stack_depth = -(stack_ops_count - i - 1) @@ -241,8 +236,12 @@ def _generate_evm_for_basicblock_r( self.clean_stack_from_cfg_in(asm, basicblock, stack) - for inst in basicblock.instructions: + for i, inst in enumerate(basicblock.instructions): asm = self._generate_evm_for_instruction(asm, inst, stack) + if inst.volatile and i + 1 < len(basicblock.instructions): + liveness = basicblock.instructions[i + 1].liveness + if inst.output is not None and inst.output not in liveness: + self.pop(asm, stack) for bb in basicblock.reachable: self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) @@ -399,8 +398,6 @@ def _generate_evm_for_instruction( ] ) self.label_counter += 1 - if stack.height > 0 and stack.peek(0) in inst.dup_requirements: - self.pop(assembly, stack) elif opcode == "call": assembly.append("CALL") elif opcode == "staticcall": From beeb6021fdcbb7616bdc78812650d8c53d2c91b4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 7 Feb 2024 00:50:49 +0200 Subject: [PATCH 096/322] fix comment --- vyper/venom/basicblock.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 196c4990cf..dd9a48df0b 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -406,9 +406,7 @@ def append_invoke_instruction( self, args: list[IROperand | int], returns: bool ) -> Optional[IRVariable]: """ - Append an instruction to the basic block - - Returns the output variable if the instruction supports one + Append an invoke to the basic block """ assert not self.is_terminated, self ret = None From 757698d1570292de8e50e70859d273bb2dc5ef1a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 7 Feb 2024 00:51:15 +0200 Subject: [PATCH 097/322] fixes in handling of self calls and internal functions --- vyper/venom/ir_node_to_venom.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 8491c704dd..27f15e0f4b 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -154,12 +154,15 @@ def _handle_self_call( for arg in args_ir: if arg.is_literal: - sym = symbols.get(f"&{arg.value}", None) + sym = _get_variable_from_address(variables, arg.value) if sym is None: ret = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) ret_args.append(ret) else: - ret_args.append(sym) # type: ignore + if allocated_variables.get(sym.name) is None: + ret_args.append(sym) # type: ignore + else: + ret_args.append(allocated_variables.get(sym.name)) else: ret = _convert_ir_bb(ctx, arg._optimized, symbols, variables, allocated_variables) if arg.location and arg.location.load_op == "calldataload": @@ -180,7 +183,11 @@ def _handle_self_call( def _handle_internal_func( - ctx: IRFunction, ir: IRnode, func_t: ContractFunctionT, symbols: SymbolTable + ctx: IRFunction, + ir: IRnode, + func_t: ContractFunctionT, + symbols: SymbolTable, + allocated_variables: dict[str, IRVariable], ) -> IRnode: bb = IRBasicBlock(IRLabel(ir.args[0].args[0].value, True), ctx) # type: ignore bb = ctx.append_basic_block(bb) @@ -189,7 +196,9 @@ def _handle_internal_func( old_ir_mempos += 64 for arg in func_t.arguments: - symbols[f"&{old_ir_mempos}"] = bb.append_instruction("param") + new_var = bb.append_instruction("param") + symbols[f"&{old_ir_mempos}"] = new_var + allocated_variables[arg.name] = new_var bb.instructions[-1].annotation = arg.name old_ir_mempos += 32 # arg.typ.memory_bytes_required @@ -318,7 +327,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): {v: True for v in ir.passthrough_metadata["frame_info"].frame_vars.values()} ) if func_t.is_internal: - ir = _handle_internal_func(ctx, ir, func_t, symbols) + ir = _handle_internal_func(ctx, ir, func_t, symbols, allocated_variables) # fallthrough ret = None @@ -429,10 +438,14 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): sym = ir.args[0] if isinstance(ret, IRLiteral): + var = _get_variable_from_address(variables, int(ret.value)) + if var is not None: + with_symbols[f"&{ret}"] = allocated_variables[var.name] + else: + with_symbols[sym.value] = ret + else: new_var = ctx.get_basic_block().append_instruction("store", ret) # type: ignore with_symbols[sym.value] = new_var - else: - with_symbols[sym.value] = ret # type: ignore return _convert_ir_bb(ctx, ir.args[2], with_symbols, variables, allocated_variables) # body elif ir.value == "goto": @@ -641,6 +654,8 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = new_var return new_var + else: + return allocated_variables[var.name] else: return sym From 694cca3eaf25557438596787f2487a3a27e9eebc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 7 Feb 2024 12:07:32 +0200 Subject: [PATCH 098/322] unused params cleanup --- vyper/venom/venom_to_assembly.py | 33 +++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index d4bd88d66b..f2fee9b373 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -178,6 +178,13 @@ def _stack_reorder( self.swap(assembly, stack, depth) self.swap(assembly, stack, final_stack_depth) + def _cleanup_stack_for_ret(self, asm: list, bb: IRBasicBlock, stack: StackModel) -> None: + for i, inst in enumerate(bb.instructions): + if inst.volatile and i + 1 < len(bb.instructions): + liveness = bb.instructions[i + 1].liveness + if inst.output is not None and inst.output not in liveness: + self.pop(asm, stack) + def _emit_input_operands( self, assembly: list, inst: IRInstruction, ops: list[IROperand], stack: StackModel ) -> None: @@ -236,16 +243,32 @@ def _generate_evm_for_basicblock_r( self.clean_stack_from_cfg_in(asm, basicblock, stack) - for i, inst in enumerate(basicblock.instructions): + param_insts = [inst for inst in basicblock.instructions if inst.opcode == "param"] + main_insts = [inst for inst in basicblock.instructions if inst.opcode != "param"] + + for inst in param_insts: + asm = self._generate_evm_for_instruction(asm, inst, stack) + + self._clean_unused_params(asm, basicblock, stack) + + for inst in main_insts: asm = self._generate_evm_for_instruction(asm, inst, stack) - if inst.volatile and i + 1 < len(basicblock.instructions): - liveness = basicblock.instructions[i + 1].liveness - if inst.output is not None and inst.output not in liveness: - self.pop(asm, stack) for bb in basicblock.reachable: self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) + def _clean_unused_params(self, asm: list, bb: IRBasicBlock, stack: StackModel) -> None: + for i, inst in enumerate(bb.instructions): + if inst.opcode != "param": + break + if inst.volatile and i + 1 < len(bb.instructions): + liveness = bb.instructions[i + 1].liveness + if inst.output is not None and inst.output not in liveness: + depth = stack.get_depth(inst.output) + if depth != 0: + self.swap(asm, stack, depth) + self.pop(asm, stack) + # pop values from stack at entry to bb # note this produces the same result(!) no matter which basic block # we enter from in the CFG. From eb8662a10e6e2dc8ccdf61cceab1fa53d348aedd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 7 Feb 2024 12:07:57 +0200 Subject: [PATCH 099/322] Remove unused method _cleanup_stack_for_ret() in VenomCompiler class --- vyper/venom/venom_to_assembly.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index f2fee9b373..451a2f71f2 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -178,13 +178,6 @@ def _stack_reorder( self.swap(assembly, stack, depth) self.swap(assembly, stack, final_stack_depth) - def _cleanup_stack_for_ret(self, asm: list, bb: IRBasicBlock, stack: StackModel) -> None: - for i, inst in enumerate(bb.instructions): - if inst.volatile and i + 1 < len(bb.instructions): - liveness = bb.instructions[i + 1].liveness - if inst.output is not None and inst.output not in liveness: - self.pop(asm, stack) - def _emit_input_operands( self, assembly: list, inst: IRInstruction, ops: list[IROperand], stack: StackModel ) -> None: From c79fae1246d08340bae76d8262a4e738b6dad2ae Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 7 Feb 2024 13:57:14 +0200 Subject: [PATCH 100/322] fix symbol assignment bug in ir_node_to_venom.py --- vyper/venom/ir_node_to_venom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 27f15e0f4b..6d445af270 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -650,7 +650,8 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): new_var = _convert_ir_bb( ctx, sym_ir, symbols, variables, allocated_variables ) - symbols[f"&{sym_ir.value}"] = new_var + if not isinstance(new_var, IRLiteral): + symbols[f"&{sym_ir.value}"] = new_var if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = new_var return new_var From 85ab7c1ded862cb2d2aaefa92dc169bbbeaa33a1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 7 Feb 2024 18:39:28 +0200 Subject: [PATCH 101/322] trascription magic --- vyper/venom/ir_node_to_venom.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 6d445af270..020629c709 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -154,15 +154,21 @@ def _handle_self_call( for arg in args_ir: if arg.is_literal: - sym = _get_variable_from_address(variables, arg.value) - if sym is None: + var = _get_variable_from_address(variables, arg.value) + if var is None: ret = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) ret_args.append(ret) else: - if allocated_variables.get(sym.name) is None: - ret_args.append(sym) # type: ignore + if allocated_variables.get(var.name) is not None: + ret_args.append(allocated_variables.get(var.name)) else: - ret_args.append(allocated_variables.get(sym.name)) + ret = _convert_ir_bb( + ctx, arg._optimized, symbols, variables, allocated_variables + ) + if arg.location and arg.location.load_op == "calldataload": + bb = ctx.get_basic_block() + ret = bb.append_instruction(arg.location.load_op, ret) + ret_args.append(ret) else: ret = _convert_ir_bb(ctx, arg._optimized, symbols, variables, allocated_variables) if arg.location and arg.location.load_op == "calldataload": @@ -440,7 +446,12 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if isinstance(ret, IRLiteral): var = _get_variable_from_address(variables, int(ret.value)) if var is not None: - with_symbols[f"&{ret}"] = allocated_variables[var.name] + if var.size > 32: + new_var = ctx.get_basic_block().append_instruction("store", ret) + allocated_variables[sym.value] = new_var + with_symbols[sym.value] = new_var + else: + with_symbols[f"&{ret}"] = allocated_variables.get(var.name) else: with_symbols[sym.value] = ret else: From af5f16b9487fdf40a9bac5bf09a74d7d390e0d52 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 7 Feb 2024 13:15:43 -0500 Subject: [PATCH 102/322] add blockhash instruction --- vyper/venom/venom_to_assembly.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 451a2f71f2..48f4bf4420 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -49,6 +49,7 @@ "mstore", "timestamp", "caller", + "blockhash", "selfdestruct", "signextend", "stop", From 77a633af6dad4b12395b48dbdde6326d773a42b4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 8 Feb 2024 20:53:38 +0200 Subject: [PATCH 103/322] Refactor variable handling in _convert_ir_bb function --- vyper/venom/ir_node_to_venom.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 020629c709..7a85911cc3 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -441,24 +441,16 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # Handle with nesting with same symbol with_symbols = symbols.copy() + with_allocated_variables = allocated_variables.copy() sym = ir.args[0] - if isinstance(ret, IRLiteral): - var = _get_variable_from_address(variables, int(ret.value)) - if var is not None: - if var.size > 32: - new_var = ctx.get_basic_block().append_instruction("store", ret) - allocated_variables[sym.value] = new_var - with_symbols[sym.value] = new_var - else: - with_symbols[f"&{ret}"] = allocated_variables.get(var.name) - else: - with_symbols[sym.value] = ret - else: - new_var = ctx.get_basic_block().append_instruction("store", ret) # type: ignore - with_symbols[sym.value] = new_var + new_var = ctx.get_basic_block().append_instruction("store", ret) + with_allocated_variables[sym.value] = new_var + with_symbols[sym.value] = new_var - return _convert_ir_bb(ctx, ir.args[2], with_symbols, variables, allocated_variables) # body + return _convert_ir_bb( + ctx, ir.args[2], with_symbols, variables, with_allocated_variables + ) # body elif ir.value == "goto": _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "djump": From b886630f001b60d30e1d032d64867b2ca26facc5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 8 Feb 2024 20:54:01 +0200 Subject: [PATCH 104/322] fix if-else block handling --- vyper/venom/ir_node_to_venom.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 7a85911cc3..d7e125408f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -401,7 +401,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): assert isinstance(else_ret_val.value, int) # help mypy else_ret_val = ctx.get_basic_block().append_instruction("store", else_ret_val) - else_block = ctx.get_basic_block() + else_block_finish = ctx.get_basic_block() # convert "then" then_block = IRBasicBlock(ctx.get_next_label("then"), ctx) @@ -415,22 +415,22 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): cond_block.append_instruction("jnz", cont_ret, then_block.label, else_block.label) - then_block = ctx.get_basic_block() + then_block_finish = ctx.get_basic_block() # exit bb exit_bb = IRBasicBlock(ctx.get_next_label("if_exit"), ctx) exit_bb = ctx.append_basic_block(exit_bb) - if_ret = ctx.get_next_variable("if_ret") + if_ret = ctx.get_next_variable() if then_ret_val is not None and else_ret_val is not None: - then_block.append_instruction("store", then_ret_val, ret=if_ret) - else_block.append_instruction("store", else_ret_val, ret=if_ret) + then_block_finish.append_instruction("store", then_ret_val, ret=if_ret) + else_block_finish.append_instruction("store", else_ret_val, ret=if_ret) if not else_block.is_terminated: - else_block.append_instruction("jmp", exit_bb.label) + else_block_finish.append_instruction("jmp", exit_bb.label) if not then_block.is_terminated: - then_block.append_instruction("jmp", exit_bb.label) + then_block_finish.append_instruction("jmp", exit_bb.label) return if_ret From a3ba760444bc55dd71dace7d95fe8a71608840f3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 12 Feb 2024 14:53:01 +0200 Subject: [PATCH 105/322] get_variable_op --- vyper/venom/ir_node_to_venom.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index d7e125408f..f11b23ad32 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -235,6 +235,16 @@ def _convert_ir_simple_node( _continue_target: Optional[IRBasicBlock] = None +def _get_variable_from_op( + variables: OrderedSet[VariableRecord], allocated_variables: [], var: IRVariable +) -> VariableRecord: + for name, ivar in allocated_variables.items(): + if var.name == name: + for v in variables.keys(): + if v.name == ivar: + return v + + def _get_variable_from_address( variables: OrderedSet[VariableRecord], addr: int ) -> Optional[VariableRecord]: @@ -323,6 +333,15 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx.immutables_len = ir.args[2].value return None elif ir.value == "seq": + # Special case when all args are mstores + if len([arg for arg in ir.args if arg.value != "mstore"]) == 0: + bb = ctx.get_basic_block() + for ir_node in ir.args: # NOTE: skip the last one + ret = _convert_ir_bb(ctx, ir_node.args[1], symbols, variables, allocated_variables) + bb.append_instruction("mstore", ret) + + return None + func_t = ir.passthrough_metadata.get("func_t", None) if ir.is_self_call: return _handle_self_call(ctx, ir, symbols, variables, allocated_variables) @@ -337,7 +356,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # fallthrough ret = None - for ir_node in ir.args: # NOTE: skip the last one + for ir_node in ir.args: ret = _convert_ir_bb(ctx, ir_node, symbols, variables, allocated_variables) return ret @@ -371,6 +390,14 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): else: argsOffsetVar = argsOffset + bb = ctx.get_basic_block() + if isinstance(address, IRVariable): + var = _get_variable_from_op(variables, allocated_variables, address) + + var = _get_variable_from_address(variables, offset) + if var: + bb.append_instruction("mstore", offset, allocated_variables[var.name]) + if ir.value == "call": args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] else: From 0c99b0199b5d2f085c8e8ab0ed82891cf296e7b8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 12 Feb 2024 20:10:47 +0200 Subject: [PATCH 106/322] Revert "get_variable_op" This reverts commit a3ba760444bc55dd71dace7d95fe8a71608840f3. --- vyper/venom/ir_node_to_venom.py | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index f11b23ad32..d7e125408f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -235,16 +235,6 @@ def _convert_ir_simple_node( _continue_target: Optional[IRBasicBlock] = None -def _get_variable_from_op( - variables: OrderedSet[VariableRecord], allocated_variables: [], var: IRVariable -) -> VariableRecord: - for name, ivar in allocated_variables.items(): - if var.name == name: - for v in variables.keys(): - if v.name == ivar: - return v - - def _get_variable_from_address( variables: OrderedSet[VariableRecord], addr: int ) -> Optional[VariableRecord]: @@ -333,15 +323,6 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx.immutables_len = ir.args[2].value return None elif ir.value == "seq": - # Special case when all args are mstores - if len([arg for arg in ir.args if arg.value != "mstore"]) == 0: - bb = ctx.get_basic_block() - for ir_node in ir.args: # NOTE: skip the last one - ret = _convert_ir_bb(ctx, ir_node.args[1], symbols, variables, allocated_variables) - bb.append_instruction("mstore", ret) - - return None - func_t = ir.passthrough_metadata.get("func_t", None) if ir.is_self_call: return _handle_self_call(ctx, ir, symbols, variables, allocated_variables) @@ -356,7 +337,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # fallthrough ret = None - for ir_node in ir.args: + for ir_node in ir.args: # NOTE: skip the last one ret = _convert_ir_bb(ctx, ir_node, symbols, variables, allocated_variables) return ret @@ -390,14 +371,6 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): else: argsOffsetVar = argsOffset - bb = ctx.get_basic_block() - if isinstance(address, IRVariable): - var = _get_variable_from_op(variables, allocated_variables, address) - - var = _get_variable_from_address(variables, offset) - if var: - bb.append_instruction("mstore", offset, allocated_variables[var.name]) - if ir.value == "call": args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] else: From a6533dff923870a0518b14d6b7716271bb5c7392 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 13 Feb 2024 19:16:45 +0200 Subject: [PATCH 107/322] refactor --- vyper/venom/ir_node_to_venom.py | 133 +++++--------------------------- 1 file changed, 21 insertions(+), 112 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index d7e125408f..2b8d601d1f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -165,9 +165,8 @@ def _handle_self_call( ret = _convert_ir_bb( ctx, arg._optimized, symbols, variables, allocated_variables ) - if arg.location and arg.location.load_op == "calldataload": - bb = ctx.get_basic_block() - ret = bb.append_instruction(arg.location.load_op, ret) + bb = ctx.get_basic_block() + ret = bb.append_instruction(arg.location.load_op, ret) ret_args.append(ret) else: ret = _convert_ir_bb(ctx, arg._optimized, symbols, variables, allocated_variables) @@ -555,7 +554,6 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ) if var is not None: allocated_var = allocated_variables.get(var.name, None) - assert allocated_var is not None, "unallocated variable" new_var = symbols.get(f"&{ret_ir.value}", allocated_var) # type: ignore if var.size and int(var.size) > 32: @@ -566,6 +564,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ptr_var = allocated_var bb.append_instruction("return", last_ir, ptr_var) else: + new_var = bb.append_instruction(var.location.load_op, ret_ir) _append_return_for_stack_operand(ctx, symbols, new_var, last_ir) else: if isinstance(ret_ir, IRLiteral): @@ -624,115 +623,25 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): return None elif ir.value == "mload": - sym_ir = ir.args[0] - var = ( - _get_variable_from_address(variables, int(sym_ir.value)) if sym_ir.is_literal else None - ) - bb = ctx.get_basic_block() - if var is not None: - if var.size and var.size > 32: - if is_array_like(var.typ): - return bb.append_instruction("store", var.pos) - - if allocated_variables.get(var.name, None) is None: - allocated_variables[var.name] = bb.append_instruction( - "alloca", var.size, var.pos - ) - - offset = int(sym_ir.value) - var.pos - if offset > 0: - ptr_var = bb.append_instruction("add", var.pos, offset) - else: - ptr_var = allocated_variables[var.name] - - return bb.append_instruction("mload", ptr_var) - else: - if sym_ir.is_literal: - sym = symbols.get(f"&{sym_ir.value}", None) - if sym is None: - new_var = _convert_ir_bb( - ctx, sym_ir, symbols, variables, allocated_variables - ) - if not isinstance(new_var, IRLiteral): - symbols[f"&{sym_ir.value}"] = new_var - if allocated_variables.get(var.name, None) is None: - allocated_variables[var.name] = new_var - return new_var - else: - return allocated_variables[var.name] - else: - return sym - - sym = symbols.get(f"&{sym_ir.value}", None) - assert sym is not None, "unallocated variable" - return sym - else: - if sym_ir.is_literal: - new_var = symbols.get(f"&{sym_ir.value}", None) - if new_var is not None: - return bb.append_instruction("mload", new_var) - else: - return bb.append_instruction("mload", sym_ir.value) - else: - new_var = _convert_ir_bb(ctx, sym_ir, symbols, variables, allocated_variables) - # - # Old IR gets it's return value as a reference in the stack - # New IR gets it's return value in stack in case of 32 bytes or less - # So here we detect ahead of time if this mload leads a self call and - # and we skip the mload - # - if sym_ir.is_self_call: - return new_var - return ctx.get_basic_block().append_instruction("mload", new_var) - + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) + # return_buffer special case + if allocated_variables.get("return_buffer") == arg_0.name: + return arg_0 + sym = symbols.get(f"&{arg_0.value}") + if sym is not None: + return sym + if isinstance(arg_0, IRLiteral): + var = _get_variable_from_address(variables, arg_0.value) + if var is not None: + avar = allocated_variables.get(var.name) + if avar is not None: + return avar + return ctx.get_basic_block().append_instruction("mload", arg_0) elif ir.value == "mstore": - sym_ir, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) - - bb = ctx.get_basic_block() - - var = None - if isinstance(sym_ir, IRLiteral): - var = _get_variable_from_address(variables, int(sym_ir.value)) - - if var is not None and var.size is not None: - if var.size and var.size > 32: - if allocated_variables.get(var.name, None) is None: - new_var = IRVariable(var.name) - allocated_variables[var.name] = new_var - bb.append_instruction("alloca", var.size, var.pos, ret=new_var) - - offset = int(sym_ir.value) - var.pos - if offset > 0: - ptr_var = bb.append_instruction("add", var.pos, offset) - else: - ptr_var = allocated_variables[var.name] - - bb.append_instruction("mstore", arg_1, ptr_var) - else: - if isinstance(sym_ir, IRLiteral): - new_var = IRVariable(var.name) - bb.append_instruction("store", arg_1, ret=new_var) - symbols[f"&{sym_ir.value}"] = new_var - allocated_variables[var.name] = new_var - return new_var - else: - if not isinstance(sym_ir, IRLiteral): - bb.append_instruction("mstore", arg_1, sym_ir) - return None - - sym = symbols.get(f"&{sym_ir.value}", None) - if sym is None: - bb.append_instruction("mstore", arg_1, sym_ir) - if arg_1 and not isinstance(sym_ir, IRLiteral): - symbols[f"&{sym_ir.value}"] = arg_1 - return None - - if isinstance(sym_ir, IRLiteral): - bb.append_instruction("mstore", arg_1, sym) - return None - else: - symbols[sym_ir.value] = arg_1 - return arg_1 + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) + if isinstance(arg_1, IRVariable): + symbols[f"&{arg_0.value}"] = arg_1 + ctx.get_basic_block().append_instruction("mstore", arg_1, arg_0) elif ir.value == "ceil32": x = ir.args[0] expanded = IRnode.from_list(["and", ["add", x, 31], ["not", 31]]) From 863a78a3c43a7695328e3dc41dedc5ab6f26fc62 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 00:30:32 +0200 Subject: [PATCH 108/322] simplify --- vyper/venom/ir_node_to_venom.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 2b8d601d1f..3cd2a81f60 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -443,9 +443,8 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): with_allocated_variables = allocated_variables.copy() sym = ir.args[0] - new_var = ctx.get_basic_block().append_instruction("store", ret) - with_allocated_variables[sym.value] = new_var - with_symbols[sym.value] = new_var + with_allocated_variables[sym.value] = ret + with_symbols[sym.value] = ret return _convert_ir_bb( ctx, ir.args[2], with_symbols, variables, with_allocated_variables @@ -461,8 +460,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "set": sym = ir.args[0] arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) - new_var = ctx.get_basic_block().append_instruction("store", arg_1) # type: ignore - symbols[sym.value] = new_var + symbols[sym.value] = arg_1 elif ir.value == "calldatacopy": arg_0, arg_1, size = _convert_ir_bb_list( From e6f58c920fca84a0aba62650cc6a765734efc614 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 00:30:53 +0200 Subject: [PATCH 109/322] fix --- vyper/venom/passes/make_ssa.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index b573130604..23daa41aaa 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -1,16 +1,19 @@ from vyper.utils import OrderedSet -from vyper.venom.analysis import calculate_cfg +from vyper.venom.analysis import calculate_cfg, calculate_liveness from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IROperand, IRVariable from vyper.venom.dominators import DominatorTree from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass +count = 1 + class MakeSSA(IRPass): dom: DominatorTree defs: dict[IRVariable, OrderedSet[IRBasicBlock]] def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: + global count self.ctx = ctx calculate_cfg(ctx) @@ -24,6 +27,14 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self._rename_vars(entry) self._remove_degenerate_phis(entry) + # if count == 3: + # calculate_liveness(ctx) + # print(ctx.as_graph()) + # import sys + + # sys.exit() + # count += 1 + return 0 def _add_phi_nodes(self): @@ -120,7 +131,7 @@ def _rename_vars(self, basic_block: IRBasicBlock): self.stacks[op_name].pop() def _remove_degenerate_phis(self, entry: IRBasicBlock): - for inst in entry.instructions: + for inst in entry.instructions.copy(): if inst.opcode != "phi": continue @@ -130,12 +141,8 @@ def _remove_degenerate_phis(self, entry: IRBasicBlock): continue new_ops.extend([label, op]) new_ops_len = len(new_ops) - if new_ops_len == 0: + if new_ops_len <= 2: entry.instructions.remove(inst) - elif new_ops_len == 2: - inst.opcode = "store" - inst.output = new_ops[1] - inst.operands = [new_ops[0]] else: inst.operands = new_ops From aa32d41389978928b0085aac456ddd1f5a504672 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 10:04:52 +0200 Subject: [PATCH 110/322] bug fix then symbols --- vyper/venom/ir_node_to_venom.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 3cd2a81f60..502b9011d9 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -403,6 +403,10 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): else_block_finish = ctx.get_basic_block() # convert "then" + cond_symbols = symbols.copy() + cond_variables = variables.copy() + cond_allocated_variables = allocated_variables.copy() + then_block = IRBasicBlock(ctx.get_next_label("then"), ctx) ctx.append_basic_block(then_block) From 5a44a8365ce7f414979c76d902ce77f93685fddc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 10:05:08 +0200 Subject: [PATCH 111/322] remove debuging --- vyper/venom/passes/make_ssa.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 23daa41aaa..cf54f52fbd 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -5,8 +5,6 @@ from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass -count = 1 - class MakeSSA(IRPass): dom: DominatorTree @@ -27,14 +25,6 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self._rename_vars(entry) self._remove_degenerate_phis(entry) - # if count == 3: - # calculate_liveness(ctx) - # print(ctx.as_graph()) - # import sys - - # sys.exit() - # count += 1 - return 0 def _add_phi_nodes(self): From 0c0a3cb96704e514bed16a933ed426ef4238d4ee Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 13:47:22 +0200 Subject: [PATCH 112/322] debuging --- vyper/venom/passes/make_ssa.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index cf54f52fbd..319d83d2a7 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -1,10 +1,13 @@ from vyper.utils import OrderedSet from vyper.venom.analysis import calculate_cfg, calculate_liveness from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IROperand, IRVariable +from vyper.venom.bb_optimizer import _optimize_unused_variables, ir_pass_optimize_unused_variables from vyper.venom.dominators import DominatorTree from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass +count = 1 + class MakeSSA(IRPass): dom: DominatorTree @@ -25,6 +28,19 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self._rename_vars(entry) self._remove_degenerate_phis(entry) + # if True or "_transfer_in" in entry.label.value: + # if count == 3: + # calculate_liveness(ctx) + # print(ctx.as_graph()) + # import sys + + # sys.exit() + # count += 1 + + # calculate_liveness(ctx) + # ir_pass_optimize_unused_variables(ctx) + # calculate_liveness(ctx) + return 0 def _add_phi_nodes(self): @@ -50,6 +66,10 @@ def _add_phi_nodes(self): defs.append(dom) def _place_phi(self, var: IRVariable, basic_block: IRBasicBlock): + # if "%529" not in var.name: + # return + # if var.name == "%856": + # return args: list[IROperand] = [] for bb in basic_block.cfg_in: if bb == basic_block: @@ -131,8 +151,12 @@ def _remove_degenerate_phis(self, entry: IRBasicBlock): continue new_ops.extend([label, op]) new_ops_len = len(new_ops) - if new_ops_len <= 2: + if new_ops_len == 0: + entry.instructions.remove(inst) + elif new_ops_len == 2: entry.instructions.remove(inst) + # inst.opcode = "store" + # inst.operands = [new_ops[1]] else: inst.operands = new_ops From 245cb271870c96949887d8b88e6523995c197efa Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 13:47:51 +0200 Subject: [PATCH 113/322] clear out vars on reseting liveness --- vyper/venom/analysis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 98a4ea0720..88bc7cb833 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -38,6 +38,7 @@ def calculate_cfg(ctx: IRFunction) -> None: def _reset_liveness(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: + bb.out_vars = OrderedSet() for inst in bb.instructions: inst.liveness = OrderedSet() From 7f1e938e13487665512cfbd74306aa24d7bd65c8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 13:48:06 +0200 Subject: [PATCH 114/322] make ssa test --- tests/unit/compiler/venom/test_make_ssa.py | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/unit/compiler/venom/test_make_ssa.py diff --git a/tests/unit/compiler/venom/test_make_ssa.py b/tests/unit/compiler/venom/test_make_ssa.py new file mode 100644 index 0000000000..c4093c5248 --- /dev/null +++ b/tests/unit/compiler/venom/test_make_ssa.py @@ -0,0 +1,43 @@ +from vyper.venom.analysis import calculate_cfg, calculate_liveness +from vyper.venom.basicblock import IRBasicBlock, IRLabel +from vyper.venom.bb_optimizer import _optimize_unused_variables +from vyper.venom.function import IRFunction +from vyper.venom.passes.make_ssa import MakeSSA + + +def test_phi_case(): + ctx = IRFunction(IRLabel("_global")) + + bb = ctx.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) + + v = bb.append_instruction("mload", 64) + bb_cont.append_instruction("jnz", v, bb_then.label, bb_else.label) + + bb_if_exit.append_instruction("add", v, 1, ret=v) + bb_if_exit.append_instruction("jmp", bb_cont.label) + + bb_then.append_instruction("assert", bb_then.append_instruction("mload", 96)) + bb_then.append_instruction("jmp", bb_if_exit.label) + bb_else.append_instruction("jmp", bb_if_exit.label) + + bb.append_instruction("jmp", bb_cont.label) + + calculate_cfg(ctx) + MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) + calculate_liveness(ctx) + # _optimize_unused_variables(ctx) + # calculate_liveness(ctx) + print(ctx.as_graph()) + + +if __name__ == "__main__": + test_phi_case() From 4b1b9b88ae41443f271238f91e6b2c57fc06df24 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 16:44:44 +0200 Subject: [PATCH 115/322] move to orderedset --- vyper/venom/dominators.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index 433f11dad9..cc9066788a 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -1,4 +1,5 @@ from vyper.exceptions import CompilerPanic +from vyper.utils import OrderedSet from vyper.venom.basicblock import IRBasicBlock from vyper.venom.function import IRFunction @@ -12,10 +13,10 @@ class DominatorTree: entry: IRBasicBlock dfs_order: dict[IRBasicBlock, int] dfs: list[IRBasicBlock] - dominators: dict[IRBasicBlock, set[IRBasicBlock]] + dominators: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] idoms: dict[IRBasicBlock, IRBasicBlock] - dominated: dict[IRBasicBlock, set[IRBasicBlock]] - df: dict[IRBasicBlock, set[IRBasicBlock]] + dominated: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] + df: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] def __init__(self, ctx: IRFunction, entry: IRBasicBlock): self.ctx = ctx @@ -35,15 +36,15 @@ def immediate_dominator(self, bb): return self.idoms.get(bb) def _compute(self): - self._dfs(self.entry, set()) + self._dfs(self.entry, OrderedSet()) self._compute_dominators() self._compute_idoms() self._compute_df() def _compute_dominators(self): basic_blocks = list(self.dfs_order.keys()) - self.dominators = {bb: set(basic_blocks) for bb in basic_blocks} - self.dominators[self.entry] = {self.entry} + self.dominators = {bb: OrderedSet(basic_blocks) for bb in basic_blocks} + self.dominators[self.entry] = OrderedSet({self.entry}) changed = True count = len(basic_blocks) ** 2 # TODO: find a proper bound for this while changed: @@ -56,7 +57,9 @@ def _compute_dominators(self): continue preds = bb.cfg_in if len(preds) > 0: - new_dominators = set.intersection(*[self.dominators[pred] for pred in preds]) + new_dominators = OrderedSet.intersection( + *[self.dominators[pred] for pred in preds] + ) new_dominators.add(bb) if new_dominators != self.dominators[bb]: self.dominators[bb] = new_dominators @@ -79,7 +82,7 @@ def _compute_idoms(self): doms = sorted(self.dominators[bb], key=lambda x: self.dfs_order[x]) self.idoms[bb] = doms[1] - self.dominated = {bb: set() for bb in self.dfs} + self.dominated = {bb: OrderedSet() for bb in self.dfs} for dom, target in self.idoms.items(): self.dominated[target].add(dom) @@ -93,7 +96,7 @@ def _compute_df(self): Compute dominance frontier """ basic_blocks = self.dfs - self.df = {bb: set() for bb in basic_blocks} + self.df = {bb: OrderedSet() for bb in basic_blocks} for bb in self.dfs: if len(bb.cfg_in) > 1: @@ -112,7 +115,7 @@ def dominance_frontier(self, basic_blocks: list[IRBasicBlock]): """ Compute dominance frontier of a set of basic blocks. """ - df = set() + df = OrderedSet() for bb in basic_blocks: df.update(self.df[bb]) return df From c5047e50fa0e54c92565ed3d11e34dff3d48b89b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 16:44:59 +0200 Subject: [PATCH 116/322] add intersection method to OrderedSet --- vyper/utils.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vyper/utils.py b/vyper/utils.py index bb50ba01d3..b77d1d00fe 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -65,6 +65,18 @@ def __iter__(self) -> Iterator[_T]: def copy(self): return self.__class__(super().copy()) + @classmethod + def intersection(cls, *sets): + res = OrderedSet() + if not sets: + return res + if len(sets) == 1: + return sets[0].copy() + for e in sets[0].keys(): + if all(e in s for s in sets[1:]): + res.add(e) + return res + class DecimalContextOverride(decimal.Context): def __setattr__(self, name, value): From 2057bf366bf9f6baaa5573fc4e1e69ccb6e113e9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 16:50:28 +0200 Subject: [PATCH 117/322] update test --- tests/unit/compiler/venom/test_dominator_tree.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index fc78a4ccee..edb379f7ee 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -1,6 +1,7 @@ from typing import Optional from vyper.exceptions import CompilerPanic +from vyper.utils import OrderedSet from vyper.venom.analysis import calculate_cfg from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRLiteral, IRVariable from vyper.venom.dominators import DominatorTree @@ -50,13 +51,13 @@ def test_deminator_frontier_calculation(): calculate_cfg(ctx) dom = DominatorTree(ctx, bb1) - assert dom.df[bb1] == set(), dom.df[bb1] - assert dom.df[bb2] == {bb2}, dom.df[bb2] - assert dom.df[bb3] == {bb3, bb6}, dom.df[bb3] - assert dom.df[bb4] == {bb6}, dom.df[bb4] - assert dom.df[bb5] == {bb3, bb6}, dom.df[bb5] - assert dom.df[bb6] == {bb2}, dom.df[bb6] - assert dom.df[bb7] == set(), dom.df[bb7] + assert len(dom.df[bb1]) == 0, dom.df[bb1] + assert dom.df[bb2] == OrderedSet({bb2}), dom.df[bb2] + assert dom.df[bb3] == OrderedSet({bb3, bb6}), dom.df[bb3] + assert dom.df[bb4] == OrderedSet({bb6}), dom.df[bb4] + assert dom.df[bb5] == OrderedSet({bb3, bb6}), dom.df[bb5] + assert dom.df[bb6] == OrderedSet({bb2}), dom.df[bb6] + assert len(dom.df[bb7]) == 0, dom.df[bb7] def test_phi_placement(): From 287c0dd6eb8a11be0aac3b2972e1ce84da1aaef9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 20:46:20 +0200 Subject: [PATCH 118/322] cleanup --- vyper/venom/passes/make_ssa.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 319d83d2a7..71db7dd617 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -6,8 +6,6 @@ from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass -count = 1 - class MakeSSA(IRPass): dom: DominatorTree @@ -21,6 +19,7 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: dom = DominatorTree(ctx, entry) self.dom = dom + calculate_liveness(ctx) self._add_phi_nodes() self.var_names = {var.name: 0 for var in self.defs.keys()} @@ -28,19 +27,6 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self._rename_vars(entry) self._remove_degenerate_phis(entry) - # if True or "_transfer_in" in entry.label.value: - # if count == 3: - # calculate_liveness(ctx) - # print(ctx.as_graph()) - # import sys - - # sys.exit() - # count += 1 - - # calculate_liveness(ctx) - # ir_pass_optimize_unused_variables(ctx) - # calculate_liveness(ctx) - return 0 def _add_phi_nodes(self): @@ -66,10 +52,10 @@ def _add_phi_nodes(self): defs.append(dom) def _place_phi(self, var: IRVariable, basic_block: IRBasicBlock): - # if "%529" not in var.name: - # return - # if var.name == "%856": - # return + liveness = basic_block.instructions[0].liveness + if var not in liveness: + return + args: list[IROperand] = [] for bb in basic_block.cfg_in: if bb == basic_block: From b4cdfa752563a89bd60f90f6bee431bbdc5bf72d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 14 Feb 2024 21:38:55 +0200 Subject: [PATCH 119/322] fix internal invocation --- vyper/venom/ir_node_to_venom.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 502b9011d9..0792c8f806 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -157,6 +157,9 @@ def _handle_self_call( var = _get_variable_from_address(variables, arg.value) if var is None: ret = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) + if isinstance(ret, IRLiteral): + bb = ctx.get_basic_block() + ret = bb.append_instruction("mload", ret) ret_args.append(ret) else: if allocated_variables.get(var.name) is not None: From 4e28eca264ab69c0dda2f8c88cc5cdceb45d7e35 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 15 Feb 2024 12:13:40 +0200 Subject: [PATCH 120/322] enable experimental --- vyper/compiler/phases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 5b7decec7b..467776b79b 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -97,7 +97,7 @@ def __init__( Do not add metadata to bytecode. Defaults to False """ # to force experimental codegen, uncomment: - # settings.experimental_codegen = True + settings.experimental_codegen = True if isinstance(file_input, str): file_input = FileInput( From 882116e91cdb639dc7d8c0ed9efa4a43984cf152 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 15 Feb 2024 21:16:20 +0200 Subject: [PATCH 121/322] fix constructor exit issue --- .../compiler/venom/test_duplicate_operands.py | 27 ------------------- vyper/venom/ir_node_to_venom.py | 2 +- vyper/venom/venom_to_assembly.py | 13 +++++++-- 3 files changed, 12 insertions(+), 30 deletions(-) delete mode 100644 tests/unit/compiler/venom/test_duplicate_operands.py diff --git a/tests/unit/compiler/venom/test_duplicate_operands.py b/tests/unit/compiler/venom/test_duplicate_operands.py deleted file mode 100644 index b96c7f3351..0000000000 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ /dev/null @@ -1,27 +0,0 @@ -from vyper.compiler.settings import OptimizationLevel -from vyper.venom import generate_assembly_experimental -from vyper.venom.function import IRFunction - - -def test_duplicate_operands(): - """ - Test the duplicate operands code generation. - The venom code: - - %1 = 10 - %2 = add %1, %1 - %3 = mul %1, %2 - stop - - Should compile to: [PUSH1, 10, DUP1, DUP1, DUP1, ADD, MUL, STOP] - """ - ctx = IRFunction() - bb = ctx.get_basic_block() - op = bb.append_instruction("store", 10) - sum_ = bb.append_instruction("add", op, op) - bb.append_instruction("mul", sum_, op) - bb.append_instruction("stop") - - asm = generate_assembly_experimental(ctx, optimize=OptimizationLevel.CODESIZE) - - assert asm == ["PUSH1", 10, "DUP1", "DUP1", "DUP1", "ADD", "MUL", "STOP", "REVERT"] diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 0792c8f806..40c9bbd53e 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -529,7 +529,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb = ctx.get_basic_block() if func_t.is_external: # Hardcoded contructor special case - if func_t.name == "__init__": + if func_t.is_constructor: label = IRLabel(ir.args[0].value, True) bb.append_instruction("jmp", label) return None diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 48f4bf4420..09eba5ed27 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -126,9 +126,8 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: self._generate_evm_for_basicblock_r(asm, ctx.basic_blocks[0], StackModel()) # TODO make this property on IRFunction + asm.extend(["_sym__ctor_exit", "JUMPDEST"]) if ctx.immutables_len is not None and ctx.ctor_mem_size is not None: - while asm[-1] != "JUMPDEST": - asm.pop() asm.extend( ["_sym_subcode_size", "_sym_runtime_begin", "_mem_deploy_start", "CODECOPY"] ) @@ -231,6 +230,11 @@ def _generate_evm_for_basicblock_r( return self.visited_basicblocks.add(basicblock) + bb_label = basicblock.label.value + is_constructor_cleanup = ( + "__init__" in bb_label and "_cleanup" in bb_label + ) or bb_label == "__global" + # assembly entry point into the block asm.append(f"_sym_{basicblock.label}") asm.append("JUMPDEST") @@ -246,6 +250,11 @@ def _generate_evm_for_basicblock_r( self._clean_unused_params(asm, basicblock, stack) for inst in main_insts: + if is_constructor_cleanup and inst.opcode == "stop": + asm.append("_sym__ctor_exit") + asm.append("JUMP") + continue + asm = self._generate_evm_for_instruction(asm, inst, stack) for bb in basicblock.reachable: From 484df7f8f45ababf28bb86b3c27a8c5ad4cbc8d6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 01:17:24 +0200 Subject: [PATCH 122/322] stuff --- vyper/compiler/phases.py | 3 ++- vyper/venom/ir_node_to_venom.py | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 467776b79b..850b80e1f3 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -97,7 +97,8 @@ def __init__( Do not add metadata to bytecode. Defaults to False """ # to force experimental codegen, uncomment: - settings.experimental_codegen = True + if settings: + settings.experimental_codegen = True if isinstance(file_input, str): file_input = FileInput( diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 40c9bbd53e..8046929f4f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -445,6 +445,9 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx, ir.args[1], symbols, variables, allocated_variables ) # initialization + if isinstance(ret, IRLiteral): + ret = ctx.get_basic_block().append_instruction("store", ret.value) + # Handle with nesting with same symbol with_symbols = symbols.copy() with_allocated_variables = allocated_variables.copy() @@ -467,7 +470,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "set": sym = ir.args[0] arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) - symbols[sym.value] = arg_1 + ctx.get_basic_block().append_instruction("store", arg_1, ret=allocated_variables[sym.value]) elif ir.value == "calldatacopy": arg_0, arg_1, size = _convert_ir_bb_list( @@ -480,12 +483,12 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): vars = _get_variables_from_address_and_size( variables, int(arg_0.value), int(size.value) ) - for var in vars: - if allocated_variables.get(var.name, None) is None: - new_v = IRVariable(var.name) - ctx.get_basic_block().append_instruction("alloca", var.size, var.pos, ret=new_v) - allocated_variables[var.name] = new_v - symbols[f"&{var.pos}"] = new_v + # for var in vars: + # if allocated_variables.get(var.name, None) is None: + # new_v = IRVariable(var.name) + # ctx.get_basic_block().append_instruction("alloca", var.size, var.pos, ret=new_v) + # allocated_variables[var.name] = new_v + # symbols[f"&{var.pos}"] = new_v bb.append_instruction("calldatacopy", size, arg_1, arg_0) # type: ignore @@ -632,9 +635,9 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # return_buffer special case if allocated_variables.get("return_buffer") == arg_0.name: return arg_0 - sym = symbols.get(f"&{arg_0.value}") - if sym is not None: - return sym + # sym = symbols.get(f"&{arg_0.value}") + # if sym is not None: + # return sym if isinstance(arg_0, IRLiteral): var = _get_variable_from_address(variables, arg_0.value) if var is not None: From d4ed07d06f3eaca197b06f98c9a3e3dd0a559d7e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 11:24:42 +0200 Subject: [PATCH 123/322] creates --- vyper/venom/ir_node_to_venom.py | 5 +++++ vyper/venom/venom_to_assembly.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 8046929f4f..075db014f6 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -75,6 +75,8 @@ "extcodesize", "extcodehash", "balance", + "msize", + "extcodecopy", ] SymbolTable = dict[str, Optional[IROperand]] @@ -768,6 +770,9 @@ def emit_body_blocks(): topic_count = int(ir.value[3:]) assert topic_count >= 0 and topic_count <= 4, "invalid topic count" ctx.get_basic_block().append_instruction("log", topic_count, *args) + elif ir.value == "create" or ir.value == "create2": + args = reversed(_convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables)) + return ctx.get_basic_block().append_instruction(ir.value, *args) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): _convert_ir_opcode(ctx, ir, symbols, variables, allocated_variables) elif isinstance(ir.value, str) and ir.value in symbols: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 09eba5ed27..af534fad46 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -74,6 +74,9 @@ "lt", "slt", "sgt", + "create", + "create2", + "msize", ] ) From 60362dab02d120b07d89cfdfc1ec912ae14c19f8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 11:56:13 +0200 Subject: [PATCH 124/322] offset try --- vyper/venom/ir_node_to_venom.py | 16 +++++++++++++--- vyper/venom/venom_to_assembly.py | 21 ++++++++++++--------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 075db014f6..c17ab87d85 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -662,12 +662,22 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): expanded = IRnode.from_list(["xor", b, ["mul", cond, ["xor", a, b]]]) return _convert_ir_bb(ctx, expanded, symbols, variables, allocated_variables) - elif ir.value in ["sload", "iload"]: + elif ir.value == "sload": arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) return ctx.get_basic_block().append_instruction(ir.value, arg_0) - elif ir.value in ["sstore", "istore"]: + elif ir.value == "sstore": arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) - ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) + bb.append_instruction(ir.value, arg_1, arg_0) + elif ir.value == "iload": + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) + bb = ctx.get_basic_block() + offset = bb.append_instruction("offset", IRLabel("_mem_deploy_end"), arg_0) + return bb.append_instruction("mload", offset) + elif ir.value == "istore": + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) + bb = ctx.get_basic_block() + offset = bb.append_instruction("offset", IRLabel("_mem_deploy_end"), arg_1) + bb.append_instruction("mstore", arg_1, offset) elif ir.value == "unique_symbol": sym = ir.args[0] new_var = ctx.get_next_variable() diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index af534fad46..c71dad8501 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -321,10 +321,12 @@ def _generate_evm_for_instruction( operands = inst.get_non_label_operands() elif opcode == "alloca": operands = inst.operands[1:2] - elif opcode == "iload": - operands = [] - elif opcode == "istore": - operands = inst.operands[0:1] + elif opcode == "offset": + offset = inst.operands[1] + if isinstance(offset, IRLiteral): + operands = [] + else: + operands = inst.operands[1:2] elif opcode == "log": log_topic_count = inst.operands[0].value assert log_topic_count in [0, 1, 2, 3, 4], "Invalid topic count" @@ -455,12 +457,13 @@ def _generate_evm_for_instruction( assembly.extend([*PUSH(31), "ADD", *PUSH(31), "NOT", "AND"]) elif opcode == "assert": assembly.extend(["ISZERO", "_sym___revert", "JUMPI"]) - elif opcode == "iload": - loc = inst.operands[0].value - assembly.extend(["_OFST", "_mem_deploy_end", loc, "MLOAD"]) - elif opcode == "istore": + elif opcode == "offset": + sym = inst.operands[0].value loc = inst.operands[1].value - assembly.extend(["_OFST", "_mem_deploy_end", loc, "MSTORE"]) + if isinstance(loc, int): + assembly.extend(["_OFST", sym, loc]) + else: + assembly.extend([sym, "ADD"]) elif opcode == "log": assembly.extend([f"LOG{log_topic_count}"]) else: From d19bf392b0dd9f90ca2853d93839f161330cab63 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 12:25:23 +0200 Subject: [PATCH 125/322] istore iload --- vyper/venom/ir_node_to_venom.py | 17 ++++------------ vyper/venom/venom_to_assembly.py | 33 ++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c17ab87d85..7cf047678b 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -662,22 +662,13 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): expanded = IRnode.from_list(["xor", b, ["mul", cond, ["xor", a, b]]]) return _convert_ir_bb(ctx, expanded, symbols, variables, allocated_variables) - elif ir.value == "sload": + elif ir.value in ["iload", "sload"]: arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) return ctx.get_basic_block().append_instruction(ir.value, arg_0) - elif ir.value == "sstore": + elif ir.value in ["istore", "sstore"]: arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) - bb.append_instruction(ir.value, arg_1, arg_0) - elif ir.value == "iload": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) - bb = ctx.get_basic_block() - offset = bb.append_instruction("offset", IRLabel("_mem_deploy_end"), arg_0) - return bb.append_instruction("mload", offset) - elif ir.value == "istore": - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) - bb = ctx.get_basic_block() - offset = bb.append_instruction("offset", IRLabel("_mem_deploy_end"), arg_1) - bb.append_instruction("mstore", arg_1, offset) + ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) + elif ir.value == "unique_symbol": sym = ir.args[0] new_var = ctx.get_next_variable() diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index c71dad8501..aad3f6902b 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -321,12 +321,18 @@ def _generate_evm_for_instruction( operands = inst.get_non_label_operands() elif opcode == "alloca": operands = inst.operands[1:2] - elif opcode == "offset": - offset = inst.operands[1] - if isinstance(offset, IRLiteral): + elif opcode == "iload": + addr = inst.operands[0] + if isinstance(addr, IRLiteral): operands = [] else: - operands = inst.operands[1:2] + operands = inst.operands + elif opcode == "istore": + addr = inst.operands[1] + if isinstance(addr, IRLiteral): + operands = inst.operands[0:1] + else: + operands = inst.operands elif opcode == "log": log_topic_count = inst.operands[0].value assert log_topic_count in [0, 1, 2, 3, 4], "Invalid topic count" @@ -457,13 +463,20 @@ def _generate_evm_for_instruction( assembly.extend([*PUSH(31), "ADD", *PUSH(31), "NOT", "AND"]) elif opcode == "assert": assembly.extend(["ISZERO", "_sym___revert", "JUMPI"]) - elif opcode == "offset": - sym = inst.operands[0].value - loc = inst.operands[1].value - if isinstance(loc, int): - assembly.extend(["_OFST", sym, loc]) + elif opcode == "iload": + addr = inst.operands[0] + if isinstance(addr, IRLiteral): + assembly.extend(["_OFST", "_mem_deploy_end", addr.value]) + else: + assembly.extend(["_mem_deploy_end", "ADD"]) + assembly.append("MLOAD") + elif opcode == "istore": + addr = inst.operands[0] + if isinstance(addr, IRLiteral): + assembly.extend(["_OFST", "_mem_deploy_end", addr.value]) else: - assembly.extend([sym, "ADD"]) + assembly.extend(["_mem_deploy_end", "ADD"]) + assembly.append("MSTORE") elif opcode == "log": assembly.extend([f"LOG{log_topic_count}"]) else: From 38c550f0aecd8dfec08fee23834c835fa3e4dfba Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 14:33:02 +0200 Subject: [PATCH 126/322] extcodecopy --- vyper/venom/basicblock.py | 1 + vyper/venom/ir_node_to_venom.py | 12 ++++++------ vyper/venom/venom_to_assembly.py | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index dd9a48df0b..b3cd7852d2 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -40,6 +40,7 @@ "dloadbytes", "calldatacopy", "codecopy", + "extcodecopy", "return", "ret", "revert", diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 7cf047678b..963fdc6090 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -76,7 +76,6 @@ "extcodehash", "balance", "msize", - "extcodecopy", ] SymbolTable = dict[str, Optional[IROperand]] @@ -230,8 +229,11 @@ def _convert_ir_simple_node( symbols: SymbolTable, variables: OrderedSet, allocated_variables: dict[str, IRVariable], + reverse: bool = False, ) -> Optional[IRVariable]: args = [_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args] + if reverse: + args = reversed(args) return ctx.get_basic_block().append_instruction(ir.value, *args) # type: ignore @@ -495,12 +497,10 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("calldatacopy", size, arg_1, arg_0) # type: ignore return None - elif ir.value == "codecopy": - arg_0, arg_1, size = _convert_ir_bb_list( - ctx, ir.args, symbols, variables, allocated_variables + elif ir.value in ["extcodecopy", "codecopy"]: + return _convert_ir_simple_node( + ctx, ir, symbols, variables, allocated_variables, reverse=True ) - - ctx.get_basic_block().append_instruction("codecopy", size, arg_1, arg_0) # type: ignore elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) elif ir.value == "data": diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index aad3f6902b..0e309c3805 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -39,6 +39,7 @@ "number", "extcodesize", "extcodehash", + "extcodecopy", "returndatasize", "returndatacopy", "callvalue", From c6572bcf538428304851eafe3667d98a0d812d6e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 15:19:45 +0200 Subject: [PATCH 127/322] facepalm --- vyper/venom/venom_to_assembly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 0e309c3805..74380bb33e 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -472,7 +472,7 @@ def _generate_evm_for_instruction( assembly.extend(["_mem_deploy_end", "ADD"]) assembly.append("MLOAD") elif opcode == "istore": - addr = inst.operands[0] + addr = inst.operands[1] if isinstance(addr, IRLiteral): assembly.extend(["_OFST", "_mem_deploy_end", addr.value]) else: From ef4310340114153a693435236ca42d1a4fcd7393 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 15:31:57 +0200 Subject: [PATCH 128/322] addmod mulmod --- vyper/venom/ir_node_to_venom.py | 3 +++ vyper/venom/venom_to_assembly.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 963fdc6090..2ff3502468 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -322,6 +322,9 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): elif ir.value in PASS_THROUGH_INSTRUCTIONS: return _convert_ir_simple_node(ctx, ir, symbols, variables, allocated_variables) + elif ir.value in ["addmod", "mulmod"]: + return _convert_ir_simple_node(ctx, ir, symbols, variables, allocated_variables, True) + elif ir.value in ["pass", "stop", "return"]: pass elif ir.value == "deploy": diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 74380bb33e..5b4015621d 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -68,6 +68,8 @@ "sdiv", "mod", "exp", + "addmod", + "mulmod", "eq", "iszero", "not", From 5a6a260ef338668f1078b0f4280efdff7332a669 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 17:17:22 +0200 Subject: [PATCH 129/322] revert --- vyper/venom/ir_node_to_venom.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 2ff3502468..2ba8bca36e 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -574,26 +574,17 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if offset > 0: ptr_var = bb.append_instruction("add", var.pos, offset) else: - ptr_var = allocated_var + ptr_var = var.pos bb.append_instruction("return", last_ir, ptr_var) else: new_var = bb.append_instruction(var.location.load_op, ret_ir) _append_return_for_stack_operand(ctx, symbols, new_var, last_ir) else: if isinstance(ret_ir, IRLiteral): - sym = symbols.get(f"&{ret_ir.value}", None) - if sym is None: - bb.append_instruction("return", last_ir, ret_ir) - else: - if func_t.return_type.memory_bytes_required > 32: - new_var = bb.append_instruction("alloca", 32, ret_ir) - bb.append_instruction("mstore", sym, new_var) - bb.append_instruction("return", last_ir, new_var) - else: - bb.append_instruction("return", last_ir, ret_ir) + bb.append_instruction("return", last_ir, ret_ir) else: if last_ir and int(last_ir.value) > 32: - bb.append_instruction("return", last_ir, ret_ir) + bb.append_instruction("return", ret_ir, last_ir) else: ret_buf = 128 # TODO: need allocator new_var = bb.append_instruction("alloca", 32, ret_buf) From 9d18a54c2b5c0dda3693e8566985cca8ff24b9eb Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 17:36:47 +0200 Subject: [PATCH 130/322] fix push literals --- vyper/venom/venom_to_assembly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 5b4015621d..d85890c233 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -210,7 +210,7 @@ def _emit_input_operands( continue if isinstance(op, IRLiteral): - assembly.extend([*PUSH(op.value)]) + assembly.extend(PUSH(op.value % 2**256)) stack.push(op) continue From eefb3ebef7a1482ad65c3e96890590711944da24 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 23:38:00 +0200 Subject: [PATCH 131/322] in_vars property --- vyper/venom/basicblock.py | 12 +++++++++++- vyper/venom/passes/make_ssa.py | 3 +-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index b3cd7852d2..d96b2e7bc1 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -296,8 +296,11 @@ def __repr__(self) -> str: s += f"{self.output} = " opcode = f"{self.opcode} " if self.opcode != "store" else "" s += opcode + operands = self.operands + if opcode not in ["jmp", "jnz", "invoke"]: + operands.reverse() operands = ", ".join( - [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in self.operands] + [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in operands] ) s += operands @@ -476,6 +479,13 @@ def is_terminal(self) -> bool: """ return len(self.cfg_out) == 0 + @property + def in_vars(self) -> OrderedSet[IRVariable]: + for inst in self.instructions: + if inst.opcode != "phi": + return inst.liveness + return OrderedSet() + def copy(self): bb = IRBasicBlock(self.label, self.parent) bb.instructions = self.instructions.copy() diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 71db7dd617..1fff77560a 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -52,8 +52,7 @@ def _add_phi_nodes(self): defs.append(dom) def _place_phi(self, var: IRVariable, basic_block: IRBasicBlock): - liveness = basic_block.instructions[0].liveness - if var not in liveness: + if var not in basic_block.in_vars: return args: list[IROperand] = [] From d325ca17e0f1d9e84f5903c8aae86d51ecf0a712 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Feb 2024 23:38:10 +0200 Subject: [PATCH 132/322] bugfix if --- vyper/venom/ir_node_to_venom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 2ba8bca36e..9e6b2ef7d5 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -439,10 +439,10 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): then_block_finish.append_instruction("store", then_ret_val, ret=if_ret) else_block_finish.append_instruction("store", else_ret_val, ret=if_ret) - if not else_block.is_terminated: + if not else_block_finish.is_terminated: else_block_finish.append_instruction("jmp", exit_bb.label) - if not then_block.is_terminated: + if not then_block_finish.is_terminated: then_block_finish.append_instruction("jmp", exit_bb.label) return if_ret From 277a28249a870f21c01647c59fde922552c5bc12 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 17 Feb 2024 10:05:30 +0200 Subject: [PATCH 133/322] fix parameter passing case --- vyper/venom/ir_node_to_venom.py | 35 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 9e6b2ef7d5..abb4e7d81f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -155,23 +155,26 @@ def _handle_self_call( for arg in args_ir: if arg.is_literal: - var = _get_variable_from_address(variables, arg.value) - if var is None: - ret = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) - if isinstance(ret, IRLiteral): - bb = ctx.get_basic_block() - ret = bb.append_instruction("mload", ret) - ret_args.append(ret) - else: - if allocated_variables.get(var.name) is not None: - ret_args.append(allocated_variables.get(var.name)) - else: - ret = _convert_ir_bb( - ctx, arg._optimized, symbols, variables, allocated_variables - ) - bb = ctx.get_basic_block() - ret = bb.append_instruction(arg.location.load_op, ret) + if arg.is_pointer: + var = _get_variable_from_address(variables, arg.value) + if var is None: + ret = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) + if isinstance(ret, IRLiteral): + bb = ctx.get_basic_block() + ret = bb.append_instruction("mload", ret) ret_args.append(ret) + else: + if allocated_variables.get(var.name) is not None: + ret_args.append(allocated_variables.get(var.name)) + else: + ret = _convert_ir_bb( + ctx, arg._optimized, symbols, variables, allocated_variables + ) + bb = ctx.get_basic_block() + ret = bb.append_instruction(arg.location.load_op, ret) + ret_args.append(ret) + else: + ret_args.append(IRLiteral(arg.value)) else: ret = _convert_ir_bb(ctx, arg._optimized, symbols, variables, allocated_variables) if arg.location and arg.location.load_op == "calldataload": From b9927f96dc08a9777558b932223a58524985bc7f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 17 Feb 2024 12:01:21 +0200 Subject: [PATCH 134/322] array returns --- vyper/venom/ir_node_to_venom.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index abb4e7d81f..c26e8d7ba8 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -187,6 +187,8 @@ def _handle_self_call( if do_ret: ret_args.append(return_buf.value) # type: ignore invoke_ret = bb.append_invoke_instruction(ret_args, returns=True) # type: ignore + if func_t.return_type.size_in_bytes > 32: + invoke_ret = bb.append_instruction("mload", invoke_ret) allocated_variables["return_buffer"] = invoke_ret # type: ignore return invoke_ret else: From e17ddecc37af0b6f13b742cd419e1083ae1454f1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 17 Feb 2024 21:02:31 +0200 Subject: [PATCH 135/322] return buffer hacks --- vyper/venom/ir_node_to_venom.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c26e8d7ba8..a198ebaf55 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -187,9 +187,8 @@ def _handle_self_call( if do_ret: ret_args.append(return_buf.value) # type: ignore invoke_ret = bb.append_invoke_instruction(ret_args, returns=True) # type: ignore - if func_t.return_type.size_in_bytes > 32: - invoke_ret = bb.append_instruction("mload", invoke_ret) - allocated_variables["return_buffer"] = invoke_ret # type: ignore + if func_t.return_type.size_in_bytes <= 32: + allocated_variables["return_buffer"] = invoke_ret # type: ignore return invoke_ret else: bb.append_invoke_instruction(ret_args, returns=False) # type: ignore From f754ff08c472fee49f1907685d27decd08dd5094 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 17 Feb 2024 21:19:33 +0200 Subject: [PATCH 136/322] add instructions --- vyper/venom/basicblock.py | 1 + vyper/venom/ir_node_to_venom.py | 3 ++- vyper/venom/venom_to_assembly.py | 9 +++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index d96b2e7bc1..5be987705a 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -12,6 +12,7 @@ "alloca", "call", "staticcall", + "delegatecall", "invoke", "sload", "sstore", diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a198ebaf55..7e8f1cff77 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -39,6 +39,7 @@ "smul", "sdiv", "mod", + "smod", "exp", "sha3", "sha3_64", @@ -354,7 +355,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ret = _convert_ir_bb(ctx, ir_node, symbols, variables, allocated_variables) return ret - elif ir.value in ["staticcall", "call"]: # external call + elif ir.value in ["delegatecall", "staticcall", "call"]: idx = 0 gas = _convert_ir_bb(ctx, ir.args[idx], symbols, variables, allocated_variables) address = _convert_ir_bb(ctx, ir.args[idx + 1], symbols, variables, allocated_variables) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index d85890c233..f1f7a792a2 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -67,6 +67,7 @@ "smul", "sdiv", "mod", + "smod", "exp", "addmod", "mulmod", @@ -80,6 +81,10 @@ "create", "create2", "msize", + "balance", + "call", + "staticcall", + "delegatecall", ] ) @@ -438,10 +443,6 @@ def _generate_evm_for_instruction( ] ) self.label_counter += 1 - elif opcode == "call": - assembly.append("CALL") - elif opcode == "staticcall": - assembly.append("STATICCALL") elif opcode == "ret": assembly.append("JUMP") elif opcode == "return": From 850f7af24611da17db53988bc463be0c7a11a23e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 17 Feb 2024 23:28:46 +0200 Subject: [PATCH 137/322] fix const array passing --- vyper/venom/ir_node_to_venom.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 7e8f1cff77..de70c5547f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -175,7 +175,13 @@ def _handle_self_call( ret = bb.append_instruction(arg.location.load_op, ret) ret_args.append(ret) else: - ret_args.append(IRLiteral(arg.value)) + if arg.value == "multi": + seq = ir.args[1] + _convert_ir_bb(ctx, seq, symbols, variables, allocated_variables) + addr = seq.args[0].args[0].value + ret_args.append(addr) + else: + ret_args.append(IRLiteral(arg.value)) else: ret = _convert_ir_bb(ctx, arg._optimized, symbols, variables, allocated_variables) if arg.location and arg.location.load_op == "calldataload": @@ -589,7 +595,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("return", last_ir, ret_ir) else: if last_ir and int(last_ir.value) > 32: - bb.append_instruction("return", ret_ir, last_ir) + bb.append_instruction("return", last_ir, ret_ir) else: ret_buf = 128 # TODO: need allocator new_var = bb.append_instruction("alloca", 32, ret_buf) @@ -639,13 +645,20 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # sym = symbols.get(f"&{arg_0.value}") # if sym is not None: # return sym + bb = ctx.get_basic_block() if isinstance(arg_0, IRLiteral): var = _get_variable_from_address(variables, arg_0.value) if var is not None: avar = allocated_variables.get(var.name) if avar is not None: - return avar - return ctx.get_basic_block().append_instruction("mload", arg_0) + offset = arg_0.value - var.pos + if var.size > 32: + if offset > 0: + avar = bb.append_instruction("add", avar, offset) + return bb.append_instruction("mload", avar) + else: + return avar + return bb.append_instruction("mload", arg_0) elif ir.value == "mstore": arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) if isinstance(arg_1, IRVariable): From 2acf1546d7ba5085e2ba263659104c204fd481f2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 18 Feb 2024 00:01:07 +0200 Subject: [PATCH 138/322] remove unused function --- vyper/venom/ir_node_to_venom.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index de70c5547f..dcd1bc1422 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -82,18 +82,6 @@ SymbolTable = dict[str, Optional[IROperand]] -def _get_symbols_common(a: dict, b: dict) -> dict: - ret = {} - # preserves the ordering in `a` - for k in a.keys(): - if k not in b: - continue - if a[k] == b[k]: - continue - ret[k] = a[k], b[k] - return ret - - # convert IRnode directly to venom def ir_node_to_venom(ir: IRnode) -> IRFunction: ctx = IRFunction() From 85d8ec5e096c431cfde9eb66cceb1c6acd0793ff Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 18 Feb 2024 01:43:47 +0200 Subject: [PATCH 139/322] fix --- vyper/venom/ir_node_to_venom.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index dcd1bc1422..c04a31939d 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -779,6 +779,17 @@ def emit_body_blocks(): elif isinstance(ir.value, str) and ir.value in symbols: return symbols[ir.value] elif ir.is_literal: + if ir.is_pointer: + var = _get_variable_from_address(variables, ir.value) + if var and var.size > 32: + avar = allocated_variables.get(var.name) + if avar: + offset = ir.value - var.pos + if var.size > 32: + if offset > 0: + avar = ctx.get_basic_block().append_instruction("add", avar, offset) + return avar + return IRLiteral(ir.value) else: raise Exception(f"Unknown IR node: {ir}") From af88463463d70fd0d35fce399a61cd0b9b9e76dc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 18 Feb 2024 17:19:24 +0200 Subject: [PATCH 140/322] more instructions --- vyper/venom/venom_to_assembly.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index f1f7a792a2..d54df6db77 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -85,6 +85,9 @@ "call", "staticcall", "delegatecall", + "codesize", + "basefee", + "prevrandao", ] ) From d04ea938e5695ba93bae143f4c2e463b845388f0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 18 Feb 2024 17:19:45 +0200 Subject: [PATCH 141/322] external call logic simplifications --- vyper/venom/ir_node_to_venom.py | 54 ++++++--------------------------- 1 file changed, 10 insertions(+), 44 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c04a31939d..2c35578310 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -70,6 +70,7 @@ "returndatasize", "coinbase", "number", + "prevrandao", "iszero", "not", "calldataload", @@ -77,6 +78,7 @@ "extcodehash", "balance", "msize", + "basefee", ] SymbolTable = dict[str, Optional[IROperand]] @@ -544,51 +546,15 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("stop") return None else: - last_ir = None - ret_var = ir.args[1] - deleted = None - if ret_var.is_literal and symbols.get(f"&{ret_var.value}", None) is not None: - deleted = symbols[f"&{ret_var.value}"] - del symbols[f"&{ret_var.value}"] - for arg in ir.args[2:]: - last_ir = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) - if deleted is not None: - symbols[f"&{ret_var.value}"] = deleted - - ret_ir = _convert_ir_bb(ctx, ret_var, symbols, variables, allocated_variables) - - bb = ctx.get_basic_block() - - var = ( - _get_variable_from_address(variables, int(ret_ir.value)) - if isinstance(ret_ir, IRLiteral) - else None + return_buffer, return_size = _convert_ir_bb_list( + ctx, ir.args[1:], symbols, variables, allocated_variables ) - if var is not None: - allocated_var = allocated_variables.get(var.name, None) - new_var = symbols.get(f"&{ret_ir.value}", allocated_var) # type: ignore - - if var.size and int(var.size) > 32: - offset = int(ret_ir.value) - var.pos # type: ignore - if offset > 0: - ptr_var = bb.append_instruction("add", var.pos, offset) - else: - ptr_var = var.pos - bb.append_instruction("return", last_ir, ptr_var) - else: - new_var = bb.append_instruction(var.location.load_op, ret_ir) - _append_return_for_stack_operand(ctx, symbols, new_var, last_ir) - else: - if isinstance(ret_ir, IRLiteral): - bb.append_instruction("return", last_ir, ret_ir) - else: - if last_ir and int(last_ir.value) > 32: - bb.append_instruction("return", last_ir, ret_ir) - else: - ret_buf = 128 # TODO: need allocator - new_var = bb.append_instruction("alloca", 32, ret_buf) - bb.append_instruction("mstore", ret_ir, new_var) - bb.append_instruction("return", last_ir, new_var) + bb = ctx.get_basic_block() + buffer = allocated_variables.get("return_buffer") + if return_buffer == buffer: + bb.append_instruction("mstore", return_buffer, 0) + return_buffer = 0 + bb.append_instruction("return", return_size, return_buffer) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) From c44ef817ccffcb9c2bdda9febd2979cdf784acaf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 18 Feb 2024 19:43:19 +0200 Subject: [PATCH 142/322] raise, unreachable, etc --- vyper/venom/basicblock.py | 2 ++ vyper/venom/ir_node_to_venom.py | 11 +++++++++-- vyper/venom/venom_to_assembly.py | 6 +++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 5be987705a..bd70e7aadf 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -19,6 +19,7 @@ "iload", "istore", "assert", + "assert_unreachable", "mstore", "mload", "calldatacopy", @@ -46,6 +47,7 @@ "ret", "revert", "assert", + "assert_unreachable", "selfdestruct", "stop", "invalid", diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 2c35578310..ce62a7d70a 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -79,6 +79,7 @@ "balance", "msize", "basefee", + "invalid", ] SymbolTable = dict[str, Optional[IROperand]] @@ -521,8 +522,12 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx.append_data("db", [data]) # type: ignore elif ir.value == "assert": arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) - current_bb = ctx.get_basic_block() - current_bb.append_instruction("assert", arg_0) + bb = ctx.get_basic_block() + bb.append_instruction("assert", arg_0) + elif ir.value == "assert_unreachable": + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) + bb = ctx.get_basic_block() + bb.append_instruction("assert_unreachable", arg_0) elif ir.value == "label": label = IRLabel(ir.args[0].value, True) bb = ctx.get_basic_block() @@ -546,6 +551,8 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("stop") return None else: + bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) + ctx.append_basic_block(bb) return_buffer, return_size = _convert_ir_bb_list( ctx, ir.args[1:], symbols, variables, allocated_variables ) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index d54df6db77..697b7a4251 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,7 +1,7 @@ from typing import Any from vyper.exceptions import CompilerPanic -from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly +from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, mksymbol, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet from vyper.venom.analysis import ( calculate_cfg, @@ -88,6 +88,7 @@ "codesize", "basefee", "prevrandao", + "invalid", ] ) @@ -470,6 +471,9 @@ def _generate_evm_for_instruction( assembly.extend([*PUSH(31), "ADD", *PUSH(31), "NOT", "AND"]) elif opcode == "assert": assembly.extend(["ISZERO", "_sym___revert", "JUMPI"]) + elif opcode == "assert_unreachable": + end_symbol = mksymbol("reachable") + assembly.extend([end_symbol, "JUMPI", "INVALID", end_symbol, "JUMPDEST"]) elif opcode == "iload": addr = inst.operands[0] if isinstance(addr, IRLiteral): From c53afb8036e3358b13eb99be9be9f72d8d231f33 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 19 Feb 2024 00:59:23 +0200 Subject: [PATCH 143/322] more fixes --- vyper/venom/ir_node_to_venom.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index ce62a7d70a..4bd249fe71 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,7 +1,8 @@ from typing import Optional - +from vyper.codegen.core import make_setter +from vyper.evm.address_space import MEMORY +from vyper.semantics.types.subscriptable import TupleT from vyper.codegen.context import VariableRecord -from vyper.codegen.core import is_array_like from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes from vyper.exceptions import CompilerPanic @@ -140,12 +141,18 @@ def _handle_self_call( ) -> Optional[IRVariable]: func_t = ir.passthrough_metadata.get("func_t", None) args_ir = ir.passthrough_metadata["args_ir"] + setup_ir = ir.args[1] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto return_buf = goto_ir.args[1] # return buffer ret_args: list[IROperand] = [IRLabel(target_label)] # type: ignore - for arg in args_ir: + if setup_ir != goto_ir: + _convert_ir_bb(ctx, setup_ir, symbols, variables, allocated_variables) + + arg_buf_start = func_t._ir_info.frame_info.frame_start + + for i, arg in enumerate(args_ir): if arg.is_literal: if arg.is_pointer: var = _get_variable_from_address(variables, arg.value) @@ -153,7 +160,8 @@ def _handle_self_call( ret = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) if isinstance(ret, IRLiteral): bb = ctx.get_basic_block() - ret = bb.append_instruction("mload", ret) + if arg.typ.size_in_bytes > 32: + ret = arg_buf_start + i * 32 ret_args.append(ret) else: if allocated_variables.get(var.name) is not None: @@ -163,7 +171,8 @@ def _handle_self_call( ctx, arg._optimized, symbols, variables, allocated_variables ) bb = ctx.get_basic_block() - ret = bb.append_instruction(arg.location.load_op, ret) + if arg.typ.size_in_bytes <= 32: + ret = bb.append_instruction(arg.location.load_op, ret) ret_args.append(ret) else: if arg.value == "multi": From 231d1d703200495a97e343382bb2b527a3578a48 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 19 Feb 2024 11:38:56 +0200 Subject: [PATCH 144/322] fix and optimization --- vyper/venom/ir_node_to_venom.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 4bd249fe71..536ea5cfea 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -176,9 +176,9 @@ def _handle_self_call( ret_args.append(ret) else: if arg.value == "multi": - seq = ir.args[1] - _convert_ir_bb(ctx, seq, symbols, variables, allocated_variables) - addr = seq.args[0].args[0].value + # seq = ir.args[1] + # _convert_ir_bb(ctx, seq, symbols, variables, allocated_variables) + addr = arg_buf_start + i * 32 ret_args.append(addr) else: ret_args.append(IRLiteral(arg.value)) @@ -560,8 +560,9 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("stop") return None else: - bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) - ctx.append_basic_block(bb) + if bb.is_terminated: + bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) + ctx.append_basic_block(bb) return_buffer, return_size = _convert_ir_bb_list( ctx, ir.args[1:], symbols, variables, allocated_variables ) From 4d4727a461c5ddc2a5e9b85467f506b60e782fc7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 19 Feb 2024 18:31:19 +0200 Subject: [PATCH 145/322] implement loop bounds --- vyper/venom/ir_node_to_venom.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 536ea5cfea..53946e4a8c 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -679,6 +679,12 @@ def emit_body_blocks(): ctx, ir.args[1:4], symbols, variables, allocated_variables ) + assert ir.args[3].is_literal, "repeat bound expected to be literal" + + bound = ir.args[3].value + if isinstance(end, IRLiteral) and end.value <= bound: + bound = None + body = ir.args[4] entry_block = IRBasicBlock(ctx.get_next_label("repeat"), ctx) @@ -701,6 +707,10 @@ def emit_body_blocks(): ctx.append_basic_block(cond_block) ctx.append_basic_block(body_block) + if bound: + xor_ret = body_block.append_instruction("xor", counter_var, bound) + body_block.append_instruction("assert", xor_ret) + emit_body_blocks() body_end = ctx.get_basic_block() From 154fca3b35a772bc927045d0477ad0110ebdfa00 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 19 Feb 2024 19:25:44 +0200 Subject: [PATCH 146/322] finalize repeat --- vyper/venom/ir_node_to_venom.py | 43 ++++++++++++++------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 53946e4a8c..b94f1abe2d 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -658,19 +658,11 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): symbols[f"&{sym.value}"] = new_var return new_var elif ir.value == "repeat": - # - # repeat(sym, start, end, bound, body) - # 1) entry block ] - # 2) init counter block ] -> same block - # 3) condition block (exit block, body block) - # 4) body block - # 5) increment block - # 6) exit block - # TODO: Add the extra bounds check after clarify + def emit_body_blocks(): global _break_target, _continue_target old_targets = _break_target, _continue_target - _break_target, _continue_target = exit_block, increment_block + _break_target, _continue_target = exit_block, incr_block _convert_ir_bb(ctx, body, symbols.copy(), variables, allocated_variables.copy()) _break_target, _continue_target = old_targets @@ -682,7 +674,11 @@ def emit_body_blocks(): assert ir.args[3].is_literal, "repeat bound expected to be literal" bound = ir.args[3].value - if isinstance(end, IRLiteral) and end.value <= bound: + if ( + isinstance(end, IRLiteral) + and isinstance(start, IRLiteral) + and end.value + start.value <= bound + ): bound = None body = ir.args[4] @@ -690,17 +686,19 @@ def emit_body_blocks(): entry_block = IRBasicBlock(ctx.get_next_label("repeat"), ctx) cond_block = IRBasicBlock(ctx.get_next_label("condition"), ctx) body_block = IRBasicBlock(ctx.get_next_label("body"), ctx) - jump_up_block = IRBasicBlock(ctx.get_next_label("jump_up"), ctx) - increment_block = IRBasicBlock(ctx.get_next_label("increment"), ctx) + incr_block = IRBasicBlock(ctx.get_next_label("incr"), ctx) exit_block = IRBasicBlock(ctx.get_next_label("exit"), ctx) bb = ctx.get_basic_block() bb.append_instruction("jmp", entry_block.label) ctx.append_basic_block(entry_block) - counter_var = ctx.get_basic_block().append_instruction("store", start) + counter_var = entry_block.append_instruction("store", start) symbols[sym.value] = counter_var - ctx.get_basic_block().append_instruction("jmp", cond_block.label) + end = entry_block.append_instruction("add", start, end) + if bound: + bound = entry_block.append_instruction("add", start, bound) + entry_block.append_instruction("jmp", cond_block.label) xor_ret = cond_block.append_instruction("xor", counter_var, end) cont_ret = cond_block.append_instruction("iszero", xor_ret) @@ -713,19 +711,14 @@ def emit_body_blocks(): emit_body_blocks() body_end = ctx.get_basic_block() + if body_end.is_terminated is False: + body_end.append_instruction("jmp", incr_block.label) - if not body_end.is_terminated: - body_end.append_instruction("jmp", jump_up_block.label) - - jump_up_block.append_instruction("jmp", increment_block.label) - ctx.append_basic_block(jump_up_block) - - increment_block.insert_instruction( + ctx.append_basic_block(incr_block) + incr_block.insert_instruction( IRInstruction("add", [counter_var, IRLiteral(1)], counter_var) ) - - increment_block.append_instruction("jmp", cond_block.label) - ctx.append_basic_block(increment_block) + incr_block.append_instruction("jmp", cond_block.label) ctx.append_basic_block(exit_block) From 48b7c6c3e825d03134be133f358b3fb69d21b652 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 22 Feb 2024 18:20:50 +0200 Subject: [PATCH 147/322] fix isqrt --- vyper/venom/ir_node_to_venom.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index b94f1abe2d..e95f412055 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -463,8 +463,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx, ir.args[1], symbols, variables, allocated_variables ) # initialization - if isinstance(ret, IRLiteral): - ret = ctx.get_basic_block().append_instruction("store", ret.value) + ret = ctx.get_basic_block().append_instruction("store", ret) # Handle with nesting with same symbol with_symbols = symbols.copy() From e1d5f735f153dc52473f4b4138861fe6bb833b59 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 22 Feb 2024 18:26:40 +0200 Subject: [PATCH 148/322] include postamble --- tests/unit/compiler/venom/test_duplicate_operands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/compiler/venom/test_duplicate_operands.py b/tests/unit/compiler/venom/test_duplicate_operands.py index 437185cc72..c81220ca3d 100644 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ b/tests/unit/compiler/venom/test_duplicate_operands.py @@ -23,5 +23,4 @@ def test_duplicate_operands(): bb.append_instruction("stop") asm = generate_assembly_experimental(ctx, optimize=OptimizationLevel.GAS) - - assert asm == ["PUSH1", 10, "DUP1", "DUP1", "DUP1", "ADD", "MUL", "STOP"] + assert asm == ["PUSH1", 10, "DUP1", "DUP1", "DUP1", "ADD", "MUL", "PUSH0", "DUP1", "REVERT"] From ddf41bca6a4ffc256dee82553a4f7eeded55d97f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 22 Feb 2024 23:03:05 +0200 Subject: [PATCH 149/322] remove unused function --- vyper/venom/ir_node_to_venom.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index e95f412055..e32f57691f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -278,25 +278,6 @@ def _get_variables_from_address_and_size( return ret -def _append_return_for_stack_operand( - ctx: IRFunction, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable -) -> None: - bb = ctx.get_basic_block() - if isinstance(ret_ir, IRLiteral): - sym = symbols.get(f"&{ret_ir.value}", None) - new_var = bb.append_instruction("alloca", 32, ret_ir) - bb.append_instruction("mstore", sym, new_var) # type: ignore - else: - sym = symbols.get(ret_ir.value, None) - if sym is None: - # FIXME: needs real allocations - new_var = bb.append_instruction("alloca", 32, 0) - bb.append_instruction("mstore", ret_ir, new_var) # type: ignore - else: - new_var = ret_ir - bb.append_instruction("return", last_ir, new_var) # type: ignore - - def _convert_ir_bb_list(ctx, ir, symbols, variables, allocated_variables): ret = [] for ir_node in ir: From 7fdf4a59aadcdc91971916f997d38b44e9f7fb58 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 22 Feb 2024 23:04:56 +0200 Subject: [PATCH 150/322] cleanup calldatacopy --- vyper/venom/ir_node_to_venom.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index e32f57691f..a29a85f699 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -474,22 +474,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): arg_0, arg_1, size = _convert_ir_bb_list( ctx, ir.args, symbols, variables, allocated_variables ) - - new_v = arg_0 - bb = ctx.get_basic_block() - if isinstance(arg_0, IRLiteral) and isinstance(size, IRLiteral): - vars = _get_variables_from_address_and_size( - variables, int(arg_0.value), int(size.value) - ) - # for var in vars: - # if allocated_variables.get(var.name, None) is None: - # new_v = IRVariable(var.name) - # ctx.get_basic_block().append_instruction("alloca", var.size, var.pos, ret=new_v) - # allocated_variables[var.name] = new_v - # symbols[f"&{var.pos}"] = new_v - bb.append_instruction("calldatacopy", size, arg_1, arg_0) # type: ignore - return None elif ir.value in ["extcodecopy", "codecopy"]: return _convert_ir_simple_node( From ef408e941d2370d956947378665ff56ad0c2ae3a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 22 Feb 2024 23:06:42 +0200 Subject: [PATCH 151/322] remove unused function --- vyper/venom/ir_node_to_venom.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a29a85f699..ecefcbbb08 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -578,9 +578,6 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # return_buffer special case if allocated_variables.get("return_buffer") == arg_0.name: return arg_0 - # sym = symbols.get(f"&{arg_0.value}") - # if sym is not None: - # return sym bb = ctx.get_basic_block() if isinstance(arg_0, IRLiteral): var = _get_variable_from_address(variables, arg_0.value) @@ -609,14 +606,12 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): cond, a, b = ir.args expanded = IRnode.from_list(["xor", b, ["mul", cond, ["xor", a, b]]]) return _convert_ir_bb(ctx, expanded, symbols, variables, allocated_variables) - elif ir.value in ["iload", "sload"]: arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) return ctx.get_basic_block().append_instruction(ir.value, arg_0) elif ir.value in ["istore", "sstore"]: arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) - elif ir.value == "unique_symbol": sym = ir.args[0] new_var = ctx.get_next_variable() @@ -761,15 +756,3 @@ def _convert_ir_opcode( if isinstance(arg, IRnode): inst_args.append(_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables)) ctx.get_basic_block().append_instruction(opcode, *inst_args) - - -def _data_ofst_of(sym, ofst, height_): - # e.g. _OFST _sym_foo 32 - assert is_symbol(sym) or is_mem_sym(sym) - if isinstance(ofst.value, int): - # resolve at compile time using magic _OFST op - return ["_OFST", sym, ofst.value] - else: - # if we can't resolve at compile time, resolve at runtime - # ofst = _compile_to_assembly(ofst, withargs, existing_labels, break_dest, height_) - return ofst + [sym, "ADD"] From 20bdc0834260352da9242d220e99b4dfc0a752be Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 23 Feb 2024 18:32:50 +0200 Subject: [PATCH 152/322] dynarray fixes --- vyper/venom/ir_node_to_venom.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index ecefcbbb08..c90c104da7 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -150,7 +150,7 @@ def _handle_self_call( if setup_ir != goto_ir: _convert_ir_bb(ctx, setup_ir, symbols, variables, allocated_variables) - arg_buf_start = func_t._ir_info.frame_info.frame_start + arg_buf_pos = func_t._ir_info.frame_info.frame_start for i, arg in enumerate(args_ir): if arg.is_literal: @@ -161,7 +161,8 @@ def _handle_self_call( if isinstance(ret, IRLiteral): bb = ctx.get_basic_block() if arg.typ.size_in_bytes > 32: - ret = arg_buf_start + i * 32 + ret = arg_buf_pos + arg_buf_pos += arg.typ.size_in_bytes ret_args.append(ret) else: if allocated_variables.get(var.name) is not None: @@ -176,9 +177,8 @@ def _handle_self_call( ret_args.append(ret) else: if arg.value == "multi": - # seq = ir.args[1] - # _convert_ir_bb(ctx, seq, symbols, variables, allocated_variables) - addr = arg_buf_start + i * 32 + addr = arg_buf_pos + arg_buf_pos += arg.typ.size_in_bytes ret_args.append(addr) else: ret_args.append(IRLiteral(arg.value)) @@ -474,7 +474,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): arg_0, arg_1, size = _convert_ir_bb_list( ctx, ir.args, symbols, variables, allocated_variables ) - bb.append_instruction("calldatacopy", size, arg_1, arg_0) # type: ignore + ctx.get_basic_block().append_instruction("calldatacopy", size, arg_1, arg_0) # type: ignore return None elif ir.value in ["extcodecopy", "codecopy"]: return _convert_ir_simple_node( From 5e6edd1f66d7b26c8faa0f5eacc2f0293f329d4a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 23 Feb 2024 18:52:53 +0200 Subject: [PATCH 153/322] more dynarray fixes --- vyper/venom/ir_node_to_venom.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c90c104da7..86a9ba7d16 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -162,7 +162,7 @@ def _handle_self_call( bb = ctx.get_basic_block() if arg.typ.size_in_bytes > 32: ret = arg_buf_pos - arg_buf_pos += arg.typ.size_in_bytes + arg_buf_pos += arg.typ.size_in_bytes ret_args.append(ret) else: if allocated_variables.get(var.name) is not None: @@ -177,11 +177,10 @@ def _handle_self_call( ret_args.append(ret) else: if arg.value == "multi": - addr = arg_buf_pos - arg_buf_pos += arg.typ.size_in_bytes - ret_args.append(addr) + ret_args.append(arg_buf_pos) else: ret_args.append(IRLiteral(arg.value)) + arg_buf_pos += arg.typ.size_in_bytes else: ret = _convert_ir_bb(ctx, arg._optimized, symbols, variables, allocated_variables) if arg.location and arg.location.load_op == "calldataload": From 935a2a9f7ad5156750a0e62da8351dbbda561982 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 23 Feb 2024 22:39:57 +0200 Subject: [PATCH 154/322] fix mload handling --- vyper/venom/ir_node_to_venom.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 86a9ba7d16..94ea04e0bb 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -577,10 +577,16 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # return_buffer special case if allocated_variables.get("return_buffer") == arg_0.name: return arg_0 + bb = ctx.get_basic_block() + if isinstance(arg_0, IRVariable): + return bb.append_instruction("mload", arg_0) + if isinstance(arg_0, IRLiteral): var = _get_variable_from_address(variables, arg_0.value) - if var is not None: + if var is not None and var.mutable is True: + # trying to differenciate parameters from + # allocated variables. need to change this. avar = allocated_variables.get(var.name) if avar is not None: offset = arg_0.value - var.pos @@ -724,17 +730,6 @@ def emit_body_blocks(): elif isinstance(ir.value, str) and ir.value in symbols: return symbols[ir.value] elif ir.is_literal: - if ir.is_pointer: - var = _get_variable_from_address(variables, ir.value) - if var and var.size > 32: - avar = allocated_variables.get(var.name) - if avar: - offset = ir.value - var.pos - if var.size > 32: - if offset > 0: - avar = ctx.get_basic_block().append_instruction("add", avar, offset) - return avar - return IRLiteral(ir.value) else: raise Exception(f"Unknown IR node: {ir}") From dbe92023d5b395db19714cdaab8ea67b5cb50f1c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 24 Feb 2024 22:01:03 +0200 Subject: [PATCH 155/322] handle unused return values of volatile call and staticcall --- vyper/venom/venom_to_assembly.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 697b7a4251..0fb677f156 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -264,13 +264,14 @@ def _generate_evm_for_basicblock_r( self._clean_unused_params(asm, basicblock, stack) - for inst in main_insts: + for i, inst in enumerate(main_insts): if is_constructor_cleanup and inst.opcode == "stop": asm.append("_sym__ctor_exit") asm.append("JUMP") continue - asm = self._generate_evm_for_instruction(asm, inst, stack) + next_liveness = main_insts[i + 1].liveness if i + 1 < len(main_insts) else [] + asm = self._generate_evm_for_instruction(asm, inst, stack, next_liveness) for bb in basicblock.reachable: self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) @@ -319,7 +320,7 @@ def clean_stack_from_cfg_in( self.pop(asm, stack) def _generate_evm_for_instruction( - self, assembly: list, inst: IRInstruction, stack: StackModel + self, assembly: list, inst: IRInstruction, stack: StackModel, next_liveness: OrderedSet = [] ) -> list[str]: opcode = inst.opcode @@ -499,6 +500,10 @@ def _generate_evm_for_instruction( if inst.output.mem_type == MemType.MEMORY: assembly.extend([*PUSH(inst.output.mem_addr)]) + # TODO: revisit this + if "call" in inst.opcode and inst.output not in next_liveness: + self.pop(assembly, stack) + return assembly def pop(self, assembly, stack, num=1): From 412a8b1ebcdd4e93c3f624187c66451810a95073 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 25 Feb 2024 00:05:09 +0200 Subject: [PATCH 156/322] for internals called from ctor --- vyper/venom/ir_node_to_venom.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 94ea04e0bb..6c29da661b 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -95,8 +95,14 @@ def ir_node_to_venom(ir: IRnode) -> IRFunction: # a jump. terminate final basic block with STOP. for i, bb in enumerate(ctx.basic_blocks): if not bb.is_terminated: - if i < len(ctx.basic_blocks) - 1: - bb.append_instruction("jmp", ctx.basic_blocks[i + 1].label) + if len(ctx.basic_blocks) - 1 > i: + # TODO: revisit this. When contructor calls internal functions they + # are linked to the last ctor block. Should separate them before this + # so we don't have to handle this here + if ctx.basic_blocks[i + 1].label.value.startswith("internal"): + bb.append_instruction("stop") + else: + bb.append_instruction("jmp", ctx.basic_blocks[i + 1].label) else: bb.append_instruction("stop") From 813ab1081649136aed1821ef36216ec760806d58 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 25 Feb 2024 00:26:35 +0200 Subject: [PATCH 157/322] handle mstore arguments in order the old code expects --- vyper/venom/ir_node_to_venom.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 6c29da661b..b7982e9e8f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -604,7 +604,9 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): return avar return bb.append_instruction("mload", arg_0) elif ir.value == "mstore": - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) + arg_1, arg_0 = _convert_ir_bb_list( + ctx, reversed(ir.args), symbols, variables, allocated_variables + ) if isinstance(arg_1, IRVariable): symbols[f"&{arg_0.value}"] = arg_1 ctx.get_basic_block().append_instruction("mstore", arg_1, arg_0) From 855e72698761a8c7c0246f1daf5d0333d1e13cd3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 25 Feb 2024 11:07:59 +0200 Subject: [PATCH 158/322] more acrobatics for new cconv --- vyper/venom/ir_node_to_venom.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index b7982e9e8f..70ded26a52 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -607,6 +607,14 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): arg_1, arg_0 = _convert_ir_bb_list( ctx, reversed(ir.args), symbols, variables, allocated_variables ) + + if isinstance(arg_0, IRLiteral): + var = _get_variable_from_address(variables, arg_0.value) + if var: + avar = allocated_variables.get(var.name) + if avar: + allocated_variables[var.name] = arg_1 + if isinstance(arg_1, IRVariable): symbols[f"&{arg_0.value}"] = arg_1 ctx.get_basic_block().append_instruction("mstore", arg_1, arg_0) From b94eeeae7cba5ec4b39e2c6d63bc145f5b43a79d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 25 Feb 2024 18:06:30 +0200 Subject: [PATCH 159/322] fix --- vyper/venom/ir_node_to_venom.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 70ded26a52..1bea48c174 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -590,18 +590,16 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if isinstance(arg_0, IRLiteral): var = _get_variable_from_address(variables, arg_0.value) - if var is not None and var.mutable is True: - # trying to differenciate parameters from - # allocated variables. need to change this. - avar = allocated_variables.get(var.name) - if avar is not None: - offset = arg_0.value - var.pos - if var.size > 32: - if offset > 0: - avar = bb.append_instruction("add", avar, offset) - return bb.append_instruction("mload", avar) - else: - return avar + if var is not None: + return bb.append_instruction("mload", arg_0) + avar = symbols.get(f"%{arg_0.value}") + if avar is not None: + offset = arg_0.value - var.pos + if var.size > 32: + if offset > 0: + avar = bb.append_instruction("add", avar, offset) + return bb.append_instruction("mload", avar) + return bb.append_instruction("mload", arg_0) elif ir.value == "mstore": arg_1, arg_0 = _convert_ir_bb_list( From a91ac18ea2c00565c21a125586f8828bc93abde1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 25 Feb 2024 21:08:26 +0200 Subject: [PATCH 160/322] fix --- vyper/venom/ir_node_to_venom.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 1bea48c174..a1d763e186 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -364,16 +364,18 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if isinstance(argsOffset, IRLiteral): offset = int(argsOffset.value) - addr = offset - 32 + 4 if offset > 0 else 0 - argsOffsetVar = symbols.get(f"&{addr}", None) - if argsOffsetVar is None: - argsOffsetVar = argsOffset - elif isinstance(argsOffsetVar, IRVariable): - argsOffsetVar.mem_type = MemType.MEMORY - argsOffsetVar.mem_addr = addr - argsOffsetVar.offset = 32 - 4 if offset > 0 else 0 - else: # pragma: nocover - raise CompilerPanic("unreachable") + var = _get_variable_from_address(variables, offset) + if var: + if var.size > 32: + argsOffsetVar = argsOffset + else: + argsOffsetVar = argsOffset + else: + argsOffsetVar = symbols.get(f"&{offset}", None) + if argsOffsetVar is None: # or offset > 0: + argsOffsetVar = argsOffset + else: # pragma: nocover + argsOffsetVar = argsOffset else: argsOffsetVar = argsOffset From 5dcdfe6158621129575b6a81217d5596fa9e402f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 28 Feb 2024 11:24:47 +0200 Subject: [PATCH 161/322] call conv --- vyper/venom/ir_node_to_venom.py | 84 ++++----------------------------- 1 file changed, 8 insertions(+), 76 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a1d763e186..708a8d8aae 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -150,61 +150,21 @@ def _handle_self_call( setup_ir = ir.args[1] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto - return_buf = goto_ir.args[1] # return buffer + return_buf_ir = goto_ir.args[1] # return buffer ret_args: list[IROperand] = [IRLabel(target_label)] # type: ignore if setup_ir != goto_ir: _convert_ir_bb(ctx, setup_ir, symbols, variables, allocated_variables) - arg_buf_pos = func_t._ir_info.frame_info.frame_start - - for i, arg in enumerate(args_ir): - if arg.is_literal: - if arg.is_pointer: - var = _get_variable_from_address(variables, arg.value) - if var is None: - ret = _convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) - if isinstance(ret, IRLiteral): - bb = ctx.get_basic_block() - if arg.typ.size_in_bytes > 32: - ret = arg_buf_pos - arg_buf_pos += arg.typ.size_in_bytes - ret_args.append(ret) - else: - if allocated_variables.get(var.name) is not None: - ret_args.append(allocated_variables.get(var.name)) - else: - ret = _convert_ir_bb( - ctx, arg._optimized, symbols, variables, allocated_variables - ) - bb = ctx.get_basic_block() - if arg.typ.size_in_bytes <= 32: - ret = bb.append_instruction(arg.location.load_op, ret) - ret_args.append(ret) - else: - if arg.value == "multi": - ret_args.append(arg_buf_pos) - else: - ret_args.append(IRLiteral(arg.value)) - arg_buf_pos += arg.typ.size_in_bytes - else: - ret = _convert_ir_bb(ctx, arg._optimized, symbols, variables, allocated_variables) - if arg.location and arg.location.load_op == "calldataload": - bb = ctx.get_basic_block() - ret = bb.append_instruction(arg.location.load_op, ret) - ret_args.append(ret) + return_buf = _convert_ir_bb(ctx, return_buf_ir, symbols, variables, allocated_variables) bb = ctx.get_basic_block() - do_ret = func_t.return_type is not None - if do_ret: + if func_t.return_type is not None: ret_args.append(return_buf.value) # type: ignore - invoke_ret = bb.append_invoke_instruction(ret_args, returns=True) # type: ignore - if func_t.return_type.size_in_bytes <= 32: - allocated_variables["return_buffer"] = invoke_ret # type: ignore - return invoke_ret - else: - bb.append_invoke_instruction(ret_args, returns=False) # type: ignore - return None + + bb.append_invoke_instruction(ret_args, returns=False) # type: ignore + + return return_buf def _handle_internal_func( @@ -217,16 +177,6 @@ def _handle_internal_func( bb = IRBasicBlock(IRLabel(ir.args[0].args[0].value, True), ctx) # type: ignore bb = ctx.append_basic_block(bb) - old_ir_mempos = 0 - old_ir_mempos += 64 - - for arg in func_t.arguments: - new_var = bb.append_instruction("param") - symbols[f"&{old_ir_mempos}"] = new_var - allocated_variables[arg.name] = new_var - bb.instructions[-1].annotation = arg.name - old_ir_mempos += 32 # arg.typ.memory_bytes_required - # return buffer if func_t.return_type is not None: symbols["return_buffer"] = bb.append_instruction("param") @@ -269,25 +219,10 @@ def _get_variable_from_address( return None -def _get_variables_from_address_and_size( - variables: OrderedSet[VariableRecord], addr: int, size: int -) -> list[VariableRecord]: - assert isinstance(addr, int), "non-int address" - addr_end = addr + size - ret = [] - for var in variables.keys(): - if var.location.name != "memory": - continue - if var.pos >= addr and var.pos + var.size <= addr_end: # type: ignore - ret.append(var) - return ret - - def _convert_ir_bb_list(ctx, ir, symbols, variables, allocated_variables): ret = [] for ir_node in ir: venom = _convert_ir_bb(ctx, ir_node, symbols, variables, allocated_variables) - assert venom is not None, ir_node ret.append(venom) return ret @@ -539,10 +474,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx, ir.args[1:], symbols, variables, allocated_variables ) bb = ctx.get_basic_block() - buffer = allocated_variables.get("return_buffer") - if return_buffer == buffer: - bb.append_instruction("mstore", return_buffer, 0) - return_buffer = 0 + assert return_buffer is not None bb.append_instruction("return", return_size, return_buffer) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) From 5505b9bea53207e84ed8163dbb08c9b95a462285 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 28 Feb 2024 14:24:30 +0200 Subject: [PATCH 162/322] old call conv --- vyper/venom/basicblock.py | 2 ++ vyper/venom/ir_node_to_venom.py | 10 ++-------- vyper/venom/venom_to_assembly.py | 9 +++------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index bd70e7aadf..0d275604ef 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -423,6 +423,8 @@ def append_invoke_instruction( # Wrap raw integers in IRLiterals inst_args = [_ir_operand_from_value(arg) for arg in args] + assert isinstance(inst_args[0], IRLabel), "Invoked non label" + inst = IRInstruction("invoke", inst_args, ret) inst.parent = self self.instructions.append(inst) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 708a8d8aae..b38e0cab6b 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -481,14 +481,8 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): elif func_t.is_internal: assert ir.args[1].value == "return_pc", "return_pc not found" - if func_t.return_type is None: - bb.append_instruction("ret", symbols["return_pc"]) - else: - if func_t.return_type.memory_bytes_required > 32: - bb.append_instruction("ret", symbols["return_buffer"], symbols["return_pc"]) - else: - ret_by_value = bb.append_instruction("mload", symbols["return_buffer"]) - bb.append_instruction("ret", ret_by_value, symbols["return_pc"]) + # TODO: never passing return values with the new convention + bb.append_instruction("ret", symbols["return_pc"]) elif ir.value == "revert": arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 0fb677f156..a7741354a4 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -437,7 +437,9 @@ def _generate_evm_for_instruction( assembly.append("LT") elif opcode == "invoke": target = inst.operands[0] - assert isinstance(target, IRLabel), "invoke target must be a label" + assert isinstance( + target, IRLabel + ), f"invoke target must be a label (is ${type(target)} ${target})" assembly.extend( [ f"_sym_label_ret_{self.label_counter}", @@ -496,11 +498,6 @@ def _generate_evm_for_instruction( # Step 6: Emit instructions output operands (if any) if inst.output is not None: - assert isinstance(inst.output, IRVariable), "Return value must be a variable" - if inst.output.mem_type == MemType.MEMORY: - assembly.extend([*PUSH(inst.output.mem_addr)]) - - # TODO: revisit this if "call" in inst.opcode and inst.output not in next_liveness: self.pop(assembly, stack) From 4f8dbecaa61c2883323c78daf67cdd94769535eb Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 15:14:50 +0200 Subject: [PATCH 163/322] more fixes --- vyper/venom/basicblock.py | 2 ++ vyper/venom/ir_node_to_venom.py | 5 +---- vyper/venom/venom_to_assembly.py | 9 +-------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 0d275604ef..29aed0ca4b 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -23,6 +23,7 @@ "mstore", "mload", "calldatacopy", + "returndatacopy", "codecopy", "dloadbytes", "dload", @@ -41,6 +42,7 @@ "istore", "dloadbytes", "calldatacopy", + "returndatacopy", "codecopy", "extcodecopy", "return", diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index b38e0cab6b..c8c4e80983 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -650,10 +650,7 @@ def emit_body_blocks(): ctx, ir.args, symbols, variables, allocated_variables ) - new_var = ctx.get_basic_block().append_instruction("returndatacopy", arg_1, size) - - symbols[f"&{arg_0.value}"] = new_var - return new_var + ctx.get_basic_block().append_instruction("returndatacopy", size, arg_1, arg_0) elif ir.value == "selfdestruct": arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) ctx.get_basic_block().append_instruction("selfdestruct", arg_0) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index a7741354a4..0a4fe2c28c 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -223,19 +223,12 @@ def _emit_input_operands( stack.push(op) continue - if op in inst.dup_requirements: + if op in inst.dup_requirements and op not in emitted_ops: self.dup_op(assembly, stack, op) if op in emitted_ops: self.dup_op(assembly, stack, op) - # REVIEW: this seems like it can be reordered across volatile - # boundaries (which includes memory fences). maybe just - # remove it entirely at this point - if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: - assembly.extend([*PUSH(op.mem_addr)]) - assembly.append("MLOAD") - emitted_ops.add(op) def _generate_evm_for_basicblock_r( From a9ceb5a33b2ff00657a99d094993da4f5dbf5859 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 15:26:38 +0200 Subject: [PATCH 164/322] fix test --- tests/unit/compiler/venom/test_duplicate_operands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/compiler/venom/test_duplicate_operands.py b/tests/unit/compiler/venom/test_duplicate_operands.py index c81220ca3d..66998619bd 100644 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ b/tests/unit/compiler/venom/test_duplicate_operands.py @@ -23,4 +23,4 @@ def test_duplicate_operands(): bb.append_instruction("stop") asm = generate_assembly_experimental(ctx, optimize=OptimizationLevel.GAS) - assert asm == ["PUSH1", 10, "DUP1", "DUP1", "DUP1", "ADD", "MUL", "PUSH0", "DUP1", "REVERT"] + assert asm == ["PUSH1", 10, "DUP1", "DUP1", "ADD", "MUL", "PUSH0", "DUP1", "REVERT"] From f6bcb25adb6695d455989e35f6a07e746496ef7c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 15:35:44 +0200 Subject: [PATCH 165/322] add difficulty instruction --- vyper/venom/ir_node_to_venom.py | 1 + vyper/venom/venom_to_assembly.py | 1 + 2 files changed, 2 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c8c4e80983..fcb5b85fb7 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -72,6 +72,7 @@ "coinbase", "number", "prevrandao", + "difficulty", "iszero", "not", "calldataload", diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 0a4fe2c28c..bb11e3f29b 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -88,6 +88,7 @@ "codesize", "basefee", "prevrandao", + "difficulty", "invalid", ] ) From 3a3174ba1b5819e731ad3c9e4539e6f34c52c186 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 16:17:54 +0200 Subject: [PATCH 166/322] func_t cleanup --- vyper/venom/ir_node_to_venom.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index fcb5b85fb7..53b1e62d54 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -146,8 +146,6 @@ def _handle_self_call( variables: OrderedSet, allocated_variables: dict[str, IRVariable], ) -> Optional[IRVariable]: - func_t = ir.passthrough_metadata.get("func_t", None) - args_ir = ir.passthrough_metadata["args_ir"] setup_ir = ir.args[1] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto @@ -160,7 +158,7 @@ def _handle_self_call( return_buf = _convert_ir_bb(ctx, return_buf_ir, symbols, variables, allocated_variables) bb = ctx.get_basic_block() - if func_t.return_type is not None: + if len(goto_ir.args) > 2: ret_args.append(return_buf.value) # type: ignore bb.append_invoke_instruction(ret_args, returns=False) # type: ignore @@ -171,15 +169,14 @@ def _handle_self_call( def _handle_internal_func( ctx: IRFunction, ir: IRnode, - func_t: ContractFunctionT, + does_return_data: bool, symbols: SymbolTable, - allocated_variables: dict[str, IRVariable], ) -> IRnode: bb = IRBasicBlock(IRLabel(ir.args[0].args[0].value, True), ctx) # type: ignore bb = ctx.append_basic_block(bb) # return buffer - if func_t.return_type is not None: + if does_return_data: symbols["return_buffer"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_buffer" @@ -265,17 +262,24 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx.immutables_len = ir.args[2].value return None elif ir.value == "seq": + if len(ir.args) == 0: + return None func_t = ir.passthrough_metadata.get("func_t", None) if ir.is_self_call: return _handle_self_call(ctx, ir, symbols, variables, allocated_variables) - elif func_t is not None: + # elif func_t is not None: + elif ir.args[0].value == "label" and ir.args[0].args[0].value.startswith("internal"): + # Internal definition + var_list = ir.args[0].args[1] + does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args + assert does_return_data == (func_t.return_type != None) symbols = {} allocated_variables = {} variables = OrderedSet( {v: True for v in ir.passthrough_metadata["frame_info"].frame_vars.values()} ) if func_t.is_internal: - ir = _handle_internal_func(ctx, ir, func_t, symbols, allocated_variables) + ir = _handle_internal_func(ctx, ir, does_return_data, symbols) # fallthrough ret = None From 63aa60b21b56d982e98121ee01f5390a11110c27 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 16:36:52 +0200 Subject: [PATCH 167/322] more func_t cleanup --- vyper/venom/ir_node_to_venom.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 53b1e62d54..dc2802cc69 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -264,22 +264,18 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "seq": if len(ir.args) == 0: return None - func_t = ir.passthrough_metadata.get("func_t", None) if ir.is_self_call: return _handle_self_call(ctx, ir, symbols, variables, allocated_variables) - # elif func_t is not None: elif ir.args[0].value == "label" and ir.args[0].args[0].value.startswith("internal"): # Internal definition var_list = ir.args[0].args[1] does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args - assert does_return_data == (func_t.return_type != None) symbols = {} allocated_variables = {} variables = OrderedSet( {v: True for v in ir.passthrough_metadata["frame_info"].frame_vars.values()} ) - if func_t.is_internal: - ir = _handle_internal_func(ctx, ir, does_return_data, symbols) + ir = _handle_internal_func(ctx, ir, does_return_data, symbols) # fallthrough ret = None @@ -461,13 +457,22 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): func_t = ir.passthrough_metadata.get("func_t", None) assert func_t is not None, "exit_to without func_t" + label = ir.args[0] + + is_constructor = "__init__(" in label.value + is_external = label.value.startswith("external") and not is_constructor + is_internal = label.value.startswith("internal") + + # assert label.value.startswith("external") == func_t.is_external, label + # assert label.value.startswith("internal") == func_t.is_internal, label + bb = ctx.get_basic_block() - if func_t.is_external: + if is_external: # Hardcoded contructor special case - if func_t.is_constructor: - label = IRLabel(ir.args[0].value, True) - bb.append_instruction("jmp", label) - return None + # if func_t.is_constructor: + # label = IRLabel(ir.args[0].value, True) + # bb.append_instruction("jmp", label) + # return None if func_t.return_type is None: bb.append_instruction("stop") return None @@ -484,7 +489,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) - elif func_t.is_internal: + elif is_internal: assert ir.args[1].value == "return_pc", "return_pc not found" # TODO: never passing return values with the new convention bb.append_instruction("ret", symbols["return_pc"]) From b0fecda03d09d2d8ca0714485dc41cdcd575532f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 16:46:27 +0200 Subject: [PATCH 168/322] get rid of passthrough_metadata --- .../function_definitions/external_function.py | 4 ---- .../function_definitions/internal_function.py | 4 ---- vyper/codegen/ir_node.py | 6 ----- vyper/codegen/return_.py | 1 - vyper/codegen/self_call.py | 2 -- vyper/ir/optimizer.py | 2 -- vyper/venom/ir_node_to_venom.py | 22 ++----------------- 7 files changed, 2 insertions(+), 39 deletions(-) diff --git a/vyper/codegen/function_definitions/external_function.py b/vyper/codegen/function_definitions/external_function.py index b380eab2ce..460d060a64 100644 --- a/vyper/codegen/function_definitions/external_function.py +++ b/vyper/codegen/function_definitions/external_function.py @@ -154,10 +154,6 @@ def _adjust_gas_estimate(func_t, common_ir): common_ir.add_gas_estimate += mem_expansion_cost func_t._ir_info.gas_estimate = common_ir.gas - # pass metadata through for venom pipeline: - common_ir.passthrough_metadata["func_t"] = func_t - common_ir.passthrough_metadata["frame_info"] = frame_info - def generate_ir_for_external_function(code, compilation_target): # TODO type hints: diff --git a/vyper/codegen/function_definitions/internal_function.py b/vyper/codegen/function_definitions/internal_function.py index 0cf9850b70..3ae7438892 100644 --- a/vyper/codegen/function_definitions/internal_function.py +++ b/vyper/codegen/function_definitions/internal_function.py @@ -82,8 +82,4 @@ def generate_ir_for_internal_function( func_t._ir_info.gas_estimate = ir_node.gas frame_info = tag_frame_info(func_t, context) - # pass metadata through for venom pipeline: - ir_node.passthrough_metadata["frame_info"] = frame_info - ir_node.passthrough_metadata["func_t"] = func_t - return InternalFuncIR(ir_node) diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index b1a71021c8..30668e5dff 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -173,7 +173,6 @@ class IRnode: args: List["IRnode"] value: Union[str, int] is_self_call: bool - passthrough_metadata: dict[str, Any] func_ir: Any common_ir: Any @@ -190,7 +189,6 @@ def __init__( add_gas_estimate: int = 0, encoding: Encoding = Encoding.VYPER, is_self_call: bool = False, - passthrough_metadata: dict[str, Any] = None, ): if args is None: args = [] @@ -209,7 +207,6 @@ def __init__( self.encoding = encoding self.as_hex = AS_HEX_DEFAULT self.is_self_call = is_self_call - self.passthrough_metadata = passthrough_metadata or {} self.func_ir = None self.common_ir = None @@ -609,7 +606,6 @@ def from_list( mutable: bool = True, add_gas_estimate: int = 0, is_self_call: bool = False, - passthrough_metadata: dict[str, Any] = None, encoding: Encoding = Encoding.VYPER, ) -> "IRnode": if isinstance(typ, str): @@ -643,7 +639,6 @@ def from_list( encoding=encoding, error_msg=error_msg, is_self_call=is_self_call, - passthrough_metadata=passthrough_metadata, ) else: return cls( @@ -658,5 +653,4 @@ def from_list( encoding=encoding, error_msg=error_msg, is_self_call=is_self_call, - passthrough_metadata=passthrough_metadata, ) diff --git a/vyper/codegen/return_.py b/vyper/codegen/return_.py index 41fa11ab56..e1f48d4991 100644 --- a/vyper/codegen/return_.py +++ b/vyper/codegen/return_.py @@ -41,7 +41,6 @@ def finalize(fill_return_buffer): # NOTE: because stack analysis is incomplete, cleanup_repeat must # come after fill_return_buffer otherwise the stack will break jump_to_exit_ir = IRnode.from_list(jump_to_exit) - jump_to_exit_ir.passthrough_metadata["func_t"] = func_t return IRnode.from_list(["seq", fill_return_buffer, cleanup_loops, jump_to_exit_ir]) if context.return_type is None: diff --git a/vyper/codegen/self_call.py b/vyper/codegen/self_call.py index f53e4a81b4..2363de3641 100644 --- a/vyper/codegen/self_call.py +++ b/vyper/codegen/self_call.py @@ -112,6 +112,4 @@ def ir_for_self_call(stmt_expr, context): add_gas_estimate=func_t._ir_info.gas_estimate, ) o.is_self_call = True - o.passthrough_metadata["func_t"] = func_t - o.passthrough_metadata["args_ir"] = args_ir return o diff --git a/vyper/ir/optimizer.py b/vyper/ir/optimizer.py index 79e02f041d..33ed8aadba 100644 --- a/vyper/ir/optimizer.py +++ b/vyper/ir/optimizer.py @@ -441,7 +441,6 @@ def _optimize(node: IRnode, parent: Optional[IRnode]) -> Tuple[bool, IRnode]: annotation = node.annotation add_gas_estimate = node.add_gas_estimate is_self_call = node.is_self_call - passthrough_metadata = node.passthrough_metadata changed = False @@ -465,7 +464,6 @@ def finalize(val, args): annotation=annotation, add_gas_estimate=add_gas_estimate, is_self_call=is_self_call, - passthrough_metadata=passthrough_metadata, ) if should_check_symbols: diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index dc2802cc69..0761d929af 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -230,11 +230,6 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): assert isinstance(variables, OrderedSet) global _break_target, _continue_target - frame_info = ir.passthrough_metadata.get("frame_info", None) - if frame_info is not None: - local_vars = OrderedSet[VariableRecord](frame_info.frame_vars.values()) - variables |= local_vars - assert isinstance(variables, OrderedSet) if ir.value in _BINARY_IR_INSTRUCTIONS: @@ -272,9 +267,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args symbols = {} allocated_variables = {} - variables = OrderedSet( - {v: True for v in ir.passthrough_metadata["frame_info"].frame_vars.values()} - ) + variables = OrderedSet() ir = _handle_internal_func(ctx, ir, does_return_data, symbols) # fallthrough @@ -454,26 +447,15 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx.append_basic_block(bb) _convert_ir_bb(ctx, ir.args[2], symbols, variables, allocated_variables) elif ir.value == "exit_to": - func_t = ir.passthrough_metadata.get("func_t", None) - assert func_t is not None, "exit_to without func_t" - label = ir.args[0] is_constructor = "__init__(" in label.value is_external = label.value.startswith("external") and not is_constructor is_internal = label.value.startswith("internal") - # assert label.value.startswith("external") == func_t.is_external, label - # assert label.value.startswith("internal") == func_t.is_internal, label - bb = ctx.get_basic_block() if is_external: - # Hardcoded contructor special case - # if func_t.is_constructor: - # label = IRLabel(ir.args[0].value, True) - # bb.append_instruction("jmp", label) - # return None - if func_t.return_type is None: + if len(ir.args) == 1: # no return value bb.append_instruction("stop") return None else: From 4ca5bcbf462e6b4bb5031b5b79331219fd4d1e06 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 17:00:25 +0200 Subject: [PATCH 169/322] remove get_variable --- vyper/venom/ir_node_to_venom.py | 39 +++++---------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 0761d929af..7269718ca5 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -205,18 +205,6 @@ def _convert_ir_simple_node( _continue_target: Optional[IRBasicBlock] = None -def _get_variable_from_address( - variables: OrderedSet[VariableRecord], addr: int -) -> Optional[VariableRecord]: - assert isinstance(addr, int), "non-int address" - for var in variables.keys(): - if var.location.name != "memory": - continue - if addr >= var.pos and addr < var.pos + var.size: # type: ignore - return var - return None - - def _convert_ir_bb_list(ctx, ir, symbols, variables, allocated_variables): ret = [] for ir_node in ir: @@ -293,18 +281,11 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if isinstance(argsOffset, IRLiteral): offset = int(argsOffset.value) - var = _get_variable_from_address(variables, offset) - if var: - if var.size > 32: - argsOffsetVar = argsOffset - else: - argsOffsetVar = argsOffset - else: - argsOffsetVar = symbols.get(f"&{offset}", None) - if argsOffsetVar is None: # or offset > 0: - argsOffsetVar = argsOffset - else: # pragma: nocover - argsOffsetVar = argsOffset + argsOffsetVar = symbols.get(f"&{offset}", None) + if argsOffsetVar is None: # or offset > 0: + argsOffsetVar = argsOffset + else: # pragma: nocover + argsOffsetVar = argsOffset else: argsOffsetVar = argsOffset @@ -509,9 +490,6 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): return bb.append_instruction("mload", arg_0) if isinstance(arg_0, IRLiteral): - var = _get_variable_from_address(variables, arg_0.value) - if var is not None: - return bb.append_instruction("mload", arg_0) avar = symbols.get(f"%{arg_0.value}") if avar is not None: offset = arg_0.value - var.pos @@ -526,13 +504,6 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): ctx, reversed(ir.args), symbols, variables, allocated_variables ) - if isinstance(arg_0, IRLiteral): - var = _get_variable_from_address(variables, arg_0.value) - if var: - avar = allocated_variables.get(var.name) - if avar: - allocated_variables[var.name] = arg_1 - if isinstance(arg_1, IRVariable): symbols[f"&{arg_0.value}"] = arg_1 ctx.get_basic_block().append_instruction("mstore", arg_1, arg_0) From a1d21109130a62751a437092300bb38933ee71f6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 17:03:59 +0200 Subject: [PATCH 170/322] remove variables --- vyper/venom/ir_node_to_venom.py | 138 ++++++++++++-------------------- 1 file changed, 50 insertions(+), 88 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 7269718ca5..9f5adda2ae 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -90,7 +90,7 @@ # convert IRnode directly to venom def ir_node_to_venom(ir: IRnode) -> IRFunction: ctx = IRFunction() - _convert_ir_bb(ctx, ir, {}, OrderedSet(), {}) + _convert_ir_bb(ctx, ir, {}, {}) # Patch up basic blocks. Connect unterminated blocks to the next with # a jump. terminate final basic block with STOP. @@ -114,12 +114,11 @@ def _convert_binary_op( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, - variables: OrderedSet, allocated_variables: dict[str, IRVariable], swap: bool = False, ) -> Optional[IRVariable]: ir_args = ir.args[::-1] if swap else ir.args - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir_args, symbols, variables, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir_args, symbols, allocated_variables) assert isinstance(ir.value, str) # mypy hint return ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) @@ -143,7 +142,6 @@ def _handle_self_call( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, - variables: OrderedSet, allocated_variables: dict[str, IRVariable], ) -> Optional[IRVariable]: setup_ir = ir.args[1] @@ -153,9 +151,9 @@ def _handle_self_call( ret_args: list[IROperand] = [IRLabel(target_label)] # type: ignore if setup_ir != goto_ir: - _convert_ir_bb(ctx, setup_ir, symbols, variables, allocated_variables) + _convert_ir_bb(ctx, setup_ir, symbols, allocated_variables) - return_buf = _convert_ir_bb(ctx, return_buf_ir, symbols, variables, allocated_variables) + return_buf = _convert_ir_bb(ctx, return_buf_ir, symbols, allocated_variables) bb = ctx.get_basic_block() if len(goto_ir.args) > 2: @@ -191,11 +189,10 @@ def _convert_ir_simple_node( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, - variables: OrderedSet, allocated_variables: dict[str, IRVariable], reverse: bool = False, ) -> Optional[IRVariable]: - args = [_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args] + args = [_convert_ir_bb(ctx, arg, symbols, allocated_variables) for arg in ir.args] if reverse: args = reversed(args) return ctx.get_basic_block().append_instruction(ir.value, *args) # type: ignore @@ -205,38 +202,33 @@ def _convert_ir_simple_node( _continue_target: Optional[IRBasicBlock] = None -def _convert_ir_bb_list(ctx, ir, symbols, variables, allocated_variables): +def _convert_ir_bb_list(ctx, ir, symbols, allocated_variables): ret = [] for ir_node in ir: - venom = _convert_ir_bb(ctx, ir_node, symbols, variables, allocated_variables) + venom = _convert_ir_bb(ctx, ir_node, symbols, allocated_variables) ret.append(venom) return ret -def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): +def _convert_ir_bb(ctx, ir, symbols, allocated_variables): assert isinstance(ir, IRnode), ir - assert isinstance(variables, OrderedSet) global _break_target, _continue_target - assert isinstance(variables, OrderedSet) - if ir.value in _BINARY_IR_INSTRUCTIONS: - return _convert_binary_op( - ctx, ir, symbols, variables, allocated_variables, ir.value in ["sha3_64"] - ) + return _convert_binary_op(ctx, ir, symbols, allocated_variables, ir.value in ["sha3_64"]) elif ir.value in INVERSE_MAPPED_IR_INSTRUCTIONS: org_value = ir.value ir.value = INVERSE_MAPPED_IR_INSTRUCTIONS[ir.value] - new_var = _convert_binary_op(ctx, ir, symbols, variables, allocated_variables) + new_var = _convert_binary_op(ctx, ir, symbols, allocated_variables) ir.value = org_value return ctx.get_basic_block().append_instruction("iszero", new_var) elif ir.value in PASS_THROUGH_INSTRUCTIONS: - return _convert_ir_simple_node(ctx, ir, symbols, variables, allocated_variables) + return _convert_ir_simple_node(ctx, ir, symbols, allocated_variables) elif ir.value in ["addmod", "mulmod"]: - return _convert_ir_simple_node(ctx, ir, symbols, variables, allocated_variables, True) + return _convert_ir_simple_node(ctx, ir, symbols, allocated_variables, True) elif ir.value in ["pass", "stop", "return"]: pass @@ -248,35 +240,34 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if len(ir.args) == 0: return None if ir.is_self_call: - return _handle_self_call(ctx, ir, symbols, variables, allocated_variables) + return _handle_self_call(ctx, ir, symbols, allocated_variables) elif ir.args[0].value == "label" and ir.args[0].args[0].value.startswith("internal"): # Internal definition var_list = ir.args[0].args[1] does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args symbols = {} allocated_variables = {} - variables = OrderedSet() ir = _handle_internal_func(ctx, ir, does_return_data, symbols) # fallthrough ret = None for ir_node in ir.args: # NOTE: skip the last one - ret = _convert_ir_bb(ctx, ir_node, symbols, variables, allocated_variables) + ret = _convert_ir_bb(ctx, ir_node, symbols, allocated_variables) return ret elif ir.value in ["delegatecall", "staticcall", "call"]: idx = 0 - gas = _convert_ir_bb(ctx, ir.args[idx], symbols, variables, allocated_variables) - address = _convert_ir_bb(ctx, ir.args[idx + 1], symbols, variables, allocated_variables) + gas = _convert_ir_bb(ctx, ir.args[idx], symbols, allocated_variables) + address = _convert_ir_bb(ctx, ir.args[idx + 1], symbols, allocated_variables) value = None if ir.value == "call": - value = _convert_ir_bb(ctx, ir.args[idx + 2], symbols, variables, allocated_variables) + value = _convert_ir_bb(ctx, ir.args[idx + 2], symbols, allocated_variables) else: idx -= 1 argsOffset, argsSize, retOffset, retSize = _convert_ir_bb_list( - ctx, ir.args[idx + 3 : idx + 7], symbols, variables, allocated_variables + ctx, ir.args[idx + 3 : idx + 7], symbols, allocated_variables ) if isinstance(argsOffset, IRLiteral): @@ -299,11 +290,10 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): cond = ir.args[0] # convert the condition - cont_ret = _convert_ir_bb(ctx, cond, symbols, variables, allocated_variables) + cont_ret = _convert_ir_bb(ctx, cond, symbols, allocated_variables) cond_block = ctx.get_basic_block() cond_symbols = symbols.copy() - cond_variables = variables.copy() cond_allocated_variables = allocated_variables.copy() else_block = IRBasicBlock(ctx.get_next_label("else"), ctx) @@ -312,9 +302,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # convert "else" else_ret_val = None if len(ir.args) == 3: - else_ret_val = _convert_ir_bb( - ctx, ir.args[2], cond_symbols, cond_variables, cond_allocated_variables - ) + else_ret_val = _convert_ir_bb(ctx, ir.args[2], cond_symbols, cond_allocated_variables) if isinstance(else_ret_val, IRLiteral): assert isinstance(else_ret_val.value, int) # help mypy else_ret_val = ctx.get_basic_block().append_instruction("store", else_ret_val) @@ -323,15 +311,12 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): # convert "then" cond_symbols = symbols.copy() - cond_variables = variables.copy() cond_allocated_variables = allocated_variables.copy() then_block = IRBasicBlock(ctx.get_next_label("then"), ctx) ctx.append_basic_block(then_block) - then_ret_val = _convert_ir_bb( - ctx, ir.args[1], cond_symbols, cond_variables, cond_allocated_variables - ) + then_ret_val = _convert_ir_bb(ctx, ir.args[1], cond_symbols, cond_allocated_variables) if isinstance(then_ret_val, IRLiteral): then_ret_val = ctx.get_basic_block().append_instruction("store", then_ret_val) @@ -357,9 +342,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): return if_ret elif ir.value == "with": - ret = _convert_ir_bb( - ctx, ir.args[1], symbols, variables, allocated_variables - ) # initialization + ret = _convert_ir_bb(ctx, ir.args[1], symbols, allocated_variables) # initialization ret = ctx.get_basic_block().append_instruction("store", ret) @@ -371,32 +354,26 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): with_allocated_variables[sym.value] = ret with_symbols[sym.value] = ret - return _convert_ir_bb( - ctx, ir.args[2], with_symbols, variables, with_allocated_variables - ) # body + return _convert_ir_bb(ctx, ir.args[2], with_symbols, with_allocated_variables) # body elif ir.value == "goto": _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "djump": - args = [_convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables)] + args = [_convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables)] for target in ir.args[1:]: args.append(IRLabel(target.value)) ctx.get_basic_block().append_instruction("djmp", *args) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] - arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols, variables, allocated_variables) + arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols, allocated_variables) ctx.get_basic_block().append_instruction("store", arg_1, ret=allocated_variables[sym.value]) elif ir.value == "calldatacopy": - arg_0, arg_1, size = _convert_ir_bb_list( - ctx, ir.args, symbols, variables, allocated_variables - ) + arg_0, arg_1, size = _convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables) ctx.get_basic_block().append_instruction("calldatacopy", size, arg_1, arg_0) # type: ignore return None elif ir.value in ["extcodecopy", "codecopy"]: - return _convert_ir_simple_node( - ctx, ir, symbols, variables, allocated_variables, reverse=True - ) + return _convert_ir_simple_node(ctx, ir, symbols, allocated_variables, reverse=True) elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) elif ir.value == "data": @@ -409,14 +386,14 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): elif isinstance(c, bytes): ctx.append_data("db", [c]) # type: ignore elif isinstance(c, IRnode): - data = _convert_ir_bb(ctx, c, symbols, variables, allocated_variables) + data = _convert_ir_bb(ctx, c, symbols, allocated_variables) ctx.append_data("db", [data]) # type: ignore elif ir.value == "assert": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) bb = ctx.get_basic_block() bb.append_instruction("assert", arg_0) elif ir.value == "assert_unreachable": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) bb = ctx.get_basic_block() bb.append_instruction("assert_unreachable", arg_0) elif ir.value == "label": @@ -426,7 +403,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("jmp", label) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) - _convert_ir_bb(ctx, ir.args[2], symbols, variables, allocated_variables) + _convert_ir_bb(ctx, ir.args[2], symbols, allocated_variables) elif ir.value == "exit_to": label = ir.args[0] @@ -444,7 +421,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) ctx.append_basic_block(bb) return_buffer, return_size = _convert_ir_bb_list( - ctx, ir.args[1:], symbols, variables, allocated_variables + ctx, ir.args[1:], symbols, allocated_variables ) bb = ctx.get_basic_block() assert return_buffer is not None @@ -458,11 +435,11 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("ret", symbols["return_pc"]) elif ir.value == "revert": - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables) ctx.get_basic_block().append_instruction("revert", arg_1, arg_0) elif ir.value == "dload": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) bb = ctx.get_basic_block() src = bb.append_instruction("add", arg_0, IRLabel("code_end")) @@ -470,9 +447,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): return bb.append_instruction("mload", MemoryPositions.FREE_VAR_SPACE) elif ir.value == "dloadbytes": - dst, src_offset, len_ = _convert_ir_bb_list( - ctx, ir.args, symbols, variables, allocated_variables - ) + dst, src_offset, len_ = _convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables) bb = ctx.get_basic_block() src = bb.append_instruction("add", src_offset, IRLabel("code_end")) @@ -480,7 +455,7 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): return None elif ir.value == "mload": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) # return_buffer special case if allocated_variables.get("return_buffer") == arg_0.name: return arg_0 @@ -492,17 +467,11 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): if isinstance(arg_0, IRLiteral): avar = symbols.get(f"%{arg_0.value}") if avar is not None: - offset = arg_0.value - var.pos - if var.size > 32: - if offset > 0: - avar = bb.append_instruction("add", avar, offset) - return bb.append_instruction("mload", avar) + return bb.append_instruction("mload", avar) return bb.append_instruction("mload", arg_0) elif ir.value == "mstore": - arg_1, arg_0 = _convert_ir_bb_list( - ctx, reversed(ir.args), symbols, variables, allocated_variables - ) + arg_1, arg_0 = _convert_ir_bb_list(ctx, reversed(ir.args), symbols, allocated_variables) if isinstance(arg_1, IRVariable): symbols[f"&{arg_0.value}"] = arg_1 @@ -510,17 +479,17 @@ def _convert_ir_bb(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "ceil32": x = ir.args[0] expanded = IRnode.from_list(["and", ["add", x, 31], ["not", 31]]) - return _convert_ir_bb(ctx, expanded, symbols, variables, allocated_variables) + return _convert_ir_bb(ctx, expanded, symbols, allocated_variables) elif ir.value == "select": # b ^ ((a ^ b) * cond) where cond is 1 or 0 cond, a, b = ir.args expanded = IRnode.from_list(["xor", b, ["mul", cond, ["xor", a, b]]]) - return _convert_ir_bb(ctx, expanded, symbols, variables, allocated_variables) + return _convert_ir_bb(ctx, expanded, symbols, allocated_variables) elif ir.value in ["iload", "sload"]: - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) return ctx.get_basic_block().append_instruction(ir.value, arg_0) elif ir.value in ["istore", "sstore"]: - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables) ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) elif ir.value == "unique_symbol": sym = ir.args[0] @@ -533,13 +502,11 @@ def emit_body_blocks(): global _break_target, _continue_target old_targets = _break_target, _continue_target _break_target, _continue_target = exit_block, incr_block - _convert_ir_bb(ctx, body, symbols.copy(), variables, allocated_variables.copy()) + _convert_ir_bb(ctx, body, symbols.copy(), allocated_variables.copy()) _break_target, _continue_target = old_targets sym = ir.args[0] - start, end, _ = _convert_ir_bb_list( - ctx, ir.args[1:4], symbols, variables, allocated_variables - ) + start, end, _ = _convert_ir_bb_list(ctx, ir.args[1:4], symbols, allocated_variables) assert ir.args[3].is_literal, "repeat bound expected to be literal" @@ -609,26 +576,22 @@ def emit_body_blocks(): return ctx.get_basic_block().append_instruction("returndatasize") elif ir.value == "returndatacopy": assert len(ir.args) == 3, "returndatacopy with wrong number of arguments" - arg_0, arg_1, size = _convert_ir_bb_list( - ctx, ir.args, symbols, variables, allocated_variables - ) + arg_0, arg_1, size = _convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables) ctx.get_basic_block().append_instruction("returndatacopy", size, arg_1, arg_0) elif ir.value == "selfdestruct": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, variables, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) ctx.get_basic_block().append_instruction("selfdestruct", arg_0) elif isinstance(ir.value, str) and ir.value.startswith("log"): - args = reversed( - [_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args] - ) + args = reversed([_convert_ir_bb(ctx, arg, symbols, allocated_variables) for arg in ir.args]) topic_count = int(ir.value[3:]) assert topic_count >= 0 and topic_count <= 4, "invalid topic count" ctx.get_basic_block().append_instruction("log", topic_count, *args) elif ir.value == "create" or ir.value == "create2": - args = reversed(_convert_ir_bb_list(ctx, ir.args, symbols, variables, allocated_variables)) + args = reversed(_convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables)) return ctx.get_basic_block().append_instruction(ir.value, *args) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): - _convert_ir_opcode(ctx, ir, symbols, variables, allocated_variables) + _convert_ir_opcode(ctx, ir, symbols, allocated_variables) elif isinstance(ir.value, str) and ir.value in symbols: return symbols[ir.value] elif ir.is_literal: @@ -643,12 +606,11 @@ def _convert_ir_opcode( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, - variables: OrderedSet, allocated_variables: dict[str, IRVariable], ) -> None: opcode = ir.value.upper() # type: ignore inst_args = [] for arg in ir.args: if isinstance(arg, IRnode): - inst_args.append(_convert_ir_bb(ctx, arg, symbols, variables, allocated_variables)) + inst_args.append(_convert_ir_bb(ctx, arg, symbols, allocated_variables)) ctx.get_basic_block().append_instruction(opcode, *inst_args) From 776a33ee23b821bc1e2fbe83298ae238a3ba8bcd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 20:50:25 +0200 Subject: [PATCH 171/322] remove allocated_variables --- vyper/venom/ir_node_to_venom.py | 115 ++++++++++++++------------------ 1 file changed, 50 insertions(+), 65 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 9f5adda2ae..fe384208f4 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -90,7 +90,7 @@ # convert IRnode directly to venom def ir_node_to_venom(ir: IRnode) -> IRFunction: ctx = IRFunction() - _convert_ir_bb(ctx, ir, {}, {}) + _convert_ir_bb(ctx, ir, {}) # Patch up basic blocks. Connect unterminated blocks to the next with # a jump. terminate final basic block with STOP. @@ -114,11 +114,10 @@ def _convert_binary_op( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, - allocated_variables: dict[str, IRVariable], swap: bool = False, ) -> Optional[IRVariable]: ir_args = ir.args[::-1] if swap else ir.args - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir_args, symbols, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir_args, symbols) assert isinstance(ir.value, str) # mypy hint return ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) @@ -142,7 +141,6 @@ def _handle_self_call( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, - allocated_variables: dict[str, IRVariable], ) -> Optional[IRVariable]: setup_ir = ir.args[1] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] @@ -151,9 +149,9 @@ def _handle_self_call( ret_args: list[IROperand] = [IRLabel(target_label)] # type: ignore if setup_ir != goto_ir: - _convert_ir_bb(ctx, setup_ir, symbols, allocated_variables) + _convert_ir_bb(ctx, setup_ir, symbols) - return_buf = _convert_ir_bb(ctx, return_buf_ir, symbols, allocated_variables) + return_buf = _convert_ir_bb(ctx, return_buf_ir, symbols) bb = ctx.get_basic_block() if len(goto_ir.args) > 2: @@ -189,10 +187,9 @@ def _convert_ir_simple_node( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, - allocated_variables: dict[str, IRVariable], reverse: bool = False, ) -> Optional[IRVariable]: - args = [_convert_ir_bb(ctx, arg, symbols, allocated_variables) for arg in ir.args] + args = [_convert_ir_bb(ctx, arg, symbols) for arg in ir.args] if reverse: args = reversed(args) return ctx.get_basic_block().append_instruction(ir.value, *args) # type: ignore @@ -202,33 +199,33 @@ def _convert_ir_simple_node( _continue_target: Optional[IRBasicBlock] = None -def _convert_ir_bb_list(ctx, ir, symbols, allocated_variables): +def _convert_ir_bb_list(ctx, ir, symbols): ret = [] for ir_node in ir: - venom = _convert_ir_bb(ctx, ir_node, symbols, allocated_variables) + venom = _convert_ir_bb(ctx, ir_node, symbols) ret.append(venom) return ret -def _convert_ir_bb(ctx, ir, symbols, allocated_variables): +def _convert_ir_bb(ctx, ir, symbols): assert isinstance(ir, IRnode), ir global _break_target, _continue_target if ir.value in _BINARY_IR_INSTRUCTIONS: - return _convert_binary_op(ctx, ir, symbols, allocated_variables, ir.value in ["sha3_64"]) + return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3_64"]) elif ir.value in INVERSE_MAPPED_IR_INSTRUCTIONS: org_value = ir.value ir.value = INVERSE_MAPPED_IR_INSTRUCTIONS[ir.value] - new_var = _convert_binary_op(ctx, ir, symbols, allocated_variables) + new_var = _convert_binary_op(ctx, ir, symbols) ir.value = org_value return ctx.get_basic_block().append_instruction("iszero", new_var) elif ir.value in PASS_THROUGH_INSTRUCTIONS: - return _convert_ir_simple_node(ctx, ir, symbols, allocated_variables) + return _convert_ir_simple_node(ctx, ir, symbols) elif ir.value in ["addmod", "mulmod"]: - return _convert_ir_simple_node(ctx, ir, symbols, allocated_variables, True) + return _convert_ir_simple_node(ctx, ir, symbols, True) elif ir.value in ["pass", "stop", "return"]: pass @@ -240,34 +237,33 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): if len(ir.args) == 0: return None if ir.is_self_call: - return _handle_self_call(ctx, ir, symbols, allocated_variables) + return _handle_self_call(ctx, ir, symbols) elif ir.args[0].value == "label" and ir.args[0].args[0].value.startswith("internal"): # Internal definition var_list = ir.args[0].args[1] does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args symbols = {} - allocated_variables = {} ir = _handle_internal_func(ctx, ir, does_return_data, symbols) # fallthrough ret = None for ir_node in ir.args: # NOTE: skip the last one - ret = _convert_ir_bb(ctx, ir_node, symbols, allocated_variables) + ret = _convert_ir_bb(ctx, ir_node, symbols) return ret elif ir.value in ["delegatecall", "staticcall", "call"]: idx = 0 - gas = _convert_ir_bb(ctx, ir.args[idx], symbols, allocated_variables) - address = _convert_ir_bb(ctx, ir.args[idx + 1], symbols, allocated_variables) + gas = _convert_ir_bb(ctx, ir.args[idx], symbols) + address = _convert_ir_bb(ctx, ir.args[idx + 1], symbols) value = None if ir.value == "call": - value = _convert_ir_bb(ctx, ir.args[idx + 2], symbols, allocated_variables) + value = _convert_ir_bb(ctx, ir.args[idx + 2], symbols) else: idx -= 1 argsOffset, argsSize, retOffset, retSize = _convert_ir_bb_list( - ctx, ir.args[idx + 3 : idx + 7], symbols, allocated_variables + ctx, ir.args[idx + 3 : idx + 7], symbols ) if isinstance(argsOffset, IRLiteral): @@ -290,11 +286,10 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): cond = ir.args[0] # convert the condition - cont_ret = _convert_ir_bb(ctx, cond, symbols, allocated_variables) + cont_ret = _convert_ir_bb(ctx, cond, symbols) cond_block = ctx.get_basic_block() cond_symbols = symbols.copy() - cond_allocated_variables = allocated_variables.copy() else_block = IRBasicBlock(ctx.get_next_label("else"), ctx) ctx.append_basic_block(else_block) @@ -302,7 +297,7 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): # convert "else" else_ret_val = None if len(ir.args) == 3: - else_ret_val = _convert_ir_bb(ctx, ir.args[2], cond_symbols, cond_allocated_variables) + else_ret_val = _convert_ir_bb(ctx, ir.args[2], cond_symbols) if isinstance(else_ret_val, IRLiteral): assert isinstance(else_ret_val.value, int) # help mypy else_ret_val = ctx.get_basic_block().append_instruction("store", else_ret_val) @@ -311,12 +306,11 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): # convert "then" cond_symbols = symbols.copy() - cond_allocated_variables = allocated_variables.copy() then_block = IRBasicBlock(ctx.get_next_label("then"), ctx) ctx.append_basic_block(then_block) - then_ret_val = _convert_ir_bb(ctx, ir.args[1], cond_symbols, cond_allocated_variables) + then_ret_val = _convert_ir_bb(ctx, ir.args[1], cond_symbols) if isinstance(then_ret_val, IRLiteral): then_ret_val = ctx.get_basic_block().append_instruction("store", then_ret_val) @@ -342,38 +336,36 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): return if_ret elif ir.value == "with": - ret = _convert_ir_bb(ctx, ir.args[1], symbols, allocated_variables) # initialization + ret = _convert_ir_bb(ctx, ir.args[1], symbols) # initialization ret = ctx.get_basic_block().append_instruction("store", ret) # Handle with nesting with same symbol with_symbols = symbols.copy() - with_allocated_variables = allocated_variables.copy() sym = ir.args[0] - with_allocated_variables[sym.value] = ret with_symbols[sym.value] = ret - return _convert_ir_bb(ctx, ir.args[2], with_symbols, with_allocated_variables) # body + return _convert_ir_bb(ctx, ir.args[2], with_symbols) # body elif ir.value == "goto": _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "djump": - args = [_convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables)] + args = [_convert_ir_bb(ctx, ir.args[0], symbols)] for target in ir.args[1:]: args.append(IRLabel(target.value)) ctx.get_basic_block().append_instruction("djmp", *args) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] - arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols, allocated_variables) - ctx.get_basic_block().append_instruction("store", arg_1, ret=allocated_variables[sym.value]) + arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols) + ctx.get_basic_block().append_instruction("store", arg_1, ret=symbols[sym.value]) elif ir.value == "calldatacopy": - arg_0, arg_1, size = _convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables) + arg_0, arg_1, size = _convert_ir_bb_list(ctx, ir.args, symbols) ctx.get_basic_block().append_instruction("calldatacopy", size, arg_1, arg_0) # type: ignore return None elif ir.value in ["extcodecopy", "codecopy"]: - return _convert_ir_simple_node(ctx, ir, symbols, allocated_variables, reverse=True) + return _convert_ir_simple_node(ctx, ir, symbols, reverse=True) elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) elif ir.value == "data": @@ -386,14 +378,14 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): elif isinstance(c, bytes): ctx.append_data("db", [c]) # type: ignore elif isinstance(c, IRnode): - data = _convert_ir_bb(ctx, c, symbols, allocated_variables) + data = _convert_ir_bb(ctx, c, symbols) ctx.append_data("db", [data]) # type: ignore elif ir.value == "assert": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) bb = ctx.get_basic_block() bb.append_instruction("assert", arg_0) elif ir.value == "assert_unreachable": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) bb = ctx.get_basic_block() bb.append_instruction("assert_unreachable", arg_0) elif ir.value == "label": @@ -403,7 +395,7 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): bb.append_instruction("jmp", label) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) - _convert_ir_bb(ctx, ir.args[2], symbols, allocated_variables) + _convert_ir_bb(ctx, ir.args[2], symbols) elif ir.value == "exit_to": label = ir.args[0] @@ -420,9 +412,7 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): if bb.is_terminated: bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) ctx.append_basic_block(bb) - return_buffer, return_size = _convert_ir_bb_list( - ctx, ir.args[1:], symbols, allocated_variables - ) + return_buffer, return_size = _convert_ir_bb_list(ctx, ir.args[1:], symbols) bb = ctx.get_basic_block() assert return_buffer is not None bb.append_instruction("return", return_size, return_buffer) @@ -435,11 +425,11 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): bb.append_instruction("ret", symbols["return_pc"]) elif ir.value == "revert": - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) ctx.get_basic_block().append_instruction("revert", arg_1, arg_0) elif ir.value == "dload": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) bb = ctx.get_basic_block() src = bb.append_instruction("add", arg_0, IRLabel("code_end")) @@ -447,7 +437,7 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): return bb.append_instruction("mload", MemoryPositions.FREE_VAR_SPACE) elif ir.value == "dloadbytes": - dst, src_offset, len_ = _convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables) + dst, src_offset, len_ = _convert_ir_bb_list(ctx, ir.args, symbols) bb = ctx.get_basic_block() src = bb.append_instruction("add", src_offset, IRLabel("code_end")) @@ -455,11 +445,7 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): return None elif ir.value == "mload": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) - # return_buffer special case - if allocated_variables.get("return_buffer") == arg_0.name: - return arg_0 - + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) bb = ctx.get_basic_block() if isinstance(arg_0, IRVariable): return bb.append_instruction("mload", arg_0) @@ -471,7 +457,7 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): return bb.append_instruction("mload", arg_0) elif ir.value == "mstore": - arg_1, arg_0 = _convert_ir_bb_list(ctx, reversed(ir.args), symbols, allocated_variables) + arg_1, arg_0 = _convert_ir_bb_list(ctx, reversed(ir.args), symbols) if isinstance(arg_1, IRVariable): symbols[f"&{arg_0.value}"] = arg_1 @@ -479,17 +465,17 @@ def _convert_ir_bb(ctx, ir, symbols, allocated_variables): elif ir.value == "ceil32": x = ir.args[0] expanded = IRnode.from_list(["and", ["add", x, 31], ["not", 31]]) - return _convert_ir_bb(ctx, expanded, symbols, allocated_variables) + return _convert_ir_bb(ctx, expanded, symbols) elif ir.value == "select": # b ^ ((a ^ b) * cond) where cond is 1 or 0 cond, a, b = ir.args expanded = IRnode.from_list(["xor", b, ["mul", cond, ["xor", a, b]]]) - return _convert_ir_bb(ctx, expanded, symbols, allocated_variables) + return _convert_ir_bb(ctx, expanded, symbols) elif ir.value in ["iload", "sload"]: - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) return ctx.get_basic_block().append_instruction(ir.value, arg_0) elif ir.value in ["istore", "sstore"]: - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) elif ir.value == "unique_symbol": sym = ir.args[0] @@ -502,11 +488,11 @@ def emit_body_blocks(): global _break_target, _continue_target old_targets = _break_target, _continue_target _break_target, _continue_target = exit_block, incr_block - _convert_ir_bb(ctx, body, symbols.copy(), allocated_variables.copy()) + _convert_ir_bb(ctx, body, symbols.copy()) _break_target, _continue_target = old_targets sym = ir.args[0] - start, end, _ = _convert_ir_bb_list(ctx, ir.args[1:4], symbols, allocated_variables) + start, end, _ = _convert_ir_bb_list(ctx, ir.args[1:4], symbols) assert ir.args[3].is_literal, "repeat bound expected to be literal" @@ -576,22 +562,22 @@ def emit_body_blocks(): return ctx.get_basic_block().append_instruction("returndatasize") elif ir.value == "returndatacopy": assert len(ir.args) == 3, "returndatacopy with wrong number of arguments" - arg_0, arg_1, size = _convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables) + arg_0, arg_1, size = _convert_ir_bb_list(ctx, ir.args, symbols) ctx.get_basic_block().append_instruction("returndatacopy", size, arg_1, arg_0) elif ir.value == "selfdestruct": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols, allocated_variables) + arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) ctx.get_basic_block().append_instruction("selfdestruct", arg_0) elif isinstance(ir.value, str) and ir.value.startswith("log"): - args = reversed([_convert_ir_bb(ctx, arg, symbols, allocated_variables) for arg in ir.args]) + args = reversed([_convert_ir_bb(ctx, arg, symbols) for arg in ir.args]) topic_count = int(ir.value[3:]) assert topic_count >= 0 and topic_count <= 4, "invalid topic count" ctx.get_basic_block().append_instruction("log", topic_count, *args) elif ir.value == "create" or ir.value == "create2": - args = reversed(_convert_ir_bb_list(ctx, ir.args, symbols, allocated_variables)) + args = reversed(_convert_ir_bb_list(ctx, ir.args, symbols)) return ctx.get_basic_block().append_instruction(ir.value, *args) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): - _convert_ir_opcode(ctx, ir, symbols, allocated_variables) + _convert_ir_opcode(ctx, ir, symbols) elif isinstance(ir.value, str) and ir.value in symbols: return symbols[ir.value] elif ir.is_literal: @@ -606,11 +592,10 @@ def _convert_ir_opcode( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, - allocated_variables: dict[str, IRVariable], ) -> None: opcode = ir.value.upper() # type: ignore inst_args = [] for arg in ir.args: if isinstance(arg, IRnode): - inst_args.append(_convert_ir_bb(ctx, arg, symbols, allocated_variables)) + inst_args.append(_convert_ir_bb(ctx, arg, symbols)) ctx.get_basic_block().append_instruction(opcode, *inst_args) From 1e182f7524b574d27780e1d6eafbd57b7e7d48db Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 22:54:21 +0200 Subject: [PATCH 172/322] transient storage --- vyper/venom/basicblock.py | 2 ++ vyper/venom/ir_node_to_venom.py | 4 ++-- vyper/venom/venom_to_assembly.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 29aed0ca4b..4e5edd98c5 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -18,6 +18,8 @@ "sstore", "iload", "istore", + "tload", + "tstore", "assert", "assert_unreachable", "mstore", diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index fe384208f4..3f8869808d 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -471,10 +471,10 @@ def _convert_ir_bb(ctx, ir, symbols): cond, a, b = ir.args expanded = IRnode.from_list(["xor", b, ["mul", cond, ["xor", a, b]]]) return _convert_ir_bb(ctx, expanded, symbols) - elif ir.value in ["iload", "sload"]: + elif ir.value in ["iload", "sload", "tload"]: arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) return ctx.get_basic_block().append_instruction(ir.value, arg_0) - elif ir.value in ["istore", "sstore"]: + elif ir.value in ["istore", "sstore", "tstore"]: arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) elif ir.value == "unique_symbol": diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index bb11e3f29b..adcd8bb484 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -48,6 +48,8 @@ "sstore", "mload", "mstore", + "tload", + "tstore", "timestamp", "caller", "blockhash", From 0fd4f574450fb2dc00b94673151a70e6ed06d789 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Feb 2024 23:25:35 +0200 Subject: [PATCH 173/322] mcopy --- vyper/venom/basicblock.py | 2 ++ vyper/venom/ir_node_to_venom.py | 5 ++--- vyper/venom/venom_to_assembly.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 4e5edd98c5..d275cbf448 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -25,6 +25,7 @@ "mstore", "mload", "calldatacopy", + "mcopy", "returndatacopy", "codecopy", "dloadbytes", @@ -44,6 +45,7 @@ "istore", "dloadbytes", "calldatacopy", + "mcopy", "returndatacopy", "codecopy", "extcodecopy", diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 3f8869808d..e51b610431 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -359,10 +359,9 @@ def _convert_ir_bb(ctx, ir, symbols): sym = ir.args[0] arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols) ctx.get_basic_block().append_instruction("store", arg_1, ret=symbols[sym.value]) - - elif ir.value == "calldatacopy": + elif ir.value in ["calldatacopy", "mcopy"]: arg_0, arg_1, size = _convert_ir_bb_list(ctx, ir.args, symbols) - ctx.get_basic_block().append_instruction("calldatacopy", size, arg_1, arg_0) # type: ignore + ctx.get_basic_block().append_instruction(ir.value, size, arg_1, arg_0) # type: ignore return None elif ir.value in ["extcodecopy", "codecopy"]: return _convert_ir_simple_node(ctx, ir, symbols, reverse=True) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index adcd8bb484..89719c942f 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -29,6 +29,7 @@ "coinbase", "calldatasize", "calldatacopy", + "mcopy", "calldataload", "gas", "gasprice", From a9fefa633827e4257db8ef9825a9aa6ba4eb19ac Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 10:40:50 +0200 Subject: [PATCH 174/322] fix reordering of stack with duplicated --- vyper/venom/stack_model.py | 9 ++++++--- vyper/venom/venom_to_assembly.py | 6 +++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 66c62b74d2..c1de2283be 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -30,16 +30,19 @@ def push(self, op: IROperand) -> None: def pop(self, num: int = 1) -> None: del self._stack[len(self._stack) - num :] - def get_depth(self, op: IROperand) -> int: + def get_depth(self, op: IROperand, n: int = 1) -> int: """ - Returns the depth of the first matching operand in the stack map. + Returns the depth of the n-th matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ assert isinstance(op, IROperand), f"{type(op)}: {op}" for i, stack_op in enumerate(reversed(self._stack)): if stack_op.value == op.value: - return -i + if n <= 1: + return -i + else: + n -= 1 return StackModel.NOT_IN_STACK # type: ignore diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 89719c942f..b5d7b3cee2 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,3 +1,4 @@ +from collections import Counter from typing import Any from vyper.exceptions import CompilerPanic @@ -183,10 +184,13 @@ def _stack_reorder( ) -> None: stack_ops_count = len(stack_ops) + counts = Counter(stack_ops) + for i in range(stack_ops_count): op = stack_ops[i] final_stack_depth = -(stack_ops_count - i - 1) - depth = stack.get_depth(op) # type: ignore + depth = stack.get_depth(op, counts[op]) # type: ignore + counts[op] -= 1 if depth == StackModel.NOT_IN_STACK: raise CompilerPanic(f"Variable {op} not in stack") From c1b4965359ec5322968e7747eebcecec89a639f5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 12:33:25 +0200 Subject: [PATCH 175/322] exit_to acrobatics --- vyper/venom/ir_node_to_venom.py | 28 +++++++++++++++++++++------- vyper/venom/venom_to_assembly.py | 4 +++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index e51b610431..42a9cc3b31 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -227,8 +227,12 @@ def _convert_ir_bb(ctx, ir, symbols): elif ir.value in ["addmod", "mulmod"]: return _convert_ir_simple_node(ctx, ir, symbols, True) - elif ir.value in ["pass", "stop", "return"]: + elif ir.value in ["pass", "stop"]: pass + elif ir.value == "return": + ret_ofst = IRVariable("ret_ofst") + ret_size = IRVariable("ret_size") + ctx.get_basic_block().append_instruction("return", ret_size, ret_ofst) elif ir.value == "deploy": ctx.ctor_mem_size = ir.args[0].value ctx.immutables_len = ir.args[2].value @@ -396,11 +400,11 @@ def _convert_ir_bb(ctx, ir, symbols): ctx.append_basic_block(bb) _convert_ir_bb(ctx, ir.args[2], symbols) elif ir.value == "exit_to": - label = ir.args[0] + label = IRLabel(ir.args[0].value) - is_constructor = "__init__(" in label.value - is_external = label.value.startswith("external") and not is_constructor - is_internal = label.value.startswith("internal") + is_constructor = "__init__(" in label.name + is_external = label.name.startswith("external") and not is_constructor + is_internal = label.name.startswith("internal") bb = ctx.get_basic_block() if is_external: @@ -412,11 +416,21 @@ def _convert_ir_bb(ctx, ir, symbols): bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) ctx.append_basic_block(bb) return_buffer, return_size = _convert_ir_bb_list(ctx, ir.args[1:], symbols) + + if bb.is_terminated: + bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) + ctx.append_basic_block(bb) + ret_ofst = IRVariable("ret_ofst") + ret_size = IRVariable("ret_size") + bb.append_instruction("store", return_buffer, ret=ret_ofst) + bb.append_instruction("store", return_size, ret=ret_size) + bb = ctx.get_basic_block() assert return_buffer is not None - bb.append_instruction("return", return_size, return_buffer) - ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) + bb.append_instruction("jmp", label) + # bb.append_instruction("return", return_size, return_buffer) + # ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) elif is_internal: assert ir.args[1].value == "return_pc", "return_pc not found" diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index b5d7b3cee2..cc920f726b 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -430,7 +430,9 @@ def _generate_evm_for_instruction( assembly.append(f"_sym_{inst.operands[0].value}") assembly.append("JUMP") elif opcode == "djmp": - assert isinstance(inst.operands[0], IRVariable) + assert isinstance( + inst.operands[0], IRVariable + ), f"Expected IRVariable, got {inst.operands[0]}" assembly.append("JUMP") elif opcode == "gt": assembly.append("GT") From 48ea42995a86625b07278e59fcf5405fe1b928d3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 15:35:30 +0200 Subject: [PATCH 176/322] exit_to with no parameters --- vyper/venom/ir_node_to_venom.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 42a9cc3b31..6d4cea8f4a 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -82,6 +82,7 @@ "msize", "basefee", "invalid", + "stop", ] SymbolTable = dict[str, Optional[IROperand]] @@ -227,7 +228,7 @@ def _convert_ir_bb(ctx, ir, symbols): elif ir.value in ["addmod", "mulmod"]: return _convert_ir_simple_node(ctx, ir, symbols, True) - elif ir.value in ["pass", "stop"]: + elif ir.value in ["pass"]: pass elif ir.value == "return": ret_ofst = IRVariable("ret_ofst") @@ -408,10 +409,7 @@ def _convert_ir_bb(ctx, ir, symbols): bb = ctx.get_basic_block() if is_external: - if len(ir.args) == 1: # no return value - bb.append_instruction("stop") - return None - else: + if len(ir.args) > 1: # no return value if bb.is_terminated: bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) ctx.append_basic_block(bb) @@ -428,9 +426,7 @@ def _convert_ir_bb(ctx, ir, symbols): bb = ctx.get_basic_block() assert return_buffer is not None - bb.append_instruction("jmp", label) - # bb.append_instruction("return", return_size, return_buffer) - # ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) + bb.append_instruction("jmp", label) elif is_internal: assert ir.args[1].value == "return_pc", "return_pc not found" From f54da860db0c208435c94f4ed71185f047c89c8c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 16:12:09 +0200 Subject: [PATCH 177/322] ret_ofst ret_size undefined case --- vyper/venom/ir_node_to_venom.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 6d4cea8f4a..bef2ca94c6 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -249,10 +249,24 @@ def _convert_ir_bb(ctx, ir, symbols): does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args symbols = {} ir = _handle_internal_func(ctx, ir, does_return_data, symbols) - # fallthrough + for ir_node in ir.args: + ret = _convert_ir_bb(ctx, ir_node, symbols) + + return ret + elif ir.args[0].value == "label" and ir.args[0].args[0].value.startswith("external"): + ret = _convert_ir_bb(ctx, ir.args[0], symbols) + bb = ctx.get_basic_block() + if bb.is_terminated: + bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) + ctx.append_basic_block(bb) + ret_ofst = IRVariable("ret_ofst") + ret_size = IRVariable("ret_size") + bb.append_instruction("store", 0, ret=ret_ofst) + bb.append_instruction("store", 0, ret=ret_size) + else: + ret = _convert_ir_bb(ctx, ir.args[0], symbols) - ret = None - for ir_node in ir.args: # NOTE: skip the last one + for ir_node in ir.args[1:]: ret = _convert_ir_bb(ctx, ir_node, symbols) return ret From 96f7c3b9e0ebe5181df8c8e5d106f64e993ded90 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 16:25:43 +0200 Subject: [PATCH 178/322] multi phi get_depth --- vyper/venom/stack_model.py | 11 +++++------ vyper/venom/venom_to_assembly.py | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index c1de2283be..a98e5bb25b 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -46,21 +46,20 @@ def get_depth(self, op: IROperand, n: int = 1) -> int: return StackModel.NOT_IN_STACK # type: ignore - def get_phi_depth(self, phi1: IRVariable, phi2: IRVariable) -> int: + def get_phi_depth(self, phis: list[IRVariable]) -> int: """ Returns the depth of the first matching phi variable in the stack map. If the none of the phi operands are in the stack, returns NOT_IN_STACK. - Asserts that exactly one of phi1 and phi2 is found. + Asserts that exactly one of phis is found. """ - assert isinstance(phi1, IRVariable) - assert isinstance(phi2, IRVariable) + assert isinstance(phis, list) ret = StackModel.NOT_IN_STACK for i, stack_item in enumerate(reversed(self._stack)): - if stack_item in (phi1, phi2): + if stack_item in phis: assert ( ret is StackModel.NOT_IN_STACK - ), f"phi argument is not unique! {phi1}, {phi2}, {self._stack}" + ), f"phi argument is not unique! {phis}, {self._stack}" ret = -i return ret # type: ignore diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index cc920f726b..039e8f64a2 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -356,8 +356,8 @@ def _generate_evm_for_instruction( if opcode == "phi": ret = inst.get_outputs()[0] - phi1, phi2 = inst.get_inputs() - depth = stack.get_phi_depth(phi1, phi2) + phis = inst.get_inputs() + depth = stack.get_phi_depth(phis) # collapse the arguments to the phi node in the stack. # example, for `%56 = %label1 %13 %label2 %14`, we will # find an instance of %13 *or* %14 in the stack and replace it with %56. From a055a8372249f72cf06101bccfd1e5fcb061d720 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 18:47:57 +0200 Subject: [PATCH 179/322] small cleanup / refactor --- vyper/venom/ir_node_to_venom.py | 38 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index bef2ca94c6..a20924b1eb 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -138,6 +138,17 @@ def _new_block(ctx: IRFunction) -> IRBasicBlock: return bb +def _append_return_args(ctx: IRFunction, ofst: int = 0, size: int = 0): + bb = ctx.get_basic_block() + if bb.is_terminated: + bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) + ctx.append_basic_block(bb) + ret_ofst = IRVariable("ret_ofst") + ret_size = IRVariable("ret_size") + bb.append_instruction("store", ofst, ret=ret_ofst) + bb.append_instruction("store", size, ret=ret_size) + + def _handle_self_call( ctx: IRFunction, ir: IRnode, @@ -231,9 +242,9 @@ def _convert_ir_bb(ctx, ir, symbols): elif ir.value in ["pass"]: pass elif ir.value == "return": - ret_ofst = IRVariable("ret_ofst") - ret_size = IRVariable("ret_size") - ctx.get_basic_block().append_instruction("return", ret_size, ret_ofst) + ctx.get_basic_block().append_instruction( + "return", IRVariable("ret_size"), IRVariable("ret_ofst") + ) elif ir.value == "deploy": ctx.ctor_mem_size = ir.args[0].value ctx.immutables_len = ir.args[2].value @@ -255,14 +266,7 @@ def _convert_ir_bb(ctx, ir, symbols): return ret elif ir.args[0].value == "label" and ir.args[0].args[0].value.startswith("external"): ret = _convert_ir_bb(ctx, ir.args[0], symbols) - bb = ctx.get_basic_block() - if bb.is_terminated: - bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) - ctx.append_basic_block(bb) - ret_ofst = IRVariable("ret_ofst") - ret_size = IRVariable("ret_size") - bb.append_instruction("store", 0, ret=ret_ofst) - bb.append_instruction("store", 0, ret=ret_size) + _append_return_args(ctx) else: ret = _convert_ir_bb(ctx, ir.args[0], symbols) @@ -427,18 +431,12 @@ def _convert_ir_bb(ctx, ir, symbols): if bb.is_terminated: bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) ctx.append_basic_block(bb) - return_buffer, return_size = _convert_ir_bb_list(ctx, ir.args[1:], symbols) + ret_ofst, ret_size = _convert_ir_bb_list(ctx, ir.args[1:], symbols) - if bb.is_terminated: - bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) - ctx.append_basic_block(bb) - ret_ofst = IRVariable("ret_ofst") - ret_size = IRVariable("ret_size") - bb.append_instruction("store", return_buffer, ret=ret_ofst) - bb.append_instruction("store", return_size, ret=ret_size) + _append_return_args(ctx, ret_ofst, ret_size) bb = ctx.get_basic_block() - assert return_buffer is not None + assert ret_ofst is not None bb.append_instruction("jmp", label) From 18dd18d9cf566714e461c3f29e4977c02993aaa7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 19:16:36 +0200 Subject: [PATCH 180/322] cleanup --- vyper/venom/ir_node_to_venom.py | 139 ++++++++++++++------------------ 1 file changed, 61 insertions(+), 78 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a20924b1eb..97f44b4aff 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -53,37 +53,63 @@ # Instructions that have a direct EVM opcode equivalent and can # be passed through to the EVM assembly without special handling -PASS_THROUGH_INSTRUCTIONS = [ - "chainid", - "basefee", - "timestamp", - "blockhash", - "caller", - "selfbalance", - "calldatasize", - "callvalue", - "address", - "origin", - "codesize", - "gas", - "gasprice", - "gaslimit", - "returndatasize", - "coinbase", - "number", - "prevrandao", - "difficulty", - "iszero", - "not", - "calldataload", - "extcodesize", - "extcodehash", - "balance", - "msize", - "basefee", - "invalid", - "stop", -] +PASS_THROUGH_INSTRUCTIONS = frozenset( + [ + "chainid", + "basefee", + "timestamp", + "blockhash", + "caller", + "selfbalance", + "calldatasize", + "callvalue", + "address", + "origin", + "codesize", + "gas", + "gasprice", + "gaslimit", + "returndatasize", + "returndatacopy", + "iload", + "sload", + "tload", + "coinbase", + "number", + "prevrandao", + "difficulty", + "iszero", + "not", + "calldataload", + "extcodesize", + "extcodehash", + "balance", + "msize", + "basefee", + "invalid", + "stop", + "selfdestruct", + "assert", + "assert_unreachable", + ] +) + +PASS_THROUGH_REVERSED_INSTRUCTIONS = frozenset( + [ + "calldatacopy", + "mcopy", + "extcodecopy", + "codecopy", + "revert", + "istore", + "sstore", + "tstore", + "create", + "create2", + "addmod", + "mulmod", + ] +) SymbolTable = dict[str, Optional[IROperand]] @@ -225,22 +251,16 @@ def _convert_ir_bb(ctx, ir, symbols): if ir.value in _BINARY_IR_INSTRUCTIONS: return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3_64"]) - elif ir.value in INVERSE_MAPPED_IR_INSTRUCTIONS: org_value = ir.value ir.value = INVERSE_MAPPED_IR_INSTRUCTIONS[ir.value] new_var = _convert_binary_op(ctx, ir, symbols) ir.value = org_value return ctx.get_basic_block().append_instruction("iszero", new_var) - elif ir.value in PASS_THROUGH_INSTRUCTIONS: return _convert_ir_simple_node(ctx, ir, symbols) - - elif ir.value in ["addmod", "mulmod"]: - return _convert_ir_simple_node(ctx, ir, symbols, True) - - elif ir.value in ["pass"]: - pass + elif ir.value in PASS_THROUGH_REVERSED_INSTRUCTIONS: + return _convert_ir_simple_node(ctx, ir, symbols, reverse=True) elif ir.value == "return": ctx.get_basic_block().append_instruction( "return", IRVariable("ret_size"), IRVariable("ret_ofst") @@ -382,12 +402,6 @@ def _convert_ir_bb(ctx, ir, symbols): sym = ir.args[0] arg_1 = _convert_ir_bb(ctx, ir.args[1], symbols) ctx.get_basic_block().append_instruction("store", arg_1, ret=symbols[sym.value]) - elif ir.value in ["calldatacopy", "mcopy"]: - arg_0, arg_1, size = _convert_ir_bb_list(ctx, ir.args, symbols) - ctx.get_basic_block().append_instruction(ir.value, size, arg_1, arg_0) # type: ignore - return None - elif ir.value in ["extcodecopy", "codecopy"]: - return _convert_ir_simple_node(ctx, ir, symbols, reverse=True) elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) elif ir.value == "data": @@ -402,14 +416,6 @@ def _convert_ir_bb(ctx, ir, symbols): elif isinstance(c, IRnode): data = _convert_ir_bb(ctx, c, symbols) ctx.append_data("db", [data]) # type: ignore - elif ir.value == "assert": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) - bb = ctx.get_basic_block() - bb.append_instruction("assert", arg_0) - elif ir.value == "assert_unreachable": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) - bb = ctx.get_basic_block() - bb.append_instruction("assert_unreachable", arg_0) elif ir.value == "label": label = IRLabel(ir.args[0].value, True) bb = ctx.get_basic_block() @@ -444,11 +450,6 @@ def _convert_ir_bb(ctx, ir, symbols): assert ir.args[1].value == "return_pc", "return_pc not found" # TODO: never passing return values with the new convention bb.append_instruction("ret", symbols["return_pc"]) - - elif ir.value == "revert": - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) - ctx.get_basic_block().append_instruction("revert", arg_1, arg_0) - elif ir.value == "dload": arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) bb = ctx.get_basic_block() @@ -492,10 +493,7 @@ def _convert_ir_bb(ctx, ir, symbols): cond, a, b = ir.args expanded = IRnode.from_list(["xor", b, ["mul", cond, ["xor", a, b]]]) return _convert_ir_bb(ctx, expanded, symbols) - elif ir.value in ["iload", "sload", "tload"]: - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) - return ctx.get_basic_block().append_instruction(ir.value, arg_0) - elif ir.value in ["istore", "sstore", "tstore"]: + elif ir.value in []: arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) elif ir.value == "unique_symbol": @@ -567,7 +565,7 @@ def emit_body_blocks(): ctx.append_basic_block(exit_block) cond_block.append_instruction("jnz", cont_ret, exit_block.label, body_block.label) - elif ir.value == "cleanup_repeat": + elif ir.value in ["cleanup_repeat", "pass"]: pass elif ir.value == "break": assert _break_target is not None, "Break with no break target" @@ -577,26 +575,11 @@ def emit_body_blocks(): assert _continue_target is not None, "Continue with no contrinue target" ctx.get_basic_block().append_instruction("jmp", _continue_target.label) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) - elif ir.value == "gas": - return ctx.get_basic_block().append_instruction("gas") - elif ir.value == "returndatasize": - return ctx.get_basic_block().append_instruction("returndatasize") - elif ir.value == "returndatacopy": - assert len(ir.args) == 3, "returndatacopy with wrong number of arguments" - arg_0, arg_1, size = _convert_ir_bb_list(ctx, ir.args, symbols) - - ctx.get_basic_block().append_instruction("returndatacopy", size, arg_1, arg_0) - elif ir.value == "selfdestruct": - arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) - ctx.get_basic_block().append_instruction("selfdestruct", arg_0) elif isinstance(ir.value, str) and ir.value.startswith("log"): args = reversed([_convert_ir_bb(ctx, arg, symbols) for arg in ir.args]) topic_count = int(ir.value[3:]) assert topic_count >= 0 and topic_count <= 4, "invalid topic count" ctx.get_basic_block().append_instruction("log", topic_count, *args) - elif ir.value == "create" or ir.value == "create2": - args = reversed(_convert_ir_bb_list(ctx, ir.args, symbols)) - return ctx.get_basic_block().append_instruction(ir.value, *args) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): _convert_ir_opcode(ctx, ir, symbols) elif isinstance(ir.value, str) and ir.value in symbols: From b3b9663a3f97115ba12f4f0af2be6dbda2025d89 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 19:23:40 +0200 Subject: [PATCH 181/322] clean up phase 2 --- tests/unit/compiler/venom/test_make_ssa.py | 1 - .../function_definitions/internal_function.py | 2 +- vyper/venom/ir_node_to_venom.py | 37 +++---------------- vyper/venom/passes/make_ssa.py | 1 - vyper/venom/venom_to_assembly.py | 9 ++++- 5 files changed, 14 insertions(+), 36 deletions(-) diff --git a/tests/unit/compiler/venom/test_make_ssa.py b/tests/unit/compiler/venom/test_make_ssa.py index c4093c5248..9400800ba5 100644 --- a/tests/unit/compiler/venom/test_make_ssa.py +++ b/tests/unit/compiler/venom/test_make_ssa.py @@ -1,6 +1,5 @@ from vyper.venom.analysis import calculate_cfg, calculate_liveness from vyper.venom.basicblock import IRBasicBlock, IRLabel -from vyper.venom.bb_optimizer import _optimize_unused_variables from vyper.venom.function import IRFunction from vyper.venom.passes.make_ssa import MakeSSA diff --git a/vyper/codegen/function_definitions/internal_function.py b/vyper/codegen/function_definitions/internal_function.py index 3ae7438892..cde1ec5c87 100644 --- a/vyper/codegen/function_definitions/internal_function.py +++ b/vyper/codegen/function_definitions/internal_function.py @@ -80,6 +80,6 @@ def generate_ir_for_internal_function( # tag gas estimate and frame info func_t._ir_info.gas_estimate = ir_node.gas - frame_info = tag_frame_info(func_t, context) + tag_frame_info(func_t, context) return InternalFuncIR(ir_node) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 97f44b4aff..c96ef5a6dc 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,14 +1,7 @@ from typing import Optional -from vyper.codegen.core import make_setter -from vyper.evm.address_space import MEMORY -from vyper.semantics.types.subscriptable import TupleT -from vyper.codegen.context import VariableRecord from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes -from vyper.exceptions import CompilerPanic -from vyper.ir.compile_ir import is_mem_sym, is_symbol -from vyper.semantics.types.function import ContractFunctionT -from vyper.utils import MemoryPositions, OrderedSet +from vyper.utils import MemoryPositions from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, @@ -16,7 +9,6 @@ IRLiteral, IROperand, IRVariable, - MemType, ) from vyper.venom.function import IRFunction @@ -138,10 +130,7 @@ def ir_node_to_venom(ir: IRnode) -> IRFunction: def _convert_binary_op( - ctx: IRFunction, - ir: IRnode, - symbols: SymbolTable, - swap: bool = False, + ctx: IRFunction, ir: IRnode, symbols: SymbolTable, swap: bool = False ) -> Optional[IRVariable]: ir_args = ir.args[::-1] if swap else ir.args arg_0, arg_1 = _convert_ir_bb_list(ctx, ir_args, symbols) @@ -175,11 +164,7 @@ def _append_return_args(ctx: IRFunction, ofst: int = 0, size: int = 0): bb.append_instruction("store", size, ret=ret_size) -def _handle_self_call( - ctx: IRFunction, - ir: IRnode, - symbols: SymbolTable, -) -> Optional[IRVariable]: +def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optional[IRVariable]: setup_ir = ir.args[1] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto @@ -201,10 +186,7 @@ def _handle_self_call( def _handle_internal_func( - ctx: IRFunction, - ir: IRnode, - does_return_data: bool, - symbols: SymbolTable, + ctx: IRFunction, ir: IRnode, does_return_data: bool, symbols: SymbolTable ) -> IRnode: bb = IRBasicBlock(IRLabel(ir.args[0].args[0].value, True), ctx) # type: ignore bb = ctx.append_basic_block(bb) @@ -222,10 +204,7 @@ def _handle_internal_func( def _convert_ir_simple_node( - ctx: IRFunction, - ir: IRnode, - symbols: SymbolTable, - reverse: bool = False, + ctx: IRFunction, ir: IRnode, symbols: SymbolTable, reverse: bool = False ) -> Optional[IRVariable]: args = [_convert_ir_bb(ctx, arg, symbols) for arg in ir.args] if reverse: @@ -592,11 +571,7 @@ def emit_body_blocks(): return None -def _convert_ir_opcode( - ctx: IRFunction, - ir: IRnode, - symbols: SymbolTable, -) -> None: +def _convert_ir_opcode(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> None: opcode = ir.value.upper() # type: ignore inst_args = [] for arg in ir.args: diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 1fff77560a..b28d316d2f 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -1,7 +1,6 @@ from vyper.utils import OrderedSet from vyper.venom.analysis import calculate_cfg, calculate_liveness from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IROperand, IRVariable -from vyper.venom.bb_optimizer import _optimize_unused_variables, ir_pass_optimize_unused_variables from vyper.venom.dominators import DominatorTree from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 039e8f64a2..409d2b0d49 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -17,7 +17,6 @@ IRLiteral, IROperand, IRVariable, - MemType, ) from vyper.venom.function import IRFunction from vyper.venom.passes.normalization import NormalizationPass @@ -321,8 +320,14 @@ def clean_stack_from_cfg_in( self.pop(asm, stack) def _generate_evm_for_instruction( - self, assembly: list, inst: IRInstruction, stack: StackModel, next_liveness: OrderedSet = [] + self, + assembly: list, + inst: IRInstruction, + stack: StackModel, + next_liveness: OrderedSet = None, ) -> list[str]: + if next_liveness is None: + next_liveness = [] opcode = inst.opcode # From 760c4243d2c85f78724263f605a010a7dcd32004 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 19:32:35 +0200 Subject: [PATCH 182/322] fix --- vyper/venom/function.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index a2e541112c..c9018ed3bf 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,5 +1,4 @@ -from collections.abc import Generator -from typing import Optional +from typing import Iterator, Optional from vyper.utils import OrderedSet from vyper.venom.basicblock import ( @@ -90,7 +89,7 @@ def get_basic_block_after(self, label: IRLabel) -> IRBasicBlock: return self.basic_blocks[i + 1] raise AssertionError(f"Basic block after '{label}' not found") - def get_terminal_basicblocks(self) -> Generator[IRBasicBlock]: + def get_terminal_basicblocks(self) -> Iterator[IRBasicBlock]: """ Get basic blocks that are terminal. """ From 4595b1d75107d23af2222726df33eec6c1787740 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 19:43:06 +0200 Subject: [PATCH 183/322] returndatacopy in reversed ops --- vyper/venom/ir_node_to_venom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c96ef5a6dc..d90e0f9e0f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -62,7 +62,6 @@ "gasprice", "gaslimit", "returndatasize", - "returndatacopy", "iload", "sload", "tload", @@ -92,6 +91,7 @@ "mcopy", "extcodecopy", "codecopy", + "returndatacopy", "revert", "istore", "sstore", From 6ed922f02474fc3aa23890e2aec30a033c9758c2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Mar 2024 19:54:04 +0200 Subject: [PATCH 184/322] lint --- vyper/venom/basicblock.py | 3 +-- vyper/venom/dominators.py | 4 ++-- vyper/venom/ir_node_to_venom.py | 2 +- vyper/venom/venom_to_assembly.py | 5 +++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index d275cbf448..2df436582b 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -308,10 +308,9 @@ def __repr__(self) -> str: operands = self.operands if opcode not in ["jmp", "jnz", "invoke"]: operands.reverse() - operands = ", ".join( + s += ", ".join( [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in operands] ) - s += operands if self.annotation: s += f" <{self.annotation}>" diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index cc9066788a..f8c6f5883f 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -111,11 +111,11 @@ def _compute_df(self): # for df in self.df[bb]: # print(" ", df.label) - def dominance_frontier(self, basic_blocks: list[IRBasicBlock]): + def dominance_frontier(self, basic_blocks: list[IRBasicBlock]) -> OrderedSet[IRBasicBlock]: """ Compute dominance frontier of a set of basic blocks. """ - df = OrderedSet() + df = OrderedSet[IRBasicBlock]() for bb in basic_blocks: df.update(self.df[bb]) return df diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index d90e0f9e0f..162a3278f4 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -208,7 +208,7 @@ def _convert_ir_simple_node( ) -> Optional[IRVariable]: args = [_convert_ir_bb(ctx, arg, symbols) for arg in ir.args] if reverse: - args = reversed(args) + args = reversed(args) # type: ignore return ctx.get_basic_block().append_instruction(ir.value, *args) # type: ignore diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 409d2b0d49..0b1876f845 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -270,7 +270,8 @@ def _generate_evm_for_basicblock_r( asm.append("JUMP") continue - next_liveness = main_insts[i + 1].liveness if i + 1 < len(main_insts) else [] + next_liveness = main_insts[i + 1].liveness if i + 1 < len(main_insts) else OrderedSet() + asm = self._generate_evm_for_instruction(asm, inst, stack, next_liveness) for bb in basicblock.reachable: @@ -327,7 +328,7 @@ def _generate_evm_for_instruction( next_liveness: OrderedSet = None, ) -> list[str]: if next_liveness is None: - next_liveness = [] + next_liveness = OrderedSet() opcode = inst.opcode # From 116bc7fa1b1e14dc226a4f5b1f54bfc38734619e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Mar 2024 13:38:21 -0500 Subject: [PATCH 185/322] fix lint --- vyper/compiler/phases.py | 6 +++--- vyper/venom/ir_node_to_venom.py | 1 + vyper/venom/venom_to_assembly.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 93ae9c974c..abf834946a 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -97,9 +97,6 @@ def __init__( no_bytecode_metadata: bool, optional Do not add metadata to bytecode. Defaults to False """ - # to force experimental codegen, uncomment: - if settings: - settings.experimental_codegen = True if isinstance(file_input, str): file_input = FileInput( @@ -117,6 +114,9 @@ def __init__( _ = self._generate_ast # force settings to be calculated + # to force experimental codegen, uncomment: + # self.settings.experimental_codegen = True + @cached_property def source_code(self): return self.file_input.source_code diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 162a3278f4..6700ed89dd 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,4 +1,5 @@ from typing import Optional + from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes from vyper.utils import MemoryPositions diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 0b1876f845..460935acb4 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,7 +1,7 @@ from collections import Counter from typing import Any -from vyper.exceptions import CompilerPanic +from vyper.exceptions import CompilerPanic from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, mksymbol, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet from vyper.venom.analysis import ( From d1fa29561127b6acb0fae512278258dac03307c3 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Mar 2024 18:43:42 +0000 Subject: [PATCH 186/322] update some tests --- tests/unit/compiler/venom/test_call.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/compiler/venom/test_call.py b/tests/unit/compiler/venom/test_call.py index a04a3922a1..99f6ebc451 100644 --- a/tests/unit/compiler/venom/test_call.py +++ b/tests/unit/compiler/venom/test_call.py @@ -4,15 +4,15 @@ @pytest.fixture def market_maker(get_contract): contract_code = """ -from ethereum.ercs import ERC20 +from ethereum.ercs import IERC20 unused: public(uint256) -token_address: ERC20 +token_address: IERC20 @external @payable def foo(token_addr: address, token_quantity: uint256): - self.token_address = ERC20(token_addr) + self.token_address = IERC20(token_addr) self.token_address.transferFrom(msg.sender, self, token_quantity) """ return get_contract(contract_code) From ad619b76a2ff8b3489ca1bce2ea51f1980f257d7 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Mar 2024 19:56:38 +0000 Subject: [PATCH 187/322] add --use-venom test option, venom_xfail fixture --- setup.cfg | 2 + tests/conftest.py | 62 +++++++++++++++---- .../builtins/codegen/test_abi_decode.py | 4 +- .../builtins/codegen/test_abi_encode.py | 4 ++ vyper/exceptions.py | 4 ++ vyper/venom/venom_to_assembly.py | 5 +- 6 files changed, 67 insertions(+), 14 deletions(-) diff --git a/setup.cfg b/setup.cfg index dd4a32a3ac..5b7051614c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,6 +35,8 @@ python_files = test_*.py testpaths = tests markers = fuzzing: Run Hypothesis fuzz test suite (deselect with '-m "not fuzzing"') + venom_xfail: mark a test case as a regression (expected to fail) under the venom pipeline + [tool:mypy] ignore_missing_imports = True diff --git a/tests/conftest.py b/tests/conftest.py index 201f723efa..cd52668f98 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -58,6 +58,7 @@ def pytest_addoption(parser): help="change optimization mode", ) parser.addoption("--enable-compiler-debug-mode", action="store_true") + parser.addoption("--use-venom", action="store_true") @pytest.fixture(scope="module") @@ -81,12 +82,33 @@ def debug(pytestconfig): _set_debug_mode(debug) +@pytest.fixture(scope="session") +def venom_pipeline(pytestconfig): + ret = pytestconfig.getoption("use_venom") + assert isinstance(ret, bool) + return ret + + +@pytest.fixture(autouse=True) +def check_venom_xfail(request, venom_pipeline): + if not venom_pipeline: + return + + marker = request.node.get_closest_marker("venom_xfail") + if marker is None: + return + + # https://github.com/okken/pytest-runtime-xfail?tab=readme-ov-file#alternatives + request.node.add_marker(pytest.mark.xfail(**marker.kwargs)) + + @pytest.fixture def chdir_tmp_path(tmp_path): with working_directory(tmp_path): yield +# CMC 2024-03-01 this doesn't need to be a fixture @pytest.fixture def keccak(): return Web3.keccak @@ -300,6 +322,7 @@ def _get_contract( w3, source_code, optimize, + venom_pipeline, output_formats, *args, override_opt_level=None, @@ -309,6 +332,7 @@ def _get_contract( settings = Settings() settings.evm_version = kwargs.pop("evm_version", None) settings.optimize = override_opt_level or optimize + settings.experimental_codegen = venom_pipeline out = compiler.compile_code( source_code, # test that all output formats can get generated @@ -332,17 +356,21 @@ def _get_contract( @pytest.fixture(scope="module") -def get_contract(w3, optimize, output_formats): +def get_contract(w3, optimize, venom_pipeline, output_formats): def fn(source_code, *args, **kwargs): - return _get_contract(w3, source_code, optimize, output_formats, *args, **kwargs) + return _get_contract( + w3, source_code, optimize, venom_pipeline, output_formats, *args, **kwargs + ) return fn @pytest.fixture -def get_contract_with_gas_estimation(tester, w3, optimize, output_formats): +def get_contract_with_gas_estimation(tester, w3, optimize, venom_pipeline, output_formats): def get_contract_with_gas_estimation(source_code, *args, **kwargs): - contract = _get_contract(w3, source_code, optimize, output_formats, *args, **kwargs) + contract = _get_contract( + w3, source_code, optimize, venom_pipeline, output_formats, *args, **kwargs + ) for abi_ in contract._classic_contract.functions.abi: if abi_["type"] == "function": set_decorator_to_contract_function(w3, tester, contract, source_code, abi_["name"]) @@ -352,15 +380,17 @@ def get_contract_with_gas_estimation(source_code, *args, **kwargs): @pytest.fixture -def get_contract_with_gas_estimation_for_constants(w3, optimize, output_formats): +def get_contract_with_gas_estimation_for_constants(w3, optimize, venom_pipeline, output_formats): def get_contract_with_gas_estimation_for_constants(source_code, *args, **kwargs): - return _get_contract(w3, source_code, optimize, output_formats, *args, **kwargs) + return _get_contract( + w3, source_code, optimize, venom_pipeline, output_formats, *args, **kwargs + ) return get_contract_with_gas_estimation_for_constants @pytest.fixture(scope="module") -def get_contract_module(optimize, output_formats): +def get_contract_module(optimize, venom_pipeline, output_formats): """ This fixture is used for Hypothesis tests to ensure that the same contract is called over multiple runs of the test. @@ -373,13 +403,21 @@ def get_contract_module(optimize, output_formats): w3.eth.set_gas_price_strategy(zero_gas_price_strategy) def get_contract_module(source_code, *args, **kwargs): - return _get_contract(w3, source_code, optimize, output_formats, *args, **kwargs) + return _get_contract( + w3, source_code, optimize, venom_pipeline, output_formats, *args, **kwargs + ) return get_contract_module def _deploy_blueprint_for( - w3, source_code, optimize, output_formats, initcode_prefix=ERC5202_PREFIX, **kwargs + w3, + source_code, + optimize, + venom_pipeline, + output_formats, + initcode_prefix=ERC5202_PREFIX, + **kwargs, ): settings = Settings() settings.evm_version = kwargs.pop("evm_version", None) @@ -419,9 +457,11 @@ def factory(address): @pytest.fixture(scope="module") -def deploy_blueprint_for(w3, optimize, output_formats): +def deploy_blueprint_for(w3, optimize, venom_pipeline, output_formats): def deploy_blueprint_for(source_code, *args, **kwargs): - return _deploy_blueprint_for(w3, source_code, optimize, output_formats, *args, **kwargs) + return _deploy_blueprint_for( + w3, source_code, optimize, venom_pipeline, output_formats, *args, **kwargs + ) return deploy_blueprint_for diff --git a/tests/functional/builtins/codegen/test_abi_decode.py b/tests/functional/builtins/codegen/test_abi_decode.py index 9dd9691aa5..0f5b9da4a6 100644 --- a/tests/functional/builtins/codegen/test_abi_decode.py +++ b/tests/functional/builtins/codegen/test_abi_decode.py @@ -3,7 +3,7 @@ import pytest from eth.codecs import abi -from vyper.exceptions import ArgumentException, StructureException +from vyper.exceptions import ArgumentException, StackTooDeep, StructureException TEST_ADDR = "0x" + b"".join(chr(i).encode("utf-8") for i in range(20)).hex() @@ -196,6 +196,7 @@ def abi_decode(x: Bytes[{len}]) -> DynArray[DynArray[uint256, 3], 3]: @pytest.mark.parametrize("args", nested_3d_array_args) @pytest.mark.parametrize("unwrap_tuple", (True, False)) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_abi_decode_nested_dynarray2(get_contract, args, unwrap_tuple): if unwrap_tuple is True: encoded = abi.encode("(uint256[][][])", (args,)) @@ -273,6 +274,7 @@ def foo(bs: Bytes[160]) -> (uint256, DynArray[uint256, 3]): assert c.foo(encoded) == [2**256 - 1, bs] +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_abi_decode_private_nested_dynarray(get_contract): code = """ bytez: DynArray[DynArray[DynArray[uint256, 3], 3], 3] diff --git a/tests/functional/builtins/codegen/test_abi_encode.py b/tests/functional/builtins/codegen/test_abi_encode.py index 2078cf65f3..9ff4ccb01c 100644 --- a/tests/functional/builtins/codegen/test_abi_encode.py +++ b/tests/functional/builtins/codegen/test_abi_encode.py @@ -3,6 +3,8 @@ import pytest from eth.codecs import abi +from vyper.exceptions import StackTooDeep + # @pytest.mark.parametrize("string", ["a", "abc", "abcde", "potato"]) def test_abi_encode(get_contract): @@ -226,6 +228,7 @@ def abi_encode( @pytest.mark.parametrize("args", nested_3d_array_args) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_abi_encode_nested_dynarray_2(get_contract, args): code = """ @external @@ -330,6 +333,7 @@ def foo(bs: DynArray[uint256, 3]) -> (uint256, Bytes[160]): assert c.foo(bs) == [2**256 - 1, abi.encode("(uint256[])", (bs,))] +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_abi_encode_private_nested_dynarray(get_contract): code = """ bytez: Bytes[1696] diff --git a/vyper/exceptions.py b/vyper/exceptions.py index ced249f247..445e67da14 100644 --- a/vyper/exceptions.py +++ b/vyper/exceptions.py @@ -385,6 +385,10 @@ class CodegenPanic(VyperInternalException): """Invalid code generated during codegen phase""" +class StackTooDeep(CodegenPanic): + """Stack too deep""" # (should not happen) + + class UnexpectedNodeType(VyperInternalException): """Unexpected AST node type.""" diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 460935acb4..db9d8397b9 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,7 +1,7 @@ from collections import Counter from typing import Any -from vyper.exceptions import CompilerPanic +from vyper.exceptions import CompilerPanic, StackTooDeep from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, mksymbol, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet from vyper.venom.analysis import ( @@ -535,7 +535,8 @@ def dup_op(self, assembly, stack, op): def _evm_swap_for(depth: int) -> str: swap_idx = -depth - assert 1 <= swap_idx <= 16, f"Unsupported swap depth {swap_idx}" + if not (1 <= swap_idx <= 16): + raise StackTooDeep(f"Unsupported swap depth {swap_idx}") return f"SWAP{swap_idx}" From d0ae1de90e22d6e14cc0c9d68dfe1993b5b64e0b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Mar 2024 20:07:01 +0000 Subject: [PATCH 188/322] fix a setting --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index cd52668f98..7eb628fb42 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -422,6 +422,7 @@ def _deploy_blueprint_for( settings = Settings() settings.evm_version = kwargs.pop("evm_version", None) settings.optimize = optimize + settings.experimental_codegen = venom_pipeline out = compiler.compile_code( source_code, output_formats=output_formats, From 28c10a69274eb2ae2852d6ead29bdb9f03bf1d99 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Mar 2024 20:23:26 +0000 Subject: [PATCH 189/322] add test pipeline for venom --- .github/workflows/test.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 51b220e5a0..e21898feb2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -82,14 +82,18 @@ jobs: # run in modes: --optimize [gas, none, codesize] opt-mode: ["gas", "none", "codesize"] debug: [true, false] + use-venom: [true, false] + # run across other python versions.# we don't really need to run all # modes across all python versions - one is enough include: - python-version: ["3.10", "310"] opt-mode: gas debug: false + use-venom: false + - name: py${{ matrix.python-version[1] }}-opt-${{ matrix.opt-mode }}${{ matrix.debug && '-debug' || '' }} + name: py${{ matrix.python-version[1] }}-opt-${{ matrix.opt-mode }}${{ matrix.debug && '-debug' || '' }}${{ matrix.use-venom && '-venom' || '' }} steps: - uses: actions/checkout@v4 @@ -107,7 +111,7 @@ jobs: run: pip install tox - name: Run Tox - run: TOXENV=py${{ matrix.python-version[1] }} tox -r -- --optimize ${{ matrix.opt-mode }} ${{ matrix.debug && '--enable-compiler-debug-mode' || '' }} -r aR tests/ + run: TOXENV=py${{ matrix.python-version[1] }} tox -r -- --optimize ${{ matrix.opt-mode }} ${{ matrix.debug && '--enable-compiler-debug-mode' || '' }} ${{ matrix.use-venom && '--use-venom' || '' }} -r aR tests/ - name: Upload Coverage uses: codecov/codecov-action@v4 From 60f0b6b8604e9fc4338fa590d3cc954ec69e31f5 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Mar 2024 20:23:39 +0000 Subject: [PATCH 190/322] mark more xfails --- tests/functional/codegen/features/test_clampers.py | 2 ++ tests/functional/codegen/features/test_constructor.py | 3 +++ tests/functional/codegen/features/test_immutable.py | 2 ++ tests/functional/codegen/types/test_dynamic_array.py | 3 +++ 4 files changed, 10 insertions(+) diff --git a/tests/functional/codegen/features/test_clampers.py b/tests/functional/codegen/features/test_clampers.py index c028805c6a..287ad6aae1 100644 --- a/tests/functional/codegen/features/test_clampers.py +++ b/tests/functional/codegen/features/test_clampers.py @@ -5,6 +5,7 @@ from eth_utils import keccak from vyper.evm.opcodes import EVM_VERSIONS +from vyper.exceptions import StackTooDeep from vyper.utils import int_bounds @@ -521,6 +522,7 @@ def foo(b: DynArray[int128, 10]) -> DynArray[int128, 10]: @pytest.mark.parametrize("value", [0, 1, -1, 2**127 - 1, -(2**127)]) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_multidimension_dynarray_clamper_passing(w3, get_contract, value): code = """ @external diff --git a/tests/functional/codegen/features/test_constructor.py b/tests/functional/codegen/features/test_constructor.py index 9146ace8a6..3052f61150 100644 --- a/tests/functional/codegen/features/test_constructor.py +++ b/tests/functional/codegen/features/test_constructor.py @@ -1,6 +1,8 @@ import pytest from web3.exceptions import ValidationError +from vyper.exceptions import StackTooDeep + def test_init_argument_test(get_contract_with_gas_estimation): init_argument_test = """ @@ -208,6 +210,7 @@ def get_foo() -> DynArray[DynArray[uint256, 3], 3]: assert c.get_foo() == [[37, 41, 73], [37041, 41073, 73037], [146, 123, 148]] +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_initialise_nested_dynamic_array_2(w3, get_contract_with_gas_estimation): code = """ foo: DynArray[DynArray[DynArray[int128, 3], 3], 3] diff --git a/tests/functional/codegen/features/test_immutable.py b/tests/functional/codegen/features/test_immutable.py index 49ff54b353..1c679853e7 100644 --- a/tests/functional/codegen/features/test_immutable.py +++ b/tests/functional/codegen/features/test_immutable.py @@ -1,6 +1,7 @@ import pytest from vyper.compiler.settings import OptimizationLevel +from vyper.exceptions import StackTooDeep @pytest.mark.parametrize( @@ -198,6 +199,7 @@ def get_idx_two() -> uint256: assert c.get_idx_two() == expected_values[2][2] +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_nested_dynarray_immutable(get_contract): code = """ my_list: immutable(DynArray[DynArray[DynArray[int128, 3], 3], 3]) diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index b55f07639b..b904be5f8c 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -5,6 +5,7 @@ from vyper.compiler import compile_code from vyper.exceptions import ( ArgumentException, + StackTooDeep, ArrayIndexException, ImmutableViolation, OverflowException, @@ -1478,6 +1479,7 @@ def foo(x: int128) -> int128: assert c.foo(7) == 392 +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_struct_of_lists(get_contract): code = """ struct Foo: @@ -1695,6 +1697,7 @@ def __init__(): ("DynArray[DynArray[DynArray[uint256, 5], 5], 5]", [[[], []], []]), ], ) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_empty_nested_dynarray(get_contract, typ, val): code = f""" @external From a8053031426f93fa3e4255e4fb651b0f86b08bac Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Mar 2024 20:30:55 +0000 Subject: [PATCH 191/322] fix lint --- tests/functional/codegen/types/test_dynamic_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index b904be5f8c..15686d8e5b 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -5,10 +5,10 @@ from vyper.compiler import compile_code from vyper.exceptions import ( ArgumentException, - StackTooDeep, ArrayIndexException, ImmutableViolation, OverflowException, + StackTooDeep, StateAccessViolation, TypeMismatch, ) From 619ab3dea916cf09396fd9948aa1bf7ded07ac07 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Mar 2024 20:31:59 +0000 Subject: [PATCH 192/322] update workflow file --- .github/workflows/test.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e21898feb2..daadd45edf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -82,16 +82,22 @@ jobs: # run in modes: --optimize [gas, none, codesize] opt-mode: ["gas", "none", "codesize"] debug: [true, false] - use-venom: [true, false] - # run across other python versions.# we don't really need to run all - # modes across all python versions - one is enough + # run across other python versions. + # (we don't really need to run all + # modes across all python versions - one is enough) include: - python-version: ["3.10", "310"] opt-mode: gas debug: false use-venom: false + - python-version: ["3.11", "311"] + opt-mode: gas + debug: false + use-venom: true + + name: py${{ matrix.python-version[1] }}-opt-${{ matrix.opt-mode }}${{ matrix.debug && '-debug' || '' }}${{ matrix.use-venom && '-venom' || '' }} From b0785f877dbcc871c152492bc40235112f43357f Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Mar 2024 20:45:10 +0000 Subject: [PATCH 193/322] some more xfails --- tests/conftest.py | 10 ++++++++++ tests/functional/codegen/types/test_dynamic_array.py | 7 +++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7eb628fb42..833f44b244 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -102,6 +102,16 @@ def check_venom_xfail(request, venom_pipeline): request.node.add_marker(pytest.mark.xfail(**marker.kwargs)) +@pytest.fixture +def venom_xfail(request, venom_pipeline): + def _xfail(*args, **kwargs): + if not venom_pipeline: + return + request.node.add_marker(pytest.mark.xfail(*args, **kwargs)) + + return _xfail + + @pytest.fixture def chdir_tmp_path(tmp_path): with working_directory(tmp_path): diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index 15686d8e5b..64ef8f90d4 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -733,6 +733,7 @@ def test_array_decimal_return3() -> DynArray[DynArray[decimal, 2], 2]: assert c.test_array_decimal_return3() == [[1.0, 2.0], [3.0]] +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_mult_list(get_contract_with_gas_estimation): code = """ nest3: DynArray[DynArray[DynArray[uint256, 2], 2], 2] @@ -1568,6 +1569,7 @@ def bar(x: int128) -> DynArray[int128, 3]: assert c.bar(7) == [7, 14] +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) def test_nested_struct_of_lists(get_contract, assert_compile_failed, optimize): code = """ struct nestedFoo: @@ -1697,8 +1699,9 @@ def __init__(): ("DynArray[DynArray[DynArray[uint256, 5], 5], 5]", [[[], []], []]), ], ) -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) -def test_empty_nested_dynarray(get_contract, typ, val): +def test_empty_nested_dynarray(get_contract, typ, val, venom_xfail): + if val == [[[], []], []]: + venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) code = f""" @external def foo() -> {typ}: From d7b39b6e3da088d02fb4f541289c50f622731a59 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 2 Mar 2024 11:44:20 -0500 Subject: [PATCH 194/322] add `#pragma experimental-codegen` --- vyper/ast/pre_parser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/ast/pre_parser.py b/vyper/ast/pre_parser.py index 159dfc0ace..4b71350523 100644 --- a/vyper/ast/pre_parser.py +++ b/vyper/ast/pre_parser.py @@ -192,6 +192,10 @@ def pre_parse(code: str) -> tuple[Settings, ModificationOffsets, dict, str]: if evm_version not in EVM_VERSIONS: raise StructureException("Invalid evm version: `{evm_version}`", start) settings.evm_version = evm_version + elif pragma.startswith("experimental-codegen"): + if settings.experimental_codegen is not None: + raise StructureException("pragma experimental-codegen specified twice!", start) + settings.experimental_codegen = True else: raise StructureException(f"Unknown pragma `{pragma.split()[0]}`") From 9f595ca11edf1ff5e67aa310205535a5ad9b461d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Mar 2024 09:48:15 +0200 Subject: [PATCH 195/322] cleanup make_ssa test --- tests/unit/compiler/venom/test_make_ssa.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/unit/compiler/venom/test_make_ssa.py b/tests/unit/compiler/venom/test_make_ssa.py index 9400800ba5..9d4d5e5407 100644 --- a/tests/unit/compiler/venom/test_make_ssa.py +++ b/tests/unit/compiler/venom/test_make_ssa.py @@ -33,10 +33,3 @@ def test_phi_case(): calculate_cfg(ctx) MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) calculate_liveness(ctx) - # _optimize_unused_variables(ctx) - # calculate_liveness(ctx) - print(ctx.as_graph()) - - -if __name__ == "__main__": - test_phi_case() From 04260acd2407a04cc17049e48ccf49ce53abce06 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 5 Mar 2024 18:20:58 +0200 Subject: [PATCH 196/322] fix case where cfg_in empty on non entry nodes --- vyper/venom/dominators.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index f8c6f5883f..b3051b9c22 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -56,10 +56,9 @@ def _compute_dominators(self): if bb == self.entry: continue preds = bb.cfg_in - if len(preds) > 0: - new_dominators = OrderedSet.intersection( - *[self.dominators[pred] for pred in preds] - ) + if len(preds) == 0: + continue + new_dominators = OrderedSet.intersection(*[self.dominators[pred] for pred in preds]) new_dominators.add(bb) if new_dominators != self.dominators[bb]: self.dominators[bb] = new_dominators From a0ff82864ece39fe4553c29a3251bed1a967243b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 5 Mar 2024 08:21:31 -0800 Subject: [PATCH 197/322] force strict=True for venom_xfail --- tests/conftest.py | 4 ++-- tests/functional/builtins/codegen/test_abi_decode.py | 4 ++-- tests/functional/builtins/codegen/test_abi_encode.py | 4 ++-- tests/functional/codegen/features/test_clampers.py | 2 +- tests/functional/codegen/features/test_constructor.py | 2 +- tests/functional/codegen/features/test_immutable.py | 2 +- tests/functional/codegen/types/test_dynamic_array.py | 8 ++++---- vyper/ast/pre_parser.py | 4 +++- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 71da54136b..1bc6e71a4a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -99,7 +99,7 @@ def check_venom_xfail(request, venom_pipeline): return # https://github.com/okken/pytest-runtime-xfail?tab=readme-ov-file#alternatives - request.node.add_marker(pytest.mark.xfail(**marker.kwargs)) + request.node.add_marker(pytest.mark.xfail(strict=True, **marker.kwargs)) @pytest.fixture @@ -107,7 +107,7 @@ def venom_xfail(request, venom_pipeline): def _xfail(*args, **kwargs): if not venom_pipeline: return - request.node.add_marker(pytest.mark.xfail(*args, **kwargs)) + request.node.add_marker(pytest.mark.xfail(*args, strict=True, **kwargs)) return _xfail diff --git a/tests/functional/builtins/codegen/test_abi_decode.py b/tests/functional/builtins/codegen/test_abi_decode.py index 0f5b9da4a6..c73e13ca08 100644 --- a/tests/functional/builtins/codegen/test_abi_decode.py +++ b/tests/functional/builtins/codegen/test_abi_decode.py @@ -196,7 +196,7 @@ def abi_decode(x: Bytes[{len}]) -> DynArray[DynArray[uint256, 3], 3]: @pytest.mark.parametrize("args", nested_3d_array_args) @pytest.mark.parametrize("unwrap_tuple", (True, False)) -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_abi_decode_nested_dynarray2(get_contract, args, unwrap_tuple): if unwrap_tuple is True: encoded = abi.encode("(uint256[][][])", (args,)) @@ -274,7 +274,7 @@ def foo(bs: Bytes[160]) -> (uint256, DynArray[uint256, 3]): assert c.foo(encoded) == [2**256 - 1, bs] -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_abi_decode_private_nested_dynarray(get_contract): code = """ bytez: DynArray[DynArray[DynArray[uint256, 3], 3], 3] diff --git a/tests/functional/builtins/codegen/test_abi_encode.py b/tests/functional/builtins/codegen/test_abi_encode.py index 9ff4ccb01c..3cf3c2b91e 100644 --- a/tests/functional/builtins/codegen/test_abi_encode.py +++ b/tests/functional/builtins/codegen/test_abi_encode.py @@ -228,7 +228,7 @@ def abi_encode( @pytest.mark.parametrize("args", nested_3d_array_args) -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_abi_encode_nested_dynarray_2(get_contract, args): code = """ @external @@ -333,7 +333,7 @@ def foo(bs: DynArray[uint256, 3]) -> (uint256, Bytes[160]): assert c.foo(bs) == [2**256 - 1, abi.encode("(uint256[])", (bs,))] -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_abi_encode_private_nested_dynarray(get_contract): code = """ bytez: Bytes[1696] diff --git a/tests/functional/codegen/features/test_clampers.py b/tests/functional/codegen/features/test_clampers.py index 287ad6aae1..8b2e3867ea 100644 --- a/tests/functional/codegen/features/test_clampers.py +++ b/tests/functional/codegen/features/test_clampers.py @@ -522,7 +522,7 @@ def foo(b: DynArray[int128, 10]) -> DynArray[int128, 10]: @pytest.mark.parametrize("value", [0, 1, -1, 2**127 - 1, -(2**127)]) -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_multidimension_dynarray_clamper_passing(w3, get_contract, value): code = """ @external diff --git a/tests/functional/codegen/features/test_constructor.py b/tests/functional/codegen/features/test_constructor.py index 3052f61150..465b2e9963 100644 --- a/tests/functional/codegen/features/test_constructor.py +++ b/tests/functional/codegen/features/test_constructor.py @@ -210,7 +210,7 @@ def get_foo() -> DynArray[DynArray[uint256, 3], 3]: assert c.get_foo() == [[37, 41, 73], [37041, 41073, 73037], [146, 123, 148]] -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_initialise_nested_dynamic_array_2(w3, get_contract_with_gas_estimation): code = """ foo: DynArray[DynArray[DynArray[int128, 3], 3], 3] diff --git a/tests/functional/codegen/features/test_immutable.py b/tests/functional/codegen/features/test_immutable.py index 1c679853e7..874600633a 100644 --- a/tests/functional/codegen/features/test_immutable.py +++ b/tests/functional/codegen/features/test_immutable.py @@ -199,7 +199,7 @@ def get_idx_two() -> uint256: assert c.get_idx_two() == expected_values[2][2] -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_nested_dynarray_immutable(get_contract): code = """ my_list: immutable(DynArray[DynArray[DynArray[int128, 3], 3], 3]) diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index 64ef8f90d4..1a3bf17295 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -733,7 +733,7 @@ def test_array_decimal_return3() -> DynArray[DynArray[decimal, 2], 2]: assert c.test_array_decimal_return3() == [[1.0, 2.0], [3.0]] -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_mult_list(get_contract_with_gas_estimation): code = """ nest3: DynArray[DynArray[DynArray[uint256, 2], 2], 2] @@ -1480,7 +1480,7 @@ def foo(x: int128) -> int128: assert c.foo(7) == 392 -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_struct_of_lists(get_contract): code = """ struct Foo: @@ -1569,7 +1569,7 @@ def bar(x: int128) -> DynArray[int128, 3]: assert c.bar(7) == [7, 14] -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_nested_struct_of_lists(get_contract, assert_compile_failed, optimize): code = """ struct nestedFoo: @@ -1701,7 +1701,7 @@ def __init__(): ) def test_empty_nested_dynarray(get_contract, typ, val, venom_xfail): if val == [[[], []], []]: - venom_xfail(raises=StackTooDeep, reason="stack scheduler regression", strict=True) + venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") code = f""" @external def foo() -> {typ}: diff --git a/vyper/ast/pre_parser.py b/vyper/ast/pre_parser.py index 4b71350523..edb284bbb9 100644 --- a/vyper/ast/pre_parser.py +++ b/vyper/ast/pre_parser.py @@ -194,7 +194,9 @@ def pre_parse(code: str) -> tuple[Settings, ModificationOffsets, dict, str]: settings.evm_version = evm_version elif pragma.startswith("experimental-codegen"): if settings.experimental_codegen is not None: - raise StructureException("pragma experimental-codegen specified twice!", start) + raise StructureException( + "pragma experimental-codegen specified twice!", start + ) settings.experimental_codegen = True else: From 43a11c227f60ede182483f3391f55303a5f58094 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 7 Mar 2024 23:50:52 +0200 Subject: [PATCH 198/322] cleanup --- vyper/venom/ir_node_to_venom.py | 40 ++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 6700ed89dd..8825182467 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -3,6 +3,7 @@ from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes from vyper.utils import MemoryPositions +from vyper.venom.analysis import calculate_cfg, calculate_liveness from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, @@ -127,6 +128,13 @@ def ir_node_to_venom(ir: IRnode) -> IRFunction: else: bb.append_instruction("stop") + # calculate_cfg(ctx) + # calculate_liveness(ctx) + # print(ctx.as_graph()) + # import sys + + # sys.exit() + return ctx @@ -225,6 +233,9 @@ def _convert_ir_bb_list(ctx, ir, symbols): return ret +current_func = None + + def _convert_ir_bb(ctx, ir, symbols): assert isinstance(ir, IRnode), ir global _break_target, _continue_target @@ -254,19 +265,22 @@ def _convert_ir_bb(ctx, ir, symbols): return None if ir.is_self_call: return _handle_self_call(ctx, ir, symbols) - elif ir.args[0].value == "label" and ir.args[0].args[0].value.startswith("internal"): - # Internal definition - var_list = ir.args[0].args[1] - does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args - symbols = {} - ir = _handle_internal_func(ctx, ir, does_return_data, symbols) - for ir_node in ir.args: - ret = _convert_ir_bb(ctx, ir_node, symbols) - - return ret - elif ir.args[0].value == "label" and ir.args[0].args[0].value.startswith("external"): - ret = _convert_ir_bb(ctx, ir.args[0], symbols) - _append_return_args(ctx) + elif ir.args[0].value == "label": + is_external = ir.args[0].args[0].value.startswith("external") + is_internal = ir.args[0].args[0].value.startswith("internal") + if is_internal: + # Internal definition + var_list = ir.args[0].args[1] + does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args + symbols = {} + ir = _handle_internal_func(ctx, ir, does_return_data, symbols) + for ir_node in ir.args: + ret = _convert_ir_bb(ctx, ir_node, symbols) + + return ret + elif is_external: + ret = _convert_ir_bb(ctx, ir.args[0], symbols) + _append_return_args(ctx) else: ret = _convert_ir_bb(ctx, ir.args[0], symbols) From 6b74b20f513a5b73ae130b04a95196de7dcc3e0e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Mar 2024 00:03:07 +0200 Subject: [PATCH 199/322] var list --- vyper/venom/ir_node_to_venom.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 8825182467..9185b3ee15 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -234,11 +234,12 @@ def _convert_ir_bb_list(ctx, ir, symbols): current_func = None +var_list = [] def _convert_ir_bb(ctx, ir, symbols): assert isinstance(ir, IRnode), ir - global _break_target, _continue_target + global _break_target, _continue_target, current_func, var_list if ir.value in _BINARY_IR_INSTRUCTIONS: return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3_64"]) @@ -253,6 +254,7 @@ def _convert_ir_bb(ctx, ir, symbols): elif ir.value in PASS_THROUGH_REVERSED_INSTRUCTIONS: return _convert_ir_simple_node(ctx, ir, symbols, reverse=True) elif ir.value == "return": + _append_return_args(ctx, *var_list) ctx.get_basic_block().append_instruction( "return", IRVariable("ret_size"), IRVariable("ret_ofst") ) @@ -266,8 +268,9 @@ def _convert_ir_bb(ctx, ir, symbols): if ir.is_self_call: return _handle_self_call(ctx, ir, symbols) elif ir.args[0].value == "label": - is_external = ir.args[0].args[0].value.startswith("external") - is_internal = ir.args[0].args[0].value.startswith("internal") + current_func = ir.args[0].args[0].value + is_external = current_func.startswith("external") + is_internal = current_func.startswith("internal") if is_internal: # Internal definition var_list = ir.args[0].args[1] @@ -431,12 +434,10 @@ def _convert_ir_bb(ctx, ir, symbols): if bb.is_terminated: bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) ctx.append_basic_block(bb) - ret_ofst, ret_size = _convert_ir_bb_list(ctx, ir.args[1:], symbols) - - _append_return_args(ctx, ret_ofst, ret_size) + var_list = _convert_ir_bb_list(ctx, ir.args[1:], symbols) bb = ctx.get_basic_block() - assert ret_ofst is not None + # assert ret_ofst is not None bb.append_instruction("jmp", label) From 9d7336de9a753ad6a2b974243a0a56a784761537 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Mar 2024 00:10:26 +0200 Subject: [PATCH 200/322] more fixes --- vyper/compiler/phases.py | 2 +- vyper/venom/ir_node_to_venom.py | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index abf834946a..c6d8bb65ca 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -115,7 +115,7 @@ def __init__( _ = self._generate_ast # force settings to be calculated # to force experimental codegen, uncomment: - # self.settings.experimental_codegen = True + self.settings.experimental_codegen = True @cached_property def source_code(self): diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 9185b3ee15..cb0a528929 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -107,6 +107,8 @@ SymbolTable = dict[str, Optional[IROperand]] +count = 0 + # convert IRnode directly to venom def ir_node_to_venom(ir: IRnode) -> IRFunction: @@ -128,12 +130,16 @@ def ir_node_to_venom(ir: IRnode) -> IRFunction: else: bb.append_instruction("stop") - # calculate_cfg(ctx) - # calculate_liveness(ctx) - # print(ctx.as_graph()) - # import sys + # global count + # if count == 1: + # calculate_cfg(ctx) + # calculate_liveness(ctx) + # print(ctx.as_graph()) + # import sys + + # sys.exit() - # sys.exit() + # count += 1 return ctx @@ -437,9 +443,8 @@ def _convert_ir_bb(ctx, ir, symbols): var_list = _convert_ir_bb_list(ctx, ir.args[1:], symbols) bb = ctx.get_basic_block() - # assert ret_ofst is not None - bb.append_instruction("jmp", label) + # bb.append_instruction("jmp", label) elif is_internal: assert ir.args[1].value == "return_pc", "return_pc not found" From bbb25fa68c97d07652cbd55cb266deaf39ad105b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Mar 2024 00:35:37 +0200 Subject: [PATCH 201/322] temp --- vyper/venom/ir_node_to_venom.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index cb0a528929..783d9c9a8a 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,5 +1,5 @@ from typing import Optional - +import re from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes from vyper.utils import MemoryPositions @@ -131,7 +131,7 @@ def ir_node_to_venom(ir: IRnode) -> IRFunction: bb.append_instruction("stop") # global count - # if count == 1: + # if count == 0: # calculate_cfg(ctx) # calculate_liveness(ctx) # print(ctx.as_graph()) @@ -277,7 +277,7 @@ def _convert_ir_bb(ctx, ir, symbols): current_func = ir.args[0].args[0].value is_external = current_func.startswith("external") is_internal = current_func.startswith("internal") - if is_internal: + if is_internal or len(re.findall(r"external.*__init__\(.*_deploy", current_func)) > 0: # Internal definition var_list = ir.args[0].args[1] does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args @@ -289,7 +289,6 @@ def _convert_ir_bb(ctx, ir, symbols): return ret elif is_external: ret = _convert_ir_bb(ctx, ir.args[0], symbols) - _append_return_args(ctx) else: ret = _convert_ir_bb(ctx, ir.args[0], symbols) @@ -426,10 +425,18 @@ def _convert_ir_bb(ctx, ir, symbols): bb.append_instruction("jmp", label) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) - _convert_ir_bb(ctx, ir.args[2], symbols) + code = ir.args[2] + if code.value == "pass": + bb.append_instruction("stop") + else: + _convert_ir_bb(ctx, code, symbols) elif ir.value == "exit_to": label = IRLabel(ir.args[0].value) + if label.value == "return_pc": + ctx.get_basic_block().append_instruction("stop") + return None + is_constructor = "__init__(" in label.name is_external = label.name.startswith("external") and not is_constructor is_internal = label.name.startswith("internal") @@ -441,15 +448,11 @@ def _convert_ir_bb(ctx, ir, symbols): bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) ctx.append_basic_block(bb) var_list = _convert_ir_bb_list(ctx, ir.args[1:], symbols) - - bb = ctx.get_basic_block() - - # bb.append_instruction("jmp", label) - elif is_internal: assert ir.args[1].value == "return_pc", "return_pc not found" # TODO: never passing return values with the new convention bb.append_instruction("ret", symbols["return_pc"]) + elif ir.value == "dload": arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) bb = ctx.get_basic_block() @@ -565,7 +568,9 @@ def emit_body_blocks(): ctx.append_basic_block(exit_block) cond_block.append_instruction("jnz", cont_ret, exit_block.label, body_block.label) - elif ir.value in ["cleanup_repeat", "pass"]: + elif ir.value == "cleanup_repeat": + pass + elif ir.value == "pass": pass elif ir.value == "break": assert _break_target is not None, "Break with no break target" From 94e7328e426d64003826f3a7d489ac00b6365c76 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Mar 2024 13:03:07 +0200 Subject: [PATCH 202/322] fix lib issues --- vyper/venom/__init__.py | 6 +++- vyper/venom/ir_node_to_venom.py | 56 ++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 8e8cbf5f0c..dd5734c69b 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -44,7 +44,11 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: ir_pass_optimize_empty_blocks(ctx) ir_pass_remove_unreachable_blocks(ctx) - internals = [bb for bb in ctx.basic_blocks if bb.label.value.startswith("internal")] + internals = [ + bb + for bb in ctx.basic_blocks + if bb.label.value.startswith("internal") and len(bb.cfg_in) == 0 + ] MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) for entry in internals: MakeSSA.run_pass(ctx, entry) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 783d9c9a8a..2ed8a300d9 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -131,7 +131,9 @@ def ir_node_to_venom(ir: IRnode) -> IRFunction: bb.append_instruction("stop") # global count - # if count == 0: + # if count == 1: + # calculate_cfg(ctx) + # ctx.remove_unreachable_blocks() # calculate_cfg(ctx) # calculate_liveness(ctx) # print(ctx.as_graph()) @@ -215,7 +217,7 @@ def _handle_internal_func( symbols["return_pc"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_pc" - return ir.args[0].args[2] + return _convert_ir_bb(ctx, ir.args[0].args[2], symbols) def _convert_ir_simple_node( @@ -282,8 +284,8 @@ def _convert_ir_bb(ctx, ir, symbols): var_list = ir.args[0].args[1] does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args symbols = {} - ir = _handle_internal_func(ctx, ir, does_return_data, symbols) - for ir_node in ir.args: + _ir = _handle_internal_func(ctx, ir, does_return_data, symbols) + for ir_node in ir.args[1:]: ret = _convert_ir_bb(ctx, ir_node, symbols) return ret @@ -431,27 +433,37 @@ def _convert_ir_bb(ctx, ir, symbols): else: _convert_ir_bb(ctx, code, symbols) elif ir.value == "exit_to": - label = IRLabel(ir.args[0].value) + args = _convert_ir_bb_list(ctx, ir.args[1:], symbols) + var_list = args + bb = ctx.get_basic_block() + if bb.is_terminated: + bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) + ctx.append_basic_block(bb) + bb = ctx.get_basic_block() + label = IRLabel(ir.args[0].value) if label.value == "return_pc": - ctx.get_basic_block().append_instruction("stop") - return None + label = symbols.get("return_pc") + bb.append_instruction("ret", label) + else: + bb.append_instruction("jmp", label) - is_constructor = "__init__(" in label.name - is_external = label.name.startswith("external") and not is_constructor - is_internal = label.name.startswith("internal") + is_constructor = "__init__(" in current_func - bb = ctx.get_basic_block() - if is_external: - if len(ir.args) > 1: # no return value - if bb.is_terminated: - bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) - ctx.append_basic_block(bb) - var_list = _convert_ir_bb_list(ctx, ir.args[1:], symbols) - elif is_internal: - assert ir.args[1].value == "return_pc", "return_pc not found" - # TODO: never passing return values with the new convention - bb.append_instruction("ret", symbols["return_pc"]) + # is_external = label.name.startswith("external") and not is_constructor + # is_internal = label.name.startswith("internal") + + # bb = ctx.get_basic_block() + # if is_external: + # if len(ir.args) > 1: + # if bb.is_terminated: + # bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) + # ctx.append_basic_block(bb) + # var_list = _convert_ir_bb_list(ctx, ir.args[1:], symbols) + # elif is_internal: + # assert ir.args[1].value == "return_pc", "return_pc not found" + # # TODO: never passing return values with the new convention + # bb.append_instruction("ret", symbols["return_pc"]) elif ir.value == "dload": arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) @@ -580,6 +592,8 @@ def emit_body_blocks(): assert _continue_target is not None, "Continue with no contrinue target" ctx.get_basic_block().append_instruction("jmp", _continue_target.label) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) + elif ir.value == "var_list": + pass elif isinstance(ir.value, str) and ir.value.startswith("log"): args = reversed([_convert_ir_bb(ctx, arg, symbols) for arg in ir.args]) topic_count = int(ir.value[3:]) From 7a650dfc7a7f0bd0e7ab0e711ba23c273feada49 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Mar 2024 13:06:48 +0200 Subject: [PATCH 203/322] cleanup --- vyper/venom/ir_node_to_venom.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 2ed8a300d9..e31ba4d53b 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,5 +1,6 @@ -from typing import Optional import re +from typing import Optional + from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes from vyper.utils import MemoryPositions @@ -107,8 +108,6 @@ SymbolTable = dict[str, Optional[IROperand]] -count = 0 - # convert IRnode directly to venom def ir_node_to_venom(ir: IRnode) -> IRFunction: @@ -130,19 +129,6 @@ def ir_node_to_venom(ir: IRnode) -> IRFunction: else: bb.append_instruction("stop") - # global count - # if count == 1: - # calculate_cfg(ctx) - # ctx.remove_unreachable_blocks() - # calculate_cfg(ctx) - # calculate_liveness(ctx) - # print(ctx.as_graph()) - # import sys - - # sys.exit() - - # count += 1 - return ctx @@ -204,7 +190,7 @@ def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> Opti def _handle_internal_func( ctx: IRFunction, ir: IRnode, does_return_data: bool, symbols: SymbolTable -) -> IRnode: +): bb = IRBasicBlock(IRLabel(ir.args[0].args[0].value, True), ctx) # type: ignore bb = ctx.append_basic_block(bb) @@ -217,7 +203,7 @@ def _handle_internal_func( symbols["return_pc"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_pc" - return _convert_ir_bb(ctx, ir.args[0].args[2], symbols) + _convert_ir_bb(ctx, ir.args[0].args[2], symbols) def _convert_ir_simple_node( @@ -242,7 +228,7 @@ def _convert_ir_bb_list(ctx, ir, symbols): current_func = None -var_list = [] +var_list: list[str] = [] def _convert_ir_bb(ctx, ir, symbols): @@ -284,7 +270,7 @@ def _convert_ir_bb(ctx, ir, symbols): var_list = ir.args[0].args[1] does_return_data = IRnode.from_list(["return_buffer"]) in var_list.args symbols = {} - _ir = _handle_internal_func(ctx, ir, does_return_data, symbols) + _handle_internal_func(ctx, ir, does_return_data, symbols) for ir_node in ir.args[1:]: ret = _convert_ir_bb(ctx, ir_node, symbols) From b828a86f9c8b7f3f6bf49ed51d35a35417aa514d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Mar 2024 16:26:58 +0200 Subject: [PATCH 204/322] fix --- vyper/venom/ir_node_to_venom.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index e31ba4d53b..dbe98db3c1 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -248,7 +248,6 @@ def _convert_ir_bb(ctx, ir, symbols): elif ir.value in PASS_THROUGH_REVERSED_INSTRUCTIONS: return _convert_ir_simple_node(ctx, ir, symbols, reverse=True) elif ir.value == "return": - _append_return_args(ctx, *var_list) ctx.get_basic_block().append_instruction( "return", IRVariable("ret_size"), IRVariable("ret_ofst") ) @@ -277,6 +276,7 @@ def _convert_ir_bb(ctx, ir, symbols): return ret elif is_external: ret = _convert_ir_bb(ctx, ir.args[0], symbols) + _append_return_args(ctx) else: ret = _convert_ir_bb(ctx, ir.args[0], symbols) @@ -421,6 +421,7 @@ def _convert_ir_bb(ctx, ir, symbols): elif ir.value == "exit_to": args = _convert_ir_bb_list(ctx, ir.args[1:], symbols) var_list = args + _append_return_args(ctx, *var_list) bb = ctx.get_basic_block() if bb.is_terminated: bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) @@ -434,23 +435,6 @@ def _convert_ir_bb(ctx, ir, symbols): else: bb.append_instruction("jmp", label) - is_constructor = "__init__(" in current_func - - # is_external = label.name.startswith("external") and not is_constructor - # is_internal = label.name.startswith("internal") - - # bb = ctx.get_basic_block() - # if is_external: - # if len(ir.args) > 1: - # if bb.is_terminated: - # bb = IRBasicBlock(ctx.get_next_label("exit_to"), ctx) - # ctx.append_basic_block(bb) - # var_list = _convert_ir_bb_list(ctx, ir.args[1:], symbols) - # elif is_internal: - # assert ir.args[1].value == "return_pc", "return_pc not found" - # # TODO: never passing return values with the new convention - # bb.append_instruction("ret", symbols["return_pc"]) - elif ir.value == "dload": arg_0 = _convert_ir_bb(ctx, ir.args[0], symbols) bb = ctx.get_basic_block() From 338dc09bffcd5ab97379efb88094154d34ca2172 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Mar 2024 19:49:39 +0200 Subject: [PATCH 205/322] remove unused imports --- vyper/venom/ir_node_to_venom.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index dbe98db3c1..95ffd0a90e 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -4,7 +4,6 @@ from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes from vyper.utils import MemoryPositions -from vyper.venom.analysis import calculate_cfg, calculate_liveness from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, From 14450967d1b7cdc8b1e772e25558619baabb4f4a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Mar 2024 21:00:56 +0200 Subject: [PATCH 206/322] codesize dispatcher --- vyper/venom/ir_node_to_venom.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 95ffd0a90e..281950a6d2 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -142,11 +142,12 @@ def _convert_binary_op( def _append_jmp(ctx: IRFunction, label: IRLabel) -> None: - ctx.get_basic_block().append_instruction("jmp", label) + bb = ctx.get_basic_block() + if bb.is_terminated: + bb = IRBasicBlock(ctx.get_next_label("jmp_target"), ctx) + ctx.append_basic_block(bb) - label = ctx.get_next_label() - bb = IRBasicBlock(label, ctx) - ctx.append_basic_block(bb) + bb.append_instruction("jmp", label) def _new_block(ctx: IRFunction) -> IRBasicBlock: @@ -400,7 +401,7 @@ def _convert_ir_bb(ctx, ir, symbols): if isinstance(c, int): assert 0 <= c <= 255, "data with invalid size" ctx.append_data("db", [c]) # type: ignore - elif isinstance(c, bytes): + elif isinstance(c.value, bytes): ctx.append_data("db", [c]) # type: ignore elif isinstance(c, IRnode): data = _convert_ir_bb(ctx, c, symbols) From b01c7309eb34e00e155fb0c1c019fa2fdf1efc30 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Mar 2024 21:14:30 +0200 Subject: [PATCH 207/322] Update dense selector to emit the target constrained djump instruction --- vyper/codegen/module.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/codegen/module.py b/vyper/codegen/module.py index 251deeac83..1844569138 100644 --- a/vyper/codegen/module.py +++ b/vyper/codegen/module.py @@ -229,7 +229,9 @@ def _selector_section_dense(external_functions, module_t): error_msg="bad calldatasize or callvalue", ) x.append(check_entry_conditions) - x.append(["jump", function_label]) + jump_targets = [func.args[0].value for func in function_irs] + jump_instr = IRnode.from_list(["djump", function_label, *jump_targets]) + x.append(jump_instr) selector_section.append(b1.resolve(x)) bucket_headers = ["data", "BUCKET_HEADERS"] From e8c8ba0ef7f1d3394672cc42cafb2488c716434e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Mar 2024 21:14:44 +0200 Subject: [PATCH 208/322] handle raw data --- vyper/venom/venom_to_assembly.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index db9d8397b9..19bdcbc32e 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -169,7 +169,11 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: label = inst.operands[0].value data_segments[label] = [DataHeader(f"_sym_{label}")] elif inst.opcode == "db": - data_segments[label].append(f"_sym_{inst.operands[0].value}") + data = inst.operands[0].value + if isinstance(data, bytes): + data_segments[label].append(data) + else: + data_segments[label].append(f"_sym_{inst.operands[0].value}") asm.extend(list(data_segments.values())) From 862bcd8161ab4dba76865c79ec3e6b94a0449b69 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Mar 2024 00:27:03 +0200 Subject: [PATCH 209/322] fix test_assert_reason_revert_length --- vyper/venom/ir_node_to_venom.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 281950a6d2..557d01c35e 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -278,6 +278,10 @@ def _convert_ir_bb(ctx, ir, symbols): ret = _convert_ir_bb(ctx, ir.args[0], symbols) _append_return_args(ctx) else: + bb = ctx.get_basic_block() + if bb.is_terminated: + bb = IRBasicBlock(ctx.get_next_label("seq"), ctx) + ctx.append_basic_block(bb) ret = _convert_ir_bb(ctx, ir.args[0], symbols) for ir_node in ir.args[1:]: From 3ebd99eed5354c9e88dc823ead135767d6a5f1a3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Mar 2024 12:36:02 +0200 Subject: [PATCH 210/322] pass down source pos --- vyper/venom/basicblock.py | 10 ++++++++++ vyper/venom/function.py | 28 ++++++++++++++++++++++++++++ vyper/venom/ir_node_to_venom.py | 15 +++++++++++++++ vyper/venom/venom_to_assembly.py | 30 ++++++++++++++++++++++++------ 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 2df436582b..9e4bdfe69b 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -222,6 +222,8 @@ class IRInstruction: parent: Optional["IRBasicBlock"] fence_id: int annotation: Optional[str] + source_pos: int + error_msg: str def __init__( self, @@ -240,6 +242,8 @@ def __init__( self.parent = None self.fence_id = -1 self.annotation = None + self.source_pos = None + self.error_msg = None def get_label_operands(self) -> list[IRLabel]: """ @@ -411,6 +415,8 @@ def append_instruction( inst = IRInstruction(opcode, inst_args, ret) inst.parent = self + inst.source_pos = self.parent.source_pos + inst.error_msg = self.parent.error_msg self.instructions.append(inst) return ret @@ -432,6 +438,8 @@ def append_invoke_instruction( inst = IRInstruction("invoke", inst_args, ret) inst.parent = self + inst.source_pos = self.parent.source_pos + inst.error_msg = self.parent.error_msg self.instructions.append(inst) return ret @@ -442,6 +450,8 @@ def insert_instruction(self, instruction: IRInstruction, index: Optional[int] = assert not self.is_terminated, self index = len(self.instructions) instruction.parent = self + instruction.source_pos = self.parent.source_pos + instruction.error_msg = self.parent.error_msg self.instructions.insert(index, instruction) def remove_instruction(self, instruction: IRInstruction) -> None: diff --git a/vyper/venom/function.py b/vyper/venom/function.py index c9018ed3bf..0430743fc3 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,4 +1,6 @@ from typing import Iterator, Optional +from vyper.codegen.ir_node import IRnode + from vyper.utils import OrderedSet from vyper.venom.basicblock import ( @@ -29,6 +31,10 @@ class IRFunction: last_label: int last_variable: int + # Used during code generation + _source_pos: list[int] + _error_msg: list[str] + def __init__(self, name: IRLabel = None) -> None: if name is None: name = GLOBAL_LABEL @@ -42,6 +48,9 @@ def __init__(self, name: IRLabel = None) -> None: self.last_label = 0 self.last_variable = 0 + self._source_pos = [] + self._error_msg = [] + self.add_entry_point(name) self.append_basic_block(IRBasicBlock(name, self)) @@ -200,6 +209,25 @@ def normalized(self) -> bool: # The function is normalized return True + def push_source(self, ir): + if isinstance(ir, IRnode): + self._source_pos.append(ir.source_pos) + self._error_msg.append(ir.error_msg) + + def pop_source(self): + assert len(self._source_pos) > 0, "Empty source stack" + self._source_pos.pop() + assert len(self._error_msg) > 0, "Empty error stack" + self._error_msg.pop() + + @property + def source_pos(self) -> Optional[int]: + return self._source_pos[-1] if len(self._source_pos) > 0 else None + + @property + def error_msg(self) -> Optional[str]: + return self._error_msg[-1] if len(self._error_msg) > 0 else None + def copy(self): new = IRFunction(self.name) new.basic_blocks = self.basic_blocks.copy() diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 557d01c35e..d0bb25dc9b 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,3 +1,4 @@ +import functools import re from typing import Optional @@ -231,10 +232,24 @@ def _convert_ir_bb_list(ctx, ir, symbols): var_list: list[str] = [] +def pop_source_on_return(func): + @functools.wraps(func) + def pop_source(*args, **kwargs): + ctx = args[0] + ret = func(*args, **kwargs) + ctx.pop_source() + return ret + + return pop_source + + +@pop_source_on_return def _convert_ir_bb(ctx, ir, symbols): assert isinstance(ir, IRnode), ir global _break_target, _continue_target, current_func, var_list + ctx.push_source(ir) + if ir.value in _BINARY_IR_INSTRUCTIONS: return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3_64"]) elif ir.value in INVERSE_MAPPED_IR_INSTRUCTIONS: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 19bdcbc32e..e44cd8bc08 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,8 +1,16 @@ from collections import Counter +import functools from typing import Any from vyper.exceptions import CompilerPanic, StackTooDeep -from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, mksymbol, optimize_assembly +from vyper.ir.compile_ir import ( + PUSH, + DataHeader, + Instruction, + RuntimeHeader, + mksymbol, + optimize_assembly, +) from vyper.utils import MemoryPositions, OrderedSet from vyper.venom.analysis import ( calculate_cfg, @@ -99,6 +107,16 @@ _REVERT_POSTAMBLE = ["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"] +def apply_line_numbers(inst: IRInstruction, asm) -> list[str]: + ret = [] + for op in asm: + if isinstance(op, str) and not isinstance(op, Instruction): + ret.append(Instruction(op, inst.source_pos, inst.error_msg)) + else: + ret.append(op) + return ret + + # TODO: "assembly" gets into the recursion due to how the original # IR was structured recursively in regards with the deploy instruction. # There, recursing into the deploy instruction was by design, and @@ -264,7 +282,7 @@ def _generate_evm_for_basicblock_r( main_insts = [inst for inst in basicblock.instructions if inst.opcode != "param"] for inst in param_insts: - asm = self._generate_evm_for_instruction(asm, inst, stack) + asm.extend(self._generate_evm_for_instruction(inst, stack)) self._clean_unused_params(asm, basicblock, stack) @@ -276,7 +294,7 @@ def _generate_evm_for_basicblock_r( next_liveness = main_insts[i + 1].liveness if i + 1 < len(main_insts) else OrderedSet() - asm = self._generate_evm_for_instruction(asm, inst, stack, next_liveness) + asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) for bb in basicblock.reachable: self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) @@ -326,11 +344,11 @@ def clean_stack_from_cfg_in( def _generate_evm_for_instruction( self, - assembly: list, inst: IRInstruction, stack: StackModel, next_liveness: OrderedSet = None, ) -> list[str]: + assembly = [] if next_liveness is None: next_liveness = OrderedSet() opcode = inst.opcode @@ -378,7 +396,7 @@ def _generate_evm_for_instruction( stack.poke(0, ret) else: stack.poke(depth, ret) - return assembly + return apply_line_numbers(inst, assembly) # Step 2: Emit instruction's input operands self._emit_input_operands(assembly, inst, operands, stack) @@ -514,7 +532,7 @@ def _generate_evm_for_instruction( if "call" in inst.opcode and inst.output not in next_liveness: self.pop(assembly, stack) - return assembly + return apply_line_numbers(inst, assembly) def pop(self, assembly, stack, num=1): stack.pop(num) From c017371256ae45be1334bb54568efb5eb28d64c0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Mar 2024 23:00:26 +0200 Subject: [PATCH 211/322] update test for venom --- tests/unit/compiler/asm/test_asm_optimizer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unit/compiler/asm/test_asm_optimizer.py b/tests/unit/compiler/asm/test_asm_optimizer.py index ce32249202..d1edd97189 100644 --- a/tests/unit/compiler/asm/test_asm_optimizer.py +++ b/tests/unit/compiler/asm/test_asm_optimizer.py @@ -95,7 +95,7 @@ def test_dead_code_eliminator(code): assert all(ctor_only not in instr for instr in runtime_asm) -def test_library_code_eliminator(make_input_bundle): +def test_library_code_eliminator(make_input_bundle, venom_pipeline): library = """ @internal def unused1(): @@ -119,6 +119,7 @@ def foo(): input_bundle = make_input_bundle({"library.vy": library}) res = compile_code(code, input_bundle=input_bundle, output_formats=["asm"]) asm = res["asm"] - assert "some_function()" in asm + if not venom_pipeline: + assert "some_function()" in asm assert "unused1()" not in asm assert "unused2()" not in asm From 2bf47c7df198fb20a3ace284f5c0d7e15602115f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Mar 2024 23:07:43 +0200 Subject: [PATCH 212/322] lint --- vyper/venom/basicblock.py | 4 ++-- vyper/venom/venom_to_assembly.py | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 9e4bdfe69b..cd4ebf498a 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -222,8 +222,8 @@ class IRInstruction: parent: Optional["IRBasicBlock"] fence_id: int annotation: Optional[str] - source_pos: int - error_msg: str + source_pos: Optional[int] + error_msg: Optional[str] def __init__( self, diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index e44cd8bc08..769e17fcfc 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,5 +1,4 @@ from collections import Counter -import functools from typing import Any from vyper.exceptions import CompilerPanic, StackTooDeep @@ -114,7 +113,7 @@ def apply_line_numbers(inst: IRInstruction, asm) -> list[str]: ret.append(Instruction(op, inst.source_pos, inst.error_msg)) else: ret.append(op) - return ret + return ret # type: ignore # TODO: "assembly" gets into the recursion due to how the original @@ -343,12 +342,9 @@ def clean_stack_from_cfg_in( self.pop(asm, stack) def _generate_evm_for_instruction( - self, - inst: IRInstruction, - stack: StackModel, - next_liveness: OrderedSet = None, + self, inst: IRInstruction, stack: StackModel, next_liveness: OrderedSet = None ) -> list[str]: - assembly = [] + assembly: list[str | int] = [] if next_liveness is None: next_liveness = OrderedSet() opcode = inst.opcode From 7bc1b13cc40bb8e8bb336fcd885b22c847c88891 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Mar 2024 23:10:40 +0200 Subject: [PATCH 213/322] revert force experimental flag --- vyper/compiler/phases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index c6d8bb65ca..abf834946a 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -115,7 +115,7 @@ def __init__( _ = self._generate_ast # force settings to be calculated # to force experimental codegen, uncomment: - self.settings.experimental_codegen = True + # self.settings.experimental_codegen = True @cached_property def source_code(self): From a87076e0054e3d55f8b3d1f284733b8fea23b970 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Mar 2024 23:13:52 +0200 Subject: [PATCH 214/322] lint --- vyper/venom/function.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 0430743fc3..290b8b0505 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,7 +1,6 @@ from typing import Iterator, Optional -from vyper.codegen.ir_node import IRnode - +from vyper.codegen.ir_node import IRnode from vyper.utils import OrderedSet from vyper.venom.basicblock import ( CFG_ALTERING_INSTRUCTIONS, From ea8367402b44ff1940a937eefac20092eaa0e715 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Mar 2024 23:35:28 +0200 Subject: [PATCH 215/322] update to new external call syntax --- tests/unit/compiler/venom/test_call.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/compiler/venom/test_call.py b/tests/unit/compiler/venom/test_call.py index 99f6ebc451..d483191e99 100644 --- a/tests/unit/compiler/venom/test_call.py +++ b/tests/unit/compiler/venom/test_call.py @@ -13,7 +13,7 @@ def market_maker(get_contract): @payable def foo(token_addr: address, token_quantity: uint256): self.token_address = IERC20(token_addr) - self.token_address.transferFrom(msg.sender, self, token_quantity) + extcall self.token_address.transferFrom(msg.sender, self, token_quantity) """ return get_contract(contract_code) From 7994423cb53217cab28f66cafff8306d7dd8f08b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 10 Mar 2024 00:31:53 +0200 Subject: [PATCH 216/322] fix test for venom --- tests/functional/syntax/test_codehash.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/functional/syntax/test_codehash.py b/tests/functional/syntax/test_codehash.py index 8aada22da7..7e8ad26f07 100644 --- a/tests/functional/syntax/test_codehash.py +++ b/tests/functional/syntax/test_codehash.py @@ -7,7 +7,7 @@ @pytest.mark.parametrize("evm_version", list(EVM_VERSIONS)) -def test_get_extcodehash(get_contract, evm_version, optimize): +def test_get_extcodehash(get_contract, evm_version, optimize, venom_pipeline): code = """ a: address @@ -32,7 +32,9 @@ def foo3() -> bytes32: def foo4() -> bytes32: return self.a.codehash """ - settings = Settings(evm_version=evm_version, optimize=optimize) + settings = Settings( + evm_version=evm_version, optimize=optimize, experimental_codegen=venom_pipeline + ) compiled = compile_code(code, output_formats=["bytecode_runtime"], settings=settings) bytecode = bytes.fromhex(compiled["bytecode_runtime"][2:]) hash_ = keccak256(bytecode) From f455b666dfd789821545f10f4affc8a52cea9591 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 10 Mar 2024 00:36:36 +0200 Subject: [PATCH 217/322] fix tests for venom --- tests/functional/examples/factory/test_factory.py | 6 ++++-- tests/functional/syntax/test_address_code.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/functional/examples/factory/test_factory.py b/tests/functional/examples/factory/test_factory.py index 0c5cf61b04..0c422ef332 100644 --- a/tests/functional/examples/factory/test_factory.py +++ b/tests/functional/examples/factory/test_factory.py @@ -31,12 +31,14 @@ def create_exchange(token, factory): @pytest.fixture -def factory(get_contract, optimize): +def factory(get_contract, optimize, venom_pipeline): with open("examples/factory/Exchange.vy") as f: code = f.read() exchange_interface = vyper.compile_code( - code, output_formats=["bytecode_runtime"], settings=Settings(optimize=optimize) + code, + output_formats=["bytecode_runtime"], + settings=Settings(optimize=optimize, experimental_codegen=venom_pipeline), ) exchange_deployed_bytecode = exchange_interface["bytecode_runtime"] diff --git a/tests/functional/syntax/test_address_code.py b/tests/functional/syntax/test_address_code.py index 6556fc90b9..3618ba117c 100644 --- a/tests/functional/syntax/test_address_code.py +++ b/tests/functional/syntax/test_address_code.py @@ -161,7 +161,7 @@ def test_address_code_compile_success(code: str): compiler.compile_code(code) -def test_address_code_self_success(get_contract, optimize): +def test_address_code_self_success(get_contract, optimize, venom_pipeline): code = """ code_deployment: public(Bytes[32]) @@ -174,7 +174,7 @@ def code_runtime() -> Bytes[32]: return slice(self.code, 0, 32) """ contract = get_contract(code) - settings = Settings(optimize=optimize) + settings = Settings(optimize=optimize, experimental_codegen=venom_pipeline) code_compiled = compiler.compile_code( code, output_formats=["bytecode", "bytecode_runtime"], settings=settings ) From c0b7de076c55566e88764b59aff03c4725a8ce3b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 10 Mar 2024 23:57:16 +0200 Subject: [PATCH 218/322] simplify cfg --- vyper/venom/__init__.py | 9 +++++++ vyper/venom/analysis.py | 2 +- vyper/venom/passes/simplify_cfg.py | 42 ++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 vyper/venom/passes/simplify_cfg.py diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index dd5734c69b..75e39261e5 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -18,6 +18,7 @@ from vyper.venom.passes.dft import DFTPass from vyper.venom.passes.make_ssa import MakeSSA from vyper.venom.passes.normalization import NormalizationPass +from vyper.venom.passes.simplify_cfg import SimplifyCFGPass from vyper.venom.venom_to_assembly import VenomCompiler DEFAULT_OPT_LEVEL = OptimizationLevel.default() @@ -44,6 +45,14 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: ir_pass_optimize_empty_blocks(ctx) ir_pass_remove_unreachable_blocks(ctx) + + calculate_cfg(ctx) + SimplifyCFGPass.run_pass(ctx) + SimplifyCFGPass.run_pass(ctx) + SimplifyCFGPass.run_pass(ctx) + ir_pass_remove_unreachable_blocks(ctx) + calculate_cfg(ctx) + internals = [ bb for bb in ctx.basic_blocks diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 88bc7cb833..82a8b9965f 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -116,7 +116,7 @@ def input_vars_from(source: IRBasicBlock, target: IRBasicBlock) -> OrderedSet[IR # bad path into this phi node if source.label not in inst.operands: - raise CompilerPanic(f"unreachable: {inst}") + raise CompilerPanic(f"unreachable: {inst} from {source.label}") for label, var in inst.phi_operands: if label == source.label: diff --git a/vyper/venom/passes/simplify_cfg.py b/vyper/venom/passes/simplify_cfg.py new file mode 100644 index 0000000000..248557d232 --- /dev/null +++ b/vyper/venom/passes/simplify_cfg.py @@ -0,0 +1,42 @@ +from vyper.venom.analysis import calculate_cfg +from vyper.venom.basicblock import IRBasicBlock, IRLabel +from vyper.venom.function import IRFunction +from vyper.venom.passes.base_pass import IRPass + + +class SimplifyCFGPass(IRPass): + def _merge_blocks(self) -> None: + ctx = self.ctx + + to_be_removed = [] + + for bb in ctx.basic_blocks: + if bb in to_be_removed: + continue + if len(bb.cfg_out) == 1: + next = bb.cfg_out.first() + if len(next.cfg_in) == 1: + bb.instructions.pop() + for inst in next.instructions: + assert inst.opcode != "phi", "Not implemented yet" + if inst.opcode == "phi": + bb.instructions.insert(0, inst) + else: + bb.instructions.append(inst) + bb.cfg_out = next.cfg_out + + for n in next.cfg_out: + del n.cfg_in[next] + n.cfg_in.add(bb) + + assert next in ctx.basic_blocks, next.label + to_be_removed.append(next) + + for bb in to_be_removed: + # assert bb in ctx.basic_blocks, bb.label + ctx.basic_blocks.remove(bb) + + def _run_pass(self, ctx: IRFunction) -> None: + self.ctx = ctx + + self._merge_blocks() From 86ea74fcff263c0f323e634025dd994b49e6cb3b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Mar 2024 00:40:28 +0200 Subject: [PATCH 219/322] fix --- vyper/venom/passes/simplify_cfg.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vyper/venom/passes/simplify_cfg.py b/vyper/venom/passes/simplify_cfg.py index 248557d232..29b9c07726 100644 --- a/vyper/venom/passes/simplify_cfg.py +++ b/vyper/venom/passes/simplify_cfg.py @@ -22,12 +22,13 @@ def _merge_blocks(self) -> None: if inst.opcode == "phi": bb.instructions.insert(0, inst) else: + inst.parent = bb bb.instructions.append(inst) bb.cfg_out = next.cfg_out for n in next.cfg_out: - del n.cfg_in[next] - n.cfg_in.add(bb) + n.remove_cfg_in(next) + n.add_cfg_in(bb) assert next in ctx.basic_blocks, next.label to_be_removed.append(next) From 75fc03522c568cc034281d5dcea85ccac0220156 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Mar 2024 13:10:36 +0200 Subject: [PATCH 220/322] raise on dup out of stack --- vyper/venom/venom_to_assembly.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 769e17fcfc..fe696ba0d7 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -560,5 +560,6 @@ def _evm_swap_for(depth: int) -> str: def _evm_dup_for(depth: int) -> str: dup_idx = 1 - depth - assert 1 <= dup_idx <= 16, f"Unsupported dup depth {dup_idx}" + if not (1 <= dup_idx <= 16): + raise StackTooDeep(f"Unsupported dup depth {dup_idx}") return f"DUP{dup_idx}" From f1465da377723cf2d8ba09f73c6dc2b4aea8fda8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Mar 2024 16:32:46 +0200 Subject: [PATCH 221/322] simplify cfg refactor and optimization --- vyper/venom/__init__.py | 13 ++---- vyper/venom/passes/simplify_cfg.py | 74 ++++++++++++++++-------------- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 75e39261e5..2efd58ad6c 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -46,18 +46,16 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: ir_pass_optimize_empty_blocks(ctx) ir_pass_remove_unreachable_blocks(ctx) - calculate_cfg(ctx) - SimplifyCFGPass.run_pass(ctx) - SimplifyCFGPass.run_pass(ctx) - SimplifyCFGPass.run_pass(ctx) - ir_pass_remove_unreachable_blocks(ctx) - calculate_cfg(ctx) - internals = [ bb for bb in ctx.basic_blocks if bb.label.value.startswith("internal") and len(bb.cfg_in) == 0 ] + + SimplifyCFGPass.run_pass(ctx, ctx.basic_blocks[0]) + for entry in internals: + SimplifyCFGPass.run_pass(ctx, entry) + MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) for entry in internals: MakeSSA.run_pass(ctx, entry) @@ -75,7 +73,6 @@ def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: calculate_cfg(ctx) calculate_liveness(ctx) - changes += ir_pass_constant_propagation(ctx) changes += DFTPass.run_pass(ctx) calculate_cfg(ctx) diff --git a/vyper/venom/passes/simplify_cfg.py b/vyper/venom/passes/simplify_cfg.py index 29b9c07726..da9e460c3f 100644 --- a/vyper/venom/passes/simplify_cfg.py +++ b/vyper/venom/passes/simplify_cfg.py @@ -1,3 +1,4 @@ +from vyper.utils import OrderedSet from vyper.venom.analysis import calculate_cfg from vyper.venom.basicblock import IRBasicBlock, IRLabel from vyper.venom.function import IRFunction @@ -5,39 +6,44 @@ class SimplifyCFGPass(IRPass): - def _merge_blocks(self) -> None: - ctx = self.ctx - - to_be_removed = [] - - for bb in ctx.basic_blocks: - if bb in to_be_removed: - continue - if len(bb.cfg_out) == 1: - next = bb.cfg_out.first() - if len(next.cfg_in) == 1: - bb.instructions.pop() - for inst in next.instructions: - assert inst.opcode != "phi", "Not implemented yet" - if inst.opcode == "phi": - bb.instructions.insert(0, inst) - else: - inst.parent = bb - bb.instructions.append(inst) - bb.cfg_out = next.cfg_out - - for n in next.cfg_out: - n.remove_cfg_in(next) - n.add_cfg_in(bb) - - assert next in ctx.basic_blocks, next.label - to_be_removed.append(next) - - for bb in to_be_removed: - # assert bb in ctx.basic_blocks, bb.label - ctx.basic_blocks.remove(bb) - - def _run_pass(self, ctx: IRFunction) -> None: + visited: OrderedSet + + def _merge_blocks(self, a: IRBasicBlock, b: IRBasicBlock): + a.instructions.pop() + for inst in b.instructions: + assert inst.opcode != "phi", "Not implemented yet" + if inst.opcode == "phi": + a.instructions.insert(0, inst) + else: + inst.parent = a + a.instructions.append(inst) + a.cfg_out = b.cfg_out + + for n in b.cfg_out: + n.remove_cfg_in(b) + n.add_cfg_in(a) + + def _collapse_chained_blocks_r(self, bb: IRBasicBlock): + if len(bb.cfg_out) == 1: + next = bb.cfg_out.first() + if len(next.cfg_in) == 1: + self._merge_blocks(bb, next) + self.ctx.basic_blocks.remove(next) + self._collapse_chained_blocks_r(bb) + return + + if bb in self.visited: + return + self.visited.add(bb) + + for bb_out in bb.cfg_out: + self._collapse_chained_blocks_r(bb_out) + + def _collapse_chained_blocks(self, entry: IRBasicBlock): + self.visited = OrderedSet() + self._collapse_chained_blocks_r(entry) + + def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> None: self.ctx = ctx - self._merge_blocks() + self._collapse_chained_blocks(entry) From f2e3871958cbdb1dd8852fc0e70cbdc0a2212903 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Mar 2024 17:01:26 +0200 Subject: [PATCH 222/322] update cfg on remove unreachable blocks --- vyper/venom/function.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 290b8b0505..82cd033972 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -143,6 +143,7 @@ def remove_unreachable_blocks(self) -> int: # Remove phi instructions that reference removed basic blocks for bb in removed: for out_bb in bb.cfg_out: + out_bb.remove_cfg_in(bb) for inst in out_bb.instructions: if inst.opcode != "phi": continue From 926eeb3dbe2ce032023edf95e87d3bf8d4469430 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Mar 2024 18:10:42 +0200 Subject: [PATCH 223/322] simplify jumps --- vyper/venom/passes/simplify_cfg.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/simplify_cfg.py b/vyper/venom/passes/simplify_cfg.py index da9e460c3f..c0b45646cf 100644 --- a/vyper/venom/passes/simplify_cfg.py +++ b/vyper/venom/passes/simplify_cfg.py @@ -23,14 +23,33 @@ def _merge_blocks(self, a: IRBasicBlock, b: IRBasicBlock): n.remove_cfg_in(b) n.add_cfg_in(a) + self.ctx.basic_blocks.remove(b) + + def _merge_jump(self, a: IRBasicBlock, b: IRBasicBlock): + next = b.cfg_out.first() + jump_inst = a.instructions[-1] + assert b.label in jump_inst.operands, f"{b.label} {jump_inst.operands}" + jump_inst.operands[jump_inst.operands.index(b.label)] = next.label + a.remove_cfg_out(b) + a.add_cfg_out(next) + next.remove_cfg_in(b) + next.add_cfg_in(a) + self.ctx.basic_blocks.remove(b) + def _collapse_chained_blocks_r(self, bb: IRBasicBlock): if len(bb.cfg_out) == 1: next = bb.cfg_out.first() if len(next.cfg_in) == 1: self._merge_blocks(bb, next) - self.ctx.basic_blocks.remove(next) self._collapse_chained_blocks_r(bb) return + elif len(bb.cfg_out) == 2: + bb_out = bb.cfg_out.copy() + for next in bb_out: + if len(next.cfg_in) == 1 and len(next.cfg_out) == 1 and len(next.instructions) == 1: + self._merge_jump(bb, next) + self._collapse_chained_blocks_r(bb) + return if bb in self.visited: return From 5e496ef33c0e97701f7f16ba33183a719d8e784a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Mar 2024 18:12:43 +0200 Subject: [PATCH 224/322] remove unsused imports --- vyper/venom/passes/simplify_cfg.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/venom/passes/simplify_cfg.py b/vyper/venom/passes/simplify_cfg.py index c0b45646cf..e4c3c9a1cd 100644 --- a/vyper/venom/passes/simplify_cfg.py +++ b/vyper/venom/passes/simplify_cfg.py @@ -1,6 +1,5 @@ from vyper.utils import OrderedSet -from vyper.venom.analysis import calculate_cfg -from vyper.venom.basicblock import IRBasicBlock, IRLabel +from vyper.venom.basicblock import IRBasicBlock from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass From 7e9ee92ad30f5a18bee4b4564c13d83105dc999b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Mar 2024 00:06:59 +0200 Subject: [PATCH 225/322] exit venom instruction --- vyper/venom/basicblock.py | 3 ++- vyper/venom/ir_node_to_venom.py | 3 ++- vyper/venom/venom_to_assembly.py | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index cd4ebf498a..76ad287722 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -4,7 +4,7 @@ from vyper.utils import OrderedSet # instructions which can terminate a basic block -BB_TERMINATORS = frozenset(["jmp", "djmp", "jnz", "ret", "return", "revert", "stop"]) +BB_TERMINATORS = frozenset(["jmp", "djmp", "jnz", "ret", "return", "revert", "stop", "exit"]) VOLATILE_INSTRUCTIONS = frozenset( [ @@ -62,6 +62,7 @@ "djmp", "jnz", "log", + "exit", ] ) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index d0bb25dc9b..9ac2c86d6c 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -85,6 +85,7 @@ "selfdestruct", "assert", "assert_unreachable", + "exit", ] ) @@ -434,7 +435,7 @@ def _convert_ir_bb(ctx, ir, symbols): ctx.append_basic_block(bb) code = ir.args[2] if code.value == "pass": - bb.append_instruction("stop") + bb.append_instruction("exit") else: _convert_ir_bb(ctx, code, symbols) elif ir.value == "exit_to": diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index fe696ba0d7..7ab24e3f09 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -481,6 +481,8 @@ def _generate_evm_for_instruction( assembly.append("JUMP") elif opcode == "return": assembly.append("RETURN") + elif opcode == "exit": + assembly.extend(["_sym__ctor_exit", "JUMP"]) elif opcode == "phi": pass elif opcode == "sha3": From e2b51abaebf26e4ff2cf9a15b7c71e8ed524e336 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Mar 2024 00:17:56 +0200 Subject: [PATCH 226/322] mark tests xfail for venom --- tests/functional/builtins/codegen/test_abi_encode.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/builtins/codegen/test_abi_encode.py b/tests/functional/builtins/codegen/test_abi_encode.py index 305c4b1356..d32c30c3a2 100644 --- a/tests/functional/builtins/codegen/test_abi_encode.py +++ b/tests/functional/builtins/codegen/test_abi_encode.py @@ -178,6 +178,7 @@ def abi_encode(d: DynArray[uint256, 3], ensure_tuple: bool, include_method_id: b ] +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") @pytest.mark.parametrize("args", nested_2d_array_args) def test_abi_encode_nested_dynarray(get_contract, args): code = """ From 7e9b87802797573cb1caa87a7f881ad2bd0a0974 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Mar 2024 00:34:35 +0200 Subject: [PATCH 227/322] use exit instruction --- vyper/venom/ir_node_to_venom.py | 2 +- vyper/venom/venom_to_assembly.py | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 9ac2c86d6c..b97de68eb5 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -128,7 +128,7 @@ def ir_node_to_venom(ir: IRnode) -> IRFunction: else: bb.append_instruction("jmp", ctx.basic_blocks[i + 1].label) else: - bb.append_instruction("stop") + bb.append_instruction("exit") return ctx diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 7ab24e3f09..064df0c7e4 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -266,11 +266,6 @@ def _generate_evm_for_basicblock_r( return self.visited_basicblocks.add(basicblock) - bb_label = basicblock.label.value - is_constructor_cleanup = ( - "__init__" in bb_label and "_cleanup" in bb_label - ) or bb_label == "__global" - # assembly entry point into the block asm.append(f"_sym_{basicblock.label}") asm.append("JUMPDEST") @@ -286,11 +281,6 @@ def _generate_evm_for_basicblock_r( self._clean_unused_params(asm, basicblock, stack) for i, inst in enumerate(main_insts): - if is_constructor_cleanup and inst.opcode == "stop": - asm.append("_sym__ctor_exit") - asm.append("JUMP") - continue - next_liveness = main_insts[i + 1].liveness if i + 1 < len(main_insts) else OrderedSet() asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) From 47dfd0525f459e9589bf09816b6476cd6f12d5b5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Mar 2024 00:38:35 +0200 Subject: [PATCH 228/322] mark xfail --- tests/functional/codegen/features/test_constructor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/codegen/features/test_constructor.py b/tests/functional/codegen/features/test_constructor.py index 465b2e9963..d96a889497 100644 --- a/tests/functional/codegen/features/test_constructor.py +++ b/tests/functional/codegen/features/test_constructor.py @@ -165,6 +165,7 @@ def get_foo() -> uint256: assert c.get_foo() == 39 +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_nested_dynamic_array_constructor_arg_2(w3, get_contract_with_gas_estimation): code = """ foo: int128 From 743b15dfef48a4e15b90374ce0f180c8579e4942 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Mar 2024 08:33:38 +0200 Subject: [PATCH 229/322] mark xfail venom --- tests/functional/codegen/types/test_dynamic_array.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index 1a3bf17295..df5ea94693 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -866,6 +866,7 @@ def parse_list_fail(): assert_compile_failed(lambda: get_contract_with_gas_estimation(code), OverflowException) +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_2d_array_input_1(get_contract): code = """ @internal @@ -885,6 +886,7 @@ def test_values( assert c.test_values([[1, 2]], 3) == [[[1, 2]], 3] +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_2d_array_input_2(get_contract): code = """ @internal From 4059e88f25c0d4a6a63cab4847bde0143f8b1a8e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Mar 2024 09:56:31 +0200 Subject: [PATCH 230/322] Add cfg and cfg_runtime output types --- tests/conftest.py | 2 ++ tests/unit/cli/vyper_json/test_compile_json.py | 4 +++- vyper/compiler/__init__.py | 2 ++ vyper/compiler/output.py | 8 ++++++++ vyper/venom/function.py | 1 - 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1bc6e71a4a..4ba147edd0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -66,6 +66,8 @@ def output_formats(): output_formats = compiler.OUTPUT_FORMATS.copy() del output_formats["bb"] del output_formats["bb_runtime"] + del output_formats["cfg"] + del output_formats["cfg_runtime"] return output_formats diff --git a/tests/unit/cli/vyper_json/test_compile_json.py b/tests/unit/cli/vyper_json/test_compile_json.py index e5f7384068..84aca5c86c 100644 --- a/tests/unit/cli/vyper_json/test_compile_json.py +++ b/tests/unit/cli/vyper_json/test_compile_json.py @@ -113,11 +113,13 @@ def test_keyerror_becomes_jsonerror(input_json): def test_compile_json(input_json, input_bundle): foo_input = input_bundle.load_file("contracts/foo.vy") - # remove bb and bb_runtime from output formats + # remove venom related from output formats # because they require venom (experimental) output_formats = OUTPUT_FORMATS.copy() del output_formats["bb"] del output_formats["bb_runtime"] + del output_formats["cfg"] + del output_formats["cfg_runtime"] foo = compile_from_file_input( foo_input, output_formats=output_formats, input_bundle=input_bundle ) diff --git a/vyper/compiler/__init__.py b/vyper/compiler/__init__.py index 9297f9e3c3..7a2179e53b 100644 --- a/vyper/compiler/__init__.py +++ b/vyper/compiler/__init__.py @@ -25,6 +25,8 @@ "interface": output.build_interface_output, "bb": output.build_bb_output, "bb_runtime": output.build_bb_runtime_output, + "cfg": output.build_cfg_output, + "cfg_runtime": output.build_cfg_runtime_output, "ir": output.build_ir_output, "ir_runtime": output.build_ir_runtime_output, "ir_dict": output.build_ir_dict_output, diff --git a/vyper/compiler/output.py b/vyper/compiler/output.py index 707c99291b..68c5cbdddd 100644 --- a/vyper/compiler/output.py +++ b/vyper/compiler/output.py @@ -92,6 +92,14 @@ def build_bb_runtime_output(compiler_data: CompilerData) -> IRnode: return compiler_data.venom_functions[1] +def build_cfg_output(compiler_data: CompilerData) -> str: + return compiler_data.venom_functions[0].as_graph() + + +def build_cfg_runtime_output(compiler_data: CompilerData) -> str: + return compiler_data.venom_functions[1].as_graph() + + def build_ir_output(compiler_data: CompilerData) -> IRnode: if compiler_data.show_gas_estimates: IRnode.repr_show_gas = True diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 82cd033972..0c338a7912 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -263,7 +263,6 @@ def _make_label(bb): return ret def __repr__(self) -> str: - return self.as_graph() str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: str += f"{bb}\n" From 7fc72a2509d04de06b8cb5e94c7f8c56b7437899 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 13 Mar 2024 12:38:57 +0200 Subject: [PATCH 231/322] change source_pos to ast_source --- vyper/venom/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 0c338a7912..b37ec35fac 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -211,7 +211,7 @@ def normalized(self) -> bool: def push_source(self, ir): if isinstance(ir, IRnode): - self._source_pos.append(ir.source_pos) + self._source_pos.append(ir.ast_source) self._error_msg.append(ir.error_msg) def pop_source(self): From 651a076c1aa774bb42063c2e9c25668c84f699d4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 13 Mar 2024 12:51:34 +0200 Subject: [PATCH 232/322] optimization --- vyper/venom/venom_to_assembly.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 064df0c7e4..41fff42154 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -365,6 +365,8 @@ def _generate_evm_for_instruction( log_topic_count = inst.operands[0].value assert log_topic_count in [0, 1, 2, 3, 4], "Invalid topic count" operands = inst.operands[1:] + elif opcode in ["call", "staticcall", "delegatecall"]: + operands = inst.operands[::-1] else: operands = inst.operands @@ -397,7 +399,7 @@ def _generate_evm_for_instruction( # NOTE: stack in general can contain multiple copies of the same variable, # however we are safe in the case of jmp/djmp/jnz as it's not going to # have multiples. - target_stack_list = [x for x in target_stack.keys()] + target_stack_list = list(target_stack.keys()) self._stack_reorder(assembly, stack, target_stack_list) # final step to get the inputs to this instruction ordered From d513bb4c70383223de930a1196d3490fa487ea6e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 13 Mar 2024 21:08:09 +0200 Subject: [PATCH 233/322] cleanup, optimization --- vyper/venom/ir_node_to_venom.py | 32 ++------------------------------ vyper/venom/venom_to_assembly.py | 2 -- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index b97de68eb5..d7367ddd3b 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -305,35 +305,7 @@ def _convert_ir_bb(ctx, ir, symbols): return ret elif ir.value in ["delegatecall", "staticcall", "call"]: - idx = 0 - gas = _convert_ir_bb(ctx, ir.args[idx], symbols) - address = _convert_ir_bb(ctx, ir.args[idx + 1], symbols) - - value = None - if ir.value == "call": - value = _convert_ir_bb(ctx, ir.args[idx + 2], symbols) - else: - idx -= 1 - - argsOffset, argsSize, retOffset, retSize = _convert_ir_bb_list( - ctx, ir.args[idx + 3 : idx + 7], symbols - ) - - if isinstance(argsOffset, IRLiteral): - offset = int(argsOffset.value) - argsOffsetVar = symbols.get(f"&{offset}", None) - if argsOffsetVar is None: # or offset > 0: - argsOffsetVar = argsOffset - else: # pragma: nocover - argsOffsetVar = argsOffset - else: - argsOffsetVar = argsOffset - - if ir.value == "call": - args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] - else: - args = [retSize, retOffset, argsSize, argsOffsetVar, address, gas] - + args = reversed(_convert_ir_bb_list(ctx, ir.args, symbols)) return ctx.get_basic_block().append_instruction(ir.value, *args) elif ir.value == "if": cond = ir.args[0] @@ -585,7 +557,7 @@ def emit_body_blocks(): elif ir.value == "var_list": pass elif isinstance(ir.value, str) and ir.value.startswith("log"): - args = reversed([_convert_ir_bb(ctx, arg, symbols) for arg in ir.args]) + args = reversed(_convert_ir_bb_list(ctx, ir.args, symbols)) topic_count = int(ir.value[3:]) assert topic_count >= 0 and topic_count <= 4, "invalid topic count" ctx.get_basic_block().append_instruction("log", topic_count, *args) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 41fff42154..31e982eb7a 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -365,8 +365,6 @@ def _generate_evm_for_instruction( log_topic_count = inst.operands[0].value assert log_topic_count in [0, 1, 2, 3, 4], "Invalid topic count" operands = inst.operands[1:] - elif opcode in ["call", "staticcall", "delegatecall"]: - operands = inst.operands[::-1] else: operands = inst.operands From 4d700775f138a92649298bfb2cc88e0258580e38 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 13 Mar 2024 21:12:36 +0200 Subject: [PATCH 234/322] no operation instructions for translator --- vyper/venom/ir_node_to_venom.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index d7367ddd3b..ffb389527c 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -107,6 +107,8 @@ ] ) +NOOP_INSTRUCTIONS = frozenset(["pass", "cleanup_repeat", "var_list", "unique_symbol"]) + SymbolTable = dict[str, Optional[IROperand]] @@ -473,11 +475,6 @@ def _convert_ir_bb(ctx, ir, symbols): elif ir.value in []: arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) - elif ir.value == "unique_symbol": - sym = ir.args[0] - new_var = ctx.get_next_variable() - symbols[f"&{sym.value}"] = new_var - return new_var elif ir.value == "repeat": def emit_body_blocks(): @@ -542,10 +539,6 @@ def emit_body_blocks(): ctx.append_basic_block(exit_block) cond_block.append_instruction("jnz", cont_ret, exit_block.label, body_block.label) - elif ir.value == "cleanup_repeat": - pass - elif ir.value == "pass": - pass elif ir.value == "break": assert _break_target is not None, "Break with no break target" ctx.get_basic_block().append_instruction("jmp", _break_target.label) @@ -554,7 +547,7 @@ def emit_body_blocks(): assert _continue_target is not None, "Continue with no contrinue target" ctx.get_basic_block().append_instruction("jmp", _continue_target.label) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) - elif ir.value == "var_list": + elif ir.value in NOOP_INSTRUCTIONS: pass elif isinstance(ir.value, str) and ir.value.startswith("log"): args = reversed(_convert_ir_bb_list(ctx, ir.args, symbols)) From 559a80b7c07712bbd1fc65e4eaa0cc846ae9819d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 14 Mar 2024 10:07:50 +0200 Subject: [PATCH 235/322] update test --- tests/unit/compiler/venom/test_duplicate_operands.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/compiler/venom/test_duplicate_operands.py b/tests/unit/compiler/venom/test_duplicate_operands.py index 66998619bd..7f12bba51d 100644 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ b/tests/unit/compiler/venom/test_duplicate_operands.py @@ -23,4 +23,5 @@ def test_duplicate_operands(): bb.append_instruction("stop") asm = generate_assembly_experimental(ctx, optimize=OptimizationLevel.GAS) - assert asm == ["PUSH1", 10, "DUP1", "DUP1", "ADD", "MUL", "PUSH0", "DUP1", "REVERT"] + print(asm) + assert asm == ["PUSH1", 10, "DUP1", "DUP1", "ADD", "MUL", "STOP"] From 5477ec179239d584e469d489468915fc1d8cefc4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 14 Mar 2024 11:13:14 +0200 Subject: [PATCH 236/322] graphviz output for DFG --- vyper/venom/analysis.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 82a8b9965f..b3b01eacec 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -170,3 +170,20 @@ def build_dfg(cls, ctx: IRFunction) -> "DFG": dfg._dfg_outputs[op] = inst return dfg + + def as_graph(self) -> str: + """ + Generate a graphviz representation of the dfg + """ + lines = ["digraph dfg_graph {"] + 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("}") + return "\n".join(lines) + + def __repr__(self) -> str: + return self.as_graph() From dfc9b8602af2b0049bbad6e1919bb8142810a20d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 14 Mar 2024 11:13:25 +0200 Subject: [PATCH 237/322] fix --- vyper/venom/dominators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index b3051b9c22..e5b03c1bde 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -149,6 +149,6 @@ def as_graph(self) -> str: idom = self.immediate_dominator(bb) if idom is None: continue - lines.append(f' "{idom.label}" -> "{bb.label}"') + lines.append(f' " {idom.label} " -> " {bb.label} "') lines.append("}") return "\n".join(lines) From 31e6c8eb6cf9253151244f1732c6e0237b185bb3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 14 Mar 2024 11:13:45 +0200 Subject: [PATCH 238/322] small stack optimization --- vyper/venom/venom_to_assembly.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 31e982eb7a..456e8a11ab 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -218,6 +218,9 @@ def _stack_reorder( if depth == final_stack_depth: continue + if op == stack.peek(final_stack_depth): + continue + self.swap(assembly, stack, depth) self.swap(assembly, stack, final_stack_depth) From 46f5f8c8465b7bc6e1eb962a96f0be9851bf39d8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 14 Mar 2024 11:14:15 +0200 Subject: [PATCH 239/322] stack reorder pass scaffold --- vyper/venom/passes/stack_reorder.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 vyper/venom/passes/stack_reorder.py diff --git a/vyper/venom/passes/stack_reorder.py b/vyper/venom/passes/stack_reorder.py new file mode 100644 index 0000000000..b32ec4abde --- /dev/null +++ b/vyper/venom/passes/stack_reorder.py @@ -0,0 +1,24 @@ +from vyper.utils import OrderedSet +from vyper.venom.basicblock import IRBasicBlock +from vyper.venom.function import IRFunction +from vyper.venom.passes.base_pass import IRPass + + +class StackReorderPass(IRPass): + visited: OrderedSet + + def _reorder_stack(self, bb: IRBasicBlock): + pass + + def _visit(self, bb: IRBasicBlock): + if bb in self.visited: + return + self.visited.add(bb) + + for bb_out in bb.cfg_out: + self._visit(bb_out) + + def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock): + self.ctx = ctx + self.visited = OrderedSet() + self._visit(entry) From dab020db2c992ebf86dc544ea83fa4a8baecffc1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 15 Mar 2024 13:08:15 +0200 Subject: [PATCH 240/322] dfg testing --- vyper/venom/analysis.py | 5 +++-- vyper/venom/passes/dft.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index b3b01eacec..240632d199 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -1,3 +1,4 @@ +from typing import Optional from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet from vyper.venom.basicblock import ( @@ -144,8 +145,8 @@ def get_uses(self, op: IRVariable) -> list[IRInstruction]: return self._dfg_inputs.get(op, []) # the instruction which produces this variable. - def get_producing_instruction(self, op: IRVariable) -> IRInstruction: - return self._dfg_outputs[op] + def get_producing_instruction(self, op: IRVariable) -> Optional[IRInstruction]: + return self._dfg_outputs.get(op) @classmethod def build_dfg(cls, ctx: IRFunction) -> "DFG": diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 26994bd27f..9e4ac0a24d 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -6,6 +6,9 @@ # DataFlow Transformation +run = 0 + + class DFTPass(IRPass): def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): if inst in self.visited_instructions: @@ -18,9 +21,9 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): bb.instructions.append(inst) return - for op in inst.get_inputs(): + for op in inst.liveness: target = self.dfg.get_producing_instruction(op) - if target.parent != inst.parent or target.fence_id != inst.fence_id: + if target is None or target.parent != inst.parent or target.fence_id != inst.fence_id: # don't reorder across basic block or fence boundaries continue self._process_instruction_r(bb, target) @@ -41,9 +44,21 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: for inst in instructions: self._process_instruction_r(bb, inst) + # bb.instructions.append(instructions[-1]) + def _run_pass(self, ctx: IRFunction) -> None: self.ctx = ctx self.dfg = DFG.build_dfg(ctx) + # return + # global run + # if run == 2: + # print(self.dfg) + # # print(self.ctx.as_graph()) + # import sys + + # sys.exit(0) + # run += 1 + self.fence_id = 0 self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet() From 24730adcbea9e6be95631fcffea73cf8870b70fd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 18 Mar 2024 20:37:10 +0200 Subject: [PATCH 241/322] optimize double swaps --- vyper/venom/venom_to_assembly.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 456e8a11ab..b3161dfb2f 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -530,10 +530,20 @@ def pop(self, assembly, stack, num=1): assembly.extend(["POP"] * num) def swap(self, assembly, stack, depth): + # Swaps of the top is no op if depth == 0: return + + inst = _evm_swap_for(depth) + + # Double swaps cancel each other out + if len(assembly) > 0 and inst == assembly[-1]: + assembly.pop() + stack.swap(depth) + return + stack.swap(depth) - assembly.append(_evm_swap_for(depth)) + assembly.append(inst) def dup(self, assembly, stack, depth): stack.dup(depth) From 1cd994c45ae30891f47edd218f0a267214fc2c13 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 19 Mar 2024 11:09:45 +0200 Subject: [PATCH 242/322] cleanup --- vyper/compiler/phases.py | 3 --- vyper/venom/dominators.py | 15 --------------- vyper/venom/passes/dft.py | 15 --------------- vyper/venom/passes/make_ssa.py | 2 -- 4 files changed, 35 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index abf834946a..d794185195 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -114,9 +114,6 @@ def __init__( _ = self._generate_ast # force settings to be calculated - # to force experimental codegen, uncomment: - # self.settings.experimental_codegen = True - @cached_property def source_code(self): return self.file_input.source_code diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index e5b03c1bde..6a7690ffb6 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -64,11 +64,6 @@ def _compute_dominators(self): self.dominators[bb] = new_dominators changed = True - # for bb in basic_blocks: - # print(bb.label) - # for dom in self.dominators[bb]: - # print(" ", dom.label) - def _compute_idoms(self): """ Compute immediate dominators @@ -85,11 +80,6 @@ def _compute_idoms(self): for dom, target in self.idoms.items(): self.dominated[target].add(dom) - # for dom, targets in self.dominated.items(): - # print(dom.label) - # for t in targets: - # print(" ", t.label) - def _compute_df(self): """ Compute dominance frontier @@ -105,11 +95,6 @@ def _compute_df(self): self.df[runner].add(bb) runner = self.idoms[runner] - # for bb in self.dfs: - # print(bb.label) - # for df in self.df[bb]: - # print(" ", df.label) - def dominance_frontier(self, basic_blocks: list[IRBasicBlock]) -> OrderedSet[IRBasicBlock]: """ Compute dominance frontier of a set of basic blocks. diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 9e4ac0a24d..ba30065ff4 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -5,10 +5,6 @@ from vyper.venom.passes.base_pass import IRPass -# DataFlow Transformation -run = 0 - - class DFTPass(IRPass): def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): if inst in self.visited_instructions: @@ -44,20 +40,9 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: for inst in instructions: self._process_instruction_r(bb, inst) - # bb.instructions.append(instructions[-1]) - def _run_pass(self, ctx: IRFunction) -> None: self.ctx = ctx self.dfg = DFG.build_dfg(ctx) - # return - # global run - # if run == 2: - # print(self.dfg) - # # print(self.ctx.as_graph()) - # import sys - - # sys.exit(0) - # run += 1 self.fence_id = 0 self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet() diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index b28d316d2f..965b0e0945 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -139,8 +139,6 @@ def _remove_degenerate_phis(self, entry: IRBasicBlock): entry.instructions.remove(inst) elif new_ops_len == 2: entry.instructions.remove(inst) - # inst.opcode = "store" - # inst.operands = [new_ops[1]] else: inst.operands = new_ops From 25fa225c71bac8e6f7ae2b8acbaaf913039ee9a0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 19 Mar 2024 15:25:32 +0200 Subject: [PATCH 243/322] cleanup --- vyper/venom/passes/simplify_cfg.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/vyper/venom/passes/simplify_cfg.py b/vyper/venom/passes/simplify_cfg.py index e4c3c9a1cd..56816c5324 100644 --- a/vyper/venom/passes/simplify_cfg.py +++ b/vyper/venom/passes/simplify_cfg.py @@ -16,11 +16,13 @@ def _merge_blocks(self, a: IRBasicBlock, b: IRBasicBlock): else: inst.parent = a a.instructions.append(inst) - a.cfg_out = b.cfg_out - for n in b.cfg_out: - n.remove_cfg_in(b) - n.add_cfg_in(a) + # Update CFG + a.cfg_out = b.cfg_out + if len(b.cfg_out) > 0: + next = b.cfg_out.first() + next.remove_cfg_in(b) + next.add_cfg_in(a) self.ctx.basic_blocks.remove(b) @@ -29,20 +31,26 @@ def _merge_jump(self, a: IRBasicBlock, b: IRBasicBlock): jump_inst = a.instructions[-1] assert b.label in jump_inst.operands, f"{b.label} {jump_inst.operands}" jump_inst.operands[jump_inst.operands.index(b.label)] = next.label + + # Update CFG a.remove_cfg_out(b) a.add_cfg_out(next) next.remove_cfg_in(b) next.add_cfg_in(a) + self.ctx.basic_blocks.remove(b) def _collapse_chained_blocks_r(self, bb: IRBasicBlock): + """ + DFS into the cfg and collapse blocks with a single predecessor to the predecessor + """ if len(bb.cfg_out) == 1: next = bb.cfg_out.first() if len(next.cfg_in) == 1: self._merge_blocks(bb, next) self._collapse_chained_blocks_r(bb) return - elif len(bb.cfg_out) == 2: + elif len(bb.cfg_out) > 1: bb_out = bb.cfg_out.copy() for next in bb_out: if len(next.cfg_in) == 1 and len(next.cfg_out) == 1 and len(next.instructions) == 1: @@ -58,6 +66,9 @@ def _collapse_chained_blocks_r(self, bb: IRBasicBlock): self._collapse_chained_blocks_r(bb_out) def _collapse_chained_blocks(self, entry: IRBasicBlock): + """ + Collapse blocks with a single predecessor to their predecessor + """ self.visited = OrderedSet() self._collapse_chained_blocks_r(entry) From 84f1b695101a8ff8fc3f3aa24be01732c8d5252f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 19 Mar 2024 15:28:08 +0200 Subject: [PATCH 244/322] docs --- vyper/venom/passes/make_ssa.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 965b0e0945..4169451c2e 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -7,6 +7,10 @@ class MakeSSA(IRPass): + """ + This pass converts the function into Static Single Assignment (SSA) form. + """ + dom: DominatorTree defs: dict[IRVariable, OrderedSet[IRBasicBlock]] @@ -29,6 +33,9 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: return 0 def _add_phi_nodes(self): + """ + Add phi nodes to the function. + """ self._compute_defs() self.work = {var: 0 for var in self.dom.dfs} self.has_already = {var: 0 for var in self.dom.dfs} @@ -84,6 +91,9 @@ def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock) -> bool: return True def _rename_vars(self, basic_block: IRBasicBlock): + """ + Rename variables in the basic block. This follows the placement of phi nodes. + """ outs = [] for inst in basic_block.instructions: new_ops = [] @@ -148,6 +158,9 @@ def _remove_degenerate_phis(self, entry: IRBasicBlock): self._remove_degenerate_phis(bb) def _compute_defs(self): + """ + Compute the definition points of variables in the function. + """ self.defs = {} for bb in self.dom.dfs: assignments = bb.get_assignments() From d7ef9efcdadffca00395a3c5fa3f44078b356dd2 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Mar 2024 10:08:47 -0400 Subject: [PATCH 245/322] rename venom_pipeline to experimental-codegen --- tests/conftest.py | 42 ++++++++++--------- .../examples/factory/test_factory.py | 4 +- tests/functional/syntax/test_address_code.py | 4 +- tests/functional/syntax/test_codehash.py | 6 +-- tests/unit/compiler/asm/test_asm_optimizer.py | 4 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a2bccceb47..d0681cdf42 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -59,7 +59,7 @@ def pytest_addoption(parser): help="change optimization mode", ) parser.addoption("--enable-compiler-debug-mode", action="store_true") - parser.addoption("--use-venom", action="store_true") + parser.addoption("--experimental-codegen", action="store_true") parser.addoption( "--evm-version", @@ -93,15 +93,15 @@ def debug(pytestconfig): @pytest.fixture(scope="session") -def venom_pipeline(pytestconfig): +def experimental_codegen(pytestconfig): ret = pytestconfig.getoption("experimental_codegen") assert isinstance(ret, bool) return ret @pytest.fixture(autouse=True) -def check_venom_xfail(request, venom_pipeline): - if not venom_pipeline: +def check_venom_xfail(request, experimental_codegen): + if not experimental_codegen: return marker = request.node.get_closest_marker("venom_xfail") @@ -113,9 +113,9 @@ def check_venom_xfail(request, venom_pipeline): @pytest.fixture -def venom_xfail(request, venom_pipeline): +def venom_xfail(request, experimental_codegen): def _xfail(*args, **kwargs): - if not venom_pipeline: + if not experimental_codegen: return request.node.add_marker(pytest.mark.xfail(*args, strict=True, **kwargs)) @@ -355,7 +355,7 @@ def _get_contract( w3, source_code, optimize, - venom_pipeline, + experimental_codegen, output_formats, *args, override_opt_level=None, @@ -364,7 +364,7 @@ def _get_contract( ): settings = Settings() settings.optimize = override_opt_level or optimize - settings.experimental_codegen = venom_pipeline + settings.experimental_codegen = experimental_codegen out = compiler.compile_code( source_code, # test that all output formats can get generated @@ -388,20 +388,20 @@ def _get_contract( @pytest.fixture(scope="module") -def get_contract(w3, optimize, venom_pipeline, output_formats): +def get_contract(w3, optimize, experimental_codegen, output_formats): def fn(source_code, *args, **kwargs): return _get_contract( - w3, source_code, optimize, venom_pipeline, output_formats, *args, **kwargs + w3, source_code, optimize, experimental_codegen, output_formats, *args, **kwargs ) return fn @pytest.fixture -def get_contract_with_gas_estimation(tester, w3, optimize, venom_pipeline, output_formats): +def get_contract_with_gas_estimation(tester, w3, optimize, experimental_codegen, output_formats): def get_contract_with_gas_estimation(source_code, *args, **kwargs): contract = _get_contract( - w3, source_code, optimize, venom_pipeline, output_formats, *args, **kwargs + w3, source_code, optimize, experimental_codegen, output_formats, *args, **kwargs ) for abi_ in contract._classic_contract.functions.abi: if abi_["type"] == "function": @@ -412,17 +412,19 @@ def get_contract_with_gas_estimation(source_code, *args, **kwargs): @pytest.fixture -def get_contract_with_gas_estimation_for_constants(w3, optimize, venom_pipeline, output_formats): +def get_contract_with_gas_estimation_for_constants( + w3, optimize, experimental_codegen, output_formats +): def get_contract_with_gas_estimation_for_constants(source_code, *args, **kwargs): return _get_contract( - w3, source_code, optimize, venom_pipeline, output_formats, *args, **kwargs + w3, source_code, optimize, experimental_codegen, output_formats, *args, **kwargs ) return get_contract_with_gas_estimation_for_constants @pytest.fixture(scope="module") -def get_contract_module(optimize, venom_pipeline, output_formats): +def get_contract_module(optimize, experimental_codegen, output_formats): """ This fixture is used for Hypothesis tests to ensure that the same contract is called over multiple runs of the test. @@ -436,7 +438,7 @@ def get_contract_module(optimize, venom_pipeline, output_formats): def get_contract_module(source_code, *args, **kwargs): return _get_contract( - w3, source_code, optimize, venom_pipeline, output_formats, *args, **kwargs + w3, source_code, optimize, experimental_codegen, output_formats, *args, **kwargs ) return get_contract_module @@ -446,14 +448,14 @@ def _deploy_blueprint_for( w3, source_code, optimize, - venom_pipeline, + experimental_codegen, output_formats, initcode_prefix=ERC5202_PREFIX, **kwargs, ): settings = Settings() settings.optimize = optimize - settings.experimental_codegen = venom_pipeline + settings.experimental_codegen = experimental_codegen out = compiler.compile_code( source_code, output_formats=output_formats, @@ -489,10 +491,10 @@ def factory(address): @pytest.fixture(scope="module") -def deploy_blueprint_for(w3, optimize, venom_pipeline, output_formats): +def deploy_blueprint_for(w3, optimize, experimental_codegen, output_formats): def deploy_blueprint_for(source_code, *args, **kwargs): return _deploy_blueprint_for( - w3, source_code, optimize, venom_pipeline, output_formats, *args, **kwargs + w3, source_code, optimize, experimental_codegen, output_formats, *args, **kwargs ) return deploy_blueprint_for diff --git a/tests/functional/examples/factory/test_factory.py b/tests/functional/examples/factory/test_factory.py index 0c422ef332..18f6222c20 100644 --- a/tests/functional/examples/factory/test_factory.py +++ b/tests/functional/examples/factory/test_factory.py @@ -31,14 +31,14 @@ def create_exchange(token, factory): @pytest.fixture -def factory(get_contract, optimize, venom_pipeline): +def factory(get_contract, optimize, experimental_codegen): with open("examples/factory/Exchange.vy") as f: code = f.read() exchange_interface = vyper.compile_code( code, output_formats=["bytecode_runtime"], - settings=Settings(optimize=optimize, experimental_codegen=venom_pipeline), + settings=Settings(optimize=optimize, experimental_codegen=experimental_codegen), ) exchange_deployed_bytecode = exchange_interface["bytecode_runtime"] diff --git a/tests/functional/syntax/test_address_code.py b/tests/functional/syntax/test_address_code.py index 3618ba117c..6be50a509b 100644 --- a/tests/functional/syntax/test_address_code.py +++ b/tests/functional/syntax/test_address_code.py @@ -161,7 +161,7 @@ def test_address_code_compile_success(code: str): compiler.compile_code(code) -def test_address_code_self_success(get_contract, optimize, venom_pipeline): +def test_address_code_self_success(get_contract, optimize, experimental_codegen): code = """ code_deployment: public(Bytes[32]) @@ -174,7 +174,7 @@ def code_runtime() -> Bytes[32]: return slice(self.code, 0, 32) """ contract = get_contract(code) - settings = Settings(optimize=optimize, experimental_codegen=venom_pipeline) + settings = Settings(optimize=optimize, experimental_codegen=experimental_codegen) code_compiled = compiler.compile_code( code, output_formats=["bytecode", "bytecode_runtime"], settings=settings ) diff --git a/tests/functional/syntax/test_codehash.py b/tests/functional/syntax/test_codehash.py index 0fdee1f776..7aa01a68e9 100644 --- a/tests/functional/syntax/test_codehash.py +++ b/tests/functional/syntax/test_codehash.py @@ -3,7 +3,7 @@ from vyper.utils import keccak256 -def test_get_extcodehash(get_contract, optimize, venom_pipeline): +def test_get_extcodehash(get_contract, optimize, experimental_codegen): code = """ a: address @@ -28,9 +28,7 @@ def foo3() -> bytes32: def foo4() -> bytes32: return self.a.codehash """ - settings = Settings( - optimize=optimize, experimental_codegen=venom_pipeline - ) + settings = Settings(optimize=optimize, experimental_codegen=experimental_codegen) compiled = compile_code(code, output_formats=["bytecode_runtime"], settings=settings) bytecode = bytes.fromhex(compiled["bytecode_runtime"][2:]) hash_ = keccak256(bytecode) diff --git a/tests/unit/compiler/asm/test_asm_optimizer.py b/tests/unit/compiler/asm/test_asm_optimizer.py index d1edd97189..ce7188b0a0 100644 --- a/tests/unit/compiler/asm/test_asm_optimizer.py +++ b/tests/unit/compiler/asm/test_asm_optimizer.py @@ -95,7 +95,7 @@ def test_dead_code_eliminator(code): assert all(ctor_only not in instr for instr in runtime_asm) -def test_library_code_eliminator(make_input_bundle, venom_pipeline): +def test_library_code_eliminator(make_input_bundle, experimental_codegen): library = """ @internal def unused1(): @@ -119,7 +119,7 @@ def foo(): input_bundle = make_input_bundle({"library.vy": library}) res = compile_code(code, input_bundle=input_bundle, output_formats=["asm"]) asm = res["asm"] - if not venom_pipeline: + if not experimental_codegen: assert "some_function()" in asm assert "unused1()" not in asm assert "unused2()" not in asm From 71882ed5ec76132cfae00afec056e9ccbd8bc9f1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 19 Mar 2024 16:29:38 +0200 Subject: [PATCH 246/322] add passthrough_data --- vyper/codegen/ir_node.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index e512b6c9bb..14e396ff74 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -135,6 +135,7 @@ class IRnode: args: List["IRnode"] value: Union[str, int] is_self_call: bool + passthrough_metadata: dict[str, Any] func_ir: Any common_ir: Any @@ -151,6 +152,7 @@ def __init__( add_gas_estimate: int = 0, encoding: Encoding = Encoding.VYPER, is_self_call: bool = False, + passthrough_metadata: dict[str, Any] = None, ): if args is None: args = [] @@ -169,6 +171,7 @@ def __init__( self.encoding = encoding self.as_hex = AS_HEX_DEFAULT self.is_self_call = is_self_call + self.passthrough_metadata = passthrough_metadata or {} self.func_ir = None self.common_ir = None @@ -554,6 +557,7 @@ def from_list( mutable: bool = True, add_gas_estimate: int = 0, is_self_call: bool = False, + passthrough_metadata: dict[str, Any] = None, encoding: Encoding = Encoding.VYPER, ) -> "IRnode": if isinstance(typ, str): # pragma: nocover @@ -587,6 +591,7 @@ def from_list( encoding=encoding, error_msg=error_msg, is_self_call=is_self_call, + passthrough_metadata=passthrough_metadata, ) else: return cls( @@ -601,4 +606,5 @@ def from_list( encoding=encoding, error_msg=error_msg, is_self_call=is_self_call, + passthrough_metadata=passthrough_metadata, ) From 9c63c3eef47bce81a12757d5ea38f0f195c31595 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 19 Mar 2024 16:31:55 +0200 Subject: [PATCH 247/322] fix typo --- tests/unit/compiler/venom/test_dominator_tree.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index edb379f7ee..177ca902e0 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -14,12 +14,12 @@ def _add_bb( ) -> IRBasicBlock: bb = bb if bb is not None else IRBasicBlock(label, ctx) ctx.append_basic_block(bb) - cgf_outs_len = len(cfg_outs) - if cgf_outs_len == 0: + cfg_outs_len = len(cfg_outs) + if cfg_outs_len == 0: bb.append_instruction("stop") - elif cgf_outs_len == 1: + elif cfg_outs_len == 1: bb.append_instruction("jmp", cfg_outs[0]) - elif cgf_outs_len == 2: + elif cfg_outs_len == 2: bb.append_instruction("jnz", IRLiteral(1), cfg_outs[0], cfg_outs[1]) else: raise CompilerPanic("Invalid number of CFG outs") From ea076324460378fcbdffd2f795525d0d2b1cd917 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 19 Mar 2024 16:35:15 +0200 Subject: [PATCH 248/322] remove print --- tests/unit/compiler/venom/test_duplicate_operands.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/compiler/venom/test_duplicate_operands.py b/tests/unit/compiler/venom/test_duplicate_operands.py index 7f12bba51d..7cc58e6f5c 100644 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ b/tests/unit/compiler/venom/test_duplicate_operands.py @@ -23,5 +23,4 @@ def test_duplicate_operands(): bb.append_instruction("stop") asm = generate_assembly_experimental(ctx, optimize=OptimizationLevel.GAS) - print(asm) assert asm == ["PUSH1", 10, "DUP1", "DUP1", "ADD", "MUL", "STOP"] From e37e54a73fbdea0b60948be1d98dad235b2af996 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Mar 2024 10:19:43 -0400 Subject: [PATCH 249/322] fixes from bad merge --- .github/workflows/test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 810aae7d67..10f96bdc21 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -117,11 +117,12 @@ jobs: - python-version: ["3.10", "310"] opt-mode: gas debug: false + evm-version: shanghai - python-version: ["3.11", "311"] opt-mode: gas debug: false - + evm-version: shanghai name: py${{ matrix.python-version[1] }}-opt-${{ matrix.opt-mode }}${{ matrix.debug && '-debug' || '' }}${{ matrix.memorymock && '-memorymock' || '' }}${{ matrix.experimental-codegen && '-experimental' || '' }}-${{ matrix.evm-version }} @@ -153,7 +154,7 @@ jobs: --evm-version ${{ matrix.evm-version }} \ ${{ matrix.debug && '--enable-compiler-debug-mode' || '' }} \ ${{ matrix.memorymock && '--memorymock' || '' }} \ - ${{ matrix.experimental-codegen && '--experimental-codegen || '' }} + ${{ matrix.experimental-codegen && '--experimental-codegen || '' }} \ --showlocals -r aR \ tests/ From 34316f352b68c72065baa6bff63ae575f5257ff7 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Mar 2024 10:52:58 -0400 Subject: [PATCH 250/322] move an optimization to assembly --- vyper/ir/compile_ir.py | 6 +++++- vyper/venom/analysis.py | 1 + vyper/venom/venom_to_assembly.py | 10 +--------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index e4a4cc60f7..191803295e 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -1020,7 +1020,11 @@ def _stack_peephole_opts(assembly): changed = True del assembly[i] continue - if assembly[i : i + 2] == ["SWAP1", "SWAP1"]: + if ( + isinstance(assembly[i], str) + and assembly[i].startswith("SWAP") + and assembly[i] == assembly[i + 1] + ): changed = True del assembly[i : i + 2] if assembly[i] == "SWAP1" and assembly[i + 1].lower() in COMMUTATIVE_OPS: diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 240632d199..0bc1ec0fac 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -1,4 +1,5 @@ from typing import Optional + from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet from vyper.venom.basicblock import ( diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index b3161dfb2f..ad8cbb8356 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -534,16 +534,8 @@ def swap(self, assembly, stack, depth): if depth == 0: return - inst = _evm_swap_for(depth) - - # Double swaps cancel each other out - if len(assembly) > 0 and inst == assembly[-1]: - assembly.pop() - stack.swap(depth) - return - stack.swap(depth) - assembly.append(inst) + assembly.append(_evm_swap_for(depth)) def dup(self, assembly, stack, depth): stack.dup(depth) From 41474df95ddbc2e8c8bfb69de71c50f44b9e68a1 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Mar 2024 11:18:55 -0400 Subject: [PATCH 251/322] rename a variable --- vyper/venom/passes/simplify_cfg.py | 32 +++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/vyper/venom/passes/simplify_cfg.py b/vyper/venom/passes/simplify_cfg.py index 56816c5324..a8a553694d 100644 --- a/vyper/venom/passes/simplify_cfg.py +++ b/vyper/venom/passes/simplify_cfg.py @@ -20,23 +20,23 @@ def _merge_blocks(self, a: IRBasicBlock, b: IRBasicBlock): # Update CFG a.cfg_out = b.cfg_out if len(b.cfg_out) > 0: - next = b.cfg_out.first() - next.remove_cfg_in(b) - next.add_cfg_in(a) + next_bb = b.cfg_out.first() + next_bb.remove_cfg_in(b) + next_bb.add_cfg_in(a) self.ctx.basic_blocks.remove(b) def _merge_jump(self, a: IRBasicBlock, b: IRBasicBlock): - next = b.cfg_out.first() + next_bb = b.cfg_out.first() jump_inst = a.instructions[-1] assert b.label in jump_inst.operands, f"{b.label} {jump_inst.operands}" - jump_inst.operands[jump_inst.operands.index(b.label)] = next.label + jump_inst.operands[jump_inst.operands.index(b.label)] = next_bb.label # Update CFG a.remove_cfg_out(b) - a.add_cfg_out(next) - next.remove_cfg_in(b) - next.add_cfg_in(a) + a.add_cfg_out(next_bb) + next_bb.remove_cfg_in(b) + next_bb.add_cfg_in(a) self.ctx.basic_blocks.remove(b) @@ -45,16 +45,20 @@ def _collapse_chained_blocks_r(self, bb: IRBasicBlock): DFS into the cfg and collapse blocks with a single predecessor to the predecessor """ if len(bb.cfg_out) == 1: - next = bb.cfg_out.first() - if len(next.cfg_in) == 1: - self._merge_blocks(bb, next) + next_bb = bb.cfg_out.first() + if len(next_bb.cfg_in) == 1: + self._merge_blocks(bb, next_bb) self._collapse_chained_blocks_r(bb) return elif len(bb.cfg_out) > 1: bb_out = bb.cfg_out.copy() - for next in bb_out: - if len(next.cfg_in) == 1 and len(next.cfg_out) == 1 and len(next.instructions) == 1: - self._merge_jump(bb, next) + for next_bb in bb_out: + if ( + len(next_bb.cfg_in) == 1 + and len(next_bb.cfg_out) == 1 + and len(next_bb.instructions) == 1 + ): + self._merge_jump(bb, next_bb) self._collapse_chained_blocks_r(bb) return From c3e0ac91e4d8fa54156e43501d7d0a2c4ae657fc Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Mar 2024 11:38:54 -0400 Subject: [PATCH 252/322] simplify ir_node_to_venom --- vyper/venom/ir_node_to_venom.py | 55 ++++++++------------------------ vyper/venom/venom_to_assembly.py | 4 +-- 2 files changed, 16 insertions(+), 43 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index ffb389527c..5100af4c2c 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -15,8 +15,14 @@ ) from vyper.venom.function import IRFunction -_BINARY_IR_INSTRUCTIONS = frozenset( +# Instructions that are mapped to their inverse +INVERSE_MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} + +# Instructions that have a direct EVM opcode equivalent and can +# be passed through to the EVM assembly without special handling +PASS_THROUGH_INSTRUCTIONS = frozenset( [ + # binary instructions "eq", "gt", "lt", @@ -40,16 +46,6 @@ "sha3", "sha3_64", "signextend", - ] -) - -# Instructions that are mapped to their inverse -INVERSE_MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} - -# Instructions that have a direct EVM opcode equivalent and can -# be passed through to the EVM assembly without special handling -PASS_THROUGH_INSTRUCTIONS = frozenset( - [ "chainid", "basefee", "timestamp", @@ -86,11 +82,6 @@ "assert", "assert_unreachable", "exit", - ] -) - -PASS_THROUGH_REVERSED_INSTRUCTIONS = frozenset( - [ "calldatacopy", "mcopy", "extcodecopy", @@ -104,6 +95,9 @@ "create2", "addmod", "mulmod", + "call", + "delegatecall", + "staticcall", ] ) @@ -135,16 +129,6 @@ def ir_node_to_venom(ir: IRnode) -> IRFunction: return ctx -def _convert_binary_op( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable, swap: bool = False -) -> Optional[IRVariable]: - ir_args = ir.args[::-1] if swap else ir.args - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir_args, symbols) - - assert isinstance(ir.value, str) # mypy hint - return ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) - - def _append_jmp(ctx: IRFunction, label: IRLabel) -> None: bb = ctx.get_basic_block() if bb.is_terminated: @@ -210,12 +194,8 @@ def _handle_internal_func( _convert_ir_bb(ctx, ir.args[0].args[2], symbols) -def _convert_ir_simple_node( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable, reverse: bool = False -) -> Optional[IRVariable]: - args = [_convert_ir_bb(ctx, arg, symbols) for arg in ir.args] - if reverse: - args = reversed(args) # type: ignore +def _convert_ir_simple_node( ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optional[IRVariable]: + args = reversed([_convert_ir_bb(ctx, arg, symbols) for arg in ir.args]) return ctx.get_basic_block().append_instruction(ir.value, *args) # type: ignore @@ -253,18 +233,14 @@ def _convert_ir_bb(ctx, ir, symbols): ctx.push_source(ir) - if ir.value in _BINARY_IR_INSTRUCTIONS: - return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3_64"]) - elif ir.value in INVERSE_MAPPED_IR_INSTRUCTIONS: + if ir.value in INVERSE_MAPPED_IR_INSTRUCTIONS: org_value = ir.value ir.value = INVERSE_MAPPED_IR_INSTRUCTIONS[ir.value] - new_var = _convert_binary_op(ctx, ir, symbols) + new_var = _convert_ir_simple_node(ctx, ir, symbols) ir.value = org_value return ctx.get_basic_block().append_instruction("iszero", new_var) elif ir.value in PASS_THROUGH_INSTRUCTIONS: return _convert_ir_simple_node(ctx, ir, symbols) - elif ir.value in PASS_THROUGH_REVERSED_INSTRUCTIONS: - return _convert_ir_simple_node(ctx, ir, symbols, reverse=True) elif ir.value == "return": ctx.get_basic_block().append_instruction( "return", IRVariable("ret_size"), IRVariable("ret_ofst") @@ -306,9 +282,6 @@ def _convert_ir_bb(ctx, ir, symbols): ret = _convert_ir_bb(ctx, ir_node, symbols) return ret - elif ir.value in ["delegatecall", "staticcall", "call"]: - args = reversed(_convert_ir_bb_list(ctx, ir.args, symbols)) - return ctx.get_basic_block().append_instruction(ir.value, *args) elif ir.value == "if": cond = ir.args[0] diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index ad8cbb8356..879cd4c7b4 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -483,10 +483,10 @@ def _generate_evm_for_instruction( elif opcode == "sha3_64": assembly.extend( [ - *PUSH(MemoryPositions.FREE_VAR_SPACE2), - "MSTORE", *PUSH(MemoryPositions.FREE_VAR_SPACE), "MSTORE", + *PUSH(MemoryPositions.FREE_VAR_SPACE2), + "MSTORE", *PUSH(64), *PUSH(MemoryPositions.FREE_VAR_SPACE), "SHA3", From 2a7cb0bbd397660b4909b513e8d5d2530d0a1688 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Mar 2024 11:43:04 -0400 Subject: [PATCH 253/322] add a couple comments --- vyper/venom/ir_node_to_venom.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 5100af4c2c..34daf350ae 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -194,8 +194,13 @@ def _handle_internal_func( _convert_ir_bb(ctx, ir.args[0].args[2], symbols) -def _convert_ir_simple_node( ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optional[IRVariable]: - args = reversed([_convert_ir_bb(ctx, arg, symbols) for arg in ir.args]) +def _convert_ir_simple_node( + ctx: IRFunction, ir: IRnode, symbols: SymbolTable +) -> Optional[IRVariable]: + # execute in order + args = [_convert_ir_bb(ctx, arg, symbols) for arg in ir.args] + # reverse for stack + args.reverse() return ctx.get_basic_block().append_instruction(ir.value, *args) # type: ignore From 5ec9e6052df50cf9e21935d86cc3eed69e0c226a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Mar 2024 11:45:00 -0400 Subject: [PATCH 254/322] count iterations, not changes for exception --- vyper/venom/passes/base_pass.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/venom/passes/base_pass.py b/vyper/venom/passes/base_pass.py index 649a939677..38aaa234ee 100644 --- a/vyper/venom/passes/base_pass.py +++ b/vyper/venom/passes/base_pass.py @@ -9,13 +9,13 @@ def run_pass(cls, *args, **kwargs): t = cls() count = 0 - while True: + for i in range(1000): changes_count = t._run_pass(*args, **kwargs) or 0 count += changes_count if changes_count == 0: break - if count > 1000: - raise Exception("Too many iterations in IR pass!", t.__class__) + else: + raise Exception("Too many iterations in IR pass!", t.__class__) return count From 6de6e7dde4b2b0a5d1878a047cd9302198b153c0 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Mar 2024 12:07:06 -0400 Subject: [PATCH 255/322] wip - alloca --- vyper/codegen/expr.py | 1 + vyper/codegen/stmt.py | 1 + vyper/ir/optimizer.py | 2 ++ vyper/venom/ir_node_to_venom.py | 6 ++++-- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index d7afe6c7f6..1dce16281c 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -177,6 +177,7 @@ def parse_Name(self): annotation=self.expr.id, mutable=var.mutable, ) + ret.passthrough_metadata["alloca"] = (self.expr.id, var.pos, var.typ.memory_bytes_required) ret._referenced_variables = {var} return ret diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py index 1da31d3bda..3487da5142 100644 --- a/vyper/codegen/stmt.py +++ b/vyper/codegen/stmt.py @@ -64,6 +64,7 @@ def parse_AnnAssign(self): rhs = Expr(self.stmt.value, self.context).ir_node lhs = IRnode.from_list(alloced, typ=ltyp, location=MEMORY) + lhs.passthrough_metadata["alloca"] = (varname, alloced, ltyp.memory_bytes_required) return make_setter(lhs, rhs) diff --git a/vyper/ir/optimizer.py b/vyper/ir/optimizer.py index c6e9ba4771..7ff5390e4b 100644 --- a/vyper/ir/optimizer.py +++ b/vyper/ir/optimizer.py @@ -441,6 +441,7 @@ def _optimize(node: IRnode, parent: Optional[IRnode]) -> Tuple[bool, IRnode]: annotation = node.annotation add_gas_estimate = node.add_gas_estimate is_self_call = node.is_self_call + passthrough_metadata = node.passthrough_metadata changed = False @@ -464,6 +465,7 @@ def finalize(val, args): annotation=annotation, add_gas_estimate=add_gas_estimate, is_self_call=is_self_call, + passthrough_metadata=passthrough_metadata, ) if should_check_symbols: diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 34daf350ae..7a84d314de 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -198,8 +198,8 @@ def _convert_ir_simple_node( ctx: IRFunction, ir: IRnode, symbols: SymbolTable ) -> Optional[IRVariable]: # execute in order - args = [_convert_ir_bb(ctx, arg, symbols) for arg in ir.args] - # reverse for stack + args = _convert_ir_bb_list(ctx, ir.args, symbols) + # reverse output variables for stack args.reverse() return ctx.get_basic_block().append_instruction(ir.value, *args) # type: ignore @@ -535,6 +535,8 @@ def emit_body_blocks(): elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): _convert_ir_opcode(ctx, ir, symbols) elif isinstance(ir.value, str) and ir.value in symbols: + if "alloca" in ir.passthrough_metadata: + ctx.get_basic_block().append_instruction("alloca", *ir.passthrough_metadata["alloca"]) return symbols[ir.value] elif ir.is_literal: return IRLiteral(ir.value) From 5c58549db5dd0a4d7d08a937f291e894c01a4b62 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 11:34:38 +0200 Subject: [PATCH 256/322] fixes --- vyper/venom/ir_node_to_venom.py | 2 +- vyper/venom/passes/simplify_cfg.py | 2 +- vyper/venom/venom_to_assembly.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 7a84d314de..502e658c68 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -374,7 +374,7 @@ def _convert_ir_bb(ctx, ir, symbols): assert 0 <= c <= 255, "data with invalid size" ctx.append_data("db", [c]) # type: ignore elif isinstance(c.value, bytes): - ctx.append_data("db", [c]) # type: ignore + ctx.append_data("db", [c.value]) # type: ignore elif isinstance(c, IRnode): data = _convert_ir_bb(ctx, c, symbols) ctx.append_data("db", [data]) # type: ignore diff --git a/vyper/venom/passes/simplify_cfg.py b/vyper/venom/passes/simplify_cfg.py index a8a553694d..7f02ccf819 100644 --- a/vyper/venom/passes/simplify_cfg.py +++ b/vyper/venom/passes/simplify_cfg.py @@ -50,7 +50,7 @@ def _collapse_chained_blocks_r(self, bb: IRBasicBlock): self._merge_blocks(bb, next_bb) self._collapse_chained_blocks_r(bb) return - elif len(bb.cfg_out) > 1: + elif len(bb.cfg_out) == 2: bb_out = bb.cfg_out.copy() for next_bb in bb_out: if ( diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 879cd4c7b4..715a7c0860 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -186,11 +186,11 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: label = inst.operands[0].value data_segments[label] = [DataHeader(f"_sym_{label}")] elif inst.opcode == "db": - data = inst.operands[0].value - if isinstance(data, bytes): - data_segments[label].append(data) + data = inst.operands[0] + if isinstance(data, IRLabel): + data_segments[label].append(f"_sym_{data.value}") else: - data_segments[label].append(f"_sym_{inst.operands[0].value}") + data_segments[label].append(data) asm.extend(list(data_segments.values())) From 5e7f93cf7ee85513cdaf776c1f9772792716171f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 11:36:19 +0200 Subject: [PATCH 257/322] equalities --- vyper/venom/basicblock.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 76ad287722..e70f29fd7d 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -124,6 +124,12 @@ def __init__(self, value: int) -> None: assert isinstance(value, int), "value must be an int" self.value = value + def __hash__(self) -> int: + return self.value.__hash__() + + def __eq__(self, v: object) -> bool: + return self.value == v + def __repr__(self) -> str: return str(self.value) @@ -199,6 +205,12 @@ def __init__(self, value: str, is_symbol: bool = False) -> None: self.value = value self.is_symbol = is_symbol + def __hash__(self) -> int: + return hash(self.value) + + def __eq__(self, v: object) -> bool: + return self.value == v + def __repr__(self) -> str: return self.value From 752b685189588ad6e1d4caa5c07ec4777939da51 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 11:43:50 +0200 Subject: [PATCH 258/322] remove unnecessary assert --- tests/unit/compiler/venom/test_liveness_simple_loop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/compiler/venom/test_liveness_simple_loop.py b/tests/unit/compiler/venom/test_liveness_simple_loop.py index d92a50596a..63c7011b67 100644 --- a/tests/unit/compiler/venom/test_liveness_simple_loop.py +++ b/tests/unit/compiler/venom/test_liveness_simple_loop.py @@ -13,4 +13,3 @@ def foo(a: uint256): def test_liveness_simple_loop(): vyper.compile_code(source, ["opcodes"]) - assert True From e5d2c4598286e470bf0c97270ffec2162199486b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 11:45:53 +0200 Subject: [PATCH 259/322] compile with experimental --- tests/unit/compiler/venom/test_liveness_simple_loop.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/compiler/venom/test_liveness_simple_loop.py b/tests/unit/compiler/venom/test_liveness_simple_loop.py index 63c7011b67..e725518179 100644 --- a/tests/unit/compiler/venom/test_liveness_simple_loop.py +++ b/tests/unit/compiler/venom/test_liveness_simple_loop.py @@ -1,4 +1,5 @@ import vyper +from vyper.compiler.settings import Settings source = """ @external @@ -12,4 +13,4 @@ def foo(a: uint256): def test_liveness_simple_loop(): - vyper.compile_code(source, ["opcodes"]) + vyper.compile_code(source, ["opcodes"], settings=Settings(experimental_codegen=True)) From 5c453f30c18256c333d034d3663a16917099d5d5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 11:51:10 +0200 Subject: [PATCH 260/322] bound check int literal --- vyper/venom/venom_to_assembly.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 715a7c0860..7f648e39bc 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -250,6 +250,10 @@ def _emit_input_operands( continue if isinstance(op, IRLiteral): + if op.value < -(2**255): + raise Exception(f"Value too low: {op.value}") + elif op.value >= 2**256: + raise Exception(f"Value too high: {op.value}") assembly.extend(PUSH(op.value % 2**256)) stack.push(op) continue From c42011666a9f1022c3971ecffdb4585bb5aab02d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 11:56:06 +0200 Subject: [PATCH 261/322] variable rename --- vyper/venom/basicblock.py | 10 +++++----- vyper/venom/function.py | 14 +++++++------- vyper/venom/venom_to_assembly.py | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index e70f29fd7d..3171fd0172 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -235,7 +235,7 @@ class IRInstruction: parent: Optional["IRBasicBlock"] fence_id: int annotation: Optional[str] - source_pos: Optional[int] + ast_source: Optional[int] error_msg: Optional[str] def __init__( @@ -255,7 +255,7 @@ def __init__( self.parent = None self.fence_id = -1 self.annotation = None - self.source_pos = None + self.ast_source = None self.error_msg = None def get_label_operands(self) -> list[IRLabel]: @@ -428,7 +428,7 @@ def append_instruction( inst = IRInstruction(opcode, inst_args, ret) inst.parent = self - inst.source_pos = self.parent.source_pos + inst.ast_source = self.parent.ast_source inst.error_msg = self.parent.error_msg self.instructions.append(inst) return ret @@ -451,7 +451,7 @@ def append_invoke_instruction( inst = IRInstruction("invoke", inst_args, ret) inst.parent = self - inst.source_pos = self.parent.source_pos + inst.ast_source = self.parent.ast_source inst.error_msg = self.parent.error_msg self.instructions.append(inst) return ret @@ -463,7 +463,7 @@ def insert_instruction(self, instruction: IRInstruction, index: Optional[int] = assert not self.is_terminated, self index = len(self.instructions) instruction.parent = self - instruction.source_pos = self.parent.source_pos + instruction.ast_source = self.parent.ast_source instruction.error_msg = self.parent.error_msg self.instructions.insert(index, instruction) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index b37ec35fac..32bbd90797 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -31,7 +31,7 @@ class IRFunction: last_variable: int # Used during code generation - _source_pos: list[int] + _ast_source: list[int] _error_msg: list[str] def __init__(self, name: IRLabel = None) -> None: @@ -47,7 +47,7 @@ def __init__(self, name: IRLabel = None) -> None: self.last_label = 0 self.last_variable = 0 - self._source_pos = [] + self._ast_source = [] self._error_msg = [] self.add_entry_point(name) @@ -211,18 +211,18 @@ def normalized(self) -> bool: def push_source(self, ir): if isinstance(ir, IRnode): - self._source_pos.append(ir.ast_source) + self._ast_source.append(ir.ast_source) self._error_msg.append(ir.error_msg) def pop_source(self): - assert len(self._source_pos) > 0, "Empty source stack" - self._source_pos.pop() + assert len(self._ast_source) > 0, "Empty source stack" + self._ast_source.pop() assert len(self._error_msg) > 0, "Empty error stack" self._error_msg.pop() @property - def source_pos(self) -> Optional[int]: - return self._source_pos[-1] if len(self._source_pos) > 0 else None + def ast_source(self) -> Optional[int]: + return self._ast_source[-1] if len(self._ast_source) > 0 else None @property def error_msg(self) -> Optional[str]: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 7f648e39bc..087cce836a 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -110,7 +110,7 @@ def apply_line_numbers(inst: IRInstruction, asm) -> list[str]: ret = [] for op in asm: if isinstance(op, str) and not isinstance(op, Instruction): - ret.append(Instruction(op, inst.source_pos, inst.error_msg)) + ret.append(Instruction(op, inst.ast_source, inst.error_msg)) else: ret.append(op) return ret # type: ignore From 43185a0ce51fee2b96d6a5fba7531ac73b950142 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 11:57:18 +0200 Subject: [PATCH 262/322] style change --- vyper/venom/venom_to_assembly.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 087cce836a..a630552529 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -342,8 +342,7 @@ def _generate_evm_for_instruction( self, inst: IRInstruction, stack: StackModel, next_liveness: OrderedSet = None ) -> list[str]: assembly: list[str | int] = [] - if next_liveness is None: - next_liveness = OrderedSet() + next_liveness = next_liveness or OrderedSet() opcode = inst.opcode # From a866575a09585102a6e03732842cc92f92d0c8a2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 11:58:23 +0200 Subject: [PATCH 263/322] style --- vyper/venom/venom_to_assembly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index a630552529..b4430a19d6 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -364,7 +364,7 @@ def _generate_evm_for_instruction( elif opcode == "istore": addr = inst.operands[1] if isinstance(addr, IRLiteral): - operands = inst.operands[0:1] + operands = inst.operands[:1] else: operands = inst.operands elif opcode == "log": From aff18ad9a79d0efbc4888bd0bb4aa1b9b8ac0942 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 12:04:36 +0200 Subject: [PATCH 264/322] added comment --- vyper/venom/venom_to_assembly.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index b4430a19d6..d702b8dc2b 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -355,6 +355,10 @@ def _generate_evm_for_instruction( operands = inst.get_non_label_operands() elif opcode == "alloca": operands = inst.operands[1:2] + + # iload and istore are special cases because they can take a literal + # that is handled specialy with the _OFST macro. Look below, after the + # stack reordering. elif opcode == "iload": addr = inst.operands[0] if isinstance(addr, IRLiteral): From 560e4a94d14123d7ae3a4d89627aae74076a5892 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 12:05:29 +0200 Subject: [PATCH 265/322] remove debugging leftover --- vyper/venom/passes/make_ssa.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 4169451c2e..84b416c03a 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -15,7 +15,6 @@ class MakeSSA(IRPass): defs: dict[IRVariable, OrderedSet[IRBasicBlock]] def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: - global count self.ctx = ctx calculate_cfg(ctx) From b942c0329cfdc5b44e1b8930c5709ef40733c277 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 12:07:58 +0200 Subject: [PATCH 266/322] rename members --- vyper/venom/function.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 32bbd90797..81d46fec3e 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -31,8 +31,8 @@ class IRFunction: last_variable: int # Used during code generation - _ast_source: list[int] - _error_msg: list[str] + _ast_source_stack: list[int] + _error_msg_stack: list[str] def __init__(self, name: IRLabel = None) -> None: if name is None: @@ -47,8 +47,8 @@ def __init__(self, name: IRLabel = None) -> None: self.last_label = 0 self.last_variable = 0 - self._ast_source = [] - self._error_msg = [] + self._ast_source_stack = [] + self._error_msg_stack = [] self.add_entry_point(name) self.append_basic_block(IRBasicBlock(name, self)) @@ -211,22 +211,22 @@ def normalized(self) -> bool: def push_source(self, ir): if isinstance(ir, IRnode): - self._ast_source.append(ir.ast_source) - self._error_msg.append(ir.error_msg) + self._ast_source_stack.append(ir.ast_source) + self._error_msg_stack.append(ir.error_msg) def pop_source(self): - assert len(self._ast_source) > 0, "Empty source stack" - self._ast_source.pop() - assert len(self._error_msg) > 0, "Empty error stack" - self._error_msg.pop() + assert len(self._ast_source_stack) > 0, "Empty source stack" + self._ast_source_stack.pop() + assert len(self._error_msg_stack) > 0, "Empty error stack" + self._error_msg_stack.pop() @property def ast_source(self) -> Optional[int]: - return self._ast_source[-1] if len(self._ast_source) > 0 else None + return self._ast_source_stack[-1] if len(self._ast_source_stack) > 0 else None @property def error_msg(self) -> Optional[str]: - return self._error_msg[-1] if len(self._error_msg) > 0 else None + return self._error_msg_stack[-1] if len(self._error_msg_stack) > 0 else None def copy(self): new = IRFunction(self.name) From 0c066f6abd908a640f6214c9ee64f5e1499f5704 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 12:10:16 +0200 Subject: [PATCH 267/322] lint --- vyper/codegen/expr.py | 13 +++++++++---- vyper/ir/compile_ir.py | 8 +++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index 1dce16281c..0625deb16a 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -177,7 +177,11 @@ def parse_Name(self): annotation=self.expr.id, mutable=var.mutable, ) - ret.passthrough_metadata["alloca"] = (self.expr.id, var.pos, var.typ.memory_bytes_required) + ret.passthrough_metadata["alloca"] = ( + self.expr.id, + var.pos, + var.typ.memory_bytes_required, + ) ret._referenced_variables = {var} return ret @@ -455,9 +459,10 @@ def build_in_comparator(self): ret = ["seq"] - with left.cache_when_complex("needle") as (b1, left), right.cache_when_complex( - "haystack" - ) as (b2, right): + with ( + left.cache_when_complex("needle") as (b1, left), + right.cache_when_complex("haystack") as (b2, right), + ): # unroll the loop for compile-time list literals if right.value == "multi": # empty list literals should be rejected at typechecking time diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 191803295e..1e57881e9f 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -201,9 +201,11 @@ def apply_line_no_wrapper(*args, **kwargs): ret = func(*args, **kwargs) new_ret = [ - Instruction(i, code.ast_source, code.error_msg) - if isinstance(i, str) and not isinstance(i, Instruction) - else i + ( + Instruction(i, code.ast_source, code.error_msg) + if isinstance(i, str) and not isinstance(i, Instruction) + else i + ) for i in ret ] return new_ret From 54feec49c910e84cd59cfcf6dce203243a60d362 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Mar 2024 12:16:39 +0200 Subject: [PATCH 268/322] assert there is a producing instruction --- vyper/venom/passes/dft.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index ba30065ff4..7839b826b4 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -19,6 +19,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): for op in inst.liveness: target = self.dfg.get_producing_instruction(op) + assert target is not None, f"no producing instruction for {op}" if target is None or target.parent != inst.parent or target.fence_id != inst.fence_id: # don't reorder across basic block or fence boundaries continue From 0b5e7645c2e4f930a843ba6d33d5ea2b5ef63d42 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Mar 2024 09:08:00 +0200 Subject: [PATCH 269/322] remove unnecessary check --- vyper/venom/passes/dft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 7839b826b4..60b95a1991 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -20,7 +20,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): for op in inst.liveness: target = self.dfg.get_producing_instruction(op) assert target is not None, f"no producing instruction for {op}" - if target is None or target.parent != inst.parent or target.fence_id != inst.fence_id: + if target.parent != inst.parent or target.fence_id != inst.fence_id: # don't reorder across basic block or fence boundaries continue self._process_instruction_r(bb, target) From 20536bf3ba5381a4d8193e09465d8fde2b864711 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Mar 2024 15:35:52 +0200 Subject: [PATCH 270/322] variable name elaboration --- vyper/venom/dominators.py | 62 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index 6a7690ffb6..d089ea1f42 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -10,33 +10,33 @@ class DominatorTree: """ ctx: IRFunction - entry: IRBasicBlock + entry_block: IRBasicBlock dfs_order: dict[IRBasicBlock, int] - dfs: list[IRBasicBlock] + dfs_walk: list[IRBasicBlock] dominators: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] - idoms: dict[IRBasicBlock, IRBasicBlock] + immediate_dominators: dict[IRBasicBlock, IRBasicBlock] dominated: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] - df: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] + dominator_frontiers: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] def __init__(self, ctx: IRFunction, entry: IRBasicBlock): self.ctx = ctx - self.entry = entry + self.entry_block = entry self.dfs_order = {} - self.dfs = [] + self.dfs_walk = [] self.dominators = {} - self.idoms = {} + self.immediate_dominators = {} self.dominated = {} - self.df = {} + self.dominator_frontiers = {} self._compute() def dominates(self, bb1, bb2): return bb2 in self.dominators[bb1] def immediate_dominator(self, bb): - return self.idoms.get(bb) + return self.immediate_dominators.get(bb) def _compute(self): - self._dfs(self.entry, OrderedSet()) + self._dfs(self.entry_block, OrderedSet()) self._compute_dominators() self._compute_idoms() self._compute_df() @@ -44,7 +44,7 @@ def _compute(self): def _compute_dominators(self): basic_blocks = list(self.dfs_order.keys()) self.dominators = {bb: OrderedSet(basic_blocks) for bb in basic_blocks} - self.dominators[self.entry] = OrderedSet({self.entry}) + self.dominators[self.entry_block] = OrderedSet({self.entry_block}) changed = True count = len(basic_blocks) ** 2 # TODO: find a proper bound for this while changed: @@ -53,7 +53,7 @@ def _compute_dominators(self): raise CompilerPanic("Dominators computation failed to converge") changed = False for bb in basic_blocks: - if bb == self.entry: + if bb == self.entry_block: continue preds = bb.cfg_in if len(preds) == 0: @@ -68,32 +68,32 @@ def _compute_idoms(self): """ Compute immediate dominators """ - self.idoms = {bb: None for bb in self.dfs_order.keys()} - self.idoms[self.entry] = self.entry - for bb in self.dfs: - if bb == self.entry: + self.immediate_dominators = {bb: None for bb in self.dfs_order.keys()} + self.immediate_dominators[self.entry_block] = self.entry_block + for bb in self.dfs_walk: + if bb == self.entry_block: continue doms = sorted(self.dominators[bb], key=lambda x: self.dfs_order[x]) - self.idoms[bb] = doms[1] + self.immediate_dominators[bb] = doms[1] - self.dominated = {bb: OrderedSet() for bb in self.dfs} - for dom, target in self.idoms.items(): + self.dominated = {bb: OrderedSet() for bb in self.dfs_walk} + for dom, target in self.immediate_dominators.items(): self.dominated[target].add(dom) def _compute_df(self): """ Compute dominance frontier """ - basic_blocks = self.dfs - self.df = {bb: OrderedSet() for bb in basic_blocks} + basic_blocks = self.dfs_walk + self.dominator_frontiers = {bb: OrderedSet() for bb in basic_blocks} - for bb in self.dfs: + for bb in self.dfs_walk: if len(bb.cfg_in) > 1: for pred in bb.cfg_in: runner = pred - while runner != self.idoms[bb]: - self.df[runner].add(bb) - runner = self.idoms[runner] + while runner != self.immediate_dominators[bb]: + self.dominator_frontiers[runner].add(bb) + runner = self.immediate_dominators[runner] def dominance_frontier(self, basic_blocks: list[IRBasicBlock]) -> OrderedSet[IRBasicBlock]: """ @@ -101,16 +101,16 @@ def dominance_frontier(self, basic_blocks: list[IRBasicBlock]) -> OrderedSet[IRB """ df = OrderedSet[IRBasicBlock]() for bb in basic_blocks: - df.update(self.df[bb]) + df.update(self.dominator_frontiers[bb]) return df def _intersect(self, bb1, bb2): dfs_order = self.dfs_order while bb1 != bb2: while dfs_order[bb1] < dfs_order[bb2]: - bb1 = self.idoms[bb1] + bb1 = self.immediate_dominators[bb1] while dfs_order[bb1] > dfs_order[bb2]: - bb2 = self.idoms[bb2] + bb2 = self.immediate_dominators[bb2] return bb1 def _dfs(self, entry: IRBasicBlock, visited): @@ -120,8 +120,8 @@ def _dfs(self, entry: IRBasicBlock, visited): if bb not in visited: self._dfs(bb, visited) - self.dfs.append(entry) - self.dfs_order[entry] = len(self.dfs) + self.dfs_walk.append(entry) + self.dfs_order[entry] = len(self.dfs_walk) def as_graph(self) -> str: """ @@ -129,7 +129,7 @@ def as_graph(self) -> str: """ lines = ["digraph dominator_tree {"] for bb in self.ctx.basic_blocks: - if bb == self.entry: + if bb == self.entry_block: continue idom = self.immediate_dominator(bb) if idom is None: From 2a404cc02fe0720c08f77f1fb20d09767cf64310 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Mar 2024 15:39:49 +0200 Subject: [PATCH 271/322] add documentation --- vyper/venom/dominators.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index d089ea1f42..ce6a4868cf 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -6,7 +6,9 @@ class DominatorTree: """ - Dominator tree. + Dominator tree implementation. This class computes the dominator tree of a + function and provides methods to query the tree. The tree is computed using + the Lengauer-Tarjan algorithm. """ ctx: IRFunction @@ -30,18 +32,30 @@ def __init__(self, ctx: IRFunction, entry: IRBasicBlock): self._compute() def dominates(self, bb1, bb2): + """ + Check if bb1 dominates bb2. + """ return bb2 in self.dominators[bb1] def immediate_dominator(self, bb): + """ + Return the immediate dominator of a basic block. + """ return self.immediate_dominators.get(bb) def _compute(self): + """ + Compute the dominator tree. + """ self._dfs(self.entry_block, OrderedSet()) self._compute_dominators() self._compute_idoms() self._compute_df() def _compute_dominators(self): + """ + Compute dominators + """ basic_blocks = list(self.dfs_order.keys()) self.dominators = {bb: OrderedSet(basic_blocks) for bb in basic_blocks} self.dominators[self.entry_block] = OrderedSet({self.entry_block}) @@ -105,6 +119,9 @@ def dominance_frontier(self, basic_blocks: list[IRBasicBlock]) -> OrderedSet[IRB return df def _intersect(self, bb1, bb2): + """ + Find the nearest common dominator of two basic blocks. + """ dfs_order = self.dfs_order while bb1 != bb2: while dfs_order[bb1] < dfs_order[bb2]: @@ -114,6 +131,12 @@ def _intersect(self, bb1, bb2): return bb1 def _dfs(self, entry: IRBasicBlock, visited): + """ + Depth-first search to compute the DFS order of the basic blocks. This + is used to compute the dominator tree. The sequence of basic blocks in + the DFS order is stored in `self.dfs_walk`. The DFS order of each basic + block is stored in `self.dfs_order`. + """ visited.add(entry) for bb in entry.cfg_out: From 625996205151d8c4a18dce7694a4160a8d238b2d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Mar 2024 16:21:24 +0200 Subject: [PATCH 272/322] update dominator users --- vyper/venom/passes/make_ssa.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 84b416c03a..b4c3e1a509 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -36,8 +36,8 @@ def _add_phi_nodes(self): Add phi nodes to the function. """ self._compute_defs() - self.work = {var: 0 for var in self.dom.dfs} - self.has_already = {var: 0 for var in self.dom.dfs} + self.work = {var: 0 for var in self.dom.dfs_walk} + self.has_already = {var: 0 for var in self.dom.dfs_walk} i = 0 # Iterate over all variables @@ -46,7 +46,7 @@ def _add_phi_nodes(self): defs = list(d) while len(defs) > 0: bb = defs.pop() - for dom in self.dom.df[bb]: + for dom in self.dom.dominator_frontiers[bb]: if self.has_already[dom] >= i: continue @@ -161,7 +161,7 @@ def _compute_defs(self): Compute the definition points of variables in the function. """ self.defs = {} - for bb in self.dom.dfs: + for bb in self.dom.dfs_walk: assignments = bb.get_assignments() for var in assignments: if var not in self.defs: From 3c88c50de85f572ae0a183c0ad707a313aff32a0 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 21 Mar 2024 12:43:59 -0400 Subject: [PATCH 273/322] lint --- vyper/codegen/expr.py | 7 +++---- vyper/venom/passes/base_pass.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index 0625deb16a..10c3d68b50 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -459,10 +459,9 @@ def build_in_comparator(self): ret = ["seq"] - with ( - left.cache_when_complex("needle") as (b1, left), - right.cache_when_complex("haystack") as (b2, right), - ): + with left.cache_when_complex("needle") as (b1, left), right.cache_when_complex( + "haystack" + ) as (b2, right): # unroll the loop for compile-time list literals if right.value == "multi": # empty list literals should be rejected at typechecking time diff --git a/vyper/venom/passes/base_pass.py b/vyper/venom/passes/base_pass.py index 38aaa234ee..3fbbdef6df 100644 --- a/vyper/venom/passes/base_pass.py +++ b/vyper/venom/passes/base_pass.py @@ -9,7 +9,7 @@ def run_pass(cls, *args, **kwargs): t = cls() count = 0 - for i in range(1000): + for _ in range(1000): changes_count = t._run_pass(*args, **kwargs) or 0 count += changes_count if changes_count == 0: From 50146d1b85c83b60632dfd69a4a11b40514c360c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Mar 2024 18:56:15 +0200 Subject: [PATCH 274/322] depth first --- vyper/venom/ir_node_to_venom.py | 2 +- vyper/venom/passes/dft.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 502e658c68..6f9efac414 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -169,7 +169,7 @@ def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> Opti bb = ctx.get_basic_block() if len(goto_ir.args) > 2: - ret_args.append(return_buf.value) # type: ignore + ret_args.append(return_buf) # type: ignore bb.append_invoke_instruction(ret_args, returns=False) # type: ignore diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 60b95a1991..ff6d36c0e5 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -7,6 +7,13 @@ class DFTPass(IRPass): def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): + for op in inst.get_outputs(): + for uses_this in reversed(self.dfg.get_uses(op)): + if uses_this.parent != inst.parent or uses_this.fence_id != inst.fence_id: + # don't reorder across basic block or fence boundaries + continue + self._process_instruction_r(bb, uses_this) + if inst in self.visited_instructions: return self.visited_instructions.add(inst) From 06d216fe76d59f5d720f3a8f2700fa7ac6ea7ac3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Mar 2024 19:02:12 +0200 Subject: [PATCH 275/322] lint --- vyper/venom/passes/dft.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index ff6d36c0e5..69f790625f 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -1,6 +1,6 @@ from vyper.utils import OrderedSet from vyper.venom.analysis import DFG -from vyper.venom.basicblock import IRBasicBlock, IRInstruction +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass @@ -8,6 +8,7 @@ class DFTPass(IRPass): def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): for op in inst.get_outputs(): + assert isinstance(op, IRVariable), f"expected variable, got {op}" for uses_this in reversed(self.dfg.get_uses(op)): if uses_this.parent != inst.parent or uses_this.fence_id != inst.fence_id: # don't reorder across basic block or fence boundaries From d72a41f3a4a4e02987a041ad09e6b8ae8e98e1d2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Mar 2024 00:15:53 +0200 Subject: [PATCH 276/322] add volatile instruction --- vyper/venom/basicblock.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 3171fd0172..15d2932d36 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -26,6 +26,7 @@ "mload", "calldatacopy", "mcopy", + "extcodecopy", "returndatacopy", "codecopy", "dloadbytes", From 46fe75783d500e6887d6dbc79a5d3357353c7525 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Mar 2024 00:16:02 +0200 Subject: [PATCH 277/322] fix dominator test --- .../unit/compiler/venom/test_dominator_tree.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index 177ca902e0..527b0f1657 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -50,14 +50,15 @@ def test_deminator_frontier_calculation(): calculate_cfg(ctx) dom = DominatorTree(ctx, bb1) - - assert len(dom.df[bb1]) == 0, dom.df[bb1] - assert dom.df[bb2] == OrderedSet({bb2}), dom.df[bb2] - assert dom.df[bb3] == OrderedSet({bb3, bb6}), dom.df[bb3] - assert dom.df[bb4] == OrderedSet({bb6}), dom.df[bb4] - assert dom.df[bb5] == OrderedSet({bb3, bb6}), dom.df[bb5] - assert dom.df[bb6] == OrderedSet({bb2}), dom.df[bb6] - assert len(dom.df[bb7]) == 0, dom.df[bb7] + df = dom.dominator_frontiers + + assert len(df[bb1]) == 0, df[bb1] + assert df[bb2] == OrderedSet({bb2}), df[bb2] + assert df[bb3] == OrderedSet({bb3, bb6}), df[bb3] + assert df[bb4] == OrderedSet({bb6}), df[bb4] + assert df[bb5] == OrderedSet({bb3, bb6}), df[bb5] + assert df[bb6] == OrderedSet({bb2}), df[bb6] + assert len(df[bb7]) == 0, df[bb7] def test_phi_placement(): From 1325d48024c5c5d8e10d7d4fbb8f5ba7b93a24b8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Mar 2024 00:16:18 +0200 Subject: [PATCH 278/322] revert --- vyper/venom/passes/dft.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 69f790625f..75561bf495 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -9,7 +9,9 @@ class DFTPass(IRPass): def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): for op in inst.get_outputs(): assert isinstance(op, IRVariable), f"expected variable, got {op}" - for uses_this in reversed(self.dfg.get_uses(op)): + uses = self.dfg.get_uses(op) + + for uses_this in uses: if uses_this.parent != inst.parent or uses_this.fence_id != inst.fence_id: # don't reorder across basic block or fence boundaries continue @@ -25,7 +27,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): bb.instructions.append(inst) return - for op in inst.liveness: + for op in inst.get_inputs(): target = self.dfg.get_producing_instruction(op) assert target is not None, f"no producing instruction for {op}" if target.parent != inst.parent or target.fence_id != inst.fence_id: From ef78ccf9e38bbf8305e920dc32113e7f6ea806c1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Mar 2024 15:59:15 +0200 Subject: [PATCH 279/322] before cleanup --- vyper/venom/passes/dft.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 75561bf495..a5459dad24 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -1,12 +1,12 @@ from vyper.utils import OrderedSet from vyper.venom.analysis import DFG -from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable +from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass class DFTPass(IRPass): - def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): + def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: int = 0): for op in inst.get_outputs(): assert isinstance(op, IRVariable), f"expected variable, got {op}" uses = self.dfg.get_uses(op) @@ -15,7 +15,9 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): if uses_this.parent != inst.parent or uses_this.fence_id != inst.fence_id: # don't reorder across basic block or fence boundaries continue - self._process_instruction_r(bb, uses_this) + self._process_instruction_r( + bb, uses_this, 1000 if uses_this.opcode in BB_TERMINATORS else offset + ) if inst in self.visited_instructions: return @@ -24,7 +26,9 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): if inst.opcode == "phi": # phi instructions stay at the beginning of the basic block # and no input processing is needed - bb.instructions.append(inst) + # bb.instructions.append(inst) + self.inst_order_num += 1 + self.inst_order[inst] = self.inst_order_num + offset return for op in inst.get_inputs(): @@ -33,24 +37,28 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): if target.parent != inst.parent or target.fence_id != inst.fence_id: # don't reorder across basic block or fence boundaries continue - self._process_instruction_r(bb, target) + self._process_instruction_r(bb, target, offset) - bb.instructions.append(inst) + # bb.instructions.append(inst) + self.inst_order_num += 1 + self.inst_order[inst] = self.inst_order_num + offset def _process_basic_block(self, bb: IRBasicBlock) -> None: self.ctx.append_basic_block(bb) - instructions = bb.instructions - bb.instructions = [] - - for inst in instructions: + for inst in bb.instructions: inst.fence_id = self.fence_id if inst.volatile: self.fence_id += 1 - for inst in instructions: + self.inst_order = {} + self.inst_order_num = 0 + for inst in bb.instructions: self._process_instruction_r(bb, inst) + bb.instructions.sort(key=lambda x: self.inst_order[x]) + pass + def _run_pass(self, ctx: IRFunction) -> None: self.ctx = ctx self.dfg = DFG.build_dfg(ctx) From 86acccb10baa773584e89702fbdf968bb2831147 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 22 Mar 2024 11:12:00 -0400 Subject: [PATCH 280/322] improvements to OrderedSet performance --- vyper/utils.py | 34 ++++++++++++++++++++++++---------- vyper/venom/analysis.py | 13 ++++++++----- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/vyper/utils.py b/vyper/utils.py index ba615e58d7..ca1d96ce0e 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -1,4 +1,5 @@ import binascii +import itertools import contextlib import decimal import enum @@ -27,8 +28,7 @@ class OrderedSet(Generic[_T], dict[_T, None]): def __init__(self, iterable=None): super().__init__() if iterable is not None: - for item in iterable: - self.add(item) + self.update(iterable) def __repr__(self): keys = ", ".join(repr(k) for k in self.keys()) @@ -46,24 +46,38 @@ def add(self, item: _T) -> None: def remove(self, item: _T) -> None: del self[item] + def drop(self, item: _T): + # friendly version of remove + super().pop(item, None) + + def dropmany(self, iterable): + for item in iterable: + self.drop(item) + def difference(self, other): ret = self.copy() - for k in other.keys(): - if k in ret: - ret.remove(k) + ret.dropmany(other) return ret + def update(self, other): + # CMC 2024-03-22 for some reason, this is faster than super().update? + # (maybe size dependent) + for item in other: + self.add(item) + + def __ior__(self, other): + self.update(other) + return self + def union(self, other): return self | other - def update(self, other): - super().update(self.__class__.fromkeys(other)) - def __or__(self, other): - return self.__class__(super().__or__(other)) + cls = self.__class__ + return cls(itertools.chain(self, other)) def copy(self): - return self.__class__(super().copy()) + return self.__class__(iter(self)) @classmethod def intersection(cls, *sets): diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 0bc1ec0fac..0e2613b9f7 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -1,4 +1,5 @@ from typing import Optional +import itertools from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet @@ -53,12 +54,14 @@ def _calculate_liveness(bb: IRBasicBlock) -> bool: orig_liveness = bb.instructions[0].liveness.copy() liveness = bb.out_vars.copy() for instruction in reversed(bb.instructions): - ops = instruction.get_inputs() + ins = instruction.get_inputs() + outs = instruction.get_outputs() + + if ins or outs: + liveness = liveness.copy() + liveness.update(ins) + liveness.dropmany(outs) - liveness = liveness.union(OrderedSet.fromkeys(ops)) - out = instruction.get_outputs()[0] if len(instruction.get_outputs()) > 0 else None - if out in liveness: - liveness.remove(out) instruction.liveness = liveness return orig_liveness != bb.instructions[0].liveness From fbe4e2828d8732a5ddf2910ea2149b7e8639a94c Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 22 Mar 2024 11:17:41 -0400 Subject: [PATCH 281/322] index basic blocks --- vyper/venom/basicblock.py | 1 + vyper/venom/function.py | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 3171fd0172..2bfeaea2a9 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -1,5 +1,6 @@ from enum import Enum, auto from typing import TYPE_CHECKING, Any, Generator, Iterator, Optional, Union +from functools import cached_property from vyper.utils import OrderedSet diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 81d46fec3e..e52343ca32 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -49,6 +49,7 @@ def __init__(self, name: IRLabel = None) -> None: self._ast_source_stack = [] self._error_msg_stack = [] + self._bb_index = {} self.add_entry_point(name) self.append_basic_block(IRBasicBlock(name, self)) @@ -76,6 +77,15 @@ def append_basic_block(self, bb: IRBasicBlock) -> IRBasicBlock: return self.basic_blocks[-1] + def _get_basicblock_index(self, label: str): + ix = self._bb_index.get(label, -1) + if 0 <= ix < len(self.basic_blocks) and self.basic_blocks[ix].label == label: + return ix + # do a reindex + self._bb_index = dict((bb.label, ix) for ix, bb in enumerate(self.basic_blocks)) + return self._bb_index[label] + + def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: """ Get basic block by label. @@ -83,18 +93,16 @@ def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: """ if label is None: return self.basic_blocks[-1] - for bb in self.basic_blocks: - if bb.label.value == label: - return bb - raise AssertionError(f"Basic block '{label}' not found") + ix = self._get_basicblock_index(label) + return self.basic_blocks[ix] def get_basic_block_after(self, label: IRLabel) -> IRBasicBlock: """ Get basic block after label. """ - for i, bb in enumerate(self.basic_blocks[:-1]): - if bb.label.value == label.value: - return self.basic_blocks[i + 1] + ix = self._get_basicblock_index(label.value) + if 0 <= ix < len(self.basic_blocks) - 1: + return self.basic_blocks[ix + 1] raise AssertionError(f"Basic block after '{label}' not found") def get_terminal_basicblocks(self) -> Iterator[IRBasicBlock]: From 6ebf97707efa306c3e4be71999407024f3a1ecd4 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 22 Mar 2024 11:21:12 -0400 Subject: [PATCH 282/322] add comment --- vyper/venom/analysis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 0e2613b9f7..4be4e48f28 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -58,6 +58,7 @@ def _calculate_liveness(bb: IRBasicBlock) -> bool: outs = instruction.get_outputs() if ins or outs: + # perf: only copy if changed liveness = liveness.copy() liveness.update(ins) liveness.dropmany(outs) From bcfa42ddd0e0e163b41bbcfc53a1fefc3de4d630 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 22 Mar 2024 12:28:24 -0400 Subject: [PATCH 283/322] OrderedSet hide dict implementation better --- vyper/utils.py | 47 ++++++++++++++++++++------------ vyper/venom/dominators.py | 2 +- vyper/venom/venom_to_assembly.py | 2 +- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/vyper/utils.py b/vyper/utils.py index ca1d96ce0e..9ddd78c75d 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -16,7 +16,7 @@ _T = TypeVar("_T") -class OrderedSet(Generic[_T], dict[_T, None]): +class OrderedSet(Generic[_T]): """ a minimal "ordered set" class. this is needed in some places because, while dict guarantees you can recover insertion order @@ -26,29 +26,35 @@ class OrderedSet(Generic[_T], dict[_T, None]): """ def __init__(self, iterable=None): - super().__init__() + self._data = dict() if iterable is not None: self.update(iterable) def __repr__(self): - keys = ", ".join(repr(k) for k in self.keys()) + keys = ", ".join(repr(k) for k in self) return f"{{{keys}}}" - def get(self, *args, **kwargs): - raise RuntimeError("can't call get() on OrderedSet!") + def __iter__(self): + return iter(self._data) + + def __contains__(self, item): + return self._data.__contains__(item) + + def __len__(self): + return len(self._data) def first(self): return next(iter(self)) def add(self, item: _T) -> None: - self[item] = None + self._data[item] = None def remove(self, item: _T) -> None: - del self[item] + del self._data[item] def drop(self, item: _T): # friendly version of remove - super().pop(item, None) + self._data.pop(item, None) def dropmany(self, iterable): for item in iterable: @@ -73,23 +79,28 @@ def union(self, other): return self | other def __or__(self, other): - cls = self.__class__ - return cls(itertools.chain(self, other)) + ret = self.copy() + ret |= other + return ret + + def __eq__(self, other): + return self._data == other._data def copy(self): - return self.__class__(iter(self)) + ret = self.__class__() + ret._data = self._data.copy() + return ret @classmethod def intersection(cls, *sets): - res = OrderedSet() if len(sets) == 0: raise ValueError("undefined: intersection of no sets") - if len(sets) == 1: - return sets[0].copy() - for e in sets[0].keys(): - if all(e in s for s in sets[1:]): - res.add(e) - return res + + ret = sets[0].copy() + for e in sets[0]: + if any(e not in s for s in sets[1:]): + ret.remove(e) + return ret class StringEnum(enum.Enum): diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index ce6a4868cf..d11d61cec3 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -58,7 +58,7 @@ def _compute_dominators(self): """ basic_blocks = list(self.dfs_order.keys()) self.dominators = {bb: OrderedSet(basic_blocks) for bb in basic_blocks} - self.dominators[self.entry_block] = OrderedSet({self.entry_block}) + self.dominators[self.entry_block] = OrderedSet([self.entry_block]) changed = True count = len(basic_blocks) ** 2 # TODO: find a proper bound for this while changed: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index d702b8dc2b..0cb13becf2 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -407,7 +407,7 @@ def _generate_evm_for_instruction( # NOTE: stack in general can contain multiple copies of the same variable, # however we are safe in the case of jmp/djmp/jnz as it's not going to # have multiples. - target_stack_list = list(target_stack.keys()) + target_stack_list = list(target_stack) self._stack_reorder(assembly, stack, target_stack_list) # final step to get the inputs to this instruction ordered From 3e4de66300068586884ff34888200d7f2e793aad Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Mar 2024 18:38:50 +0200 Subject: [PATCH 284/322] cleanupo --- vyper/venom/passes/dft.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index a5459dad24..9c8ee80791 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -15,9 +15,12 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: if uses_this.parent != inst.parent or uses_this.fence_id != inst.fence_id: # don't reorder across basic block or fence boundaries continue - self._process_instruction_r( - bb, uses_this, 1000 if uses_this.opcode in BB_TERMINATORS else offset - ) + + # if the instruction is a terminator, we need to place it at the end of the basic block + # along with all the instructions that "lead" to it + if uses_this.opcode in BB_TERMINATORS: + offset = len(bb.instructions) + self._process_instruction_r(bb, uses_this, offset) if inst in self.visited_instructions: return @@ -27,8 +30,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: # phi instructions stay at the beginning of the basic block # and no input processing is needed # bb.instructions.append(inst) - self.inst_order_num += 1 - self.inst_order[inst] = self.inst_order_num + offset + self.inst_order[inst] = 0 return for op in inst.get_inputs(): @@ -39,7 +41,6 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: continue self._process_instruction_r(bb, target, offset) - # bb.instructions.append(inst) self.inst_order_num += 1 self.inst_order[inst] = self.inst_order_num + offset @@ -51,13 +52,15 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: if inst.volatile: self.fence_id += 1 + # We go throught the instructions and calculate the order in which they should be executed + # based on the data flow graph. This order is stored in the inst_order dictionary. + # We then sort the instructions based on this order. self.inst_order = {} self.inst_order_num = 0 for inst in bb.instructions: self._process_instruction_r(bb, inst) bb.instructions.sort(key=lambda x: self.inst_order[x]) - pass def _run_pass(self, ctx: IRFunction) -> None: self.ctx = ctx From 84a02d76af58f3a7b8b2ed64b4320351ff1f613f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Mar 2024 18:40:38 +0200 Subject: [PATCH 285/322] mark test --- tests/functional/codegen/types/test_dynamic_array.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index df5ea94693..ae57dbf3fa 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -61,6 +61,7 @@ def loo(x: DynArray[DynArray[int128, 2], 2]) -> int128: print("Passed list tests") +@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_string_list(get_contract): code = """ @external From afa51731e2444efd9bfd5408849aa621c230a1d3 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 22 Mar 2024 13:04:15 -0400 Subject: [PATCH 286/322] some more small optimizations --- vyper/utils.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/vyper/utils.py b/vyper/utils.py index 9ddd78c75d..0fd74b8972 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -58,7 +58,7 @@ def drop(self, item: _T): def dropmany(self, iterable): for item in iterable: - self.drop(item) + self._data.pop(item, None) def difference(self, other): ret = self.copy() @@ -66,28 +66,29 @@ def difference(self, other): return ret def update(self, other): - # CMC 2024-03-22 for some reason, this is faster than super().update? + # CMC 2024-03-22 for some reason, this is faster than dict.update? # (maybe size dependent) for item in other: - self.add(item) + self._data[item] = None + + def union(self, other): + return self | other def __ior__(self, other): self.update(other) return self - def union(self, other): - return self | other - def __or__(self, other): ret = self.copy() - ret |= other + ret.update(other) return ret def __eq__(self, other): return self._data == other._data def copy(self): - ret = self.__class__() + cls = self.__class__ + ret = cls.__new__(cls) ret._data = self._data.copy() return ret From 58e1a336302b12846754dcf65302869cebc95be8 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 22 Mar 2024 13:09:32 -0400 Subject: [PATCH 287/322] fix a couple tests --- tests/unit/compiler/venom/test_multi_entry_block.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/compiler/venom/test_multi_entry_block.py b/tests/unit/compiler/venom/test_multi_entry_block.py index cc148416a5..47f4b88707 100644 --- a/tests/unit/compiler/venom/test_multi_entry_block.py +++ b/tests/unit/compiler/venom/test_multi_entry_block.py @@ -39,7 +39,7 @@ def test_multi_entry_block_1(): assert ctx.normalized, "CFG should be normalized" finish_bb = ctx.get_basic_block(finish_label.value) - cfg_in = list(finish_bb.cfg_in.keys()) + 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" @@ -91,7 +91,7 @@ def test_multi_entry_block_2(): assert ctx.normalized, "CFG should be normalized" finish_bb = ctx.get_basic_block(finish_label.value) - cfg_in = list(finish_bb.cfg_in.keys()) + 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" @@ -132,7 +132,7 @@ def test_multi_entry_block_with_dynamic_jump(): assert ctx.normalized, "CFG should be normalized" finish_bb = ctx.get_basic_block(finish_label.value) - cfg_in = list(finish_bb.cfg_in.keys()) + 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" From 8bc466fb1cc6e8d5bc8bdb34934be4c07079dbdf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Mar 2024 23:14:08 +0200 Subject: [PATCH 288/322] fix workflow --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 10f96bdc21..34816f31cb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -154,7 +154,7 @@ jobs: --evm-version ${{ matrix.evm-version }} \ ${{ matrix.debug && '--enable-compiler-debug-mode' || '' }} \ ${{ matrix.memorymock && '--memorymock' || '' }} \ - ${{ matrix.experimental-codegen && '--experimental-codegen || '' }} \ + ${{ matrix.experimental-codegen && '--experimental-codegen' || '' }} \ --showlocals -r aR \ tests/ From 6f9923e0d8ec6f9bb2d9f0cff36256a9baffeb06 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Mar 2024 23:18:19 +0200 Subject: [PATCH 289/322] lint --- vyper/venom/function.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index e52343ca32..fc9782d341 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -85,7 +85,6 @@ def _get_basicblock_index(self, label: str): self._bb_index = dict((bb.label, ix) for ix, bb in enumerate(self.basic_blocks)) return self._bb_index[label] - def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: """ Get basic block by label. From 66977b49445ee2530c7f2f8fec7755c71e3b710d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 22 Mar 2024 21:25:16 +0000 Subject: [PATCH 290/322] remove some xfails --- tests/functional/builtins/codegen/test_abi_encode.py | 1 - tests/functional/codegen/types/test_dynamic_array.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/tests/functional/builtins/codegen/test_abi_encode.py b/tests/functional/builtins/codegen/test_abi_encode.py index d32c30c3a2..305c4b1356 100644 --- a/tests/functional/builtins/codegen/test_abi_encode.py +++ b/tests/functional/builtins/codegen/test_abi_encode.py @@ -178,7 +178,6 @@ def abi_encode(d: DynArray[uint256, 3], ensure_tuple: bool, include_method_id: b ] -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") @pytest.mark.parametrize("args", nested_2d_array_args) def test_abi_encode_nested_dynarray(get_contract, args): code = """ diff --git a/tests/functional/codegen/types/test_dynamic_array.py b/tests/functional/codegen/types/test_dynamic_array.py index ae57dbf3fa..efa2799480 100644 --- a/tests/functional/codegen/types/test_dynamic_array.py +++ b/tests/functional/codegen/types/test_dynamic_array.py @@ -867,7 +867,6 @@ def parse_list_fail(): assert_compile_failed(lambda: get_contract_with_gas_estimation(code), OverflowException) -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_2d_array_input_1(get_contract): code = """ @internal @@ -887,7 +886,6 @@ def test_values( assert c.test_values([[1, 2]], 3) == [[[1, 2]], 3] -@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression") def test_2d_array_input_2(get_contract): code = """ @internal From 91ea9bde11bfaefc585c2058ce25d6c33efb1024 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 24 Mar 2024 00:04:32 +0200 Subject: [PATCH 291/322] lint --- vyper/utils.py | 1 - vyper/venom/analysis.py | 1 - vyper/venom/basicblock.py | 1 - vyper/venom/function.py | 3 ++- vyper/venom/passes/dft.py | 6 +++++- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/vyper/utils.py b/vyper/utils.py index 0fd74b8972..114ddf97c2 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -1,5 +1,4 @@ import binascii -import itertools import contextlib import decimal import enum diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 4be4e48f28..066a60f45e 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -1,5 +1,4 @@ from typing import Optional -import itertools from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index e03e8c1b10..15d2932d36 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -1,6 +1,5 @@ from enum import Enum, auto from typing import TYPE_CHECKING, Any, Generator, Iterator, Optional, Union -from functools import cached_property from vyper.utils import OrderedSet diff --git a/vyper/venom/function.py b/vyper/venom/function.py index fc9782d341..b7ba26c708 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -33,6 +33,7 @@ class IRFunction: # Used during code generation _ast_source_stack: list[int] _error_msg_stack: list[str] + _bb_index: dict[str, int] def __init__(self, name: IRLabel = None) -> None: if name is None: @@ -82,7 +83,7 @@ def _get_basicblock_index(self, label: str): if 0 <= ix < len(self.basic_blocks) and self.basic_blocks[ix].label == label: return ix # do a reindex - self._bb_index = dict((bb.label, ix) for ix, bb in enumerate(self.basic_blocks)) + self._bb_index = dict((bb.label.name, ix) for ix, bb in enumerate(self.basic_blocks)) return self._bb_index[label] def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 9c8ee80791..5d149cf003 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -6,6 +6,9 @@ class DFTPass(IRPass): + inst_order: dict[IRInstruction, int] + inst_order_num: int + def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: int = 0): for op in inst.get_outputs(): assert isinstance(op, IRVariable), f"expected variable, got {op}" @@ -16,7 +19,8 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: # don't reorder across basic block or fence boundaries continue - # if the instruction is a terminator, we need to place it at the end of the basic block + # if the instruction is a terminator, we need to place + # it at the end of the basic block # along with all the instructions that "lead" to it if uses_this.opcode in BB_TERMINATORS: offset = len(bb.instructions) From 544c90cb95a50b5325cb9b72dbdb5569a9a1a5ac Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 23 Mar 2024 19:17:17 -0400 Subject: [PATCH 292/322] add some comments, sanity check --- vyper/venom/function.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index b7ba26c708..9603e6d9da 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -74,16 +74,20 @@ def append_basic_block(self, bb: IRBasicBlock) -> IRBasicBlock: assert isinstance(bb, IRBasicBlock), f"append_basic_block takes IRBasicBlock, got '{bb}'" self.basic_blocks.append(bb) - # TODO add sanity check somewhere that basic blocks have unique labels - return self.basic_blocks[-1] def _get_basicblock_index(self, label: str): + # perf: keep an "index" of labels to block indices to + # perform fast lookup. + # TODO: maybe better just to throw basic blocks in an ordered + # dict of some kind. ix = self._bb_index.get(label, -1) if 0 <= ix < len(self.basic_blocks) and self.basic_blocks[ix].label == label: return ix # do a reindex self._bb_index = dict((bb.label.name, ix) for ix, bb in enumerate(self.basic_blocks)) + # sanity check - no duplicate labels + assert len(self._bb_index) == len(self.basic_blocks) return self._bb_index[label] def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: @@ -115,7 +119,7 @@ def get_terminal_basicblocks(self) -> Iterator[IRBasicBlock]: def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: """ - Get basic blocks that contain label. + Get basic blocks that point to the given basic block """ return [bb for bb in self.basic_blocks if basic_block.label in bb.cfg_in] From c05bcfffa8a9de7aef63be95cc32e59d4d3f3255 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 23 Mar 2024 23:32:36 +0000 Subject: [PATCH 293/322] add some review comments --- vyper/venom/ir_node_to_venom.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 6f9efac414..47056ef9fe 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -436,10 +436,15 @@ def _convert_ir_bb(ctx, ir, symbols): return bb.append_instruction("mload", arg_0) elif ir.value == "mstore": + # REVIEW: this should probably be + # arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) arg_1, arg_0 = _convert_ir_bb_list(ctx, reversed(ir.args), symbols) if isinstance(arg_1, IRVariable): symbols[f"&{arg_0.value}"] = arg_1 + + # REVIEW: should probably be + # ctx.get_basic_block().append_instruction("mstore", arg_0, arg_1) ctx.get_basic_block().append_instruction("mstore", arg_1, arg_0) elif ir.value == "ceil32": x = ir.args[0] @@ -447,9 +452,12 @@ def _convert_ir_bb(ctx, ir, symbols): return _convert_ir_bb(ctx, expanded, symbols) elif ir.value == "select": # b ^ ((a ^ b) * cond) where cond is 1 or 0 + # REVIEW: should protect a, b from double evaluation + # (e.g. `with "b" b (...)`) cond, a, b = ir.args expanded = IRnode.from_list(["xor", b, ["mul", cond, ["xor", a, b]]]) return _convert_ir_bb(ctx, expanded, symbols) + # REVIEW: dead branch elif ir.value in []: arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) From 91a9401cbe86577d8d0ce5dfe4cf775e3aca7519 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sun, 24 Mar 2024 03:08:21 +0000 Subject: [PATCH 294/322] fix cursed __repr__ function --- vyper/venom/basicblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 15d2932d36..53075887e7 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -325,7 +325,7 @@ def __repr__(self) -> str: s += opcode operands = self.operands if opcode not in ["jmp", "jnz", "invoke"]: - operands.reverse() + operands = reversed(operands) s += ", ".join( [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in operands] ) From 10d3c82b503d84264c89d6b491f21403cc9a68e4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 24 Mar 2024 10:19:50 +0200 Subject: [PATCH 295/322] lint --- vyper/venom/basicblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 53075887e7..eb805b0079 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -325,7 +325,7 @@ def __repr__(self) -> str: s += opcode operands = self.operands if opcode not in ["jmp", "jnz", "invoke"]: - operands = reversed(operands) + operands = reversed(operands) # type: ignore s += ", ".join( [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in operands] ) From affdb09db4c486783aecd263f64061421340f17b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 24 Mar 2024 10:24:24 +0200 Subject: [PATCH 296/322] change mstore evaluation order --- vyper/venom/ir_node_to_venom.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 47056ef9fe..f66486fd24 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -436,15 +436,11 @@ def _convert_ir_bb(ctx, ir, symbols): return bb.append_instruction("mload", arg_0) elif ir.value == "mstore": - # REVIEW: this should probably be - # arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) - arg_1, arg_0 = _convert_ir_bb_list(ctx, reversed(ir.args), symbols) + arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) if isinstance(arg_1, IRVariable): symbols[f"&{arg_0.value}"] = arg_1 - # REVIEW: should probably be - # ctx.get_basic_block().append_instruction("mstore", arg_0, arg_1) ctx.get_basic_block().append_instruction("mstore", arg_1, arg_0) elif ir.value == "ceil32": x = ir.args[0] From 990951320a6f9db2148a1ce90aed4ca2f311a2ca Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 24 Mar 2024 10:27:43 +0200 Subject: [PATCH 297/322] protect b from double evaluation --- vyper/venom/ir_node_to_venom.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index f66486fd24..20337f9d88 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -447,16 +447,9 @@ def _convert_ir_bb(ctx, ir, symbols): expanded = IRnode.from_list(["and", ["add", x, 31], ["not", 31]]) return _convert_ir_bb(ctx, expanded, symbols) elif ir.value == "select": - # b ^ ((a ^ b) * cond) where cond is 1 or 0 - # REVIEW: should protect a, b from double evaluation - # (e.g. `with "b" b (...)`) cond, a, b = ir.args - expanded = IRnode.from_list(["xor", b, ["mul", cond, ["xor", a, b]]]) + expanded = IRnode.from_list(["with", "b", b, ["xor", "b", ["mul", cond, ["xor", a, "b"]]]]) return _convert_ir_bb(ctx, expanded, symbols) - # REVIEW: dead branch - elif ir.value in []: - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) - ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0) elif ir.value == "repeat": def emit_body_blocks(): From c3b4dc70da99f6ba4e1a54ef3d44fdd3f3e7ae42 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 24 Mar 2024 10:37:32 +0200 Subject: [PATCH 298/322] revert mstore evaluation order --- vyper/venom/ir_node_to_venom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 20337f9d88..4ab7082118 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -436,7 +436,7 @@ def _convert_ir_bb(ctx, ir, symbols): return bb.append_instruction("mload", arg_0) elif ir.value == "mstore": - arg_0, arg_1 = _convert_ir_bb_list(ctx, ir.args, symbols) + arg_1, arg_0 = _convert_ir_bb_list(ctx, reversed(ir.args), symbols) if isinstance(arg_1, IRVariable): symbols[f"&{arg_0.value}"] = arg_1 From b1ba5cbd1a02c52635a2a8cf60a192646e8d097e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 26 Mar 2024 12:43:42 -0400 Subject: [PATCH 299/322] add review --- vyper/venom/dominators.py | 3 +++ vyper/venom/passes/make_ssa.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index d11d61cec3..583017f229 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -29,6 +29,8 @@ def __init__(self, ctx: IRFunction, entry: IRBasicBlock): self.immediate_dominators = {} self.dominated = {} self.dominator_frontiers = {} + + # REVIEW: move computation out of constructor self._compute() def dominates(self, bb1, bb2): @@ -130,6 +132,7 @@ def _intersect(self, bb1, bb2): bb2 = self.immediate_dominators[bb2] return bb1 + # REVIEW: maybe _compute_dfs? def _dfs(self, entry: IRBasicBlock, visited): """ Depth-first search to compute the DFS order of the basic blocks. This diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index b4c3e1a509..6521788fb2 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -24,7 +24,9 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: calculate_liveness(ctx) self._add_phi_nodes() + # REVIEW: rename to version or var_version self.var_names = {var.name: 0 for var in self.defs.keys()} + # REVIEW: rename to versions self.stacks = {var.name: [0] for var in self.defs.keys()} self._rename_vars(entry) self._remove_degenerate_phis(entry) @@ -37,6 +39,7 @@ def _add_phi_nodes(self): """ self._compute_defs() self.work = {var: 0 for var in self.dom.dfs_walk} + # REVIEW: rename to num_placements? self.has_already = {var: 0 for var in self.dom.dfs_walk} i = 0 @@ -69,6 +72,7 @@ def _place_phi(self, var: IRVariable, basic_block: IRBasicBlock): args.append(var) # type: ignore phi = IRInstruction("phi", args, var) + # REVIEW: use insert_instruction or append_instruction here? basic_block.instructions.insert(0, phi) def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock) -> bool: @@ -109,11 +113,14 @@ def _rename_vars(self, basic_block: IRBasicBlock): if inst.output is not None: v_name = inst.output.name i = self.var_names[v_name] - inst.output = IRVariable(v_name, version=i) - outs.append(inst.output.name) + self.stacks[v_name].append(i) self.var_names[v_name] = i + 1 + inst.output = IRVariable(v_name, version=i) + # note - after previous line, inst.output.name != v_name + outs.append(inst.output.name) + for bb in basic_block.cfg_out: for inst in bb.instructions: if inst.opcode != "phi": @@ -131,6 +138,7 @@ def _rename_vars(self, basic_block: IRBasicBlock): self._rename_vars(bb) for op_name in outs: + # NOTE: each pop corresponds to an append above self.stacks[op_name].pop() def _remove_degenerate_phis(self, entry: IRBasicBlock): From ebf776ea388f6e6164b2dace25a5ea89ed9ebe87 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Mar 2024 22:18:50 +0200 Subject: [PATCH 300/322] refactor dominator tree constructor --- .../compiler/venom/test_dominator_tree.py | 3 ++- vyper/venom/dominators.py | 25 ++++++++----------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index 527b0f1657..b03306c10a 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -49,7 +49,8 @@ def test_deminator_frontier_calculation(): bb1, bb2, bb3, bb4, bb5, bb6, bb7 = [ctx.get_basic_block(str(i)) for i in range(1, 8)] calculate_cfg(ctx) - dom = DominatorTree(ctx, bb1) + dom = DominatorTree() + dom.compute(ctx, bb1) df = dom.dominator_frontiers assert len(df[bb1]) == 0, df[bb1] diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index 583017f229..de0e7d3ba6 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -20,7 +20,10 @@ class DominatorTree: dominated: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] dominator_frontiers: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] - def __init__(self, ctx: IRFunction, entry: IRBasicBlock): + def compute(self, ctx: IRFunction, entry: IRBasicBlock): + """ + Compute the dominator tree. + """ self.ctx = ctx self.entry_block = entry self.dfs_order = {} @@ -30,8 +33,10 @@ def __init__(self, ctx: IRFunction, entry: IRBasicBlock): self.dominated = {} self.dominator_frontiers = {} - # REVIEW: move computation out of constructor - self._compute() + self._compute_dfs(self.entry_block, OrderedSet()) + self._compute_dominators() + self._compute_idoms() + self._compute_df() def dominates(self, bb1, bb2): """ @@ -45,15 +50,6 @@ def immediate_dominator(self, bb): """ return self.immediate_dominators.get(bb) - def _compute(self): - """ - Compute the dominator tree. - """ - self._dfs(self.entry_block, OrderedSet()) - self._compute_dominators() - self._compute_idoms() - self._compute_df() - def _compute_dominators(self): """ Compute dominators @@ -132,8 +128,7 @@ def _intersect(self, bb1, bb2): bb2 = self.immediate_dominators[bb2] return bb1 - # REVIEW: maybe _compute_dfs? - def _dfs(self, entry: IRBasicBlock, visited): + def _compute_dfs(self, entry: IRBasicBlock, visited): """ Depth-first search to compute the DFS order of the basic blocks. This is used to compute the dominator tree. The sequence of basic blocks in @@ -144,7 +139,7 @@ def _dfs(self, entry: IRBasicBlock, visited): for bb in entry.cfg_out: if bb not in visited: - self._dfs(bb, visited) + self._compute_dfs(bb, visited) self.dfs_walk.append(entry) self.dfs_order[entry] = len(self.dfs_walk) From 84004815ac4e23717f81cff8fe0d62219611a71f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Mar 2024 22:19:26 +0200 Subject: [PATCH 301/322] rename variables, refactor code --- vyper/venom/passes/make_ssa.py | 38 +++++++++++++++------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 6521788fb2..3f8da14b01 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -18,16 +18,15 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self.ctx = ctx calculate_cfg(ctx) - dom = DominatorTree(ctx, entry) + dom = DominatorTree() + dom.compute(ctx, entry) self.dom = dom calculate_liveness(ctx) self._add_phi_nodes() - # REVIEW: rename to version or var_version - self.var_names = {var.name: 0 for var in self.defs.keys()} - # REVIEW: rename to versions - self.stacks = {var.name: [0] for var in self.defs.keys()} + self.var_name_counters = {var.name: 0 for var in self.defs.keys()} + self.var_name_stacks = {var.name: [0] for var in self.defs.keys()} self._rename_vars(entry) self._remove_degenerate_phis(entry) @@ -38,9 +37,8 @@ def _add_phi_nodes(self): Add phi nodes to the function. """ self._compute_defs() - self.work = {var: 0 for var in self.dom.dfs_walk} - # REVIEW: rename to num_placements? - self.has_already = {var: 0 for var in self.dom.dfs_walk} + work = {var: 0 for var in self.dom.dfs_walk} + has_already = {var: 0 for var in self.dom.dfs_walk} i = 0 # Iterate over all variables @@ -50,13 +48,13 @@ def _add_phi_nodes(self): while len(defs) > 0: bb = defs.pop() for dom in self.dom.dominator_frontiers[bb]: - if self.has_already[dom] >= i: + if has_already[dom] >= i: continue self._place_phi(var, dom) - self.has_already[dom] = i - if self.work[dom] < i: - self.work[dom] = i + has_already[dom] = i + if work[dom] < i: + work[dom] = i defs.append(dom) def _place_phi(self, var: IRVariable, basic_block: IRBasicBlock): @@ -71,9 +69,7 @@ def _place_phi(self, var: IRVariable, basic_block: IRBasicBlock): args.append(bb.label) # type: ignore args.append(var) # type: ignore - phi = IRInstruction("phi", args, var) - # REVIEW: use insert_instruction or append_instruction here? - basic_block.instructions.insert(0, phi) + basic_block.insert_instruction(IRInstruction("phi", args, var), 0) def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock) -> bool: for inst in basic_block.instructions: @@ -106,16 +102,16 @@ def _rename_vars(self, basic_block: IRBasicBlock): new_ops.append(op) continue - new_ops.append(IRVariable(op.name, version=self.stacks[op.name][-1])) + new_ops.append(IRVariable(op.name, version=self.var_name_stacks[op.name][-1])) inst.operands = new_ops if inst.output is not None: v_name = inst.output.name - i = self.var_names[v_name] + i = self.var_name_counters[v_name] - self.stacks[v_name].append(i) - self.var_names[v_name] = i + 1 + self.var_name_stacks[v_name].append(i) + self.var_name_counters[v_name] = i + 1 inst.output = IRVariable(v_name, version=i) # note - after previous line, inst.output.name != v_name @@ -129,7 +125,7 @@ def _rename_vars(self, basic_block: IRBasicBlock): for i, op in enumerate(inst.operands): if op == basic_block.label: inst.operands[i + 1] = IRVariable( - inst.output.name, version=self.stacks[inst.output.name][-1] + inst.output.name, version=self.var_name_stacks[inst.output.name][-1] ) for bb in self.dom.dominated[basic_block]: @@ -139,7 +135,7 @@ def _rename_vars(self, basic_block: IRBasicBlock): for op_name in outs: # NOTE: each pop corresponds to an append above - self.stacks[op_name].pop() + self.var_name_stacks[op_name].pop() def _remove_degenerate_phis(self, entry: IRBasicBlock): for inst in entry.instructions.copy(): From 52c6f61c0fa82ad21044ff645e0b15660e15479a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Mar 2024 22:23:41 +0200 Subject: [PATCH 302/322] commenting --- vyper/venom/passes/make_ssa.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 3f8da14b01..f152360e5a 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -91,9 +91,11 @@ def _add_phi(self, var: IRVariable, basic_block: IRBasicBlock) -> bool: def _rename_vars(self, basic_block: IRBasicBlock): """ - Rename variables in the basic block. This follows the placement of phi nodes. + Rename variables. This follows the placement of phi nodes. """ outs = [] + + # Pre-action for inst in basic_block.instructions: new_ops = [] if inst.opcode != "phi": @@ -128,13 +130,14 @@ def _rename_vars(self, basic_block: IRBasicBlock): inst.output.name, version=self.var_name_stacks[inst.output.name][-1] ) + # Post-action for bb in self.dom.dominated[basic_block]: if bb == basic_block: continue self._rename_vars(bb) for op_name in outs: - # NOTE: each pop corresponds to an append above + # NOTE: each pop corresponds to an append in the pre-action above self.var_name_stacks[op_name].pop() def _remove_degenerate_phis(self, entry: IRBasicBlock): From a5a1844a6a5c292ff48d692f13eed341fbbf79f2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Mar 2024 22:31:12 +0200 Subject: [PATCH 303/322] add DominatorTree.build_dominator_tree() --- tests/unit/compiler/venom/test_dominator_tree.py | 3 +-- vyper/venom/dominators.py | 6 ++++++ vyper/venom/passes/make_ssa.py | 4 +--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index b03306c10a..ff5dcbc43b 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -49,8 +49,7 @@ def test_deminator_frontier_calculation(): bb1, bb2, bb3, bb4, bb5, bb6, bb7 = [ctx.get_basic_block(str(i)) for i in range(1, 8)] calculate_cfg(ctx) - dom = DominatorTree() - dom.compute(ctx, bb1) + dom = DominatorTree.build_dominator_tree(ctx, bb1) df = dom.dominator_frontiers assert len(df[bb1]) == 0, df[bb1] diff --git a/vyper/venom/dominators.py b/vyper/venom/dominators.py index de0e7d3ba6..b69c17e1d8 100644 --- a/vyper/venom/dominators.py +++ b/vyper/venom/dominators.py @@ -20,6 +20,12 @@ class DominatorTree: dominated: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] dominator_frontiers: dict[IRBasicBlock, OrderedSet[IRBasicBlock]] + @classmethod + def build_dominator_tree(cls, ctx, entry): + ret = DominatorTree() + ret.compute(ctx, entry) + return ret + def compute(self, ctx: IRFunction, entry: IRBasicBlock): """ Compute the dominator tree. diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index f152360e5a..6b16ad7b84 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -18,9 +18,7 @@ def _run_pass(self, ctx: IRFunction, entry: IRBasicBlock) -> int: self.ctx = ctx calculate_cfg(ctx) - dom = DominatorTree() - dom.compute(ctx, entry) - self.dom = dom + self.dom = DominatorTree.build_dominator_tree(ctx, entry) calculate_liveness(ctx) self._add_phi_nodes() From fec8b84af82f5e53a34e10efcb2b62e8f4096ade Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 27 Mar 2024 09:49:20 +0200 Subject: [PATCH 304/322] ensure revaluation order for select --- vyper/venom/ir_node_to_venom.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 4ab7082118..c6661d4e85 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -448,7 +448,32 @@ def _convert_ir_bb(ctx, ir, symbols): return _convert_ir_bb(ctx, expanded, symbols) elif ir.value == "select": cond, a, b = ir.args - expanded = IRnode.from_list(["with", "b", b, ["xor", "b", ["mul", cond, ["xor", a, "b"]]]]) + expanded = IRnode.from_list( + [ + "with", + "cond", + cond, + [ + "with", + "a", + a, + [ + "with", + "b", + b, + [ + "xor", + "b", + [ + "mul", + "cond", + ["xor", "a", "b"], + ], + ], + ], + ], + ] + ) return _convert_ir_bb(ctx, expanded, symbols) elif ir.value == "repeat": From 50cb1b4a546fe6312f0dba3e2cf1cdfe685eca2c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 28 Mar 2024 12:05:59 +0200 Subject: [PATCH 305/322] lint --- vyper/venom/ir_node_to_venom.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c6661d4e85..c8ae43935b 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -457,20 +457,7 @@ def _convert_ir_bb(ctx, ir, symbols): "with", "a", a, - [ - "with", - "b", - b, - [ - "xor", - "b", - [ - "mul", - "cond", - ["xor", "a", "b"], - ], - ], - ], + ["with", "b", b, ["xor", "b", ["mul", "cond", ["xor", "a", "b"]]]], ], ] ) From 4a8a472ecae1a35776288c8258a858bbdbae5cf7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 28 Mar 2024 12:07:21 +0200 Subject: [PATCH 306/322] Update vyper/codegen/stmt.py Co-authored-by: Charles Cooper --- vyper/codegen/stmt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py index 671890f72a..9c5720d61c 100644 --- a/vyper/codegen/stmt.py +++ b/vyper/codegen/stmt.py @@ -59,7 +59,6 @@ def parse_AnnAssign(self): rhs = Expr(self.stmt.value, self.context).ir_node lhs = IRnode.from_list(alloced, typ=ltyp, location=MEMORY) - lhs.passthrough_metadata["alloca"] = (varname, alloced, ltyp.memory_bytes_required) return make_setter(lhs, rhs) From 4beaaa40545e75fda5ddc6f94de5a797fe638c82 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 28 Mar 2024 12:10:08 +0200 Subject: [PATCH 307/322] unlint --- vyper/ir/compile_ir.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 1e57881e9f..191803295e 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -201,11 +201,9 @@ def apply_line_no_wrapper(*args, **kwargs): ret = func(*args, **kwargs) new_ret = [ - ( - Instruction(i, code.ast_source, code.error_msg) - if isinstance(i, str) and not isinstance(i, Instruction) - else i - ) + Instruction(i, code.ast_source, code.error_msg) + if isinstance(i, str) and not isinstance(i, Instruction) + else i for i in ret ] return new_ret From f57a9d1c577888ebb2724246ae30ce3463ac0f3c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 28 Mar 2024 12:25:38 +0200 Subject: [PATCH 308/322] verify phi placement --- tests/unit/compiler/venom/test_make_ssa.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/unit/compiler/venom/test_make_ssa.py b/tests/unit/compiler/venom/test_make_ssa.py index 9d4d5e5407..2a04dfc134 100644 --- a/tests/unit/compiler/venom/test_make_ssa.py +++ b/tests/unit/compiler/venom/test_make_ssa.py @@ -33,3 +33,16 @@ def test_phi_case(): calculate_cfg(ctx) MakeSSA.run_pass(ctx, ctx.basic_blocks[0]) calculate_liveness(ctx) + + condition_block = ctx.get_basic_block("condition") + assert len(condition_block.instructions) == 2 + + phi_inst = condition_block.instructions[0] + assert phi_inst.opcode == "phi" + assert phi_inst.operands[0].name == "_global" + assert phi_inst.operands[1].name == "%1" + assert phi_inst.operands[2].name == "if_exit" + assert phi_inst.operands[3].name == "%1" + assert phi_inst.output.name == "%1" + assert phi_inst.output.value != phi_inst.operands[1].value + assert phi_inst.output.value != phi_inst.operands[3].value From fece7b10f10a9572c387d65284b430947617d86c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 28 Mar 2024 16:37:55 +0200 Subject: [PATCH 309/322] remove stray alloca --- vyper/codegen/expr.py | 5 ----- vyper/venom/ir_node_to_venom.py | 2 -- 2 files changed, 7 deletions(-) diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py index 32860d0e0a..691a42876e 100644 --- a/vyper/codegen/expr.py +++ b/vyper/codegen/expr.py @@ -177,11 +177,6 @@ def parse_Name(self): annotation=self.expr.id, mutable=var.mutable, ) - ret.passthrough_metadata["alloca"] = ( - self.expr.id, - var.pos, - var.typ.memory_bytes_required, - ) ret._referenced_variables = {var} return ret diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c8ae43935b..e390dd0a58 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -544,8 +544,6 @@ def emit_body_blocks(): elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): _convert_ir_opcode(ctx, ir, symbols) elif isinstance(ir.value, str) and ir.value in symbols: - if "alloca" in ir.passthrough_metadata: - ctx.get_basic_block().append_instruction("alloca", *ir.passthrough_metadata["alloca"]) return symbols[ir.value] elif ir.is_literal: return IRLiteral(ir.value) From 34eba0987409f29d30aadd20ba88de9f761c6057 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 2 Apr 2024 12:18:28 +0300 Subject: [PATCH 310/322] missplaced comment --- vyper/venom/passes/make_ssa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/make_ssa.py b/vyper/venom/passes/make_ssa.py index 6b16ad7b84..06c61c9ea7 100644 --- a/vyper/venom/passes/make_ssa.py +++ b/vyper/venom/passes/make_ssa.py @@ -128,12 +128,12 @@ def _rename_vars(self, basic_block: IRBasicBlock): inst.output.name, version=self.var_name_stacks[inst.output.name][-1] ) - # Post-action for bb in self.dom.dominated[basic_block]: if bb == basic_block: continue self._rename_vars(bb) + # Post-action for op_name in outs: # NOTE: each pop corresponds to an append in the pre-action above self.var_name_stacks[op_name].pop() From 7d6547efa51173053cb9ae1ab71ececed1aa815e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 2 Apr 2024 18:12:06 +0300 Subject: [PATCH 311/322] remove merge leftover --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index fc49ed4876..f84c947981 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,3 @@ xfail_strict = true markers = fuzzing: Run Hypothesis fuzz test suite (deselect with '-m "not fuzzing"') venom_xfail: mark a test case as a regression (expected to fail) under the venom pipeline - -[tool:mypy] -ignore_missing_imports = True From 3f85aae50e5c74dbc3ecc91325e2843ae1785be1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 2 Apr 2024 18:18:30 +0300 Subject: [PATCH 312/322] remove test --- tests/unit/compiler/venom/test_call.py | 44 -------------------------- 1 file changed, 44 deletions(-) delete mode 100644 tests/unit/compiler/venom/test_call.py diff --git a/tests/unit/compiler/venom/test_call.py b/tests/unit/compiler/venom/test_call.py deleted file mode 100644 index d483191e99..0000000000 --- a/tests/unit/compiler/venom/test_call.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest - - -@pytest.fixture -def market_maker(get_contract): - contract_code = """ -from ethereum.ercs import IERC20 - -unused: public(uint256) -token_address: IERC20 - -@external -@payable -def foo(token_addr: address, token_quantity: uint256): - self.token_address = IERC20(token_addr) - extcall self.token_address.transferFrom(msg.sender, self, token_quantity) -""" - return get_contract(contract_code) - - -TOKEN_NAME = "Vypercoin" -TOKEN_SYMBOL = "FANG" -TOKEN_DECIMALS = 18 -TOKEN_INITIAL_SUPPLY = 21 * 10**6 -TOKEN_TOTAL_SUPPLY = TOKEN_INITIAL_SUPPLY * (10**TOKEN_DECIMALS) - - -@pytest.fixture -def erc20(get_contract): - with open("examples/tokens/ERC20.vy") as f: - contract_code = f.read() - return get_contract( - contract_code, *[TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, TOKEN_INITIAL_SUPPLY] - ) - - -def test_call(w3, market_maker, erc20, tx_failed): - # a0 = w3.eth.accounts[0] - ether, ethers = w3.to_wei(1, "ether"), w3.to_wei(2, "ether") - erc20.approve(market_maker.address, ethers, transact={}) - assert erc20.name() == TOKEN_NAME - assert erc20.decimals() == TOKEN_DECIMALS - - market_maker.foo(erc20.address, ether, transact={"value": ethers}) From 5fc8ea46e784a1c639c2d8b87f14fdbf0a1ec4ae Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 2 Apr 2024 18:31:05 +0300 Subject: [PATCH 313/322] Update python-version to 3.12 and 312 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 61830b1079..10312413e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -118,7 +118,7 @@ jobs: debug: false evm-version: shanghai - - python-version: ["3.11", "311"] + - python-version: ["3.12", "312"] opt-mode: gas debug: false evm-version: shanghai From 958d7435a841666b59381dec083889a39cb6feed Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Apr 2024 15:31:35 +0000 Subject: [PATCH 314/322] fix IRLiteral.__eq__() --- vyper/venom/basicblock.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index eb805b0079..1577aa6bca 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -128,8 +128,10 @@ def __init__(self, value: int) -> None: def __hash__(self) -> int: return self.value.__hash__() - def __eq__(self, v: object) -> bool: - return self.value == v + def __eq__(self, other) -> bool: + if not isinstance(other, type(self)): + return False + return self.value == other.value def __repr__(self) -> str: return str(self.value) From 679543bc26902706597f5585137dfc826fef5b40 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 2 Apr 2024 18:32:18 +0300 Subject: [PATCH 315/322] Remove unused code in test_dominator_tree.py --- tests/unit/compiler/venom/test_dominator_tree.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index ff5dcbc43b..445e470961 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -72,6 +72,3 @@ def test_phi_placement(): MakeSSA.run_pass(ctx, bb1) - -if __name__ == "__main__": - test_phi_placement() From c724265aa5a384d7d7a12939273eb263dc4c6136 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Apr 2024 15:33:54 +0000 Subject: [PATCH 316/322] fix some more `__eq__()` instances --- vyper/venom/basicblock.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 1577aa6bca..59c4f14aa2 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -186,8 +186,10 @@ def version(self) -> int: def __hash__(self) -> int: return self.value.__hash__() - def __eq__(self, v: object) -> bool: - return self.value == v + def __eq__(self, other) -> bool: + if not isinstance(other, type(self)): + return False + return self.value == other.value def __repr__(self) -> str: return self.value @@ -211,8 +213,10 @@ def __init__(self, value: str, is_symbol: bool = False) -> None: def __hash__(self) -> int: return hash(self.value) - def __eq__(self, v: object) -> bool: - return self.value == v + def __eq__(self, other) -> bool: + if not isinstance(other, type(self)): + return False + return self.value == other.value def __repr__(self) -> str: return self.value From 8df8573dcfac8f4e5e3b513fbd5c769e35e8567a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 2 Apr 2024 18:38:49 +0300 Subject: [PATCH 317/322] assert in experimental case --- tests/unit/compiler/asm/test_asm_optimizer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/compiler/asm/test_asm_optimizer.py b/tests/unit/compiler/asm/test_asm_optimizer.py index ce7188b0a0..3d19102c38 100644 --- a/tests/unit/compiler/asm/test_asm_optimizer.py +++ b/tests/unit/compiler/asm/test_asm_optimizer.py @@ -121,5 +121,7 @@ def foo(): asm = res["asm"] if not experimental_codegen: assert "some_function()" in asm + else: + assert "some_function()" not in asm assert "unused1()" not in asm assert "unused2()" not in asm From 1becca741f31af527efe18c77b2a8b5c086ebedd Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Apr 2024 16:03:44 +0000 Subject: [PATCH 318/322] add a comment --- vyper/venom/ir_node_to_venom.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index e390dd0a58..f610e17f58 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -436,6 +436,8 @@ def _convert_ir_bb(ctx, ir, symbols): return bb.append_instruction("mload", arg_0) elif ir.value == "mstore": + # some upstream code depends on reversed order of evaluation -- + # to fix upstream. arg_1, arg_0 = _convert_ir_bb_list(ctx, reversed(ir.args), symbols) if isinstance(arg_1, IRVariable): From cd5f3d7ea51ca9a176f2a9fa20ba6311852e8b07 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 2 Apr 2024 19:14:53 +0300 Subject: [PATCH 319/322] Remove mem_type and mem_addr --- vyper/venom/basicblock.py | 14 +------------- vyper/venom/function.py | 6 ++---- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 59c4f14aa2..1a866bdbff 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -150,17 +150,7 @@ class IRVariable(IRValue): value: str offset: int = 0 - # some variables can be in memory for conversion from legacy IR to venom - mem_type: MemType = MemType.OPERAND_STACK - mem_addr: Optional[int] = None - - def __init__( - self, - value: str, - mem_type: MemType = MemType.OPERAND_STACK, - mem_addr: int = None, - version: Optional[str | int] = None, - ) -> None: + def __init__(self, value: str, version: Optional[str | int] = None) -> None: assert isinstance(value, str) assert ":" not in value, "Variable name cannot contain ':'" if version: @@ -170,8 +160,6 @@ def __init__( value = f"%{value}" self.value = value self.offset = 0 - self.mem_type = mem_type - self.mem_addr = mem_addr @property def name(self) -> str: diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 9603e6d9da..aa5f5ce0f4 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -129,11 +129,9 @@ def get_next_label(self, suffix: str = "") -> IRLabel: self.last_label += 1 return IRLabel(f"{self.last_label}{suffix}") - def get_next_variable( - self, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: Optional[int] = None - ) -> IRVariable: + def get_next_variable(self) -> IRVariable: self.last_variable += 1 - return IRVariable(f"%{self.last_variable}", mem_type, mem_addr) + return IRVariable(f"%{self.last_variable}") def get_last_variable(self) -> str: return f"%{self.last_variable}" From e0dd344e7e885a90bca5f3afd8785324f083fa54 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Apr 2024 12:14:49 -0400 Subject: [PATCH 320/322] remove MemType datatype --- vyper/venom/basicblock.py | 6 ------ vyper/venom/function.py | 1 - 2 files changed, 7 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 1a866bdbff..6c509d8f95 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -1,4 +1,3 @@ -from enum import Enum, auto from typing import TYPE_CHECKING, Any, Generator, Iterator, Optional, Union from vyper.utils import OrderedSet @@ -137,11 +136,6 @@ def __repr__(self) -> str: return str(self.value) -class MemType(Enum): - OPERAND_STACK = auto() - MEMORY = auto() - - class IRVariable(IRValue): """ IRVariable represents a variable in IR. A variable is a string that starts with a %. diff --git a/vyper/venom/function.py b/vyper/venom/function.py index aa5f5ce0f4..d1680385f5 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -9,7 +9,6 @@ IRLabel, IROperand, IRVariable, - MemType, ) GLOBAL_LABEL = IRLabel("__global") From 7efa00603525745a5bc04c282aa8cb85d4187a83 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Apr 2024 16:24:11 +0000 Subject: [PATCH 321/322] fix a test --- tests/unit/compiler/asm/test_asm_optimizer.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/unit/compiler/asm/test_asm_optimizer.py b/tests/unit/compiler/asm/test_asm_optimizer.py index 3d19102c38..5742f7c8df 100644 --- a/tests/unit/compiler/asm/test_asm_optimizer.py +++ b/tests/unit/compiler/asm/test_asm_optimizer.py @@ -119,9 +119,7 @@ def foo(): input_bundle = make_input_bundle({"library.vy": library}) res = compile_code(code, input_bundle=input_bundle, output_formats=["asm"]) asm = res["asm"] - if not experimental_codegen: - assert "some_function()" in asm - else: - assert "some_function()" not in asm + assert "some_function()" in asm + assert "unused1()" not in asm assert "unused2()" not in asm From ba29b2448690c878e3d70b5af521ab342478da26 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Apr 2024 12:27:46 -0400 Subject: [PATCH 322/322] fix lint --- tests/unit/compiler/venom/test_dominator_tree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/compiler/venom/test_dominator_tree.py b/tests/unit/compiler/venom/test_dominator_tree.py index 445e470961..dc27380796 100644 --- a/tests/unit/compiler/venom/test_dominator_tree.py +++ b/tests/unit/compiler/venom/test_dominator_tree.py @@ -71,4 +71,3 @@ def test_phi_placement(): bb7.insert_instruction(IRInstruction("mstore", [x, IRLiteral(0)]), 0) MakeSSA.run_pass(ctx, bb1) -
{html.escape(str(bb.label))}
{html.escape(str(inst))}