From 65553b0613c77753cbdbfc37d2c1e76e271b12f7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 7 Dec 2023 14:25:24 +0200 Subject: [PATCH 01/60] Add method to add instruction to IRBasicBlock --- vyper/venom/basicblock.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index b95d7416ca..998acfb797 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -301,6 +301,17 @@ def append_instruction(self, instruction: IRInstruction) -> None: instruction.parent = self self.instructions.append(instruction) + def add_instruction_no_return(self, opcode: str, **args) -> IRInstruction: + inst = IRInstruction(opcode, args) + self.append_instruction(inst) + return inst + + def add_instruction(self, opcode: str, **args) -> IRInstruction: + ret = self.parent.get_next_variable() + inst = IRInstruction(opcode, args, ret) + self.append_instruction(inst) + return inst + def insert_instruction(self, instruction: IRInstruction, index: int) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" instruction.parent = self From 29d54476a3671674b0bef7ad0573f9b8701da341 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Dec 2023 14:50:30 +0200 Subject: [PATCH 02/60] Refactor add_instruction_no_return and add_instruction methods --- vyper/venom/basicblock.py | 11 +++++------ vyper/venom/ir_node_to_venom.py | 14 ++++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 998acfb797..fa6c0ab0db 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -301,16 +301,15 @@ def append_instruction(self, instruction: IRInstruction) -> None: instruction.parent = self self.instructions.append(instruction) - def add_instruction_no_return(self, opcode: str, **args) -> IRInstruction: - inst = IRInstruction(opcode, args) + def add_instruction_no_return(self, opcode: str, *args) -> None: + inst = IRInstruction(opcode, list(args)) self.append_instruction(inst) - return inst - def add_instruction(self, opcode: str, **args) -> IRInstruction: + def add_instruction(self, opcode: str, *args) -> IRVariable: ret = self.parent.get_next_variable() - inst = IRInstruction(opcode, args, ret) + inst = IRInstruction(opcode, list(args), ret) self.append_instruction(inst) - return inst + return ret def insert_instruction(self, instruction: IRInstruction, index: int) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 19bd5c8b73..7108fd4268 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -164,16 +164,22 @@ def _handle_self_call( ctx, arg._optimized, symbols, variables, allocated_variables ) if arg.location and arg.location.load_op == "calldataload": - ret = ctx.append_instruction(arg.location.load_op, [ret]) + bb = ctx.get_basic_block() + ret = bb.add_instruction(arg.location.load_op, ret) ret_args.append(ret) if return_buf.is_literal: ret_args.append(IRLiteral(return_buf.value)) # type: ignore + bb = ctx.get_basic_block() do_ret = func_t.return_type is not None - invoke_ret = ctx.append_instruction("invoke", ret_args, do_ret) # type: ignore - allocated_variables["return_buffer"] = invoke_ret # type: ignore - return invoke_ret + if do_ret: + invoke_ret = bb.add_instruction("invoke", *ret_args) + allocated_variables["return_buffer"] = invoke_ret # type: ignore + return invoke_ret + else: + bb.add_instruction_no_return("invoke", *ret_args) + return None def _handle_internal_func( From 5f8a9b31283a9ad003bb147721bb0a637258c611 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Dec 2023 14:57:36 +0200 Subject: [PATCH 03/60] refactor to use new methods in bb --- 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 7108fd4268..21bf1435b6 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -228,7 +228,7 @@ def _convert_ir_simple_node( args = [ _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args ] - return ctx.append_instruction(ir.value, args) # type: ignore + return ctx.get_basic_block().add_instruction(ir.value, *args) # type: ignore _break_target: Optional[IRBasicBlock] = None @@ -250,16 +250,17 @@ def _get_variable_from_address( def _get_return_for_stack_operand( ctx: IRFunction, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable ) -> IRInstruction: + bb = ctx.get_basic_block() if isinstance(ret_ir, IRLiteral): sym = symbols.get(f"&{ret_ir.value}", None) - new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) - ctx.append_instruction("mstore", [sym, new_var], False) # type: ignore + new_var = bb.add_instruction("alloca", IRLiteral(32), ret_ir) + bb.add_instruction_no_return("mstore", sym, new_var) # type: ignore else: sym = symbols.get(ret_ir.value, None) if sym is None: # FIXME: needs real allocations - new_var = ctx.append_instruction("alloca", [IRLiteral(32), IRLiteral(0)]) - ctx.append_instruction("mstore", [ret_ir, new_var], False) # type: ignore + new_var = bb.add_instruction("alloca", IRLiteral(32), IRLiteral(0)) + bb.add_instruction_no_return("mstore", ret_ir, new_var) # type: ignore else: new_var = ret_ir return IRInstruction("return", [last_ir, new_var]) # type: ignore @@ -286,7 +287,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ir.value = INVERSE_MAPPED_IR_INSTRUCTIONS[ir.value] new_var = _convert_binary_op(ctx, ir, symbols, variables, allocated_variables) ir.value = org_value - return ctx.append_instruction("iszero", [new_var]) + return ctx.get_basic_block().add_instruction("iszero", new_var) elif ir.value in PASS_THROUGH_INSTRUCTIONS: return _convert_ir_simple_node(ctx, ir, symbols, variables, allocated_variables) @@ -375,12 +376,14 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): retVar = ctx.get_next_variable(MemType.MEMORY, retOffsetValue) symbols[f"&{retOffsetValue}"] = retVar + bb = ctx.get_basic_block() + if ir.value == "call": args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] - return ctx.append_instruction(ir.value, args) + return bb.add_instruction(ir.value, *args) else: args = [retSize, retOffset, argsSize, argsOffsetVar, address, gas] - return ctx.append_instruction(ir.value, args) + return bb.add_instruction(ir.value, *args) elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() @@ -400,7 +403,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ) if isinstance(else_ret_val, IRLiteral): assert isinstance(else_ret_val.value, int) # help mypy - else_ret_val = ctx.append_instruction("store", [IRLiteral(else_ret_val.value)]) + else_ret_val = ctx.get_basic_block().add_instruction( + "store", IRLiteral(else_ret_val.value) + ) after_else_syms = else_syms.copy() # convert "then" @@ -411,10 +416,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ctx, ir.args[1], symbols, variables, allocated_variables ) if isinstance(then_ret_val, IRLiteral): - then_ret_val = ctx.append_instruction("store", [IRLiteral(then_ret_val.value)]) + then_ret_val = ctx.get_basic_block().add_instruction( + "store", IRLiteral(then_ret_val.value) + ) - inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) - current_bb.append_instruction(inst) + current_bb.add_instruction_no_return("jnz", cont_ret, then_block.label, else_block.label) after_then_syms = symbols.copy() From cc0ce9d8167a4876a7c3fbe9c1959e31b495c1d6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Dec 2023 15:06:37 +0200 Subject: [PATCH 04/60] refactor exit_to --- vyper/venom/ir_node_to_venom.py | 40 +++++++++++++++++---------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 21bf1435b6..518057ba83 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -471,7 +471,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): sym = ir.args[0] if isinstance(ret, IRLiteral): - new_var = ctx.append_instruction("store", [ret]) # type: ignore + new_var = ctx.get_basic_block().add_instruction("store", ret) # type: ignore with_symbols[sym.value] = new_var else: with_symbols[sym.value] = ret # type: ignore @@ -489,7 +489,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "set": sym = ir.args[0] arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - new_var = ctx.append_instruction("store", [arg_1]) # type: ignore + new_var = ctx.get_basic_block().add_instruction("store", arg_1) # type: ignore symbols[sym.value] = new_var elif ir.value == "calldatacopy": @@ -503,16 +503,17 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): 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 = ctx.append_instruction( - "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] # type: ignore + new_v = bb.add_instruction( + "alloca", IRLiteral(var.size), IRLiteral(var.pos) # type: ignore ) allocated_variables[var.name] = new_v # type: ignore - ctx.append_instruction("calldatacopy", [size, arg_1, new_v], False) # type: ignore + bb.add_instruction_no_return("calldatacopy", size, arg_1, new_v) # type: ignore symbols[f"&{var.pos}"] = new_v # type: ignore else: - ctx.append_instruction("calldatacopy", [size, arg_1, new_v], False) # type: ignore + bb.add_instruction_no_return("calldatacopy", size, arg_1, new_v) # type: ignore return new_v elif ir.value == "codecopy": @@ -520,7 +521,7 @@ def _convert_ir_basicblock(ctx, ir, 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) - ctx.append_instruction("codecopy", [size, arg_1, arg_0], False) # type: ignore + ctx.get_basic_block().add_instruction_no_return("codecopy", size, arg_1, arg_0) # type: ignore elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) elif ir.value == "data": @@ -581,6 +582,8 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): 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) @@ -594,8 +597,8 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if var.size and int(var.size) > 32: offset = int(ret_ir.value) - var.pos # type: ignore if offset > 0: - ptr_var = ctx.append_instruction( - "add", [IRLiteral(var.pos), IRLiteral(offset)] + ptr_var = bb.add_instruction( + "add", IRLiteral(var.pos), IRLiteral(offset) ) else: ptr_var = allocated_var @@ -606,24 +609,23 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if isinstance(ret_ir, IRLiteral): sym = symbols.get(f"&{ret_ir.value}", None) if sym is None: - inst = IRInstruction("return", [last_ir, ret_ir]) + bb.add_instruction_no_return("return", last_ir, ret_ir) else: if func_t.return_type.memory_bytes_required > 32: - new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) - ctx.append_instruction("mstore", [sym, new_var], False) - inst = IRInstruction("return", [last_ir, new_var]) + new_var = bb.add_instruction("alloca", IRLiteral(32), ret_ir) + bb.append_instruction_no_return("mstore", sym, new_var) + bb.add_instruction_no_return("return", last_ir, new_var) else: - inst = IRInstruction("return", [last_ir, ret_ir]) + bb.add_instruction_no_return("return", last_ir, ret_ir) else: if last_ir and int(last_ir.value) > 32: - inst = IRInstruction("return", [last_ir, ret_ir]) + bb.add_instruction_no_return("return", last_ir, ret_ir) else: ret_buf = IRLiteral(128) # TODO: need allocator - new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_buf]) - ctx.append_instruction("mstore", [ret_ir, new_var], False) - inst = IRInstruction("return", [last_ir, new_var]) + new_var = bb.add_instruction("alloca", IRLiteral(32), ret_buf) + bb.add_instruction_no_return("mstore", ret_ir, new_var) + bb.add_instruction_no_return("return", last_ir, new_var) - ctx.get_basic_block().append_instruction(inst) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) if func_t.is_internal: From 6ec4ecffa04954cb5b94b1216db4b89860460add Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Dec 2023 15:17:58 +0200 Subject: [PATCH 05/60] finish phase out direct ctx instruction adding --- vyper/venom/ir_node_to_venom.py | 75 +++++++++++++++++---------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 518057ba83..568e80a143 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -628,18 +628,19 @@ def _convert_ir_basicblock(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: assert ir.args[1].value == "return_pc", "return_pc not found" if func_t.return_type is None: - inst = IRInstruction("ret", [symbols["return_pc"]]) + bb.add_instruction_no_return("ret", symbols["return_pc"]) else: if func_t.return_type.memory_bytes_required > 32: - inst = IRInstruction("ret", [symbols["return_buffer"], symbols["return_pc"]]) + bb.add_instruction_no_return( + "ret", symbols["return_buffer"], symbols["return_pc"] + ) else: - ret_by_value = ctx.append_instruction("mload", [symbols["return_buffer"]]) - inst = IRInstruction("ret", [ret_by_value, symbols["return_pc"]]) - - ctx.get_basic_block().append_instruction(inst) + ret_by_value = bb.add_instruction("mload", symbols["return_buffer"]) + bb.add_instruction_no_return("ret", ret_by_value, symbols["return_pc"]) elif ir.value == "revert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) @@ -649,12 +650,13 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "dload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - src = ctx.append_instruction("add", [arg_0, IRLabel("code_end")]) + bb = ctx.get_basic_block() + src = bb.add_instruction("add", arg_0, IRLabel("code_end")) - ctx.append_instruction( - "dloadbytes", [IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE)], False + bb.add_instruction_no_return( + "dloadbytes", IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE) ) - return ctx.append_instruction("mload", [IRLiteral(MemoryPositions.FREE_VAR_SPACE)]) + return bb.add_instruction("mload", IRLiteral(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( @@ -662,7 +664,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ) len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) - src = ctx.append_instruction("add", [src_offset, IRLabel("code_end")]) + src = ctx.get_basic_block().add_instruction("add", src_offset, IRLabel("code_end")) inst = IRInstruction("dloadbytes", [len_, src, dst]) ctx.get_basic_block().append_instruction(inst) @@ -672,25 +674,26 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): 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 allocated_variables.get(var.name, None) is None: - allocated_variables[var.name] = ctx.append_instruction( - "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] + allocated_variables[var.name] = bb.add_instruction( + "alloca", IRLiteral(var.size), IRLiteral(var.pos) ) offset = int(sym_ir.value) - var.pos if offset > 0: - ptr_var = ctx.append_instruction("add", [IRLiteral(var.pos), IRLiteral(offset)]) + ptr_var = bb.add_instruction("add", IRLiteral(var.pos), IRLiteral(offset)) else: ptr_var = allocated_variables[var.name] - return ctx.append_instruction("mload", [ptr_var]) + return bb.add_instruction("mload", ptr_var) else: if sym_ir.is_literal: sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: - new_var = ctx.append_instruction("store", [sym_ir]) + new_var = bb.add_instruction("store", sym_ir) symbols[f"&{sym_ir.value}"] = new_var if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = new_var @@ -705,9 +708,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if sym_ir.is_literal: new_var = symbols.get(f"&{sym_ir.value}", None) if new_var is not None: - return ctx.append_instruction("mload", [new_var]) + return bb.add_instruction("mload", new_var) else: - return ctx.append_instruction("mload", [IRLiteral(sym_ir.value)]) + return bb.add_instruction("mload", IRLiteral(sym_ir.value)) else: new_var = _convert_ir_basicblock( ctx, sym_ir, symbols, variables, allocated_variables @@ -720,12 +723,14 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): # if sym_ir.is_self_call: return new_var - return ctx.append_instruction("mload", [new_var]) + return bb.add_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) + bb = ctx.get_basic_block() + var = None if isinstance(sym_ir, IRLiteral): var = _get_variable_from_address(variables, int(sym_ir.value)) @@ -733,41 +738,38 @@ def _convert_ir_basicblock(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] = ctx.append_instruction( - "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] + allocated_variables[var.name] = bb.add_instruction( + "alloca", IRLiteral(var.size), IRLiteral(var.pos) ) offset = int(sym_ir.value) - var.pos if offset > 0: - ptr_var = ctx.append_instruction("add", [IRLiteral(var.pos), IRLiteral(offset)]) + ptr_var = bb.add_instruction("add", IRLiteral(var.pos), IRLiteral(offset)) else: ptr_var = allocated_variables[var.name] - return ctx.append_instruction("mstore", [arg_1, ptr_var], False) + bb.add_instruction_no_return("mstore", arg_1, ptr_var) else: if isinstance(sym_ir, IRLiteral): - new_var = ctx.append_instruction("store", [arg_1]) + new_var = bb.add_instruction("store", arg_1) 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: if not isinstance(sym_ir, IRLiteral): - inst = IRInstruction("mstore", [arg_1, sym_ir]) - ctx.get_basic_block().append_instruction(inst) + bb.add_instruction_no_return("mstore", arg_1, sym_ir) return None sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: - inst = IRInstruction("mstore", [arg_1, sym_ir]) - ctx.get_basic_block().append_instruction(inst) + bb.add_instruction_no_return("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): - inst = IRInstruction("mstore", [arg_1, sym]) - ctx.get_basic_block().append_instruction(inst) + bb.add_instruction_no_return("mstore", arg_1, sym) return None else: symbols[sym_ir.value] = arg_1 @@ -775,12 +777,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value in ["sload", "iload"]: arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - return ctx.append_instruction(ir.value, [arg_0]) + return ctx.get_basic_block().add_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) - inst = IRInstruction(ir.value, [arg_1, arg_0]) - ctx.get_basic_block().append_instruction(inst) + ctx.get_basic_block().add_instruction_no_return(ir.value, arg_1, arg_0) elif ir.value == "unique_symbol": sym = ir.args[0] new_var = ctx.get_next_variable() @@ -892,22 +893,22 @@ def emit_body_block(): ctx.get_basic_block().append_instruction(inst) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) elif ir.value == "gas": - return ctx.append_instruction("gas", []) + return ctx.get_basic_block().append_instruction("gas") elif ir.value == "returndatasize": - return ctx.append_instruction("returndatasize", []) + return ctx.get_basic_block().add_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) - new_var = ctx.append_instruction("returndatacopy", [arg_1, size]) + new_var = ctx.get_basic_block().add_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) - ctx.append_instruction("selfdestruct", [arg_0], False) + ctx.get_basic_block().add_instruction_no_return("selfdestruct", arg_0) elif isinstance(ir.value, str) and ir.value.startswith("log"): args = [ _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) From 24c328e21771f1096ff98b5ba00eae9e26296b99 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Dec 2023 15:18:30 +0200 Subject: [PATCH 06/60] Remove append_instruction method from IRFunction --- vyper/venom/function.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index c14ad77345..e16b2ad6e6 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -98,17 +98,6 @@ def remove_unreachable_blocks(self) -> int: self.basic_blocks = new_basic_blocks return removed - def append_instruction( - self, opcode: str, args: list[IROperand], do_ret: bool = True - ) -> Optional[IRVariable]: - """ - Append instruction to last basic block. - """ - ret = self.get_next_variable() if do_ret else None - inst = IRInstruction(opcode, args, ret) # type: ignore - self.get_basic_block().append_instruction(inst) - return ret - def append_data(self, opcode: str, args: list[IROperand]) -> None: """ Append data From 7e09a3f242faaa3f6ffde130b9b669fd8623a95c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Dec 2023 15:50:40 +0200 Subject: [PATCH 07/60] refactor basic block instruction appending --- vyper/venom/basicblock.py | 10 +- vyper/venom/ir_node_to_venom.py | 155 +++++++++++----------------- vyper/venom/passes/normalization.py | 2 +- 3 files changed, 64 insertions(+), 103 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index fa6c0ab0db..7e9cc3a737 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -243,8 +243,8 @@ class IRBasicBlock: %2 = mul %1, 2 is represented as: bb = IRBasicBlock("bb", function) - bb.append_instruction(IRInstruction("add", ["%0", "1"], "%1")) - bb.append_instruction(IRInstruction("mul", ["%1", "2"], "%2")) + r1 = bb.add_instruction("add", "%0", "1") + r2 = bb.add_instruction("mul", r1, "2") The label of a basic block is used to refer to it from other basic blocks in order to branch to it. @@ -296,19 +296,19 @@ def remove_cfg_out(self, bb: "IRBasicBlock") -> None: def is_reachable(self) -> bool: return len(self.cfg_in) > 0 - def append_instruction(self, instruction: IRInstruction) -> None: + def _append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" instruction.parent = self self.instructions.append(instruction) def add_instruction_no_return(self, opcode: str, *args) -> None: inst = IRInstruction(opcode, list(args)) - self.append_instruction(inst) + self._append_instruction(inst) def add_instruction(self, opcode: str, *args) -> IRVariable: ret = self.parent.get_next_variable() inst = IRInstruction(opcode, list(args), ret) - self.append_instruction(inst) + self._append_instruction(inst) return ret def insert_instruction(self, instruction: IRInstruction, index: int) -> None: diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 568e80a143..f81153de7e 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -93,11 +93,11 @@ def convert_ir_basicblock(ir: IRnode) -> IRFunction: for i, bb in enumerate(global_function.basic_blocks): if not bb.is_terminated and i < len(global_function.basic_blocks) - 1: - bb.append_instruction(IRInstruction("jmp", [global_function.basic_blocks[i + 1].label])) + bb.add_instruction("jmp", global_function.basic_blocks[i + 1].label) revert_bb = IRBasicBlock(IRLabel("__revert"), global_function) revert_bb = global_function.append_basic_block(revert_bb) - revert_bb.append_instruction(IRInstruction("revert", [IRLiteral(0), IRLiteral(0)])) + revert_bb.add_instruction_no_return("revert", IRLiteral(0), IRLiteral(0)) return global_function @@ -113,18 +113,12 @@ def _convert_binary_op( 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) - args = [arg_1, arg_0] - ret = ctx.get_next_variable() - - inst = IRInstruction(ir.value, args, ret) # type: ignore - ctx.get_basic_block().append_instruction(inst) - return ret + return ctx.get_basic_block().add_instruction(ir.value, arg_1, arg_0) def _append_jmp(ctx: IRFunction, label: IRLabel) -> None: - inst = IRInstruction("jmp", [label]) - ctx.get_basic_block().append_instruction(inst) + ctx.get_basic_block().add_instruction_no_return("jmp", label) label = ctx.get_next_label() bb = IRBasicBlock(label, ctx) @@ -192,28 +186,18 @@ def _handle_internal_func( old_ir_mempos += 64 for arg in func_t.arguments: - new_var = ctx.get_next_variable() - - alloca_inst = IRInstruction("param", [], new_var) - alloca_inst.annotation = arg.name - bb.append_instruction(alloca_inst) - symbols[f"&{old_ir_mempos}"] = new_var + symbols[f"&{old_ir_mempos}"] = bb.add_instruction("param") + 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: - new_var = ctx.get_next_variable() - alloca_inst = IRInstruction("param", [], new_var) - bb.append_instruction(alloca_inst) - alloca_inst.annotation = "return_buffer" - symbols["return_buffer"] = new_var + symbols["return_buffer"] = bb.add_instruction("param") + bb.instructions[-1].annotation = "return_buffer" # return address - new_var = ctx.get_next_variable() - alloca_inst = IRInstruction("param", [], new_var) - bb.append_instruction(alloca_inst) - alloca_inst.annotation = "return_pc" - symbols["return_pc"] = new_var + symbols["return_pc"] = bb.add_instruction("param") + bb.instructions[-1].annotation = "return_pc" return ir.args[0].args[2] @@ -303,8 +287,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): runtimeLabel = ctx.get_next_label() - inst = IRInstruction("deploy", [IRLiteral(memsize), runtimeLabel, IRLiteral(padding)]) - ctx.get_basic_block().append_instruction(inst) + ctx.get_basic_block().add_instruction_no_return( + "deploy", IRLiteral(memsize), runtimeLabel, IRLiteral(padding) + ) bb = IRBasicBlock(runtimeLabel, ctx) ctx.append_basic_block(bb) @@ -431,33 +416,25 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if_ret = None if then_ret_val is not None and else_ret_val is not None: - if_ret = ctx.get_next_variable() - bb.append_instruction( - IRInstruction( - "phi", [then_block.label, then_ret_val, else_block.label, else_ret_val], if_ret - ) + if_ret = bb.add_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 = ctx.get_next_variable() + ret = bb.add_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 - bb.append_instruction( - IRInstruction("phi", [then_block.label, val[0], else_block.label, val[1]], ret) - ) if not else_block.is_terminated: - exit_inst = IRInstruction("jmp", [bb.label]) - else_block.append_instruction(exit_inst) + else_block.add_instruction_no_return("jmp", bb.label) if not then_block.is_terminated: - exit_inst = IRInstruction("jmp", [bb.label]) - then_block.append_instruction(exit_inst) + then_block.add_instruction_no_return("jmp", bb.label) return if_ret @@ -483,8 +460,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "jump": arg_1 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - inst = IRInstruction("jmp", [arg_1]) - ctx.get_basic_block().append_instruction(inst) + ctx.get_basic_block().add_instruction_no_return("jmp", arg_1) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] @@ -539,13 +515,12 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) current_bb = ctx.get_basic_block() - inst = IRInstruction("assert", [arg_0]) # type: ignore - current_bb.append_instruction(inst) + current_bb.add_instruction_no_return("assert", arg_0) elif ir.value == "label": label = IRLabel(ir.args[0].value, True) - if not ctx.get_basic_block().is_terminated: - inst = IRInstruction("jmp", [label]) - ctx.get_basic_block().append_instruction(inst) + bb = ctx.get_basic_block() + if not bb.is_terminated: + bb.add_instruction_no_return("jmp", label) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) @@ -555,14 +530,13 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): 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) - inst = IRInstruction("jmp", [label]) - ctx.get_basic_block().append_instruction(inst) + bb.add_instruction_no_return("jmp", label) return None if func_t.return_type is None: - inst = IRInstruction("stop", []) - ctx.get_basic_block().append_instruction(inst) + bb.add_instruction_no_return("stop") return None else: last_ir = None @@ -613,7 +587,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): else: if func_t.return_type.memory_bytes_required > 32: new_var = bb.add_instruction("alloca", IRLiteral(32), ret_ir) - bb.append_instruction_no_return("mstore", sym, new_var) + bb.add_instruction_no_return("mstore", sym, new_var) bb.add_instruction_no_return("return", last_ir, new_var) else: bb.add_instruction_no_return("return", last_ir, ret_ir) @@ -645,8 +619,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): 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) - inst = IRInstruction("revert", [arg_1, arg_0]) - ctx.get_basic_block().append_instruction(inst) + ctx.get_basic_block().add_instruction_no_return("revert", arg_1, arg_0) elif ir.value == "dload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) @@ -657,18 +630,18 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): "dloadbytes", IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE) ) return bb.add_instruction("mload", IRLiteral(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 ) len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) - - src = ctx.get_basic_block().add_instruction("add", src_offset, IRLabel("code_end")) - - inst = IRInstruction("dloadbytes", [len_, src, dst]) - ctx.get_basic_block().append_instruction(inst) + bb = ctx.get_basic_block() + src = bb.add_instruction("add", src_offset, IRLabel("code_end")) + bb.add_instruction_no_return("dloadbytes", len_, src, dst) return None + elif ir.value == "mload": sym_ir = ir.args[0] var = ( @@ -818,28 +791,20 @@ def emit_body_block(): increment_block = IRBasicBlock(ctx.get_next_label(), ctx) exit_block = IRBasicBlock(ctx.get_next_label(), ctx) - counter_var = ctx.get_next_variable() counter_inc_var = ctx.get_next_variable() ret = ctx.get_next_variable() - inst = IRInstruction("store", [start], counter_var) - ctx.get_basic_block().append_instruction(inst) + counter_var = ctx.get_basic_block().add_instruction("store", start) symbols[sym.value] = counter_var - inst = IRInstruction("jmp", [cond_block.label]) - ctx.get_basic_block().append_instruction(inst) + ctx.get_basic_block().add_instruction_no_return("jmp", cond_block.label) - symbols[sym.value] = ret - cond_block.append_instruction( - IRInstruction( - "phi", [entry_block.label, counter_var, increment_block.label, counter_inc_var], ret - ) + ret = cond_block.add_instruction( + "phi", entry_block.label, counter_var, increment_block.label, counter_inc_var ) + symbols[sym.value] = ret - xor_ret = ctx.get_next_variable() - cont_ret = ctx.get_next_variable() - inst = IRInstruction("xor", [ret, end], xor_ret) - cond_block.append_instruction(inst) - cond_block.append_instruction(IRInstruction("iszero", [xor_ret], cont_ret)) + xor_ret = cond_block.add_instruction("xor", ret, end) + cont_ret = cond_block.add_instruction("iszero", xor_ret) ctx.append_basic_block(cond_block) # Do a dry run to get the symbols needing phi nodes @@ -866,34 +831,30 @@ def emit_body_block(): body_end = ctx.get_basic_block() if not body_end.is_terminated: - body_end.append_instruction(IRInstruction("jmp", [jump_up_block.label])) + body_end.add_instruction_no_return("jmp", jump_up_block.label) - jump_cond = IRInstruction("jmp", [increment_block.label]) - jump_up_block.append_instruction(jump_cond) + jump_up_block.add_instruction_no_return("jmp", increment_block.label) ctx.append_basic_block(jump_up_block) - increment_block.append_instruction( - IRInstruction("add", [ret, IRLiteral(1)], counter_inc_var) - ) - increment_block.append_instruction(IRInstruction("jmp", [cond_block.label])) + increment_block.add_instruction_no_return(IRInstruction("add", ret, IRLiteral(1))) + increment_block.insert_instruction[-1].output = counter_inc_var + + increment_block.add_instruction_no_return("jmp", cond_block.label) ctx.append_basic_block(increment_block) ctx.append_basic_block(exit_block) - inst = IRInstruction("jnz", [cont_ret, exit_block.label, body_block.label]) - cond_block.append_instruction(inst) + cond_block.add_instruction("jnz", cont_ret, exit_block.label, body_block.label) elif ir.value == "break": assert _break_target is not None, "Break with no break target" - inst = IRInstruction("jmp", [_break_target.label]) - ctx.get_basic_block().append_instruction(inst) + ctx.get_basic_block().add_instruction("jmp", _break_target.label) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) elif ir.value == "continue": assert _continue_target is not None, "Continue with no contrinue target" - inst = IRInstruction("jmp", [_continue_target.label]) - ctx.get_basic_block().append_instruction(inst) + ctx.get_basic_block().add_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") + return ctx.get_basic_block().add_instruction("gas") elif ir.value == "returndatasize": return ctx.get_basic_block().add_instruction("returndatasize") elif ir.value == "returndatacopy": @@ -910,12 +871,13 @@ def emit_body_block(): arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) ctx.get_basic_block().add_instruction_no_return("selfdestruct", arg_0) elif isinstance(ir.value, str) and ir.value.startswith("log"): - args = [ - _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) - for arg in ir.args - ] - inst = IRInstruction(ir.value, reversed(args)) - ctx.get_basic_block().append_instruction(inst) + args = reversed( + [ + _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) + for arg in ir.args + ] + ) + ctx.get_basic_block().add_instruction_no_return(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: @@ -942,8 +904,7 @@ def _convert_ir_opcode( inst_args.append( _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) ) - instruction = IRInstruction(opcode, inst_args) # type: ignore - ctx.get_basic_block().append_instruction(instruction) + ctx.get_basic_block().add_instruction(opcode, *inst_args) def _data_ofst_of(sym, ofst, height_): diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 9ee1012f91..b55dd20f23 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -61,7 +61,7 @@ def _insert_split_basicblock(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> IRB source = in_bb.label.value target = bb.label.value split_bb = IRBasicBlock(IRLabel(f"{target}_split_{source}"), self.ctx) - split_bb.append_instruction(IRInstruction("jmp", [bb.label])) + split_bb.add_instruction_no_return("jmp", bb.label) self.ctx.append_basic_block(split_bb) # Rewire the CFG From 86d8fbe33ee255ba5cfb8b13fbcc2b44cbeecfcd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Dec 2023 15:56:04 +0200 Subject: [PATCH 08/60] Refactor basic block instruction appending names adopted a shorted name for frequently used methods --- vyper/venom/basicblock.py | 19 ++++++++----------- vyper/venom/passes/normalization.py | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 7e9cc3a737..8b7089c1c7 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -243,8 +243,8 @@ class IRBasicBlock: %2 = mul %1, 2 is represented as: bb = IRBasicBlock("bb", function) - r1 = bb.add_instruction("add", "%0", "1") - r2 = bb.add_instruction("mul", r1, "2") + r1 = bb.append_inst("add", "%0", "1") + r2 = bb.append_inst("mul", r1, "2") The label of a basic block is used to refer to it from other basic blocks in order to branch to it. @@ -296,19 +296,16 @@ def remove_cfg_out(self, bb: "IRBasicBlock") -> None: def is_reachable(self) -> bool: return len(self.cfg_in) > 0 - def _append_instruction(self, instruction: IRInstruction) -> None: - assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" - instruction.parent = self - self.instructions.append(instruction) - - def add_instruction_no_return(self, opcode: str, *args) -> None: + def append_inst_no_ret(self, opcode: str, *args) -> None: inst = IRInstruction(opcode, list(args)) - self._append_instruction(inst) + inst.parent = self + self.instructions.append(inst) - def add_instruction(self, opcode: str, *args) -> IRVariable: + def append_inst(self, opcode: str, *args) -> IRVariable: ret = self.parent.get_next_variable() inst = IRInstruction(opcode, list(args), ret) - self._append_instruction(inst) + inst.parent = self + self.instructions.append(inst) return ret def insert_instruction(self, instruction: IRInstruction, index: int) -> None: diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index b55dd20f23..02ca63efa4 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -61,7 +61,7 @@ def _insert_split_basicblock(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> IRB source = in_bb.label.value target = bb.label.value split_bb = IRBasicBlock(IRLabel(f"{target}_split_{source}"), self.ctx) - split_bb.add_instruction_no_return("jmp", bb.label) + split_bb.append_inst_no_ret("jmp", bb.label) self.ctx.append_basic_block(split_bb) # Rewire the CFG From 53f23229a4d27599c671e38160abd78378011243 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Dec 2023 16:03:03 +0200 Subject: [PATCH 09/60] left out commit --- vyper/venom/ir_node_to_venom.py | 199 ++++++++++++++-------------- vyper/venom/passes/normalization.py | 2 +- 2 files changed, 97 insertions(+), 104 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index f81153de7e..47d9eb1269 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -93,11 +93,11 @@ def convert_ir_basicblock(ir: IRnode) -> IRFunction: for i, bb in enumerate(global_function.basic_blocks): if not bb.is_terminated and i < len(global_function.basic_blocks) - 1: - bb.add_instruction("jmp", global_function.basic_blocks[i + 1].label) + bb.append_inst("jmp", global_function.basic_blocks[i + 1].label) revert_bb = IRBasicBlock(IRLabel("__revert"), global_function) revert_bb = global_function.append_basic_block(revert_bb) - revert_bb.add_instruction_no_return("revert", IRLiteral(0), IRLiteral(0)) + revert_bb.append_inst_no_ret("revert", IRLiteral(0), IRLiteral(0)) return global_function @@ -114,11 +114,11 @@ def _convert_binary_op( 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) - return ctx.get_basic_block().add_instruction(ir.value, arg_1, arg_0) + return ctx.get_basic_block().append_inst(ir.value, arg_1, arg_0) def _append_jmp(ctx: IRFunction, label: IRLabel) -> None: - ctx.get_basic_block().add_instruction_no_return("jmp", label) + ctx.get_basic_block().append_inst_no_ret("jmp", label) label = ctx.get_next_label() bb = IRBasicBlock(label, ctx) @@ -159,7 +159,7 @@ def _handle_self_call( ) if arg.location and arg.location.load_op == "calldataload": bb = ctx.get_basic_block() - ret = bb.add_instruction(arg.location.load_op, ret) + ret = bb.append_inst(arg.location.load_op, ret) ret_args.append(ret) if return_buf.is_literal: @@ -168,11 +168,11 @@ def _handle_self_call( bb = ctx.get_basic_block() do_ret = func_t.return_type is not None if do_ret: - invoke_ret = bb.add_instruction("invoke", *ret_args) + invoke_ret = bb.append_inst("invoke", *ret_args) allocated_variables["return_buffer"] = invoke_ret # type: ignore return invoke_ret else: - bb.add_instruction_no_return("invoke", *ret_args) + bb.append_inst_no_ret("invoke", *ret_args) return None @@ -186,17 +186,17 @@ def _handle_internal_func( old_ir_mempos += 64 for arg in func_t.arguments: - symbols[f"&{old_ir_mempos}"] = bb.add_instruction("param") + symbols[f"&{old_ir_mempos}"] = bb.append_inst("param") 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.add_instruction("param") + symbols["return_buffer"] = bb.append_inst("param") bb.instructions[-1].annotation = "return_buffer" # return address - symbols["return_pc"] = bb.add_instruction("param") + symbols["return_pc"] = bb.append_inst("param") bb.instructions[-1].annotation = "return_pc" return ir.args[0].args[2] @@ -212,7 +212,7 @@ def _convert_ir_simple_node( args = [ _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args ] - return ctx.get_basic_block().add_instruction(ir.value, *args) # type: ignore + return ctx.get_basic_block().append_inst(ir.value, *args) # type: ignore _break_target: Optional[IRBasicBlock] = None @@ -231,23 +231,22 @@ def _get_variable_from_address( return None -def _get_return_for_stack_operand( - ctx: IRFunction, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable +def _append_return_for_stack_operand( + bb: IRBasicBlock, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable ) -> IRInstruction: - bb = ctx.get_basic_block() if isinstance(ret_ir, IRLiteral): sym = symbols.get(f"&{ret_ir.value}", None) - new_var = bb.add_instruction("alloca", IRLiteral(32), ret_ir) - bb.add_instruction_no_return("mstore", sym, new_var) # type: ignore + new_var = bb.append_inst("alloca", IRLiteral(32), ret_ir) + bb.append_inst_no_ret("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.add_instruction("alloca", IRLiteral(32), IRLiteral(0)) - bb.add_instruction_no_return("mstore", ret_ir, new_var) # type: ignore + new_var = bb.append_inst("alloca", IRLiteral(32), IRLiteral(0)) + bb.append_inst_no_ret("mstore", ret_ir, new_var) # type: ignore else: new_var = ret_ir - return IRInstruction("return", [last_ir, new_var]) # type: ignore + bb.append_inst_no_ret("return", last_ir, new_var) # type: ignore def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): @@ -271,7 +270,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ir.value = INVERSE_MAPPED_IR_INSTRUCTIONS[ir.value] new_var = _convert_binary_op(ctx, ir, symbols, variables, allocated_variables) ir.value = org_value - return ctx.get_basic_block().add_instruction("iszero", new_var) + return ctx.get_basic_block().append_inst("iszero", new_var) elif ir.value in PASS_THROUGH_INSTRUCTIONS: return _convert_ir_simple_node(ctx, ir, symbols, variables, allocated_variables) @@ -287,7 +286,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): runtimeLabel = ctx.get_next_label() - ctx.get_basic_block().add_instruction_no_return( + ctx.get_basic_block().append_inst_no_ret( "deploy", IRLiteral(memsize), runtimeLabel, IRLiteral(padding) ) @@ -365,10 +364,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if ir.value == "call": args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] - return bb.add_instruction(ir.value, *args) + return bb.append_inst(ir.value, *args) else: args = [retSize, retOffset, argsSize, argsOffsetVar, address, gas] - return bb.add_instruction(ir.value, *args) + return bb.append_inst(ir.value, *args) elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() @@ -388,7 +387,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ) if isinstance(else_ret_val, IRLiteral): assert isinstance(else_ret_val.value, int) # help mypy - else_ret_val = ctx.get_basic_block().add_instruction( + else_ret_val = ctx.get_basic_block().append_inst( "store", IRLiteral(else_ret_val.value) ) after_else_syms = else_syms.copy() @@ -401,11 +400,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ctx, ir.args[1], symbols, variables, allocated_variables ) if isinstance(then_ret_val, IRLiteral): - then_ret_val = ctx.get_basic_block().add_instruction( - "store", IRLiteral(then_ret_val.value) - ) + then_ret_val = ctx.get_basic_block().append_inst("store", IRLiteral(then_ret_val.value)) - current_bb.add_instruction_no_return("jnz", cont_ret, then_block.label, else_block.label) + current_bb.append_inst_no_ret("jnz", cont_ret, then_block.label, else_block.label) after_then_syms = symbols.copy() @@ -416,13 +413,13 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if_ret = None if then_ret_val is not None and else_ret_val is not None: - if_ret = bb.add_instruction( + if_ret = bb.append_inst( "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.add_instruction("phi", then_block.label, val[0], else_block.label, val[1]) + ret = bb.append_inst("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: @@ -431,10 +428,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): allocated_variables[idx] = ret # type: ignore if not else_block.is_terminated: - else_block.add_instruction_no_return("jmp", bb.label) + else_block.append_inst_no_ret("jmp", bb.label) if not then_block.is_terminated: - then_block.add_instruction_no_return("jmp", bb.label) + then_block.append_inst_no_ret("jmp", bb.label) return if_ret @@ -448,7 +445,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): sym = ir.args[0] if isinstance(ret, IRLiteral): - new_var = ctx.get_basic_block().add_instruction("store", ret) # type: ignore + new_var = ctx.get_basic_block().append_inst("store", ret) # type: ignore with_symbols[sym.value] = new_var else: with_symbols[sym.value] = ret # type: ignore @@ -460,12 +457,12 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "jump": arg_1 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - ctx.get_basic_block().add_instruction_no_return("jmp", arg_1) + ctx.get_basic_block().append_inst_no_ret("jmp", arg_1) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - new_var = ctx.get_basic_block().add_instruction("store", arg_1) # type: ignore + new_var = ctx.get_basic_block().append_inst("store", arg_1) # type: ignore symbols[sym.value] = new_var elif ir.value == "calldatacopy": @@ -482,14 +479,14 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb = ctx.get_basic_block() if var is not None: if allocated_variables.get(var.name, None) is None: - new_v = bb.add_instruction( + new_v = bb.append_inst( "alloca", IRLiteral(var.size), IRLiteral(var.pos) # type: ignore ) allocated_variables[var.name] = new_v # type: ignore - bb.add_instruction_no_return("calldatacopy", size, arg_1, new_v) # type: ignore + bb.append_inst_no_ret("calldatacopy", size, arg_1, new_v) # type: ignore symbols[f"&{var.pos}"] = new_v # type: ignore else: - bb.add_instruction_no_return("calldatacopy", size, arg_1, new_v) # type: ignore + bb.append_inst_no_ret("calldatacopy", size, arg_1, new_v) # type: ignore return new_v elif ir.value == "codecopy": @@ -497,7 +494,7 @@ def _convert_ir_basicblock(ctx, ir, 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) - ctx.get_basic_block().add_instruction_no_return("codecopy", size, arg_1, arg_0) # type: ignore + ctx.get_basic_block().append_inst_no_ret("codecopy", size, arg_1, arg_0) # type: ignore elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) elif ir.value == "data": @@ -515,12 +512,12 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) current_bb = ctx.get_basic_block() - current_bb.add_instruction_no_return("assert", arg_0) + current_bb.append_inst_no_ret("assert", arg_0) elif ir.value == "label": label = IRLabel(ir.args[0].value, True) bb = ctx.get_basic_block() if not bb.is_terminated: - bb.add_instruction_no_return("jmp", label) + bb.append_inst_no_ret("jmp", label) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) @@ -533,10 +530,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb = ctx.get_basic_block() if func_t.name == "__init__": label = IRLabel(ir.args[0].value, True) - bb.add_instruction_no_return("jmp", label) + bb.append_inst_no_ret("jmp", label) return None if func_t.return_type is None: - bb.add_instruction_no_return("stop") + bb.append_inst_no_ret("stop") return None else: last_ir = None @@ -571,34 +568,32 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if var.size and int(var.size) > 32: offset = int(ret_ir.value) - var.pos # type: ignore if offset > 0: - ptr_var = bb.add_instruction( - "add", IRLiteral(var.pos), IRLiteral(offset) - ) + ptr_var = bb.append_inst("add", IRLiteral(var.pos), IRLiteral(offset)) else: ptr_var = allocated_var - inst = IRInstruction("return", [last_ir, ptr_var]) + bb.append_inst_no_ret("return", last_ir, ptr_var) else: - inst = _get_return_for_stack_operand(ctx, symbols, new_var, last_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.add_instruction_no_return("return", last_ir, ret_ir) + bb.append_inst_no_ret("return", last_ir, ret_ir) else: if func_t.return_type.memory_bytes_required > 32: - new_var = bb.add_instruction("alloca", IRLiteral(32), ret_ir) - bb.add_instruction_no_return("mstore", sym, new_var) - bb.add_instruction_no_return("return", last_ir, new_var) + new_var = bb.append_inst("alloca", IRLiteral(32), ret_ir) + bb.append_inst_no_ret("mstore", sym, new_var) + bb.append_inst_no_ret("return", last_ir, new_var) else: - bb.add_instruction_no_return("return", last_ir, ret_ir) + bb.append_inst_no_ret("return", last_ir, ret_ir) else: if last_ir and int(last_ir.value) > 32: - bb.add_instruction_no_return("return", last_ir, ret_ir) + bb.append_inst_no_ret("return", last_ir, ret_ir) else: ret_buf = IRLiteral(128) # TODO: need allocator - new_var = bb.add_instruction("alloca", IRLiteral(32), ret_buf) - bb.add_instruction_no_return("mstore", ret_ir, new_var) - bb.add_instruction_no_return("return", last_ir, new_var) + new_var = bb.append_inst("alloca", IRLiteral(32), ret_buf) + bb.append_inst_no_ret("mstore", ret_ir, new_var) + bb.append_inst_no_ret("return", last_ir, new_var) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) @@ -606,30 +601,28 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if func_t.is_internal: assert ir.args[1].value == "return_pc", "return_pc not found" if func_t.return_type is None: - bb.add_instruction_no_return("ret", symbols["return_pc"]) + bb.append_inst_no_ret("ret", symbols["return_pc"]) else: if func_t.return_type.memory_bytes_required > 32: - bb.add_instruction_no_return( - "ret", symbols["return_buffer"], symbols["return_pc"] - ) + bb.append_inst_no_ret("ret", symbols["return_buffer"], symbols["return_pc"]) else: - ret_by_value = bb.add_instruction("mload", symbols["return_buffer"]) - bb.add_instruction_no_return("ret", ret_by_value, symbols["return_pc"]) + ret_by_value = bb.append_inst("mload", symbols["return_buffer"]) + bb.append_inst_no_ret("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) - ctx.get_basic_block().add_instruction_no_return("revert", arg_1, arg_0) + ctx.get_basic_block().append_inst_no_ret("revert", arg_1, arg_0) elif ir.value == "dload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) bb = ctx.get_basic_block() - src = bb.add_instruction("add", arg_0, IRLabel("code_end")) + src = bb.append_inst("add", arg_0, IRLabel("code_end")) - bb.add_instruction_no_return( + bb.append_inst_no_ret( "dloadbytes", IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE) ) - return bb.add_instruction("mload", IRLiteral(MemoryPositions.FREE_VAR_SPACE)) + return bb.append_inst("mload", IRLiteral(MemoryPositions.FREE_VAR_SPACE)) elif ir.value == "dloadbytes": dst = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) @@ -638,8 +631,8 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ) len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) bb = ctx.get_basic_block() - src = bb.add_instruction("add", src_offset, IRLabel("code_end")) - bb.add_instruction_no_return("dloadbytes", len_, src, dst) + src = bb.append_inst("add", src_offset, IRLabel("code_end")) + bb.append_inst_no_ret("dloadbytes", len_, src, dst) return None elif ir.value == "mload": @@ -651,22 +644,22 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if var is not None: if var.size and var.size > 32: if allocated_variables.get(var.name, None) is None: - allocated_variables[var.name] = bb.add_instruction( + allocated_variables[var.name] = bb.append_inst( "alloca", IRLiteral(var.size), IRLiteral(var.pos) ) offset = int(sym_ir.value) - var.pos if offset > 0: - ptr_var = bb.add_instruction("add", IRLiteral(var.pos), IRLiteral(offset)) + ptr_var = bb.append_inst("add", IRLiteral(var.pos), IRLiteral(offset)) else: ptr_var = allocated_variables[var.name] - return bb.add_instruction("mload", ptr_var) + return bb.append_inst("mload", ptr_var) else: if sym_ir.is_literal: sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: - new_var = bb.add_instruction("store", sym_ir) + new_var = bb.append_inst("store", sym_ir) symbols[f"&{sym_ir.value}"] = new_var if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = new_var @@ -681,9 +674,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if sym_ir.is_literal: new_var = symbols.get(f"&{sym_ir.value}", None) if new_var is not None: - return bb.add_instruction("mload", new_var) + return bb.append_inst("mload", new_var) else: - return bb.add_instruction("mload", IRLiteral(sym_ir.value)) + return bb.append_inst("mload", IRLiteral(sym_ir.value)) else: new_var = _convert_ir_basicblock( ctx, sym_ir, symbols, variables, allocated_variables @@ -696,7 +689,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): # if sym_ir.is_self_call: return new_var - return bb.add_instruction("mload", new_var) + return bb.append_inst("mload", new_var) elif ir.value == "mstore": sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) @@ -711,38 +704,38 @@ def _convert_ir_basicblock(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.add_instruction( + allocated_variables[var.name] = bb.append_inst( "alloca", IRLiteral(var.size), IRLiteral(var.pos) ) offset = int(sym_ir.value) - var.pos if offset > 0: - ptr_var = bb.add_instruction("add", IRLiteral(var.pos), IRLiteral(offset)) + ptr_var = bb.append_inst("add", IRLiteral(var.pos), IRLiteral(offset)) else: ptr_var = allocated_variables[var.name] - bb.add_instruction_no_return("mstore", arg_1, ptr_var) + bb.append_inst_no_ret("mstore", arg_1, ptr_var) else: if isinstance(sym_ir, IRLiteral): - new_var = bb.add_instruction("store", arg_1) + new_var = bb.append_inst("store", arg_1) 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: if not isinstance(sym_ir, IRLiteral): - bb.add_instruction_no_return("mstore", arg_1, sym_ir) + bb.append_inst_no_ret("mstore", arg_1, sym_ir) return None sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: - bb.add_instruction_no_return("mstore", arg_1, sym_ir) + bb.append_inst_no_ret("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.add_instruction_no_return("mstore", arg_1, sym) + bb.append_inst_no_ret("mstore", arg_1, sym) return None else: symbols[sym_ir.value] = arg_1 @@ -750,11 +743,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value in ["sload", "iload"]: arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - return ctx.get_basic_block().add_instruction(ir.value, arg_0) + return ctx.get_basic_block().append_inst(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) - ctx.get_basic_block().add_instruction_no_return(ir.value, arg_1, arg_0) + ctx.get_basic_block().append_inst_no_ret(ir.value, arg_1, arg_0) elif ir.value == "unique_symbol": sym = ir.args[0] new_var = ctx.get_next_variable() @@ -794,17 +787,17 @@ def emit_body_block(): counter_inc_var = ctx.get_next_variable() ret = ctx.get_next_variable() - counter_var = ctx.get_basic_block().add_instruction("store", start) + counter_var = ctx.get_basic_block().append_inst("store", start) symbols[sym.value] = counter_var - ctx.get_basic_block().add_instruction_no_return("jmp", cond_block.label) + ctx.get_basic_block().append_inst_no_ret("jmp", cond_block.label) - ret = cond_block.add_instruction( + ret = cond_block.append_inst( "phi", entry_block.label, counter_var, increment_block.label, counter_inc_var ) symbols[sym.value] = ret - xor_ret = cond_block.add_instruction("xor", ret, end) - cont_ret = cond_block.add_instruction("iszero", xor_ret) + xor_ret = cond_block.append_inst("xor", ret, end) + cont_ret = cond_block.append_inst("iszero", xor_ret) ctx.append_basic_block(cond_block) # Do a dry run to get the symbols needing phi nodes @@ -831,45 +824,45 @@ def emit_body_block(): body_end = ctx.get_basic_block() if not body_end.is_terminated: - body_end.add_instruction_no_return("jmp", jump_up_block.label) + body_end.append_inst_no_ret("jmp", jump_up_block.label) - jump_up_block.add_instruction_no_return("jmp", increment_block.label) + jump_up_block.append_inst_no_ret("jmp", increment_block.label) ctx.append_basic_block(jump_up_block) - increment_block.add_instruction_no_return(IRInstruction("add", ret, IRLiteral(1))) + increment_block.append_inst_no_ret(IRInstruction("add", ret, IRLiteral(1))) increment_block.insert_instruction[-1].output = counter_inc_var - increment_block.add_instruction_no_return("jmp", cond_block.label) + increment_block.append_inst_no_ret("jmp", cond_block.label) ctx.append_basic_block(increment_block) ctx.append_basic_block(exit_block) - cond_block.add_instruction("jnz", cont_ret, exit_block.label, body_block.label) + cond_block.append_inst("jnz", cont_ret, exit_block.label, body_block.label) elif ir.value == "break": assert _break_target is not None, "Break with no break target" - ctx.get_basic_block().add_instruction("jmp", _break_target.label) + ctx.get_basic_block().append_inst("jmp", _break_target.label) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) elif ir.value == "continue": assert _continue_target is not None, "Continue with no contrinue target" - ctx.get_basic_block().add_instruction("jmp", _continue_target.label) + ctx.get_basic_block().append_inst("jmp", _continue_target.label) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) elif ir.value == "gas": - return ctx.get_basic_block().add_instruction("gas") + return ctx.get_basic_block().append_inst("gas") elif ir.value == "returndatasize": - return ctx.get_basic_block().add_instruction("returndatasize") + return ctx.get_basic_block().append_inst("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) - new_var = ctx.get_basic_block().add_instruction("returndatacopy", arg_1, size) + new_var = ctx.get_basic_block().append_inst("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) - ctx.get_basic_block().add_instruction_no_return("selfdestruct", arg_0) + ctx.get_basic_block().append_inst_no_ret("selfdestruct", arg_0) elif isinstance(ir.value, str) and ir.value.startswith("log"): args = reversed( [ @@ -877,7 +870,7 @@ def emit_body_block(): for arg in ir.args ] ) - ctx.get_basic_block().add_instruction_no_return(ir.value, *args) + ctx.get_basic_block().append_inst_no_ret(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: @@ -904,7 +897,7 @@ def _convert_ir_opcode( inst_args.append( _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) ) - ctx.get_basic_block().add_instruction(opcode, *inst_args) + ctx.get_basic_block().append_inst(opcode, *inst_args) def _data_ofst_of(sym, ofst, height_): diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 02ca63efa4..98d6146e8c 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -1,5 +1,5 @@ from vyper.exceptions import CompilerPanic -from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRVariable +from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass From f03986f6dc343c69862ed38bc5142d3655cdeac8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Dec 2023 16:21:54 +0200 Subject: [PATCH 10/60] "naming things" refactor Refactored append instructions into a single append_instruction() method with named arguments for returning or not a variable --- vyper/venom/analysis.py | 4 +- vyper/venom/basicblock.py | 51 +++++-- vyper/venom/ir_node_to_venom.py | 212 +++++++++++++++------------- vyper/venom/passes/normalization.py | 2 +- 4 files changed, 158 insertions(+), 111 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 5980e21028..1a82ca85d0 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -2,7 +2,7 @@ from vyper.utils import OrderedSet from vyper.venom.basicblock import ( BB_TERMINATORS, - CFG_ALTERING_OPS, + CFG_ALTERING_INSTRUCTIONS, IRBasicBlock, IRInstruction, IRVariable, @@ -55,7 +55,7 @@ def calculate_cfg(ctx: IRFunction) -> None: assert last_inst.opcode in BB_TERMINATORS, f"Last instruction should be a terminator {bb}" for inst in bb.instructions: - if inst.opcode in CFG_ALTERING_OPS: + if inst.opcode in CFG_ALTERING_INSTRUCTIONS: ops = inst.get_label_operands() for op in ops: ctx.get_basic_block(op.value).add_cfg_in(bb) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 8b7089c1c7..d91aae774e 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -31,8 +31,25 @@ ] ) -CFG_ALTERING_OPS = frozenset(["jmp", "jnz", "call", "staticcall", "invoke", "deploy"]) +NO_OUTPUT_INSTRUCTIONS = frozenset( + [ + "mstore", + "sstore", + "dstore", + "calldatacopy", + "codecopy", + "return", + "ret", + "revert", + "assert", + "selfdestruct", + "stop", + "invalid", + "log", + ] +) +CFG_ALTERING_INSTRUCTIONS = frozenset(["jmp", "jnz", "call", "staticcall", "invoke", "deploy"]) if TYPE_CHECKING: from vyper.venom.function import IRFunction @@ -243,8 +260,8 @@ class IRBasicBlock: %2 = mul %1, 2 is represented as: bb = IRBasicBlock("bb", function) - r1 = bb.append_inst("add", "%0", "1") - r2 = bb.append_inst("mul", r1, "2") + r1 = bb.append_instruction("add", "%0", "1") + r2 = bb.append_instruction("mul", r1, "2") The label of a basic block is used to refer to it from other basic blocks in order to branch to it. @@ -296,13 +313,29 @@ def remove_cfg_out(self, bb: "IRBasicBlock") -> None: def is_reachable(self) -> bool: return len(self.cfg_in) > 0 - def append_inst_no_ret(self, opcode: str, *args) -> None: - inst = IRInstruction(opcode, list(args)) - inst.parent = self - self.instructions.append(inst) + def append_instruction( + self, opcode: str, *args: IROperand, output: bool = None, force: bool = False + ) -> Optional[IRVariable]: + """ + Append an instruction to basic block. If output is True, the instruction + produces an output value, which can be used as an operand in other + instructions. If output is False, the instruction does not produce an + output value, and the return value is None. If output is None, the function + will automatically determine if the instruction produces an output value based + on the opcode. + + The method will assert the output value is appropriate for the opcode, unless + force is True. This is useful for the case that you want to append an instruction + and performa manual manipulations later. + """ + if output is None: + output = opcode not in NO_OUTPUT_INSTRUCTIONS + + assert force or not ( + output is True and opcode in NO_OUTPUT_INSTRUCTIONS + ), f"output=={output} without appropriate opcode '{opcode}'" - def append_inst(self, opcode: str, *args) -> IRVariable: - ret = self.parent.get_next_variable() + ret = self.parent.get_next_variable() if output else None inst = IRInstruction(opcode, list(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 47d9eb1269..a7e8e309e2 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -72,7 +72,7 @@ "balance", ] -SymbolTable = dict[str, IROperand] +SymbolTable = dict[str, Optional[IROperand]] def _get_symbols_common(a: dict, b: dict) -> dict: @@ -93,11 +93,11 @@ def convert_ir_basicblock(ir: IRnode) -> IRFunction: for i, bb in enumerate(global_function.basic_blocks): if not bb.is_terminated and i < len(global_function.basic_blocks) - 1: - bb.append_inst("jmp", global_function.basic_blocks[i + 1].label) + bb.append_instruction("jmp", global_function.basic_blocks[i + 1].label) revert_bb = IRBasicBlock(IRLabel("__revert"), global_function) revert_bb = global_function.append_basic_block(revert_bb) - revert_bb.append_inst_no_ret("revert", IRLiteral(0), IRLiteral(0)) + revert_bb.append_instruction("revert", IRLiteral(0), IRLiteral(0), output=False) return global_function @@ -109,16 +109,16 @@ def _convert_binary_op( variables: OrderedSet, allocated_variables: dict[str, IRVariable], swap: bool = False, -) -> IRVariable: +) -> 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) - return ctx.get_basic_block().append_inst(ir.value, arg_1, arg_0) + return ctx.get_basic_block().append_instruction(str(ir.value), arg_1, arg_0) def _append_jmp(ctx: IRFunction, label: IRLabel) -> None: - ctx.get_basic_block().append_inst_no_ret("jmp", label) + ctx.get_basic_block().append_instruction("jmp", label, output=False) label = ctx.get_next_label() bb = IRBasicBlock(label, ctx) @@ -159,7 +159,7 @@ def _handle_self_call( ) if arg.location and arg.location.load_op == "calldataload": bb = ctx.get_basic_block() - ret = bb.append_inst(arg.location.load_op, ret) + ret = bb.append_instruction(arg.location.load_op, ret) ret_args.append(ret) if return_buf.is_literal: @@ -168,11 +168,11 @@ def _handle_self_call( bb = ctx.get_basic_block() do_ret = func_t.return_type is not None if do_ret: - invoke_ret = bb.append_inst("invoke", *ret_args) + invoke_ret = bb.append_instruction("invoke", *ret_args) allocated_variables["return_buffer"] = invoke_ret # type: ignore return invoke_ret else: - bb.append_inst_no_ret("invoke", *ret_args) + bb.append_instruction("invoke", *ret_args, output=False) return None @@ -186,17 +186,17 @@ def _handle_internal_func( old_ir_mempos += 64 for arg in func_t.arguments: - symbols[f"&{old_ir_mempos}"] = bb.append_inst("param") + symbols[f"&{old_ir_mempos}"] = bb.append_instruction("param") 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_inst("param") + symbols["return_buffer"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_buffer" # return address - symbols["return_pc"] = bb.append_inst("param") + symbols["return_pc"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_pc" return ir.args[0].args[2] @@ -212,7 +212,7 @@ def _convert_ir_simple_node( args = [ _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args ] - return ctx.get_basic_block().append_inst(ir.value, *args) # type: ignore + return ctx.get_basic_block().append_instruction(ir.value, *args) # type: ignore _break_target: Optional[IRBasicBlock] = None @@ -233,20 +233,20 @@ def _get_variable_from_address( def _append_return_for_stack_operand( bb: IRBasicBlock, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable -) -> IRInstruction: +) -> None: if isinstance(ret_ir, IRLiteral): sym = symbols.get(f"&{ret_ir.value}", None) - new_var = bb.append_inst("alloca", IRLiteral(32), ret_ir) - bb.append_inst_no_ret("mstore", sym, new_var) # type: ignore + new_var = bb.append_instruction("alloca", IRLiteral(32), ret_ir) + bb.append_instruction("mstore", sym, new_var, output=False) # type: ignore else: sym = symbols.get(ret_ir.value, None) if sym is None: # FIXME: needs real allocations - new_var = bb.append_inst("alloca", IRLiteral(32), IRLiteral(0)) - bb.append_inst_no_ret("mstore", ret_ir, new_var) # type: ignore + new_var = bb.append_instruction("alloca", IRLiteral(32), IRLiteral(0)) + bb.append_instruction("mstore", ret_ir, new_var, output=False) # type: ignore else: new_var = ret_ir - bb.append_inst_no_ret("return", last_ir, new_var) # type: ignore + bb.append_instruction("return", last_ir, new_var, output=False) # type: ignore def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): @@ -270,7 +270,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ir.value = INVERSE_MAPPED_IR_INSTRUCTIONS[ir.value] new_var = _convert_binary_op(ctx, ir, symbols, variables, allocated_variables) ir.value = org_value - return ctx.get_basic_block().append_inst("iszero", new_var) + 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) @@ -286,8 +286,8 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): runtimeLabel = ctx.get_next_label() - ctx.get_basic_block().append_inst_no_ret( - "deploy", IRLiteral(memsize), runtimeLabel, IRLiteral(padding) + ctx.get_basic_block().append_instruction( + "deploy", IRLiteral(memsize), runtimeLabel, IRLiteral(padding), output=False ) bb = IRBasicBlock(runtimeLabel, ctx) @@ -364,10 +364,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if ir.value == "call": args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] - return bb.append_inst(ir.value, *args) + return bb.append_instruction(ir.value, *args) else: args = [retSize, retOffset, argsSize, argsOffsetVar, address, gas] - return bb.append_inst(ir.value, *args) + return bb.append_instruction(ir.value, *args) elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() @@ -387,7 +387,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, 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_inst( + else_ret_val = ctx.get_basic_block().append_instruction( "store", IRLiteral(else_ret_val.value) ) after_else_syms = else_syms.copy() @@ -400,9 +400,13 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ctx, ir.args[1], symbols, variables, allocated_variables ) if isinstance(then_ret_val, IRLiteral): - then_ret_val = ctx.get_basic_block().append_inst("store", IRLiteral(then_ret_val.value)) + then_ret_val = ctx.get_basic_block().append_instruction( + "store", IRLiteral(then_ret_val.value) + ) - current_bb.append_inst_no_ret("jnz", cont_ret, then_block.label, else_block.label) + current_bb.append_instruction( + "jnz", cont_ret, then_block.label, else_block.label, output=False + ) after_then_syms = symbols.copy() @@ -413,13 +417,13 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if_ret = None if then_ret_val is not None and else_ret_val is not None: - if_ret = bb.append_inst( + if_ret = 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_inst("phi", then_block.label, val[0], else_block.label, val[1]) + ret = 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: @@ -428,10 +432,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): allocated_variables[idx] = ret # type: ignore if not else_block.is_terminated: - else_block.append_inst_no_ret("jmp", bb.label) + else_block.append_instruction("jmp", bb.label, output=False) if not then_block.is_terminated: - then_block.append_inst_no_ret("jmp", bb.label) + then_block.append_instruction("jmp", bb.label, output=False) return if_ret @@ -445,7 +449,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): sym = ir.args[0] if isinstance(ret, IRLiteral): - new_var = ctx.get_basic_block().append_inst("store", ret) # type: ignore + 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 @@ -457,12 +461,12 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "jump": arg_1 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - ctx.get_basic_block().append_inst_no_ret("jmp", arg_1) + ctx.get_basic_block().append_instruction("jmp", arg_1, output=False) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - new_var = ctx.get_basic_block().append_inst("store", arg_1) # type: ignore + new_var = ctx.get_basic_block().append_instruction("store", arg_1) # type: ignore symbols[sym.value] = new_var elif ir.value == "calldatacopy": @@ -479,14 +483,14 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb = ctx.get_basic_block() if var is not None: if allocated_variables.get(var.name, None) is None: - new_v = bb.append_inst( + new_v = bb.append_instruction( "alloca", IRLiteral(var.size), IRLiteral(var.pos) # type: ignore ) allocated_variables[var.name] = new_v # type: ignore - bb.append_inst_no_ret("calldatacopy", size, arg_1, new_v) # type: ignore + bb.append_instruction("calldatacopy", size, arg_1, new_v, output=False) # type: ignore symbols[f"&{var.pos}"] = new_v # type: ignore else: - bb.append_inst_no_ret("calldatacopy", size, arg_1, new_v) # type: ignore + bb.append_instruction("calldatacopy", size, arg_1, new_v, output=False) # type: ignore return new_v elif ir.value == "codecopy": @@ -494,7 +498,9 @@ def _convert_ir_basicblock(ctx, ir, 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) - ctx.get_basic_block().append_inst_no_ret("codecopy", size, arg_1, arg_0) # type: ignore + ctx.get_basic_block().append_instruction( + "codecopy", size, arg_1, arg_0, output=False + ) # type: ignore elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) elif ir.value == "data": @@ -512,12 +518,12 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) current_bb = ctx.get_basic_block() - current_bb.append_inst_no_ret("assert", arg_0) + current_bb.append_instruction("assert", arg_0, output=False) elif ir.value == "label": label = IRLabel(ir.args[0].value, True) bb = ctx.get_basic_block() if not bb.is_terminated: - bb.append_inst_no_ret("jmp", label) + bb.append_instruction("jmp", label, output=False) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) @@ -530,10 +536,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb = ctx.get_basic_block() if func_t.name == "__init__": label = IRLabel(ir.args[0].value, True) - bb.append_inst_no_ret("jmp", label) + bb.append_instruction("jmp", label, output=False) return None if func_t.return_type is None: - bb.append_inst_no_ret("stop") + bb.append_instruction("stop", output=False) return None else: last_ir = None @@ -568,32 +574,34 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if var.size and int(var.size) > 32: offset = int(ret_ir.value) - var.pos # type: ignore if offset > 0: - ptr_var = bb.append_inst("add", IRLiteral(var.pos), IRLiteral(offset)) + ptr_var = bb.append_instruction( + "add", IRLiteral(var.pos), IRLiteral(offset) + ) else: ptr_var = allocated_var - bb.append_inst_no_ret("return", last_ir, ptr_var) + bb.append_instruction("return", last_ir, ptr_var, output=False) else: _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_inst_no_ret("return", last_ir, ret_ir) + bb.append_instruction("return", last_ir, ret_ir, output=False) else: if func_t.return_type.memory_bytes_required > 32: - new_var = bb.append_inst("alloca", IRLiteral(32), ret_ir) - bb.append_inst_no_ret("mstore", sym, new_var) - bb.append_inst_no_ret("return", last_ir, new_var) + new_var = bb.append_instruction("alloca", IRLiteral(32), ret_ir) + bb.append_instruction("mstore", sym, new_var, output=False) + bb.append_instruction("return", last_ir, new_var, output=False) else: - bb.append_inst_no_ret("return", last_ir, ret_ir) + bb.append_instruction("return", last_ir, ret_ir, output=False) else: if last_ir and int(last_ir.value) > 32: - bb.append_inst_no_ret("return", last_ir, ret_ir) + bb.append_instruction("return", last_ir, ret_ir, output=False) else: ret_buf = IRLiteral(128) # TODO: need allocator - new_var = bb.append_inst("alloca", IRLiteral(32), ret_buf) - bb.append_inst_no_ret("mstore", ret_ir, new_var) - bb.append_inst_no_ret("return", last_ir, new_var) + new_var = bb.append_instruction("alloca", IRLiteral(32), ret_buf) + bb.append_instruction("mstore", ret_ir, new_var, output=False) + bb.append_instruction("return", last_ir, new_var, output=False) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) @@ -601,28 +609,34 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if func_t.is_internal: assert ir.args[1].value == "return_pc", "return_pc not found" if func_t.return_type is None: - bb.append_inst_no_ret("ret", symbols["return_pc"]) + bb.append_instruction("ret", symbols["return_pc"], output=False) else: if func_t.return_type.memory_bytes_required > 32: - bb.append_inst_no_ret("ret", symbols["return_buffer"], symbols["return_pc"]) + bb.append_instruction( + "ret", symbols["return_buffer"], symbols["return_pc"], output=False + ) else: - ret_by_value = bb.append_inst("mload", symbols["return_buffer"]) - bb.append_inst_no_ret("ret", ret_by_value, symbols["return_pc"]) + ret_by_value = bb.append_instruction("mload", symbols["return_buffer"]) + bb.append_instruction("ret", ret_by_value, symbols["return_pc"], output=False) 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) - ctx.get_basic_block().append_inst_no_ret("revert", arg_1, arg_0) + ctx.get_basic_block().append_instruction("revert", arg_1, arg_0, output=False) elif ir.value == "dload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) bb = ctx.get_basic_block() - src = bb.append_inst("add", arg_0, IRLabel("code_end")) - - bb.append_inst_no_ret( - "dloadbytes", IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE) + src = bb.append_instruction("add", arg_0, IRLabel("code_end")) + + bb.append_instruction( + "dloadbytes", + IRLiteral(32), + src, + IRLiteral(MemoryPositions.FREE_VAR_SPACE), + output=False, ) - return bb.append_inst("mload", IRLiteral(MemoryPositions.FREE_VAR_SPACE)) + return bb.append_instruction("mload", IRLiteral(MemoryPositions.FREE_VAR_SPACE)) elif ir.value == "dloadbytes": dst = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) @@ -631,8 +645,8 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ) len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) bb = ctx.get_basic_block() - src = bb.append_inst("add", src_offset, IRLabel("code_end")) - bb.append_inst_no_ret("dloadbytes", len_, src, dst) + src = bb.append_instruction("add", src_offset, IRLabel("code_end")) + bb.append_instruction("dloadbytes", len_, src, dst, output=False) return None elif ir.value == "mload": @@ -644,22 +658,22 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if var 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_inst( + allocated_variables[var.name] = bb.append_instruction( "alloca", IRLiteral(var.size), IRLiteral(var.pos) ) offset = int(sym_ir.value) - var.pos if offset > 0: - ptr_var = bb.append_inst("add", IRLiteral(var.pos), IRLiteral(offset)) + ptr_var = bb.append_instruction("add", IRLiteral(var.pos), IRLiteral(offset)) else: ptr_var = allocated_variables[var.name] - return bb.append_inst("mload", ptr_var) + 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 = bb.append_inst("store", sym_ir) + new_var = bb.append_instruction("store", sym_ir) symbols[f"&{sym_ir.value}"] = new_var if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = new_var @@ -674,9 +688,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if sym_ir.is_literal: new_var = symbols.get(f"&{sym_ir.value}", None) if new_var is not None: - return bb.append_inst("mload", new_var) + return bb.append_instruction("mload", new_var) else: - return bb.append_inst("mload", IRLiteral(sym_ir.value)) + return bb.append_instruction("mload", IRLiteral(sym_ir.value)) else: new_var = _convert_ir_basicblock( ctx, sym_ir, symbols, variables, allocated_variables @@ -689,7 +703,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): # if sym_ir.is_self_call: return new_var - return bb.append_inst("mload", new_var) + return bb.append_instruction("mload", new_var) elif ir.value == "mstore": sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) @@ -704,38 +718,38 @@ def _convert_ir_basicblock(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_inst( + allocated_variables[var.name] = bb.append_instruction( "alloca", IRLiteral(var.size), IRLiteral(var.pos) ) offset = int(sym_ir.value) - var.pos if offset > 0: - ptr_var = bb.append_inst("add", IRLiteral(var.pos), IRLiteral(offset)) + ptr_var = bb.append_instruction("add", IRLiteral(var.pos), IRLiteral(offset)) else: ptr_var = allocated_variables[var.name] - bb.append_inst_no_ret("mstore", arg_1, ptr_var) + bb.append_instruction("mstore", arg_1, ptr_var, output=False) else: if isinstance(sym_ir, IRLiteral): - new_var = bb.append_inst("store", arg_1) + new_var = bb.append_instruction("store", arg_1) 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: if not isinstance(sym_ir, IRLiteral): - bb.append_inst_no_ret("mstore", arg_1, sym_ir) + bb.append_instruction("mstore", arg_1, sym_ir, output=False) return None sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: - bb.append_inst_no_ret("mstore", arg_1, sym_ir) + bb.append_instruction("mstore", arg_1, sym_ir, output=False) 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_inst_no_ret("mstore", arg_1, sym) + bb.append_instruction("mstore", arg_1, sym, output=False) return None else: symbols[sym_ir.value] = arg_1 @@ -743,11 +757,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value in ["sload", "iload"]: arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - return ctx.get_basic_block().append_inst(ir.value, arg_0) + 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) - ctx.get_basic_block().append_inst_no_ret(ir.value, arg_1, arg_0) + ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0, output=False) elif ir.value == "unique_symbol": sym = ir.args[0] new_var = ctx.get_next_variable() @@ -787,17 +801,17 @@ def emit_body_block(): counter_inc_var = ctx.get_next_variable() ret = ctx.get_next_variable() - counter_var = ctx.get_basic_block().append_inst("store", start) + counter_var = ctx.get_basic_block().append_instruction("store", start) symbols[sym.value] = counter_var - ctx.get_basic_block().append_inst_no_ret("jmp", cond_block.label) + ctx.get_basic_block().append_instruction("jmp", cond_block.label, output=False) - ret = cond_block.append_inst( + 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_inst("xor", ret, end) - cont_ret = cond_block.append_inst("iszero", xor_ret) + xor_ret = cond_block.append_instruction("xor", ret, end) + 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 @@ -824,45 +838,45 @@ def emit_body_block(): body_end = ctx.get_basic_block() if not body_end.is_terminated: - body_end.append_inst_no_ret("jmp", jump_up_block.label) + body_end.append_instruction("jmp", jump_up_block.label, output=False) - jump_up_block.append_inst_no_ret("jmp", increment_block.label) + jump_up_block.append_instruction("jmp", increment_block.label, output=False) ctx.append_basic_block(jump_up_block) - increment_block.append_inst_no_ret(IRInstruction("add", ret, IRLiteral(1))) + increment_block.append_instruction(IRInstruction("add", ret, IRLiteral(1)), output=False) increment_block.insert_instruction[-1].output = counter_inc_var - increment_block.append_inst_no_ret("jmp", cond_block.label) + increment_block.append_instruction("jmp", cond_block.label, output=False) ctx.append_basic_block(increment_block) ctx.append_basic_block(exit_block) - cond_block.append_inst("jnz", cont_ret, exit_block.label, body_block.label) + cond_block.append_instruction("jnz", cont_ret, exit_block.label, body_block.label) elif ir.value == "break": assert _break_target is not None, "Break with no break target" - ctx.get_basic_block().append_inst("jmp", _break_target.label) + ctx.get_basic_block().append_instruction("jmp", _break_target.label) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) elif ir.value == "continue": assert _continue_target is not None, "Continue with no contrinue target" - ctx.get_basic_block().append_inst("jmp", _continue_target.label) + 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_inst("gas") + return ctx.get_basic_block().append_instruction("gas") elif ir.value == "returndatasize": - return ctx.get_basic_block().append_inst("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 = _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) - new_var = ctx.get_basic_block().append_inst("returndatacopy", arg_1, size) + 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) - ctx.get_basic_block().append_inst_no_ret("selfdestruct", arg_0) + ctx.get_basic_block().append_instruction("selfdestruct", arg_0, output=False) elif isinstance(ir.value, str) and ir.value.startswith("log"): args = reversed( [ @@ -870,7 +884,7 @@ def emit_body_block(): for arg in ir.args ] ) - ctx.get_basic_block().append_inst_no_ret(ir.value, *args) + ctx.get_basic_block().append_instruction(ir.value, *args, output=False) 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: @@ -897,7 +911,7 @@ def _convert_ir_opcode( inst_args.append( _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) ) - ctx.get_basic_block().append_inst(opcode, *inst_args) + ctx.get_basic_block().append_instruction(opcode, *inst_args) def _data_ofst_of(sym, ofst, height_): diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 98d6146e8c..9aeaecd476 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -61,7 +61,7 @@ def _insert_split_basicblock(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> IRB source = in_bb.label.value target = bb.label.value split_bb = IRBasicBlock(IRLabel(f"{target}_split_{source}"), self.ctx) - split_bb.append_inst_no_ret("jmp", bb.label) + split_bb.append_instruction("jmp", bb.label, output=False) self.ctx.append_basic_block(split_bb) # Rewire the CFG From 1ae9bc16d3eb8141b88a8c8887192e52c56ce57f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Dec 2023 18:13:46 +0200 Subject: [PATCH 11/60] Refactor multi-entry block test cases for venom --- .../compiler/venom/test_multi_entry_block.py | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/tests/unit/compiler/venom/test_multi_entry_block.py b/tests/unit/compiler/venom/test_multi_entry_block.py index bb57fa1065..a2b9d9e215 100644 --- a/tests/unit/compiler/venom/test_multi_entry_block.py +++ b/tests/unit/compiler/venom/test_multi_entry_block.py @@ -11,25 +11,26 @@ def test_multi_entry_block_1(): target_label = IRLabel("target") block_1_label = IRLabel("block_1", ctx) - op = ctx.append_instruction("store", [IRLiteral(10)]) - acc = ctx.append_instruction("add", [op, op]) - ctx.append_instruction("jnz", [acc, finish_label, block_1_label], False) + bb = ctx.get_basic_block() + op = bb.append_instruction("store", IRLiteral(10)) + acc = bb.append_instruction("add", op, op) + bb.append_instruction("jnz", acc, finish_label, block_1_label, output=False) block_1 = IRBasicBlock(block_1_label, ctx) ctx.append_basic_block(block_1) - acc = ctx.append_instruction("add", [acc, op]) - op = ctx.append_instruction("store", [IRLiteral(10)]) - ctx.append_instruction("mstore", [acc, op], False) - ctx.append_instruction("jnz", [acc, finish_label, target_label], False) + acc = block_1.append_instruction("add", acc, op) + op = block_1.append_instruction("store", IRLiteral(10)) + block_1.append_instruction("mstore", acc, op, output=False) + block_1.append_instruction("jnz", acc, finish_label, target_label, output=False) target_bb = IRBasicBlock(target_label, ctx) ctx.append_basic_block(target_bb) - ctx.append_instruction("mul", [acc, acc]) - ctx.append_instruction("jmp", [finish_label], False) + target_bb.append_instruction("mul", acc, acc) + target_bb.append_instruction("jmp", finish_label, output=False) finish_bb = IRBasicBlock(finish_label, ctx) ctx.append_basic_block(finish_bb) - ctx.append_instruction("stop", [], False) + finish_bb.append_instruction("stop", output=False) calculate_cfg(ctx) assert not ctx.normalized, "CFG should not be normalized" @@ -54,33 +55,34 @@ def test_multi_entry_block_2(): block_1_label = IRLabel("block_1", ctx) block_2_label = IRLabel("block_2", ctx) - op = ctx.append_instruction("store", [IRLiteral(10)]) - acc = ctx.append_instruction("add", [op, op]) - ctx.append_instruction("jnz", [acc, finish_label, block_1_label], False) + bb = ctx.get_basic_block() + op = bb.append_instruction("store", IRLiteral(10)) + acc = bb.append_instruction("add", op, op) + bb.append_instruction("jnz", acc, finish_label, block_1_label, output=False) block_1 = IRBasicBlock(block_1_label, ctx) ctx.append_basic_block(block_1) - acc = ctx.append_instruction("add", [acc, op]) - op = ctx.append_instruction("store", [IRLiteral(10)]) - ctx.append_instruction("mstore", [acc, op], False) - ctx.append_instruction("jnz", [acc, target_label, finish_label], False) + acc = block_1.append_instruction("add", acc, op) + op = block_1.append_instruction("store", IRLiteral(10)) + block_1.append_instruction("mstore", acc, op, output=False) + block_1.append_instruction("jnz", acc, target_label, finish_label, output=False) block_2 = IRBasicBlock(block_2_label, ctx) ctx.append_basic_block(block_2) - acc = ctx.append_instruction("add", [acc, op]) - op = ctx.append_instruction("store", [IRLiteral(10)]) - ctx.append_instruction("mstore", [acc, op], False) - # switch the order of the labels, for fun - ctx.append_instruction("jnz", [acc, finish_label, target_label], False) + acc = block_2.append_instruction("add", acc, op) + op = block_2.append_instruction("store", IRLiteral(10)) + block_2.append_instruction("mstore", acc, op, output=False) + # switch the order of the labels, for fun and profit + block_2.append_instruction("jnz", acc, finish_label, target_label, output=False) target_bb = IRBasicBlock(target_label, ctx) ctx.append_basic_block(target_bb) - ctx.append_instruction("mul", [acc, acc]) - ctx.append_instruction("jmp", [finish_label], False) + target_bb.append_instruction("mul", acc, acc) + target_bb.append_instruction("jmp", finish_label, output=False) finish_bb = IRBasicBlock(finish_label, ctx) ctx.append_basic_block(finish_bb) - ctx.append_instruction("stop", [], False) + finish_bb.append_instruction("stop", output=False) calculate_cfg(ctx) assert not ctx.normalized, "CFG should not be normalized" From 020c1053d669398c71fee7a0dbfa5fcf554e31e4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Dec 2023 18:25:52 +0200 Subject: [PATCH 12/60] Refactor test_duplicate_operands --- tests/unit/compiler/venom/test_duplicate_operands.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit/compiler/venom/test_duplicate_operands.py b/tests/unit/compiler/venom/test_duplicate_operands.py index 505f01e31b..30e17cac44 100644 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ b/tests/unit/compiler/venom/test_duplicate_operands.py @@ -17,11 +17,11 @@ def test_duplicate_operands(): Should compile to: [PUSH1, 10, DUP1, DUP1, DUP1, ADD, MUL, STOP] """ ctx = IRFunction() - - op = ctx.append_instruction("store", [IRLiteral(10)]) - sum = ctx.append_instruction("add", [op, op]) - ctx.append_instruction("mul", [sum, op]) - ctx.append_instruction("stop", [], False) + bb = ctx.get_basic_block() + op = bb.append_instruction("store", IRLiteral(10)) + sum = bb.append_instruction("add", op, op) + bb.append_instruction("mul", sum, op) + bb.append_instruction("stop", output=False) asm = generate_assembly_experimental(ctx, OptimizationLevel.CODESIZE) From 7555caf796dcb02fd9c7ca313358fc76cb8d61cd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Dec 2023 18:58:52 +0200 Subject: [PATCH 13/60] Make Venom log instruction Until now log instructions where pass through. Now we have a log instruction in venom that takes the first argument to be the topic count (in shape of doing table jmp etc in the future -essentialy have instructions that require manipulation of operants before emit) --- vyper/venom/ir_node_to_venom.py | 4 +++- vyper/venom/venom_to_assembly.py | 11 ++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a7e8e309e2..30d8cfebcf 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -884,7 +884,9 @@ def emit_body_block(): for arg in ir.args ] ) - ctx.get_basic_block().append_instruction(ir.value, *args, output=False) + topic_count = int(ir.value[3:]) + assert topic_count >= 0 and topic_count <= 4, "invalid topic count" + ctx.get_basic_block().append_instruction("log", IRLiteral(topic_count), *args, output=False) 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 f6ec45440a..8760e9aa63 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -62,11 +62,6 @@ "lt", "slt", "sgt", - "log0", - "log1", - "log2", - "log3", - "log4", ] ) @@ -274,6 +269,10 @@ def _generate_evm_for_instruction( operands = [] elif opcode == "istore": operands = inst.operands[0:1] + elif opcode == "log": + log_topic_count = inst.operands[0].value + assert log_topic_count in [0, 1, 2, 3, 4], "Invalid topic count" + operands = inst.operands[1:] else: operands = inst.operands @@ -417,6 +416,8 @@ def _generate_evm_for_instruction( elif opcode == "istore": loc = inst.operands[1].value assembly.extend(["_OFST", "_mem_deploy_end", loc, "MSTORE"]) + elif opcode == "log": + assembly.extend([f"LOG{log_topic_count}"]) else: raise Exception(f"Unknown opcode: {opcode}") From ad108321dfd277444cd0822bf4eb8e5ae285b7c2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Dec 2023 19:21:50 +0200 Subject: [PATCH 14/60] Refactor-out instruction output Remove output argument append_instruction() in IRBasicBlock now the method decides by itself if there is an output value --- vyper/venom/basicblock.py | 32 +++------ vyper/venom/ir_node_to_venom.py | 102 +++++++++++++--------------- vyper/venom/passes/normalization.py | 2 +- 3 files changed, 59 insertions(+), 77 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index d91aae774e..34fa0334b2 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -33,9 +33,12 @@ NO_OUTPUT_INSTRUCTIONS = frozenset( [ + "deploy", "mstore", "sstore", "dstore", + "istore", + "dloadbytes", "calldatacopy", "codecopy", "return", @@ -45,6 +48,9 @@ "selfdestruct", "stop", "invalid", + "invoke", + "jmp", + "jnz", "log", ] ) @@ -313,29 +319,13 @@ def remove_cfg_out(self, bb: "IRBasicBlock") -> None: def is_reachable(self) -> bool: return len(self.cfg_in) > 0 - def append_instruction( - self, opcode: str, *args: IROperand, output: bool = None, force: bool = False - ) -> Optional[IRVariable]: - """ - Append an instruction to basic block. If output is True, the instruction - produces an output value, which can be used as an operand in other - instructions. If output is False, the instruction does not produce an - output value, and the return value is None. If output is None, the function - will automatically determine if the instruction produces an output value based - on the opcode. - - The method will assert the output value is appropriate for the opcode, unless - force is True. This is useful for the case that you want to append an instruction - and performa manual manipulations later. + def append_instruction(self, opcode: str, *args: IROperand) -> Optional[IRVariable]: """ - if output is None: - output = opcode not in NO_OUTPUT_INSTRUCTIONS + Append an instruction to the basic block - assert force or not ( - output is True and opcode in NO_OUTPUT_INSTRUCTIONS - ), f"output=={output} without appropriate opcode '{opcode}'" - - ret = self.parent.get_next_variable() if output else None + Returns the output variable if the instruction supports one + """ + ret = self.parent.get_next_variable() if opcode not in NO_OUTPUT_INSTRUCTIONS else None inst = IRInstruction(opcode, list(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 30d8cfebcf..b4c907c80e 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -97,7 +97,7 @@ def convert_ir_basicblock(ir: IRnode) -> IRFunction: revert_bb = IRBasicBlock(IRLabel("__revert"), global_function) revert_bb = global_function.append_basic_block(revert_bb) - revert_bb.append_instruction("revert", IRLiteral(0), IRLiteral(0), output=False) + revert_bb.append_instruction("revert", IRLiteral(0), IRLiteral(0)) return global_function @@ -118,7 +118,7 @@ def _convert_binary_op( def _append_jmp(ctx: IRFunction, label: IRLabel) -> None: - ctx.get_basic_block().append_instruction("jmp", label, output=False) + ctx.get_basic_block().append_instruction("jmp", label) label = ctx.get_next_label() bb = IRBasicBlock(label, ctx) @@ -168,11 +168,13 @@ def _handle_self_call( bb = ctx.get_basic_block() do_ret = func_t.return_type is not None if do_ret: - invoke_ret = bb.append_instruction("invoke", *ret_args) + invoke_ret = ctx.get_next_variable() + bb.append_instruction("invoke", *ret_args) + bb.instructions[-1].output = invoke_ret allocated_variables["return_buffer"] = invoke_ret # type: ignore return invoke_ret else: - bb.append_instruction("invoke", *ret_args, output=False) + bb.append_instruction("invoke", *ret_args) return None @@ -237,16 +239,16 @@ def _append_return_for_stack_operand( if isinstance(ret_ir, IRLiteral): sym = symbols.get(f"&{ret_ir.value}", None) new_var = bb.append_instruction("alloca", IRLiteral(32), ret_ir) - bb.append_instruction("mstore", sym, new_var, output=False) # type: ignore + 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", IRLiteral(32), IRLiteral(0)) - bb.append_instruction("mstore", ret_ir, new_var, output=False) # type: ignore + bb.append_instruction("mstore", ret_ir, new_var) # type: ignore else: new_var = ret_ir - bb.append_instruction("return", last_ir, new_var, output=False) # type: ignore + bb.append_instruction("return", last_ir, new_var) # type: ignore def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): @@ -287,7 +289,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): runtimeLabel = ctx.get_next_label() ctx.get_basic_block().append_instruction( - "deploy", IRLiteral(memsize), runtimeLabel, IRLiteral(padding), output=False + "deploy", IRLiteral(memsize), runtimeLabel, IRLiteral(padding) ) bb = IRBasicBlock(runtimeLabel, ctx) @@ -404,9 +406,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): "store", IRLiteral(then_ret_val.value) ) - current_bb.append_instruction( - "jnz", cont_ret, then_block.label, else_block.label, output=False - ) + current_bb.append_instruction("jnz", cont_ret, then_block.label, else_block.label) after_then_syms = symbols.copy() @@ -432,10 +432,10 @@ def _convert_ir_basicblock(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, output=False) + else_block.append_instruction("jmp", bb.label) if not then_block.is_terminated: - then_block.append_instruction("jmp", bb.label, output=False) + then_block.append_instruction("jmp", bb.label) return if_ret @@ -461,7 +461,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "jump": arg_1 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - ctx.get_basic_block().append_instruction("jmp", arg_1, output=False) + ctx.get_basic_block().append_instruction("jmp", arg_1) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] @@ -487,10 +487,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): "alloca", IRLiteral(var.size), IRLiteral(var.pos) # type: ignore ) allocated_variables[var.name] = new_v # type: ignore - bb.append_instruction("calldatacopy", size, arg_1, new_v, output=False) # 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, output=False) # type: ignore + bb.append_instruction("calldatacopy", size, arg_1, new_v) # type: ignore return new_v elif ir.value == "codecopy": @@ -498,9 +498,7 @@ def _convert_ir_basicblock(ctx, ir, 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) - ctx.get_basic_block().append_instruction( - "codecopy", size, arg_1, arg_0, output=False - ) # type: ignore + 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": @@ -518,12 +516,12 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) current_bb = ctx.get_basic_block() - current_bb.append_instruction("assert", arg_0, output=False) + current_bb.append_instruction("assert", arg_0) elif ir.value == "label": label = IRLabel(ir.args[0].value, True) bb = ctx.get_basic_block() if not bb.is_terminated: - bb.append_instruction("jmp", label, output=False) + 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) @@ -536,10 +534,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb = ctx.get_basic_block() if func_t.name == "__init__": label = IRLabel(ir.args[0].value, True) - bb.append_instruction("jmp", label, output=False) + bb.append_instruction("jmp", label) return None if func_t.return_type is None: - bb.append_instruction("stop", output=False) + bb.append_instruction("stop") return None else: last_ir = None @@ -579,29 +577,29 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ) else: ptr_var = allocated_var - bb.append_instruction("return", last_ir, ptr_var, output=False) + bb.append_instruction("return", last_ir, ptr_var) else: _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, output=False) + bb.append_instruction("return", last_ir, ret_ir) else: if func_t.return_type.memory_bytes_required > 32: new_var = bb.append_instruction("alloca", IRLiteral(32), ret_ir) - bb.append_instruction("mstore", sym, new_var, output=False) - bb.append_instruction("return", last_ir, new_var, output=False) + bb.append_instruction("mstore", sym, new_var) + bb.append_instruction("return", last_ir, new_var) else: - bb.append_instruction("return", last_ir, ret_ir, output=False) + 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, output=False) + bb.append_instruction("return", last_ir, ret_ir) else: ret_buf = IRLiteral(128) # TODO: need allocator new_var = bb.append_instruction("alloca", IRLiteral(32), ret_buf) - bb.append_instruction("mstore", ret_ir, new_var, output=False) - bb.append_instruction("return", last_ir, new_var, output=False) + bb.append_instruction("mstore", ret_ir, new_var) + bb.append_instruction("return", last_ir, new_var) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) @@ -609,20 +607,18 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if 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"], output=False) + 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"], output=False - ) + 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"], output=False) + 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) - ctx.get_basic_block().append_instruction("revert", arg_1, arg_0, output=False) + 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) @@ -630,11 +626,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): src = bb.append_instruction("add", arg_0, IRLabel("code_end")) bb.append_instruction( - "dloadbytes", - IRLiteral(32), - src, - IRLiteral(MemoryPositions.FREE_VAR_SPACE), - output=False, + "dloadbytes", IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE) ) return bb.append_instruction("mload", IRLiteral(MemoryPositions.FREE_VAR_SPACE)) @@ -646,7 +638,7 @@ def _convert_ir_basicblock(ctx, ir, 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, output=False) + bb.append_instruction("dloadbytes", len_, src, dst) return None elif ir.value == "mload": @@ -728,7 +720,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): else: ptr_var = allocated_variables[var.name] - bb.append_instruction("mstore", arg_1, ptr_var, output=False) + bb.append_instruction("mstore", arg_1, ptr_var) else: if isinstance(sym_ir, IRLiteral): new_var = bb.append_instruction("store", arg_1) @@ -738,18 +730,18 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return new_var else: if not isinstance(sym_ir, IRLiteral): - bb.append_instruction("mstore", arg_1, sym_ir, output=False) + 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, output=False) + 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, output=False) + bb.append_instruction("mstore", arg_1, sym) return None else: symbols[sym_ir.value] = arg_1 @@ -761,7 +753,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): 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) - ctx.get_basic_block().append_instruction(ir.value, arg_1, arg_0, output=False) + 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() @@ -803,7 +795,7 @@ def emit_body_block(): 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, output=False) + 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 @@ -838,15 +830,15 @@ def emit_body_block(): body_end = ctx.get_basic_block() if not body_end.is_terminated: - body_end.append_instruction("jmp", jump_up_block.label, output=False) + body_end.append_instruction("jmp", jump_up_block.label) - jump_up_block.append_instruction("jmp", increment_block.label, output=False) + jump_up_block.append_instruction("jmp", increment_block.label) ctx.append_basic_block(jump_up_block) - increment_block.append_instruction(IRInstruction("add", ret, IRLiteral(1)), output=False) + increment_block.append_instruction(IRInstruction("add", ret, IRLiteral(1))) increment_block.insert_instruction[-1].output = counter_inc_var - increment_block.append_instruction("jmp", cond_block.label, output=False) + increment_block.append_instruction("jmp", cond_block.label) ctx.append_basic_block(increment_block) ctx.append_basic_block(exit_block) @@ -876,7 +868,7 @@ def emit_body_block(): return new_var elif ir.value == "selfdestruct": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - ctx.get_basic_block().append_instruction("selfdestruct", arg_0, output=False) + ctx.get_basic_block().append_instruction("selfdestruct", arg_0) elif isinstance(ir.value, str) and ir.value.startswith("log"): args = reversed( [ @@ -886,7 +878,7 @@ def emit_body_block(): ) topic_count = int(ir.value[3:]) assert topic_count >= 0 and topic_count <= 4, "invalid topic count" - ctx.get_basic_block().append_instruction("log", IRLiteral(topic_count), *args, output=False) + ctx.get_basic_block().append_instruction("log", IRLiteral(topic_count), *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/passes/normalization.py b/vyper/venom/passes/normalization.py index 9aeaecd476..90dd60e881 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -61,7 +61,7 @@ def _insert_split_basicblock(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> IRB source = in_bb.label.value target = bb.label.value split_bb = IRBasicBlock(IRLabel(f"{target}_split_{source}"), self.ctx) - split_bb.append_instruction("jmp", bb.label, output=False) + split_bb.append_instruction("jmp", bb.label) self.ctx.append_basic_block(split_bb) # Rewire the CFG From 87ee7bbc652f6aaff649a2b6f0aeb454c998d402 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 9 Dec 2023 19:24:53 +0200 Subject: [PATCH 15/60] Update tests after output property removal --- .../compiler/venom/test_duplicate_operands.py | 2 +- .../compiler/venom/test_multi_entry_block.py | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/unit/compiler/venom/test_duplicate_operands.py b/tests/unit/compiler/venom/test_duplicate_operands.py index 30e17cac44..47e0919d34 100644 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ b/tests/unit/compiler/venom/test_duplicate_operands.py @@ -21,7 +21,7 @@ def test_duplicate_operands(): op = bb.append_instruction("store", IRLiteral(10)) sum = bb.append_instruction("add", op, op) bb.append_instruction("mul", sum, op) - bb.append_instruction("stop", output=False) + bb.append_instruction("stop") asm = generate_assembly_experimental(ctx, OptimizationLevel.CODESIZE) diff --git a/tests/unit/compiler/venom/test_multi_entry_block.py b/tests/unit/compiler/venom/test_multi_entry_block.py index a2b9d9e215..1ef6c5cab7 100644 --- a/tests/unit/compiler/venom/test_multi_entry_block.py +++ b/tests/unit/compiler/venom/test_multi_entry_block.py @@ -14,23 +14,23 @@ def test_multi_entry_block_1(): bb = ctx.get_basic_block() op = bb.append_instruction("store", IRLiteral(10)) acc = bb.append_instruction("add", op, op) - bb.append_instruction("jnz", acc, finish_label, block_1_label, output=False) + bb.append_instruction("jnz", acc, finish_label, block_1_label) block_1 = IRBasicBlock(block_1_label, ctx) ctx.append_basic_block(block_1) acc = block_1.append_instruction("add", acc, op) op = block_1.append_instruction("store", IRLiteral(10)) - block_1.append_instruction("mstore", acc, op, output=False) - block_1.append_instruction("jnz", acc, finish_label, target_label, output=False) + block_1.append_instruction("mstore", acc, op) + block_1.append_instruction("jnz", acc, finish_label, target_label) target_bb = IRBasicBlock(target_label, ctx) ctx.append_basic_block(target_bb) target_bb.append_instruction("mul", acc, acc) - target_bb.append_instruction("jmp", finish_label, output=False) + target_bb.append_instruction("jmp", finish_label) finish_bb = IRBasicBlock(finish_label, ctx) ctx.append_basic_block(finish_bb) - finish_bb.append_instruction("stop", output=False) + finish_bb.append_instruction("stop") calculate_cfg(ctx) assert not ctx.normalized, "CFG should not be normalized" @@ -58,31 +58,31 @@ def test_multi_entry_block_2(): bb = ctx.get_basic_block() op = bb.append_instruction("store", IRLiteral(10)) acc = bb.append_instruction("add", op, op) - bb.append_instruction("jnz", acc, finish_label, block_1_label, output=False) + bb.append_instruction("jnz", acc, finish_label, block_1_label) block_1 = IRBasicBlock(block_1_label, ctx) ctx.append_basic_block(block_1) acc = block_1.append_instruction("add", acc, op) op = block_1.append_instruction("store", IRLiteral(10)) - block_1.append_instruction("mstore", acc, op, output=False) - block_1.append_instruction("jnz", acc, target_label, finish_label, output=False) + block_1.append_instruction("mstore", acc, op) + block_1.append_instruction("jnz", acc, target_label, finish_label) block_2 = IRBasicBlock(block_2_label, ctx) ctx.append_basic_block(block_2) acc = block_2.append_instruction("add", acc, op) op = block_2.append_instruction("store", IRLiteral(10)) - block_2.append_instruction("mstore", acc, op, output=False) + block_2.append_instruction("mstore", acc, op) # switch the order of the labels, for fun and profit - block_2.append_instruction("jnz", acc, finish_label, target_label, output=False) + block_2.append_instruction("jnz", acc, finish_label, target_label) target_bb = IRBasicBlock(target_label, ctx) ctx.append_basic_block(target_bb) target_bb.append_instruction("mul", acc, acc) - target_bb.append_instruction("jmp", finish_label, output=False) + target_bb.append_instruction("jmp", finish_label) finish_bb = IRBasicBlock(finish_label, ctx) ctx.append_basic_block(finish_bb) - finish_bb.append_instruction("stop", output=False) + finish_bb.append_instruction("stop") calculate_cfg(ctx) assert not ctx.normalized, "CFG should not be normalized" From ff998ef5e3dcd7fff06cd4343d4beb389ec70380 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 10 Dec 2023 12:34:27 +0200 Subject: [PATCH 16/60] Automakit IRLiteral inferance --- .../compiler/venom/test_duplicate_operands.py | 3 +-- .../compiler/venom/test_multi_entry_block.py | 11 +++++------ vyper/venom/basicblock.py | 10 +++++++--- vyper/venom/ir_node_to_venom.py | 18 ++++++++---------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/unit/compiler/venom/test_duplicate_operands.py b/tests/unit/compiler/venom/test_duplicate_operands.py index 47e0919d34..a51992df67 100644 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ b/tests/unit/compiler/venom/test_duplicate_operands.py @@ -1,6 +1,5 @@ from vyper.compiler.settings import OptimizationLevel from vyper.venom import generate_assembly_experimental -from vyper.venom.basicblock import IRLiteral from vyper.venom.function import IRFunction @@ -18,7 +17,7 @@ def test_duplicate_operands(): """ ctx = IRFunction() bb = ctx.get_basic_block() - op = bb.append_instruction("store", IRLiteral(10)) + op = bb.append_instruction("store", 10) sum = bb.append_instruction("add", op, op) bb.append_instruction("mul", sum, op) bb.append_instruction("stop") diff --git a/tests/unit/compiler/venom/test_multi_entry_block.py b/tests/unit/compiler/venom/test_multi_entry_block.py index 1ef6c5cab7..6e7e6995d6 100644 --- a/tests/unit/compiler/venom/test_multi_entry_block.py +++ b/tests/unit/compiler/venom/test_multi_entry_block.py @@ -1,5 +1,4 @@ from vyper.venom.analysis import calculate_cfg -from vyper.venom.basicblock import IRLiteral from vyper.venom.function import IRBasicBlock, IRFunction, IRLabel from vyper.venom.passes.normalization import NormalizationPass @@ -12,14 +11,14 @@ def test_multi_entry_block_1(): block_1_label = IRLabel("block_1", ctx) bb = ctx.get_basic_block() - op = bb.append_instruction("store", IRLiteral(10)) + op = bb.append_instruction("store", 10) acc = bb.append_instruction("add", op, op) bb.append_instruction("jnz", acc, finish_label, block_1_label) block_1 = IRBasicBlock(block_1_label, ctx) ctx.append_basic_block(block_1) acc = block_1.append_instruction("add", acc, op) - op = block_1.append_instruction("store", IRLiteral(10)) + op = block_1.append_instruction("store", 10) block_1.append_instruction("mstore", acc, op) block_1.append_instruction("jnz", acc, finish_label, target_label) @@ -56,21 +55,21 @@ def test_multi_entry_block_2(): block_2_label = IRLabel("block_2", ctx) bb = ctx.get_basic_block() - op = bb.append_instruction("store", IRLiteral(10)) + op = bb.append_instruction("store", 10) acc = bb.append_instruction("add", op, op) bb.append_instruction("jnz", acc, finish_label, block_1_label) block_1 = IRBasicBlock(block_1_label, ctx) ctx.append_basic_block(block_1) acc = block_1.append_instruction("add", acc, op) - op = block_1.append_instruction("store", IRLiteral(10)) + op = block_1.append_instruction("store", 10) block_1.append_instruction("mstore", acc, op) block_1.append_instruction("jnz", acc, target_label, finish_label) block_2 = IRBasicBlock(block_2_label, ctx) ctx.append_basic_block(block_2) acc = block_2.append_instruction("add", acc, op) - op = block_2.append_instruction("store", IRLiteral(10)) + op = block_2.append_instruction("store", 10) block_2.append_instruction("mstore", acc, op) # switch the order of the labels, for fun and profit block_2.append_instruction("jnz", acc, finish_label, target_label) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 34fa0334b2..9fe366617e 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 +from typing import TYPE_CHECKING, Any, Iterator, Optional, Union from vyper.utils import OrderedSet @@ -319,14 +319,18 @@ def remove_cfg_out(self, bb: "IRBasicBlock") -> None: def is_reachable(self) -> bool: return len(self.cfg_in) > 0 - def append_instruction(self, opcode: str, *args: IROperand) -> Optional[IRVariable]: + def append_instruction(self, opcode: str, *args: Union[IROperand, int]) -> Optional[IRVariable]: """ Append an instruction to the basic block Returns the output variable if the instruction supports one """ ret = self.parent.get_next_variable() if opcode not in NO_OUTPUT_INSTRUCTIONS else None - inst = IRInstruction(opcode, list(args), ret) + + # Wrap raw integers in IRLiterals + inst_args = [IRLiteral(arg) if isinstance(arg, int) else arg for arg in args] + + inst = IRInstruction(opcode, inst_args, ret) inst.parent = self self.instructions.append(inst) return ret diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index b4c907c80e..ac4bfff9f3 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -97,7 +97,7 @@ def convert_ir_basicblock(ir: IRnode) -> IRFunction: revert_bb = IRBasicBlock(IRLabel("__revert"), global_function) revert_bb = global_function.append_basic_block(revert_bb) - revert_bb.append_instruction("revert", IRLiteral(0), IRLiteral(0)) + revert_bb.append_instruction("revert", 0, 0) return global_function @@ -238,13 +238,13 @@ def _append_return_for_stack_operand( ) -> None: if isinstance(ret_ir, IRLiteral): sym = symbols.get(f"&{ret_ir.value}", None) - new_var = bb.append_instruction("alloca", IRLiteral(32), ret_ir) + 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", IRLiteral(32), IRLiteral(0)) + new_var = bb.append_instruction("alloca", 32, 0) bb.append_instruction("mstore", ret_ir, new_var) # type: ignore else: new_var = ret_ir @@ -587,7 +587,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb.append_instruction("return", last_ir, ret_ir) else: if func_t.return_type.memory_bytes_required > 32: - new_var = bb.append_instruction("alloca", IRLiteral(32), ret_ir) + 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: @@ -596,8 +596,8 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if last_ir and int(last_ir.value) > 32: bb.append_instruction("return", last_ir, ret_ir) else: - ret_buf = IRLiteral(128) # TODO: need allocator - new_var = bb.append_instruction("alloca", IRLiteral(32), ret_buf) + 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) @@ -625,9 +625,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb = ctx.get_basic_block() src = bb.append_instruction("add", arg_0, IRLabel("code_end")) - bb.append_instruction( - "dloadbytes", IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE) - ) + bb.append_instruction("dloadbytes", 32, src, IRLiteral(MemoryPositions.FREE_VAR_SPACE)) return bb.append_instruction("mload", IRLiteral(MemoryPositions.FREE_VAR_SPACE)) elif ir.value == "dloadbytes": @@ -835,7 +833,7 @@ 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, IRLiteral(1))) + increment_block.append_instruction(IRInstruction("add", ret, 1)) increment_block.insert_instruction[-1].output = counter_inc_var increment_block.append_instruction("jmp", cond_block.label) From 7df1e76c31619303d5553a13de17d012b26e35f3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Dec 2023 12:41:15 +0200 Subject: [PATCH 17/60] Pass jump targets as metadata to the jump IRNode --- vyper/codegen/module.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/vyper/codegen/module.py b/vyper/codegen/module.py index bfdafa8ba9..04da60db17 100644 --- a/vyper/codegen/module.py +++ b/vyper/codegen/module.py @@ -293,12 +293,6 @@ def _selector_section_sparse(external_functions, global_ctx): ret.append(["codecopy", dst, bucket_hdr_location, SZ_BUCKET_HEADER]) - jumpdest = IRnode.from_list(["mload", 0]) - # don't particularly like using `jump` here since it can cause - # issues for other backends, consider changing `goto` to allow - # dynamic jumps, or adding some kind of jumptable instruction - ret.append(["jump", jumpdest]) - jumptable_data = ["data", "selector_buckets"] for i in range(n_buckets): if i in buckets: @@ -308,6 +302,15 @@ def _selector_section_sparse(external_functions, global_ctx): # empty bucket jumptable_data.append(["symbol", "fallback"]) + jumpdest = IRnode.from_list(["mload", 0]) + + # don't particularly like using `jump` here since it can cause + # issues for other backends, consider changing `goto` to allow + # dynamic jumps, or adding some kind of jumptable instruction + jump = IRnode.from_list(["jump", jumpdest]) + jump.passthrough_metadata["jumptable_data"] = jumptable_data + ret.append(jump) + ret.append(jumptable_data) for bucket_id, bucket in buckets.items(): From f416d150700c45a97f74b33f3163261b961ce257 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Dec 2023 12:49:01 +0200 Subject: [PATCH 18/60] Eliminate special cfg for O(1) dispatcher --- vyper/venom/analysis.py | 9 --------- vyper/venom/ir_node_to_venom.py | 8 ++++++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 1a82ca85d0..6dfc3c3d7c 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -40,15 +40,6 @@ def calculate_cfg(ctx: IRFunction) -> None: else: entry_block = ctx.basic_blocks[0] - # TODO: Special case for the jump table of selector buckets and fallback. - # this will be cleaner when we introduce an "indirect jump" instruction - # for the selector table (which includes all possible targets). it will - # also clean up the code for normalization because it will not have to - # handle this case specially. - for bb in ctx.basic_blocks: - if "selector_bucket_" in bb.label.value or bb.label.value == "fallback": - bb.add_cfg_in(entry_block) - for bb in ctx.basic_blocks: assert len(bb.instructions) > 0, "Basic block should not be empty" last_inst = bb.instructions[-1] diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index ac4bfff9f3..b39366d19a 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -460,8 +460,12 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value == "goto": _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "jump": - arg_1 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - ctx.get_basic_block().append_instruction("jmp", arg_1) + args = [_convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables)] + jump_table = ir.passthrough_metadata.get("jumptable_data", []) + for data in jump_table: + if isinstance(data, list) and data[0] == "symbol": + args.append(IRLabel(data[1], True)) + ctx.get_basic_block().append_instruction("jmp", *args) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] From 32d3fcedbabcaf7d18e3ab9d6e458c1abbb900bc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Dec 2023 13:48:25 +0200 Subject: [PATCH 19/60] Add test for multi-entry block with dynamic jump --- .../compiler/venom/test_multi_entry_block.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/unit/compiler/venom/test_multi_entry_block.py b/tests/unit/compiler/venom/test_multi_entry_block.py index 6e7e6995d6..f6fe584887 100644 --- a/tests/unit/compiler/venom/test_multi_entry_block.py +++ b/tests/unit/compiler/venom/test_multi_entry_block.py @@ -95,3 +95,44 @@ def test_multi_entry_block_2(): 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" + + +def test_multi_entry_block_with_dynamic_jump(): + ctx = IRFunction() + + finish_label = IRLabel("finish") + target_label = IRLabel("target") + block_1_label = IRLabel("block_1", ctx) + + bb = ctx.get_basic_block() + op = bb.append_instruction("store", 10) + acc = bb.append_instruction("add", op, op) + bb.append_instruction("jmp", acc, finish_label, block_1_label) + + block_1 = IRBasicBlock(block_1_label, ctx) + ctx.append_basic_block(block_1) + acc = block_1.append_instruction("add", acc, op) + op = block_1.append_instruction("store", 10) + block_1.append_instruction("mstore", acc, op) + block_1.append_instruction("jnz", acc, finish_label, target_label) + + target_bb = IRBasicBlock(target_label, ctx) + ctx.append_basic_block(target_bb) + target_bb.append_instruction("mul", acc, acc) + target_bb.append_instruction("jmp", finish_label) + + finish_bb = IRBasicBlock(finish_label, ctx) + ctx.append_basic_block(finish_bb) + finish_bb.append_instruction("stop") + + calculate_cfg(ctx) + assert not ctx.normalized, "CFG should not be normalized" + + NormalizationPass.run_pass(ctx) + assert ctx.normalized, "CFG should be normalized" + + 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" From bb6165fe266254dc445d716bbb103ce5fdb23a67 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Dec 2023 13:48:41 +0200 Subject: [PATCH 20/60] Refactor basic block splitting logic in normalization pass --- vyper/venom/passes/normalization.py | 65 +++++++++-------------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 90dd60e881..4a27522619 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -2,6 +2,7 @@ from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass +from vyper.venom.analysis import calculate_cfg class NormalizationPass(IRPass): @@ -19,72 +20,46 @@ def _split_basic_block(self, bb: IRBasicBlock) -> None: jump_inst = in_bb.instructions[-1] assert bb in in_bb.cfg_out - # Handle static and dynamic branching - if jump_inst.opcode == "jnz": - self._split_for_static_branch(bb, in_bb) - elif jump_inst.opcode == "jmp" and isinstance(jump_inst.operands[0], IRVariable): - self._split_for_dynamic_branch(bb, in_bb) + # Handle branching + if jump_inst.opcode == "jnz" or ( + jump_inst.opcode == "jmp" and isinstance(jump_inst.operands[0], IRVariable) + ): + self._insert_split_basicblock(bb, in_bb) else: continue self.changes += 1 - def _split_for_static_branch(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> None: - jump_inst = in_bb.instructions[-1] - for i, op in enumerate(jump_inst.operands): - if op == bb.label: - edge = i - break - else: - # none of the edges points to this bb - raise CompilerPanic("bad CFG") - - assert edge in (1, 2) # the arguments which can be labels - - split_bb = self._insert_split_basicblock(bb, in_bb) - - # Redirect the original conditional jump to the intermediary basic block - jump_inst.operands[edge] = split_bb.label - - def _split_for_dynamic_branch(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> None: - split_bb = self._insert_split_basicblock(bb, in_bb) - - # Update any affected labels in the data segment - # TODO: this DESTROYS the cfg! refactor so the translation of the - # selector table produces indirect jumps properly. - for inst in self.ctx.data_segment: - if inst.opcode == "db" and inst.operands[0] == bb.label: - inst.operands[0] = split_bb.label - def _insert_split_basicblock(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> IRBasicBlock: # Create an intermediary basic block and append it source = in_bb.label.value target = bb.label.value - split_bb = IRBasicBlock(IRLabel(f"{target}_split_{source}"), self.ctx) + + split_label = IRLabel(f"{target}_split_{source}") + in_terminal = in_bb.instructions[-1] + in_terminal.replace_operands({bb.label: split_label}) + + split_bb = IRBasicBlock(split_label, self.ctx) split_bb.append_instruction("jmp", bb.label) self.ctx.append_basic_block(split_bb) - # Rewire the CFG - # TODO: this is cursed code, it is necessary instead of just running - # calculate_cfg() because split_for_dynamic_branch destroys the CFG! - # ideally, remove this rewiring and just re-run calculate_cfg(). - split_bb.add_cfg_in(in_bb) - split_bb.add_cfg_out(bb) - in_bb.remove_cfg_out(bb) - in_bb.add_cfg_out(split_bb) - bb.remove_cfg_in(in_bb) - bb.add_cfg_in(split_bb) + for inst in self.ctx.data_segment: + if inst.opcode == "db" and inst.operands[0] == bb.label: + inst.operands[0] = split_bb.label + return split_bb def _run_pass(self, ctx: IRFunction) -> int: self.ctx = ctx self.changes = 0 + # Split blocks that need splitting for bb in ctx.basic_blocks: if len(bb.cfg_in) > 1: self._split_basic_block(bb) - # Sanity check - assert ctx.normalized, "Normalization pass failed" + # If we made changes, recalculate the cfg + if self.changes > 0: + calculate_cfg(ctx) return self.changes From 023dd0523fd26291b5350ca6dcbc8c658db56f89 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Dec 2023 14:17:27 +0200 Subject: [PATCH 21/60] Add replace_label_operants() method --- vyper/venom/basicblock.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 9fe366617e..b72889b594 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -236,6 +236,16 @@ def replace_operands(self, replacements: dict) -> None: if operand in replacements: self.operands[i] = replacements[operand] + def replace_label_operands(self, replacements: dict) -> None: + """ + Update label operands with replacements. + replacements are represented using a dict: "key" is replaced by "value". + """ + replacements = {k.value: v for k, v in replacements.items()} + for i, operand in enumerate(self.operands): + if isinstance(operand, IRLabel) and operand.value in replacements: + self.operands[i] = replacements[operand.value] + def __repr__(self) -> str: s = "" if self.output: From b27902e4bbac6a50f552ccb4a9e87cf1d3c5c0c1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Dec 2023 14:21:30 +0200 Subject: [PATCH 22/60] finalize split basicblock insertions --- vyper/venom/passes/normalization.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 4a27522619..b6c391dcbd 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -25,10 +25,8 @@ def _split_basic_block(self, bb: IRBasicBlock) -> None: jump_inst.opcode == "jmp" and isinstance(jump_inst.operands[0], IRVariable) ): self._insert_split_basicblock(bb, in_bb) - else: - continue - - self.changes += 1 + self.changes += 1 + break def _insert_split_basicblock(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> IRBasicBlock: # Create an intermediary basic block and append it @@ -37,7 +35,7 @@ def _insert_split_basicblock(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> IRB split_label = IRLabel(f"{target}_split_{source}") in_terminal = in_bb.instructions[-1] - in_terminal.replace_operands({bb.label: split_label}) + in_terminal.replace_label_operands({bb.label: split_label}) split_bb = IRBasicBlock(split_label, self.ctx) split_bb.append_instruction("jmp", bb.label) From 5ffa6e7f86192a88a0e4a3638f1554480906806b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Dec 2023 14:36:06 +0200 Subject: [PATCH 23/60] fix import order in normalization pass --- vyper/venom/passes/normalization.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index b6c391dcbd..f01ece9d52 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -1,8 +1,7 @@ -from vyper.exceptions import CompilerPanic +from vyper.venom.analysis import calculate_cfg from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass -from vyper.venom.analysis import calculate_cfg class NormalizationPass(IRPass): From f12f0dc5b4d309a68570437ef99a4b72053e3f40 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Dec 2023 16:14:44 +0200 Subject: [PATCH 24/60] Cleanup IRLiteral convertions --- vyper/venom/ir_node_to_venom.py | 38 ++++++++++++--------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index b39366d19a..41d615a9fb 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -163,7 +163,7 @@ def _handle_self_call( ret_args.append(ret) if return_buf.is_literal: - ret_args.append(IRLiteral(return_buf.value)) # type: ignore + ret_args.append(return_buf.value) # type: ignore bb = ctx.get_basic_block() do_ret = func_t.return_type is not None @@ -288,9 +288,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): runtimeLabel = ctx.get_next_label() - ctx.get_basic_block().append_instruction( - "deploy", IRLiteral(memsize), runtimeLabel, IRLiteral(padding) - ) + ctx.get_basic_block().append_instruction("deploy", memsize, runtimeLabel, padding) bb = IRBasicBlock(runtimeLabel, ctx) ctx.append_basic_block(bb) @@ -389,9 +387,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, 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", IRLiteral(else_ret_val.value) - ) + else_ret_val = ctx.get_basic_block().append_instruction("store", else_ret_val) after_else_syms = else_syms.copy() # convert "then" @@ -402,9 +398,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ctx, ir.args[1], symbols, variables, allocated_variables ) if isinstance(then_ret_val, IRLiteral): - then_ret_val = ctx.get_basic_block().append_instruction( - "store", IRLiteral(then_ret_val.value) - ) + then_ret_val = ctx.get_basic_block().append_instruction("store", then_ret_val.value) current_bb.append_instruction("jnz", cont_ret, then_block.label, else_block.label) @@ -487,9 +481,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): 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", IRLiteral(var.size), IRLiteral(var.pos) # type: ignore - ) + 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 @@ -576,9 +568,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): 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", IRLiteral(var.pos), IRLiteral(offset) - ) + ptr_var = bb.append_instruction("add", var.pos, offset) else: ptr_var = allocated_var bb.append_instruction("return", last_ir, ptr_var) @@ -629,8 +619,8 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): bb = ctx.get_basic_block() src = bb.append_instruction("add", arg_0, IRLabel("code_end")) - bb.append_instruction("dloadbytes", 32, src, IRLiteral(MemoryPositions.FREE_VAR_SPACE)) - return bb.append_instruction("mload", IRLiteral(MemoryPositions.FREE_VAR_SPACE)) + bb.append_instruction("dloadbytes", 32, src, MemoryPositions.FREE_VAR_SPACE) + 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) @@ -653,12 +643,12 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if var.size and var.size > 32: if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = bb.append_instruction( - "alloca", IRLiteral(var.size), IRLiteral(var.pos) + "alloca", var.size, var.pos ) offset = int(sym_ir.value) - var.pos if offset > 0: - ptr_var = bb.append_instruction("add", IRLiteral(var.pos), IRLiteral(offset)) + ptr_var = bb.append_instruction("add", var.pos, offset) else: ptr_var = allocated_variables[var.name] @@ -684,7 +674,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if new_var is not None: return bb.append_instruction("mload", new_var) else: - return bb.append_instruction("mload", IRLiteral(sym_ir.value)) + return bb.append_instruction("mload", sym_ir.value) else: new_var = _convert_ir_basicblock( ctx, sym_ir, symbols, variables, allocated_variables @@ -713,12 +703,12 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if var.size and var.size > 32: if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = bb.append_instruction( - "alloca", IRLiteral(var.size), IRLiteral(var.pos) + "alloca", var.size, var.pos ) offset = int(sym_ir.value) - var.pos if offset > 0: - ptr_var = bb.append_instruction("add", IRLiteral(var.pos), IRLiteral(offset)) + ptr_var = bb.append_instruction("add", var.pos, offset) else: ptr_var = allocated_variables[var.name] @@ -880,7 +870,7 @@ def emit_body_block(): ) topic_count = int(ir.value[3:]) assert topic_count >= 0 and topic_count <= 4, "invalid topic count" - ctx.get_basic_block().append_instruction("log", IRLiteral(topic_count), *args) + ctx.get_basic_block().append_instruction("log", topic_count, *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: From 8836a4c3b7cda67be95739b020f3787a0287486f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 13 Dec 2023 12:12:25 +0200 Subject: [PATCH 25/60] add small comment --- vyper/venom/passes/normalization.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index f01ece9d52..9d740920a2 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -40,6 +40,7 @@ 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) + # Update the labels in the data segment for inst in self.ctx.data_segment: if inst.opcode == "db" and inst.operands[0] == bb.label: inst.operands[0] = split_bb.label From dde51e2c0f431d5d5cd77eee4f137893737d60d8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 17 Dec 2023 21:07:10 +0200 Subject: [PATCH 26/60] djump instruction --- vyper/codegen/module.py | 9 +++++++-- vyper/venom/ir_node_to_venom.py | 8 +++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/module.py b/vyper/codegen/module.py index 04da60db17..3234e937a9 100644 --- a/vyper/codegen/module.py +++ b/vyper/codegen/module.py @@ -11,6 +11,8 @@ from vyper.exceptions import CompilerPanic from vyper.utils import method_id_int +experimental_codegen = True + def _topsort_helper(functions, lookup): # single pass to get a global topological sort of functions (so that each @@ -307,8 +309,11 @@ def _selector_section_sparse(external_functions, global_ctx): # don't particularly like using `jump` here since it can cause # issues for other backends, consider changing `goto` to allow # dynamic jumps, or adding some kind of jumptable instruction - jump = IRnode.from_list(["jump", jumpdest]) - jump.passthrough_metadata["jumptable_data"] = jumptable_data + if experimental_codegen: + jump_targets = [data[1] for data in jumptable_data[2:]] + jump = IRnode.from_list(["djump", jumpdest, *jump_targets]) + else: + jump = IRnode.from_list(["jump", jumpdest]) ret.append(jump) ret.append(jumptable_data) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index aa7a0a47b6..ddf9cf666d 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -451,12 +451,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ) # body elif ir.value == "goto": _append_jmp(ctx, IRLabel(ir.args[0].value)) - elif ir.value == "jump": + elif ir.value in ["jump", "djump"]: args = [_convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables)] - jump_table = ir.passthrough_metadata.get("jumptable_data", []) - for data in jump_table: - if isinstance(data, list) and data[0] == "symbol": - args.append(IRLabel(data[1], True)) + for target in ir.args[1:]: + args.append(IRLabel(target.value)) ctx.get_basic_block().append_instruction("jmp", *args) _new_block(ctx) elif ir.value == "set": From 7c75da57a59007424f26515edee124c91884885e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 18 Dec 2023 00:41:42 +0200 Subject: [PATCH 27/60] Move experimental_codegen in settings --- vyper/cli/vyper_compile.py | 7 ++++--- vyper/compiler/__init__.py | 2 -- vyper/compiler/phases.py | 27 +++++++++++++++++++-------- vyper/compiler/settings.py | 1 + 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/vyper/cli/vyper_compile.py b/vyper/cli/vyper_compile.py index ca1792384e..3ae5691a92 100755 --- a/vyper/cli/vyper_compile.py +++ b/vyper/cli/vyper_compile.py @@ -145,6 +145,7 @@ def _parse_args(argv): "--experimental-codegen", help="The compiler use the new IR codegen. This is an experimental feature.", action="store_true", + dest="experimental_codegen", ) args = parser.parse_args(argv) @@ -182,6 +183,9 @@ def _parse_args(argv): if args.evm_version: settings.evm_version = args.evm_version + if args.experimental_codegen: + settings.experimental_codegen = args.experimental_codegen + if args.verbose: print(f"cli specified: `{settings}`", file=sys.stderr) @@ -193,7 +197,6 @@ def _parse_args(argv): settings, args.storage_layout, args.no_bytecode_metadata, - args.experimental_codegen, ) if args.output_path: @@ -231,7 +234,6 @@ def compile_files( settings: Optional[Settings] = None, storage_layout_paths: list[str] = None, no_bytecode_metadata: bool = False, - experimental_codegen: bool = False, ) -> dict: root_path = Path(root_folder).resolve() if not root_path.exists(): @@ -282,7 +284,6 @@ def compile_files( storage_layout_override=storage_layout_override, show_gas_estimates=show_gas_estimates, no_bytecode_metadata=no_bytecode_metadata, - experimental_codegen=experimental_codegen, ) ret[file_path] = output diff --git a/vyper/compiler/__init__.py b/vyper/compiler/__init__.py index 61d7a7c229..62ea05b243 100644 --- a/vyper/compiler/__init__.py +++ b/vyper/compiler/__init__.py @@ -55,7 +55,6 @@ def compile_code( no_bytecode_metadata: bool = False, show_gas_estimates: bool = False, exc_handler: Optional[Callable] = None, - experimental_codegen: bool = False, ) -> dict: """ Generate consumable compiler output(s) from a single contract source code. @@ -105,7 +104,6 @@ def compile_code( storage_layout_override, show_gas_estimates, no_bytecode_metadata, - experimental_codegen, ) ret = {} diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 4e32812fee..c9e1452ee4 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -61,7 +61,6 @@ def __init__( storage_layout: StorageLayout = None, show_gas_estimates: bool = False, no_bytecode_metadata: bool = False, - experimental_codegen: bool = False, ) -> None: """ Initialization method. @@ -80,18 +79,15 @@ def __init__( Show gas estimates for abi and ir output modes no_bytecode_metadata: bool, optional Do not add metadata to bytecode. Defaults to False - experimental_codegen: bool, optional - Use experimental codegen. Defaults to False """ # to force experimental codegen, uncomment: - # experimental_codegen = True + # settings.experimental_codegen = True self.contract_path = contract_path self.source_code = source_code self.source_id = source_id self.storage_layout_override = storage_layout self.show_gas_estimates = show_gas_estimates self.no_bytecode_metadata = no_bytecode_metadata - self.experimental_codegen = experimental_codegen self.settings = settings or Settings() self.input_bundle = input_bundle or FilesystemInputBundle([Path(".")]) @@ -124,10 +120,25 @@ def _generate_ast(self): ) self.settings.optimize = settings.optimize + if settings.experimental_codegen is not None: + if ( + self.settings.experimental_codegen is not None + and self.settings.experimental_codegen != settings.experimental_codegen + ): + raise StructureException( + f"compiler options indicate experimental codegen mode " + f"{self.settings.experimental_codegen}, " + f"but source pragma indicates {settings.experimental_codegen}." + ) + self.settings.experimental_codegen = settings.experimental_codegen + # ensure defaults if self.settings.optimize is None: self.settings.optimize = OptimizationLevel.default() + if self.settings.experimental_codegen is None: + self.settings.experimental_codegen = False + # note self.settings.compiler_version is erased here as it is # not used after pre-parsing return ast @@ -167,7 +178,7 @@ def global_ctx(self) -> GlobalContext: def _ir_output(self): # fetch both deployment and runtime IR nodes = generate_ir_nodes(self.global_ctx, self.settings.optimize) - if self.experimental_codegen: + if self.settings.experimental_codegen: return [generate_ir(nodes[0]), generate_ir(nodes[1])] else: return nodes @@ -193,7 +204,7 @@ def function_signatures(self) -> dict[str, ContractFunctionT]: @cached_property def assembly(self) -> list: - if self.experimental_codegen: + if self.settings.experimental_codegen: return generate_assembly_experimental( self.ir_nodes, self.settings.optimize # type: ignore ) @@ -202,7 +213,7 @@ def assembly(self) -> list: @cached_property def assembly_runtime(self) -> list: - if self.experimental_codegen: + if self.settings.experimental_codegen: return generate_assembly_experimental( self.ir_runtime, self.settings.optimize # type: ignore ) diff --git a/vyper/compiler/settings.py b/vyper/compiler/settings.py index d2c88a8592..51c8d64e41 100644 --- a/vyper/compiler/settings.py +++ b/vyper/compiler/settings.py @@ -42,6 +42,7 @@ class Settings: compiler_version: Optional[str] = None optimize: Optional[OptimizationLevel] = None evm_version: Optional[str] = None + experimental_codegen: Optional[bool] = None _DEBUG = False From 65baf5bbb2e6abde23006ff23f06a8f3eeb4f532 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 18 Dec 2023 11:31:35 +0200 Subject: [PATCH 28/60] Disable ir_dict output type for venom --- vyper/compiler/output.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/compiler/output.py b/vyper/compiler/output.py index 6d1e7ef70f..dc2a43720e 100644 --- a/vyper/compiler/output.py +++ b/vyper/compiler/output.py @@ -89,6 +89,9 @@ def build_ir_runtime_output(compiler_data: CompilerData) -> IRnode: def _ir_to_dict(ir_node): + # Currently only supported with IRnode and not VenomIR + if not isinstance(ir_node, IRnode): + return args = ir_node.args if len(args) > 0 or ir_node.value == "seq": return {ir_node.value: [_ir_to_dict(x) for x in args]} From 7d961c625a34fd90a5926d497330bfc93427b525 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 18 Dec 2023 16:04:19 +0200 Subject: [PATCH 29/60] experimental codegen flag passing --- vyper/codegen/core.py | 11 ++++++++++- vyper/codegen/module.py | 4 +--- vyper/compiler/phases.py | 10 +++++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/core.py b/vyper/codegen/core.py index e1d3ea12b4..a1471aabd7 100644 --- a/vyper/codegen/core.py +++ b/vyper/codegen/core.py @@ -890,10 +890,14 @@ def make_setter(left, right): _opt_level = OptimizationLevel.GAS +_opt_experimental_codegen = False +# FIXME: this is to get around the fact that we don't have a +# proper context object in the IR generation phase. experimental_codegen is +# temporary until it become mainstream, when it will go away @contextlib.contextmanager -def anchor_opt_level(new_level: OptimizationLevel) -> Generator: +def anchor_opt_level(new_level: OptimizationLevel, experimental_codegen: bool) -> Generator: """ Set the global optimization level variable for the duration of this context manager. @@ -901,12 +905,17 @@ def anchor_opt_level(new_level: OptimizationLevel) -> Generator: assert isinstance(new_level, OptimizationLevel) global _opt_level + global _opt_experimental_codegen try: tmp = _opt_level _opt_level = new_level + + tmp_codegen = _opt_experimental_codegen + _opt_experimental_codegen = experimental_codegen yield finally: _opt_level = tmp + _opt_experimental_codegen = tmp_codegen def _opt_codesize(): diff --git a/vyper/codegen/module.py b/vyper/codegen/module.py index 522b039991..38fd10545a 100644 --- a/vyper/codegen/module.py +++ b/vyper/codegen/module.py @@ -11,8 +11,6 @@ from vyper.semantics.types.module import ModuleT from vyper.utils import OrderedSet, method_id_int -experimental_codegen = True - def _topsort(functions): # single pass to get a global topological sort of functions (so that each @@ -327,7 +325,7 @@ def _selector_section_sparse(external_functions, module_ctx): # don't particularly like using `jump` here since it can cause # issues for other backends, consider changing `goto` to allow # dynamic jumps, or adding some kind of jumptable instruction - if experimental_codegen: + if core._opt_experimental_codegen: jump_targets = [data[1] for data in jumptable_data[2:]] jump = IRnode.from_list(["djump", jumpdest, *jump_targets]) else: diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 271a35fddd..6ab1e2770a 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -195,7 +195,9 @@ def global_ctx(self) -> ModuleT: @cached_property def _ir_output(self): # fetch both deployment and runtime IR - nodes = generate_ir_nodes(self.global_ctx, self.settings.optimize) + nodes = generate_ir_nodes( + self.global_ctx, self.settings.optimize, self.settings.experimental_codegen + ) if self.settings.experimental_codegen: return [generate_ir(nodes[0]), generate_ir(nodes[1])] else: @@ -305,7 +307,9 @@ def generate_folded_ast( return vyper_module_folded, symbol_tables -def generate_ir_nodes(global_ctx: ModuleT, optimize: OptimizationLevel) -> tuple[IRnode, IRnode]: +def generate_ir_nodes( + global_ctx: ModuleT, optimize: OptimizationLevel, experimental_codegen: bool +) -> tuple[IRnode, IRnode]: """ Generate the intermediate representation (IR) from the contextualized AST. @@ -325,7 +329,7 @@ def generate_ir_nodes(global_ctx: ModuleT, optimize: OptimizationLevel) -> tuple IR to generate deployment bytecode IR to generate runtime bytecode """ - with anchor_opt_level(optimize): + with anchor_opt_level(optimize, experimental_codegen): ir_nodes, ir_runtime = module.generate_ir_for_module(global_ctx) if optimize != OptimizationLevel.NONE: ir_nodes = optimizer.optimize(ir_nodes) From 937d5ccffa0a9bb9ba01f9088896b699bdccba9d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Dec 2023 11:27:51 -0500 Subject: [PATCH 30/60] rename "djump" to "mjump" - remove experimental codegen flag passing - rewrite a bit of code in module.py - add the code generator in old IR --- vyper/codegen/core.py | 11 ++--------- vyper/codegen/module.py | 22 +++++++++------------- vyper/compiler/phases.py | 2 +- vyper/ir/compile_ir.py | 7 +++++++ vyper/utils.py | 1 + vyper/venom/ir_node_to_venom.py | 2 +- 6 files changed, 21 insertions(+), 24 deletions(-) diff --git a/vyper/codegen/core.py b/vyper/codegen/core.py index a1471aabd7..503e0e2f3b 100644 --- a/vyper/codegen/core.py +++ b/vyper/codegen/core.py @@ -890,14 +890,12 @@ def make_setter(left, right): _opt_level = OptimizationLevel.GAS -_opt_experimental_codegen = False # FIXME: this is to get around the fact that we don't have a -# proper context object in the IR generation phase. experimental_codegen is -# temporary until it become mainstream, when it will go away +# proper context object in the IR generation phase. @contextlib.contextmanager -def anchor_opt_level(new_level: OptimizationLevel, experimental_codegen: bool) -> Generator: +def anchor_opt_level(new_level: OptimizationLevel) -> Generator: """ Set the global optimization level variable for the duration of this context manager. @@ -905,17 +903,12 @@ def anchor_opt_level(new_level: OptimizationLevel, experimental_codegen: bool) - assert isinstance(new_level, OptimizationLevel) global _opt_level - global _opt_experimental_codegen try: tmp = _opt_level _opt_level = new_level - - tmp_codegen = _opt_experimental_codegen - _opt_experimental_codegen = experimental_codegen yield finally: _opt_level = tmp - _opt_experimental_codegen = tmp_codegen def _opt_codesize(): diff --git a/vyper/codegen/module.py b/vyper/codegen/module.py index 38fd10545a..7581eed0dd 100644 --- a/vyper/codegen/module.py +++ b/vyper/codegen/module.py @@ -311,27 +311,23 @@ def _selector_section_sparse(external_functions, module_ctx): ret.append(["codecopy", dst, bucket_hdr_location, SZ_BUCKET_HEADER]) - jumptable_data = ["data", "selector_buckets"] + jump_targets = [] + for i in range(n_buckets): if i in buckets: bucket_label = f"selector_bucket_{i}" - jumptable_data.append(["symbol", bucket_label]) + jump_targets.append(bucket_label) else: # empty bucket - jumptable_data.append(["symbol", "fallback"]) + jump_targets.append("fallback") - jumpdest = IRnode.from_list(["mload", 0]) + jumptable_data = ["data", "selector_buckets"] + jumptable_data.extend(["symbol", label] for label in jump_targets) - # don't particularly like using `jump` here since it can cause - # issues for other backends, consider changing `goto` to allow - # dynamic jumps, or adding some kind of jumptable instruction - if core._opt_experimental_codegen: - jump_targets = [data[1] for data in jumptable_data[2:]] - jump = IRnode.from_list(["djump", jumpdest, *jump_targets]) - else: - jump = IRnode.from_list(["jump", jumpdest]) - ret.append(jump) + jumpdest = IRnode.from_list(["mload", 0]) + jump_instr = IRnode.from_list(["mjump", jumpdest, *jump_targets]) + ret.append(jump_instr) ret.append(jumptable_data) for bucket_id, bucket in buckets.items(): diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 6ab1e2770a..62e9d17a15 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -329,7 +329,7 @@ def generate_ir_nodes( IR to generate deployment bytecode IR to generate runtime bytecode """ - with anchor_opt_level(optimize, experimental_codegen): + with anchor_opt_level(optimize): ir_nodes, ir_runtime = module.generate_ir_for_module(global_ctx) if optimize != OptimizationLevel.NONE: ir_nodes = optimizer.optimize(ir_nodes) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 1d3df8becb..d49bf8fc4b 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -702,6 +702,13 @@ def _height_of(witharg): o.extend(_compile_to_assembly(c, withargs, existing_labels, break_dest, height + i)) o.extend(["_sym_" + code.args[0].value, "JUMP"]) return o + elif code.value == "mjump": + o = [] + # "mjump" compiles to a raw EVM jump instruction + jump_target = code.args[0] + o.extend(_compile_to_assembly(jump_target, withargs, existing_labels, break_dest, height)) + o.append("JUMP") + return o # push a literal symbol elif code.value == "symbol": return ["_sym_" + code.args[0].value] diff --git a/vyper/utils.py b/vyper/utils.py index 6816db9bae..0ed517b212 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -331,6 +331,7 @@ class SizeLimits: "with", "label", "goto", + "mjump", # "multi-destination jump" "~extcode", "~selfcode", "~calldata", diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index ddf9cf666d..29adbc842e 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -451,7 +451,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ) # body elif ir.value == "goto": _append_jmp(ctx, IRLabel(ir.args[0].value)) - elif ir.value in ["jump", "djump"]: + elif ir.value == "mjump": args = [_convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables)] for target in ir.args[1:]: args.append(IRLabel(target.value)) From 20d28e67bc2ae9c00302340967e3351d16c7cbe9 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Dec 2023 11:42:24 -0500 Subject: [PATCH 31/60] add mjmp to venom --- .../unit/compiler/venom/test_multi_entry_block.py | 2 +- vyper/venom/basicblock.py | 7 +++++-- vyper/venom/function.py | 9 ++------- vyper/venom/ir_node_to_venom.py | 2 +- vyper/venom/passes/normalization.py | 6 ++---- vyper/venom/venom_to_assembly.py | 15 ++++++++------- 6 files changed, 19 insertions(+), 22 deletions(-) diff --git a/tests/unit/compiler/venom/test_multi_entry_block.py b/tests/unit/compiler/venom/test_multi_entry_block.py index f6fe584887..ed5f5a55ad 100644 --- a/tests/unit/compiler/venom/test_multi_entry_block.py +++ b/tests/unit/compiler/venom/test_multi_entry_block.py @@ -107,7 +107,7 @@ def test_multi_entry_block_with_dynamic_jump(): bb = ctx.get_basic_block() op = bb.append_instruction("store", 10) acc = bb.append_instruction("add", op, op) - bb.append_instruction("jmp", acc, finish_label, block_1_label) + bb.append_instruction("mjmp", acc, finish_label, block_1_label) block_1 = IRBasicBlock(block_1_label, ctx) ctx.append_basic_block(block_1) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 460671fab4..51f438afe7 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", "jnz", "ret", "return", "revert", "deploy", "stop"]) +BB_TERMINATORS = frozenset(["jmp", "mjmp", "jnz", "ret", "return", "revert", "deploy", "stop"]) VOLATILE_INSTRUCTIONS = frozenset( [ @@ -50,12 +50,15 @@ "invalid", "invoke", "jmp", + "mjmp", "jnz", "log", ] ) -CFG_ALTERING_INSTRUCTIONS = frozenset(["jmp", "jnz", "call", "staticcall", "invoke", "deploy"]) +CFG_ALTERING_INSTRUCTIONS = frozenset( + ["jmp", "mjmp", "jnz", "call", "staticcall", "invoke", "deploy"] +) if TYPE_CHECKING: from vyper.venom.function import IRFunction diff --git a/vyper/venom/function.py b/vyper/venom/function.py index e16b2ad6e6..3096316a6b 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -129,13 +129,8 @@ def normalized(self) -> bool: # selector table indirect jumps). for in_bb in bb.cfg_in: jump_inst = in_bb.instructions[-1] - if jump_inst.opcode != "jnz": - continue - if jump_inst.opcode == "jmp" and isinstance(jump_inst.operands[0], IRLabel): - continue - - # The function is not normalized - return False + if jump_inst.opcode in ("jnz", "mjmp"): + return False # The function is normalized return True diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 29adbc842e..3c00d729ad 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -455,7 +455,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): args = [_convert_ir_basicblock(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("jmp", *args) + ctx.get_basic_block().append_instruction("mjmp", *args) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 9d740920a2..7ccf500f73 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -1,5 +1,5 @@ from vyper.venom.analysis import calculate_cfg -from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable +from vyper.venom.basicblock import IRBasicBlock, IRLabel from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass @@ -20,9 +20,7 @@ def _split_basic_block(self, bb: IRBasicBlock) -> None: assert bb in in_bb.cfg_out # Handle branching - if jump_inst.opcode == "jnz" or ( - jump_inst.opcode == "jmp" and isinstance(jump_inst.operands[0], IRVariable) - ): + if jump_inst.opcode in ("jnz", "mjmp"): self._insert_split_basicblock(bb, in_bb) self.changes += 1 break diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 8760e9aa63..5ab0f9bba8 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -261,7 +261,7 @@ def _generate_evm_for_instruction( # Step 1: Apply instruction special stack manipulations - if opcode in ["jmp", "jnz", "invoke"]: + if opcode in ["jmp", "mjmp", "jnz", "invoke"]: operands = inst.get_non_label_operands() elif opcode == "alloca": operands = inst.operands[1:2] @@ -296,7 +296,7 @@ def _generate_evm_for_instruction( self._emit_input_operands(assembly, inst, operands, stack) # Step 3: Reorder stack - if opcode in ["jnz", "jmp"]: + if opcode in ["jnz", "mjmp", "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)) @@ -344,11 +344,12 @@ def _generate_evm_for_instruction( assembly.append("JUMP") elif opcode == "jmp": - if isinstance(inst.operands[0], IRLabel): - assembly.append(f"_sym_{inst.operands[0].value}") - assembly.append("JUMP") - else: - assembly.append("JUMP") + assert isinstance(inst.operands[0], IRLabel) + assembly.append(f"_sym_{inst.operands[0].value}") + assembly.append("JUMP") + elif opcode == "mjmp": + assert isinstance(inst.operands[0], IRVariable) + assembly.append("JUMP") elif opcode == "gt": assembly.append("GT") elif opcode == "lt": From 9bf661c30cd2bc382568816d4f914c9061b22533 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Dec 2023 11:55:54 -0500 Subject: [PATCH 32/60] rename mjump->djump, mjmp->djmp --- tests/unit/compiler/venom/test_multi_entry_block.py | 2 +- vyper/codegen/module.py | 2 +- vyper/ir/compile_ir.py | 4 ++-- vyper/utils.py | 2 +- vyper/venom/basicblock.py | 6 +++--- vyper/venom/function.py | 2 +- vyper/venom/ir_node_to_venom.py | 4 ++-- vyper/venom/passes/normalization.py | 2 +- vyper/venom/venom_to_assembly.py | 6 +++--- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/unit/compiler/venom/test_multi_entry_block.py b/tests/unit/compiler/venom/test_multi_entry_block.py index ed5f5a55ad..104697432b 100644 --- a/tests/unit/compiler/venom/test_multi_entry_block.py +++ b/tests/unit/compiler/venom/test_multi_entry_block.py @@ -107,7 +107,7 @@ def test_multi_entry_block_with_dynamic_jump(): bb = ctx.get_basic_block() op = bb.append_instruction("store", 10) acc = bb.append_instruction("add", op, op) - bb.append_instruction("mjmp", acc, finish_label, block_1_label) + bb.append_instruction("djmp", acc, finish_label, block_1_label) block_1 = IRBasicBlock(block_1_label, ctx) ctx.append_basic_block(block_1) diff --git a/vyper/codegen/module.py b/vyper/codegen/module.py index 7581eed0dd..98395a6a0c 100644 --- a/vyper/codegen/module.py +++ b/vyper/codegen/module.py @@ -326,7 +326,7 @@ def _selector_section_sparse(external_functions, module_ctx): jumpdest = IRnode.from_list(["mload", 0]) - jump_instr = IRnode.from_list(["mjump", jumpdest, *jump_targets]) + jump_instr = IRnode.from_list(["djump", jumpdest, *jump_targets]) ret.append(jump_instr) ret.append(jumptable_data) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index d49bf8fc4b..8ce8c887f1 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -702,9 +702,9 @@ def _height_of(witharg): o.extend(_compile_to_assembly(c, withargs, existing_labels, break_dest, height + i)) o.extend(["_sym_" + code.args[0].value, "JUMP"]) return o - elif code.value == "mjump": + elif code.value == "djump": o = [] - # "mjump" compiles to a raw EVM jump instruction + # "djump" compiles to a raw EVM jump instruction jump_target = code.args[0] o.extend(_compile_to_assembly(jump_target, withargs, existing_labels, break_dest, height)) o.append("JUMP") diff --git a/vyper/utils.py b/vyper/utils.py index 0ed517b212..a778a4e31b 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -331,7 +331,7 @@ class SizeLimits: "with", "label", "goto", - "mjump", # "multi-destination jump" + "djump", # "dynamic jump", i.e. constrained, multi-destination jump "~extcode", "~selfcode", "~calldata", diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 51f438afe7..9afaa5e6fd 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", "mjmp", "jnz", "ret", "return", "revert", "deploy", "stop"]) +BB_TERMINATORS = frozenset(["jmp", "djmp", "jnz", "ret", "return", "revert", "deploy", "stop"]) VOLATILE_INSTRUCTIONS = frozenset( [ @@ -50,14 +50,14 @@ "invalid", "invoke", "jmp", - "mjmp", + "djmp", "jnz", "log", ] ) CFG_ALTERING_INSTRUCTIONS = frozenset( - ["jmp", "mjmp", "jnz", "call", "staticcall", "invoke", "deploy"] + ["jmp", "djmp", "jnz", "call", "staticcall", "invoke", "deploy"] ) if TYPE_CHECKING: diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 3096316a6b..e743886b40 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -129,7 +129,7 @@ def normalized(self) -> bool: # selector table indirect jumps). for in_bb in bb.cfg_in: jump_inst = in_bb.instructions[-1] - if jump_inst.opcode in ("jnz", "mjmp"): + if jump_inst.opcode in ("jnz", "djmp"): return False # The function is normalized diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 3c00d729ad..9fe9ef9951 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -451,11 +451,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ) # body elif ir.value == "goto": _append_jmp(ctx, IRLabel(ir.args[0].value)) - elif ir.value == "mjump": + elif ir.value == "djump": args = [_convert_ir_basicblock(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("mjmp", *args) + ctx.get_basic_block().append_instruction("djmp", *args) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 7ccf500f73..43e8d47235 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -20,7 +20,7 @@ def _split_basic_block(self, bb: IRBasicBlock) -> None: assert bb in in_bb.cfg_out # Handle branching - if jump_inst.opcode in ("jnz", "mjmp"): + if jump_inst.opcode in ("jnz", "djmp"): self._insert_split_basicblock(bb, in_bb) self.changes += 1 break diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 5ab0f9bba8..0c32c3b816 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -261,7 +261,7 @@ def _generate_evm_for_instruction( # Step 1: Apply instruction special stack manipulations - if opcode in ["jmp", "mjmp", "jnz", "invoke"]: + if opcode in ["jmp", "djmp", "jnz", "invoke"]: operands = inst.get_non_label_operands() elif opcode == "alloca": operands = inst.operands[1:2] @@ -296,7 +296,7 @@ def _generate_evm_for_instruction( self._emit_input_operands(assembly, inst, operands, stack) # Step 3: Reorder stack - if opcode in ["jnz", "mjmp", "jmp"]: + 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)) @@ -347,7 +347,7 @@ def _generate_evm_for_instruction( assert isinstance(inst.operands[0], IRLabel) assembly.append(f"_sym_{inst.operands[0].value}") assembly.append("JUMP") - elif opcode == "mjmp": + elif opcode == "djmp": assert isinstance(inst.operands[0], IRVariable) assembly.append("JUMP") elif opcode == "gt": From c9dd07542099a0627ffea5da7bc36e3002c27655 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 19 Dec 2023 11:57:05 -0500 Subject: [PATCH 33/60] update a comment --- 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 e743886b40..665fa0c6c2 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -125,8 +125,7 @@ def normalized(self) -> bool: # TODO: this check could be: # `if len(in_bb.cfg_out) > 1: return False` # but the cfg is currently not calculated "correctly" for - # certain special instructions (deploy instruction and - # selector table indirect jumps). + # the special deploy instruction. for in_bb in bb.cfg_in: jump_inst = in_bb.instructions[-1] if jump_inst.opcode in ("jnz", "djmp"): From d7f3ac195084e2c257cbee1f9949263bb8fd0e86 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 11:43:01 +0200 Subject: [PATCH 34/60] Default experimental codegen setting is None --- vyper/compiler/phases.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 62e9d17a15..28da3bcc3a 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -154,9 +154,6 @@ def _generate_ast(self): if self.settings.optimize is None: self.settings.optimize = OptimizationLevel.default() - if self.settings.experimental_codegen is None: - self.settings.experimental_codegen = False - # note self.settings.compiler_version is erased here as it is # not used after pre-parsing return ast From 690b9f9234b5048c93aad08b0db2d952c2fb0c76 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 11:57:54 +0200 Subject: [PATCH 35/60] Add entry_points to IRFunction class --- vyper/venom/function.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 665fa0c6c2..5283a8f396 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -18,6 +18,7 @@ class IRFunction: """ name: IRLabel # symbol name + entry_points: list[IRLabel] # entry points args: list basic_blocks: list[IRBasicBlock] data_segment: list[IRInstruction] @@ -28,6 +29,7 @@ def __init__(self, name: IRLabel = None) -> None: if name is None: name = GLOBAL_LABEL self.name = name + self.entry_points = [] self.args = [] self.basic_blocks = [] self.data_segment = [] @@ -91,7 +93,11 @@ 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.value != "global": + if ( + not bb.is_reachable + and bb.label.value != "global" + and bb.label not in self.entry_points + ): removed += 1 else: new_basic_blocks.append(bb) From 051fe802f65b8f520e904d8b6f53454fb4b51efe Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 11:56:06 +0200 Subject: [PATCH 36/60] Fix _append_return_for_stack_operand's arguments --- 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 9fe9ef9951..9f5c23df0b 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -232,8 +232,9 @@ def _get_variable_from_address( def _append_return_for_stack_operand( - bb: IRBasicBlock, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable + 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) From ec00dfa69dc84e20b97562f6a05e6e47fb74f46e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 12:06:53 +0200 Subject: [PATCH 37/60] Add and remove entry points in IRFunction class --- vyper/venom/function.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 5283a8f396..42a087772b 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -36,8 +36,21 @@ def __init__(self, name: IRLabel = None) -> None: self.last_label = 0 self.last_variable = 0 + self.add_entry_point(name) self.append_basic_block(IRBasicBlock(name, self)) + def add_entry_point(self, label: IRLabel) -> None: + """ + Add entry point. + """ + self.entry_points.append(label) + + def remove_entry_point(self, label: IRLabel) -> None: + """ + Remove entry point. + """ + self.entry_points.remove(label) + def append_basic_block(self, bb: IRBasicBlock) -> IRBasicBlock: """ Append basic block to function. @@ -93,11 +106,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.value != "global" - and bb.label not in self.entry_points - ): + if not bb.is_reachable and bb.label not in self.entry_points: removed += 1 else: new_basic_blocks.append(bb) From b119518223c90d732dbc5079e620c29bb33a0203 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 14:01:31 +0200 Subject: [PATCH 38/60] Update global label and add runtime entry point --- tests/unit/compiler/venom/test_multi_entry_block.py | 6 +++--- vyper/venom/function.py | 2 +- vyper/venom/ir_node_to_venom.py | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/unit/compiler/venom/test_multi_entry_block.py b/tests/unit/compiler/venom/test_multi_entry_block.py index 104697432b..6d8b074994 100644 --- a/tests/unit/compiler/venom/test_multi_entry_block.py +++ b/tests/unit/compiler/venom/test_multi_entry_block.py @@ -41,7 +41,7 @@ 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[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" @@ -93,7 +93,7 @@ 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[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" @@ -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[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" diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 42a087772b..daad9de75f 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -9,7 +9,7 @@ MemType, ) -GLOBAL_LABEL = IRLabel("global") +GLOBAL_LABEL = IRLabel("__global") class IRFunction: diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 9f5c23df0b..7086d4c8b5 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -285,7 +285,8 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): assert isinstance(memsize, int), "non-int memsize" assert isinstance(padding, int), "non-int padding" - runtimeLabel = ctx.get_next_label() + runtimeLabel = IRLabel("__runtime_entry") + ctx.add_entry_point(runtimeLabel) ctx.get_basic_block().append_instruction("deploy", memsize, runtimeLabel, padding) From 60558a49240602baf9dcefaf50f3bcb149d638ae Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 14:55:06 +0200 Subject: [PATCH 39/60] Refactor Venom code generation and add postample instructions --- vyper/compiler/phases.py | 3 +- vyper/venom/__init__.py | 22 +++++++++----- vyper/venom/function.py | 14 +++++++++ vyper/venom/ir_node_to_venom.py | 54 ++++++++++++++++++++------------- 4 files changed, 63 insertions(+), 30 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 28da3bcc3a..bb43fec467 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -196,7 +196,8 @@ def _ir_output(self): self.global_ctx, self.settings.optimize, self.settings.experimental_codegen ) if self.settings.experimental_codegen: - return [generate_ir(nodes[0]), generate_ir(nodes[1])] + ir, ir_runtime = generate_ir(nodes[0]) + return [(ir, ir_runtime), (None, ir_runtime)] else: return nodes diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 5a09f8378e..0c5f54bdcf 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -19,17 +19,15 @@ def generate_assembly_experimental( - ctx: IRFunction, optimize: Optional[OptimizationLevel] = None + ctxs: tuple[IRFunction], optimize: Optional[OptimizationLevel] = None ) -> list[str]: - compiler = VenomCompiler(ctx) + deploy_ctx, runtime_ctx = ctxs + compiler = VenomCompiler(runtime_ctx) return compiler.generate_evm(optimize is OptimizationLevel.NONE) -def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: - # Convert "old" IR to "new" IR - ctx = convert_ir_basicblock(ir) - - # Run passes on "new" IR +def _run_passes(ctx: IRFunction, optimize: Optional[OptimizationLevel] = None) -> None: + # Run passes on Venom IR # TODO: Add support for optimization levels while True: changes = 0 @@ -53,4 +51,12 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF if changes == 0: break - return ctx + +def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> tuple[IRFunction]: + # Convert "old" IR to "new" IR + ctx, ctx_runtime = convert_ir_basicblock(ir) + + _run_passes(ctx, optimize) + _run_passes(ctx_runtime, optimize) + + return ctx, ctx_runtime diff --git a/vyper/venom/function.py b/vyper/venom/function.py index daad9de75f..d64c392fda 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -119,6 +119,20 @@ def append_data(self, opcode: str, args: list[IROperand]) -> None: """ self.data_segment.append(IRInstruction(opcode, args)) # type: ignore + def addPostamples(self) -> None: + """ + Add postample instructions to function. + """ + # Add revert block + revert_bb = IRBasicBlock(IRLabel("__revert"), self) + revert_bb = self.append_basic_block(revert_bb) + revert_bb.append_instruction("revert", 0, 0) + + # Connect unterminated blocks to the next with a jump + for i, bb in enumerate(self.basic_blocks): + if not bb.is_terminated and i < len(self.basic_blocks) - 1: + bb.append_instruction("jmp", self.basic_blocks[i + 1].label) + @property def normalized(self) -> bool: """ diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 7086d4c8b5..62f321207a 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -87,19 +87,30 @@ def _get_symbols_common(a: dict, b: dict) -> dict: return ret -def convert_ir_basicblock(ir: IRnode) -> IRFunction: - global_function = IRFunction() - _convert_ir_basicblock(global_function, ir, {}, OrderedSet(), {}) +def _findIRnode(ir: IRnode, value: str) -> Optional[IRnode]: + if ir.value == value: + return ir + for arg in ir.args: + if isinstance(arg, IRnode): + ret = _findIRnode(arg, value) + if ret is not None: + return ret + return None - for i, bb in enumerate(global_function.basic_blocks): - if not bb.is_terminated and i < len(global_function.basic_blocks) - 1: - bb.append_instruction("jmp", global_function.basic_blocks[i + 1].label) - revert_bb = IRBasicBlock(IRLabel("__revert"), global_function) - revert_bb = global_function.append_basic_block(revert_bb) - revert_bb.append_instruction("revert", 0, 0) +def convert_ir_basicblock(ir: IRnode) -> IRFunction: + deploy_node = _findIRnode(ir, "deploy") + if deploy_node is not None: + deploy_code = IRFunction() + _convert_ir_basicblock(deploy_code, ir, {}, OrderedSet(), {}) + deploy_code.get_basic_block().append_instruction("stop") + ir = deploy_node.args[1] + + runtime_code = IRFunction() + _convert_ir_basicblock(runtime_code, ir, {}, OrderedSet(), {}) + runtime_code.addPostamples() - return global_function + return deploy_code, runtime_code def _convert_binary_op( @@ -279,21 +290,22 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value in ["pass", "stop", "return"]: pass elif ir.value == "deploy": - memsize = ir.args[0].value - ir_runtime = ir.args[1] - padding = ir.args[2].value - assert isinstance(memsize, int), "non-int memsize" - assert isinstance(padding, int), "non-int padding" + return None + # memsize = ir.args[0].value + # ir_runtime = ir.args[1] + # padding = ir.args[2].value + # assert isinstance(memsize, int), "non-int memsize" + # assert isinstance(padding, int), "non-int padding" - runtimeLabel = IRLabel("__runtime_entry") - ctx.add_entry_point(runtimeLabel) + # runtimeLabel = IRLabel("__runtime_entry") + # ctx.add_entry_point(runtimeLabel) - ctx.get_basic_block().append_instruction("deploy", memsize, runtimeLabel, padding) + # # ctx.get_basic_block().append_instruction("deploy", memsize, runtimeLabel, padding) - bb = IRBasicBlock(runtimeLabel, ctx) - ctx.append_basic_block(bb) + # bb = IRBasicBlock(runtimeLabel, ctx) + # ctx.append_basic_block(bb) - _convert_ir_basicblock(ctx, ir_runtime, symbols, variables, allocated_variables) + # _convert_ir_basicblock(ctx, ir_runtime, symbols, variables, allocated_variables) elif ir.value == "seq": func_t = ir.passthrough_metadata.get("func_t", None) if ir.is_self_call: From d2c74d4236fc3677b61369315680ddc2003f85b2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 15:30:33 +0200 Subject: [PATCH 40/60] Add ctor_mem_size and immutables_len to IRFunction --- vyper/venom/function.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index d64c392fda..1b3fd9d1c5 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -20,6 +20,8 @@ class IRFunction: name: IRLabel # symbol name entry_points: list[IRLabel] # entry points args: list + ctor_mem_size: Optional[int] + immutables_len: Optional[int] basic_blocks: list[IRBasicBlock] data_segment: list[IRInstruction] last_label: int @@ -31,6 +33,8 @@ def __init__(self, name: IRLabel = None) -> None: self.name = name self.entry_points = [] self.args = [] + self.ctor_mem_size = None + self.immutables_len = None self.basic_blocks = [] self.data_segment = [] self.last_label = 0 From 19bc41293a026c2483fdca2d4b5d36cd89da47e1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 15:30:48 +0200 Subject: [PATCH 41/60] Disable deploy logic in ir_node_to_venom.py --- vyper/venom/ir_node_to_venom.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 62f321207a..7dd6778c64 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -290,10 +290,12 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value in ["pass", "stop", "return"]: pass elif ir.value == "deploy": + ir_runtime = ir.args[1] + + ctx.ctor_mem_size = ir.args[0].value + ctx.immutables_len = ir.args[2].value + return None - # memsize = ir.args[0].value - # ir_runtime = ir.args[1] - # padding = ir.args[2].value # assert isinstance(memsize, int), "non-int memsize" # assert isinstance(padding, int), "non-int padding" From 56bfd9c7b7e88e8b55b96b06ba2266424d2b4c50 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 15:31:04 +0200 Subject: [PATCH 42/60] Refactor generate_assembly_experimental function signature --- vyper/venom/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 0c5f54bdcf..22c6c4ff7b 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -21,8 +21,7 @@ def generate_assembly_experimental( ctxs: tuple[IRFunction], optimize: Optional[OptimizationLevel] = None ) -> list[str]: - deploy_ctx, runtime_ctx = ctxs - compiler = VenomCompiler(runtime_ctx) + compiler = VenomCompiler(ctxs) return compiler.generate_evm(optimize is OptimizationLevel.NONE) From 9b90ac444ebd7d16616251a6395e704f158edb01 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 15:31:21 +0200 Subject: [PATCH 43/60] Refactor VenomCompiler class to support multiple contexts --- vyper/venom/venom_to_assembly.py | 81 +++++++++++++++++++------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 0c32c3b816..270e90456c 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -65,6 +65,8 @@ ] ) +_REVERT_POSTAMBLE = ["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"] + # TODO: "assembly" gets into the recursion due to how the original # IR was structured recursively in regards with the deploy instruction. @@ -75,13 +77,13 @@ # with the assembler. My suggestion is to let this be for now, and we can # refactor it later when we are finished phasing out the old IR. class VenomCompiler: - ctx: IRFunction + ctxs: list[IRFunction] label_counter = 0 visited_instructions: OrderedSet # {IRInstruction} visited_basicblocks: OrderedSet # {IRBasicBlock} - def __init__(self, ctx: IRFunction): - self.ctx = ctx + def __init__(self, ctxs: list[IRFunction]): + self.ctxs = ctxs self.label_counter = 0 self.visited_instructions = OrderedSet() self.visited_basicblocks = OrderedSet() @@ -91,8 +93,8 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: self.visited_basicblocks = OrderedSet() self.label_counter = 0 - stack = StackModel() asm: list[str] = [] + top_asm = asm # Before emitting the assembly, we need to make sure that the # CFG is normalized. Calling calculate_cfg() will denormalize IR (reset) @@ -101,41 +103,54 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: # assembly generation. # This is a side-effect of how dynamic jumps are temporarily being used # to support the O(1) dispatcher. -> look into calculate_cfg() - calculate_cfg(self.ctx) - NormalizationPass.run_pass(self.ctx) - calculate_liveness(self.ctx) - - assert self.ctx.normalized, "Non-normalized CFG!" - - self._generate_evm_for_basicblock_r(asm, self.ctx.basic_blocks[0], stack) + for ctx in self.ctxs: + if ctx is None: + continue - # Append postambles - revert_postamble = ["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"] - runtime = None - if isinstance(asm[-1], list) and isinstance(asm[-1][0], RuntimeHeader): - runtime = asm.pop() + calculate_cfg(ctx) + NormalizationPass.run_pass(ctx) + calculate_liveness(ctx) + + assert ctx.normalized, "Non-normalized CFG!" + + self._generate_evm_for_basicblock_r(asm, ctx.basic_blocks[0], StackModel()) + 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"] + ) + asm.extend(["_OFST", "_sym_subcode_size", ctx.immutables_len]) # stack: len + asm.extend(["_mem_deploy_start"]) # stack: len mem_ofst + asm.extend(["RETURN"]) + asm.extend(_REVERT_POSTAMBLE) + asm.append( + [RuntimeHeader("_sym_runtime_begin", ctx.ctor_mem_size, ctx.immutables_len)] + ) + asm = asm[-1] + else: + asm.extend(_REVERT_POSTAMBLE) - asm.extend(revert_postamble) - if runtime: - runtime.extend(revert_postamble) - asm.append(runtime) + # ctx = self.ctxs[1] + # self._generate_evm_for_basicblock_r(asm, ctx.basic_blocks[0], StackModel()) + # asm.extend(_REVERT_POSTAMBLE) - # Append data segment - data_segments: dict[Any, list[Any]] = dict() - for inst in self.ctx.data_segment: - if inst.opcode == "dbname": - 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}") + # Append data segment + data_segments: dict[Any, list[Any]] = dict() + for inst in ctx.data_segment: + if inst.opcode == "dbname": + 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}") - extent_point = asm if not isinstance(asm[-1], list) else asm[-1] - extent_point.extend([data_segments[label] for label in data_segments]) # type: ignore + extent_point = asm if not isinstance(asm[-1], list) else asm[-1] + extent_point.extend([data_segments[label] for label in data_segments]) # type: ignore - if no_optimize is False: - optimize_assembly(asm) + if no_optimize is False: + optimize_assembly(asm) - return asm + return top_asm def _stack_reorder( self, assembly: list, stack: StackModel, _stack_ops: OrderedSet[IRVariable] From 610beaf6c530227cef63d0cf8a0c8938c72a9bd8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 15:31:37 +0200 Subject: [PATCH 44/60] Fix generate_assembly_experimental arguments in test_duplicate_operands --- 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 a51992df67..b999d857a2 100644 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ b/tests/unit/compiler/venom/test_duplicate_operands.py @@ -22,6 +22,6 @@ def test_duplicate_operands(): bb.append_instruction("mul", sum, op) bb.append_instruction("stop") - asm = generate_assembly_experimental(ctx, OptimizationLevel.CODESIZE) + asm = generate_assembly_experimental((None, ctx), OptimizationLevel.CODESIZE) assert asm == ["PUSH1", 10, "DUP1", "DUP1", "DUP1", "ADD", "MUL", "STOP", "REVERT"] From f4516ea2645fb26e1a625dac4863aea702532b28 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 15:36:09 +0200 Subject: [PATCH 45/60] Remove "deploy" opcode from basicblock.py and venom_to_assembly.py --- vyper/venom/basicblock.py | 7 ++----- vyper/venom/ir_node_to_venom.py | 15 --------------- vyper/venom/venom_to_assembly.py | 18 ------------------ 3 files changed, 2 insertions(+), 38 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 9afaa5e6fd..4ece4c5c5d 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", "deploy", "stop"]) +BB_TERMINATORS = frozenset(["jmp", "djmp", "jnz", "ret", "return", "revert", "stop"]) VOLATILE_INSTRUCTIONS = frozenset( [ @@ -33,7 +33,6 @@ NO_OUTPUT_INSTRUCTIONS = frozenset( [ - "deploy", "mstore", "sstore", "dstore", @@ -56,9 +55,7 @@ ] ) -CFG_ALTERING_INSTRUCTIONS = frozenset( - ["jmp", "djmp", "jnz", "call", "staticcall", "invoke", "deploy"] -) +CFG_ALTERING_INSTRUCTIONS = frozenset(["jmp", "djmp", "jnz", "call", "staticcall", "invoke"]) if TYPE_CHECKING: from vyper.venom.function import IRFunction diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 7dd6778c64..abaae44d07 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -290,24 +290,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value in ["pass", "stop", "return"]: pass elif ir.value == "deploy": - ir_runtime = ir.args[1] - ctx.ctor_mem_size = ir.args[0].value ctx.immutables_len = ir.args[2].value - return None - # assert isinstance(memsize, int), "non-int memsize" - # assert isinstance(padding, int), "non-int padding" - - # runtimeLabel = IRLabel("__runtime_entry") - # ctx.add_entry_point(runtimeLabel) - - # # ctx.get_basic_block().append_instruction("deploy", memsize, runtimeLabel, padding) - - # bb = IRBasicBlock(runtimeLabel, ctx) - # ctx.append_basic_block(bb) - - # _convert_ir_basicblock(ctx, ir_runtime, symbols, variables, allocated_variables) elif ir.value == "seq": func_t = ir.passthrough_metadata.get("func_t", None) if ir.is_self_call: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 270e90456c..c6ad822202 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -131,10 +131,6 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: else: asm.extend(_REVERT_POSTAMBLE) - # ctx = self.ctxs[1] - # self._generate_evm_for_basicblock_r(asm, ctx.basic_blocks[0], StackModel()) - # asm.extend(_REVERT_POSTAMBLE) - # Append data segment data_segments: dict[Any, list[Any]] = dict() for inst in ctx.data_segment: @@ -412,20 +408,6 @@ 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 == "deploy": - memsize = inst.operands[0].value - padding = inst.operands[2].value - # TODO: fix this by removing deploy opcode altogether me move emition to ir translation - while assembly[-1] != "JUMPDEST": - assembly.pop() - assembly.extend( - ["_sym_subcode_size", "_sym_runtime_begin", "_mem_deploy_start", "CODECOPY"] - ) - assembly.extend(["_OFST", "_sym_subcode_size", padding]) # stack: len - assembly.extend(["_mem_deploy_start"]) # stack: len mem_ofst - assembly.extend(["RETURN"]) - assembly.append([RuntimeHeader("_sym_runtime_begin", memsize, padding)]) # type: ignore - assembly = assembly[-1] elif opcode == "iload": loc = inst.operands[0].value assembly.extend(["_OFST", "_mem_deploy_end", loc, "MLOAD"]) From a471864c898732e15b64d150fc7c0061e307e2bd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 15:36:36 +0200 Subject: [PATCH 46/60] Remove deprecated code for deploy handling "hack" --- vyper/venom/analysis.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 6dfc3c3d7c..eed579463e 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -19,27 +19,6 @@ def calculate_cfg(ctx: IRFunction) -> None: bb.cfg_out = OrderedSet() bb.out_vars = OrderedSet() - # TODO: This is a hack to support the old IR format where `deploy` is - # an instruction. in the future we should have two entry points, one - # for the initcode and one for the runtime code. - deploy_bb = None - after_deploy_bb = None - for i, bb in enumerate(ctx.basic_blocks): - if bb.instructions[0].opcode == "deploy": - deploy_bb = bb - after_deploy_bb = ctx.basic_blocks[i + 1] - break - - if deploy_bb is not None: - assert after_deploy_bb is not None, "No block after deploy block" - entry_block = after_deploy_bb - has_constructor = ctx.basic_blocks[0].instructions[0].opcode != "deploy" - if has_constructor: - deploy_bb.add_cfg_in(ctx.basic_blocks[0]) - entry_block.add_cfg_in(deploy_bb) - else: - entry_block = ctx.basic_blocks[0] - for bb in ctx.basic_blocks: assert len(bb.instructions) > 0, "Basic block should not be empty" last_inst = bb.instructions[-1] From 698ec0fc4f345be4ceab4896aafefa84c18c0271 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 15:44:45 +0200 Subject: [PATCH 47/60] Update type annotations in venom module --- vyper/venom/__init__.py | 10 ++++++---- vyper/venom/ir_node_to_venom.py | 2 +- vyper/venom/venom_to_assembly.py | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 22c6c4ff7b..2cbb5f7ae5 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -1,7 +1,7 @@ # maybe rename this `main.py` or `venom.py` # (can have an `__init__.py` which exposes the API). -from typing import Optional +from typing import Any, Optional from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel @@ -19,9 +19,9 @@ def generate_assembly_experimental( - ctxs: tuple[IRFunction], optimize: Optional[OptimizationLevel] = None + ctxs: tuple[IRFunction, IRFunction], optimize: Optional[OptimizationLevel] = None ) -> list[str]: - compiler = VenomCompiler(ctxs) + compiler = VenomCompiler(list(ctxs)) return compiler.generate_evm(optimize is OptimizationLevel.NONE) @@ -51,7 +51,9 @@ def _run_passes(ctx: IRFunction, optimize: Optional[OptimizationLevel] = None) - break -def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> tuple[IRFunction]: +def generate_ir( + ir: IRnode, optimize: Optional[OptimizationLevel] = None +) -> tuple[IRFunction, IRFunction]: # Convert "old" IR to "new" IR ctx, ctx_runtime = convert_ir_basicblock(ir) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index abaae44d07..cc6390c17d 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) -> IRFunction: +def convert_ir_basicblock(ir: IRnode) -> tuple[IRFunction, IRFunction]: deploy_node = _findIRnode(ir, "deploy") if deploy_node is not None: deploy_code = IRFunction() diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index c6ad822202..db547de7a5 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -93,7 +93,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: self.visited_basicblocks = OrderedSet() self.label_counter = 0 - asm: list[str] = [] + asm: list[Any] = [] top_asm = asm # Before emitting the assembly, we need to make sure that the From 25e764c2b6aab2dbadbe5d713811daf49834c393 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 16:01:18 +0200 Subject: [PATCH 48/60] Inline postable insertion --- vyper/venom/function.py | 14 -------------- vyper/venom/ir_node_to_venom.py | 11 ++++++++++- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 1b3fd9d1c5..43afc2fdfb 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -123,20 +123,6 @@ def append_data(self, opcode: str, args: list[IROperand]) -> None: """ self.data_segment.append(IRInstruction(opcode, args)) # type: ignore - def addPostamples(self) -> None: - """ - Add postample instructions to function. - """ - # Add revert block - revert_bb = IRBasicBlock(IRLabel("__revert"), self) - revert_bb = self.append_basic_block(revert_bb) - revert_bb.append_instruction("revert", 0, 0) - - # Connect unterminated blocks to the next with a jump - for i, bb in enumerate(self.basic_blocks): - if not bb.is_terminated and i < len(self.basic_blocks) - 1: - bb.append_instruction("jmp", self.basic_blocks[i + 1].label) - @property def normalized(self) -> bool: """ diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index cc6390c17d..33514d0cf1 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -108,7 +108,16 @@ def convert_ir_basicblock(ir: IRnode) -> tuple[IRFunction, IRFunction]: runtime_code = IRFunction() _convert_ir_basicblock(runtime_code, ir, {}, OrderedSet(), {}) - runtime_code.addPostamples() + + # Add revert block + # revert_bb = IRBasicBlock(IRLabel("__revert"), runtime_code) + # revert_bb = runtime_code.append_basic_block(revert_bb) + # revert_bb.append_instruction("revert", 0, 0) + + # Connect unterminated blocks to the next with a jump + for i, bb in enumerate(runtime_code.basic_blocks): + if not bb.is_terminated and i < len(runtime_code.basic_blocks) - 1: + bb.append_instruction("jmp", runtime_code.basic_blocks[i + 1].label) return deploy_code, runtime_code From 6cc9401d3d75c0da077a5e5bd0d8738d12a59a3f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 16:21:38 +0200 Subject: [PATCH 49/60] Fix deploy_code initialization in convert_ir_basicblock function --- 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 33514d0cf1..cc3cd00b35 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -100,6 +100,7 @@ def _findIRnode(ir: IRnode, value: str) -> Optional[IRnode]: def convert_ir_basicblock(ir: IRnode) -> tuple[IRFunction, IRFunction]: deploy_node = _findIRnode(ir, "deploy") + deploy_code = None if deploy_node is not None: deploy_code = IRFunction() _convert_ir_basicblock(deploy_code, ir, {}, OrderedSet(), {}) @@ -109,11 +110,6 @@ def convert_ir_basicblock(ir: IRnode) -> tuple[IRFunction, IRFunction]: runtime_code = IRFunction() _convert_ir_basicblock(runtime_code, ir, {}, OrderedSet(), {}) - # Add revert block - # revert_bb = IRBasicBlock(IRLabel("__revert"), runtime_code) - # revert_bb = runtime_code.append_basic_block(revert_bb) - # revert_bb.append_instruction("revert", 0, 0) - # Connect unterminated blocks to the next with a jump for i, bb in enumerate(runtime_code.basic_blocks): if not bb.is_terminated and i < len(runtime_code.basic_blocks) - 1: From 6ab1199a610948aa8ab94446dbf46528718f7292 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Dec 2023 16:30:21 +0200 Subject: [PATCH 50/60] sad :( --- 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 cc3cd00b35..a2fb9bee6c 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -115,7 +115,7 @@ def convert_ir_basicblock(ir: IRnode) -> tuple[IRFunction, IRFunction]: if not bb.is_terminated and i < len(runtime_code.basic_blocks) - 1: bb.append_instruction("jmp", runtime_code.basic_blocks[i + 1].label) - return deploy_code, runtime_code + return deploy_code, runtime_code # type: ignore def _convert_binary_op( From d075ff4485a50eea0b6b5139127a4fd12c374084 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Dec 2023 14:34:52 +0200 Subject: [PATCH 51/60] remove duplicate test --- .../compiler/venom/test_multi_entry_block.py | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/tests/unit/compiler/venom/test_multi_entry_block.py b/tests/unit/compiler/venom/test_multi_entry_block.py index bae894575a..6d8b074994 100644 --- a/tests/unit/compiler/venom/test_multi_entry_block.py +++ b/tests/unit/compiler/venom/test_multi_entry_block.py @@ -136,44 +136,3 @@ def test_multi_entry_block_with_dynamic_jump(): 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" - - -def test_multi_entry_block_with_dynamic_jump(): - ctx = IRFunction() - - finish_label = IRLabel("finish") - target_label = IRLabel("target") - block_1_label = IRLabel("block_1", ctx) - - bb = ctx.get_basic_block() - op = bb.append_instruction("store", 10) - acc = bb.append_instruction("add", op, op) - bb.append_instruction("djmp", acc, finish_label, block_1_label) - - block_1 = IRBasicBlock(block_1_label, ctx) - ctx.append_basic_block(block_1) - acc = block_1.append_instruction("add", acc, op) - op = block_1.append_instruction("store", 10) - block_1.append_instruction("mstore", acc, op) - block_1.append_instruction("jnz", acc, finish_label, target_label) - - target_bb = IRBasicBlock(target_label, ctx) - ctx.append_basic_block(target_bb) - target_bb.append_instruction("mul", acc, acc) - target_bb.append_instruction("jmp", finish_label) - - finish_bb = IRBasicBlock(finish_label, ctx) - ctx.append_basic_block(finish_bb) - finish_bb.append_instruction("stop") - - calculate_cfg(ctx) - assert not ctx.normalized, "CFG should not be normalized" - - NormalizationPass.run_pass(ctx) - assert ctx.normalized, "CFG should be normalized" - - 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" From 59abf8db7edd8ba560579146030ddd2f29f8bf70 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 23 Dec 2023 20:45:34 +0200 Subject: [PATCH 52/60] Refactor to handle None values in glue code not in VenomCompiler --- vyper/venom/__init__.py | 3 ++- vyper/venom/venom_to_assembly.py | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 2cbb5f7ae5..72848c6476 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -21,7 +21,8 @@ def generate_assembly_experimental( ctxs: tuple[IRFunction, IRFunction], optimize: Optional[OptimizationLevel] = None ) -> list[str]: - compiler = VenomCompiler(list(ctxs)) + ctxs = [ctx for ctx in ctxs if ctx is not None] + compiler = VenomCompiler(ctxs) return compiler.generate_evm(optimize is OptimizationLevel.NONE) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index db547de7a5..36444999f5 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -104,9 +104,6 @@ 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: - if ctx is None: - continue - calculate_cfg(ctx) NormalizationPass.run_pass(ctx) calculate_liveness(ctx) From dcf14c5e515ea8f134da7b9e85310a6815d42e4f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 23 Dec 2023 21:02:35 +0200 Subject: [PATCH 53/60] goosfraba --- vyper/venom/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 72848c6476..0c553d112b 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -21,8 +21,8 @@ def generate_assembly_experimental( ctxs: tuple[IRFunction, IRFunction], optimize: Optional[OptimizationLevel] = None ) -> list[str]: - ctxs = [ctx for ctx in ctxs if ctx is not None] - compiler = VenomCompiler(ctxs) + cleaned_ctxs = [ctx for ctx in ctxs if ctx is not None] + compiler = VenomCompiler(cleaned_ctxs) return compiler.generate_evm(optimize is OptimizationLevel.NONE) From 9c5b80d527b1f2790987965c3bf73378c33dd5bc Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Jan 2024 09:42:56 -0500 Subject: [PATCH 54/60] API cleanup --- .../compiler/venom/test_duplicate_operands.py | 6 +++--- vyper/compiler/phases.py | 21 ++++++++++--------- vyper/venom/__init__.py | 18 ++++++++++------ 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/tests/unit/compiler/venom/test_duplicate_operands.py b/tests/unit/compiler/venom/test_duplicate_operands.py index b999d857a2..e998d5be13 100644 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ b/tests/unit/compiler/venom/test_duplicate_operands.py @@ -18,10 +18,10 @@ def test_duplicate_operands(): 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) + sum_ = bb.append_instruction("add", op, op) + bb.append_instruction("mul", sum_, op) bb.append_instruction("stop") - asm = generate_assembly_experimental((None, ctx), OptimizationLevel.CODESIZE) + asm = generate_assembly_experimental(ctx, OptimizationLevel.CODESIZE) assert asm == ["PUSH1", 10, "DUP1", "DUP1", "DUP1", "ADD", "MUL", "STOP", "REVERT"] diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 0d4dd3d079..58100b2bfb 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -181,14 +181,9 @@ def global_ctx(self) -> ModuleT: @cached_property def _ir_output(self): # fetch both deployment and runtime IR - nodes = generate_ir_nodes( + return generate_ir_nodes( self.global_ctx, self.settings.optimize, self.settings.experimental_codegen ) - if self.settings.experimental_codegen: - ir, ir_runtime = generate_ir(nodes[0]) - return [(ir, ir_runtime), (None, ir_runtime)] - else: - return nodes @property def ir_nodes(self) -> IRnode: @@ -209,11 +204,17 @@ def function_signatures(self) -> dict[str, ContractFunctionT]: fs = self.vyper_module_folded.get_children(vy_ast.FunctionDef) return {f.name: f._metadata["func_type"] for f in fs} + @cached_property + def venom_functions(self): + return generate_ir(self.ir_nodes, self.settings.optimize) + @cached_property def assembly(self) -> list: if self.settings.experimental_codegen: + deploy_code, runtime_code = self.venom_functions + assert self.settings.optimize is not None # mypy hint return generate_assembly_experimental( - self.ir_nodes, self.settings.optimize # type: ignore + runtime_code, deploy_code=deploy_code, optimize=self.settings.optimize ) else: return generate_assembly(self.ir_nodes, self.settings.optimize) @@ -221,9 +222,9 @@ def assembly(self) -> list: @cached_property def assembly_runtime(self) -> list: if self.settings.experimental_codegen: - return generate_assembly_experimental( - self.ir_runtime, self.settings.optimize # type: ignore - ) + _, runtime_code = self.venom_functions + assert self.settings.optimize is not None # mypy hint + return generate_assembly_experimental(runtime_code, optimize=self.settings.optimize) else: return generate_assembly(self.ir_runtime, self.settings.optimize) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 2cbb5f7ae5..38fb9b4559 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -17,15 +17,23 @@ from vyper.venom.passes.dft import DFTPass from vyper.venom.venom_to_assembly import VenomCompiler +DEFAULT_OPT_LEVEL = OptimizationLevel.default() + def generate_assembly_experimental( - ctxs: tuple[IRFunction, IRFunction], optimize: Optional[OptimizationLevel] = None + runtime_code: IRFunction, + deploy_code: Optional[IRFunction] = None, + optimize: OptimizationLevel = DEFAULT_OPT_LEVEL, ) -> list[str]: - compiler = VenomCompiler(list(ctxs)) + functions = [runtime_code] + if deploy_code is not None: + functions.append(deploy_code) + + compiler = VenomCompiler(functions) return compiler.generate_evm(optimize is OptimizationLevel.NONE) -def _run_passes(ctx: IRFunction, optimize: Optional[OptimizationLevel] = None) -> None: +def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: # Run passes on Venom IR # TODO: Add support for optimization levels while True: @@ -51,9 +59,7 @@ def _run_passes(ctx: IRFunction, optimize: Optional[OptimizationLevel] = None) - break -def generate_ir( - ir: IRnode, optimize: Optional[OptimizationLevel] = None -) -> tuple[IRFunction, IRFunction]: +def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> tuple[IRFunction, IRFunction]: # Convert "old" IR to "new" IR ctx, ctx_runtime = convert_ir_basicblock(ir) From 8db8e29fa29bf6f574bb7d8d00d5dbfc38f7cb3e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Jan 2024 10:21:13 -0500 Subject: [PATCH 55/60] whitespace --- 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 38fb9b4559..53ee5d8008 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -17,8 +17,8 @@ from vyper.venom.passes.dft import DFTPass from vyper.venom.venom_to_assembly import VenomCompiler -DEFAULT_OPT_LEVEL = OptimizationLevel.default() +DEFAULT_OPT_LEVEL = OptimizationLevel.default() def generate_assembly_experimental( runtime_code: IRFunction, From 7ac92d8427ee5d27b62d4080a077a255f11f269a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Jan 2024 10:31:58 -0500 Subject: [PATCH 56/60] rename some variables with edit distance == 1 --- vyper/venom/__init__.py | 2 +- vyper/venom/ir_node_to_venom.py | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 53ee5d8008..38fb9b4559 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -17,9 +17,9 @@ from vyper.venom.passes.dft import DFTPass from vyper.venom.venom_to_assembly import VenomCompiler - DEFAULT_OPT_LEVEL = OptimizationLevel.default() + def generate_assembly_experimental( runtime_code: IRFunction, deploy_code: Optional[IRFunction] = None, diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a2fb9bee6c..c86d3a3d67 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -99,23 +99,23 @@ def _findIRnode(ir: IRnode, value: str) -> Optional[IRnode]: def convert_ir_basicblock(ir: IRnode) -> tuple[IRFunction, IRFunction]: - deploy_node = _findIRnode(ir, "deploy") - deploy_code = None - if deploy_node is not None: - deploy_code = IRFunction() - _convert_ir_basicblock(deploy_code, ir, {}, OrderedSet(), {}) - deploy_code.get_basic_block().append_instruction("stop") - ir = deploy_node.args[1] + deploy_ir = _findIRnode(ir, "deploy") + assert deploy_ir is not None - runtime_code = IRFunction() - _convert_ir_basicblock(runtime_code, ir, {}, OrderedSet(), {}) + 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_venom = IRFunction() + _convert_ir_basicblock(runtime_venom, runtime_ir, {}, OrderedSet(), {}) # Connect unterminated blocks to the next with a jump - for i, bb in enumerate(runtime_code.basic_blocks): - if not bb.is_terminated and i < len(runtime_code.basic_blocks) - 1: - bb.append_instruction("jmp", runtime_code.basic_blocks[i + 1].label) + 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) - return deploy_code, runtime_code # type: ignore + return deploy_venom, runtime_venom def _convert_binary_op( From 88f1fa3fdd70c9cd182a7d266c7e4a15e47562ba Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Jan 2024 10:56:09 -0500 Subject: [PATCH 57/60] add some variables --- .../compiler/venom/test_duplicate_operands.py | 2 +- vyper/venom/__init__.py | 2 +- vyper/venom/venom_to_assembly.py | 21 +++++++++++-------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/unit/compiler/venom/test_duplicate_operands.py b/tests/unit/compiler/venom/test_duplicate_operands.py index e998d5be13..b96c7f3351 100644 --- a/tests/unit/compiler/venom/test_duplicate_operands.py +++ b/tests/unit/compiler/venom/test_duplicate_operands.py @@ -22,6 +22,6 @@ def test_duplicate_operands(): bb.append_instruction("mul", sum_, op) bb.append_instruction("stop") - asm = generate_assembly_experimental(ctx, OptimizationLevel.CODESIZE) + asm = generate_assembly_experimental(ctx, optimize=OptimizationLevel.CODESIZE) assert asm == ["PUSH1", 10, "DUP1", "DUP1", "DUP1", "ADD", "MUL", "STOP", "REVERT"] diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 38fb9b4559..58860e503d 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -30,7 +30,7 @@ def generate_assembly_experimental( functions.append(deploy_code) compiler = VenomCompiler(functions) - return compiler.generate_evm(optimize is OptimizationLevel.NONE) + return compiler.generate_evm(optimize == OptimizationLevel.NONE) def _run_passes(ctx: IRFunction, optimize: OptimizationLevel) -> None: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 36444999f5..b9936981fa 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -111,7 +111,10 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: assert ctx.normalized, "Non-normalized CFG!" self._generate_evm_for_basicblock_r(asm, ctx.basic_blocks[0], StackModel()) - if ctx.immutables_len is not None and ctx.ctor_mem_size is not None: + + # TODO make this property on IRFunction + is_deploy = ctx.immutables_len is not None and ctx.ctor_mem_size is not None + if is_deploy: while asm[-1] != "JUMPDEST": asm.pop() asm.extend( @@ -121,10 +124,11 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: asm.extend(["_mem_deploy_start"]) # stack: len mem_ofst asm.extend(["RETURN"]) asm.extend(_REVERT_POSTAMBLE) - asm.append( - [RuntimeHeader("_sym_runtime_begin", ctx.ctor_mem_size, ctx.immutables_len)] - ) - asm = asm[-1] + runtime_asm = [ + RuntimeHeader("_sym_runtime_begin", ctx.ctor_mem_size, ctx.immutables_len) + ] + asm.append(runtime_asm) + asm = runtime_asm else: asm.extend(_REVERT_POSTAMBLE) @@ -137,11 +141,10 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: elif inst.opcode == "db": data_segments[label].append(f"_sym_{inst.operands[0].value}") - extent_point = asm if not isinstance(asm[-1], list) else asm[-1] - extent_point.extend([data_segments[label] for label in data_segments]) # type: ignore + asm.extend(list(data_segments.values())) - if no_optimize is False: - optimize_assembly(asm) + if no_optimize is False: + optimize_assembly(top_asm) return top_asm From 8f93bf1c6f8380541b459670748591e6b122aa5f Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Jan 2024 10:59:34 -0500 Subject: [PATCH 58/60] assert message --- 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 4ece4c5c5d..598b8af7d5 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -270,7 +270,7 @@ def _ir_operand_from_value(val: Any) -> IROperand: if isinstance(val, IROperand): return val - assert isinstance(val, int) + assert isinstance(val, int), val return IRLiteral(val) From fd78df9feaa5afa06cba5721028c349c7276052a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Jan 2024 11:12:05 -0500 Subject: [PATCH 59/60] fix minor bugs --- vyper/venom/__init__.py | 6 ++++-- vyper/venom/venom_to_assembly.py | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 58860e503d..570aba771a 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -25,9 +25,11 @@ def generate_assembly_experimental( deploy_code: Optional[IRFunction] = None, optimize: OptimizationLevel = DEFAULT_OPT_LEVEL, ) -> list[str]: - functions = [runtime_code] + # note: VenomCompiler is sensitive to the order of these! if deploy_code is not None: - functions.append(deploy_code) + functions = [deploy_code, runtime_code] + else: + functions = [runtime_code] compiler = VenomCompiler(functions) return compiler.generate_evm(optimize == OptimizationLevel.NONE) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index b9936981fa..926f8df8a3 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -113,8 +113,7 @@ 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 - is_deploy = ctx.immutables_len is not None and ctx.ctor_mem_size is not None - if is_deploy: + if ctx.immutables_len is not None and ctx.ctor_mem_size is not None: while asm[-1] != "JUMPDEST": asm.pop() asm.extend( @@ -133,7 +132,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: asm.extend(_REVERT_POSTAMBLE) # Append data segment - data_segments: dict[Any, list[Any]] = dict() + data_segments: dict = dict() for inst in ctx.data_segment: if inst.opcode == "dbname": label = inst.operands[0].value From fe0e1549c5d65906f483c43b3907d12c29646ba1 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 2 Jan 2024 11:20:55 -0500 Subject: [PATCH 60/60] fix cfg normalization code we no longer need special cases because of better handling now for deploy instruction. --- vyper/venom/function.py | 10 ++-------- vyper/venom/passes/normalization.py | 8 +++----- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 43afc2fdfb..9f26fa8ec0 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -138,16 +138,10 @@ def normalized(self) -> bool: if len(bb.cfg_in) <= 1: continue - # Check if there is a conditional jump at the end + # Check if there is a branching jump at the end # of one of the predecessors - # - # TODO: this check could be: - # `if len(in_bb.cfg_out) > 1: return False` - # but the cfg is currently not calculated "correctly" for - # the special deploy instruction. for in_bb in bb.cfg_in: - jump_inst = in_bb.instructions[-1] - if jump_inst.opcode in ("jnz", "djmp"): + if len(in_bb.cfg_out) > 1: return False # The function is normalized diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 43e8d47235..26699099b2 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -14,13 +14,11 @@ class NormalizationPass(IRPass): changes = 0 def _split_basic_block(self, bb: IRBasicBlock) -> None: - # Iterate over the predecessors of the basic block + # Iterate over the predecessors to this basic block for in_bb in list(bb.cfg_in): - jump_inst = in_bb.instructions[-1] assert bb in in_bb.cfg_out - - # Handle branching - if jump_inst.opcode in ("jnz", "djmp"): + # Handle branching in the predecessor bb + if len(in_bb.cfg_out) > 1: self._insert_split_basicblock(bb, in_bb) self.changes += 1 break