From 516592b48f10c9769d61e484155308d6dabb5f44 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 15 Jun 2023 12:30:32 +0300 Subject: [PATCH 001/471] basic blocks, functions, initial bb test repr --- vyper/codegen/ir_basicblock.py | 35 +++++++++++++++++++ vyper/codegen/ir_function.py | 50 ++++++++++++++++++++++++++ vyper/compiler/__init__.py | 1 + vyper/compiler/output.py | 4 +++ vyper/compiler/phases.py | 6 ++++ vyper/ir/ir_to_bb_pass.py | 64 ++++++++++++++++++++++++++++++++++ 6 files changed, 160 insertions(+) create mode 100644 vyper/codegen/ir_basicblock.py create mode 100644 vyper/codegen/ir_function.py create mode 100644 vyper/ir/ir_to_bb_pass.py diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py new file mode 100644 index 0000000000..27b1560bbb --- /dev/null +++ b/vyper/codegen/ir_basicblock.py @@ -0,0 +1,35 @@ +class IRInstruction: + opcode: str + operands: list + + def __init__(self, opcode, operands) -> None: + self.opcode = opcode + self.operands = operands + + def __repr__(self) -> str: + str = f"{self.opcode} " + for operand in self.operands: + str += f"{operand}" + if self.operands[-1] != operand: + str += ", " + return str + + +class IRBasicBlock: + label: str + parent: any # IRFunction + instructions: list[IRInstruction] + + def __init__(self, label, parent) -> None: + self.label = label + self.parent = parent + self.instructions = [] + + def append_instruction(self, instruction): + self.instructions.append(instruction) + + def __repr__(self) -> str: + str = f"{self.label}:\n" + for instruction in self.instructions: + str += f" {instruction}\n" + return str diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py new file mode 100644 index 0000000000..a814f8b390 --- /dev/null +++ b/vyper/codegen/ir_function.py @@ -0,0 +1,50 @@ +from vyper.codegen.ir_basicblock import IRBasicBlock + + +class IRFunction: + name: str # symbol name + basic_blocks: list + args: list + last_label: int + last_variable: int + + def __init__(self, name) -> None: + self.basic_blocks = [] + self.name = name + self.last_label = 0 + self.last_variable = 0 + + self.append_basic_block(IRBasicBlock(name, self)) + + def append_basic_block(self, bb): + """ + Append basic block to function. + """ + assert isinstance(bb, IRBasicBlock), f"append_basic_block takes IRBasicBlock, got '{bb}'" + self.basic_blocks.append(bb) + + def get_basic_block(self, label=None) -> IRBasicBlock: + """ + Get basic block by label. + If label is None, return the last basic block. + """ + if label is None: + return self.basic_blocks[-1] + for bb in self.basic_blocks: + if bb.label == label: + return bb + return None + + def get_next_label(self) -> str: + self.last_label += 1 + return f"{self.last_label}" + + def get_next_variable(self) -> str: + self.last_variable += 1 + return f"%{self.last_variable}" + + def __repr__(self) -> str: + str = f"IRFunction: {self.name}\n" + for bb in self.basic_blocks: + str += f"{bb}\n" + return str diff --git a/vyper/compiler/__init__.py b/vyper/compiler/__init__.py index 7be45ce832..fa4737acbc 100644 --- a/vyper/compiler/__init__.py +++ b/vyper/compiler/__init__.py @@ -32,6 +32,7 @@ "ir_runtime_dict": output.build_ir_runtime_dict_output, "method_identifiers": output.build_method_identifiers_output, "metadata": output.build_metadata_output, + "bb": output.build_basicblock_output, # requires assembly "abi": output.build_abi_output, "asm": output.build_asm_output, diff --git a/vyper/compiler/output.py b/vyper/compiler/output.py index f061bd8e18..8093ba2eae 100644 --- a/vyper/compiler/output.py +++ b/vyper/compiler/output.py @@ -216,6 +216,10 @@ def _build_asm(asm_list): return output_string +def build_basicblock_output(compiler_data: CompilerData) -> str: + return compiler_data.bb_output + + def build_source_map_output(compiler_data: CompilerData) -> OrderedDict: _, line_number_map = compile_ir.assembly_to_evm( compiler_data.assembly_runtime, diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 5156aa1bbd..1079c3372d 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -11,6 +11,7 @@ from vyper.semantics import set_data_positions, validate_semantics from vyper.semantics.types.function import ContractFunctionT from vyper.typing import InterfaceImports, StorageLayout +from vyper.ir.ir_to_bb_pass import convert_ir_basicblock class CompilerData: @@ -121,6 +122,11 @@ def _ir_output(self): # fetch both deployment and runtime IR return generate_ir_nodes(self.global_ctx, self.no_optimize) + @cached_property + def bb_output(self): + # fetch both deployment and runtime IR + return convert_ir_basicblock(self.global_ctx, self._ir_output[0]) + @property def ir_nodes(self) -> IRnode: ir, ir_runtime = self._ir_output diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py new file mode 100644 index 0000000000..7c6f9c3312 --- /dev/null +++ b/vyper/ir/ir_to_bb_pass.py @@ -0,0 +1,64 @@ +from vyper.codegen.global_context import GlobalContext +from vyper.codegen.ir_node import IRnode +from vyper.codegen.ir_function import IRFunction +from vyper.codegen.ir_basicblock import IRInstruction +from vyper.codegen.ir_basicblock import IRBasicBlock +from vyper.evm.opcodes import get_opcodes + +_symbols = {} + +def convert_ir_basicblock(ctx: GlobalContext, ir): + global_function = IRFunction("global") + _convert_ir_basicblock(global_function, ir) + return global_function + + +def _convert_ir_basicblock(ctx: IRFunction, ir): + if isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): + _convert_ir_opcode(ctx, ir) + elif ir.value == "deploy": + _convert_ir_basicblock(ctx, ir.args[1]) + elif ir.value == "seq": + _convert_ir_seq_basicblock(ctx, ir.args) + elif ir.value == "if": + cond = ir.args[0] + _convert_ir_basicblock(ctx, cond) + _convert_ir_basicblock(ctx, ir.args[1]) + elif ir.value == "with": + _convert_ir_basicblock(ctx, ir.args[1]) # initialization + _convert_ir_basicblock(ctx, ir.args[2]) # body + elif ir.value == "le": + # args = [] + # for arg in ir.args: + # if isinstance(arg, str) and arg not in _symbols: + # _symbols[arg] = ctx.get_next_label() + # args.append(_symbols[arg]) + # else: + # args.append(arg) + _convert_ir_basicblock(ctx, ir.args[0]) + _convert_ir_basicblock(ctx, ir.args[1]) + + inst = IRInstruction("LE", ir.args) + ctx.get_basic_block().append_instruction(inst) + elif ir.value == "calldatasize": + ctx.get_basic_block().append_instruction(IRInstruction("CALLDATASIZE")) + elif ir.value == "goto": + inst = IRInstruction("JUMP", ir.args) + ctx.get_basic_block().append_instruction(inst) + # else: + # raise Exception(f"Unknown IR node: {ir}") + + +def _convert_ir_opcode(ctx: IRFunction, ir: IRnode): + opcode = ir.value.upper() + instruction = IRInstruction(opcode, ir.args) + ctx.get_basic_block().append_instruction(instruction) + pass + + +def _convert_ir_seq_basicblock(ctx: IRFunction, seq_args: list[IRnode]): + bb = IRBasicBlock(ctx.get_next_label(), ctx) + ctx.append_basic_block(bb) + + for ir_node in seq_args: + _convert_ir_basicblock(ctx, ir_node) From bea03e26137a44a4003c9d8f59f3f34585eac259 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Jun 2023 12:08:17 +0300 Subject: [PATCH 002/471] intrinsic functions --- vyper/codegen/ir_function.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index a814f8b390..0feda6ae38 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -1,16 +1,23 @@ from vyper.codegen.ir_basicblock import IRBasicBlock -class IRFunction: +class IRFunctionBase: name: str # symbol name - basic_blocks: list args: list + + def __init__(self, name, args=[]) -> None: + self.name = name + self.args = args + + +class IRFunction(IRFunctionBase): + basic_blocks: list last_label: int last_variable: int def __init__(self, name) -> None: + super().__init__(name) self.basic_blocks = [] - self.name = name self.last_label = 0 self.last_variable = 0 @@ -43,8 +50,22 @@ def get_next_variable(self) -> str: self.last_variable += 1 return f"%{self.last_variable}" + def get_last_variable(self) -> str: + return f"%{self.last_variable}" + def __repr__(self) -> str: str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: str += f"{bb}\n" return str + + +class IRFunctionIntrinsic(IRFunctionBase): + """ + Intrinsic function, to represent sertain instructions of EVM that + are directly emmitted by the compiler frontend to the s-expression IR + """ + + def __repr__(self) -> str: + args = ", ".join([str(arg) for arg in self.args]) + return f"{self.name}({args})" From 7568019fd3f92dfa67ea62a80057743b8edc9bcd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Jun 2023 12:08:38 +0300 Subject: [PATCH 003/471] return values to instructions --- vyper/codegen/ir_basicblock.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 27b1560bbb..bd9735aacc 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,18 +1,20 @@ class IRInstruction: opcode: str operands: list + ret: str - def __init__(self, opcode, operands) -> None: + def __init__(self, opcode, operands, ret=None) -> None: self.opcode = opcode self.operands = operands + self.ret = ret def __repr__(self) -> str: - str = f"{self.opcode} " - for operand in self.operands: - str += f"{operand}" - if self.operands[-1] != operand: - str += ", " - return str + s = "" + if self.ret: + s += f"{self.ret} = " + s += f"{self.opcode} " + operands = ", ".join([str(op) for op in self.operands]) + return s + operands class IRBasicBlock: From 641edff920844bb78188aec14db66c88546e3974 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Jun 2023 12:08:58 +0300 Subject: [PATCH 004/471] more ir convertion --- vyper/ir/ir_to_bb_pass.py | 89 ++++++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 10 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 7c6f9c3312..1944f711aa 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,6 +1,6 @@ from vyper.codegen.global_context import GlobalContext from vyper.codegen.ir_node import IRnode -from vyper.codegen.ir_function import IRFunction +from vyper.codegen.ir_function import IRFunction, IRFunctionIntrinsic from vyper.codegen.ir_basicblock import IRInstruction from vyper.codegen.ir_basicblock import IRBasicBlock from vyper.evm.opcodes import get_opcodes @@ -14,9 +14,7 @@ def convert_ir_basicblock(ctx: GlobalContext, ir): def _convert_ir_basicblock(ctx: IRFunction, ir): - if isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): - _convert_ir_opcode(ctx, ir) - elif ir.value == "deploy": + if ir.value == "deploy": _convert_ir_basicblock(ctx, ir.args[1]) elif ir.value == "seq": _convert_ir_seq_basicblock(ctx, ir.args) @@ -35,22 +33,93 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): # args.append(_symbols[arg]) # else: # args.append(arg) - _convert_ir_basicblock(ctx, ir.args[0]) - _convert_ir_basicblock(ctx, ir.args[1]) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) + args = [arg_0, arg_1] - inst = IRInstruction("LE", ir.args) + ret = ctx.get_next_variable() + + inst = IRInstruction("le", args, ret) ctx.get_basic_block().append_instruction(inst) - elif ir.value == "calldatasize": - ctx.get_basic_block().append_instruction(IRInstruction("CALLDATASIZE")) + return ret + elif ir.value == "iszero": + # args = [] + # for arg in ir.args: + # if isinstance(arg, str) and arg not in _symbols: + # _symbols[arg] = ctx.get_next_label() + # args.append(_symbols[arg]) + # else: + # args.append(arg) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + args = [arg_0] + + ret = ctx.get_next_variable() + + inst = IRInstruction("iszero", args, ret) + ctx.get_basic_block().append_instruction(inst) + return ret + elif ir.value == "shr": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) + args = [arg_0, arg_1] + + ret = ctx.get_next_variable() + + inst = IRInstruction("shr", args, ret) + ctx.get_basic_block().append_instruction(inst) + return ret + elif ir.value == "xor": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) + args = [arg_0, arg_1] + + ret = ctx.get_next_variable() + + inst = IRInstruction("xor", args, ret) + ctx.get_basic_block().append_instruction(inst) + return ret elif ir.value == "goto": - inst = IRInstruction("JUMP", ir.args) + inst = IRInstruction("br", ir.args) ctx.get_basic_block().append_instruction(inst) + elif ir.value == "calldatasize": + ret = ctx.get_next_variable() + func = IRFunctionIntrinsic("calldatasize", []) + inst = IRInstruction("call", [func], ret) + ctx.get_basic_block().append_instruction(inst) + return ret + elif ir.value == "calldataload": + ret = ctx.get_next_variable() + func = IRFunctionIntrinsic("calldataload", [ir.args[0]]) + inst = IRInstruction("call", [func], ret) + ctx.get_basic_block().append_instruction(inst) + return ret + elif ir.value == "callvalue": + ret = ctx.get_next_variable() + func = IRFunctionIntrinsic("callvalue", []) + inst = IRInstruction("call", [func], ret) + ctx.get_basic_block().append_instruction(inst) + return ret + elif ir.value == "assert": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + + ret = ctx.get_next_variable() + func = IRFunctionIntrinsic("assert", [arg_0]) + inst = IRInstruction("call", [func], ret) + ctx.get_basic_block().append_instruction(inst) + return ret + elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): + _convert_ir_opcode(ctx, ir) + elif ir.is_literal: + return ir.value # else: # raise Exception(f"Unknown IR node: {ir}") def _convert_ir_opcode(ctx: IRFunction, ir: IRnode): opcode = ir.value.upper() + for arg in ir.args: + if isinstance(arg, IRnode): + _convert_ir_basicblock(ctx, arg) instruction = IRInstruction(opcode, ir.args) ctx.get_basic_block().append_instruction(instruction) pass From cf6bf628cde67c8f530b07fa99b45c23a8c2a54e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Jun 2023 13:05:55 +0300 Subject: [PATCH 005/471] debug info --- vyper/codegen/ir_basicblock.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index bd9735aacc..ef61dc8d7e 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,12 +1,26 @@ +class IRDebugInfo: + line_no: int + src: str + + def __init__(self, line_no, src) -> None: + self.line_no = line_no + self.src = src + + def __repr__(self) -> str: + return f"# {self.line_no} {self.src}" + + class IRInstruction: opcode: str operands: list ret: str + dbg: IRDebugInfo - def __init__(self, opcode, operands, ret=None) -> None: + def __init__(self, opcode: str, operands: list, ret=None, dbg: IRDebugInfo = None) -> None: self.opcode = opcode self.operands = operands self.ret = ret + self.dbg = dbg def __repr__(self) -> str: s = "" @@ -14,6 +28,8 @@ def __repr__(self) -> str: s += f"{self.ret} = " s += f"{self.opcode} " operands = ", ".join([str(op) for op in self.operands]) + if self.dbg: + return s + operands + f" {self.dbg}" return s + operands From 87d49fdd734167c969c552eb4cb02278519ac90f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Jun 2023 13:22:58 +0300 Subject: [PATCH 006/471] better formating --- vyper/codegen/ir_basicblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index ef61dc8d7e..4f44003071 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -7,7 +7,7 @@ def __init__(self, line_no, src) -> None: self.src = src def __repr__(self) -> str: - return f"# {self.line_no} {self.src}" + return f"\t# line {self.line_no}: {self.src}".expandtabs(20) class IRInstruction: From 03e1cd2dc22d96f3e890f822023b858d0135b48c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Jun 2023 13:23:13 +0300 Subject: [PATCH 007/471] more instructions --- vyper/ir/ir_to_bb_pass.py | 47 +++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 1944f711aa..b73d853078 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,7 +1,7 @@ from vyper.codegen.global_context import GlobalContext from vyper.codegen.ir_node import IRnode from vyper.codegen.ir_function import IRFunction, IRFunctionIntrinsic -from vyper.codegen.ir_basicblock import IRInstruction +from vyper.codegen.ir_basicblock import IRInstruction, IRDebugInfo from vyper.codegen.ir_basicblock import IRBasicBlock from vyper.evm.opcodes import get_opcodes @@ -23,7 +23,16 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): _convert_ir_basicblock(ctx, cond) _convert_ir_basicblock(ctx, ir.args[1]) elif ir.value == "with": - _convert_ir_basicblock(ctx, ir.args[1]) # initialization + ret = _convert_ir_basicblock(ctx, ir.args[1]) # initialization + + sym = ir.args[0] + # FIXME: How do I validate that the IR is indeed a symbol? + _symbols[sym.value] = ctx.get_next_variable() + + inst = IRInstruction("load", [_symbols[sym.value], ret], None, + IRDebugInfo(ir.source_pos, f"symbol: {sym.value}")) + ctx.get_basic_block().append_instruction(inst) + _convert_ir_basicblock(ctx, ir.args[2]) # body elif ir.value == "le": # args = [] @@ -42,6 +51,23 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): inst = IRInstruction("le", args, ret) ctx.get_basic_block().append_instruction(inst) return ret + elif ir.value == "ge": + # args = [] + # for arg in ir.args: + # if isinstance(arg, str) and arg not in _symbols: + # _symbols[arg] = ctx.get_next_label() + # args.append(_symbols[arg]) + # else: + # args.append(arg) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) + args = [arg_0, arg_1] + + ret = ctx.get_next_variable() + + inst = IRInstruction("ge", args, ret) + ctx.get_basic_block().append_instruction(inst) + return ret elif ir.value == "iszero": # args = [] # for arg in ir.args: @@ -107,12 +133,25 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): inst = IRInstruction("call", [func], ret) ctx.get_basic_block().append_instruction(inst) return ret + elif ir.value == "label": + label = ir.args[0] + bb = IRBasicBlock(label, ctx) + ctx.append_basic_block(bb) + _convert_ir_basicblock(ctx, ir.args[2]) + elif ir.value == "return": + pass + elif ir.value == "exit_to": + pass + elif ir.value == "pass": + pass elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): _convert_ir_opcode(ctx, ir) + elif isinstance(ir.value, str) and ir.value in _symbols: + return _symbols[ir.value] elif ir.is_literal: return ir.value - # else: - # raise Exception(f"Unknown IR node: {ir}") + else: + raise Exception(f"Unknown IR node: {ir}") def _convert_ir_opcode(ctx: IRFunction, ir: IRnode): From 5cd80115978bdecca32d2b2b83616edb2ae87ce7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Jun 2023 13:32:24 +0300 Subject: [PATCH 008/471] refactor --- vyper/ir/ir_to_bb_pass.py | 73 +++++++-------------------------------- 1 file changed, 12 insertions(+), 61 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index b73d853078..97a98ff51b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -12,6 +12,16 @@ def convert_ir_basicblock(ctx: GlobalContext, ir): _convert_ir_basicblock(global_function, ir) return global_function +def _convert_binary_op(ctx: IRFunction, ir): + arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) + args = [arg_0, arg_1] + + ret = ctx.get_next_variable() + + inst = IRInstruction(ir.value, args, ret) + ctx.get_basic_block().append_instruction(inst) + return ret def _convert_ir_basicblock(ctx: IRFunction, ir): if ir.value == "deploy": @@ -34,48 +44,9 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): ctx.get_basic_block().append_instruction(inst) _convert_ir_basicblock(ctx, ir.args[2]) # body - elif ir.value == "le": - # args = [] - # for arg in ir.args: - # if isinstance(arg, str) and arg not in _symbols: - # _symbols[arg] = ctx.get_next_label() - # args.append(_symbols[arg]) - # else: - # args.append(arg) - arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) - args = [arg_0, arg_1] - - ret = ctx.get_next_variable() - - inst = IRInstruction("le", args, ret) - ctx.get_basic_block().append_instruction(inst) - return ret - elif ir.value == "ge": - # args = [] - # for arg in ir.args: - # if isinstance(arg, str) and arg not in _symbols: - # _symbols[arg] = ctx.get_next_label() - # args.append(_symbols[arg]) - # else: - # args.append(arg) - arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) - args = [arg_0, arg_1] - - ret = ctx.get_next_variable() - - inst = IRInstruction("ge", args, ret) - ctx.get_basic_block().append_instruction(inst) - return ret + elif ir.value in ["le", "ge", "shr", "xor"]: + return _convert_binary_op(ctx, ir) elif ir.value == "iszero": - # args = [] - # for arg in ir.args: - # if isinstance(arg, str) and arg not in _symbols: - # _symbols[arg] = ctx.get_next_label() - # args.append(_symbols[arg]) - # else: - # args.append(arg) arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) args = [arg_0] @@ -84,26 +55,6 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): inst = IRInstruction("iszero", args, ret) ctx.get_basic_block().append_instruction(inst) return ret - elif ir.value == "shr": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) - args = [arg_0, arg_1] - - ret = ctx.get_next_variable() - - inst = IRInstruction("shr", args, ret) - ctx.get_basic_block().append_instruction(inst) - return ret - elif ir.value == "xor": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) - args = [arg_0, arg_1] - - ret = ctx.get_next_variable() - - inst = IRInstruction("xor", args, ret) - ctx.get_basic_block().append_instruction(inst) - return ret elif ir.value == "goto": inst = IRInstruction("br", ir.args) ctx.get_basic_block().append_instruction(inst) From d1c6c147a4b3a4c8abe746f819df5de7a6c1fb4c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Jun 2023 13:52:26 +0300 Subject: [PATCH 009/471] mstore implemtation --- vyper/codegen/ir_basicblock.py | 3 ++- vyper/ir/ir_to_bb_pass.py | 38 ++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 4f44003071..baf9e28788 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -7,7 +7,8 @@ def __init__(self, line_no, src) -> None: self.src = src def __repr__(self) -> str: - return f"\t# line {self.line_no}: {self.src}".expandtabs(20) + src = self.src if self.src else "" + return f"\t# line {self.line_no}: {src}".expandtabs(20) class IRInstruction: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 97a98ff51b..3714c9f60e 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -7,11 +7,13 @@ _symbols = {} + def convert_ir_basicblock(ctx: GlobalContext, ir): global_function = IRFunction("global") _convert_ir_basicblock(global_function, ir) return global_function + def _convert_binary_op(ctx: IRFunction, ir): arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) @@ -23,11 +25,13 @@ def _convert_binary_op(ctx: IRFunction, ir): ctx.get_basic_block().append_instruction(inst) return ret + def _convert_ir_basicblock(ctx: IRFunction, ir): if ir.value == "deploy": _convert_ir_basicblock(ctx, ir.args[1]) elif ir.value == "seq": - _convert_ir_seq_basicblock(ctx, ir.args) + for ir_node in ir.args: + _convert_ir_basicblock(ctx, ir_node) elif ir.value == "if": cond = ir.args[0] _convert_ir_basicblock(ctx, cond) @@ -39,8 +43,12 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): # FIXME: How do I validate that the IR is indeed a symbol? _symbols[sym.value] = ctx.get_next_variable() - inst = IRInstruction("load", [_symbols[sym.value], ret], None, - IRDebugInfo(ir.source_pos, f"symbol: {sym.value}")) + inst = IRInstruction( + "load", + [_symbols[sym.value], ret], + None, + IRDebugInfo(ir.source_pos, f"symbol: {sym.value}"), + ) ctx.get_basic_block().append_instruction(inst) _convert_ir_basicblock(ctx, ir.args[2]) # body @@ -87,14 +95,23 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): elif ir.value == "label": label = ir.args[0] bb = IRBasicBlock(label, ctx) - ctx.append_basic_block(bb) + ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2]) elif ir.value == "return": - pass + pass elif ir.value == "exit_to": pass elif ir.value == "pass": - pass + pass + elif ir.value == "mstore": + sym = ir.args[0] + new_var = ctx.get_next_variable() + _symbols[f"&{sym.value}"] = new_var + assert ir.args[1].is_literal, "mstore expects a literal as second argument" + inst = IRInstruction( + "load", [new_var, ir.args[1].value], None, IRDebugInfo(ir.source_pos[0], ir.annotation) + ) + ctx.get_basic_block().append_instruction(inst) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): _convert_ir_opcode(ctx, ir) elif isinstance(ir.value, str) and ir.value in _symbols: @@ -112,12 +129,3 @@ def _convert_ir_opcode(ctx: IRFunction, ir: IRnode): _convert_ir_basicblock(ctx, arg) instruction = IRInstruction(opcode, ir.args) ctx.get_basic_block().append_instruction(instruction) - pass - - -def _convert_ir_seq_basicblock(ctx: IRFunction, seq_args: list[IRnode]): - bb = IRBasicBlock(ctx.get_next_label(), ctx) - ctx.append_basic_block(bb) - - for ir_node in seq_args: - _convert_ir_basicblock(ctx, ir_node) From 49a0a32faa687e88fcb977cb57a134556604562c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 16 Jun 2023 13:58:27 +0300 Subject: [PATCH 010/471] add some docs --- vyper/codegen/ir_basicblock.py | 34 ++++++++++++++++++++++++++++++++++ vyper/codegen/ir_function.py | 8 ++++++++ 2 files changed, 42 insertions(+) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index baf9e28788..4994e80d7e 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,4 +1,9 @@ class IRDebugInfo: + """ + IRDebugInfo represents debug information in IR, used to annotate IR instructions + with source code information when printing IR. + """ + line_no: int src: str @@ -12,6 +17,13 @@ def __repr__(self) -> str: class IRInstruction: + """ + IRInstruction represents an instruction in IR. Each instruction has an opcode, + operands, and return value. For example, the following IR instruction: + %1 = add %0, 1 + has opcode "add", operands ["%0", "1"], and return value "%1". + """ + opcode: str operands: list ret: str @@ -35,6 +47,28 @@ def __repr__(self) -> str: class IRBasicBlock: + """ + IRBasicBlock represents a basic block in IR. Each basic block has a label and + a list of instructions, while belonging to a function. + + The following IR code: + %1 = add %0, 1 + %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")) + + The label of a basic block is used to refer to it from other basic blocks in order + to branch to it. + + The parent of a basic block is the function it belongs to. + + The instructions of a basic block are executed sequentially, and the last instruction + of a basic block is always a terminator instruction, which is used to branch to other + basic blocks. + """ + label: str parent: any # IRFunction instructions: list[IRInstruction] diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 0feda6ae38..acdfc0e075 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -2,6 +2,10 @@ class IRFunctionBase: + """ + Base class for IRFunction and IRFunctionIntrinsic + """ + name: str # symbol name args: list @@ -11,6 +15,10 @@ def __init__(self, name, args=[]) -> None: class IRFunction(IRFunctionBase): + """ + Function that contains basic blocks. + """ + basic_blocks: list last_label: int last_variable: int From 41ea9c5592d14546d4c3fbfa942a0c81b7c4b31b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 17 Jun 2023 14:23:45 +0300 Subject: [PATCH 011/471] slaving to the linter --- vyper/codegen/ir_basicblock.py | 20 +++++++++++++------- vyper/codegen/ir_function.py | 11 ++++++----- vyper/ir/ir_to_bb_pass.py | 27 +++++++++++++++++---------- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 4994e80d7e..e7e02fa705 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,3 +1,9 @@ +from typing import Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from vyper.codegen.ir_function import IRFunction + + class IRDebugInfo: """ IRDebugInfo represents debug information in IR, used to annotate IR instructions @@ -7,7 +13,7 @@ class IRDebugInfo: line_no: int src: str - def __init__(self, line_no, src) -> None: + def __init__(self, line_no: int, src: str) -> None: self.line_no = line_no self.src = src @@ -26,10 +32,10 @@ class IRInstruction: opcode: str operands: list - ret: str - dbg: IRDebugInfo + ret: Optional[str] + dbg: Optional[IRDebugInfo] - def __init__(self, opcode: str, operands: list, ret=None, dbg: IRDebugInfo = None) -> None: + def __init__(self, opcode: str, operands: list, ret: str = None, dbg: IRDebugInfo = None): self.opcode = opcode self.operands = operands self.ret = ret @@ -70,15 +76,15 @@ class IRBasicBlock: """ label: str - parent: any # IRFunction + parent: "IRFunction" instructions: list[IRInstruction] - def __init__(self, label, parent) -> None: + def __init__(self, label: str, parent: "IRFunction") -> None: self.label = label self.parent = parent self.instructions = [] - def append_instruction(self, instruction): + def append_instruction(self, instruction: IRInstruction) -> None: self.instructions.append(instruction) def __repr__(self) -> str: diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index acdfc0e075..0194d89aca 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -1,3 +1,4 @@ +from typing import Optional from vyper.codegen.ir_basicblock import IRBasicBlock @@ -9,7 +10,7 @@ class IRFunctionBase: name: str # symbol name args: list - def __init__(self, name, args=[]) -> None: + def __init__(self, name: str, args: list = []) -> None: self.name = name self.args = args @@ -23,7 +24,7 @@ class IRFunction(IRFunctionBase): last_label: int last_variable: int - def __init__(self, name) -> None: + def __init__(self, name: str) -> None: super().__init__(name) self.basic_blocks = [] self.last_label = 0 @@ -31,14 +32,14 @@ def __init__(self, name) -> None: self.append_basic_block(IRBasicBlock(name, self)) - def append_basic_block(self, bb): + def append_basic_block(self, bb: IRBasicBlock) -> None: """ Append basic block to function. """ assert isinstance(bb, IRBasicBlock), f"append_basic_block takes IRBasicBlock, got '{bb}'" self.basic_blocks.append(bb) - def get_basic_block(self, label=None) -> IRBasicBlock: + def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: """ Get basic block by label. If label is None, return the last basic block. @@ -48,7 +49,7 @@ def get_basic_block(self, label=None) -> IRBasicBlock: for bb in self.basic_blocks: if bb.label == label: return bb - return None + assert False, f"Basic block '{label}' not found" def get_next_label(self) -> str: self.last_label += 1 diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 3714c9f60e..6b9f9e2202 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,3 +1,4 @@ +from typing import Optional, Union from vyper.codegen.global_context import GlobalContext from vyper.codegen.ir_node import IRnode from vyper.codegen.ir_function import IRFunction, IRFunctionIntrinsic @@ -8,25 +9,25 @@ _symbols = {} -def convert_ir_basicblock(ctx: GlobalContext, ir): +def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: global_function = IRFunction("global") _convert_ir_basicblock(global_function, ir) return global_function -def _convert_binary_op(ctx: IRFunction, ir): +def _convert_binary_op(ctx: IRFunction, ir: IRnode) -> str: arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) args = [arg_0, arg_1] ret = ctx.get_next_variable() - inst = IRInstruction(ir.value, args, ret) + inst = IRInstruction(str(ir.value), args, ret) ctx.get_basic_block().append_instruction(inst) return ret -def _convert_ir_basicblock(ctx: IRFunction, ir): +def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, int]]: if ir.value == "deploy": _convert_ir_basicblock(ctx, ir.args[1]) elif ir.value == "seq": @@ -42,12 +43,12 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): sym = ir.args[0] # FIXME: How do I validate that the IR is indeed a symbol? _symbols[sym.value] = ctx.get_next_variable() - + first_pos = ir.source_pos[0] if ir.source_pos else None inst = IRInstruction( "load", [_symbols[sym.value], ret], None, - IRDebugInfo(ir.source_pos, f"symbol: {sym.value}"), + IRDebugInfo(first_pos or 0, f"symbol: {sym.value}"), ) ctx.get_basic_block().append_instruction(inst) @@ -93,7 +94,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "label": - label = ir.args[0] + label = str(ir.args[0].value) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2]) @@ -108,8 +109,12 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): new_var = ctx.get_next_variable() _symbols[f"&{sym.value}"] = new_var assert ir.args[1].is_literal, "mstore expects a literal as second argument" + first_pos = ir.source_pos[0] if ir.source_pos else None inst = IRInstruction( - "load", [new_var, ir.args[1].value], None, IRDebugInfo(ir.source_pos[0], ir.annotation) + "load", + [new_var, ir.args[1].value], + None, + IRDebugInfo(first_pos or 0, ir.annotation or ""), ) ctx.get_basic_block().append_instruction(inst) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): @@ -121,9 +126,11 @@ def _convert_ir_basicblock(ctx: IRFunction, ir): else: raise Exception(f"Unknown IR node: {ir}") + return None + -def _convert_ir_opcode(ctx: IRFunction, ir: IRnode): - opcode = ir.value.upper() +def _convert_ir_opcode(ctx: IRFunction, ir: IRnode) -> None: + opcode = str(ir.value).upper() for arg in ir.args: if isinstance(arg, IRnode): _convert_ir_basicblock(ctx, arg) From 63d9ab6803904e848b2edb1fe2ac32c4eeb0bfe7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 26 Jun 2023 09:43:39 +0300 Subject: [PATCH 012/471] split bb at branch point --- vyper/codegen/ir_function.py | 7 ++++++- vyper/ir/ir_to_bb_pass.py | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 0194d89aca..43c11b0950 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -37,7 +37,12 @@ def append_basic_block(self, bb: IRBasicBlock) -> None: Append basic block to function. """ assert isinstance(bb, IRBasicBlock), f"append_basic_block takes IRBasicBlock, got '{bb}'" - self.basic_blocks.append(bb) + last_bb = self.basic_blocks[-1] if len(self.basic_blocks) > 0 else None + if last_bb and len(last_bb.instructions) == 0: + # last basic block is empty, replace it + self.basic_blocks[-1] = bb + else: + self.basic_blocks.append(bb) def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: """ diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 6b9f9e2202..5a77900344 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -67,6 +67,10 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i elif ir.value == "goto": inst = IRInstruction("br", ir.args) ctx.get_basic_block().append_instruction(inst) + + label = ctx.get_next_label() + bb = IRBasicBlock(label, ctx) + ctx.append_basic_block(bb) elif ir.value == "calldatasize": ret = ctx.get_next_variable() func = IRFunctionIntrinsic("calldatasize", []) From e872729ab030095e0f1dc11d45827384d077422b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 26 Jun 2023 13:00:17 +0300 Subject: [PATCH 013/471] if --- vyper/codegen/ir_function.py | 7 +++++++ vyper/ir/ir_to_bb_pass.py | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 43c11b0950..3da6654953 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -35,15 +35,22 @@ def __init__(self, name: str) -> None: def append_basic_block(self, bb: IRBasicBlock) -> None: """ Append basic block to function. + If the last basic block is empty it replaces it. Therefore it is + important to dispose of the original object when calling this method. + e.i. when you need to reference the label of the block later. """ assert isinstance(bb, IRBasicBlock), f"append_basic_block takes IRBasicBlock, got '{bb}'" last_bb = self.basic_blocks[-1] if len(self.basic_blocks) > 0 else None if last_bb and len(last_bb.instructions) == 0: # last basic block is empty, replace it + old_label = self.basic_blocks[-1].label + bb.label = old_label self.basic_blocks[-1] = bb else: self.basic_blocks.append(bb) + return self.basic_blocks[-1] + def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: """ Get basic block by label. diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 5a77900344..e73d864c78 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -35,8 +35,36 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i _convert_ir_basicblock(ctx, ir_node) elif ir.value == "if": cond = ir.args[0] - _convert_ir_basicblock(ctx, cond) + current_bb = ctx.get_basic_block() + # convert the condition + cont_ret = _convert_ir_basicblock(ctx, cond) + + label = ctx.get_next_label() + else_block = IRBasicBlock(label, ctx) + ctx.append_basic_block(else_block) + + # convert "else" + if len(ir.args) == 3: + _convert_ir_basicblock(ctx, ir.args[2]) + + # convert "then" + then_label = ctx.get_next_label() + bb = IRBasicBlock(then_label, ctx) + ctx.append_basic_block(bb) + _convert_ir_basicblock(ctx, ir.args[1]) + + inst = IRInstruction("br", [cont_ret, f"label %{then_label}", f"label %{label}"]) + current_bb.append_instruction(inst) + + # exit bb + exit_label = ctx.get_next_label() + bb = IRBasicBlock(exit_label, ctx) + bb = ctx.append_basic_block(bb) + + exit_inst = IRInstruction("br", [f"label %{bb.label}"]) + else_block.append_instruction(exit_inst) + elif ir.value == "with": ret = _convert_ir_basicblock(ctx, ir.args[1]) # initialization @@ -65,7 +93,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "goto": - inst = IRInstruction("br", ir.args) + inst = IRInstruction("br", [f"label %{ir.args[0]}"]) ctx.get_basic_block().append_instruction(inst) label = ctx.get_next_label() From 33ce2bb96f6b2a5112934b149a78c9883a3e5573 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 26 Jun 2023 13:09:40 +0300 Subject: [PATCH 014/471] readability improvement --- vyper/ir/ir_to_bb_pass.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index e73d864c78..e2ff537b0f 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -36,11 +36,11 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() + # convert the condition cont_ret = _convert_ir_basicblock(ctx, cond) - label = ctx.get_next_label() - else_block = IRBasicBlock(label, ctx) + else_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(else_block) # convert "else" @@ -48,13 +48,12 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i _convert_ir_basicblock(ctx, ir.args[2]) # convert "then" - then_label = ctx.get_next_label() - bb = IRBasicBlock(then_label, ctx) - ctx.append_basic_block(bb) + then_block = IRBasicBlock(ctx.get_next_label(), ctx) + ctx.append_basic_block(then_block) _convert_ir_basicblock(ctx, ir.args[1]) - inst = IRInstruction("br", [cont_ret, f"label %{then_label}", f"label %{label}"]) + inst = IRInstruction("br", [cont_ret, f"label %{then_block.label}"]) current_bb.append_instruction(inst) # exit bb From 7cbcd59a5c8a8e969b826cd00066f92568662024 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 26 Jun 2023 14:42:38 +0300 Subject: [PATCH 015/471] simplify --- vyper/codegen/ir_function.py | 12 +----------- vyper/ir/ir_to_bb_pass.py | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 3da6654953..2164e647fa 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -35,19 +35,9 @@ def __init__(self, name: str) -> None: def append_basic_block(self, bb: IRBasicBlock) -> None: """ Append basic block to function. - If the last basic block is empty it replaces it. Therefore it is - important to dispose of the original object when calling this method. - e.i. when you need to reference the label of the block later. """ assert isinstance(bb, IRBasicBlock), f"append_basic_block takes IRBasicBlock, got '{bb}'" - last_bb = self.basic_blocks[-1] if len(self.basic_blocks) > 0 else None - if last_bb and len(last_bb.instructions) == 0: - # last basic block is empty, replace it - old_label = self.basic_blocks[-1].label - bb.label = old_label - self.basic_blocks[-1] = bb - else: - self.basic_blocks.append(bb) + self.basic_blocks.append(bb) return self.basic_blocks[-1] diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index e2ff537b0f..8cfb291f07 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -53,7 +53,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i _convert_ir_basicblock(ctx, ir.args[1]) - inst = IRInstruction("br", [cont_ret, f"label %{then_block.label}"]) + inst = IRInstruction("br", [cont_ret, f"label %{then_block.label}", f"label %{else_block.label}"]) current_bb.append_instruction(inst) # exit bb From 2b4bff29b29987e5e8fafd13cbe4ce223747e1f8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 26 Jun 2023 15:06:40 +0300 Subject: [PATCH 016/471] specific classes for variables and labels --- vyper/codegen/ir_basicblock.py | 40 ++++++++++++++++++++++++++++++++-- vyper/codegen/ir_function.py | 6 ++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index e7e02fa705..851926f4f1 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -22,6 +22,39 @@ def __repr__(self) -> str: return f"\t# line {self.line_no}: {src}".expandtabs(20) +class IROperant: + """ + IROperant represents an operand in IR. An operand can be a variable, label, or a constant. + """ + + value: str + + def __init__(self, value: str) -> None: + self.value = value + + def __repr__(self) -> str: + return self.value + + +class IRVariable(IROperant): + """ + IRVariable represents a variable in IR. A variable is a string that starts with a %. + """ + + def __init__(self, value: str) -> None: + assert value.startswith("%"), f"IRVariable must start with '%', got '{value}'" + super().__init__(value) + + +class IRLabel(IROperant): + """ + IRLabel represents a label in IR. A label is a string that starts with a %. + """ + + def __init__(self, value: str) -> None: + super().__init__(value) + + class IRInstruction: """ IRInstruction represents an instruction in IR. Each instruction has an opcode, @@ -31,11 +64,13 @@ class IRInstruction: """ opcode: str - operands: list + operands: list[IROperant] ret: Optional[str] dbg: Optional[IRDebugInfo] - def __init__(self, opcode: str, operands: list, ret: str = None, dbg: IRDebugInfo = None): + def __init__( + self, opcode: str, operands: list[IROperant], ret: str = None, dbg: IRDebugInfo = None + ): self.opcode = opcode self.operands = operands self.ret = ret @@ -85,6 +120,7 @@ def __init__(self, label: str, parent: "IRFunction") -> None: self.instructions = [] def append_instruction(self, instruction: IRInstruction) -> None: + assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" self.instructions.append(instruction) def __repr__(self) -> str: diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 2164e647fa..c4303c1847 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -1,5 +1,5 @@ from typing import Optional -from vyper.codegen.ir_basicblock import IRBasicBlock +from vyper.codegen.ir_basicblock import IRBasicBlock, IRVariable, IRLabel class IRFunctionBase: @@ -55,11 +55,11 @@ def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: def get_next_label(self) -> str: self.last_label += 1 - return f"{self.last_label}" + return IRLabel(f"{self.last_label}") def get_next_variable(self) -> str: self.last_variable += 1 - return f"%{self.last_variable}" + return IRVariable(f"%{self.last_variable}") def get_last_variable(self) -> str: return f"%{self.last_variable}" From 245cef72a8c0fe8dcfb3ad79ae5a528214d42f3e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 26 Jun 2023 15:21:53 +0300 Subject: [PATCH 017/471] more refactoring --- vyper/codegen/ir_basicblock.py | 11 +++++---- vyper/codegen/ir_function.py | 6 ++--- vyper/ir/ir_to_bb_pass.py | 41 +++++++++++++++++++++++++++++----- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 851926f4f1..5bf2595a0a 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -30,6 +30,7 @@ class IROperant: value: str def __init__(self, value: str) -> None: + assert isinstance(value, str), "value must be a string" self.value = value def __repr__(self) -> str: @@ -42,7 +43,6 @@ class IRVariable(IROperant): """ def __init__(self, value: str) -> None: - assert value.startswith("%"), f"IRVariable must start with '%', got '{value}'" super().__init__(value) @@ -54,6 +54,9 @@ class IRLabel(IROperant): def __init__(self, value: str) -> None: super().__init__(value) + def __str__(self) -> str: + return f"label %{self.value}" + class IRInstruction: """ @@ -110,11 +113,11 @@ class IRBasicBlock: basic blocks. """ - label: str + label: IRLabel parent: "IRFunction" instructions: list[IRInstruction] - def __init__(self, label: str, parent: "IRFunction") -> None: + def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.label = label self.parent = parent self.instructions = [] @@ -124,7 +127,7 @@ def append_instruction(self, instruction: IRInstruction) -> None: self.instructions.append(instruction) def __repr__(self) -> str: - str = f"{self.label}:\n" + str = f"{repr(self.label)}:\n" for instruction in self.instructions: str += f" {instruction}\n" return str diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index c4303c1847..8b5ec50d12 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -7,10 +7,10 @@ class IRFunctionBase: Base class for IRFunction and IRFunctionIntrinsic """ - name: str # symbol name + name: IRLabel # symbol name args: list - def __init__(self, name: str, args: list = []) -> None: + def __init__(self, name: IRLabel, args: list = []) -> None: self.name = name self.args = args @@ -24,7 +24,7 @@ class IRFunction(IRFunctionBase): last_label: int last_variable: int - def __init__(self, name: str) -> None: + def __init__(self, name: IRLabel) -> None: super().__init__(name) self.basic_blocks = [] self.last_label = 0 diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 8cfb291f07..e39b923fd3 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -3,7 +3,7 @@ from vyper.codegen.ir_node import IRnode from vyper.codegen.ir_function import IRFunction, IRFunctionIntrinsic from vyper.codegen.ir_basicblock import IRInstruction, IRDebugInfo -from vyper.codegen.ir_basicblock import IRBasicBlock +from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRVariable from vyper.evm.opcodes import get_opcodes _symbols = {} @@ -12,9 +12,38 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: global_function = IRFunction("global") _convert_ir_basicblock(global_function, ir) + _optimize_empty_basicblocks(global_function) return global_function +def _optimize_empty_basicblocks(ctx: IRFunction) -> None: + """ + Remove empty basic blocks. + """ + count = 0 + i = 0 + while i < len(ctx.basic_blocks): + bb = ctx.basic_blocks[i] + i += 1 + if len(bb.instructions) > 0: + continue + + next_label = ctx.basic_blocks[i].label if i < len(ctx.basic_blocks) else None + if next_label is None: + continue + + for bb2 in ctx.basic_blocks: + for inst in bb2.instructions: + for arg in inst.operands: + if isinstance(arg, IRLabel) and arg == bb.label: + arg.label = next_label + + ctx.basic_blocks.remove(bb) + count += 1 + + return count + + def _convert_binary_op(ctx: IRFunction, ir: IRnode) -> str: arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) @@ -53,7 +82,9 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i _convert_ir_basicblock(ctx, ir.args[1]) - inst = IRInstruction("br", [cont_ret, f"label %{then_block.label}", f"label %{else_block.label}"]) + inst = IRInstruction( + "br", [cont_ret, then_block.label, else_block.label] + ) current_bb.append_instruction(inst) # exit bb @@ -61,7 +92,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i bb = IRBasicBlock(exit_label, ctx) bb = ctx.append_basic_block(bb) - exit_inst = IRInstruction("br", [f"label %{bb.label}"]) + exit_inst = IRInstruction("br", [bb.label]) else_block.append_instruction(exit_inst) elif ir.value == "with": @@ -92,7 +123,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "goto": - inst = IRInstruction("br", [f"label %{ir.args[0]}"]) + inst = IRInstruction("br", [IRLabel(ir.args[0].value)]) ctx.get_basic_block().append_instruction(inst) label = ctx.get_next_label() @@ -125,7 +156,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "label": - label = str(ir.args[0].value) + label = IRLabel(ir.args[0].value) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2]) From 2f537b912224ac09d69966bd1030267549f99384 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 26 Jun 2023 18:26:29 +0300 Subject: [PATCH 018/471] fix optimization edge cases, try to preserve symbol names --- vyper/codegen/ir_basicblock.py | 12 +++++++----- vyper/ir/ir_to_bb_pass.py | 32 +++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 5bf2595a0a..012338a04e 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -51,11 +51,11 @@ class IRLabel(IROperant): IRLabel represents a label in IR. A label is a string that starts with a %. """ - def __init__(self, value: str) -> None: - super().__init__(value) + is_symbol: bool = False - def __str__(self) -> str: - return f"label %{self.value}" + def __init__(self, value: str, is_symbol: bool = False) -> None: + super().__init__(value) + self.is_symbol = is_symbol class IRInstruction: @@ -84,7 +84,9 @@ def __repr__(self) -> str: if self.ret: s += f"{self.ret} = " s += f"{self.opcode} " - operands = ", ".join([str(op) for op in self.operands]) + operands = ", ".join( + [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in self.operands] + ) if self.dbg: return s + operands + f" {self.dbg}" return s + operands diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index e39b923fd3..ba6ce88a91 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -12,7 +12,8 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: global_function = IRFunction("global") _convert_ir_basicblock(global_function, ir) - _optimize_empty_basicblocks(global_function) + while _optimize_empty_basicblocks(global_function): + pass return global_function @@ -28,17 +29,24 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> None: if len(bb.instructions) > 0: continue - next_label = ctx.basic_blocks[i].label if i < len(ctx.basic_blocks) else None - if next_label is None: + replaced_label = bb.label + replacement_label = ctx.basic_blocks[i].label if i < len(ctx.basic_blocks) else None + if replacement_label is None: continue + # Try to preserve symbol labels + if replaced_label.is_symbol: + replaced_label, replacement_label = replacement_label, replaced_label + ctx.basic_blocks[i].label = replacement_label + for bb2 in ctx.basic_blocks: for inst in bb2.instructions: - for arg in inst.operands: - if isinstance(arg, IRLabel) and arg == bb.label: - arg.label = next_label + for op in inst.operands: + if isinstance(op, IRLabel) and op == replaced_label: + op.label = replacement_label ctx.basic_blocks.remove(bb) + i -= 1 count += 1 return count @@ -82,9 +90,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i _convert_ir_basicblock(ctx, ir.args[1]) - inst = IRInstruction( - "br", [cont_ret, then_block.label, else_block.label] - ) + inst = IRInstruction("br", [cont_ret, then_block.label, else_block.label]) current_bb.append_instruction(inst) # exit bb @@ -156,13 +162,17 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "label": - label = IRLabel(ir.args[0].value) - bb = IRBasicBlock(label, ctx) + bb = IRBasicBlock(IRLabel(ir.args[0].value, True), ctx) ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2]) elif ir.value == "return": pass elif ir.value == "exit_to": + inst = IRInstruction("br", [IRLabel(ir.args[0].value, True)]) + ctx.get_basic_block().append_instruction(inst) + + bb = IRBasicBlock(ctx.get_next_label(), ctx) + ctx.append_basic_block(bb) pass elif ir.value == "pass": pass From 6cbedc0423fe453aaa5dbd5f13ed304b087ab80f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 26 Jun 2023 18:35:40 +0300 Subject: [PATCH 019/471] temporary exit_to return --- vyper/ir/ir_to_bb_pass.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index ba6ce88a91..42c8b82ad5 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -166,8 +166,12 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2]) elif ir.value == "return": + inst = IRInstruction("ret", []) + ctx.get_basic_block().append_instruction(inst) pass elif ir.value == "exit_to": + _convert_ir_basicblock(ctx, ir.args[2]) + inst = IRInstruction("br", [IRLabel(ir.args[0].value, True)]) ctx.get_basic_block().append_instruction(inst) From c337171b77a1b81d8f5b6f9e13e66e0643158f50 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 26 Jun 2023 20:20:33 +0300 Subject: [PATCH 020/471] more fixes on variable handling --- vyper/ir/ir_to_bb_pass.py | 67 +++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 42c8b82ad5..fe29496e04 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,7 +1,7 @@ from typing import Optional, Union from vyper.codegen.global_context import GlobalContext from vyper.codegen.ir_node import IRnode -from vyper.codegen.ir_function import IRFunction, IRFunctionIntrinsic +from vyper.codegen.ir_function import IRFunctionBase, IRFunction, IRFunctionIntrinsic from vyper.codegen.ir_basicblock import IRInstruction, IRDebugInfo from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRVariable from vyper.evm.opcodes import get_opcodes @@ -14,6 +14,7 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: _convert_ir_basicblock(global_function, ir) while _optimize_empty_basicblocks(global_function): pass + _optimize_unused_variables(global_function) return global_function @@ -52,6 +53,36 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> None: return count +def _optimize_unused_variables(ctx: IRFunction) -> None: + """ + Remove unused variables. + """ + count = 0 + uses = {} + for bb in ctx.basic_blocks: + for inst in bb.instructions: + for op in inst.operands: + if isinstance(op, IRVariable): + uses[op] = uses.get(op, 0) + 1 + elif isinstance(op, IRFunctionBase): + for arg in op.args: + if isinstance(arg, IRVariable): + uses[arg] = uses.get(arg, 0) + 1 + + for bb in ctx.basic_blocks: + for inst in bb.instructions: + if inst.ret is None: + continue + + if inst.ret in uses: + continue + + print("Removing unused variable: %s" % inst.ret) + + print(uses) + return count + + def _convert_binary_op(ctx: IRFunction, ir: IRnode) -> str: arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) @@ -68,8 +99,12 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i if ir.value == "deploy": _convert_ir_basicblock(ctx, ir.args[1]) elif ir.value == "seq": - for ir_node in ir.args: - _convert_ir_basicblock(ctx, ir_node) + ret = None + for ir_node in ir.args: # NOTE: skip the last one + r = _convert_ir_basicblock(ctx, ir_node) + if ir_node.is_literal == False: + ret = r + return ret elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() @@ -110,14 +145,14 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i first_pos = ir.source_pos[0] if ir.source_pos else None inst = IRInstruction( "load", - [_symbols[sym.value], ret], - None, + [ret], + _symbols[sym.value], IRDebugInfo(first_pos or 0, f"symbol: {sym.value}"), ) ctx.get_basic_block().append_instruction(inst) _convert_ir_basicblock(ctx, ir.args[2]) # body - elif ir.value in ["le", "ge", "shr", "xor"]: + elif ir.value in ["le", "ge", "gt", "shr", "xor"]: return _convert_binary_op(ctx, ir) elif ir.value == "iszero": arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) @@ -155,29 +190,28 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i return ret elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) - - ret = ctx.get_next_variable() func = IRFunctionIntrinsic("assert", [arg_0]) - inst = IRInstruction("call", [func], ret) + inst = IRInstruction("call", [func]) ctx.get_basic_block().append_instruction(inst) - return ret elif ir.value == "label": bb = IRBasicBlock(IRLabel(ir.args[0].value, True), ctx) ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2]) elif ir.value == "return": - inst = IRInstruction("ret", []) - ctx.get_basic_block().append_instruction(inst) pass elif ir.value == "exit_to": - _convert_ir_basicblock(ctx, ir.args[2]) + ret = _convert_ir_basicblock(ctx, ir.args[2]) inst = IRInstruction("br", [IRLabel(ir.args[0].value, True)]) ctx.get_basic_block().append_instruction(inst) bb = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(bb) - pass + + # for now + inst = IRInstruction("ret", [ret]) + ctx.get_basic_block().append_instruction(inst) + elif ir.value == "pass": pass elif ir.value == "mstore": @@ -188,11 +222,12 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i first_pos = ir.source_pos[0] if ir.source_pos else None inst = IRInstruction( "load", - [new_var, ir.args[1].value], - None, + [ir.args[1].value], + new_var, IRDebugInfo(first_pos or 0, ir.annotation or ""), ) ctx.get_basic_block().append_instruction(inst) + return new_var elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): _convert_ir_opcode(ctx, ir) elif isinstance(ir.value, str) and ir.value in _symbols: From 425566104acf646c0d71a5a802532dbd599783be Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 26 Jun 2023 23:32:00 +0300 Subject: [PATCH 021/471] exit --- vyper/ir/ir_to_bb_pass.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index fe29496e04..b067a512d3 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -202,16 +202,13 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i elif ir.value == "exit_to": ret = _convert_ir_basicblock(ctx, ir.args[2]) - inst = IRInstruction("br", [IRLabel(ir.args[0].value, True)]) - ctx.get_basic_block().append_instruction(inst) - - bb = IRBasicBlock(ctx.get_next_label(), ctx) - ctx.append_basic_block(bb) - # for now inst = IRInstruction("ret", [ret]) ctx.get_basic_block().append_instruction(inst) - + elif ir.value == "revert": + func = IRFunctionIntrinsic("revert", ir.args) + inst = IRInstruction("call", [func]) + ctx.get_basic_block().append_instruction(inst) elif ir.value == "pass": pass elif ir.value == "mstore": From 5df2aa24e9575df57883c1435ce8eb3bc6b470d2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 27 Jun 2023 09:45:50 +0300 Subject: [PATCH 022/471] support additional biops and mload --- vyper/ir/ir_to_bb_pass.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index b067a512d3..06d52c032a 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -151,8 +151,8 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ) ctx.get_basic_block().append_instruction(inst) - _convert_ir_basicblock(ctx, ir.args[2]) # body - elif ir.value in ["le", "ge", "gt", "shr", "xor"]: + return _convert_ir_basicblock(ctx, ir.args[2]) # body + elif ir.value in ["eq", "le", "ge", "gt", "shr", "or", "xor", "add", "sub", "mul", "div", "mod"]: return _convert_binary_op(ctx, ir) elif ir.value == "iszero": arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) @@ -211,15 +211,23 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.get_basic_block().append_instruction(inst) elif ir.value == "pass": pass + elif ir.value == "mload": + sym = ir.args[0] + new_var = _symbols.get(f"&{sym.value}", None) + assert new_var != None, "mload without mstore" + + return new_var elif ir.value == "mstore": sym = ir.args[0] new_var = ctx.get_next_variable() _symbols[f"&{sym.value}"] = new_var - assert ir.args[1].is_literal, "mstore expects a literal as second argument" + + arg = _convert_ir_basicblock(ctx, ir.args[1]) + first_pos = ir.source_pos[0] if ir.source_pos else None inst = IRInstruction( "load", - [ir.args[1].value], + [arg], new_var, IRDebugInfo(first_pos or 0, ir.annotation or ""), ) From b5ed0880fadc3ac2c2b46fe9192b6db7448483a7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 27 Jun 2023 10:04:27 +0300 Subject: [PATCH 023/471] remove unneeded loads and stores --- vyper/ir/ir_to_bb_pass.py | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 06d52c032a..883bdcf7a4 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -141,15 +141,15 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i sym = ir.args[0] # FIXME: How do I validate that the IR is indeed a symbol? - _symbols[sym.value] = ctx.get_next_variable() - first_pos = ir.source_pos[0] if ir.source_pos else None - inst = IRInstruction( - "load", - [ret], - _symbols[sym.value], - IRDebugInfo(first_pos or 0, f"symbol: {sym.value}"), - ) - ctx.get_basic_block().append_instruction(inst) + _symbols[sym.value] = ret + # first_pos = ir.source_pos[0] if ir.source_pos else None + # inst = IRInstruction( + # "load", + # [ret], + # _symbols[sym.value], + # IRDebugInfo(first_pos or 0, f"symbol: {sym.value}"), + # ) + # ctx.get_basic_block().append_instruction(inst) return _convert_ir_basicblock(ctx, ir.args[2]) # body elif ir.value in ["eq", "le", "ge", "gt", "shr", "or", "xor", "add", "sub", "mul", "div", "mod"]: @@ -215,23 +215,11 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i sym = ir.args[0] new_var = _symbols.get(f"&{sym.value}", None) assert new_var != None, "mload without mstore" - return new_var elif ir.value == "mstore": sym = ir.args[0] - new_var = ctx.get_next_variable() + new_var = _convert_ir_basicblock(ctx, ir.args[1]) _symbols[f"&{sym.value}"] = new_var - - arg = _convert_ir_basicblock(ctx, ir.args[1]) - - first_pos = ir.source_pos[0] if ir.source_pos else None - inst = IRInstruction( - "load", - [arg], - new_var, - IRDebugInfo(first_pos or 0, ir.annotation or ""), - ) - ctx.get_basic_block().append_instruction(inst) return new_var elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): _convert_ir_opcode(ctx, ir) From b27dc0fa01d98ae0efcab0f0cfd5bb8fc2955396 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 27 Jun 2023 12:54:52 +0300 Subject: [PATCH 024/471] liveness analysis start --- vyper/codegen/ir_basicblock.py | 7 +++++++ vyper/codegen/ir_function.py | 7 +++++++ vyper/ir/ir_to_bb_pass.py | 15 +++++++++------ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 012338a04e..97e426872d 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -118,11 +118,18 @@ class IRBasicBlock: label: IRLabel parent: "IRFunction" instructions: list[IRInstruction] + in_set: set["IRBasicBlock"] + out: "IRBasicBlock" def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.label = label self.parent = parent self.instructions = [] + self.in_set = set() + self.out = None + + def add_in(self, bb: "IRBasicBlock") -> None: + self.in_set.add(bb) def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 8b5ec50d12..6824bbccf0 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -53,6 +53,12 @@ def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: return bb assert False, f"Basic block '{label}' not found" + def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: + """ + Get basic blocks that contain label. + """ + return [bb for bb in self.basic_blocks if basic_block.label == bb.label] + def get_next_label(self) -> str: self.last_label += 1 return IRLabel(f"{self.last_label}") @@ -67,6 +73,7 @@ def get_last_variable(self) -> str: def __repr__(self) -> str: str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: + str += f"in: {[b.label for b in self.get_basicblocks_in(bb)]}\n" str += f"{bb}\n" return str diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 883bdcf7a4..51e4fab708 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -136,6 +136,11 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i exit_inst = IRInstruction("br", [bb.label]) else_block.append_instruction(exit_inst) + then_block.add_in(current_bb) + else_block.add_in(current_bb) + bb.add_in(then_block) + bb.add_in(else_block) + elif ir.value == "with": ret = _convert_ir_basicblock(ctx, ir.args[1]) # initialization @@ -169,23 +174,21 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i label = ctx.get_next_label() bb = IRBasicBlock(label, ctx) + bb.add_in(ctx.get_basic_block()) ctx.append_basic_block(bb) elif ir.value == "calldatasize": ret = ctx.get_next_variable() - func = IRFunctionIntrinsic("calldatasize", []) - inst = IRInstruction("call", [func], ret) + inst = IRInstruction("calldatasize", [], ret) ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "calldataload": ret = ctx.get_next_variable() - func = IRFunctionIntrinsic("calldataload", [ir.args[0]]) - inst = IRInstruction("call", [func], ret) + inst = IRInstruction("calldataload", [ir.args[0].value], ret) ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "callvalue": ret = ctx.get_next_variable() - func = IRFunctionIntrinsic("callvalue", []) - inst = IRInstruction("call", [func], ret) + inst = IRInstruction("callvalue", [], ret) ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "assert": From 9a1424088ee7241701bc7e32e3851c8836de85dc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 27 Jun 2023 16:57:42 +0300 Subject: [PATCH 025/471] liveness --- vyper/codegen/ir_basicblock.py | 12 +++++++++--- vyper/codegen/ir_function.py | 3 +-- vyper/ir/ir_to_bb_pass.py | 6 ++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 97e426872d..ecb943fd85 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -131,12 +131,18 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: def add_in(self, bb: "IRBasicBlock") -> None: self.in_set.add(bb) + def union_in(self, bb_set: set["IRBasicBlock"]) -> None: + self.in_set = self.in_set.union(bb_set) + + def remove_in(self, bb: "IRBasicBlock") -> None: + self.in_set.remove(bb) + def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" self.instructions.append(instruction) def __repr__(self) -> str: - str = f"{repr(self.label)}:\n" + s = f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}\n" for instruction in self.instructions: - str += f" {instruction}\n" - return str + s += f" {instruction}\n" + return s diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 6824bbccf0..9f1c4d23f4 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -57,7 +57,7 @@ def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: """ Get basic blocks that contain label. """ - return [bb for bb in self.basic_blocks if basic_block.label == bb.label] + return [bb for bb in self.basic_blocks if basic_block.label in bb.in_set] def get_next_label(self) -> str: self.last_label += 1 @@ -73,7 +73,6 @@ def get_last_variable(self) -> str: def __repr__(self) -> str: str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: - str += f"in: {[b.label for b in self.get_basicblocks_in(bb)]}\n" str += f"{bb}\n" return str diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 51e4fab708..beaebc47f2 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -14,7 +14,7 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: _convert_ir_basicblock(global_function, ir) while _optimize_empty_basicblocks(global_function): pass - _optimize_unused_variables(global_function) + # _optimize_unused_variables(global_function) return global_function @@ -35,6 +35,8 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> None: if replacement_label is None: continue + ctx.basic_blocks[i].union_in(bb.in_set) + # Try to preserve symbol labels if replaced_label.is_symbol: replaced_label, replacement_label = replacement_label, replaced_label @@ -136,11 +138,11 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i exit_inst = IRInstruction("br", [bb.label]) else_block.append_instruction(exit_inst) + # make forward edges then_block.add_in(current_bb) else_block.add_in(current_bb) bb.add_in(then_block) bb.add_in(else_block) - elif ir.value == "with": ret = _convert_ir_basicblock(ctx, ir.args[1]) # initialization From 52156bdced62f0ab89fec4084b2222b333d00155 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 27 Jun 2023 17:42:17 +0300 Subject: [PATCH 026/471] terminating instructions and fix --- vyper/ir/ir_to_bb_pass.py | 55 ++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index beaebc47f2..b400028ea7 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -6,14 +6,21 @@ from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRVariable from vyper.evm.opcodes import get_opcodes +TERMINATOR_IR_INSTRUCTIONS = [ + "br", + "ret", + "revert", +] + _symbols = {} def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: - global_function = IRFunction("global") + global_function = IRFunction(IRLabel("global")) _convert_ir_basicblock(global_function, ir) while _optimize_empty_basicblocks(global_function): pass + _calculate_in_set(global_function) # _optimize_unused_variables(global_function) return global_function @@ -35,8 +42,6 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> None: if replacement_label is None: continue - ctx.basic_blocks[i].union_in(bb.in_set) - # Try to preserve symbol labels if replaced_label.is_symbol: replaced_label, replacement_label = replacement_label, replaced_label @@ -46,7 +51,7 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> None: for inst in bb2.instructions: for op in inst.operands: if isinstance(op, IRLabel) and op == replaced_label: - op.label = replacement_label + op.value = replacement_label.value ctx.basic_blocks.remove(bb) i -= 1 @@ -55,6 +60,24 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> None: return count +def _calculate_in_set(ctx: IRFunction) -> None: + """ + Calculate in set for each basic block. + """ + for bb in ctx.basic_blocks: + assert len(bb.instructions) > 0, "Basic block should not be empty" + last_inst = bb.instructions[-1] + assert ( + last_inst.opcode in TERMINATOR_IR_INSTRUCTIONS + ), "Last instruction should be a terminator" + + if last_inst.opcode == "br": + ops = last_inst.get_label_operands() + assert len(ops) >= 1, "br instruction should have at least one label operand" + for op in ops: + ctx.get_basic_block(op.value).add_in(bb) + + def _optimize_unused_variables(ctx: IRFunction) -> None: """ Remove unused variables. @@ -138,11 +161,6 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i exit_inst = IRInstruction("br", [bb.label]) else_block.append_instruction(exit_inst) - # make forward edges - then_block.add_in(current_bb) - else_block.add_in(current_bb) - bb.add_in(then_block) - bb.add_in(else_block) elif ir.value == "with": ret = _convert_ir_basicblock(ctx, ir.args[1]) # initialization @@ -159,7 +177,20 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i # ctx.get_basic_block().append_instruction(inst) return _convert_ir_basicblock(ctx, ir.args[2]) # body - elif ir.value in ["eq", "le", "ge", "gt", "shr", "or", "xor", "add", "sub", "mul", "div", "mod"]: + elif ir.value in [ + "eq", + "le", + "ge", + "gt", + "shr", + "or", + "xor", + "add", + "sub", + "mul", + "div", + "mod", + ]: return _convert_binary_op(ctx, ir) elif ir.value == "iszero": arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) @@ -176,7 +207,6 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i label = ctx.get_next_label() bb = IRBasicBlock(label, ctx) - bb.add_in(ctx.get_basic_block()) ctx.append_basic_block(bb) elif ir.value == "calldatasize": ret = ctx.get_next_variable() @@ -211,8 +241,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i inst = IRInstruction("ret", [ret]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": - func = IRFunctionIntrinsic("revert", ir.args) - inst = IRInstruction("call", [func]) + inst = IRInstruction("revert", ir.args) ctx.get_basic_block().append_instruction(inst) elif ir.value == "pass": pass From 297ac333ea3ec7990bda87bddcb39b05e034059b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 27 Jun 2023 17:42:28 +0300 Subject: [PATCH 027/471] small bug fix --- vyper/codegen/ir_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 9f1c4d23f4..900f861b4b 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -49,7 +49,7 @@ def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: if label is None: return self.basic_blocks[-1] for bb in self.basic_blocks: - if bb.label == label: + if bb.label.value == label: return bb assert False, f"Basic block '{label}' not found" From a58db755986ffa64266c11aceafbb6cbc39255a7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 27 Jun 2023 17:42:47 +0300 Subject: [PATCH 028/471] get label operands --- vyper/codegen/ir_basicblock.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index ecb943fd85..5c9d247a5b 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -79,6 +79,12 @@ def __init__( self.ret = ret self.dbg = dbg + def get_label_operands(self) -> list[IRLabel]: + """ + Get all labels in instruction. + """ + return [op for op in self.operands if isinstance(op, IRLabel)] + def __repr__(self) -> str: s = "" if self.ret: @@ -122,6 +128,7 @@ class IRBasicBlock: out: "IRBasicBlock" def __init__(self, label: IRLabel, parent: "IRFunction") -> None: + assert isinstance(label, IRLabel), "label must be an IRLabel" self.label = label self.parent = parent self.instructions = [] From cbd4fc9f31b24f8b571fe2a31c962583d3ed47a5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 27 Jun 2023 18:27:37 +0300 Subject: [PATCH 029/471] add revert to terminating instructions --- vyper/ir/ir_to_bb_pass.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index b400028ea7..00d489f769 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -10,6 +10,7 @@ "br", "ret", "revert", + "assert", ] _symbols = {} @@ -225,8 +226,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i return ret elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) - func = IRFunctionIntrinsic("assert", [arg_0]) - inst = IRInstruction("call", [func]) + inst = IRInstruction("assert", [arg_0]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "label": bb = IRBasicBlock(IRLabel(ir.args[0].value, True), ctx) From 1aa77f71d9f22c7159569dc4c08c23d350a3f787 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 27 Jun 2023 18:41:42 +0300 Subject: [PATCH 030/471] operant stuff and liveness --- vyper/codegen/ir_basicblock.py | 18 ++++++++++ vyper/ir/ir_to_bb_pass.py | 60 ++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 5c9d247a5b..164beaccaf 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -85,6 +85,15 @@ def get_label_operands(self) -> list[IRLabel]: """ return [op for op in self.operands if isinstance(op, IRLabel)] + def get_input_operands(self) -> list[IROperant]: + """ + Get all input operands in instruction. + """ + return [op for op in self.operands if not isinstance(op, IRLabel)] + + def get_output_operands(self) -> list[IROperant]: + return [self.ret] + def __repr__(self) -> str: s = "" if self.ret: @@ -148,6 +157,15 @@ def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" self.instructions.append(instruction) + def compute_liveness(self, in_ops: set[IRVariable], out_ops: set[IRVariable]) -> None: + """ + Compute liveness of each instruction in basic block. + WHEREILEFTOFF: Implement this. + """ + for instruction in self.instructions: + out_ops = instruction.get_output_operands() + in_ops = instruction.get_input_operands() + def __repr__(self) -> str: s = f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}\n" for instruction in self.instructions: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 00d489f769..5c1cf1af55 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -78,35 +78,45 @@ def _calculate_in_set(ctx: IRFunction) -> None: for op in ops: ctx.get_basic_block(op.value).add_in(bb) - -def _optimize_unused_variables(ctx: IRFunction) -> None: +def _calculate_liveness(ctx: IRFunction) -> None: """ - Remove unused variables. + Calculate liveness for each basic block. """ - count = 0 - uses = {} - for bb in ctx.basic_blocks: - for inst in bb.instructions: - for op in inst.operands: - if isinstance(op, IRVariable): - uses[op] = uses.get(op, 0) + 1 - elif isinstance(op, IRFunctionBase): - for arg in op.args: - if isinstance(arg, IRVariable): - uses[arg] = uses.get(arg, 0) + 1 - for bb in ctx.basic_blocks: - for inst in bb.instructions: - if inst.ret is None: - continue - - if inst.ret in uses: - continue - - print("Removing unused variable: %s" % inst.ret) + assert len(bb.instructions) > 0, "Basic block should not be empty" + last_inst = bb.instructions[-1] + assert ( + last_inst.opcode in TERMINATOR_IR_INSTRUCTIONS + ), "Last instruction should be a terminator" - print(uses) - return count +# def _optimize_unused_variables(ctx: IRFunction) -> None: +# """ +# Remove unused variables. +# """ +# count = 0 +# uses = {} +# for bb in ctx.basic_blocks: +# for inst in bb.instructions: +# for op in inst.operands: +# if isinstance(op, IRVariable): +# uses[op] = uses.get(op, 0) + 1 +# elif isinstance(op, IRFunctionBase): +# for arg in op.args: +# if isinstance(arg, IRVariable): +# uses[arg] = uses.get(arg, 0) + 1 + +# for bb in ctx.basic_blocks: +# for inst in bb.instructions: +# if inst.ret is None: +# continue + +# if inst.ret in uses: +# continue + +# print("Removing unused variable: %s" % inst.ret) + +# print(uses) +# return count def _convert_binary_op(ctx: IRFunction, ir: IRnode) -> str: From 3ffb5667a7edf35aee42ee22e57c04b837ea3633 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 27 Jun 2023 18:51:22 +0300 Subject: [PATCH 031/471] bb liveness compute --- vyper/codegen/ir_basicblock.py | 15 +++++++++++---- vyper/ir/ir_to_bb_pass.py | 3 +++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 164beaccaf..1708bbf086 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -89,7 +89,7 @@ def get_input_operands(self) -> list[IROperant]: """ Get all input operands in instruction. """ - return [op for op in self.operands if not isinstance(op, IRLabel)] + return [op for op in self.operands if isinstance(op, IRVariable)] def get_output_operands(self) -> list[IROperant]: return [self.ret] @@ -135,6 +135,7 @@ class IRBasicBlock: instructions: list[IRInstruction] in_set: set["IRBasicBlock"] out: "IRBasicBlock" + liveness = {} def __init__(self, label: IRLabel, parent: "IRFunction") -> None: assert isinstance(label, IRLabel), "label must be an IRLabel" @@ -157,14 +158,20 @@ def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" self.instructions.append(instruction) - def compute_liveness(self, in_ops: set[IRVariable], out_ops: set[IRVariable]) -> None: + def compute_liveness(self) -> None: """ Compute liveness of each instruction in basic block. WHEREILEFTOFF: Implement this. """ + out_vars = set() for instruction in self.instructions: - out_ops = instruction.get_output_operands() - in_ops = instruction.get_input_operands() + out_vars.union(instruction.get_input_operands()) + out = instruction.get_output_operands()[0] + if out in out_vars: + out_vars.remove(out) + self.liveness[out] = out_vars.copy() + + # print("Liveness:", self.liveness) def __repr__(self) -> str: s = f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}\n" diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 5c1cf1af55..812869be1a 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -22,6 +22,7 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: while _optimize_empty_basicblocks(global_function): pass _calculate_in_set(global_function) + _calculate_liveness(global_function) # _optimize_unused_variables(global_function) return global_function @@ -89,6 +90,8 @@ def _calculate_liveness(ctx: IRFunction) -> None: last_inst.opcode in TERMINATOR_IR_INSTRUCTIONS ), "Last instruction should be a terminator" + bb.compute_liveness() + # def _optimize_unused_variables(ctx: IRFunction) -> None: # """ # Remove unused variables. From e067186f8b18e500b35b1a4141b4369719a4f4a9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Jun 2023 14:54:02 +0300 Subject: [PATCH 032/471] in block variable liveness, back links --- vyper/codegen/ir_basicblock.py | 43 +++++++++++++++++++++++++++------- vyper/codegen/ir_function.py | 6 +++++ vyper/ir/ir_to_bb_pass.py | 18 ++++++++------ 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 1708bbf086..e571d52f8c 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,5 +1,11 @@ from typing import Optional, TYPE_CHECKING +TERMINAL_IR_INSTRUCTIONS = [ + "ret", + "revert", + "assert", +] + if TYPE_CHECKING: from vyper.codegen.ir_function import IRFunction @@ -143,7 +149,7 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.parent = parent self.instructions = [] self.in_set = set() - self.out = None + self.out_set = set() def add_in(self, bb: "IRBasicBlock") -> None: self.in_set.add(bb) @@ -154,27 +160,48 @@ def union_in(self, bb_set: set["IRBasicBlock"]) -> None: def remove_in(self, bb: "IRBasicBlock") -> None: self.in_set.remove(bb) + def add_out(self, bb: "IRBasicBlock") -> None: + self.out_set.add(bb) + + def union_out(self, bb_set: set["IRBasicBlock"]) -> None: + self.out_set = self.out_set.union(bb_set) + + def remove_out(self, bb: "IRBasicBlock") -> None: + self.out_set.remove(bb) + def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" self.instructions.append(instruction) - def compute_liveness(self) -> None: + def is_terminal(self) -> bool: + """ + Check if the basic block is terminal, i.e. the last instruction is a terminator. + """ + assert len(self.instructions) > 0, "basic block must have at least one instruction" + return self.instructions[-1].opcode in TERMINAL_IR_INSTRUCTIONS + + def compute_liveness(self, visited: set) -> None: """ Compute liveness of each instruction in basic block. - WHEREILEFTOFF: Implement this. """ + visited.add(self) + out_vars = set() - for instruction in self.instructions: - out_vars.union(instruction.get_input_operands()) + for instruction in self.instructions[::-1]: + out_vars = out_vars.union(instruction.get_input_operands()) out = instruction.get_output_operands()[0] if out in out_vars: out_vars.remove(out) - self.liveness[out] = out_vars.copy() + self.liveness[instruction] = out_vars.copy() + + print("Liveness:", self.label, "\n", self.liveness[self.instructions[0]], "\n") - # print("Liveness:", self.liveness) + for bb in self.in_set: + if bb not in visited: + bb.compute_liveness(visited) def __repr__(self) -> str: - s = f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}\n" + s = f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]} OUT={[bb.label for bb in self.out_set]} \n" for instruction in self.instructions: s += f" {instruction}\n" return s diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 900f861b4b..b1b07c7a9a 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -59,6 +59,12 @@ def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: """ return [bb for bb in self.basic_blocks if basic_block.label in bb.in_set] + def get_terminal_basicblocks(self) -> list[IRBasicBlock]: + """ + Get basic blocks that contain label. + """ + return [bb for bb in self.basic_blocks if bb.is_terminal()] + def get_next_label(self) -> str: self.last_label += 1 return IRLabel(f"{self.last_label}") diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 812869be1a..11c742f4cb 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -79,18 +79,22 @@ def _calculate_in_set(ctx: IRFunction) -> None: for op in ops: ctx.get_basic_block(op.value).add_in(bb) + # Fill in the "out" set for each basic block + for bb in ctx.basic_blocks: + for in_bb in bb.in_set: + in_bb.add_out(bb) + + def _calculate_liveness(ctx: IRFunction) -> None: """ Calculate liveness for each basic block. """ - for bb in ctx.basic_blocks: - assert len(bb.instructions) > 0, "Basic block should not be empty" - last_inst = bb.instructions[-1] - assert ( - last_inst.opcode in TERMINATOR_IR_INSTRUCTIONS - ), "Last instruction should be a terminator" + t_bblocks = ctx.get_terminal_basicblocks() + print(t_bblocks) + visited = set() + for bb in t_bblocks: + bb.compute_liveness(visited) - bb.compute_liveness() # def _optimize_unused_variables(ctx: IRFunction) -> None: # """ From 2f5c174ad1082ac6fc2396b078429d5e236ce4a5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Jun 2023 15:35:06 +0300 Subject: [PATCH 033/471] liveness works --- vyper/codegen/ir_basicblock.py | 30 ++++++++++--------- vyper/ir/ir_to_bb_pass.py | 53 ++++++++-------------------------- 2 files changed, 28 insertions(+), 55 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index e571d52f8c..30e7d6c85b 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -108,8 +108,11 @@ def __repr__(self) -> str: operands = ", ".join( [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in self.operands] ) + s += operands + if self.dbg: - return s + operands + f" {self.dbg}" + return s + f" {self.dbg}" + return s + operands @@ -140,7 +143,8 @@ class IRBasicBlock: parent: "IRFunction" instructions: list[IRInstruction] in_set: set["IRBasicBlock"] - out: "IRBasicBlock" + out_set: set["IRBasicBlock"] + out_vars: set[IRVariable] liveness = {} def __init__(self, label: IRLabel, parent: "IRFunction") -> None: @@ -150,6 +154,7 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.instructions = [] self.in_set = set() self.out_set = set() + self.out_vars = set() def add_in(self, bb: "IRBasicBlock") -> None: self.in_set.add(bb) @@ -169,6 +174,10 @@ def union_out(self, bb_set: set["IRBasicBlock"]) -> None: def remove_out(self, bb: "IRBasicBlock") -> None: self.out_set.remove(bb) + @property + def in_vars(self) -> set[IRVariable]: + return self.liveness[self.instructions[0]] + def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" self.instructions.append(instruction) @@ -180,26 +189,19 @@ def is_terminal(self) -> bool: assert len(self.instructions) > 0, "basic block must have at least one instruction" return self.instructions[-1].opcode in TERMINAL_IR_INSTRUCTIONS - def compute_liveness(self, visited: set) -> None: + def compute_liveness(self) -> None: """ Compute liveness of each instruction in basic block. """ - visited.add(self) - - out_vars = set() for instruction in self.instructions[::-1]: - out_vars = out_vars.union(instruction.get_input_operands()) + self.out_vars = self.out_vars.union(instruction.get_input_operands()) out = instruction.get_output_operands()[0] - if out in out_vars: - out_vars.remove(out) - self.liveness[instruction] = out_vars.copy() + if out in self.out_vars: + self.out_vars.remove(out) + self.liveness[instruction] = self.out_vars.copy() print("Liveness:", self.label, "\n", self.liveness[self.instructions[0]], "\n") - for bb in self.in_set: - if bb not in visited: - bb.compute_liveness(visited) - def __repr__(self) -> str: s = f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]} OUT={[bb.label for bb in self.out_set]} \n" for instruction in self.instructions: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 11c742f4cb..893a64479b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -22,7 +22,7 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: while _optimize_empty_basicblocks(global_function): pass _calculate_in_set(global_function) - _calculate_liveness(global_function) + _calculate_liveness(global_function.basic_blocks[0]) # _optimize_unused_variables(global_function) return global_function @@ -85,45 +85,15 @@ def _calculate_in_set(ctx: IRFunction) -> None: in_bb.add_out(bb) -def _calculate_liveness(ctx: IRFunction) -> None: - """ - Calculate liveness for each basic block. - """ - t_bblocks = ctx.get_terminal_basicblocks() - print(t_bblocks) - visited = set() - for bb in t_bblocks: - bb.compute_liveness(visited) - - -# def _optimize_unused_variables(ctx: IRFunction) -> None: -# """ -# Remove unused variables. -# """ -# count = 0 -# uses = {} -# for bb in ctx.basic_blocks: -# for inst in bb.instructions: -# for op in inst.operands: -# if isinstance(op, IRVariable): -# uses[op] = uses.get(op, 0) + 1 -# elif isinstance(op, IRFunctionBase): -# for arg in op.args: -# if isinstance(arg, IRVariable): -# uses[arg] = uses.get(arg, 0) + 1 - -# for bb in ctx.basic_blocks: -# for inst in bb.instructions: -# if inst.ret is None: -# continue - -# if inst.ret in uses: -# continue - -# print("Removing unused variable: %s" % inst.ret) - -# print(uses) -# return count +def _calculate_liveness(bb: IRBasicBlock) -> None: + for out_bb in bb.out_set: + _calculate_liveness(out_bb) + in_vars = out_bb.in_vars + print(in_vars, bb.out_vars) + bb.out_vars = bb.out_vars.union(in_vars) + print(in_vars, bb.out_vars) + + bb.compute_liveness() def _convert_binary_op(ctx: IRFunction, ir: IRnode) -> str: @@ -254,7 +224,8 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i elif ir.value == "exit_to": ret = _convert_ir_basicblock(ctx, ir.args[2]) - # for now + # FIXME: for now + inst = IRInstruction("ret", [ret]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": From dcdebcc29192ebf7d376bf1c6358699dd4685977 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Jun 2023 15:43:56 +0300 Subject: [PATCH 034/471] store liveness in instruction and prety print --- vyper/codegen/ir_basicblock.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 30e7d6c85b..e880038f01 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -76,6 +76,7 @@ class IRInstruction: operands: list[IROperant] ret: Optional[str] dbg: Optional[IRDebugInfo] + liveness: set[IRVariable] def __init__( self, opcode: str, operands: list[IROperant], ret: str = None, dbg: IRDebugInfo = None @@ -84,6 +85,7 @@ def __init__( self.operands = operands self.ret = ret self.dbg = dbg + self.liveness = set() def get_label_operands(self) -> list[IRLabel]: """ @@ -113,7 +115,10 @@ def __repr__(self) -> str: if self.dbg: return s + f" {self.dbg}" - return s + operands + if self.liveness: + return f"{s: <30} # {self.liveness}" + + return s class IRBasicBlock: @@ -145,7 +150,6 @@ class IRBasicBlock: in_set: set["IRBasicBlock"] out_set: set["IRBasicBlock"] out_vars: set[IRVariable] - liveness = {} def __init__(self, label: IRLabel, parent: "IRFunction") -> None: assert isinstance(label, IRLabel), "label must be an IRLabel" @@ -176,7 +180,7 @@ def remove_out(self, bb: "IRBasicBlock") -> None: @property def in_vars(self) -> set[IRVariable]: - return self.liveness[self.instructions[0]] + return self.instructions[0].liveness def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" @@ -198,9 +202,9 @@ def compute_liveness(self) -> None: out = instruction.get_output_operands()[0] if out in self.out_vars: self.out_vars.remove(out) - self.liveness[instruction] = self.out_vars.copy() + instruction.liveness = self.out_vars.copy() - print("Liveness:", self.label, "\n", self.liveness[self.instructions[0]], "\n") + print("Liveness:", self.label, "\n", self.in_vars, "\n") def __repr__(self) -> str: s = f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]} OUT={[bb.label for bb in self.out_set]} \n" From cf1ef147e577e375a16e2fad86c2436f5629053b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Jun 2023 18:32:39 +0300 Subject: [PATCH 035/471] remove debug prints --- vyper/ir/ir_to_bb_pass.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 893a64479b..db9b2db3ce 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -89,9 +89,7 @@ def _calculate_liveness(bb: IRBasicBlock) -> None: for out_bb in bb.out_set: _calculate_liveness(out_bb) in_vars = out_bb.in_vars - print(in_vars, bb.out_vars) bb.out_vars = bb.out_vars.union(in_vars) - print(in_vars, bb.out_vars) bb.compute_liveness() From b36f676e207404bdff2e2176cad22d9e8cc2ab86 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Jun 2023 18:34:40 +0300 Subject: [PATCH 036/471] consistent naming --- vyper/codegen/ir_basicblock.py | 2 +- vyper/ir/ir_to_bb_pass.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index e880038f01..3d047ed380 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -193,7 +193,7 @@ def is_terminal(self) -> bool: assert len(self.instructions) > 0, "basic block must have at least one instruction" return self.instructions[-1].opcode in TERMINAL_IR_INSTRUCTIONS - def compute_liveness(self) -> None: + def calculate_liveness(self) -> None: """ Compute liveness of each instruction in basic block. """ diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index db9b2db3ce..36dd72f64b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -91,7 +91,7 @@ def _calculate_liveness(bb: IRBasicBlock) -> None: in_vars = out_bb.in_vars bb.out_vars = bb.out_vars.union(in_vars) - bb.compute_liveness() + bb.calculate_liveness() def _convert_binary_op(ctx: IRFunction, ir: IRnode) -> str: From ce8ca6c3e7fe49da9cf9ce5e65e3249532b4b5b7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 29 Jun 2023 18:36:34 +0300 Subject: [PATCH 037/471] remove debug print --- vyper/codegen/ir_basicblock.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 3d047ed380..ea5c278644 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -204,8 +204,6 @@ def calculate_liveness(self) -> None: self.out_vars.remove(out) instruction.liveness = self.out_vars.copy() - print("Liveness:", self.label, "\n", self.in_vars, "\n") - def __repr__(self) -> str: s = f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]} OUT={[bb.label for bb in self.out_set]} \n" for instruction in self.instructions: From d5fd7752054f63fde013c45896d27d30f539e006 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 09:12:10 +0300 Subject: [PATCH 038/471] jmp & jnz instructions instead of just br --- vyper/ir/ir_to_bb_pass.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 36dd72f64b..e7108a3e9b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -7,7 +7,8 @@ from vyper.evm.opcodes import get_opcodes TERMINATOR_IR_INSTRUCTIONS = [ - "br", + "jmp", + "jnz", "ret", "revert", "assert", @@ -73,7 +74,7 @@ def _calculate_in_set(ctx: IRFunction) -> None: last_inst.opcode in TERMINATOR_IR_INSTRUCTIONS ), "Last instruction should be a terminator" - if last_inst.opcode == "br": + if last_inst.opcode in ["jmp", "jnz"]: ops = last_inst.get_label_operands() assert len(ops) >= 1, "br instruction should have at least one label operand" for op in ops: @@ -136,7 +137,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i _convert_ir_basicblock(ctx, ir.args[1]) - inst = IRInstruction("br", [cont_ret, then_block.label, else_block.label]) + inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) current_bb.append_instruction(inst) # exit bb @@ -144,7 +145,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i bb = IRBasicBlock(exit_label, ctx) bb = ctx.append_basic_block(bb) - exit_inst = IRInstruction("br", [bb.label]) + exit_inst = IRInstruction("jmp", [bb.label]) else_block.append_instruction(exit_inst) elif ir.value == "with": @@ -188,7 +189,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "goto": - inst = IRInstruction("br", [IRLabel(ir.args[0].value)]) + inst = IRInstruction("jmp", [IRLabel(ir.args[0].value)]) ctx.get_basic_block().append_instruction(inst) label = ctx.get_next_label() From af4d31c83243a53bc1b25b88af2a49493df1948d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 09:21:18 +0300 Subject: [PATCH 039/471] handle assert as a last instruction --- vyper/codegen/ir_function.py | 9 +++++++++ vyper/ir/ir_to_bb_pass.py | 2 ++ 2 files changed, 11 insertions(+) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index b1b07c7a9a..bca84a9c3c 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -53,6 +53,15 @@ def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: return bb assert False, f"Basic block '{label}' not found" + def get_basic_block_after(self, label: IRLabel) -> IRBasicBlock: + """ + Get basic block after label. + """ + for i, bb in enumerate(self.basic_blocks[:-1]): + if bb.label.value == label.value: + return self.basic_blocks[i + 1] + assert False, f"Basic block after '{label}' not found" + def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: """ Get basic blocks that contain label. diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index e7108a3e9b..1ba7195604 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -79,6 +79,8 @@ def _calculate_in_set(ctx: IRFunction) -> None: assert len(ops) >= 1, "br instruction should have at least one label operand" for op in ops: ctx.get_basic_block(op.value).add_in(bb) + elif last_inst.opcode == "assert": + ctx.get_basic_block_after(bb.label).add_in(bb) # Fill in the "out" set for each basic block for bb in ctx.basic_blocks: From bed6ef246ff6459e1d4edaff932ad03539c78970 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 13:34:50 +0300 Subject: [PATCH 040/471] branching SSA --- vyper/ir/ir_to_bb_pass.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 1ba7195604..5ad95ad8db 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -17,14 +17,21 @@ _symbols = {} +def _get_symbols_common(a: dict, b: dict) -> dict: + return {k: [a[k], b[k]] for k in a.keys() & b.keys() if a[k] != b[k]} + + def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: global_function = IRFunction(IRLabel("global")) _convert_ir_basicblock(global_function, ir) while _optimize_empty_basicblocks(global_function): pass + + # TODO: can be split into a new pass _calculate_in_set(global_function) _calculate_liveness(global_function.basic_blocks[0]) # _optimize_unused_variables(global_function) + return global_function @@ -130,8 +137,10 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.append_basic_block(else_block) # convert "else" + start_syms = _symbols.copy() if len(ir.args) == 3: _convert_ir_basicblock(ctx, ir.args[2]) + after_else_syms = _symbols.copy() # convert "then" then_block = IRBasicBlock(ctx.get_next_label(), ctx) @@ -142,11 +151,20 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) current_bb.append_instruction(inst) + after_then_syms = _symbols.copy() + # exit bb exit_label = ctx.get_next_label() bb = IRBasicBlock(exit_label, ctx) bb = ctx.append_basic_block(bb) + for sym, val in _get_symbols_common(after_then_syms, after_else_syms).items(): + ret = ctx.get_next_variable() + _symbols[sym] = ret + bb.append_instruction( + IRInstruction("select", [then_block.label, val[0], else_block.label, val[1]], ret) + ) + exit_inst = IRInstruction("jmp", [bb.label]) else_block.append_instruction(exit_inst) @@ -223,11 +241,10 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i elif ir.value == "return": pass elif ir.value == "exit_to": - ret = _convert_ir_basicblock(ctx, ir.args[2]) - - # FIXME: for now - - inst = IRInstruction("ret", [ret]) + sym = ir.args[1] + new_var = _symbols.get(f"&{sym.value}", None) + assert new_var != None, "exit_to with undefined variable" + inst = IRInstruction("ret", [new_var]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": inst = IRInstruction("revert", ir.args) From 8b8b31b7f2b5309464646c2cd1ebfb7ea7c9ba2a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 14:24:28 +0300 Subject: [PATCH 041/471] select liveness propagation and unused var elimination --- vyper/codegen/ir_basicblock.py | 21 ++++++++++++++++++--- vyper/ir/ir_to_bb_pass.py | 23 +++++++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index ea5c278644..c491443832 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -178,9 +178,18 @@ def union_out(self, bb_set: set["IRBasicBlock"]) -> None: def remove_out(self, bb: "IRBasicBlock") -> None: self.out_set.remove(bb) - @property - def in_vars(self) -> set[IRVariable]: - return self.instructions[0].liveness + def in_vars_for(self, bb: "IRBasicBlock" = None) -> set[IRVariable]: + liveness = self.instructions[0].liveness.copy() + + if bb: + for inst in self.instructions: + if inst.opcode == "select": + if inst.operands[0] == bb.label and inst.operands[3] in liveness: + liveness.remove(inst.operands[3]) + if inst.operands[2] == bb.label and inst.operands[1] in liveness: + liveness.remove(inst.operands[1]) + + return liveness def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" @@ -204,6 +213,12 @@ def calculate_liveness(self) -> None: self.out_vars.remove(out) instruction.liveness = self.out_vars.copy() + def get_liveness(self) -> set[IRVariable]: + """ + Get liveness of basic block. + """ + return self.instructions[-1].liveness + def __repr__(self) -> str: s = f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]} OUT={[bb.label for bb in self.out_set]} \n" for instruction in self.instructions: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 5ad95ad8db..fb4e82f75f 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -30,11 +30,30 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: # TODO: can be split into a new pass _calculate_in_set(global_function) _calculate_liveness(global_function.basic_blocks[0]) - # _optimize_unused_variables(global_function) + + # Optimization pass: Remove unused variables + _optimize_unused_variables(global_function) return global_function +def _optimize_unused_variables(ctx: IRFunction) -> int: + """ + Remove unused variables. + """ + count = 0 + removeList = [] + for bb in ctx.basic_blocks: + for i, inst in enumerate(bb.instructions[:-1]): + if inst.ret and inst.ret not in bb.instructions[i + 1].liveness: + removeList.append(inst) + count += 1 + + bb.instructions = [inst for inst in bb.instructions if inst not in removeList] + + return count + + def _optimize_empty_basicblocks(ctx: IRFunction) -> None: """ Remove empty basic blocks. @@ -98,7 +117,7 @@ def _calculate_in_set(ctx: IRFunction) -> None: def _calculate_liveness(bb: IRBasicBlock) -> None: for out_bb in bb.out_set: _calculate_liveness(out_bb) - in_vars = out_bb.in_vars + in_vars = out_bb.in_vars_for(bb) bb.out_vars = bb.out_vars.union(in_vars) bb.calculate_liveness() From 247e452f792d51c5707472fd043c25ee444a1844 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 15:11:25 +0300 Subject: [PATCH 042/471] proper exit fix --- vyper/ir/ir_to_bb_pass.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index fb4e82f75f..8f3c21bab5 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -260,8 +260,9 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i elif ir.value == "return": pass elif ir.value == "exit_to": + arg_2 = _convert_ir_basicblock(ctx, ir.args[2]) sym = ir.args[1] - new_var = _symbols.get(f"&{sym.value}", None) + new_var = _symbols.get(f"&{sym.value}", arg_2) assert new_var != None, "exit_to with undefined variable" inst = IRInstruction("ret", [new_var]) ctx.get_basic_block().append_instruction(inst) From 657b881bc2f68bc61fa8485625312f60073d55bc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 15:25:35 +0300 Subject: [PATCH 043/471] dfg --- vyper/codegen/dfg.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 vyper/codegen/dfg.py diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py new file mode 100644 index 0000000000..e399f2fbd2 --- /dev/null +++ b/vyper/codegen/dfg.py @@ -0,0 +1,23 @@ +from vyper.codegen.ir_basicblock import IRInstruction +from vyper.codegen.ir_function import IRFunction, IRInstruction + + +class DFGNode: + instruction: IRInstruction + predecessors: list["DFGNode"] + successors: list["DFGNode"] + + +def convert_ir_to_dfg(ir: IRFunction) -> DFGNode: + dfg_nodes = {} + + for bb in ir.basic_blocks: + for inst in bb.instructions: + operands = inst.get_input_operands() + res = inst.get_output_operands()[0] + + if res in dfg_nodes: + result_node = dfg_nodes[res] + else: + result_node = DFGNode(res) + dfg_nodes[res] = result_node From 998747d3dce249a030c617778ac82f0345a670f0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 17:09:41 +0300 Subject: [PATCH 044/471] checkpoint --- vyper/codegen/dfg.py | 82 ++++++++++++++++++++++++++++------ vyper/codegen/ir_basicblock.py | 16 ++++--- vyper/ir/ir_to_bb_pass.py | 6 +++ 3 files changed, 85 insertions(+), 19 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index e399f2fbd2..2886d35f06 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,23 +1,77 @@ -from vyper.codegen.ir_basicblock import IRInstruction -from vyper.codegen.ir_function import IRFunction, IRInstruction +from vyper.codegen.ir_basicblock import TERMINAL_IR_INSTRUCTIONS, IRInstruction, IROperant +from vyper.codegen.ir_function import IRFunction class DFGNode: - instruction: IRInstruction + value: IRInstruction | IROperant predecessors: list["DFGNode"] successors: list["DFGNode"] + def __init__(self, value: IRInstruction | IROperant): + self.value = value + self.predecessors = [] + self.successors = [] -def convert_ir_to_dfg(ir: IRFunction) -> DFGNode: - dfg_nodes = {} - for bb in ir.basic_blocks: +dfg_inputs = {} +dfg_outputs = {} + + +def convert_ir_to_dfg(ctx: IRFunction) -> None: + for bb in ctx.basic_blocks: + for inst in bb.instructions: + operands = inst.get_input_variables() + res = inst.get_output_operands() + + for op in operands: + dfg_inputs[op] = inst + + for op in res: + dfg_outputs[op] = inst + + +visited_instructions = set() + + +def generate_evm(ctx: IRFunction) -> list[str]: + dfg_inputs = {} + dfg_outputs = {} + + assembly = [] + + for bb in ctx.basic_blocks: for inst in bb.instructions: - operands = inst.get_input_operands() - res = inst.get_output_operands()[0] - - if res in dfg_nodes: - result_node = dfg_nodes[res] - else: - result_node = DFGNode(res) - dfg_nodes[res] = result_node + _generate_evm_for_instruction_r(assembly, inst) + + return assembly + + +def _generate_evm_for_instruction_r(assembly: list, inst: IRInstruction) -> None: + if inst in visited_instructions: + return + + visited_instructions.add(inst) + + for op in inst.get_input_operands(): + _generate_evm_for_instruction_r(assembly, dfg_outputs[op]) + + # Basically handle fences unmovable instructions etc WIP + # if inst.opcode in ["ret"]: + # return + # generate EVM for op + print("Generating EVM for instruction: ", inst) + _generate_evm_for_instruction(assembly, inst) + + # for op in inst.get_output_operands(): + # _generate_evm_for_instruction_r(assembly, dfg_inputs[op]) + + +def _generate_evm_for_instruction(assembly: list, inst: IRInstruction) -> None: + opcode = inst.opcode + + if opcode == "calldatasize": + assembly.append("CALLDATASIZE") + elif opcode == "calldatacopy": + assembly.append("CALLDATACOPY") + elif opcode == "le": + assembly.append("LE") diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index c491443832..0059a72388 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -92,15 +92,21 @@ def get_label_operands(self) -> list[IRLabel]: Get all labels in instruction. """ return [op for op in self.operands if isinstance(op, IRLabel)] - + def get_input_operands(self) -> list[IROperant]: """ - Get all input operands in instruction. + Get all input operants in instruction. + """ + return [op for op in self.operands if isinstance(op, IRLabel) == False] + + def get_input_variables(self) -> list[IROperant]: + """ + Get all input variables in instruction. """ return [op for op in self.operands if isinstance(op, IRVariable)] def get_output_operands(self) -> list[IROperant]: - return [self.ret] + return [self.ret] if self.ret else [] def __repr__(self) -> str: s = "" @@ -207,8 +213,8 @@ def calculate_liveness(self) -> None: Compute liveness of each instruction in basic block. """ for instruction in self.instructions[::-1]: - self.out_vars = self.out_vars.union(instruction.get_input_operands()) - out = instruction.get_output_operands()[0] + self.out_vars = self.out_vars.union(instruction.get_input_variables()) + out = instruction.get_output_operands()[0] if len(instruction.get_output_operands()) > 0 else None if out in self.out_vars: self.out_vars.remove(out) instruction.liveness = self.out_vars.copy() diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 8f3c21bab5..e3f968f5d2 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,4 +1,5 @@ from typing import Optional, Union +from vyper.codegen.dfg import convert_ir_to_dfg, generate_evm from vyper.codegen.global_context import GlobalContext from vyper.codegen.ir_node import IRnode from vyper.codegen.ir_function import IRFunctionBase, IRFunction, IRFunctionIntrinsic @@ -34,6 +35,11 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: # Optimization pass: Remove unused variables _optimize_unused_variables(global_function) + convert_ir_to_dfg(global_function) + + assembly = generate_evm(global_function) + print(assembly) + return global_function From 5215403375297b324abcf88500d769602fdfaaea Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 17:59:22 +0300 Subject: [PATCH 045/471] emit --- vyper/codegen/ir_basicblock.py | 34 +++++++++++++++++++++++++++++----- vyper/ir/ir_to_bb_pass.py | 6 +++--- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 0059a72388..7c59b32763 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -28,6 +28,9 @@ def __repr__(self) -> str: return f"\t# line {self.line_no}: {src}".expandtabs(20) +IROperantValue = str | int + + class IROperant: """ IROperant represents an operand in IR. An operand can be a variable, label, or a constant. @@ -35,20 +38,37 @@ class IROperant: value: str - def __init__(self, value: str) -> None: - assert isinstance(value, str), "value must be a string" + def __init__(self, value: IROperantValue) -> None: + assert isinstance(value, IROperantValue), "value must be a string" self.value = value + @property + def is_literal(self) -> bool: + return False + def __repr__(self) -> str: return self.value +class IRLiteral(IROperant): + """ + IRLiteral represents a literal in IR + """ + + def __init__(self, value: IROperantValue) -> None: + super().__init__(value) + + @property + def is_literal(self) -> bool: + return True + + class IRVariable(IROperant): """ IRVariable represents a variable in IR. A variable is a string that starts with a %. """ - def __init__(self, value: str) -> None: + def __init__(self, value: IROperantValue) -> None: super().__init__(value) @@ -92,7 +112,7 @@ def get_label_operands(self) -> list[IRLabel]: Get all labels in instruction. """ return [op for op in self.operands if isinstance(op, IRLabel)] - + def get_input_operands(self) -> list[IROperant]: """ Get all input operants in instruction. @@ -214,7 +234,11 @@ def calculate_liveness(self) -> None: """ for instruction in self.instructions[::-1]: self.out_vars = self.out_vars.union(instruction.get_input_variables()) - out = instruction.get_output_operands()[0] if len(instruction.get_output_operands()) > 0 else None + out = ( + instruction.get_output_operands()[0] + if len(instruction.get_output_operands()) > 0 + else None + ) if out in self.out_vars: self.out_vars.remove(out) instruction.liveness = self.out_vars.copy() diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index e3f968f5d2..2bc0a89c5c 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -4,7 +4,7 @@ from vyper.codegen.ir_node import IRnode from vyper.codegen.ir_function import IRFunctionBase, IRFunction, IRFunctionIntrinsic from vyper.codegen.ir_basicblock import IRInstruction, IRDebugInfo -from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRVariable +from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRLiteral from vyper.evm.opcodes import get_opcodes TERMINATOR_IR_INSTRUCTIONS = [ @@ -132,7 +132,7 @@ def _calculate_liveness(bb: IRBasicBlock) -> None: def _convert_binary_op(ctx: IRFunction, ir: IRnode) -> str: arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) - args = [arg_0, arg_1] + args = [arg_1, arg_0] ret = ctx.get_next_variable() @@ -273,7 +273,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i inst = IRInstruction("ret", [new_var]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": - inst = IRInstruction("revert", ir.args) + inst = IRInstruction("revert", []) ctx.get_basic_block().append_instruction(inst) elif ir.value == "pass": pass From 8fe7d1948a807470a136ec6092e26d43e179f538 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 18:12:23 +0300 Subject: [PATCH 046/471] more generation --- vyper/codegen/dfg.py | 77 +++++++++++++++++++++++++++++---------- vyper/ir/ir_to_bb_pass.py | 2 +- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 2886d35f06..ec0816a281 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,6 +1,23 @@ from vyper.codegen.ir_basicblock import TERMINAL_IR_INSTRUCTIONS, IRInstruction, IROperant from vyper.codegen.ir_function import IRFunction +ONE_TO_ONE_INSTRUCTIONS = [ + "revert", + "assert", + "calldatasize", + "calldatacopy", + "calldataload", + "callvalue", + "shr", + "xor", + "or", + "add", + "sub", + "mul", + "div", + "eq", + "iszero", +] class DFGNode: value: IRInstruction | IROperant @@ -39,39 +56,61 @@ def generate_evm(ctx: IRFunction) -> list[str]: assembly = [] - for bb in ctx.basic_blocks: + for i, bb in enumerate(ctx.basic_blocks): + if i != 0: + assembly.append(f"_label_{bb.label}") + assembly.append("JUMPDEST") for inst in bb.instructions: - _generate_evm_for_instruction_r(assembly, inst) + _generate_evm_for_instruction_r(ctx, assembly, inst) return assembly +def _generate_evm_for_instruction_r(ctx: IRFunction, assembly: list, inst: IRInstruction) -> None: + for op in inst.get_output_operands(): + _generate_evm_for_instruction_r(ctx, assembly, dfg_inputs[op]) -def _generate_evm_for_instruction_r(assembly: list, inst: IRInstruction) -> None: if inst in visited_instructions: return - visited_instructions.add(inst) - for op in inst.get_input_operands(): - _generate_evm_for_instruction_r(assembly, dfg_outputs[op]) + operands = inst.get_input_operands() # Basically handle fences unmovable instructions etc WIP # if inst.opcode in ["ret"]: # return # generate EVM for op - print("Generating EVM for instruction: ", inst) - _generate_evm_for_instruction(assembly, inst) - - # for op in inst.get_output_operands(): - # _generate_evm_for_instruction_r(assembly, dfg_inputs[op]) - - -def _generate_evm_for_instruction(assembly: list, inst: IRInstruction) -> None: opcode = inst.opcode - if opcode == "calldatasize": - assembly.append("CALLDATASIZE") - elif opcode == "calldatacopy": - assembly.append("CALLDATACOPY") + # if opcode in ["le"]: + # operands.reverse() + + _emit_input_operands(ctx, assembly, operands) + print("Generating EVM for", inst) + if opcode in ONE_TO_ONE_INSTRUCTIONS: + assembly.append(opcode.upper()) + elif opcode == "jnz": + assembly.append(f"_label_{inst.operands[1].value}") + assembly.append("JUMPI") + elif opcode == "jmp": + assembly.append(f"_label_{inst.operands[0].value}") + assembly.append("JUMP") elif opcode == "le": - assembly.append("LE") + assembly.append("GT") + elif opcode == "ge": + assembly.append("LT") + elif opcode == "ret": + assembly.append("RETURN") + elif opcode == "select": + assembly.append("select") # TODO: Implement + else: + raise Exception(f"Unknown opcode: {opcode}") + + +def _emit_input_operands(ctx: IRFunction, assembly: list, ops: list[IROperant]) -> None: + for op in ops: + if isinstance(op, int): + assembly.append(f"PUSH1") + assembly.append(f"{op:#x}") + continue + _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op]) + diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 2bc0a89c5c..ffdcd050bf 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -38,7 +38,7 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: convert_ir_to_dfg(global_function) assembly = generate_evm(global_function) - print(assembly) + print(" ".join(assembly)) return global_function From 085d4ebf086ac997d644a3105f33b1f9a485bae8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 18:22:10 +0300 Subject: [PATCH 047/471] reordering --- vyper/codegen/dfg.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index ec0816a281..6d7ed15524 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -56,16 +56,19 @@ def generate_evm(ctx: IRFunction) -> list[str]: assembly = [] + FIXED = set(["ret", "assert", "revert"]) + for i, bb in enumerate(ctx.basic_blocks): if i != 0: assembly.append(f"_label_{bb.label}") assembly.append("JUMPDEST") - for inst in bb.instructions: - _generate_evm_for_instruction_r(ctx, assembly, inst) + for inst in bb.instructions[:-1]: + _generate_evm_for_instruction_r(ctx, assembly, inst, FIXED) + _generate_evm_for_instruction_r(ctx, assembly, bb.instructions[-1]) return assembly -def _generate_evm_for_instruction_r(ctx: IRFunction, assembly: list, inst: IRInstruction) -> None: +def _generate_evm_for_instruction_r(ctx: IRFunction, assembly: list, inst: IRInstruction, fixed: set = set()) -> None: for op in inst.get_output_operands(): _generate_evm_for_instruction_r(ctx, assembly, dfg_inputs[op]) @@ -76,8 +79,8 @@ def _generate_evm_for_instruction_r(ctx: IRFunction, assembly: list, inst: IRIns operands = inst.get_input_operands() # Basically handle fences unmovable instructions etc WIP - # if inst.opcode in ["ret"]: - # return + if inst.opcode in fixed: + return # generate EVM for op opcode = inst.opcode From f1e425eddc1bdb5439454cc4a603ab8619a00295 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 4 Jul 2023 18:38:14 +0300 Subject: [PATCH 048/471] wip --- vyper/codegen/dfg.py | 50 ++++++++++++++++++++++++++++----------- vyper/ir/ir_to_bb_pass.py | 2 -- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 6d7ed15524..949a0a6ba6 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,4 +1,4 @@ -from vyper.codegen.ir_basicblock import TERMINAL_IR_INSTRUCTIONS, IRInstruction, IROperant +from vyper.codegen.ir_basicblock import TERMINAL_IR_INSTRUCTIONS, IRBasicBlock, IRInstruction, IROperant from vyper.codegen.ir_function import IRFunction ONE_TO_ONE_INSTRUCTIONS = [ @@ -35,6 +35,26 @@ def __init__(self, value: IRInstruction | IROperant): def convert_ir_to_dfg(ctx: IRFunction) -> None: + global dfg_inputs + global dfg_outputs + dfg_inputs = {} + dfg_outputs = {} + for bb in ctx.basic_blocks: + for inst in bb.instructions: + operands = inst.get_input_variables() + res = inst.get_output_operands() + + for op in operands: + dfg_inputs[op] = inst + + for op in res: + dfg_outputs[op] = inst + +def convert_bb_to_dfg(ctx: IRFunction, bb: IRBasicBlock) -> None: + global dfg_inputs + global dfg_outputs + dfg_inputs = {} + dfg_outputs = {} for bb in ctx.basic_blocks: for inst in bb.instructions: operands = inst.get_input_variables() @@ -51,26 +71,31 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: def generate_evm(ctx: IRFunction) -> list[str]: - dfg_inputs = {} - dfg_outputs = {} - assembly = [] FIXED = set(["ret", "assert", "revert"]) + #convert_ir_to_dfg(ctx) + for i, bb in enumerate(ctx.basic_blocks): + convert_bb_to_dfg(ctx,bb) if i != 0: assembly.append(f"_label_{bb.label}") assembly.append("JUMPDEST") - for inst in bb.instructions[:-1]: + for inst in bb.instructions: _generate_evm_for_instruction_r(ctx, assembly, inst, FIXED) - _generate_evm_for_instruction_r(ctx, assembly, bb.instructions[-1]) + for inst in bb.instructions: + _generate_evm_for_instruction_r(ctx, assembly, inst) return assembly def _generate_evm_for_instruction_r(ctx: IRFunction, assembly: list, inst: IRInstruction, fixed: set = set()) -> None: + # Basically handle fences unmovable instructions etc WIP + if inst.opcode in fixed: + return + for op in inst.get_output_operands(): - _generate_evm_for_instruction_r(ctx, assembly, dfg_inputs[op]) + _generate_evm_for_instruction_r(ctx, assembly, dfg_inputs[op], fixed) if inst in visited_instructions: return @@ -78,17 +103,14 @@ def _generate_evm_for_instruction_r(ctx: IRFunction, assembly: list, inst: IRIns operands = inst.get_input_operands() - # Basically handle fences unmovable instructions etc WIP - if inst.opcode in fixed: - return # generate EVM for op opcode = inst.opcode # if opcode in ["le"]: # operands.reverse() - _emit_input_operands(ctx, assembly, operands) - print("Generating EVM for", inst) + _emit_input_operands(ctx, assembly, operands, fixed) + #print("Generating EVM for", inst) if opcode in ONE_TO_ONE_INSTRUCTIONS: assembly.append(opcode.upper()) elif opcode == "jnz": @@ -109,11 +131,11 @@ def _generate_evm_for_instruction_r(ctx: IRFunction, assembly: list, inst: IRIns raise Exception(f"Unknown opcode: {opcode}") -def _emit_input_operands(ctx: IRFunction, assembly: list, ops: list[IROperant]) -> None: +def _emit_input_operands(ctx: IRFunction, assembly: list, ops: list[IROperant], fixed: set) -> None: for op in ops: if isinstance(op, int): assembly.append(f"PUSH1") assembly.append(f"{op:#x}") continue - _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op]) + _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op], fixed) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index ffdcd050bf..3ca81a720b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -35,8 +35,6 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: # Optimization pass: Remove unused variables _optimize_unused_variables(global_function) - convert_ir_to_dfg(global_function) - assembly = generate_evm(global_function) print(" ".join(assembly)) From 673fa8e1df7e02ae0c2ca650ec0f65864c6a2e87 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 11 Jul 2023 13:13:08 +0300 Subject: [PATCH 049/471] Squashed commit --- vyper/cli/vyper_compile.py | 8 ++ vyper/codegen/dfg.py | 249 ++++++++++++++++++++++++++------- vyper/codegen/ir_basicblock.py | 16 ++- vyper/compiler/__init__.py | 4 + vyper/compiler/phases.py | 18 ++- vyper/ir/compile_ir.py | 6 +- vyper/ir/ir_to_bb_pass.py | 74 ++++++---- 7 files changed, 291 insertions(+), 84 deletions(-) diff --git a/vyper/cli/vyper_compile.py b/vyper/cli/vyper_compile.py index f5e113116d..06f1b190cd 100755 --- a/vyper/cli/vyper_compile.py +++ b/vyper/cli/vyper_compile.py @@ -133,6 +133,11 @@ def _parse_args(argv): "-p", help="Set the root path for contract imports", default=".", dest="root_folder" ) parser.add_argument("-o", help="Set the output path", dest="output_path") + parser.add_argument( + "--experimental-codegen", + help="The compiler use the new IR codegen. This is an experimental feature.", + action="store_true", + ) args = parser.parse_args(argv) @@ -162,6 +167,7 @@ def _parse_args(argv): args.no_optimize, args.storage_layout, args.no_bytecode_metadata, + args.experimental_codegen, ) if args.output_path: @@ -257,6 +263,7 @@ def compile_files( no_optimize: bool = False, storage_layout: Iterable[str] = None, no_bytecode_metadata: bool = False, + experimental_codegen: bool = False, ) -> OrderedDict: root_path = Path(root_folder).resolve() if not root_path.exists(): @@ -301,6 +308,7 @@ def compile_files( storage_layouts=storage_layouts, show_gas_estimates=show_gas_estimates, no_bytecode_metadata=no_bytecode_metadata, + experimental_codegen=experimental_codegen, ) if show_version: compiler_data["version"] = vyper.__version__ diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 949a0a6ba6..65e47505bf 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,13 +1,20 @@ -from vyper.codegen.ir_basicblock import TERMINAL_IR_INSTRUCTIONS, IRBasicBlock, IRInstruction, IROperant +from vyper.codegen.ir_basicblock import ( + TERMINAL_IR_INSTRUCTIONS, + IRBasicBlock, + IRInstruction, + IROperant, + IRLiteral, +) from vyper.codegen.ir_function import IRFunction +from vyper.ir.compile_ir import PUSH, optimize_assembly ONE_TO_ONE_INSTRUCTIONS = [ "revert", "assert", - "calldatasize", - "calldatacopy", - "calldataload", - "callvalue", + "calldatasize", + "calldatacopy", + "calldataload", + "callvalue", "shr", "xor", "or", @@ -17,8 +24,21 @@ "div", "eq", "iszero", + "lg", + "lt", + "slt", + "sgt", ] +OPERANT_ORDER_IRELEVANT_INSTRUCTIONS = [ + "xor", + "or", + "add", + "mul", + "eq", +] + + class DFGNode: value: IRInstruction | IROperant predecessors: list["DFGNode"] @@ -33,24 +53,48 @@ def __init__(self, value: IRInstruction | IROperant): dfg_inputs = {} dfg_outputs = {} +NOT_IN_STACK = 1 -def convert_ir_to_dfg(ctx: IRFunction) -> None: - global dfg_inputs - global dfg_outputs - dfg_inputs = {} - dfg_outputs = {} - for bb in ctx.basic_blocks: - for inst in bb.instructions: - operands = inst.get_input_variables() - res = inst.get_output_operands() - for op in operands: - dfg_inputs[op] = inst +def stack_map_get_depth_in(stack_map: list[str], op: IROperant) -> int: + """ + Returns the depth of the first matching operand in the stack map. + If the operand is not in the stack map, returns NOT_IN_STACK. + """ + for i, stack_op in enumerate(stack_map[::-1]): + if isinstance(stack_op, IROperant) and stack_op.value == op.value: + return -i - for op in res: - dfg_outputs[op] = inst + return NOT_IN_STACK + + +def stack_map_peek(stack_map: list[str], depth: int) -> IROperant: + """ + Returns the top of the stack map. + """ + return stack_map[-depth - 1] + + +def stack_map_poke(stack_map: list[str], depth: int, op: IROperant) -> None: + """ + Pokes an operand at the given depth in the stack map. + """ + stack_map[-depth - 1] = op + + +def stack_map_dup(stack_map: list[str], depth: int) -> None: + stack_map.append(stack_map_peek(stack_map, depth)) -def convert_bb_to_dfg(ctx: IRFunction, bb: IRBasicBlock) -> None: + +def stack_map_swap(stack_map: list[str], depth: int) -> None: + stack_map[-depth - 1], stack_map[-1] = stack_map[-1], stack_map[-depth - 1] + + +def stack_map_pop(stack_map: list[str]) -> None: + stack_map.pop() + + +def convert_ir_to_dfg(ctx: IRFunction) -> None: global dfg_inputs global dfg_outputs dfg_inputs = {} @@ -61,6 +105,7 @@ def convert_bb_to_dfg(ctx: IRFunction, bb: IRBasicBlock) -> None: res = inst.get_output_operands() for op in operands: + op.use_count += 1 dfg_inputs[op] = inst for op in res: @@ -72,70 +117,176 @@ def convert_bb_to_dfg(ctx: IRFunction, bb: IRBasicBlock) -> None: def generate_evm(ctx: IRFunction) -> list[str]: assembly = [] + stack_map = [] + convert_ir_to_dfg(ctx) + + for bb in ctx.basic_blocks: + for inst in bb.instructions: + if inst.opcode != "select": + continue - FIXED = set(["ret", "assert", "revert"]) + ret_op = inst.get_output_operands()[0] - #convert_ir_to_dfg(ctx) + block_a = ctx.get_basic_block(inst.operands[0].value) + block_b = ctx.get_basic_block(inst.operands[2].value) + + block_a.phi_vars[ret_op] = (inst.operands[1], inst.operands[3]) + block_b.phi_vars[ret_op] = (inst.operands[3], inst.operands[1]) for i, bb in enumerate(ctx.basic_blocks): - convert_bb_to_dfg(ctx,bb) if i != 0: - assembly.append(f"_label_{bb.label}") + assembly.append(f"_sym_label_{bb.label}") assembly.append("JUMPDEST") for inst in bb.instructions: - _generate_evm_for_instruction_r(ctx, assembly, inst, FIXED) - for inst in bb.instructions: - _generate_evm_for_instruction_r(ctx, assembly, inst) + _generate_evm_for_instruction_r(ctx, assembly, inst, stack_map) + + optimize_assembly(assembly) return assembly -def _generate_evm_for_instruction_r(ctx: IRFunction, assembly: list, inst: IRInstruction, fixed: set = set()) -> None: - # Basically handle fences unmovable instructions etc WIP - if inst.opcode in fixed: - return - + +def _generate_evm_for_instruction_r( + ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: list[str] +) -> None: for op in inst.get_output_operands(): - _generate_evm_for_instruction_r(ctx, assembly, dfg_inputs[op], fixed) + target = dfg_inputs[op] + if target.parent != inst.parent: + continue + _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) if inst in visited_instructions: return visited_instructions.add(inst) - operands = inst.get_input_operands() - # generate EVM for op opcode = inst.opcode + operands = inst.get_input_operands() + + if opcode == "select": + ret = inst.get_output_operands()[0] + inputs = inst.get_input_variables() + for input in inputs: + input.value = ret.value + return + + _emit_input_operands(ctx, assembly, operands, stack_map) + + ### BEGIN STACK HANDLING CASES ### + if len(operands) == 1: + op = operands[0] + ucc = inst.get_use_count_correction(op) + assert op.use_count >= ucc, "Operand used up" + depth = stack_map_get_depth_in(stack_map, op) + assert depth != NOT_IN_STACK, "Operand not in stack" + needs_copy = op.use_count - ucc > 1 + in_place = depth == 0 + if in_place: + if needs_copy: + assembly.append("DUP1") + stack_map_dup(stack_map, 0) + else: + if needs_copy: + assembly.append(f"DUP{-depth+1}") + stack_map_dup(stack_map, -depth) + assembly.append(f"SWAP1") + stack_map_swap(stack_map, -depth) + + if needs_copy: + op.use_count -= 1 + + elif len(operands) == 2: + op0, op1 = operands[0], operands[1] + ucc0, ucc1 = inst.get_use_count_correction(op0), inst.get_use_count_correction(op1) + assert op0.use_count >= ucc0, "Operand 0 used up" + assert op1.use_count >= ucc1, "Operand 1 used up" + depth0 = stack_map_get_depth_in(stack_map, op0) + depth1 = stack_map_get_depth_in(stack_map, op1) + assert depth0 != NOT_IN_STACK, f"Operand {op0} not in stack" + assert depth1 != NOT_IN_STACK, f"Operand {op1} not in stack" + needs_copy0 = op0.use_count - ucc0 > 1 + needs_copy1 = op1.use_count - ucc1 > 1 + in_place0 = depth0 == -1 + in_place1 = depth1 == 0 + if in_place0 and in_place1: + if needs_copy0: + assembly.append(f"DUP{-depth0+1}") + stack_map_dup(stack_map, -depth0) + assembly.append(f"SWAP1") + stack_map_swap(stack_map, 1) + if needs_copy1: + assembly.append(f"DUP{-depth1+1}") + stack_map_dup(stack_map, -depth1) + assembly.append(f"SWAP2") + stack_map_swap(stack_map, 2) + assembly.append(f"SWAP1") + stack_map_swap(stack_map, 1) + # else: + # assembly.append(f"SWAP1") + # stack_map_swap(stack_map, 1) + else: + if needs_copy0: + assembly.append(f"DUP{-depth0+1}") + stack_map_dup(stack_map, -depth0) + else: + if depth0 != 0: + assembly.append(f"SWAP{-depth0}") + stack_map_swap(stack_map, -depth0) + assembly.append(f"SWAP1") + stack_map_swap(stack_map, 1) + + depth1 = stack_map_get_depth_in(stack_map, op1) + in_place1 = depth1 == 0 + + if needs_copy1: + if not in_place1: + assembly.append(f"SWAP{-depth1}") + stack_map_swap(stack_map, -depth1) + assembly.append(f"DUP1") + stack_map_dup(stack_map, 0) + assembly.append(f"SWAP2") + stack_map_swap(stack_map, 2) + assembly.append(f"SWAP1") + stack_map_swap(stack_map, 1) + else: + if not in_place1: + assembly.append(f"SWAP{-depth1}") + stack_map_swap(stack_map, -depth1) + + if needs_copy0: + op0.use_count -= 1 + if needs_copy1: + op1.use_count -= 1 - # if opcode in ["le"]: - # operands.reverse() + del stack_map[len(stack_map) - len(operands) :] + if inst.ret is not None: + stack_map.append(inst.ret) - _emit_input_operands(ctx, assembly, operands, fixed) - #print("Generating EVM for", inst) if opcode in ONE_TO_ONE_INSTRUCTIONS: assembly.append(opcode.upper()) elif opcode == "jnz": - assembly.append(f"_label_{inst.operands[1].value}") + assembly.append(f"_sym_label_{inst.operands[1].value}") assembly.append("JUMPI") elif opcode == "jmp": - assembly.append(f"_label_{inst.operands[0].value}") + assembly.append(f"_sym_label_{inst.operands[0].value}") assembly.append("JUMP") - elif opcode == "le": + elif opcode == "gt": assembly.append("GT") - elif opcode == "ge": + elif opcode == "lt": assembly.append("LT") elif opcode == "ret": assembly.append("RETURN") elif opcode == "select": - assembly.append("select") # TODO: Implement + pass else: raise Exception(f"Unknown opcode: {opcode}") -def _emit_input_operands(ctx: IRFunction, assembly: list, ops: list[IROperant], fixed: set) -> None: +def _emit_input_operands( + ctx: IRFunction, assembly: list, ops: list[IROperant], stack_map: list[str] +) -> None: for op in ops: - if isinstance(op, int): - assembly.append(f"PUSH1") - assembly.append(f"{op:#x}") + if op.is_literal: + assembly.extend([*PUSH(op.value)]) + stack_map.append(op) continue - _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op], fixed) - + _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op], stack_map) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 7c59b32763..6f9b21cd9d 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -37,6 +37,7 @@ class IROperant: """ value: str + use_count: int = 0 def __init__(self, value: IROperantValue) -> None: assert isinstance(value, IROperantValue), "value must be a string" @@ -47,7 +48,7 @@ def is_literal(self) -> bool: return False def __repr__(self) -> str: - return self.value + return str(self.value) class IRLiteral(IROperant): @@ -57,6 +58,7 @@ class IRLiteral(IROperant): def __init__(self, value: IROperantValue) -> None: super().__init__(value) + self.use_count = 1 @property def is_literal(self) -> bool: @@ -97,6 +99,7 @@ class IRInstruction: ret: Optional[str] dbg: Optional[IRDebugInfo] liveness: set[IRVariable] + parent: Optional["IRBasicBlock"] def __init__( self, opcode: str, operands: list[IROperant], ret: str = None, dbg: IRDebugInfo = None @@ -106,6 +109,7 @@ def __init__( self.ret = ret self.dbg = dbg self.liveness = set() + self.parent = None def get_label_operands(self) -> list[IRLabel]: """ @@ -128,6 +132,13 @@ def get_input_variables(self) -> list[IROperant]: def get_output_operands(self) -> list[IROperant]: return [self.ret] if self.ret else [] + def get_use_count_correction(self, op: IROperant) -> int: + use_count_correction = 0 + for phi in self.parent.phi_vars.values(): + if phi[1] == op: + use_count_correction += 1 + return use_count_correction + def __repr__(self) -> str: s = "" if self.ret: @@ -176,6 +187,7 @@ class IRBasicBlock: in_set: set["IRBasicBlock"] out_set: set["IRBasicBlock"] out_vars: set[IRVariable] + phi_vars: dict[IRVariable, IRVariable] def __init__(self, label: IRLabel, parent: "IRFunction") -> None: assert isinstance(label, IRLabel), "label must be an IRLabel" @@ -185,6 +197,7 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.in_set = set() self.out_set = set() self.out_vars = set() + self.phi_vars = {} def add_in(self, bb: "IRBasicBlock") -> None: self.in_set.add(bb) @@ -219,6 +232,7 @@ def in_vars_for(self, bb: "IRBasicBlock" = None) -> set[IRVariable]: def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" + instruction.parent = self self.instructions.append(instruction) def is_terminal(self) -> bool: diff --git a/vyper/compiler/__init__.py b/vyper/compiler/__init__.py index fa4737acbc..8c70d6f742 100644 --- a/vyper/compiler/__init__.py +++ b/vyper/compiler/__init__.py @@ -58,6 +58,7 @@ def compile_codes( storage_layouts: Dict[ContractPath, StorageLayout] = None, show_gas_estimates: bool = False, no_bytecode_metadata: bool = False, + experimental_codegen: bool = False, ) -> OrderedDict: """ Generate compiler output(s) from one or more contract source codes. @@ -93,6 +94,8 @@ def compile_codes( * JSON interfaces are given as lists, vyper interfaces as strings no_bytecode_metadata: bool, optional Do not add metadata to bytecode. Defaults to False + experimental_codegen: bool, optional + Use experimental codegen. Defaults to False Returns ------- @@ -131,6 +134,7 @@ def compile_codes( storage_layout_override, show_gas_estimates, no_bytecode_metadata, + experimental_codegen, ) for output_format in output_formats[contract_name]: if output_format not in OUTPUT_FORMATS: diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 8a2fb281ca..277eb0dccb 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -11,7 +11,7 @@ from vyper.semantics import set_data_positions, validate_semantics from vyper.semantics.types.function import ContractFunctionT from vyper.typing import InterfaceImports, StorageLayout -from vyper.ir.ir_to_bb_pass import convert_ir_basicblock +from vyper.ir.ir_to_bb_pass import convert_ir_basicblock, generate_assembly_experimental class CompilerData: @@ -54,6 +54,7 @@ def __init__( storage_layout: StorageLayout = None, show_gas_estimates: bool = False, no_bytecode_metadata: bool = False, + experimental_codegen: bool = False, ) -> None: """ Initialization method. @@ -76,6 +77,8 @@ 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 """ self.contract_name = contract_name self.source_code = source_code @@ -85,6 +88,7 @@ def __init__( 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 @cached_property def vyper_module(self) -> vy_ast.Module: @@ -125,7 +129,7 @@ def _ir_output(self): @cached_property def bb_output(self): # fetch both deployment and runtime IR - return convert_ir_basicblock(self.global_ctx, self._ir_output[0]) + return convert_ir_basicblock(self._ir_output[0]) @property def ir_nodes(self) -> IRnode: @@ -148,11 +152,17 @@ def function_signatures(self) -> dict[str, ContractFunctionT]: @cached_property def assembly(self) -> list: - return generate_assembly(self.ir_nodes, self.no_optimize) + if self.experimental_codegen: + return generate_assembly_experimental(self.ir_nodes) + else: + return generate_assembly(self.ir_nodes, self.no_optimize) @cached_property def assembly_runtime(self) -> list: - return generate_assembly(self.ir_runtime, self.no_optimize) + if self.experimental_codegen: + return generate_assembly_experimental(self.ir_runtime) + else: + return generate_assembly(self.ir_runtime, self.no_optimize) @cached_property def bytecode(self) -> bytes: diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 5a35b8f932..99fa4f91f7 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -213,7 +213,7 @@ def compile_to_assembly(code, no_optimize=False): _add_postambles(res) if not no_optimize: - _optimize_assembly(res) + optimize_assembly(res) return res @@ -933,10 +933,10 @@ def _stack_peephole_opts(assembly): # optimize assembly, in place -def _optimize_assembly(assembly): +def optimize_assembly(assembly): for x in assembly: if isinstance(x, list): - _optimize_assembly(x) + optimize_assembly(x) for _ in range(1024): changed = False diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 3ca81a720b..41c2360d92 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -12,7 +12,6 @@ "jnz", "ret", "revert", - "assert", ] _symbols = {} @@ -22,9 +21,19 @@ def _get_symbols_common(a: dict, b: dict) -> dict: return {k: [a[k], b[k]] for k in a.keys() & b.keys() if a[k] != b[k]} -def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: +def generate_assembly_experimental(ir: IRnode) -> list[str]: + global_function = convert_ir_basicblock(ir) + return generate_evm(global_function) + + +def convert_ir_basicblock(ir: IRnode) -> IRFunction: global_function = IRFunction(IRLabel("global")) _convert_ir_basicblock(global_function, ir) + + 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)])) + while _optimize_empty_basicblocks(global_function): pass @@ -35,9 +44,6 @@ def convert_ir_basicblock(ctx: GlobalContext, ir: IRnode) -> IRFunction: # Optimization pass: Remove unused variables _optimize_unused_variables(global_function) - assembly = generate_evm(global_function) - print(" ".join(assembly)) - return global_function @@ -106,11 +112,9 @@ def _calculate_in_set(ctx: IRFunction) -> None: if last_inst.opcode in ["jmp", "jnz"]: ops = last_inst.get_label_operands() - assert len(ops) >= 1, "br instruction should have at least one label operand" + assert len(ops) >= 1, "branch instruction should have at least one label operand" for op in ops: ctx.get_basic_block(op.value).add_in(bb) - elif last_inst.opcode == "assert": - ctx.get_basic_block_after(bb.label).add_in(bb) # Fill in the "out" set for each basic block for bb in ctx.basic_blocks: @@ -118,18 +122,25 @@ def _calculate_in_set(ctx: IRFunction) -> None: in_bb.add_out(bb) +liveness_visited = set() + + def _calculate_liveness(bb: IRBasicBlock) -> None: for out_bb in bb.out_set: _calculate_liveness(out_bb) in_vars = out_bb.in_vars_for(bb) bb.out_vars = bb.out_vars.union(in_vars) + if bb in liveness_visited: + return + liveness_visited.add(bb) bb.calculate_liveness() -def _convert_binary_op(ctx: IRFunction, ir: IRnode) -> str: - arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) +def _convert_binary_op(ctx: IRFunction, ir: IRnode, swap: bool = False) -> str: + ir_args = ir.args[::-1] if swap else ir.args + arg_0 = _convert_ir_basicblock(ctx, ir_args[0]) + arg_1 = _convert_ir_basicblock(ctx, ir_args[1]) args = [arg_1, arg_0] ret = ctx.get_next_variable() @@ -197,21 +208,14 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i sym = ir.args[0] # FIXME: How do I validate that the IR is indeed a symbol? _symbols[sym.value] = ret - # first_pos = ir.source_pos[0] if ir.source_pos else None - # inst = IRInstruction( - # "load", - # [ret], - # _symbols[sym.value], - # IRDebugInfo(first_pos or 0, f"symbol: {sym.value}"), - # ) - # ctx.get_basic_block().append_instruction(inst) return _convert_ir_basicblock(ctx, ir.args[2]) # body elif ir.value in [ "eq", - "le", - "ge", "gt", + "lt", + "slt", + "sgt", "shr", "or", "xor", @@ -221,7 +225,13 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i "div", "mod", ]: - return _convert_binary_op(ctx, ir) + return _convert_binary_op(ctx, ir, ir.value in []) + elif ir.value == "le": + ir.value = "gt" + return _convert_binary_op(ctx, ir, False) # TODO: check if this is correct order + elif ir.value == "ge": + ir.value = "lt" + return _convert_binary_op(ctx, ir, False) # TODO: check if this is correct order elif ir.value == "iszero": arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) args = [arg_0] @@ -244,8 +254,9 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "calldataload": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) ret = ctx.get_next_variable() - inst = IRInstruction("calldataload", [ir.args[0].value], ret) + inst = IRInstruction("calldataload", [arg_0], ret) ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "callvalue": @@ -254,9 +265,16 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "assert": + current_bb = ctx.get_basic_block() arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) - inst = IRInstruction("assert", [arg_0]) - ctx.get_basic_block().append_instruction(inst) + + exit_label = ctx.get_next_label() + bb = IRBasicBlock(exit_label, ctx) + bb = ctx.append_basic_block(bb) + + inst = IRInstruction("jnz", [arg_0, IRLabel("__revert"), exit_label]) + current_bb.append_instruction(inst) + elif ir.value == "label": bb = IRBasicBlock(IRLabel(ir.args[0].value, True), ctx) ctx.append_basic_block(bb) @@ -271,7 +289,9 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i inst = IRInstruction("ret", [new_var]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": - inst = IRInstruction("revert", []) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) + inst = IRInstruction("revert", [arg_0, arg_1]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "pass": pass @@ -290,7 +310,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i elif isinstance(ir.value, str) and ir.value in _symbols: return _symbols[ir.value] elif ir.is_literal: - return ir.value + return IRLiteral(ir.value) else: raise Exception(f"Unknown IR node: {ir}") From 4487ef6b0a0df630cba247fdddce3d4466bad288 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 15 Jul 2023 12:58:01 +0300 Subject: [PATCH 050/471] fix liveness, multipass unused removal --- vyper/ir/ir_to_bb_pass.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 41c2360d92..de732273b1 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -39,15 +39,18 @@ def convert_ir_basicblock(ir: IRnode) -> IRFunction: # TODO: can be split into a new pass _calculate_in_set(global_function) - _calculate_liveness(global_function.basic_blocks[0]) + while True: + _calculate_liveness(global_function.basic_blocks[0], set()) - # Optimization pass: Remove unused variables - _optimize_unused_variables(global_function) + # Optimization pass: Remove unused variables + removed = _optimize_unused_variables(global_function) + if len(removed) == 0: + break return global_function -def _optimize_unused_variables(ctx: IRFunction) -> int: +def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: """ Remove unused variables. """ @@ -61,7 +64,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> int: bb.instructions = [inst for inst in bb.instructions if inst not in removeList] - return count + return removeList def _optimize_empty_basicblocks(ctx: IRFunction) -> None: @@ -122,12 +125,10 @@ def _calculate_in_set(ctx: IRFunction) -> None: in_bb.add_out(bb) -liveness_visited = set() - - -def _calculate_liveness(bb: IRBasicBlock) -> None: +def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: + bb.out_vars = set() for out_bb in bb.out_set: - _calculate_liveness(out_bb) + _calculate_liveness(out_bb, liveness_visited) in_vars = out_bb.in_vars_for(bb) bb.out_vars = bb.out_vars.union(in_vars) From 6e4a58e5d78fb95d1772d0960204b56c667da908 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 17:45:17 +0300 Subject: [PATCH 051/471] optimize equivalent sequence --- vyper/ir/compile_ir.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 99fa4f91f7..58ec05c52c 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -913,6 +913,12 @@ def _stack_peephole_opts(assembly): changed = False i = 0 while i < len(assembly) - 2: + # "DUP1 SWAP2" is equivalent to "DUP1" + # TODO: we should be able to do this for any DUPn + if assembly[i : i + 2] == ["DUP1", "SWAP2"]: + changed = True + del assembly[i + 1] + continue # usually generated by with statements that return their input like # (with x (...x)) if assembly[i : i + 3] == ["DUP1", "SWAP1", "POP"]: From 73733892eaf2f24877fd787ab7525175bccb74aa Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 17:45:38 +0300 Subject: [PATCH 052/471] stack scheduling new --- vyper/codegen/dfg.py | 191 ++++++++++++++++++++++++++----------------- 1 file changed, 114 insertions(+), 77 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 65e47505bf..59bd5b697c 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -171,91 +171,124 @@ def _generate_evm_for_instruction_r( _emit_input_operands(ctx, assembly, operands, stack_map) - ### BEGIN STACK HANDLING CASES ### - if len(operands) == 1: - op = operands[0] + for op in operands: + # final_stack_depth = -(len(operands) - i - 1) ucc = inst.get_use_count_correction(op) assert op.use_count >= ucc, "Operand used up" depth = stack_map_get_depth_in(stack_map, op) assert depth != NOT_IN_STACK, "Operand not in stack" needs_copy = op.use_count - ucc > 1 - in_place = depth == 0 - if in_place: - if needs_copy: - assembly.append("DUP1") - stack_map_dup(stack_map, 0) - else: - if needs_copy: - assembly.append(f"DUP{-depth+1}") - stack_map_dup(stack_map, -depth) - assembly.append(f"SWAP1") - stack_map_swap(stack_map, -depth) - if needs_copy: + assembly.append(f"DUP{-depth+1}") + stack_map_dup(stack_map, -depth) op.use_count -= 1 - elif len(operands) == 2: - op0, op1 = operands[0], operands[1] - ucc0, ucc1 = inst.get_use_count_correction(op0), inst.get_use_count_correction(op1) - assert op0.use_count >= ucc0, "Operand 0 used up" - assert op1.use_count >= ucc1, "Operand 1 used up" - depth0 = stack_map_get_depth_in(stack_map, op0) - depth1 = stack_map_get_depth_in(stack_map, op1) - assert depth0 != NOT_IN_STACK, f"Operand {op0} not in stack" - assert depth1 != NOT_IN_STACK, f"Operand {op1} not in stack" - needs_copy0 = op0.use_count - ucc0 > 1 - needs_copy1 = op1.use_count - ucc1 > 1 - in_place0 = depth0 == -1 - in_place1 = depth1 == 0 - if in_place0 and in_place1: - if needs_copy0: - assembly.append(f"DUP{-depth0+1}") - stack_map_dup(stack_map, -depth0) - assembly.append(f"SWAP1") - stack_map_swap(stack_map, 1) - if needs_copy1: - assembly.append(f"DUP{-depth1+1}") - stack_map_dup(stack_map, -depth1) - assembly.append(f"SWAP2") - stack_map_swap(stack_map, 2) - assembly.append(f"SWAP1") - stack_map_swap(stack_map, 1) - # else: - # assembly.append(f"SWAP1") - # stack_map_swap(stack_map, 1) - else: - if needs_copy0: - assembly.append(f"DUP{-depth0+1}") - stack_map_dup(stack_map, -depth0) - else: - if depth0 != 0: - assembly.append(f"SWAP{-depth0}") - stack_map_swap(stack_map, -depth0) - assembly.append(f"SWAP1") - stack_map_swap(stack_map, 1) - - depth1 = stack_map_get_depth_in(stack_map, op1) - in_place1 = depth1 == 0 - - if needs_copy1: - if not in_place1: - assembly.append(f"SWAP{-depth1}") - stack_map_swap(stack_map, -depth1) - assembly.append(f"DUP1") - stack_map_dup(stack_map, 0) - assembly.append(f"SWAP2") - stack_map_swap(stack_map, 2) - assembly.append(f"SWAP1") - stack_map_swap(stack_map, 1) - else: - if not in_place1: - assembly.append(f"SWAP{-depth1}") - stack_map_swap(stack_map, -depth1) - - if needs_copy0: - op0.use_count -= 1 - if needs_copy1: - op1.use_count -= 1 + copy_count = 0 + i = 0 + for i in range(len(operands)): + op = operands[i] + final_stack_depth = -(len(operands) - i - 1) + depth = stack_map_get_depth_in(stack_map, op) + assert depth != NOT_IN_STACK, "Operand not in stack" + is_in_place = depth == final_stack_depth + + if not is_in_place: + break + + for j in range(i, len(operands)): + op = operands[j] + depth = stack_map_get_depth_in(stack_map, op) + is_in_place = depth == 0 + + if not is_in_place: + assembly.append(f"SWAP{-depth}") + stack_map_swap(stack_map, -depth) + + ### BEGIN STACK HANDLING CASES ### + # if len(operands) == 1: + # op = operands[0] + # ucc = inst.get_use_count_correction(op) + # assert op.use_count >= ucc, "Operand used up" + # depth = stack_map_get_depth_in(stack_map, op) + # assert depth != NOT_IN_STACK, "Operand not in stack" + # needs_copy = op.use_count - ucc > 1 + # in_place = depth == 0 + # if in_place: + # if needs_copy: + # assembly.append("DUP1") + # stack_map_dup(stack_map, 0) + # else: + # if needs_copy: + # assembly.append(f"DUP{-depth+1}") + # stack_map_dup(stack_map, -depth) + # assembly.append(f"SWAP1") + # stack_map_swap(stack_map, -depth) + + # if needs_copy: + # op.use_count -= 1 + + # elif len(operands) == 2: + # op0, op1 = operands[0], operands[1] + # ucc0, ucc1 = inst.get_use_count_correction(op0), inst.get_use_count_correction(op1) + # assert op0.use_count >= ucc0, "Operand 0 used up" + # assert op1.use_count >= ucc1, "Operand 1 used up" + # depth0 = stack_map_get_depth_in(stack_map, op0) + # depth1 = stack_map_get_depth_in(stack_map, op1) + # assert depth0 != NOT_IN_STACK, f"Operand {op0} not in stack" + # assert depth1 != NOT_IN_STACK, f"Operand {op1} not in stack" + # needs_copy0 = op0.use_count - ucc0 > 1 + # needs_copy1 = op1.use_count - ucc1 > 1 + # in_place0 = depth0 == -1 + # in_place1 = depth1 == 0 + # if in_place0 and in_place1: + # if needs_copy0: + # assembly.append(f"DUP{-depth0+1}") + # stack_map_dup(stack_map, -depth0) + # assembly.append(f"SWAP1") + # stack_map_swap(stack_map, 1) + # if needs_copy1: + # assembly.append(f"DUP{-depth1+1}") + # stack_map_dup(stack_map, -depth1) + # assembly.append(f"SWAP2") + # stack_map_swap(stack_map, 2) + # assembly.append(f"SWAP1") + # stack_map_swap(stack_map, 1) + # # else: + # # assembly.append(f"SWAP1") + # # stack_map_swap(stack_map, 1) + # else: + # if needs_copy0: + # assembly.append(f"DUP{-depth0+1}") + # stack_map_dup(stack_map, -depth0) + # else: + # if depth0 != 0: + # assembly.append(f"SWAP{-depth0}") + # stack_map_swap(stack_map, -depth0) + # assembly.append(f"SWAP1") + # stack_map_swap(stack_map, 1) + + # depth1 = stack_map_get_depth_in(stack_map, op1) + # in_place1 = depth1 == 0 + + # if needs_copy1: + # if not in_place1: + # assembly.append(f"SWAP{-depth1}") + # stack_map_swap(stack_map, -depth1) + # assembly.append(f"DUP1") + # stack_map_dup(stack_map, 0) + # assembly.append(f"SWAP2") + # stack_map_swap(stack_map, 2) + # assembly.append(f"SWAP1") + # stack_map_swap(stack_map, 1) + # else: + # if not in_place1: + # assembly.append(f"SWAP{-depth1}") + # stack_map_swap(stack_map, -depth1) + + # if needs_copy0: + # op0.use_count -= 1 + # if needs_copy1: + # op1.use_count -= 1 del stack_map[len(stack_map) - len(operands) :] if inst.ret is not None: @@ -280,6 +313,10 @@ def _generate_evm_for_instruction_r( else: raise Exception(f"Unknown opcode: {opcode}") + for i in range(copy_count): + assembly.append("SWAP1") + assembly.append("POP") + def _emit_input_operands( ctx: IRFunction, assembly: list, ops: list[IROperant], stack_map: list[str] From 4271710b9eeb68ed40dfaa179d8258d20b0c169c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 18:33:42 +0300 Subject: [PATCH 053/471] correction --- vyper/codegen/dfg.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 59bd5b697c..8d26a0dfd6 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -197,12 +197,22 @@ def _generate_evm_for_instruction_r( for j in range(i, len(operands)): op = operands[j] + final_stack_depth = -(len(operands) - j - 1) depth = stack_map_get_depth_in(stack_map, op) - is_in_place = depth == 0 + is_in_place = depth == final_stack_depth if not is_in_place: - assembly.append(f"SWAP{-depth}") - stack_map_swap(stack_map, -depth) + if final_stack_depth == 0: + assembly.append(f"SWAP{-depth}") + stack_map_swap(stack_map, -depth) + elif depth == 0: + assembly.append(f"SWAP{-final_stack_depth}") + stack_map_swap(stack_map, -final_stack_depth) + else: + assembly.append(f"SWAP{-depth}") + stack_map_swap(stack_map, -depth) + assembly.append(f"SWAP{-final_stack_depth}") + stack_map_swap(stack_map, -final_stack_depth) ### BEGIN STACK HANDLING CASES ### # if len(operands) == 1: From 394d7c1aa51645beaae4bb9f05adb527b552aed8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 19:03:11 +0300 Subject: [PATCH 054/471] piphole optimizer --- vyper/ir/compile_ir.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 58ec05c52c..9cb41b5215 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -913,11 +913,11 @@ def _stack_peephole_opts(assembly): changed = False i = 0 while i < len(assembly) - 2: - # "DUP1 SWAP2" is equivalent to "DUP1" - # TODO: we should be able to do this for any DUPn - if assembly[i : i + 2] == ["DUP1", "SWAP2"]: + if assembly[i : i + 3] == ["DUP1", "SWAP2", "SWAP1"]: changed = True - del assembly[i + 1] + del assembly[i + 2] + assembly[i] = "SWAP1" + assembly[i + 1] = "DUP2" continue # usually generated by with statements that return their input like # (with x (...x)) From 454ebbcdb2da131b3aaf2b8a042bb47b68530638 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 19:03:26 +0300 Subject: [PATCH 055/471] correct scheduler --- vyper/codegen/dfg.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 8d26a0dfd6..cb21382325 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -190,22 +190,15 @@ def _generate_evm_for_instruction_r( final_stack_depth = -(len(operands) - i - 1) depth = stack_map_get_depth_in(stack_map, op) assert depth != NOT_IN_STACK, "Operand not in stack" - is_in_place = depth == final_stack_depth + in_place_op = stack_map_peek(stack_map, -final_stack_depth) + is_in_place = in_place_op.value == op.value + # is_in_place = depth == final_stack_depth if not is_in_place: - break - - for j in range(i, len(operands)): - op = operands[j] - final_stack_depth = -(len(operands) - j - 1) - depth = stack_map_get_depth_in(stack_map, op) - is_in_place = depth == final_stack_depth - - if not is_in_place: - if final_stack_depth == 0: + if final_stack_depth == 0 and depth != 0: assembly.append(f"SWAP{-depth}") stack_map_swap(stack_map, -depth) - elif depth == 0: + elif final_stack_depth != 0 and depth == 0: assembly.append(f"SWAP{-final_stack_depth}") stack_map_swap(stack_map, -final_stack_depth) else: From 16753bf845725bc647539a9ec42be084ad840bc7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 19:04:15 +0300 Subject: [PATCH 056/471] remove old code --- vyper/codegen/dfg.py | 86 -------------------------------------------- 1 file changed, 86 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index cb21382325..3561adbd7a 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -207,92 +207,6 @@ def _generate_evm_for_instruction_r( assembly.append(f"SWAP{-final_stack_depth}") stack_map_swap(stack_map, -final_stack_depth) - ### BEGIN STACK HANDLING CASES ### - # if len(operands) == 1: - # op = operands[0] - # ucc = inst.get_use_count_correction(op) - # assert op.use_count >= ucc, "Operand used up" - # depth = stack_map_get_depth_in(stack_map, op) - # assert depth != NOT_IN_STACK, "Operand not in stack" - # needs_copy = op.use_count - ucc > 1 - # in_place = depth == 0 - # if in_place: - # if needs_copy: - # assembly.append("DUP1") - # stack_map_dup(stack_map, 0) - # else: - # if needs_copy: - # assembly.append(f"DUP{-depth+1}") - # stack_map_dup(stack_map, -depth) - # assembly.append(f"SWAP1") - # stack_map_swap(stack_map, -depth) - - # if needs_copy: - # op.use_count -= 1 - - # elif len(operands) == 2: - # op0, op1 = operands[0], operands[1] - # ucc0, ucc1 = inst.get_use_count_correction(op0), inst.get_use_count_correction(op1) - # assert op0.use_count >= ucc0, "Operand 0 used up" - # assert op1.use_count >= ucc1, "Operand 1 used up" - # depth0 = stack_map_get_depth_in(stack_map, op0) - # depth1 = stack_map_get_depth_in(stack_map, op1) - # assert depth0 != NOT_IN_STACK, f"Operand {op0} not in stack" - # assert depth1 != NOT_IN_STACK, f"Operand {op1} not in stack" - # needs_copy0 = op0.use_count - ucc0 > 1 - # needs_copy1 = op1.use_count - ucc1 > 1 - # in_place0 = depth0 == -1 - # in_place1 = depth1 == 0 - # if in_place0 and in_place1: - # if needs_copy0: - # assembly.append(f"DUP{-depth0+1}") - # stack_map_dup(stack_map, -depth0) - # assembly.append(f"SWAP1") - # stack_map_swap(stack_map, 1) - # if needs_copy1: - # assembly.append(f"DUP{-depth1+1}") - # stack_map_dup(stack_map, -depth1) - # assembly.append(f"SWAP2") - # stack_map_swap(stack_map, 2) - # assembly.append(f"SWAP1") - # stack_map_swap(stack_map, 1) - # # else: - # # assembly.append(f"SWAP1") - # # stack_map_swap(stack_map, 1) - # else: - # if needs_copy0: - # assembly.append(f"DUP{-depth0+1}") - # stack_map_dup(stack_map, -depth0) - # else: - # if depth0 != 0: - # assembly.append(f"SWAP{-depth0}") - # stack_map_swap(stack_map, -depth0) - # assembly.append(f"SWAP1") - # stack_map_swap(stack_map, 1) - - # depth1 = stack_map_get_depth_in(stack_map, op1) - # in_place1 = depth1 == 0 - - # if needs_copy1: - # if not in_place1: - # assembly.append(f"SWAP{-depth1}") - # stack_map_swap(stack_map, -depth1) - # assembly.append(f"DUP1") - # stack_map_dup(stack_map, 0) - # assembly.append(f"SWAP2") - # stack_map_swap(stack_map, 2) - # assembly.append(f"SWAP1") - # stack_map_swap(stack_map, 1) - # else: - # if not in_place1: - # assembly.append(f"SWAP{-depth1}") - # stack_map_swap(stack_map, -depth1) - - # if needs_copy0: - # op0.use_count -= 1 - # if needs_copy1: - # op1.use_count -= 1 - del stack_map[len(stack_map) - len(operands) :] if inst.ret is not None: stack_map.append(inst.ret) From f23b1621cad2e109e889e1ba28b57e53a71ce266 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 20:15:35 +0300 Subject: [PATCH 057/471] cleanup --- vyper/codegen/dfg.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 3561adbd7a..15fa5f61db 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -183,8 +183,6 @@ def _generate_evm_for_instruction_r( stack_map_dup(stack_map, -depth) op.use_count -= 1 - copy_count = 0 - i = 0 for i in range(len(operands)): op = operands[i] final_stack_depth = -(len(operands) - i - 1) @@ -192,7 +190,6 @@ def _generate_evm_for_instruction_r( assert depth != NOT_IN_STACK, "Operand not in stack" in_place_op = stack_map_peek(stack_map, -final_stack_depth) is_in_place = in_place_op.value == op.value - # is_in_place = depth == final_stack_depth if not is_in_place: if final_stack_depth == 0 and depth != 0: @@ -230,10 +227,6 @@ def _generate_evm_for_instruction_r( else: raise Exception(f"Unknown opcode: {opcode}") - for i in range(copy_count): - assembly.append("SWAP1") - assembly.append("POP") - def _emit_input_operands( ctx: IRFunction, assembly: list, ops: list[IROperant], stack_map: list[str] From e27f76f2260205acae094e9a08437ca091bf6520 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 20:22:20 +0300 Subject: [PATCH 058/471] honor --no-optimization flag --- vyper/codegen/dfg.py | 5 +++-- vyper/compiler/phases.py | 4 ++-- vyper/ir/ir_to_bb_pass.py | 10 +++++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 15fa5f61db..b4c8f3d343 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -115,7 +115,7 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: visited_instructions = set() -def generate_evm(ctx: IRFunction) -> list[str]: +def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: assembly = [] stack_map = [] convert_ir_to_dfg(ctx) @@ -140,7 +140,8 @@ def generate_evm(ctx: IRFunction) -> list[str]: for inst in bb.instructions: _generate_evm_for_instruction_r(ctx, assembly, inst, stack_map) - optimize_assembly(assembly) + if no_optimize == False: + optimize_assembly(assembly) return assembly diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 277eb0dccb..19b5fadce2 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -153,14 +153,14 @@ def function_signatures(self) -> dict[str, ContractFunctionT]: @cached_property def assembly(self) -> list: if self.experimental_codegen: - return generate_assembly_experimental(self.ir_nodes) + return generate_assembly_experimental(self.ir_nodes, self.no_optimize) else: return generate_assembly(self.ir_nodes, self.no_optimize) @cached_property def assembly_runtime(self) -> list: if self.experimental_codegen: - return generate_assembly_experimental(self.ir_runtime) + return generate_assembly_experimental(self.ir_runtime, self.no_optimize) else: return generate_assembly(self.ir_runtime, self.no_optimize) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index de732273b1..31c5a9ff8b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -21,12 +21,12 @@ def _get_symbols_common(a: dict, b: dict) -> dict: return {k: [a[k], b[k]] for k in a.keys() & b.keys() if a[k] != b[k]} -def generate_assembly_experimental(ir: IRnode) -> list[str]: +def generate_assembly_experimental(ir: IRnode, no_optimize: bool = False) -> list[str]: global_function = convert_ir_basicblock(ir) - return generate_evm(global_function) + return generate_evm(global_function, no_optimize) -def convert_ir_basicblock(ir: IRnode) -> IRFunction: +def convert_ir_basicblock(ir: IRnode, no_optimize: bool = False) -> IRFunction: global_function = IRFunction(IRLabel("global")) _convert_ir_basicblock(global_function, ir) @@ -39,10 +39,14 @@ def convert_ir_basicblock(ir: IRnode) -> IRFunction: # TODO: can be split into a new pass _calculate_in_set(global_function) + while True: _calculate_liveness(global_function.basic_blocks[0], set()) # Optimization pass: Remove unused variables + if no_optimize: + break + removed = _optimize_unused_variables(global_function) if len(removed) == 0: break From 6712d08d0141b7fa2a0c16b812b01aa8dfe8a64c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 20:44:18 +0300 Subject: [PATCH 059/471] cleanup --- vyper/codegen/dfg.py | 18 +++++++++--------- vyper/codegen/ir_basicblock.py | 4 ++-- vyper/codegen/ir_function.py | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index b4c8f3d343..942ddc7d4d 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -50,8 +50,8 @@ def __init__(self, value: IRInstruction | IROperant): self.successors = [] -dfg_inputs = {} -dfg_outputs = {} +dfg_inputs = {str: IRInstruction} +dfg_outputs = {str: IRInstruction} NOT_IN_STACK = 1 @@ -106,13 +106,13 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: for op in operands: op.use_count += 1 - dfg_inputs[op] = inst + dfg_inputs[op.value] = inst for op in res: - dfg_outputs[op] = inst + dfg_outputs[op.value] = inst -visited_instructions = set() +visited_instructions = {IRInstruction} def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: @@ -130,8 +130,8 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: block_a = ctx.get_basic_block(inst.operands[0].value) block_b = ctx.get_basic_block(inst.operands[2].value) - block_a.phi_vars[ret_op] = (inst.operands[1], inst.operands[3]) - block_b.phi_vars[ret_op] = (inst.operands[3], inst.operands[1]) + block_a.phi_vars[ret_op.value] = inst.operands[3] + block_b.phi_vars[ret_op.value] = inst.operands[1] for i, bb in enumerate(ctx.basic_blocks): if i != 0: @@ -150,7 +150,7 @@ def _generate_evm_for_instruction_r( ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: list[str] ) -> None: for op in inst.get_output_operands(): - target = dfg_inputs[op] + target = dfg_inputs[op.value] if target.parent != inst.parent: continue _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) @@ -237,4 +237,4 @@ def _emit_input_operands( assembly.extend([*PUSH(op.value)]) stack_map.append(op) continue - _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op], stack_map) + _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 6f9b21cd9d..7fc33c2dad 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -135,7 +135,7 @@ def get_output_operands(self) -> list[IROperant]: def get_use_count_correction(self, op: IROperant) -> int: use_count_correction = 0 for phi in self.parent.phi_vars.values(): - if phi[1] == op: + if phi == op: use_count_correction += 1 return use_count_correction @@ -187,7 +187,7 @@ class IRBasicBlock: in_set: set["IRBasicBlock"] out_set: set["IRBasicBlock"] out_vars: set[IRVariable] - phi_vars: dict[IRVariable, IRVariable] + phi_vars: dict[str:IRVariable] def __init__(self, label: IRLabel, parent: "IRFunction") -> None: assert isinstance(label, IRLabel), "label must be an IRLabel" diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index bca84a9c3c..fe2d046f08 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -20,7 +20,7 @@ class IRFunction(IRFunctionBase): Function that contains basic blocks. """ - basic_blocks: list + basic_blocks: list["IRBasicBlock"] last_label: int last_variable: int From e39d5ae7594e2b932acad8c5b2dc223f1468e25d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 20:51:38 +0300 Subject: [PATCH 060/471] more cleanup --- vyper/codegen/ir_basicblock.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 7fc33c2dad..84f0e7a3ed 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -134,8 +134,8 @@ def get_output_operands(self) -> list[IROperant]: def get_use_count_correction(self, op: IROperant) -> int: use_count_correction = 0 - for phi in self.parent.phi_vars.values(): - if phi == op: + for _, phi in self.parent.phi_vars.items(): + if phi.value == op.value: use_count_correction += 1 return use_count_correction @@ -187,7 +187,7 @@ class IRBasicBlock: in_set: set["IRBasicBlock"] out_set: set["IRBasicBlock"] out_vars: set[IRVariable] - phi_vars: dict[str:IRVariable] + phi_vars: dict[str, IRVariable] def __init__(self, label: IRLabel, parent: "IRFunction") -> None: assert isinstance(label, IRLabel), "label must be an IRLabel" From 9ef2e4cc62938b87f8868f0c588b9fee8afe115e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 21:57:46 +0300 Subject: [PATCH 061/471] stackmap class refactor --- vyper/codegen/dfg.py | 133 ++++++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 59 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 942ddc7d4d..dad7064827 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -53,45 +53,65 @@ def __init__(self, value: IRInstruction | IROperant): dfg_inputs = {str: IRInstruction} dfg_outputs = {str: IRInstruction} -NOT_IN_STACK = 1 - -def stack_map_get_depth_in(stack_map: list[str], op: IROperant) -> int: - """ - Returns the depth of the first matching operand in the stack map. - If the operand is not in the stack map, returns NOT_IN_STACK. - """ - for i, stack_op in enumerate(stack_map[::-1]): - if isinstance(stack_op, IROperant) and stack_op.value == op.value: - return -i - - return NOT_IN_STACK - - -def stack_map_peek(stack_map: list[str], depth: int) -> IROperant: - """ - Returns the top of the stack map. - """ - return stack_map[-depth - 1] - - -def stack_map_poke(stack_map: list[str], depth: int, op: IROperant) -> None: - """ - Pokes an operand at the given depth in the stack map. - """ - stack_map[-depth - 1] = op - - -def stack_map_dup(stack_map: list[str], depth: int) -> None: - stack_map.append(stack_map_peek(stack_map, depth)) - - -def stack_map_swap(stack_map: list[str], depth: int) -> None: - stack_map[-depth - 1], stack_map[-1] = stack_map[-1], stack_map[-depth - 1] - - -def stack_map_pop(stack_map: list[str]) -> None: - stack_map.pop() +class StackMap: + NOT_IN_STACK = 1 + stack_map: list[str] + assembly: list[str] + + def __init__(self, assembly: list[str]): + self.stack_map = [] + self.assembly = assembly + + def get_height(self) -> int: + """ + Returns the height of the stack map. + """ + return len(self.stack_map) + + def push(self, op: IROperant) -> None: + """ + Pushes an operand onto the stack map. + """ + self.stack_map.append(op) + + def pop(self, num: int = 1) -> None: + del self.stack_map[len(self.stack_map) - num :] + + def get_depth_in(self, op: IROperant) -> int: + """ + Returns the depth of the first matching operand in the stack map. + If the operand is not in the stack map, returns NOT_IN_STACK. + """ + for i, stack_op in enumerate(self.stack_map[::-1]): + if isinstance(stack_op, IROperant) and stack_op.value == op.value: + return -i + + return StackMap.NOT_IN_STACK + + def peek(self, depth: int) -> IROperant: + """ + Returns the top of the stack map. + """ + return self.stack_map[-depth - 1] + + def poke(self, depth: int, op: IROperant) -> None: + """ + Pokes an operand at the given depth in the stack map. + """ + self.stack_map[-depth - 1] = op + + def dup(self, depth: int) -> None: + # assert depth < 0, "Cannot dup positive depth" + self.assembly.append(f"DUP{-depth+1}") + self.stack_map.append(self.peek(-depth)) + + def swap(self, depth: int) -> None: + self.assembly.append(f"SWAP{depth}") + self.stack_map[depth - 1], self.stack_map[-1] = ( + self.stack_map[-1], + self.stack_map[depth - 1], + ) def convert_ir_to_dfg(ctx: IRFunction) -> None: @@ -117,7 +137,7 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: assembly = [] - stack_map = [] + stack_map = StackMap(assembly) convert_ir_to_dfg(ctx) for bb in ctx.basic_blocks: @@ -147,7 +167,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: def _generate_evm_for_instruction_r( - ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: list[str] + ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap ) -> None: for op in inst.get_output_operands(): target = dfg_inputs[op.value] @@ -176,38 +196,33 @@ def _generate_evm_for_instruction_r( # final_stack_depth = -(len(operands) - i - 1) ucc = inst.get_use_count_correction(op) assert op.use_count >= ucc, "Operand used up" - depth = stack_map_get_depth_in(stack_map, op) - assert depth != NOT_IN_STACK, "Operand not in stack" + depth = stack_map.get_depth_in(op) + assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" needs_copy = op.use_count - ucc > 1 if needs_copy: - assembly.append(f"DUP{-depth+1}") - stack_map_dup(stack_map, -depth) + stack_map.dup(depth) op.use_count -= 1 for i in range(len(operands)): op = operands[i] final_stack_depth = -(len(operands) - i - 1) - depth = stack_map_get_depth_in(stack_map, op) - assert depth != NOT_IN_STACK, "Operand not in stack" - in_place_op = stack_map_peek(stack_map, -final_stack_depth) + depth = stack_map.get_depth_in(op) + assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" + in_place_op = stack_map.peek(-final_stack_depth) is_in_place = in_place_op.value == op.value if not is_in_place: if final_stack_depth == 0 and depth != 0: - assembly.append(f"SWAP{-depth}") - stack_map_swap(stack_map, -depth) + stack_map.swap(depth) elif final_stack_depth != 0 and depth == 0: - assembly.append(f"SWAP{-final_stack_depth}") - stack_map_swap(stack_map, -final_stack_depth) + stack_map.swap(final_stack_depth) else: - assembly.append(f"SWAP{-depth}") - stack_map_swap(stack_map, -depth) - assembly.append(f"SWAP{-final_stack_depth}") - stack_map_swap(stack_map, -final_stack_depth) + stack_map.swap(depth) + stack_map.swap(final_stack_depth) - del stack_map[len(stack_map) - len(operands) :] + stack_map.pop(len(operands)) if inst.ret is not None: - stack_map.append(inst.ret) + stack_map.push(inst.ret) if opcode in ONE_TO_ONE_INSTRUCTIONS: assembly.append(opcode.upper()) @@ -230,11 +245,11 @@ def _generate_evm_for_instruction_r( def _emit_input_operands( - ctx: IRFunction, assembly: list, ops: list[IROperant], stack_map: list[str] + ctx: IRFunction, assembly: list, ops: list[IROperant], stack_map: StackMap ) -> None: for op in ops: if op.is_literal: assembly.extend([*PUSH(op.value)]) - stack_map.append(op) + stack_map.push(op) continue _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) From e100f1c14b43a55ded35909aa1c4b70b10be4864 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 22:00:12 +0300 Subject: [PATCH 062/471] assert guards --- vyper/codegen/dfg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index dad7064827..340b556b94 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -102,11 +102,12 @@ def poke(self, depth: int, op: IROperant) -> None: self.stack_map[-depth - 1] = op def dup(self, depth: int) -> None: - # assert depth < 0, "Cannot dup positive depth" + assert depth <= 0, "Cannot dup positive depth" self.assembly.append(f"DUP{-depth+1}") self.stack_map.append(self.peek(-depth)) def swap(self, depth: int) -> None: + assert depth < 0, "Cannot swap positive depth" self.assembly.append(f"SWAP{depth}") self.stack_map[depth - 1], self.stack_map[-1] = ( self.stack_map[-1], From 1cffc66414d21fdf951aaef007fe5b6a3f5c0777 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 16 Jul 2023 22:00:34 +0300 Subject: [PATCH 063/471] docs --- vyper/codegen/dfg.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 340b556b94..08ca310076 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -102,11 +102,17 @@ def poke(self, depth: int, op: IROperant) -> None: self.stack_map[-depth - 1] = op def dup(self, depth: int) -> None: + """ + Duplicates the operand at the given depth in the stack map. + """ assert depth <= 0, "Cannot dup positive depth" self.assembly.append(f"DUP{-depth+1}") self.stack_map.append(self.peek(-depth)) def swap(self, depth: int) -> None: + """ + Swaps the operand at the given depth in the stack map with the top of the stack. + """ assert depth < 0, "Cannot swap positive depth" self.assembly.append(f"SWAP{depth}") self.stack_map[depth - 1], self.stack_map[-1] = ( From 5a8fce692764ec23a1d6ee765edea2c0cb1627f3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 17 Jul 2023 12:41:55 +0300 Subject: [PATCH 064/471] cleanup --- vyper/codegen/dfg.py | 18 +++--------------- vyper/codegen/ir_basicblock.py | 13 ++++++------- vyper/codegen/ir_function.py | 4 ++-- vyper/ir/ir_to_bb_pass.py | 22 ++++++++-------------- 4 files changed, 19 insertions(+), 38 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 08ca310076..44c110e65e 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,10 +1,4 @@ -from vyper.codegen.ir_basicblock import ( - TERMINAL_IR_INSTRUCTIONS, - IRBasicBlock, - IRInstruction, - IROperant, - IRLiteral, -) +from vyper.codegen.ir_basicblock import IRInstruction, IROperant from vyper.codegen.ir_function import IRFunction from vyper.ir.compile_ir import PUSH, optimize_assembly @@ -30,13 +24,7 @@ "sgt", ] -OPERANT_ORDER_IRELEVANT_INSTRUCTIONS = [ - "xor", - "or", - "add", - "mul", - "eq", -] +OPERANT_ORDER_IRELEVANT_INSTRUCTIONS = ["xor", "or", "add", "mul", "eq"] class DFGNode: @@ -167,7 +155,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: for inst in bb.instructions: _generate_evm_for_instruction_r(ctx, assembly, inst, stack_map) - if no_optimize == False: + if no_optimize is False: optimize_assembly(assembly) return assembly diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 84f0e7a3ed..334813b78f 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,10 +1,6 @@ from typing import Optional, TYPE_CHECKING -TERMINAL_IR_INSTRUCTIONS = [ - "ret", - "revert", - "assert", -] +TERMINAL_IR_INSTRUCTIONS = ["ret", "revert", "assert"] if TYPE_CHECKING: from vyper.codegen.ir_function import IRFunction @@ -121,7 +117,7 @@ def get_input_operands(self) -> list[IROperant]: """ Get all input operants in instruction. """ - return [op for op in self.operands if isinstance(op, IRLabel) == False] + return [op for op in self.operands if isinstance(op, IRLabel) is False] def get_input_variables(self) -> list[IROperant]: """ @@ -264,7 +260,10 @@ def get_liveness(self) -> set[IRVariable]: return self.instructions[-1].liveness def __repr__(self) -> str: - s = f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]} OUT={[bb.label for bb in self.out_set]} \n" + s = ( + f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}" + " OUT={[bb.label for bb in self.out_set]} \n" + ) for instruction in self.instructions: s += f" {instruction}\n" return s diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index fe2d046f08..5bb82f1630 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -51,7 +51,7 @@ def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: for bb in self.basic_blocks: if bb.label.value == label: return bb - assert False, f"Basic block '{label}' not found" + raise AssertionError(f"Basic block '{label}' not found") def get_basic_block_after(self, label: IRLabel) -> IRBasicBlock: """ @@ -60,7 +60,7 @@ def get_basic_block_after(self, label: IRLabel) -> IRBasicBlock: for i, bb in enumerate(self.basic_blocks[:-1]): if bb.label.value == label.value: return self.basic_blocks[i + 1] - assert False, f"Basic block after '{label}' not found" + raise AssertionError(f"Basic block after '{label}' not found") def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: """ diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 31c5a9ff8b..24f7014173 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,18 +1,12 @@ from typing import Optional, Union -from vyper.codegen.dfg import convert_ir_to_dfg, generate_evm -from vyper.codegen.global_context import GlobalContext +from vyper.codegen.dfg import generate_evm from vyper.codegen.ir_node import IRnode -from vyper.codegen.ir_function import IRFunctionBase, IRFunction, IRFunctionIntrinsic -from vyper.codegen.ir_basicblock import IRInstruction, IRDebugInfo +from vyper.codegen.ir_function import IRFunction +from vyper.codegen.ir_basicblock import IRInstruction from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRLiteral from vyper.evm.opcodes import get_opcodes -TERMINATOR_IR_INSTRUCTIONS = [ - "jmp", - "jnz", - "ret", - "revert", -] +TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "revert"] _symbols = {} @@ -162,7 +156,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ret = None for ir_node in ir.args: # NOTE: skip the last one r = _convert_ir_basicblock(ctx, ir_node) - if ir_node.is_literal == False: + if ir_node.is_literal is False: ret = r return ret elif ir.value == "if": @@ -176,7 +170,6 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.append_basic_block(else_block) # convert "else" - start_syms = _symbols.copy() if len(ir.args) == 3: _convert_ir_basicblock(ctx, ir.args[2]) after_else_syms = _symbols.copy() @@ -229,6 +222,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i "mul", "div", "mod", + "sha3", ]: return _convert_binary_op(ctx, ir, ir.value in []) elif ir.value == "le": @@ -290,7 +284,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i arg_2 = _convert_ir_basicblock(ctx, ir.args[2]) sym = ir.args[1] new_var = _symbols.get(f"&{sym.value}", arg_2) - assert new_var != None, "exit_to with undefined variable" + assert new_var is not None, "exit_to with undefined variable" inst = IRInstruction("ret", [new_var]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": @@ -303,7 +297,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i elif ir.value == "mload": sym = ir.args[0] new_var = _symbols.get(f"&{sym.value}", None) - assert new_var != None, "mload without mstore" + assert new_var is not None, "mload without mstore" return new_var elif ir.value == "mstore": sym = ir.args[0] From ede0191f92670d70645e93f1402455e07c712fd2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 17 Jul 2023 19:36:05 +0300 Subject: [PATCH 065/471] small fix --- vyper/codegen/dfg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 44c110e65e..26ff7668c5 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -102,7 +102,7 @@ def swap(self, depth: int) -> None: Swaps the operand at the given depth in the stack map with the top of the stack. """ assert depth < 0, "Cannot swap positive depth" - self.assembly.append(f"SWAP{depth}") + self.assembly.append(f"SWAP{-depth}") self.stack_map[depth - 1], self.stack_map[-1] = ( self.stack_map[-1], self.stack_map[depth - 1], From 6e85041ca29bc900ca59d47fedfe3ab97b5dfc8c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 17 Jul 2023 19:47:37 +0300 Subject: [PATCH 066/471] forward no optimize --- vyper/ir/ir_to_bb_pass.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 7fec197ec5..5bcbe5450c 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -20,7 +20,7 @@ def generate_assembly_experimental( ir: IRnode, optimize: Optional[OptimizationLevel] = None ) -> list[str]: global_function = convert_ir_basicblock(ir) - return generate_evm(global_function, optimize) + return generate_evm(global_function, optimize is OptimizationLevel.NONE) def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: From 852735efc349cc826e2ce7754ef79ffb3645ea7c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 18 Jul 2023 18:37:30 +0300 Subject: [PATCH 067/471] spelling --- vyper/codegen/dfg.py | 18 +++++++++--------- vyper/codegen/ir_basicblock.py | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 26ff7668c5..661d0dc9d5 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,4 +1,4 @@ -from vyper.codegen.ir_basicblock import IRInstruction, IROperant +from vyper.codegen.ir_basicblock import IRInstruction, IROperand from vyper.codegen.ir_function import IRFunction from vyper.ir.compile_ir import PUSH, optimize_assembly @@ -28,11 +28,11 @@ class DFGNode: - value: IRInstruction | IROperant + value: IRInstruction | IROperand predecessors: list["DFGNode"] successors: list["DFGNode"] - def __init__(self, value: IRInstruction | IROperant): + def __init__(self, value: IRInstruction | IROperand): self.value = value self.predecessors = [] self.successors = [] @@ -57,7 +57,7 @@ def get_height(self) -> int: """ return len(self.stack_map) - def push(self, op: IROperant) -> None: + def push(self, op: IROperand) -> None: """ Pushes an operand onto the stack map. """ @@ -66,24 +66,24 @@ def push(self, op: IROperant) -> None: def pop(self, num: int = 1) -> None: del self.stack_map[len(self.stack_map) - num :] - def get_depth_in(self, op: IROperant) -> int: + def get_depth_in(self, op: IROperand) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ for i, stack_op in enumerate(self.stack_map[::-1]): - if isinstance(stack_op, IROperant) and stack_op.value == op.value: + if isinstance(stack_op, IROperand) and stack_op.value == op.value: return -i return StackMap.NOT_IN_STACK - def peek(self, depth: int) -> IROperant: + def peek(self, depth: int) -> IROperand: """ Returns the top of the stack map. """ return self.stack_map[-depth - 1] - def poke(self, depth: int, op: IROperant) -> None: + def poke(self, depth: int, op: IROperand) -> None: """ Pokes an operand at the given depth in the stack map. """ @@ -240,7 +240,7 @@ def _generate_evm_for_instruction_r( def _emit_input_operands( - ctx: IRFunction, assembly: list, ops: list[IROperant], stack_map: StackMap + ctx: IRFunction, assembly: list, ops: list[IROperand], stack_map: StackMap ) -> None: for op in ops: if op.is_literal: diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 334813b78f..4c471d3076 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -27,9 +27,9 @@ def __repr__(self) -> str: IROperantValue = str | int -class IROperant: +class IROperand: """ - IROperant represents an operand in IR. An operand can be a variable, label, or a constant. + IROperand represents an operand in IR. An operand can be a variable, label, or a constant. """ value: str @@ -47,7 +47,7 @@ def __repr__(self) -> str: return str(self.value) -class IRLiteral(IROperant): +class IRLiteral(IROperand): """ IRLiteral represents a literal in IR """ @@ -61,7 +61,7 @@ def is_literal(self) -> bool: return True -class IRVariable(IROperant): +class IRVariable(IROperand): """ IRVariable represents a variable in IR. A variable is a string that starts with a %. """ @@ -70,7 +70,7 @@ def __init__(self, value: IROperantValue) -> None: super().__init__(value) -class IRLabel(IROperant): +class IRLabel(IROperand): """ IRLabel represents a label in IR. A label is a string that starts with a %. """ @@ -91,14 +91,14 @@ class IRInstruction: """ opcode: str - operands: list[IROperant] + operands: list[IROperand] ret: Optional[str] dbg: Optional[IRDebugInfo] liveness: set[IRVariable] parent: Optional["IRBasicBlock"] def __init__( - self, opcode: str, operands: list[IROperant], ret: str = None, dbg: IRDebugInfo = None + self, opcode: str, operands: list[IROperand], ret: str = None, dbg: IRDebugInfo = None ): self.opcode = opcode self.operands = operands @@ -113,22 +113,22 @@ def get_label_operands(self) -> list[IRLabel]: """ return [op for op in self.operands if isinstance(op, IRLabel)] - def get_input_operands(self) -> list[IROperant]: + def get_input_operands(self) -> list[IROperand]: """ Get all input operants in instruction. """ return [op for op in self.operands if isinstance(op, IRLabel) is False] - def get_input_variables(self) -> list[IROperant]: + def get_input_variables(self) -> list[IROperand]: """ Get all input variables in instruction. """ return [op for op in self.operands if isinstance(op, IRVariable)] - def get_output_operands(self) -> list[IROperant]: + def get_output_operands(self) -> list[IROperand]: return [self.ret] if self.ret else [] - def get_use_count_correction(self, op: IROperant) -> int: + def get_use_count_correction(self, op: IROperand) -> int: use_count_correction = 0 for _, phi in self.parent.phi_vars.items(): if phi.value == op.value: From 4d1deab5c93f18d036f5b05ac9538cdd932c388d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 1 Aug 2023 16:38:25 +0300 Subject: [PATCH 068/471] Squashed commit of the following: commit 5366706b39ac247ca485023a1f420089fcf7c086 Author: Harry Kalogirou Date: Tue Aug 1 16:36:36 2023 +0300 change labeling commit 7933bda2dd1c6e0d44254029fc2e81ebaf98183b Author: Harry Kalogirou Date: Tue Aug 1 16:00:20 2023 +0300 rework commit f4e82232bf193e7b93cfa862b27b5f2fe316a1a2 Author: Harry Kalogirou Date: Tue Aug 1 15:05:57 2023 +0300 works commit 5ae34161671190590e847c55fc8523d81d933c96 Author: Harry Kalogirou Date: Tue Aug 1 14:42:04 2023 +0300 internal calls return commit db3bdf3891273277a6dd2dc6ad05ee554069773d Author: Harry Kalogirou Date: Tue Aug 1 13:43:32 2023 +0300 no code left to emit edge case commit 1310a3f143fc4af62fb17d835772e24b70685cec Author: Harry Kalogirou Date: Tue Aug 1 13:31:10 2023 +0300 drop dead blocks commit 5448fb5e95e974197a39729be2d7a4a0df8344fb Author: Harry Kalogirou Date: Tue Aug 1 10:12:42 2023 +0300 ret external ceil commit 7089ac603e397c5be38722a9bc7adce5a8e30dd0 Author: Harry Kalogirou Date: Tue Aug 1 10:12:31 2023 +0300 ret external commit 3cb9265c49e25f37ac6656851b8e0183b49a9f2e Author: Harry Kalogirou Date: Tue Aug 1 09:41:22 2023 +0300 invert assert commit 2b20b00c382e8edb7848d1cb10482d746be3c055 Author: Harry Kalogirou Date: Tue Aug 1 01:22:26 2023 +0300 external call fix commit 182e8536ac50abcd58ea72ccc7cce8268f899db2 Author: Harry Kalogirou Date: Mon Jul 31 22:42:38 2023 +0300 call external refactor commit 3b0fabcfc4c9d86583c2167d9e0c8473358d8d76 Author: Harry Kalogirou Date: Mon Jul 31 21:49:15 2023 +0300 sstore commit ee382a2547f679d778a90b2da1ac6d23457731d6 Author: Harry Kalogirou Date: Mon Jul 31 21:27:22 2023 +0300 swap commit dffcb246dee46d060e3772a834c7e13b870c76ed Author: Harry Kalogirou Date: Mon Jul 31 21:25:55 2023 +0300 sha commit 2f3c67f02b08bfd5289cb98b48509197c8c3036f Author: Harry Kalogirou Date: Mon Jul 31 19:09:23 2023 +0300 sha commit c81d01054a89c3710d7e82b4728d40d2fa463f0b Author: Harry Kalogirou Date: Sun Jul 30 22:07:07 2023 +0300 exit_to unification with ret commit b0ec1ec6ab56bb62502c87203c0508d7f89bc031 Author: Harry Kalogirou Date: Sun Jul 30 14:07:50 2023 +0300 refactor stack_map to utils commit 75639cda5a37f2865fc6e2a9d515e2371528394a Author: Harry Kalogirou Date: Sun Jul 30 14:01:35 2023 +0300 emit calls commit 1a896279d96445fd356741f6a8b821d82cca6d5d Author: Harry Kalogirou Date: Sun Jul 30 13:45:54 2023 +0300 proper optimize call commit 5f044c912355989e73ce43bfd9b64cc6e7720bf2 Author: Harry Kalogirou Date: Sun Jul 30 10:41:52 2023 +0300 addback optimizations commit 823f78e38c16bc492f72c56e5b095929f7ce4cd6 Author: Harry Kalogirou Date: Sat Jul 29 23:54:02 2023 +0300 operanT commit 083e1fa20f11a98d4e214ae49927728b3d6e7a18 Author: Harry Kalogirou Date: Sat Jul 29 23:52:09 2023 +0300 bugfix dfg commit 6b601adf3d9f21b60da4b3759abe2323a3ab9861 Author: Harry Kalogirou Date: Sat Jul 29 23:23:07 2023 +0300 alloca commit 9a02334d03558441e57458635eca8585c53c7c60 Author: Harry Kalogirou Date: Fri Jul 28 23:42:21 2023 +0300 updates commit b628653a52ac6588a3128f0291d59ff579a10773 Author: Harry Kalogirou Date: Fri Jul 20 23:42:08 2023 +0300 passthrough data commit 4fae5406dbf2231f70143c4b2f2321ed54818b87 Author: Harry Kalogirou Date: Fri Jul 19 16:45:49 2023 +0300 before the meta --- vyper/codegen/dfg.py | 163 ++++----- .../function_definitions/internal_function.py | 4 +- vyper/codegen/ir_basicblock.py | 28 +- vyper/codegen/ir_function.py | 15 +- vyper/codegen/ir_node.py | 12 + vyper/codegen/self_call.py | 2 + vyper/codegen/stmt.py | 6 +- vyper/compiler/phases.py | 2 +- vyper/compiler/utils.py | 69 +++- vyper/ir/ir_to_bb_pass.py | 323 ++++++++++++++---- vyper/ir/optimizer.py | 4 + 11 files changed, 469 insertions(+), 159 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 661d0dc9d5..a742ccf86d 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,6 +1,8 @@ -from vyper.codegen.ir_basicblock import IRInstruction, IROperand +from vyper.codegen.ir_basicblock import IRInstruction, IROperant, IRLabel from vyper.codegen.ir_function import IRFunction from vyper.ir.compile_ir import PUSH, optimize_assembly +from vyper.compiler.utils import StackMap +from vyper.utils import MemoryPositions ONE_TO_ONE_INSTRUCTIONS = [ "revert", @@ -9,7 +11,14 @@ "calldatacopy", "calldataload", "callvalue", + "selfbalance", + "sload", + "sstore", + "timestamp", + "caller", "shr", + "shl", + "and", "xor", "or", "add", @@ -22,93 +31,31 @@ "lt", "slt", "sgt", + "log0", + "log1", + "log2", + "log3", + "log4", ] OPERANT_ORDER_IRELEVANT_INSTRUCTIONS = ["xor", "or", "add", "mul", "eq"] class DFGNode: - value: IRInstruction | IROperand + value: IRInstruction | IROperant predecessors: list["DFGNode"] successors: list["DFGNode"] - def __init__(self, value: IRInstruction | IROperand): + def __init__(self, value: IRInstruction | IROperant): self.value = value self.predecessors = [] self.successors = [] -dfg_inputs = {str: IRInstruction} +dfg_inputs = {str: [IRInstruction]} dfg_outputs = {str: IRInstruction} -class StackMap: - NOT_IN_STACK = 1 - stack_map: list[str] - assembly: list[str] - - def __init__(self, assembly: list[str]): - self.stack_map = [] - self.assembly = assembly - - def get_height(self) -> int: - """ - Returns the height of the stack map. - """ - return len(self.stack_map) - - def push(self, op: IROperand) -> None: - """ - Pushes an operand onto the stack map. - """ - self.stack_map.append(op) - - def pop(self, num: int = 1) -> None: - del self.stack_map[len(self.stack_map) - num :] - - def get_depth_in(self, op: IROperand) -> int: - """ - Returns the depth of the first matching operand in the stack map. - If the operand is not in the stack map, returns NOT_IN_STACK. - """ - for i, stack_op in enumerate(self.stack_map[::-1]): - if isinstance(stack_op, IROperand) and stack_op.value == op.value: - return -i - - return StackMap.NOT_IN_STACK - - def peek(self, depth: int) -> IROperand: - """ - Returns the top of the stack map. - """ - return self.stack_map[-depth - 1] - - def poke(self, depth: int, op: IROperand) -> None: - """ - Pokes an operand at the given depth in the stack map. - """ - self.stack_map[-depth - 1] = op - - def dup(self, depth: int) -> None: - """ - Duplicates the operand at the given depth in the stack map. - """ - assert depth <= 0, "Cannot dup positive depth" - self.assembly.append(f"DUP{-depth+1}") - self.stack_map.append(self.peek(-depth)) - - def swap(self, depth: int) -> None: - """ - Swaps the operand at the given depth in the stack map with the top of the stack. - """ - assert depth < 0, "Cannot swap positive depth" - self.assembly.append(f"SWAP{-depth}") - self.stack_map[depth - 1], self.stack_map[-1] = ( - self.stack_map[-1], - self.stack_map[depth - 1], - ) - - def convert_ir_to_dfg(ctx: IRFunction) -> None: global dfg_inputs global dfg_outputs @@ -121,7 +68,9 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: for op in operands: op.use_count += 1 - dfg_inputs[op.value] = inst + dfg_inputs[op.value] = ( + [inst] if dfg_inputs.get(op.value) is None else dfg_inputs[op.value] + [inst] + ) for op in res: dfg_outputs[op.value] = inst @@ -150,7 +99,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: for i, bb in enumerate(ctx.basic_blocks): if i != 0: - assembly.append(f"_sym_label_{bb.label}") + assembly.append(f"_sym_{bb.label}") assembly.append("JUMPDEST") for inst in bb.instructions: _generate_evm_for_instruction_r(ctx, assembly, inst, stack_map) @@ -161,14 +110,19 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: return assembly +# TODO: refactor this +label_counter = 0 + + def _generate_evm_for_instruction_r( ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap ) -> None: + global label_counter for op in inst.get_output_operands(): - target = dfg_inputs[op.value] - if target.parent != inst.parent: - continue - _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) + for target in dfg_inputs.get(op.value, []): + if target.parent != inst.parent: + continue + _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) if inst in visited_instructions: return @@ -221,26 +175,73 @@ def _generate_evm_for_instruction_r( if opcode in ONE_TO_ONE_INSTRUCTIONS: assembly.append(opcode.upper()) + elif opcode == "alloca": + pass + elif opcode == "load": + pass elif opcode == "jnz": - assembly.append(f"_sym_label_{inst.operands[1].value}") + assembly.append(f"_sym_{inst.operands[1].value}") assembly.append("JUMPI") elif opcode == "jmp": - assembly.append(f"_sym_label_{inst.operands[0].value}") + assembly.append(f"_sym_{inst.operands[0].value}") assembly.append("JUMP") elif opcode == "gt": assembly.append("GT") elif opcode == "lt": assembly.append("LT") + elif opcode == "call": + target = inst.operands[0] + if type(target) is IRLabel: + assembly.extend( + [ + f"_sym_label_ret_{label_counter}", + f"_sym_{target.value}", + "JUMP", + f"_sym_label_ret_{label_counter}", + "JUMPDEST", + ] + ) + label_counter += 1 + else: + assembly.append("CALL") elif opcode == "ret": - assembly.append("RETURN") + if len(inst.operands) == 1: + assembly.append("SWAP1") + assembly.append("JUMP") + else: + assembly.append("RETURN") elif opcode == "select": pass + elif opcode == "sha3": + assembly.append("SHA3") + elif opcode == "sha3_64": + assembly.extend( + [ + *PUSH(MemoryPositions.FREE_VAR_SPACE2), + "MSTORE", + *PUSH(MemoryPositions.FREE_VAR_SPACE), + "MSTORE", + *PUSH(64), + *PUSH(MemoryPositions.FREE_VAR_SPACE), + "SHA3", + ] + ) + elif opcode == "ceil32": + assembly.extend( + [ + *PUSH(31), + "ADD", + *PUSH(31), + "NOT", + "AND", + ] + ) else: raise Exception(f"Unknown opcode: {opcode}") def _emit_input_operands( - ctx: IRFunction, assembly: list, ops: list[IROperand], stack_map: StackMap + ctx: IRFunction, assembly: list, ops: list[IROperant], stack_map: StackMap ) -> None: for op in ops: if op.is_literal: diff --git a/vyper/codegen/function_definitions/internal_function.py b/vyper/codegen/function_definitions/internal_function.py index 228191e3ca..cf01dbdab4 100644 --- a/vyper/codegen/function_definitions/internal_function.py +++ b/vyper/codegen/function_definitions/internal_function.py @@ -68,4 +68,6 @@ def generate_ir_for_internal_function( ["seq"] + nonreentrant_post + [["exit_to", "return_pc"]], ] - return IRnode.from_list(["seq", body, cleanup_routine]) + ir_node = IRnode.from_list(["seq", body, cleanup_routine]) + ir_node.passthrough_metadata["func_t"] = func_t + return ir_node diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 4c471d3076..81a0e7e13c 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -27,9 +27,9 @@ def __repr__(self) -> str: IROperantValue = str | int -class IROperand: +class IROperant: """ - IROperand represents an operand in IR. An operand can be a variable, label, or a constant. + IROperant represents an operand in IR. An operand can be a variable, label, or a constant. """ value: str @@ -47,7 +47,7 @@ def __repr__(self) -> str: return str(self.value) -class IRLiteral(IROperand): +class IRLiteral(IROperant): """ IRLiteral represents a literal in IR """ @@ -61,7 +61,7 @@ def is_literal(self) -> bool: return True -class IRVariable(IROperand): +class IRVariable(IROperant): """ IRVariable represents a variable in IR. A variable is a string that starts with a %. """ @@ -70,7 +70,7 @@ def __init__(self, value: IROperantValue) -> None: super().__init__(value) -class IRLabel(IROperand): +class IRLabel(IROperant): """ IRLabel represents a label in IR. A label is a string that starts with a %. """ @@ -91,14 +91,14 @@ class IRInstruction: """ opcode: str - operands: list[IROperand] + operands: list[IROperant] ret: Optional[str] dbg: Optional[IRDebugInfo] liveness: set[IRVariable] parent: Optional["IRBasicBlock"] def __init__( - self, opcode: str, operands: list[IROperand], ret: str = None, dbg: IRDebugInfo = None + self, opcode: str, operands: list[IROperant], ret: str = None, dbg: IRDebugInfo = None ): self.opcode = opcode self.operands = operands @@ -113,22 +113,22 @@ def get_label_operands(self) -> list[IRLabel]: """ return [op for op in self.operands if isinstance(op, IRLabel)] - def get_input_operands(self) -> list[IROperand]: + def get_input_operands(self) -> list[IROperant]: """ Get all input operants in instruction. """ return [op for op in self.operands if isinstance(op, IRLabel) is False] - def get_input_variables(self) -> list[IROperand]: + def get_input_variables(self) -> list[IROperant]: """ Get all input variables in instruction. """ return [op for op in self.operands if isinstance(op, IRVariable)] - def get_output_operands(self) -> list[IROperand]: + def get_output_operands(self) -> list[IROperant]: return [self.ret] if self.ret else [] - def get_use_count_correction(self, op: IROperand) -> int: + def get_use_count_correction(self, op: IROperant) -> int: use_count_correction = 0 for _, phi in self.parent.phi_vars.items(): if phi.value == op.value: @@ -226,6 +226,10 @@ def in_vars_for(self, bb: "IRBasicBlock" = None) -> set[IRVariable]: return liveness + @property + def is_reachable(self) -> bool: + return len(self.in_set) > 0 + def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" instruction.parent = self @@ -262,7 +266,7 @@ def get_liveness(self) -> set[IRVariable]: def __repr__(self) -> str: s = ( f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}" - " OUT={[bb.label for bb in self.out_set]} \n" + f" OUT={[bb.label for bb in self.out_set]} \n" ) for instruction in self.instructions: s += f" {instruction}\n" diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 5bb82f1630..e6cab5da18 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -32,7 +32,7 @@ def __init__(self, name: IRLabel) -> None: self.append_basic_block(IRBasicBlock(name, self)) - def append_basic_block(self, bb: IRBasicBlock) -> None: + def append_basic_block(self, bb: IRBasicBlock) -> IRBasicBlock: """ Append basic block to function. """ @@ -85,6 +85,19 @@ def get_next_variable(self) -> str: def get_last_variable(self) -> str: return f"%{self.last_variable}" + 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": + for bb2 in bb.out_set: + bb2.in_set.remove(bb) + removed += 1 + else: + new_basic_blocks.append(bb) + self.basic_blocks = new_basic_blocks + return removed + def __repr__(self) -> str: str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index 0895e5f02d..81b6c7d8dd 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -99,6 +99,8 @@ class IRnode: valency: int args: List["IRnode"] value: Union[str, int] + is_self_call: bool + passthrough_metadata: dict[str, Any] def __init__( self, @@ -112,6 +114,8 @@ def __init__( mutable: bool = True, add_gas_estimate: int = 0, encoding: Encoding = Encoding.VYPER, + is_self_call: bool = False, + passthrough_metadata: dict[str, Any] = None, ): if args is None: args = [] @@ -129,6 +133,8 @@ def __init__( self.add_gas_estimate = add_gas_estimate self.encoding = encoding self.as_hex = AS_HEX_DEFAULT + self.is_self_call = is_self_call + self.passthrough_metadata = passthrough_metadata or {} def _check(condition, err): if not condition: @@ -505,6 +511,8 @@ def from_list( error_msg: Optional[str] = None, mutable: bool = True, add_gas_estimate: int = 0, + is_self_call: bool = False, + passthrough_metadata: dict[str, Any] = {}, encoding: Encoding = Encoding.VYPER, ) -> "IRnode": if isinstance(typ, str): @@ -537,6 +545,8 @@ def from_list( source_pos=source_pos, encoding=encoding, error_msg=error_msg, + is_self_call=is_self_call, + passthrough_metadata=passthrough_metadata, ) else: return cls( @@ -550,4 +560,6 @@ def from_list( add_gas_estimate=add_gas_estimate, encoding=encoding, error_msg=error_msg, + is_self_call=is_self_call, + passthrough_metadata=passthrough_metadata, ) diff --git a/vyper/codegen/self_call.py b/vyper/codegen/self_call.py index c320e6889c..f03f2eb9c8 100644 --- a/vyper/codegen/self_call.py +++ b/vyper/codegen/self_call.py @@ -121,4 +121,6 @@ def ir_for_self_call(stmt_expr, context): add_gas_estimate=func_t._ir_info.gas_estimate, ) o.is_self_call = True + o.passthrough_metadata["func_t"] = func_t + o.passthrough_metadata["args_ir"] = args_ir return o diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py index 91d45f4916..5e8ba0fe53 100644 --- a/vyper/codegen/stmt.py +++ b/vyper/codegen/stmt.py @@ -67,7 +67,11 @@ def parse_AnnAssign(self): assert self.stmt.value is not None rhs = Expr(self.stmt.value, self.context).ir_node - lhs = IRnode.from_list(alloced, typ=ltyp, location=MEMORY) + lhs = IRnode.from_list( + alloced, + typ=ltyp, + location=MEMORY, + ) return make_setter(lhs, rhs) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 6c4df15a92..dd285ff7de 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -163,7 +163,7 @@ def _ir_output(self): @cached_property def bb_output(self): # fetch both deployment and runtime IR - return convert_ir_basicblock(self._ir_output[0]) + return convert_ir_basicblock(self._ir_output[1]) @property def ir_nodes(self) -> IRnode: diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 8de2589367..ae9a3125e5 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -1,5 +1,5 @@ from typing import Dict - +from vyper.codegen.ir_basicblock import IROperant from vyper.semantics.types.function import ContractFunctionT @@ -45,3 +45,70 @@ def _expand_row(row): if value: result[i] = value if i == 3 else int(value) return result + + +class StackMap: + NOT_IN_STACK = 1 + stack_map: list[str] + assembly: list[str] + + def __init__(self, assembly: list[str]): + self.stack_map = [] + self.assembly = assembly + + def get_height(self) -> int: + """ + Returns the height of the stack map. + """ + return len(self.stack_map) + + def push(self, op: IROperant) -> None: + """ + Pushes an operand onto the stack map. + """ + self.stack_map.append(op) + + def pop(self, num: int = 1) -> None: + del self.stack_map[len(self.stack_map) - num :] + + def get_depth_in(self, op: IROperant) -> int: + """ + Returns the depth of the first matching operand in the stack map. + If the operand is not in the stack map, returns NOT_IN_STACK. + """ + for i, stack_op in enumerate(self.stack_map[::-1]): + if isinstance(stack_op, IROperant) and stack_op.value == op.value: + return -i + + return StackMap.NOT_IN_STACK + + def peek(self, depth: int) -> IROperant: + """ + Returns the top of the stack map. + """ + return self.stack_map[-depth - 1] + + def poke(self, depth: int, op: IROperant) -> None: + """ + Pokes an operand at the given depth in the stack map. + """ + self.stack_map[-depth - 1] = op + + def dup(self, depth: int) -> None: + """ + Duplicates the operand at the given depth in the stack map. + """ + assert depth <= 0, "Cannot dup positive depth" + self.assembly.append(f"DUP{-depth+1}") + self.stack_map.append(self.peek(-depth)) + + def swap(self, depth: int) -> None: + """ + Swaps the operand at the given depth in the stack map with the top of the stack. + """ + assert depth < 0, "Cannot swap positive depth" + self.assembly.append(f"SWAP{-depth}") + self.stack_map[depth - 1], self.stack_map[-1] = ( + self.stack_map[-1], + self.stack_map[depth - 1], + ) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 5bcbe5450c..4c1aa0ff85 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -3,13 +3,14 @@ from vyper.codegen.ir_node import IRnode from vyper.codegen.ir_function import IRFunction from vyper.codegen.ir_basicblock import IRInstruction -from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRLiteral +from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRLiteral, IROperant from vyper.evm.opcodes import get_opcodes from vyper.compiler.settings import OptimizationLevel +from vyper.semantics.types.function import ContractFunctionT TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "revert"] -_symbols = {} +SymbolTable = dict[str, IROperant] def _get_symbols_common(a: dict, b: dict) -> dict: @@ -25,7 +26,7 @@ def generate_assembly_experimental( def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: global_function = IRFunction(IRLabel("global")) - _convert_ir_basicblock(global_function, ir) + _convert_ir_basicblock(global_function, ir, {}) revert_bb = IRBasicBlock(IRLabel("__revert"), global_function) revert_bb = global_function.append_basic_block(revert_bb) @@ -34,9 +35,14 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No while _optimize_empty_basicblocks(global_function): pass - # TODO: can be split into a new pass _calculate_in_set(global_function) + while global_function.remove_unreachable_blocks(): + pass + + if len(global_function.basic_blocks) == 0: + return global_function + while True: _calculate_liveness(global_function.basic_blocks[0], set()) @@ -59,6 +65,8 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: removeList = [] for bb in ctx.basic_blocks: for i, inst in enumerate(bb.instructions[:-1]): + if inst.opcode in ["call", "sload", "sstore"]: + continue if inst.ret and inst.ret not in bb.instructions[i + 1].liveness: removeList.append(inst) count += 1 @@ -114,11 +122,11 @@ def _calculate_in_set(ctx: IRFunction) -> None: last_inst.opcode in TERMINATOR_IR_INSTRUCTIONS ), "Last instruction should be a terminator" - if last_inst.opcode in ["jmp", "jnz"]: - ops = last_inst.get_label_operands() - assert len(ops) >= 1, "branch instruction should have at least one label operand" - for op in ops: - ctx.get_basic_block(op.value).add_in(bb) + for inst in bb.instructions: + if inst.opcode in ["jmp", "jnz", "call"]: + ops = inst.get_label_operands() + for op in ops: + ctx.get_basic_block(op.value).add_in(bb) # Fill in the "out" set for each basic block for bb in ctx.basic_blocks: @@ -139,10 +147,12 @@ def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: bb.calculate_liveness() -def _convert_binary_op(ctx: IRFunction, ir: IRnode, swap: bool = False) -> str: +def _convert_binary_op( + ctx: IRFunction, ir: IRnode, symbols: SymbolTable, swap: bool = False +) -> str: ir_args = ir.args[::-1] if swap else ir.args - arg_0 = _convert_ir_basicblock(ctx, ir_args[0]) - arg_1 = _convert_ir_basicblock(ctx, ir_args[1]) + arg_0 = _convert_ir_basicblock(ctx, ir_args[0], symbols) + arg_1 = _convert_ir_basicblock(ctx, ir_args[1], symbols) args = [arg_1, arg_0] ret = ctx.get_next_variable() @@ -152,41 +162,111 @@ def _convert_binary_op(ctx: IRFunction, ir: IRnode, swap: bool = False) -> str: return ret -def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, int]]: +def _append_jmp(ctx: IRFunction, label: IRLabel) -> None: + inst = IRInstruction("jmp", [label]) + ctx.get_basic_block().append_instruction(inst) + + label = ctx.get_next_label() + bb = IRBasicBlock(label, ctx) + ctx.append_basic_block(bb) + + +def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> None: + args_ir = ir.passthrough_metadata["args_ir"] + goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] + target_label = goto_ir.args[0].value # goto + ret_values = [IRLabel(target_label)] + for arg in args_ir: + if arg.value != "with": + ret = _convert_ir_basicblock(ctx, arg, symbols) + new_var = ctx.get_next_variable() + inst = IRInstruction("calldataload", [ret], new_var) + ctx.get_basic_block().append_instruction(inst) + ret_values.append(new_var) + else: + ret = _convert_ir_basicblock(ctx, arg, symbols) + ret_values.append(ret) + + call_ret = ctx.get_next_variable() + call_inst = IRInstruction("call", ret_values, call_ret) + ctx.get_basic_block().append_instruction(call_inst) + return call_ret + + +def _handle_internal_func( + ctx: IRFunction, ir: IRnode, func_t: ContractFunctionT, symbols: SymbolTable +) -> IRnode: + bb = IRBasicBlock(IRLabel(ir.args[0].args[0].value, True), ctx) + bb = ctx.append_basic_block(bb) + + old_ir_mempos = 0 + old_ir_mempos += 64 + + for arg in func_t.arguments: + new_var = ctx.get_next_variable() + + alloca_inst = IRInstruction("alloca", [], new_var) + bb.append_instruction(alloca_inst) + symbols[f"&{old_ir_mempos}"] = new_var + old_ir_mempos += 32 + + return ir.args[0].args[2] + + +def _convert_ir_basicblock( + ctx: IRFunction, ir: IRnode, symbols: SymbolTable +) -> Optional[Union[str, int]]: + # symbols = symbols.copy() + if ir.value == "deploy": - _convert_ir_basicblock(ctx, ir.args[1]) + _convert_ir_basicblock(ctx, ir.args[1], symbols) elif ir.value == "seq": + if ir.is_self_call: + return _handle_self_call(ctx, ir, symbols) + elif ir.passthrough_metadata.get("func_t", None) is not None: + func_t = ir.passthrough_metadata["func_t"] + ir = _handle_internal_func(ctx, ir, func_t, symbols) + # fallthrough + ret = None for ir_node in ir.args: # NOTE: skip the last one - r = _convert_ir_basicblock(ctx, ir_node) - if ir_node.is_literal is False: - ret = r + ret = _convert_ir_basicblock(ctx, ir_node, symbols) + + return ret + elif ir.value == "call": # external call + args = [] + for arg in ir.args: + args.append(_convert_ir_basicblock(ctx, arg, symbols)) + + ret = ctx.get_next_variable() + inst = IRInstruction("call", args, ret) + ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() # convert the condition - cont_ret = _convert_ir_basicblock(ctx, cond) + cont_ret = _convert_ir_basicblock(ctx, cond, symbols) else_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(else_block) # convert "else" if len(ir.args) == 3: - _convert_ir_basicblock(ctx, ir.args[2]) - after_else_syms = _symbols.copy() + _convert_ir_basicblock(ctx, ir.args[2], symbols) + after_else_syms = symbols.copy() # convert "then" then_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(then_block) - _convert_ir_basicblock(ctx, ir.args[1]) + _convert_ir_basicblock(ctx, ir.args[1], symbols) inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) current_bb.append_instruction(inst) - after_then_syms = _symbols.copy() + after_then_syms = symbols.copy() # exit bb exit_label = ctx.get_next_label() @@ -195,7 +275,7 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i for sym, val in _get_symbols_common(after_then_syms, after_else_syms).items(): ret = ctx.get_next_variable() - _symbols[sym] = ret + symbols[sym] = ret bb.append_instruction( IRInstruction("select", [then_block.label, val[0], else_block.label, val[1]], ret) ) @@ -204,13 +284,18 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i else_block.append_instruction(exit_inst) elif ir.value == "with": - ret = _convert_ir_basicblock(ctx, ir.args[1]) # initialization + ret = _convert_ir_basicblock(ctx, ir.args[1], symbols) # initialization sym = ir.args[0] - # FIXME: How do I validate that the IR is indeed a symbol? - _symbols[sym.value] = ret - - return _convert_ir_basicblock(ctx, ir.args[2]) # body + if ret.is_literal: + new_var = ctx.get_next_variable() + inst = IRInstruction("load", [ret], new_var) + ctx.get_basic_block().append_instruction(inst) + symbols[sym.value] = new_var + else: + symbols[sym.value] = ret + + return _convert_ir_basicblock(ctx, ir.args[2], symbols) # body elif ir.value in [ "eq", "gt", @@ -218,24 +303,33 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i "slt", "sgt", "shr", + "shl", "or", "xor", + "and", "add", "sub", "mul", "div", "mod", "sha3", + "sha3_64", ]: - return _convert_binary_op(ctx, ir, ir.value in []) + return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3", "sha3_64"]) elif ir.value == "le": ir.value = "gt" - return _convert_binary_op(ctx, ir, False) # TODO: check if this is correct order + return _convert_binary_op(ctx, ir, symbols, False) # TODO: check if this is correct order + elif ir.value == "sle": + ir.value = "sgt" + return _convert_binary_op(ctx, ir, symbols, False) # TODO: check if this is correct order elif ir.value == "ge": ir.value = "lt" - return _convert_binary_op(ctx, ir, False) # TODO: check if this is correct order + return _convert_binary_op(ctx, ir, symbols, False) # TODO: check if this is correct order + elif ir.value == "sge": + ir.value = "slt" + return _convert_binary_op(ctx, ir, symbols, False) # TODO: check if this is correct order elif ir.value == "iszero": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) args = [arg_0] ret = ctx.get_next_variable() @@ -244,19 +338,27 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "goto": - inst = IRInstruction("jmp", [IRLabel(ir.args[0].value)]) + return _append_jmp(ctx, IRLabel(ir.args[0].value)) + elif ir.value == "ceil32": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + new_var = ctx.get_next_variable() + inst = IRInstruction("ceil32", [arg_0], new_var) ctx.get_basic_block().append_instruction(inst) - - label = ctx.get_next_label() - bb = IRBasicBlock(label, ctx) - ctx.append_basic_block(bb) + return new_var + elif ir.value == "set": + sym = ir.args[0] + new_var = ctx.get_next_variable() + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + inst = IRInstruction("load", [arg_1], new_var) + ctx.get_basic_block().append_instruction(inst) + symbols[sym.value] = new_var elif ir.value == "calldatasize": ret = ctx.get_next_variable() inst = IRInstruction("calldatasize", [], ret) ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "calldataload": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) ret = ctx.get_next_variable() inst = IRInstruction("calldataload", [arg_0], ret) ctx.get_basic_block().append_instruction(inst) @@ -266,51 +368,150 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i inst = IRInstruction("callvalue", [], ret) ctx.get_basic_block().append_instruction(inst) return ret + elif ir.value == "calldatacopy": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + size = _convert_ir_basicblock(ctx, ir.args[2], symbols) + # orig_sym = symbols.get(f"&{arg_0.value}", None) + # assert orig_sym is None, "calldatacopy should not have a symbol" + new_var = ctx.get_next_variable() + symbols[f"&{arg_0.value}"] = new_var + inst = IRInstruction("calldatacopy", [arg_1, size], new_var) + ctx.get_basic_block().append_instruction(inst) + return new_var elif ir.value == "assert": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) current_bb = ctx.get_basic_block() - arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) + + new_var = ctx.get_next_variable() + inst = IRInstruction("iszero", [arg_0], new_var) + current_bb.append_instruction(inst) exit_label = ctx.get_next_label() bb = IRBasicBlock(exit_label, ctx) bb = ctx.append_basic_block(bb) - inst = IRInstruction("jnz", [arg_0, IRLabel("__revert"), exit_label]) + inst = IRInstruction("jnz", [new_var, IRLabel("__revert"), exit_label]) current_bb.append_instruction(inst) elif ir.value == "label": bb = IRBasicBlock(IRLabel(ir.args[0].value, True), ctx) ctx.append_basic_block(bb) - _convert_ir_basicblock(ctx, ir.args[2]) - elif ir.value == "return": - pass + _convert_ir_basicblock(ctx, ir.args[2], symbols) elif ir.value == "exit_to": - arg_2 = _convert_ir_basicblock(ctx, ir.args[2]) - sym = ir.args[1] - new_var = _symbols.get(f"&{sym.value}", arg_2) - assert new_var is not None, "exit_to with undefined variable" - inst = IRInstruction("ret", [new_var]) - ctx.get_basic_block().append_instruction(inst) + if len(ir.args) == 1: + inst = IRInstruction("ret", []) + ctx.get_basic_block().append_instruction(inst) + elif len(ir.args) >= 2: + ret_var = ir.args[1] + if ret_var.value == "return_pc": + inst = IRInstruction("ret", [symbols["return_buffer"]]) + ctx.get_basic_block().append_instruction(inst) + return None + # else: + # new_var = ctx.get_next_variable() + # symbols[f"&{ret_var.value}"] = new_var + + last_ir = None + for arg in ir.args[2:]: + last_ir = _convert_ir_basicblock(ctx, arg, symbols) + + ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols) + new_var = symbols.get(f"&{ret_ir.value}", ret_ir) + inst = IRInstruction("ret", [ret_ir, last_ir]) + ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0]) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1]) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) inst = IRInstruction("revert", [arg_0, arg_1]) ctx.get_basic_block().append_instruction(inst) + elif ir.value == "timestamp": + new_var = ctx.get_next_variable() + inst = IRInstruction("timestamp", [], new_var) + ctx.get_basic_block().append_instruction(inst) + return new_var + elif ir.value == "caller": + new_var = ctx.get_next_variable() + inst = IRInstruction("caller", [], new_var) + ctx.get_basic_block().append_instruction(inst) + return new_var + elif ir.value == "dload": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + new_var = ctx.get_next_variable() + inst = IRInstruction("calldataload", [arg_0], new_var) + ctx.get_basic_block().append_instruction(inst) + return new_var elif ir.value == "pass": pass + elif ir.value == "stop": + pass + elif ir.value == "return": + pass + elif ir.value == "selfbalance": + new_var = ctx.get_next_variable() + inst = IRInstruction("selfbalance", [], new_var) + ctx.get_basic_block().append_instruction(inst) + return new_var elif ir.value == "mload": sym = ir.args[0] - new_var = _symbols.get(f"&{sym.value}", None) - assert new_var is not None, "mload without mstore" - return new_var + if sym.is_literal: + new_var = symbols.get(f"&{sym.value}", None) + if new_var is None: + new_var = ctx.get_next_variable() + symbols[f"&{sym.value}"] = new_var + return new_var + else: + new_var = _convert_ir_basicblock(ctx, sym, symbols) + return new_var + elif ir.value == "mstore": + sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + + if sym_ir.is_literal: + sym = symbols.get(f"&{arg_1.value}", None) + if sym is None: + symbols[f"&{sym_ir.value}"] = arg_1 + return arg_1 + else: + new_var = ctx.get_next_variable() + inst = IRInstruction("load", [sym], new_var) + ctx.get_basic_block().append_instruction(inst) + symbols[f"&{sym_ir.value}"] = new_var + return new_var + else: + new_var = ctx.get_next_variable() + inst = IRInstruction("load", [arg_1], new_var) + ctx.get_basic_block().append_instruction(inst) + symbols[sym_ir.value] = new_var + return new_var + elif ir.value == "sload": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + new_var = ctx.get_next_variable() + inst = IRInstruction("sload", [arg_0], new_var) + ctx.get_basic_block().append_instruction(inst) + return new_var + elif ir.value == "sstore": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + inst = IRInstruction("sstore", [arg_0, arg_1]) + ctx.get_basic_block().append_instruction(inst) + elif ir.value == "unique_symbol": sym = ir.args[0] - new_var = _convert_ir_basicblock(ctx, ir.args[1]) - _symbols[f"&{sym.value}"] = new_var + new_var = ctx.get_next_variable() + symbols[f"&{sym.value}"] = new_var return new_var + elif ir.value == "return_buffer": + return IRLabel("return_buffer", True) + elif isinstance(ir.value, str) and ir.value.startswith("log"): + # count = int(ir.value[3:]) + args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] + inst = IRInstruction(ir.value, args) + ctx.get_basic_block().append_instruction(inst) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): - _convert_ir_opcode(ctx, ir) - elif isinstance(ir.value, str) and ir.value in _symbols: - return _symbols[ir.value] + _convert_ir_opcode(ctx, ir, symbols) + elif isinstance(ir.value, str) and ir.value in symbols: + return symbols[ir.value] elif ir.is_literal: return IRLiteral(ir.value) else: @@ -319,10 +520,10 @@ def _convert_ir_basicblock(ctx: IRFunction, ir: IRnode) -> Optional[Union[str, i return None -def _convert_ir_opcode(ctx: IRFunction, ir: IRnode) -> None: +def _convert_ir_opcode(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> None: opcode = str(ir.value).upper() for arg in ir.args: if isinstance(arg, IRnode): - _convert_ir_basicblock(ctx, arg) + _convert_ir_basicblock(ctx, arg, symbols) instruction = IRInstruction(opcode, ir.args) ctx.get_basic_block().append_instruction(instruction) diff --git a/vyper/ir/optimizer.py b/vyper/ir/optimizer.py index 40e02e79c7..32d8b6a504 100644 --- a/vyper/ir/optimizer.py +++ b/vyper/ir/optimizer.py @@ -440,6 +440,8 @@ def _optimize(node: IRnode, parent: Optional[IRnode]) -> Tuple[bool, IRnode]: error_msg = node.error_msg annotation = node.annotation add_gas_estimate = node.add_gas_estimate + is_self_call = node.is_self_call + passthrough_metadata = node.passthrough_metadata changed = False @@ -462,6 +464,8 @@ def finalize(val, args): error_msg=error_msg, annotation=annotation, add_gas_estimate=add_gas_estimate, + is_self_call=is_self_call, + passthrough_metadata=passthrough_metadata, ) if should_check_symbols: From a3288e302347495539369294359eea8204c15ade Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 1 Aug 2023 16:39:11 +0300 Subject: [PATCH 069/471] black formating --- vyper/codegen/dfg.py | 10 +--------- vyper/codegen/stmt.py | 6 +----- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index a742ccf86d..ea29bc68ee 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -227,15 +227,7 @@ def _generate_evm_for_instruction_r( ] ) elif opcode == "ceil32": - assembly.extend( - [ - *PUSH(31), - "ADD", - *PUSH(31), - "NOT", - "AND", - ] - ) + assembly.extend([*PUSH(31), "ADD", *PUSH(31), "NOT", "AND"]) else: raise Exception(f"Unknown opcode: {opcode}") diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py index 5e8ba0fe53..91d45f4916 100644 --- a/vyper/codegen/stmt.py +++ b/vyper/codegen/stmt.py @@ -67,11 +67,7 @@ def parse_AnnAssign(self): assert self.stmt.value is not None rhs = Expr(self.stmt.value, self.context).ir_node - lhs = IRnode.from_list( - alloced, - typ=ltyp, - location=MEMORY, - ) + lhs = IRnode.from_list(alloced, typ=ltyp, location=MEMORY) return make_setter(lhs, rhs) From 6816966fdd221d0eabea82edbe52fdfafebfba6e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 1 Aug 2023 18:07:04 +0300 Subject: [PATCH 070/471] lint --- vyper/codegen/dfg.py | 4 ++-- vyper/codegen/ir_basicblock.py | 2 +- vyper/codegen/ir_function.py | 3 ++- vyper/compiler/phases.py | 2 +- vyper/compiler/utils.py | 1 + vyper/ir/ir_to_bb_pass.py | 8 ++++---- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index ea29bc68ee..89783c6d96 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,7 +1,7 @@ -from vyper.codegen.ir_basicblock import IRInstruction, IROperant, IRLabel +from vyper.codegen.ir_basicblock import IRInstruction, IRLabel, IROperant from vyper.codegen.ir_function import IRFunction -from vyper.ir.compile_ir import PUSH, optimize_assembly from vyper.compiler.utils import StackMap +from vyper.ir.compile_ir import PUSH, optimize_assembly from vyper.utils import MemoryPositions ONE_TO_ONE_INSTRUCTIONS = [ diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 81a0e7e13c..201c07954c 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,4 +1,4 @@ -from typing import Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, Optional TERMINAL_IR_INSTRUCTIONS = ["ret", "revert", "assert"] diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index e6cab5da18..efe887f693 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -1,5 +1,6 @@ from typing import Optional -from vyper.codegen.ir_basicblock import IRBasicBlock, IRVariable, IRLabel + +from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRVariable class IRFunctionBase: diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index dd285ff7de..d12913ba9c 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -11,10 +11,10 @@ from vyper.compiler.settings import OptimizationLevel, Settings from vyper.exceptions import StructureException from vyper.ir import compile_ir, optimizer +from vyper.ir.ir_to_bb_pass import convert_ir_basicblock, generate_assembly_experimental from vyper.semantics import set_data_positions, validate_semantics from vyper.semantics.types.function import ContractFunctionT from vyper.typing import InterfaceImports, StorageLayout -from vyper.ir.ir_to_bb_pass import convert_ir_basicblock, generate_assembly_experimental class CompilerData: diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index ae9a3125e5..60bec187e8 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -1,4 +1,5 @@ from typing import Dict + from vyper.codegen.ir_basicblock import IROperant from vyper.semantics.types.function import ContractFunctionT diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 4c1aa0ff85..0d44f300b2 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,11 +1,11 @@ from typing import Optional, Union + from vyper.codegen.dfg import generate_evm -from vyper.codegen.ir_node import IRnode +from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction, IRLabel, IRLiteral, IROperant from vyper.codegen.ir_function import IRFunction -from vyper.codegen.ir_basicblock import IRInstruction -from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRLiteral, IROperant -from vyper.evm.opcodes import get_opcodes +from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel +from vyper.evm.opcodes import get_opcodes from vyper.semantics.types.function import ContractFunctionT TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "revert"] From beb2ed6e198d402d766b3743e3ea4f5e2bf04f28 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 6 Aug 2023 19:15:19 +0300 Subject: [PATCH 071/471] repeat --- vyper/ir/ir_to_bb_pass.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 0d44f300b2..125cfbd153 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -503,6 +503,19 @@ def _convert_ir_basicblock( return new_var elif ir.value == "return_buffer": return IRLabel("return_buffer", True) + elif ir.value == "repeat": + # TODO: implement repeat + sym = ir.args[0] + start = _convert_ir_basicblock(ctx, ir.args[1], symbols) + + new_var = ctx.get_next_variable() + inst = IRInstruction("load", [start], new_var) + ctx.get_basic_block().append_instruction(inst) + symbols[sym.value] = new_var + + # rounds_bound = _convert_ir_basicblock(ctx, ir.args[2], symbols) + # body = ir.args[3] + pass elif isinstance(ir.value, str) and ir.value.startswith("log"): # count = int(ir.value[3:]) args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] From dc95f916a0356a73541c95e3be7a4d4745ff70e2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 8 Aug 2023 14:27:59 +0300 Subject: [PATCH 072/471] append function with return variable --- vyper/codegen/ir_function.py | 12 +++- vyper/ir/ir_to_bb_pass.py | 125 ++++++++++++----------------------- 2 files changed, 54 insertions(+), 83 deletions(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index efe887f693..947f891fb0 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -1,6 +1,7 @@ +from symtable import SymbolTable from typing import Optional -from vyper.codegen.ir_basicblock import IRBasicBlock, IRLabel, IRVariable +from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction, IRLabel, IRVariable, IROperant class IRFunctionBase: @@ -99,6 +100,15 @@ def remove_unreachable_blocks(self) -> int: self.basic_blocks = new_basic_blocks return removed + def append_instruction(self, opcode: str, args: list[IROperant]): + """ + Append instruction to last basic block. + """ + ret = self.get_next_variable() + inst = IRInstruction(opcode, args, ret) + self.get_basic_block().append_instruction(inst) + return ret + def __repr__(self) -> str: str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 125cfbd153..3063db3a3c 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -9,6 +9,25 @@ from vyper.semantics.types.function import ContractFunctionT TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "revert"] +BINARY_IR_INSTRUCTIONS = [ + "eq", + "gt", + "lt", + "slt", + "sgt", + "shr", + "shl", + "or", + "xor", + "and", + "add", + "sub", + "mul", + "div", + "mod", + "sha3", + "sha3_64", +] SymbolTable = dict[str, IROperant] @@ -238,10 +257,7 @@ def _convert_ir_basicblock( for arg in ir.args: args.append(_convert_ir_basicblock(ctx, arg, symbols)) - ret = ctx.get_next_variable() - inst = IRInstruction("call", args, ret) - ctx.get_basic_block().append_instruction(inst) - return ret + return ctx.append_instruction("call", args) elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() @@ -288,33 +304,13 @@ def _convert_ir_basicblock( sym = ir.args[0] if ret.is_literal: - new_var = ctx.get_next_variable() - inst = IRInstruction("load", [ret], new_var) - ctx.get_basic_block().append_instruction(inst) + new_var = ctx.append_instruction("load", [ret]) symbols[sym.value] = new_var else: symbols[sym.value] = ret return _convert_ir_basicblock(ctx, ir.args[2], symbols) # body - elif ir.value in [ - "eq", - "gt", - "lt", - "slt", - "sgt", - "shr", - "shl", - "or", - "xor", - "and", - "add", - "sub", - "mul", - "div", - "mod", - "sha3", - "sha3_64", - ]: + elif ir.value in BINARY_IR_INSTRUCTIONS: return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3", "sha3_64"]) elif ir.value == "le": ir.value = "gt" @@ -330,44 +326,24 @@ def _convert_ir_basicblock( return _convert_binary_op(ctx, ir, symbols, False) # TODO: check if this is correct order elif ir.value == "iszero": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) - args = [arg_0] - - ret = ctx.get_next_variable() - - inst = IRInstruction("iszero", args, ret) - ctx.get_basic_block().append_instruction(inst) - return ret + return ctx.append_instruction("iszero", [arg_0]) elif ir.value == "goto": return _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "ceil32": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) - new_var = ctx.get_next_variable() - inst = IRInstruction("ceil32", [arg_0], new_var) - ctx.get_basic_block().append_instruction(inst) - return new_var + return ctx.append_instruction("ceil32", [arg_0]) elif ir.value == "set": sym = ir.args[0] - new_var = ctx.get_next_variable() arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) - inst = IRInstruction("load", [arg_1], new_var) - ctx.get_basic_block().append_instruction(inst) + new_var = ctx.append_instruction("load", [arg_1]) symbols[sym.value] = new_var elif ir.value == "calldatasize": - ret = ctx.get_next_variable() - inst = IRInstruction("calldatasize", [], ret) - ctx.get_basic_block().append_instruction(inst) - return ret + return ctx.append_instruction("calldatasize", []) elif ir.value == "calldataload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) - ret = ctx.get_next_variable() - inst = IRInstruction("calldataload", [arg_0], ret) - ctx.get_basic_block().append_instruction(inst) - return ret + return ctx.append_instruction("calldataload", [arg_0]) elif ir.value == "callvalue": - ret = ctx.get_next_variable() - inst = IRInstruction("callvalue", [], ret) - ctx.get_basic_block().append_instruction(inst) - return ret + return ctx.append_instruction("callvalue", []) elif ir.value == "calldatacopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) @@ -383,9 +359,7 @@ def _convert_ir_basicblock( arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) current_bb = ctx.get_basic_block() - new_var = ctx.get_next_variable() - inst = IRInstruction("iszero", [arg_0], new_var) - current_bb.append_instruction(inst) + new_var = ctx.append_instruction("iszero", [arg_0]) exit_label = ctx.get_next_label() bb = IRBasicBlock(exit_label, ctx) @@ -426,21 +400,12 @@ def _convert_ir_basicblock( inst = IRInstruction("revert", [arg_0, arg_1]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "timestamp": - new_var = ctx.get_next_variable() - inst = IRInstruction("timestamp", [], new_var) - ctx.get_basic_block().append_instruction(inst) - return new_var + return ctx.append_instruction("timestamp", []) elif ir.value == "caller": - new_var = ctx.get_next_variable() - inst = IRInstruction("caller", [], new_var) - ctx.get_basic_block().append_instruction(inst) - return new_var + return ctx.append_instruction("caller", []) elif ir.value == "dload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) - new_var = ctx.get_next_variable() - inst = IRInstruction("calldataload", [arg_0], new_var) - ctx.get_basic_block().append_instruction(inst) - return new_var + return ctx.append_instruction("calldataload", [arg_0]) elif ir.value == "pass": pass elif ir.value == "stop": @@ -448,10 +413,7 @@ def _convert_ir_basicblock( elif ir.value == "return": pass elif ir.value == "selfbalance": - new_var = ctx.get_next_variable() - inst = IRInstruction("selfbalance", [], new_var) - ctx.get_basic_block().append_instruction(inst) - return new_var + return ctx.append_instruction("selfbalance", []) elif ir.value == "mload": sym = ir.args[0] if sym.is_literal: @@ -474,23 +436,16 @@ def _convert_ir_basicblock( symbols[f"&{sym_ir.value}"] = arg_1 return arg_1 else: - new_var = ctx.get_next_variable() - inst = IRInstruction("load", [sym], new_var) - ctx.get_basic_block().append_instruction(inst) + new_var = ctx.append_instruction("load", [sym]) symbols[f"&{sym_ir.value}"] = new_var return new_var else: - new_var = ctx.get_next_variable() - inst = IRInstruction("load", [arg_1], new_var) - ctx.get_basic_block().append_instruction(inst) + new_var = ctx.append_instruction("load", [arg_1]) symbols[sym_ir.value] = new_var return new_var elif ir.value == "sload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) - new_var = ctx.get_next_variable() - inst = IRInstruction("sload", [arg_0], new_var) - ctx.get_basic_block().append_instruction(inst) - return new_var + return ctx.append_instruction("sload", [arg_0]) elif ir.value == "sstore": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) @@ -504,9 +459,15 @@ def _convert_ir_basicblock( elif ir.value == "return_buffer": return IRLabel("return_buffer", True) elif ir.value == "repeat": - # TODO: implement repeat sym = ir.args[0] start = _convert_ir_basicblock(ctx, ir.args[1], symbols) + end = _convert_ir_basicblock(ctx, ir.args[2], symbols) + bound = _convert_ir_basicblock(ctx, ir.args[3], symbols) + body = ir.args[4] + + r_ir = IRnode.from_list(["with", sym, [start], body]) + + return _convert_ir_opcode(r_ir) new_var = ctx.get_next_variable() inst = IRInstruction("load", [start], new_var) From a5ede13db571fd462fdd01948b577fa16259d6e2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 8 Aug 2023 14:59:38 +0300 Subject: [PATCH 073/471] refactor --- vyper/ir/ir_to_bb_pass.py | 86 ++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 52 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 3063db3a3c..f03984f20e 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -29,6 +29,8 @@ "sha3_64", ] +MAPPED_IR_INSTRUCTIONS = {"le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} + SymbolTable = dict[str, IROperant] @@ -198,18 +200,13 @@ def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> None for arg in args_ir: if arg.value != "with": ret = _convert_ir_basicblock(ctx, arg, symbols) - new_var = ctx.get_next_variable() - inst = IRInstruction("calldataload", [ret], new_var) - ctx.get_basic_block().append_instruction(inst) + new_var = ctx.append_instruction("calldataload", [ret]) ret_values.append(new_var) else: ret = _convert_ir_basicblock(ctx, arg, symbols) ret_values.append(ret) - call_ret = ctx.get_next_variable() - call_inst = IRInstruction("call", ret_values, call_ret) - ctx.get_basic_block().append_instruction(call_inst) - return call_ret + return ctx.append_instruction("call", ret_values) def _handle_internal_func( @@ -232,12 +229,35 @@ def _handle_internal_func( return ir.args[0].args[2] +def _convert_ir_simple_node( + ctx: IRFunction, ir: IRnode, symbols: SymbolTable +) -> Optional[Union[str, int]]: + args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] + return ctx.append_instruction(ir.value, args) + + def _convert_ir_basicblock( ctx: IRFunction, ir: IRnode, symbols: SymbolTable ) -> Optional[Union[str, int]]: # symbols = symbols.copy() - if ir.value == "deploy": + if ir.value in BINARY_IR_INSTRUCTIONS: + return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3", "sha3_64"]) + + elif ir.value in MAPPED_IR_INSTRUCTIONS.keys(): + ir.value = MAPPED_IR_INSTRUCTIONS[ir.value] + return _convert_binary_op(ctx, ir, symbols) + + elif ir.value in ["iszero", "ceil32", "calldataload"]: + return _convert_ir_simple_node(ctx, ir, symbols) + + elif ir.value in ["timestamp", "caller", "selfbalance", "calldatasize", "callvalue"]: + return ctx.append_instruction(ir.value, []) + + elif ir.value in ["pass", "stop", "return"]: + pass + + elif ir.value == "deploy": _convert_ir_basicblock(ctx, ir.args[1], symbols) elif ir.value == "seq": if ir.is_self_call: @@ -310,50 +330,22 @@ def _convert_ir_basicblock( symbols[sym.value] = ret return _convert_ir_basicblock(ctx, ir.args[2], symbols) # body - elif ir.value in BINARY_IR_INSTRUCTIONS: - return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3", "sha3_64"]) - elif ir.value == "le": - ir.value = "gt" - return _convert_binary_op(ctx, ir, symbols, False) # TODO: check if this is correct order - elif ir.value == "sle": - ir.value = "sgt" - return _convert_binary_op(ctx, ir, symbols, False) # TODO: check if this is correct order - elif ir.value == "ge": - ir.value = "lt" - return _convert_binary_op(ctx, ir, symbols, False) # TODO: check if this is correct order - elif ir.value == "sge": - ir.value = "slt" - return _convert_binary_op(ctx, ir, symbols, False) # TODO: check if this is correct order - elif ir.value == "iszero": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) - return ctx.append_instruction("iszero", [arg_0]) elif ir.value == "goto": return _append_jmp(ctx, IRLabel(ir.args[0].value)) - elif ir.value == "ceil32": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) - return ctx.append_instruction("ceil32", [arg_0]) elif ir.value == "set": sym = ir.args[0] arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) new_var = ctx.append_instruction("load", [arg_1]) symbols[sym.value] = new_var - elif ir.value == "calldatasize": - return ctx.append_instruction("calldatasize", []) - elif ir.value == "calldataload": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) - return ctx.append_instruction("calldataload", [arg_0]) - elif ir.value == "callvalue": - return ctx.append_instruction("callvalue", []) + elif ir.value == "calldatacopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) size = _convert_ir_basicblock(ctx, ir.args[2], symbols) - # orig_sym = symbols.get(f"&{arg_0.value}", None) - # assert orig_sym is None, "calldatacopy should not have a symbol" - new_var = ctx.get_next_variable() + + new_var = ctx.append_instruction("calldatacopy", [arg_1, size]) + symbols[f"&{arg_0.value}"] = new_var - inst = IRInstruction("calldatacopy", [arg_1, size], new_var) - ctx.get_basic_block().append_instruction(inst) return new_var elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) @@ -399,21 +391,11 @@ def _convert_ir_basicblock( arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) inst = IRInstruction("revert", [arg_0, arg_1]) ctx.get_basic_block().append_instruction(inst) - elif ir.value == "timestamp": - return ctx.append_instruction("timestamp", []) - elif ir.value == "caller": - return ctx.append_instruction("caller", []) + elif ir.value == "dload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) return ctx.append_instruction("calldataload", [arg_0]) - elif ir.value == "pass": - pass - elif ir.value == "stop": - pass - elif ir.value == "return": - pass - elif ir.value == "selfbalance": - return ctx.append_instruction("selfbalance", []) + elif ir.value == "mload": sym = ir.args[0] if sym.is_literal: From d778fb36431e37739f4a58390c192655469ae331 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 8 Aug 2023 15:10:03 +0300 Subject: [PATCH 074/471] split out bb optimizer --- vyper/codegen/ir_basicblock.py | 1 + vyper/ir/bb_optimizer.py | 117 +++++++++++++++++++++++++++++++++ vyper/ir/ir_to_bb_pass.py | 117 ++------------------------------- 3 files changed, 123 insertions(+), 112 deletions(-) create mode 100644 vyper/ir/bb_optimizer.py diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 201c07954c..b27d0172df 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING, Optional TERMINAL_IR_INSTRUCTIONS = ["ret", "revert", "assert"] +TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "revert"] if TYPE_CHECKING: from vyper.codegen.ir_function import IRFunction diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py new file mode 100644 index 0000000000..52c3579dfd --- /dev/null +++ b/vyper/ir/bb_optimizer.py @@ -0,0 +1,117 @@ +from vyper.codegen.ir_basicblock import ( + TERMINATOR_IR_INSTRUCTIONS, + IRBasicBlock, + IRInstruction, + IRLabel, +) +from vyper.codegen.ir_function import IRFunction + + +def optimize_function(ctx: IRFunction): + while _optimize_empty_basicblocks(ctx): + pass + + _calculate_in_set(ctx) + + while ctx.remove_unreachable_blocks(): + pass + + if len(ctx.basic_blocks) == 0: + return ctx + + while True: + _calculate_liveness(ctx.basic_blocks[0], set()) + + removed = _optimize_unused_variables(ctx) + if len(removed) == 0: + break + + +def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: + """ + Remove unused variables. + """ + count = 0 + removeList = [] + for bb in ctx.basic_blocks: + for i, inst in enumerate(bb.instructions[:-1]): + if inst.opcode in ["call", "sload", "sstore"]: + continue + if inst.ret and inst.ret not in bb.instructions[i + 1].liveness: + removeList.append(inst) + count += 1 + + bb.instructions = [inst for inst in bb.instructions if inst not in removeList] + + return removeList + + +def _optimize_empty_basicblocks(ctx: IRFunction) -> None: + """ + Remove empty basic blocks. + """ + count = 0 + i = 0 + while i < len(ctx.basic_blocks): + bb = ctx.basic_blocks[i] + i += 1 + if len(bb.instructions) > 0: + continue + + replaced_label = bb.label + replacement_label = ctx.basic_blocks[i].label if i < len(ctx.basic_blocks) else None + if replacement_label is None: + continue + + # Try to preserve symbol labels + if replaced_label.is_symbol: + replaced_label, replacement_label = replacement_label, replaced_label + ctx.basic_blocks[i].label = replacement_label + + for bb2 in ctx.basic_blocks: + for inst in bb2.instructions: + for op in inst.operands: + if isinstance(op, IRLabel) and op == replaced_label: + op.value = replacement_label.value + + ctx.basic_blocks.remove(bb) + i -= 1 + count += 1 + + return count + + +def _calculate_in_set(ctx: IRFunction) -> None: + """ + Calculate in set for each basic block. + """ + for bb in ctx.basic_blocks: + assert len(bb.instructions) > 0, "Basic block should not be empty" + last_inst = bb.instructions[-1] + assert ( + last_inst.opcode in TERMINATOR_IR_INSTRUCTIONS + ), "Last instruction should be a terminator" + + for inst in bb.instructions: + if inst.opcode in ["jmp", "jnz", "call"]: + ops = inst.get_label_operands() + for op in ops: + ctx.get_basic_block(op.value).add_in(bb) + + # Fill in the "out" set for each basic block + for bb in ctx.basic_blocks: + for in_bb in bb.in_set: + in_bb.add_out(bb) + + +def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: + bb.out_vars = set() + for out_bb in bb.out_set: + _calculate_liveness(out_bb, liveness_visited) + in_vars = out_bb.in_vars_for(bb) + bb.out_vars = bb.out_vars.union(in_vars) + + if bb in liveness_visited: + return + liveness_visited.add(bb) + bb.calculate_liveness() diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index f03984f20e..df520c621c 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -6,9 +6,11 @@ from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel from vyper.evm.opcodes import get_opcodes +from vyper.ir.bb_optimizer import ( + optimize_function, +) from vyper.semantics.types.function import ContractFunctionT -TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "revert"] BINARY_IR_INSTRUCTIONS = [ "eq", "gt", @@ -53,121 +55,12 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No revert_bb = global_function.append_basic_block(revert_bb) revert_bb.append_instruction(IRInstruction("revert", [IRLiteral(0), IRLiteral(0)])) - while _optimize_empty_basicblocks(global_function): - pass - - _calculate_in_set(global_function) - - while global_function.remove_unreachable_blocks(): - pass - - if len(global_function.basic_blocks) == 0: - return global_function - - while True: - _calculate_liveness(global_function.basic_blocks[0], set()) - - # Optimization pass: Remove unused variables - if optimize is OptimizationLevel.NONE: - break - - removed = _optimize_unused_variables(global_function) - if len(removed) == 0: - break + if optimize is not OptimizationLevel.NONE: + optimize_function(global_function) return global_function -def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: - """ - Remove unused variables. - """ - count = 0 - removeList = [] - for bb in ctx.basic_blocks: - for i, inst in enumerate(bb.instructions[:-1]): - if inst.opcode in ["call", "sload", "sstore"]: - continue - if inst.ret and inst.ret not in bb.instructions[i + 1].liveness: - removeList.append(inst) - count += 1 - - bb.instructions = [inst for inst in bb.instructions if inst not in removeList] - - return removeList - - -def _optimize_empty_basicblocks(ctx: IRFunction) -> None: - """ - Remove empty basic blocks. - """ - count = 0 - i = 0 - while i < len(ctx.basic_blocks): - bb = ctx.basic_blocks[i] - i += 1 - if len(bb.instructions) > 0: - continue - - replaced_label = bb.label - replacement_label = ctx.basic_blocks[i].label if i < len(ctx.basic_blocks) else None - if replacement_label is None: - continue - - # Try to preserve symbol labels - if replaced_label.is_symbol: - replaced_label, replacement_label = replacement_label, replaced_label - ctx.basic_blocks[i].label = replacement_label - - for bb2 in ctx.basic_blocks: - for inst in bb2.instructions: - for op in inst.operands: - if isinstance(op, IRLabel) and op == replaced_label: - op.value = replacement_label.value - - ctx.basic_blocks.remove(bb) - i -= 1 - count += 1 - - return count - - -def _calculate_in_set(ctx: IRFunction) -> None: - """ - Calculate in set for each basic block. - """ - for bb in ctx.basic_blocks: - assert len(bb.instructions) > 0, "Basic block should not be empty" - last_inst = bb.instructions[-1] - assert ( - last_inst.opcode in TERMINATOR_IR_INSTRUCTIONS - ), "Last instruction should be a terminator" - - for inst in bb.instructions: - if inst.opcode in ["jmp", "jnz", "call"]: - ops = inst.get_label_operands() - for op in ops: - ctx.get_basic_block(op.value).add_in(bb) - - # Fill in the "out" set for each basic block - for bb in ctx.basic_blocks: - for in_bb in bb.in_set: - in_bb.add_out(bb) - - -def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: - bb.out_vars = set() - for out_bb in bb.out_set: - _calculate_liveness(out_bb, liveness_visited) - in_vars = out_bb.in_vars_for(bb) - bb.out_vars = bb.out_vars.union(in_vars) - - if bb in liveness_visited: - return - liveness_visited.add(bb) - bb.calculate_liveness() - - def _convert_binary_op( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, swap: bool = False ) -> str: From c513c67e998cfc6785499c24ce7b6b7c7ab4863f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 8 Aug 2023 17:34:36 +0300 Subject: [PATCH 075/471] lint --- vyper/ir/ir_to_bb_pass.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index df520c621c..249a9bca06 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -6,9 +6,7 @@ from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel from vyper.evm.opcodes import get_opcodes -from vyper.ir.bb_optimizer import ( - optimize_function, -) +from vyper.ir.bb_optimizer import optimize_function from vyper.semantics.types.function import ContractFunctionT BINARY_IR_INSTRUCTIONS = [ @@ -111,7 +109,7 @@ def _handle_internal_func( old_ir_mempos = 0 old_ir_mempos += 64 - for arg in func_t.arguments: + for _ in func_t.arguments: new_var = ctx.get_next_variable() alloca_inst = IRInstruction("alloca", [], new_var) @@ -336,8 +334,8 @@ def _convert_ir_basicblock( elif ir.value == "repeat": sym = ir.args[0] start = _convert_ir_basicblock(ctx, ir.args[1], symbols) - end = _convert_ir_basicblock(ctx, ir.args[2], symbols) - bound = _convert_ir_basicblock(ctx, ir.args[3], symbols) + # end = _convert_ir_basicblock(ctx, ir.args[2], symbols) + # bound = _convert_ir_basicblock(ctx, ir.args[3], symbols) body = ir.args[4] r_ir = IRnode.from_list(["with", sym, [start], body]) From 2135b03115fa1bb7522710b39e2567d7ca5dae9f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 13 Aug 2023 19:35:18 +0300 Subject: [PATCH 076/471] repeat --- vyper/codegen/ir_function.py | 4 +-- vyper/ir/bb_optimizer.py | 9 ++--- vyper/ir/ir_to_bb_pass.py | 64 +++++++++++++++++++++++++++--------- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 947f891fb0..0bd72c4c48 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -76,11 +76,11 @@ def get_terminal_basicblocks(self) -> list[IRBasicBlock]: """ return [bb for bb in self.basic_blocks if bb.is_terminal()] - def get_next_label(self) -> str: + def get_next_label(self) -> IRLabel: self.last_label += 1 return IRLabel(f"{self.last_label}") - def get_next_variable(self) -> str: + def get_next_variable(self) -> IRVariable: self.last_variable += 1 return IRVariable(f"%{self.last_variable}") diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 52c3579dfd..50aea7380c 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -90,7 +90,7 @@ def _calculate_in_set(ctx: IRFunction) -> None: last_inst = bb.instructions[-1] assert ( last_inst.opcode in TERMINATOR_IR_INSTRUCTIONS - ), "Last instruction should be a terminator" + ), "Last instruction should be a terminator" + str(bb) for inst in bb.instructions: if inst.opcode in ["jmp", "jnz", "call"]: @@ -105,13 +105,14 @@ def _calculate_in_set(ctx: IRFunction) -> None: def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: + if bb in liveness_visited: + return + liveness_visited.add(bb) + bb.out_vars = set() for out_bb in bb.out_set: _calculate_liveness(out_bb, liveness_visited) in_vars = out_bb.in_vars_for(bb) bb.out_vars = bb.out_vars.union(in_vars) - if bb in liveness_visited: - return - liveness_visited.add(bb) bb.calculate_liveness() diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 249a9bca06..bd96058074 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -53,8 +53,8 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No revert_bb = global_function.append_basic_block(revert_bb) revert_bb.append_instruction(IRInstruction("revert", [IRLiteral(0), IRLiteral(0)])) - if optimize is not OptimizationLevel.NONE: - optimize_function(global_function) + # if optimize is not OptimizationLevel.NONE: + # optimize_function(global_function) return global_function @@ -83,6 +83,12 @@ def _append_jmp(ctx: IRFunction, label: IRLabel) -> None: ctx.append_basic_block(bb) +def _new_block(ctx: IRFunction) -> IRBasicBlock: + bb = IRBasicBlock(ctx.get_next_label(), ctx) + bb = ctx.append_basic_block(bb) + return bb + + def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> None: args_ir = ir.passthrough_metadata["args_ir"] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] @@ -200,6 +206,8 @@ def _convert_ir_basicblock( bb = IRBasicBlock(exit_label, ctx) bb = ctx.append_basic_block(bb) + # _emit_selects(ctx, after_then_syms, after_else_syms, then_block, else_block, bb) + for sym, val in _get_symbols_common(after_then_syms, after_else_syms).items(): ret = ctx.get_next_variable() symbols[sym] = ret @@ -244,11 +252,9 @@ def _convert_ir_basicblock( new_var = ctx.append_instruction("iszero", [arg_0]) - exit_label = ctx.get_next_label() - bb = IRBasicBlock(exit_label, ctx) - bb = ctx.append_basic_block(bb) + exit_block = _new_block(ctx) - inst = IRInstruction("jnz", [new_var, IRLabel("__revert"), exit_label]) + inst = IRInstruction("jnz", [new_var, IRLabel("__revert"), exit_block.label]) current_bb.append_instruction(inst) elif ir.value == "label": @@ -334,21 +340,49 @@ def _convert_ir_basicblock( elif ir.value == "repeat": sym = ir.args[0] start = _convert_ir_basicblock(ctx, ir.args[1], symbols) - # end = _convert_ir_basicblock(ctx, ir.args[2], symbols) - # bound = _convert_ir_basicblock(ctx, ir.args[3], symbols) + end = _convert_ir_basicblock(ctx, ir.args[2], symbols) + bound = _convert_ir_basicblock(ctx, ir.args[3], symbols) body = ir.args[4] - r_ir = IRnode.from_list(["with", sym, [start], body]) + increment_block = IRBasicBlock(ctx.get_next_label(), ctx) + counter_inc_var = ctx.get_next_variable() - return _convert_ir_opcode(r_ir) + entry_block = ctx.get_basic_block() + cond_block = IRBasicBlock(ctx.get_next_label(), ctx) - new_var = ctx.get_next_variable() - inst = IRInstruction("load", [start], new_var) + counter_var = ctx.get_next_variable() + ret = ctx.get_next_variable() + symbols[sym.value] = ret + cond_block.append_instruction( + IRInstruction( + "select", + [entry_block.label, counter_var, increment_block.label, counter_inc_var], + ret, + ) + ) + + inst = IRInstruction("load", [start], counter_var) ctx.get_basic_block().append_instruction(inst) - symbols[sym.value] = new_var + symbols[sym.value] = counter_var + inst = IRInstruction("jmp", [increment_block.label]) + ctx.get_basic_block().append_instruction(inst) + + ctx.append_basic_block(increment_block) + + cont_ret = ctx.get_next_variable() + inst = IRInstruction("sub", [ret, end], cont_ret) + cond_block.append_instruction(inst) + + body_block = _new_block(ctx) + _convert_ir_basicblock(ctx, body, symbols) + jump_cond = IRInstruction("jmp", [cond_block.label]) + body_block.append_instruction(jump_cond) + + exit_block = _new_block(ctx) + + inst = IRInstruction("jnz", [cont_ret, body_block.label, exit_block.label]) + cond_block.append_instruction(inst) - # rounds_bound = _convert_ir_basicblock(ctx, ir.args[2], symbols) - # body = ir.args[3] pass elif isinstance(ir.value, str) and ir.value.startswith("log"): # count = int(ir.value[3:]) From 3176e21e0b9f64e1bafa231aed0638f6552a94b9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 14 Aug 2023 11:06:48 +0300 Subject: [PATCH 077/471] more repeat in var calculation fix ir emit fix depth from list directly manipulate stack instead of hack update invert ret params ret add todo about repeat bounds break and continue --- vyper/codegen/dfg.py | 16 ++++- vyper/codegen/ir_basicblock.py | 32 +++++++--- vyper/compiler/utils.py | 9 ++- vyper/ir/bb_optimizer.py | 11 ++-- vyper/ir/ir_to_bb_pass.py | 104 +++++++++++++++++++++++---------- 5 files changed, 118 insertions(+), 54 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 89783c6d96..ecc40bc2de 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -6,7 +6,6 @@ ONE_TO_ONE_INSTRUCTIONS = [ "revert", - "assert", "calldatasize", "calldatacopy", "calldataload", @@ -104,6 +103,9 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: for inst in bb.instructions: _generate_evm_for_instruction_r(ctx, assembly, inst, stack_map) + # Append postambles + assembly.extend(["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"]) + if no_optimize is False: optimize_assembly(assembly) @@ -135,8 +137,14 @@ def _generate_evm_for_instruction_r( if opcode == "select": ret = inst.get_output_operands()[0] inputs = inst.get_input_variables() - for input in inputs: - input.value = ret.value + depth = stack_map.get_depth_in(inputs) + assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" + to_be_replaced = stack_map.peek(depth) + if to_be_replaced.use_count > 1: + to_be_replaced.use_count -= 1 + stack_map.push(ret) + else: + stack_map.poke(depth, ret) return _emit_input_operands(ctx, assembly, operands, stack_map) @@ -228,6 +236,8 @@ def _generate_evm_for_instruction_r( ) elif opcode == "ceil32": assembly.extend([*PUSH(31), "ADD", *PUSH(31), "NOT", "AND"]) + elif opcode == "assert": + assembly.extend(["_sym___revert", "JUMPI"]) else: raise Exception(f"Unknown opcode: {opcode}") diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index b27d0172df..1e71530533 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -220,10 +220,14 @@ def in_vars_for(self, bb: "IRBasicBlock" = None) -> set[IRVariable]: if bb: for inst in self.instructions: if inst.opcode == "select": - if inst.operands[0] == bb.label and inst.operands[3] in liveness: - liveness.remove(inst.operands[3]) - if inst.operands[2] == bb.label and inst.operands[1] in liveness: - liveness.remove(inst.operands[1]) + if inst.operands[0] == bb.label: + liveness.add(inst.operands[1]) + if inst.operands[3] in liveness: + liveness.remove(inst.operands[3]) + if inst.operands[2] == bb.label: + liveness.add(inst.operands[3]) + if inst.operands[1] in liveness: + liveness.remove(inst.operands[1]) return liveness @@ -243,20 +247,30 @@ def is_terminal(self) -> bool: assert len(self.instructions) > 0, "basic block must have at least one instruction" return self.instructions[-1].opcode in TERMINAL_IR_INSTRUCTIONS + @property + def is_terminated(self) -> bool: + """ + Check if the basic block is terminal, i.e. the last instruction is a terminator. + """ + if len(self.instructions) == 0: + return False + return self.instructions[-1].opcode in TERMINATOR_IR_INSTRUCTIONS + def calculate_liveness(self) -> None: """ - Compute liveness of each instruction in basic block. + Compute liveness of each instruction in the basic block. """ + liveness = self.out_vars.copy() for instruction in self.instructions[::-1]: - self.out_vars = self.out_vars.union(instruction.get_input_variables()) + liveness = liveness.union(instruction.get_input_variables()) out = ( instruction.get_output_operands()[0] if len(instruction.get_output_operands()) > 0 else None ) - if out in self.out_vars: - self.out_vars.remove(out) - instruction.liveness = self.out_vars.copy() + if out in liveness: + liveness.remove(out) + instruction.liveness = liveness def get_liveness(self) -> set[IRVariable]: """ diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 60bec187e8..605acd9c3f 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -72,14 +72,17 @@ def push(self, op: IROperant) -> None: def pop(self, num: int = 1) -> None: del self.stack_map[len(self.stack_map) - num :] - def get_depth_in(self, op: IROperant) -> int: + def get_depth_in(self, op: IROperant | list[IROperant]) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ for i, stack_op in enumerate(self.stack_map[::-1]): - if isinstance(stack_op, IROperant) and stack_op.value == op.value: - return -i + if isinstance(stack_op, IROperant): + if isinstance(op, IROperant) and stack_op.value == op.value: + return -i + elif isinstance(op, list) and stack_op in op: + return -i return StackMap.NOT_IN_STACK diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 50aea7380c..ce26b7656a 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -20,8 +20,7 @@ def optimize_function(ctx: IRFunction): return ctx while True: - _calculate_liveness(ctx.basic_blocks[0], set()) - + _calculate_liveness(ctx.basic_blocks[0], {}) removed = _optimize_unused_variables(ctx) if len(removed) == 0: break @@ -105,12 +104,10 @@ def _calculate_in_set(ctx: IRFunction) -> None: def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: - if bb in liveness_visited: - return - liveness_visited.add(bb) - - bb.out_vars = set() for out_bb in bb.out_set: + if liveness_visited.get(bb, None) == out_bb: + continue + liveness_visited[bb] = out_bb _calculate_liveness(out_bb, liveness_visited) in_vars = out_bb.in_vars_for(bb) bb.out_vars = bb.out_vars.union(in_vars) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index bd96058074..4cfefc7a17 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -53,8 +53,8 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No revert_bb = global_function.append_basic_block(revert_bb) revert_bb.append_instruction(IRInstruction("revert", [IRLiteral(0), IRLiteral(0)])) - # if optimize is not OptimizationLevel.NONE: - # optimize_function(global_function) + if optimize is not OptimizationLevel.NONE: + optimize_function(global_function) return global_function @@ -127,15 +127,24 @@ def _handle_internal_func( def _convert_ir_simple_node( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable + ctx: IRFunction, + ir: IRnode, + symbols: SymbolTable, ) -> Optional[Union[str, int]]: args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] return ctx.append_instruction(ir.value, args) +_break_target: IRBasicBlock = None +_continue_target: IRBasicBlock = None + + def _convert_ir_basicblock( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable + ctx: IRFunction, + ir: IRnode, + symbols: SymbolTable, ) -> Optional[Union[str, int]]: + global _break_target, _continue_target # symbols = symbols.copy() if ir.value in BINARY_IR_INSTRUCTIONS: @@ -215,8 +224,13 @@ def _convert_ir_basicblock( IRInstruction("select", [then_block.label, val[0], else_block.label, val[1]], ret) ) - exit_inst = IRInstruction("jmp", [bb.label]) - else_block.append_instruction(exit_inst) + if else_block.is_terminated is False: + exit_inst = IRInstruction("jmp", [bb.label]) + else_block.append_instruction(exit_inst) + + if then_block.is_terminated is False: + exit_inst = IRInstruction("jmp", [bb.label]) + then_block.append_instruction(exit_inst) elif ir.value == "with": ret = _convert_ir_basicblock(ctx, ir.args[1], symbols) # initialization @@ -252,11 +266,8 @@ def _convert_ir_basicblock( new_var = ctx.append_instruction("iszero", [arg_0]) - exit_block = _new_block(ctx) - - inst = IRInstruction("jnz", [new_var, IRLabel("__revert"), exit_block.label]) + inst = IRInstruction("assert", [new_var]) current_bb.append_instruction(inst) - elif ir.value == "label": bb = IRBasicBlock(IRLabel(ir.args[0].value, True), ctx) ctx.append_basic_block(bb) @@ -281,7 +292,7 @@ def _convert_ir_basicblock( ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols) new_var = symbols.get(f"&{ret_ir.value}", ret_ir) - inst = IRInstruction("ret", [ret_ir, last_ir]) + inst = IRInstruction("ret", [last_ir, new_var]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) @@ -308,16 +319,12 @@ def _convert_ir_basicblock( elif ir.value == "mstore": sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + sym = symbols.get(f"&{arg_1.value}", None) if sym_ir.is_literal: - sym = symbols.get(f"&{arg_1.value}", None) - if sym is None: - symbols[f"&{sym_ir.value}"] = arg_1 - return arg_1 - else: - new_var = ctx.append_instruction("load", [sym]) - symbols[f"&{sym_ir.value}"] = new_var - return new_var + new_var = ctx.append_instruction("load", [arg_1]) + symbols[f"&{sym_ir.value}"] = new_var + return new_var else: new_var = ctx.append_instruction("load", [arg_1]) symbols[sym_ir.value] = new_var @@ -338,19 +345,30 @@ def _convert_ir_basicblock( elif ir.value == "return_buffer": return IRLabel("return_buffer", True) elif ir.value == "repeat": + # + # repeat(sym, start, end, bound, body) + # 1) entry block ] + # 2) init counter block ] -> same block + # 3) condition block (exit block, body block) + # 4) body block + # 5) increment block + # 6) exit block + # TODO: Add the extra bounds check after clarify sym = ir.args[0] start = _convert_ir_basicblock(ctx, ir.args[1], symbols) end = _convert_ir_basicblock(ctx, ir.args[2], symbols) bound = _convert_ir_basicblock(ctx, ir.args[3], symbols) body = ir.args[4] - increment_block = IRBasicBlock(ctx.get_next_label(), ctx) - counter_inc_var = ctx.get_next_variable() - entry_block = ctx.get_basic_block() cond_block = IRBasicBlock(ctx.get_next_label(), ctx) + body_block = IRBasicBlock(ctx.get_next_label(), ctx) + jump_up_block = IRBasicBlock(ctx.get_next_label(), ctx) + increment_block = IRBasicBlock(ctx.get_next_label(), ctx) + exit_block = IRBasicBlock(ctx.get_next_label(), ctx) counter_var = ctx.get_next_variable() + counter_inc_var = ctx.get_next_variable() ret = ctx.get_next_variable() symbols[sym.value] = ret cond_block.append_instruction( @@ -364,26 +382,48 @@ def _convert_ir_basicblock( inst = IRInstruction("load", [start], counter_var) ctx.get_basic_block().append_instruction(inst) symbols[sym.value] = counter_var - inst = IRInstruction("jmp", [increment_block.label]) + inst = IRInstruction("jmp", [cond_block.label]) ctx.get_basic_block().append_instruction(inst) - ctx.append_basic_block(increment_block) - cont_ret = ctx.get_next_variable() - inst = IRInstruction("sub", [ret, end], cont_ret) + inst = IRInstruction("xor", [ret, end], cont_ret) cond_block.append_instruction(inst) + ctx.append_basic_block(cond_block) - body_block = _new_block(ctx) + ctx.append_basic_block(body_block) + old_targets = _break_target, _continue_target + _break_target, _continue_target = exit_block, increment_block _convert_ir_basicblock(ctx, body, symbols) - jump_cond = IRInstruction("jmp", [cond_block.label]) - body_block.append_instruction(jump_cond) + _break_target, _continue_target = old_targets + body_end = ctx.get_basic_block() + if body_end.is_terminal() is False: + body_end.append_instruction(IRInstruction("jmp", [jump_up_block.label])) - exit_block = _new_block(ctx) + jump_cond = IRInstruction("jmp", [increment_block.label]) + jump_up_block.append_instruction(jump_cond) + ctx.append_basic_block(jump_up_block) - inst = IRInstruction("jnz", [cont_ret, body_block.label, exit_block.label]) - cond_block.append_instruction(inst) + increment_block.append_instruction( + IRInstruction("add", [counter_var, IRLiteral(1)], counter_inc_var) + ) + increment_block.append_instruction(IRInstruction("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) + 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.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) + elif ir.value == "continue": pass + 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.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) elif isinstance(ir.value, str) and ir.value.startswith("log"): # count = int(ir.value[3:]) args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] From 7a0754800b4049d3c34da698b4327eaa6a8f4136 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 16 Aug 2023 11:35:12 -0400 Subject: [PATCH 078/471] flip assert cond --- vyper/ir/ir_to_bb_pass.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 4cfefc7a17..16d7d38c2b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -264,9 +264,9 @@ def _convert_ir_basicblock( arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) current_bb = ctx.get_basic_block() - new_var = ctx.append_instruction("iszero", [arg_0]) + #new_var = ctx.append_instruction("iszero", [arg_0]) - inst = IRInstruction("assert", [new_var]) + inst = IRInstruction("assert", [arg_0]) current_bb.append_instruction(inst) elif ir.value == "label": bb = IRBasicBlock(IRLabel(ir.args[0].value, True), ctx) From 0b5f79f1ca803fad201e1246fb973e2e022f4081 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 21 Aug 2023 11:57:53 +0300 Subject: [PATCH 079/471] revert assert inversion --- vyper/ir/ir_to_bb_pass.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 16d7d38c2b..4cfefc7a17 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -264,9 +264,9 @@ def _convert_ir_basicblock( arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) current_bb = ctx.get_basic_block() - #new_var = ctx.append_instruction("iszero", [arg_0]) + new_var = ctx.append_instruction("iszero", [arg_0]) - inst = IRInstruction("assert", [arg_0]) + inst = IRInstruction("assert", [new_var]) current_bb.append_instruction(inst) elif ir.value == "label": bb = IRBasicBlock(IRLabel(ir.args[0].value, True), ctx) From c5f227f92009988618bc1898749b7dcb2eba26d1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 21 Aug 2023 11:58:06 +0300 Subject: [PATCH 080/471] fensing --- vyper/codegen/dfg.py | 10 ++++++++++ vyper/codegen/ir_basicblock.py | 2 ++ 2 files changed, 12 insertions(+) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index ecc40bc2de..644425daa8 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -100,6 +100,13 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: if i != 0: assembly.append(f"_sym_{bb.label}") assembly.append("JUMPDEST") + + fen = 0 + for inst in bb.instructions: + inst.fen = fen + if inst.opcode in ["call", "sload", "sstore", "assert"]: + fen += 1 + for inst in bb.instructions: _generate_evm_for_instruction_r(ctx, assembly, inst, stack_map) @@ -120,10 +127,13 @@ def _generate_evm_for_instruction_r( ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap ) -> None: global label_counter + for op in inst.get_output_operands(): for target in dfg_inputs.get(op.value, []): if target.parent != inst.parent: continue + if target.fen != inst.fen: + continue _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) if inst in visited_instructions: diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 1e71530533..dcc056524f 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -97,6 +97,7 @@ class IRInstruction: dbg: Optional[IRDebugInfo] liveness: set[IRVariable] parent: Optional["IRBasicBlock"] + fen: int def __init__( self, opcode: str, operands: list[IROperant], ret: str = None, dbg: IRDebugInfo = None @@ -107,6 +108,7 @@ def __init__( self.dbg = dbg self.liveness = set() self.parent = None + self.fen = -1 def get_label_operands(self) -> list[IRLabel]: """ From bf22043e55c391d9bce90a4f01be477d43817d1a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 21 Aug 2023 12:41:22 +0300 Subject: [PATCH 081/471] Add _optimize_inefficient_jumps() --- vyper/ir/compile_ir.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 94896e7377..f915c9837a 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -807,6 +807,31 @@ def _prune_inefficient_jumps(assembly): return changed +def _optimize_inefficient_jumps(assembly): + # optimize sequences `_sym_common JUMPI _sym_x JUMP _sym_common JUMPDEST` + # to `ISZERO _sym_x JUMPI _sym_common JUMPDEST` + changed = False + i = 0 + while i < len(assembly) - 6: + if ( + is_symbol(assembly[i]) + and assembly[i + 1] == "JUMPI" + and is_symbol(assembly[i + 2]) + and assembly[i + 3] == "JUMP" + and assembly[i] == assembly[i + 4] + and assembly[i + 5] == "JUMPDEST" + ): + changed = True + assembly[i] = "ISZERO" + assembly[i + 1] = assembly[i + 2] + assembly[i + 2] = "JUMPI" + del assembly[i + 3 : i + 4] + else: + i += 1 + + return changed + + def _merge_jumpdests(assembly): # When we have multiple JUMPDESTs in a row, or when a JUMPDEST # is immediately followed by another JUMP, we can skip the @@ -953,6 +978,7 @@ def optimize_assembly(assembly): changed |= _merge_iszero(assembly) changed |= _merge_jumpdests(assembly) changed |= _prune_inefficient_jumps(assembly) + changed |= _optimize_inefficient_jumps(assembly) changed |= _prune_unused_jumpdests(assembly) changed |= _stack_peephole_opts(assembly) From 69fe8db5d70ed2f16db009aa98fb4a7a6aeffc8a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 21 Aug 2023 14:03:12 +0300 Subject: [PATCH 082/471] fix label insert without jump --- vyper/ir/ir_to_bb_pass.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 16d7d38c2b..21653220dc 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -264,12 +264,16 @@ def _convert_ir_basicblock( arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) current_bb = ctx.get_basic_block() - #new_var = ctx.append_instruction("iszero", [arg_0]) + # new_var = ctx.append_instruction("iszero", [arg_0]) inst = IRInstruction("assert", [arg_0]) current_bb.append_instruction(inst) elif ir.value == "label": - bb = IRBasicBlock(IRLabel(ir.args[0].value, True), ctx) + label = IRLabel(ir.args[0].value, True) + if ctx.get_basic_block().is_terminated is False: + inst = IRInstruction("jmp", [label]) + ctx.get_basic_block().append_instruction(inst) + bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2], symbols) elif ir.value == "exit_to": From 2256e89f20817b255f110f5e5e35ee406eb2ab00 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 21 Aug 2023 22:18:03 +0300 Subject: [PATCH 083/471] assert iszero issue fix, partial spilling --- vyper/codegen/dfg.py | 24 +++++++++++++++++++++--- vyper/codegen/ir_basicblock.py | 14 ++++++++++++++ vyper/ir/ir_to_bb_pass.py | 15 +++++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 644425daa8..ba7b4022e4 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,4 +1,4 @@ -from vyper.codegen.ir_basicblock import IRInstruction, IRLabel, IROperant +from vyper.codegen.ir_basicblock import IRInstruction, IRLabel, IROperant, IRVariable from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap from vyper.ir.compile_ir import PUSH, optimize_assembly @@ -83,6 +83,11 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: stack_map = StackMap(assembly) convert_ir_to_dfg(ctx) + for bb in ctx.basic_blocks: + for inst in bb.instructions: + if inst.opcode == "ret": + inst.operands[1].mem_type = IRVariable.MemType.MEMORY + for bb in ctx.basic_blocks: for inst in bb.instructions: if inst.opcode != "select": @@ -157,7 +162,7 @@ def _generate_evm_for_instruction_r( stack_map.poke(depth, ret) return - _emit_input_operands(ctx, assembly, operands, stack_map) + _emit_input_operands(ctx, assembly, inst, stack_map) for op in operands: # final_stack_depth = -(len(operands) - i - 1) @@ -251,13 +256,26 @@ def _generate_evm_for_instruction_r( else: raise Exception(f"Unknown opcode: {opcode}") + if inst.ret is not None: + assert isinstance(inst.ret, IRVariable), "Return value must be a variable" + if inst.ret.mem_type == IRVariable.MemType.MEMORY: + assembly.extend([*PUSH(inst.ret.mem_addr)]) + assembly.append("MSTORE") + def _emit_input_operands( - ctx: IRFunction, assembly: list, ops: list[IROperant], stack_map: StackMap + ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap ) -> None: + ops = inst.get_input_operands() for op in ops: if op.is_literal: assembly.extend([*PUSH(op.value)]) stack_map.push(op) continue _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) + if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: + if inst.get_input_operant_access(ops.index(op)) == 1: + assembly.extend([*PUSH(op.mem_addr)]) + else: + assembly.extend([*PUSH(op.mem_addr)]) + assembly.append("MLOAD") diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index dcc056524f..814f1ed5a9 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,4 +1,5 @@ from typing import TYPE_CHECKING, Optional +from enum import Enum TERMINAL_IR_INSTRUCTIONS = ["ret", "revert", "assert"] TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "revert"] @@ -67,6 +68,10 @@ class IRVariable(IROperant): IRVariable represents a variable in IR. A variable is a string that starts with a %. """ + MemType = Enum("MemType", ["OPERAND_STACK", "MEMORY"]) + mem_type: MemType = MemType.OPERAND_STACK + mem_addr: int = -1 + def __init__(self, value: IROperantValue) -> None: super().__init__(value) @@ -93,6 +98,8 @@ class IRInstruction: opcode: str operands: list[IROperant] + # TODO: Refactor this into an IROperantAccess class 0: value, 1: address (memory) + operand_access: list[int] ret: Optional[str] dbg: Optional[IRDebugInfo] liveness: set[IRVariable] @@ -104,6 +111,7 @@ def __init__( ): self.opcode = opcode self.operands = operands + self.operand_access = [0] * len(operands) self.ret = ret self.dbg = dbg self.liveness = set() @@ -122,6 +130,12 @@ def get_input_operands(self) -> list[IROperant]: """ return [op for op in self.operands if isinstance(op, IRLabel) is False] + def get_input_operant_access(self, index: int) -> int: + """ + Get input operant access in instruction. + """ + return self.operand_access[index] if len(self.operand_access) > index else 0 + def get_input_variables(self) -> list[IROperant]: """ Get all input variables in instruction. diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 8a6e98b7fd..c1b4486c3f 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,7 +1,14 @@ from typing import Optional, Union from vyper.codegen.dfg import generate_evm -from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction, IRLabel, IRLiteral, IROperant +from vyper.codegen.ir_basicblock import ( + IRBasicBlock, + IRInstruction, + IRLabel, + IRLiteral, + IROperant, + IRVariable, +) from vyper.codegen.ir_function import IRFunction from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel @@ -152,7 +159,8 @@ def _convert_ir_basicblock( elif ir.value in MAPPED_IR_INSTRUCTIONS.keys(): ir.value = MAPPED_IR_INSTRUCTIONS[ir.value] - return _convert_binary_op(ctx, ir, symbols) + new_var = _convert_binary_op(ctx, ir, symbols) + return ctx.append_instruction("iszero", [new_var]) elif ir.value in ["iszero", "ceil32", "calldataload"]: return _convert_ir_simple_node(ctx, ir, symbols) @@ -296,7 +304,10 @@ def _convert_ir_basicblock( ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols) new_var = symbols.get(f"&{ret_ir.value}", ret_ir) + new_var.mem_type = IRVariable.MemType.MEMORY + new_var.mem_addr = ret_ir.value inst = IRInstruction("ret", [last_ir, new_var]) + inst.operand_access[1] = 1 ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) From 62fe82c9dd4fb6f20c64ac248bf4e102e3c3343a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 22 Aug 2023 10:58:26 +0300 Subject: [PATCH 084/471] temp --- vyper/codegen/dfg.py | 8 ++-- vyper/codegen/ir_basicblock.py | 84 ++++++++++++++++++++++++---------- vyper/ir/bb_optimizer.py | 4 +- vyper/ir/ir_to_bb_pass.py | 6 +-- 4 files changed, 70 insertions(+), 32 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index ba7b4022e4..72f0557084 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -66,7 +66,7 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: res = inst.get_output_operands() for op in operands: - op.use_count += 1 + op.target.use_count += 1 dfg_inputs[op.value] = ( [inst] if dfg_inputs.get(op.value) is None else dfg_inputs[op.value] + [inst] ) @@ -86,7 +86,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: for bb in ctx.basic_blocks: for inst in bb.instructions: if inst.opcode == "ret": - inst.operands[1].mem_type = IRVariable.MemType.MEMORY + inst.operands[1].target.mem_type = IRVariable.MemType.MEMORY for bb in ctx.basic_blocks: for inst in bb.instructions: @@ -257,7 +257,7 @@ def _generate_evm_for_instruction_r( raise Exception(f"Unknown opcode: {opcode}") if inst.ret is not None: - assert isinstance(inst.ret, IRVariable), "Return value must be a variable" + assert inst.ret.is_variable, "Return value must be a variable" if inst.ret.mem_type == IRVariable.MemType.MEMORY: assembly.extend([*PUSH(inst.ret.mem_addr)]) assembly.append("MSTORE") @@ -273,7 +273,7 @@ def _emit_input_operands( stack_map.push(op) continue _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) - if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: + if op.is_variable and op.mem_type == IRVariable.MemType.MEMORY: if inst.get_input_operant_access(ops.index(op)) == 1: assembly.extend([*PUSH(op.mem_addr)]) else: diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 814f1ed5a9..f07ed7cc70 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -26,19 +26,15 @@ def __repr__(self) -> str: return f"\t# line {self.line_no}: {src}".expandtabs(20) -IROperantValue = str | int +IRValueBaseValue = str | int -class IROperant: - """ - IROperant represents an operand in IR. An operand can be a variable, label, or a constant. - """ - - value: str +class IRValueBase: + value: IRValueBaseValue use_count: int = 0 - def __init__(self, value: IROperantValue) -> None: - assert isinstance(value, IROperantValue), "value must be a string" + def __init__(self, value: IRValueBaseValue) -> None: + assert isinstance(value, IRValueBaseValue), "value must be an IRValueBaseValue" self.value = value @property @@ -49,12 +45,12 @@ def __repr__(self) -> str: return str(self.value) -class IRLiteral(IROperant): +class IRLiteral(IRValueBase): """ IRLiteral represents a literal in IR """ - def __init__(self, value: IROperantValue) -> None: + def __init__(self, value: IRValueBaseValue) -> None: super().__init__(value) self.use_count = 1 @@ -63,7 +59,7 @@ def is_literal(self) -> bool: return True -class IRVariable(IROperant): +class IRVariable(IRValueBase): """ IRVariable represents a variable in IR. A variable is a string that starts with a %. """ @@ -72,11 +68,11 @@ class IRVariable(IROperant): mem_type: MemType = MemType.OPERAND_STACK mem_addr: int = -1 - def __init__(self, value: IROperantValue) -> None: + def __init__(self, value: IRValueBaseValue) -> None: super().__init__(value) -class IRLabel(IROperant): +class IRLabel(IRValueBase): """ IRLabel represents a label in IR. A label is a string that starts with a %. """ @@ -88,6 +84,46 @@ def __init__(self, value: str, is_symbol: bool = False) -> None: self.is_symbol = is_symbol +IROperantTarget = IRLiteral | IRVariable | IRLabel + + +class IROperant: + """ + IROperant represents an operand of an IR instuction. An operand can be a variable, label, or a constant. + """ + + target: IRValueBase + address_access: bool = False + use_count: int = 0 + + def __init__(self, target: IRValueBase, address_access: bool = False) -> None: + assert isinstance(target, IRValueBase), "value must be an IRValueBase" + self.address_access = address_access + self.target = target + + def is_targeting(self, target: IRValueBase) -> bool: + return self.target.value == target.value + + @property + def value(self) -> IRValueBaseValue: + return self.target.value + + @property + def is_literal(self) -> bool: + return isinstance(self.target, IRLiteral) + + @property + def is_variable(self) -> bool: + return isinstance(self.target, IRVariable) + + @property + def is_label(self) -> bool: + return isinstance(self.target, IRLabel) + + def __repr__(self) -> str: + return str(self.target) + + class IRInstruction: """ IRInstruction represents an instruction in IR. Each instruction has an opcode, @@ -100,17 +136,21 @@ class IRInstruction: operands: list[IROperant] # TODO: Refactor this into an IROperantAccess class 0: value, 1: address (memory) operand_access: list[int] - ret: Optional[str] + ret: Optional[IROperant] dbg: Optional[IRDebugInfo] liveness: set[IRVariable] parent: Optional["IRBasicBlock"] fen: int def __init__( - self, opcode: str, operands: list[IROperant], ret: str = None, dbg: IRDebugInfo = None + self, + opcode: str, + operands: list[IROperant | IRValueBase], + ret: IROperant = None, + dbg: IRDebugInfo = None, ): self.opcode = opcode - self.operands = operands + self.operands = [op if isinstance(op, IROperant) else IROperant(op) for op in operands] self.operand_access = [0] * len(operands) self.ret = ret self.dbg = dbg @@ -122,13 +162,13 @@ def get_label_operands(self) -> list[IRLabel]: """ Get all labels in instruction. """ - return [op for op in self.operands if isinstance(op, IRLabel)] + return [op for op in self.operands if op.is_label] def get_input_operands(self) -> list[IROperant]: """ Get all input operants in instruction. """ - return [op for op in self.operands if isinstance(op, IRLabel) is False] + return [op for op in self.operands if not op.is_label] def get_input_operant_access(self, index: int) -> int: """ @@ -140,7 +180,7 @@ def get_input_variables(self) -> list[IROperant]: """ Get all input variables in instruction. """ - return [op for op in self.operands if isinstance(op, IRVariable)] + return [op for op in self.operands if op.is_variable] def get_output_operands(self) -> list[IROperant]: return [self.ret] if self.ret else [] @@ -157,9 +197,7 @@ def __repr__(self) -> str: if self.ret: s += f"{self.ret} = " s += f"{self.opcode} " - operands = ", ".join( - [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in self.operands] - ) + operands = ", ".join([(f"label %{op}" if op.is_label else str(op)) for op in self.operands]) s += operands if self.dbg: diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index ce26b7656a..db2a4d4381 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -70,8 +70,8 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> None: for bb2 in ctx.basic_blocks: for inst in bb2.instructions: for op in inst.operands: - if isinstance(op, IRLabel) and op == replaced_label: - op.value = replacement_label.value + if op.is_label and op.value == replaced_label.value: + op.target = replacement_label ctx.basic_blocks.remove(bb) i -= 1 diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index c1b4486c3f..20c9eb02d9 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -6,7 +6,7 @@ IRInstruction, IRLabel, IRLiteral, - IROperant, + IRValueBase, IRVariable, ) from vyper.codegen.ir_function import IRFunction @@ -38,7 +38,7 @@ MAPPED_IR_INSTRUCTIONS = {"le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} -SymbolTable = dict[str, IROperant] +SymbolTable = dict[str, IRValueBase] def _get_symbols_common(a: dict, b: dict) -> dict: @@ -68,7 +68,7 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No def _convert_binary_op( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, swap: bool = False -) -> str: +) -> IRVariable: ir_args = ir.args[::-1] if swap else ir.args arg_0 = _convert_ir_basicblock(ctx, ir_args[0], symbols) arg_1 = _convert_ir_basicblock(ctx, ir_args[1], symbols) From 918123c46b4ab3d75f1581d9ef80ac5d6b88693d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 22 Aug 2023 11:08:30 +0300 Subject: [PATCH 085/471] fix liveness with after refactor --- vyper/codegen/dfg.py | 18 +++++++++--------- vyper/codegen/ir_basicblock.py | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 72f0557084..6e1471b69a 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -62,13 +62,13 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: dfg_outputs = {} for bb in ctx.basic_blocks: for inst in bb.instructions: - operands = inst.get_input_variables() + variables = inst.get_input_variables() res = inst.get_output_operands() - for op in operands: - op.target.use_count += 1 - dfg_inputs[op.value] = ( - [inst] if dfg_inputs.get(op.value) is None else dfg_inputs[op.value] + [inst] + for v in variables: + v.use_count += 1 + dfg_inputs[v.value] = ( + [inst] if dfg_inputs.get(v.value) is None else dfg_inputs[v.value] + [inst] ) for op in res: @@ -259,7 +259,7 @@ def _generate_evm_for_instruction_r( if inst.ret is not None: assert inst.ret.is_variable, "Return value must be a variable" if inst.ret.mem_type == IRVariable.MemType.MEMORY: - assembly.extend([*PUSH(inst.ret.mem_addr)]) + assembly.extend([*PUSH(inst.ret.target.mem_addr)]) assembly.append("MSTORE") @@ -273,9 +273,9 @@ def _emit_input_operands( stack_map.push(op) continue _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) - if op.is_variable and op.mem_type == IRVariable.MemType.MEMORY: + if op.is_variable and op.target.mem_type == IRVariable.MemType.MEMORY: if inst.get_input_operant_access(ops.index(op)) == 1: - assembly.extend([*PUSH(op.mem_addr)]) + assembly.extend([*PUSH(op.target.mem_addr)]) else: - assembly.extend([*PUSH(op.mem_addr)]) + assembly.extend([*PUSH(op.target.mem_addr)]) assembly.append("MLOAD") diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index f07ed7cc70..a42384f0ed 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -176,11 +176,11 @@ def get_input_operant_access(self, index: int) -> int: """ return self.operand_access[index] if len(self.operand_access) > index else 0 - def get_input_variables(self) -> list[IROperant]: + def get_input_variables(self) -> list[IRVariable]: """ Get all input variables in instruction. """ - return [op for op in self.operands if op.is_variable] + return [op.target for op in self.operands if op.is_variable] def get_output_operands(self) -> list[IROperant]: return [self.ret] if self.ret else [] @@ -274,14 +274,14 @@ def in_vars_for(self, bb: "IRBasicBlock" = None) -> set[IRVariable]: if bb: for inst in self.instructions: if inst.opcode == "select": - if inst.operands[0] == bb.label: - liveness.add(inst.operands[1]) - if inst.operands[3] in liveness: - liveness.remove(inst.operands[3]) - if inst.operands[2] == bb.label: - liveness.add(inst.operands[3]) - if inst.operands[1] in liveness: - liveness.remove(inst.operands[1]) + if inst.operands[0].target == bb.label: + liveness.add(inst.operands[1].target) + if inst.operands[3].target in liveness: + liveness.remove(inst.operands[3].target) + if inst.operands[2].target == bb.label: + liveness.add(inst.operands[3].target) + if inst.operands[1].target in liveness: + liveness.remove(inst.operands[1].target) return liveness From cfb77fa7beef387afc330c0bdde3f5b47b469931 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 22 Aug 2023 11:34:23 +0300 Subject: [PATCH 086/471] more ret --- vyper/codegen/dfg.py | 8 ++++---- vyper/codegen/ir_basicblock.py | 4 ++-- vyper/ir/bb_optimizer.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 6e1471b69a..0decf783d5 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -157,9 +157,9 @@ def _generate_evm_for_instruction_r( to_be_replaced = stack_map.peek(depth) if to_be_replaced.use_count > 1: to_be_replaced.use_count -= 1 - stack_map.push(ret) + stack_map.push(ret.target) else: - stack_map.poke(depth, ret) + stack_map.poke(depth, ret.target) return _emit_input_operands(ctx, assembly, inst, stack_map) @@ -194,7 +194,7 @@ def _generate_evm_for_instruction_r( stack_map.pop(len(operands)) if inst.ret is not None: - stack_map.push(inst.ret) + stack_map.push(inst.ret.target) if opcode in ONE_TO_ONE_INSTRUCTIONS: assembly.append(opcode.upper()) @@ -258,7 +258,7 @@ def _generate_evm_for_instruction_r( if inst.ret is not None: assert inst.ret.is_variable, "Return value must be a variable" - if inst.ret.mem_type == IRVariable.MemType.MEMORY: + if inst.ret.target.mem_type == IRVariable.MemType.MEMORY: assembly.extend([*PUSH(inst.ret.target.mem_addr)]) assembly.append("MSTORE") diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index a42384f0ed..d0666b2ecb 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -152,7 +152,7 @@ def __init__( self.opcode = opcode self.operands = [op if isinstance(op, IROperant) else IROperant(op) for op in operands] self.operand_access = [0] * len(operands) - self.ret = ret + self.ret = ret if isinstance(ret, IROperant) else IROperant(ret) if ret else None self.dbg = dbg self.liveness = set() self.parent = None @@ -318,7 +318,7 @@ def calculate_liveness(self) -> None: for instruction in self.instructions[::-1]: liveness = liveness.union(instruction.get_input_variables()) out = ( - instruction.get_output_operands()[0] + instruction.get_output_operands()[0].target if len(instruction.get_output_operands()) > 0 else None ) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index db2a4d4381..f2fcb4a88e 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -36,7 +36,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: for i, inst in enumerate(bb.instructions[:-1]): if inst.opcode in ["call", "sload", "sstore"]: continue - if inst.ret and inst.ret not in bb.instructions[i + 1].liveness: + if inst.ret and inst.ret.target not in bb.instructions[i + 1].liveness: removeList.append(inst) count += 1 From fdb798d3b207a22542bf21827c353522fa4b0f2d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 22 Aug 2023 12:05:01 +0300 Subject: [PATCH 087/471] more indirection --- vyper/codegen/dfg.py | 16 ++++++++-------- vyper/codegen/ir_basicblock.py | 2 +- vyper/compiler/utils.py | 22 ++++++++++++++-------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 0decf783d5..8c7eff9088 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -167,21 +167,21 @@ def _generate_evm_for_instruction_r( for op in operands: # final_stack_depth = -(len(operands) - i - 1) ucc = inst.get_use_count_correction(op) - assert op.use_count >= ucc, "Operand used up" - depth = stack_map.get_depth_in(op) + assert op.target.use_count >= ucc, "Operand used up" + depth = stack_map.get_depth_in(op.target) assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" - needs_copy = op.use_count - ucc > 1 + needs_copy = op.target.use_count - ucc > 1 if needs_copy: stack_map.dup(depth) - op.use_count -= 1 + op.target.use_count -= 1 for i in range(len(operands)): op = operands[i] final_stack_depth = -(len(operands) - i - 1) - depth = stack_map.get_depth_in(op) + depth = stack_map.get_depth_in(op.target) assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" - in_place_op = stack_map.peek(-final_stack_depth) - is_in_place = in_place_op.value == op.value + in_place_var = stack_map.peek(-final_stack_depth) + is_in_place = in_place_var.value == op.target.value if not is_in_place: if final_stack_depth == 0 and depth != 0: @@ -270,7 +270,7 @@ def _emit_input_operands( for op in ops: if op.is_literal: assembly.extend([*PUSH(op.value)]) - stack_map.push(op) + stack_map.push(op.target) continue _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) if op.is_variable and op.target.mem_type == IRVariable.MemType.MEMORY: diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index d0666b2ecb..88e19e5046 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -188,7 +188,7 @@ def get_output_operands(self) -> list[IROperant]: def get_use_count_correction(self, op: IROperant) -> int: use_count_correction = 0 for _, phi in self.parent.phi_vars.items(): - if phi.value == op.value: + if phi.value == op.target.value: use_count_correction += 1 return use_count_correction diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 605acd9c3f..a4636a6497 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -1,6 +1,6 @@ from typing import Dict -from vyper.codegen.ir_basicblock import IROperant +from vyper.codegen.ir_basicblock import IROperant, IRValueBase from vyper.semantics.types.function import ContractFunctionT @@ -50,7 +50,7 @@ def _expand_row(row): class StackMap: NOT_IN_STACK = 1 - stack_map: list[str] + stack_map: list[IRValueBase] assembly: list[str] def __init__(self, assembly: list[str]): @@ -63,39 +63,45 @@ def get_height(self) -> int: """ return len(self.stack_map) - def push(self, op: IROperant) -> None: + def push(self, op: IRValueBase) -> None: """ Pushes an operand onto the stack map. """ + assert isinstance(op, IRValueBase), f"push takes IRValueBase, got '{op}'" self.stack_map.append(op) def pop(self, num: int = 1) -> None: del self.stack_map[len(self.stack_map) - num :] - def get_depth_in(self, op: IROperant | list[IROperant]) -> int: + def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ + assert isinstance(op, IRValueBase) or isinstance( + op, list + ), f"get_depth_in takes IRValueBase or list, got '{op}'" for i, stack_op in enumerate(self.stack_map[::-1]): - if isinstance(stack_op, IROperant): - if isinstance(op, IROperant) and stack_op.value == op.value: + if isinstance(stack_op, IRValueBase): + if isinstance(op, IRValueBase) and stack_op.value == op.value: return -i elif isinstance(op, list) and stack_op in op: return -i return StackMap.NOT_IN_STACK - def peek(self, depth: int) -> IROperant: + def peek(self, depth: int) -> IRValueBase: """ Returns the top of the stack map. """ return self.stack_map[-depth - 1] - def poke(self, depth: int, op: IROperant) -> None: + def poke(self, depth: int, op: IRValueBase) -> None: """ Pokes an operand at the given depth in the stack map. """ + assert depth < 0, "Bad depth" + assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" self.stack_map[-depth - 1] = op def dup(self, depth: int) -> None: From 1623334e127076651d2c90533723f00bfe50b8a6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 22 Aug 2023 12:29:14 +0300 Subject: [PATCH 088/471] complete works again --- vyper/codegen/dfg.py | 10 +++++----- vyper/codegen/ir_basicblock.py | 35 +++++++++++++--------------------- vyper/codegen/ir_function.py | 11 +++++++++-- vyper/compiler/utils.py | 2 +- vyper/ir/ir_to_bb_pass.py | 9 +++++---- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 8c7eff9088..57e91c77a6 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,4 +1,4 @@ -from vyper.codegen.ir_basicblock import IRInstruction, IRLabel, IROperant, IRVariable +from vyper.codegen.ir_basicblock import IRInstruction, IRLabel, IROperand, IRVariable from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap from vyper.ir.compile_ir import PUSH, optimize_assembly @@ -37,15 +37,15 @@ "log4", ] -OPERANT_ORDER_IRELEVANT_INSTRUCTIONS = ["xor", "or", "add", "mul", "eq"] +OPERAND_ORDER_IRELEVANT_INSTRUCTIONS = ["xor", "or", "add", "mul", "eq"] class DFGNode: - value: IRInstruction | IROperant + value: IRInstruction | IROperand predecessors: list["DFGNode"] successors: list["DFGNode"] - def __init__(self, value: IRInstruction | IROperant): + def __init__(self, value: IRInstruction | IROperand): self.value = value self.predecessors = [] self.successors = [] @@ -274,7 +274,7 @@ def _emit_input_operands( continue _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) if op.is_variable and op.target.mem_type == IRVariable.MemType.MEMORY: - if inst.get_input_operant_access(ops.index(op)) == 1: + if op.address_access: assembly.extend([*PUSH(op.target.mem_addr)]) else: assembly.extend([*PUSH(op.target.mem_addr)]) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 88e19e5046..98005840c7 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -84,12 +84,12 @@ def __init__(self, value: str, is_symbol: bool = False) -> None: self.is_symbol = is_symbol -IROperantTarget = IRLiteral | IRVariable | IRLabel +IROperandTarget = IRLiteral | IRVariable | IRLabel -class IROperant: +class IROperand: """ - IROperant represents an operand of an IR instuction. An operand can be a variable, label, or a constant. + IROperand represents an operand of an IR instuction. An operand can be a variable, label, or a constant. """ target: IRValueBase @@ -133,10 +133,8 @@ class IRInstruction: """ opcode: str - operands: list[IROperant] - # TODO: Refactor this into an IROperantAccess class 0: value, 1: address (memory) - operand_access: list[int] - ret: Optional[IROperant] + operands: list[IROperand] + ret: Optional[IROperand] dbg: Optional[IRDebugInfo] liveness: set[IRVariable] parent: Optional["IRBasicBlock"] @@ -145,14 +143,13 @@ class IRInstruction: def __init__( self, opcode: str, - operands: list[IROperant | IRValueBase], - ret: IROperant = None, + operands: list[IROperand | IRValueBase], + ret: IROperand = None, dbg: IRDebugInfo = None, ): self.opcode = opcode - self.operands = [op if isinstance(op, IROperant) else IROperant(op) for op in operands] - self.operand_access = [0] * len(operands) - self.ret = ret if isinstance(ret, IROperant) else IROperant(ret) if ret else None + self.operands = [op if isinstance(op, IROperand) else IROperand(op) for op in operands] + self.ret = ret if isinstance(ret, IROperand) else IROperand(ret) if ret else None self.dbg = dbg self.liveness = set() self.parent = None @@ -164,28 +161,22 @@ def get_label_operands(self) -> list[IRLabel]: """ return [op for op in self.operands if op.is_label] - def get_input_operands(self) -> list[IROperant]: + def get_input_operands(self) -> list[IROperand]: """ - Get all input operants in instruction. + Get all input operands in instruction. """ return [op for op in self.operands if not op.is_label] - def get_input_operant_access(self, index: int) -> int: - """ - Get input operant access in instruction. - """ - return self.operand_access[index] if len(self.operand_access) > index else 0 - def get_input_variables(self) -> list[IRVariable]: """ Get all input variables in instruction. """ return [op.target for op in self.operands if op.is_variable] - def get_output_operands(self) -> list[IROperant]: + def get_output_operands(self) -> list[IROperand]: return [self.ret] if self.ret else [] - def get_use_count_correction(self, op: IROperant) -> int: + def get_use_count_correction(self, op: IROperand) -> int: use_count_correction = 0 for _, phi in self.parent.phi_vars.items(): if phi.value == op.target.value: diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 0bd72c4c48..2346d3e6ae 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -1,7 +1,14 @@ from symtable import SymbolTable from typing import Optional -from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction, IRLabel, IRVariable, IROperant +from vyper.codegen.ir_basicblock import ( + IRBasicBlock, + IRInstruction, + IRValueBase, + IRLabel, + IRVariable, + IROperand, +) class IRFunctionBase: @@ -100,7 +107,7 @@ def remove_unreachable_blocks(self) -> int: self.basic_blocks = new_basic_blocks return removed - def append_instruction(self, opcode: str, args: list[IROperant]): + def append_instruction(self, opcode: str, args: list[IROperand | IRValueBase]): """ Append instruction to last basic block. """ diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index a4636a6497..b18515ae77 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -1,6 +1,6 @@ from typing import Dict -from vyper.codegen.ir_basicblock import IROperant, IRValueBase +from vyper.codegen.ir_basicblock import IRValueBase from vyper.semantics.types.function import ContractFunctionT diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 20c9eb02d9..65f2209a89 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -8,6 +8,7 @@ IRLiteral, IRValueBase, IRVariable, + IROperand, ) from vyper.codegen.ir_function import IRFunction from vyper.codegen.ir_node import IRnode @@ -137,7 +138,7 @@ def _convert_ir_simple_node( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, -) -> Optional[Union[str, int]]: +) -> IRVariable: args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] return ctx.append_instruction(ir.value, args) @@ -150,7 +151,7 @@ def _convert_ir_basicblock( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, -) -> Optional[Union[str, int]]: +) -> Optional[IRVariable]: global _break_target, _continue_target # symbols = symbols.copy() @@ -306,8 +307,8 @@ def _convert_ir_basicblock( new_var = symbols.get(f"&{ret_ir.value}", ret_ir) new_var.mem_type = IRVariable.MemType.MEMORY new_var.mem_addr = ret_ir.value - inst = IRInstruction("ret", [last_ir, new_var]) - inst.operand_access[1] = 1 + new_op = IROperand(new_var, True) + inst = IRInstruction("ret", [last_ir, new_op]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) From a40d2b4a0e50c45c5b40e991cbe30c4f18e3293b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 22 Aug 2023 13:23:30 +0300 Subject: [PATCH 089/471] remove dead code, update to new operator --- vyper/codegen/dfg.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 57e91c77a6..fefb95def9 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -83,11 +83,6 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: stack_map = StackMap(assembly) convert_ir_to_dfg(ctx) - for bb in ctx.basic_blocks: - for inst in bb.instructions: - if inst.opcode == "ret": - inst.operands[1].target.mem_type = IRVariable.MemType.MEMORY - for bb in ctx.basic_blocks: for inst in bb.instructions: if inst.opcode != "select": @@ -214,7 +209,7 @@ def _generate_evm_for_instruction_r( assembly.append("LT") elif opcode == "call": target = inst.operands[0] - if type(target) is IRLabel: + if target.is_label: assembly.extend( [ f"_sym_label_ret_{label_counter}", From 088c0f93d5c69a7274377107fbb9408dabab8bf0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 22 Aug 2023 14:14:45 +0300 Subject: [PATCH 090/471] fix bad assert --- vyper/compiler/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index b18515ae77..4a8776c5a1 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -100,7 +100,7 @@ def poke(self, depth: int, op: IRValueBase) -> None: """ Pokes an operand at the given depth in the stack map. """ - assert depth < 0, "Bad depth" + assert depth <= 0, "Bad depth" assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" self.stack_map[-depth - 1] = op From e15d8117c06e879de72b1f300deb72b951329df9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 22 Aug 2023 16:12:29 +0300 Subject: [PATCH 091/471] decorate ptr access --- vyper/codegen/ir_basicblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 98005840c7..92eda7c234 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -121,7 +121,7 @@ def is_label(self) -> bool: return isinstance(self.target, IRLabel) def __repr__(self) -> str: - return str(self.target) + return f"{'ptr ' if self.address_access else ''}{self.target}" class IRInstruction: From 08094d7b283c4e79e78114d20e106a6a4392640c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 22 Aug 2023 16:12:47 +0300 Subject: [PATCH 092/471] arbitrary jumps --- vyper/codegen/dfg.py | 7 +++++-- vyper/ir/ir_to_bb_pass.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index fefb95def9..48130dff38 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -201,8 +201,11 @@ def _generate_evm_for_instruction_r( assembly.append(f"_sym_{inst.operands[1].value}") assembly.append("JUMPI") elif opcode == "jmp": - assembly.append(f"_sym_{inst.operands[0].value}") - assembly.append("JUMP") + if inst.operands[0].is_label: + assembly.append(f"_sym_{inst.operands[0].value}") + assembly.append("JUMP") + else: + assembly.append("JUMP") elif opcode == "gt": assembly.append("GT") elif opcode == "lt": diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 65f2209a89..c638ea17c2 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -254,6 +254,11 @@ def _convert_ir_basicblock( return _convert_ir_basicblock(ctx, ir.args[2], symbols) # body elif ir.value == "goto": return _append_jmp(ctx, IRLabel(ir.args[0].value)) + elif ir.value == "jump": + arg_1 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + inst = IRInstruction("jmp", [arg_1]) + ctx.get_basic_block().append_instruction(inst) + _new_block(ctx) elif ir.value == "set": sym = ir.args[0] arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) @@ -269,6 +274,18 @@ def _convert_ir_basicblock( symbols[f"&{arg_0.value}"] = new_var return new_var + elif ir.value == "codecopy": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + size = _convert_ir_basicblock(ctx, ir.args[2], symbols) + + new_var = ctx.append_instruction("codecopy", [arg_1, size]) + + symbols[f"&{arg_0.value}"] = new_var + elif ir.value == "symbol": + return IRLabel(ir.args[0].value) + elif ir.value == "data": + pass elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) current_bb = ctx.get_basic_block() @@ -327,6 +344,10 @@ def _convert_ir_basicblock( if new_var is None: new_var = ctx.get_next_variable() symbols[f"&{sym.value}"] = new_var + v = _convert_ir_basicblock(ctx, sym, symbols) + op = IROperand(v, True) + inst = IRInstruction("load", [op], new_var) + ctx.get_basic_block().append_instruction(inst) return new_var else: new_var = _convert_ir_basicblock(ctx, sym, symbols) From f71d88f3de9c824a3e7bf6b3733382750698365d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 27 Aug 2023 13:39:57 +0300 Subject: [PATCH 093/471] rename call to invoke for internal calls --- vyper/codegen/dfg.py | 6 +++--- vyper/ir/ir_to_bb_pass.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 48130dff38..98a9b0db56 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -210,7 +210,7 @@ def _generate_evm_for_instruction_r( assembly.append("GT") elif opcode == "lt": assembly.append("LT") - elif opcode == "call": + elif opcode == "invoke": target = inst.operands[0] if target.is_label: assembly.extend( @@ -223,8 +223,8 @@ def _generate_evm_for_instruction_r( ] ) label_counter += 1 - else: - assembly.append("CALL") + elif opcode == "call": + assembly.append("CALL") elif opcode == "ret": if len(inst.operands) == 1: assembly.append("SWAP1") diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index c638ea17c2..82739ebbe2 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -111,7 +111,7 @@ def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> None ret = _convert_ir_basicblock(ctx, arg, symbols) ret_values.append(ret) - return ctx.append_instruction("call", ret_values) + return ctx.append_instruction("invoke", ret_values) def _handle_internal_func( From 362d0e2fcd8a84d29dcad5d5d5dd699f89d15447 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 27 Aug 2023 13:45:35 +0300 Subject: [PATCH 094/471] invert IR assert logic --- vyper/codegen/dfg.py | 2 +- vyper/ir/ir_to_bb_pass.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 98a9b0db56..1bc3e6a725 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -250,7 +250,7 @@ def _generate_evm_for_instruction_r( elif opcode == "ceil32": assembly.extend([*PUSH(31), "ADD", *PUSH(31), "NOT", "AND"]) elif opcode == "assert": - assembly.extend(["_sym___revert", "JUMPI"]) + assembly.extend(["ISZERO", "_sym___revert", "JUMPI"]) else: raise Exception(f"Unknown opcode: {opcode}") diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 82739ebbe2..1b5cdf199c 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -289,10 +289,7 @@ def _convert_ir_basicblock( elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) current_bb = ctx.get_basic_block() - - new_var = ctx.append_instruction("iszero", [arg_0]) - - inst = IRInstruction("assert", [new_var]) + inst = IRInstruction("assert", [arg_0]) current_bb.append_instruction(inst) elif ir.value == "label": label = IRLabel(ir.args[0].value, True) From 83ae79198ec0ab36439b9b1d34467124a65c0a77 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 28 Aug 2023 13:21:17 +0300 Subject: [PATCH 095/471] invoke, return --- vyper/codegen/dfg.py | 31 +++++++++++++++---------------- vyper/ir/ir_to_bb_pass.py | 2 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 1bc3e6a725..c66ce2c2c2 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -212,25 +212,24 @@ def _generate_evm_for_instruction_r( assembly.append("LT") elif opcode == "invoke": target = inst.operands[0] - if target.is_label: - assembly.extend( - [ - f"_sym_label_ret_{label_counter}", - f"_sym_{target.value}", - "JUMP", - f"_sym_label_ret_{label_counter}", - "JUMPDEST", - ] - ) - label_counter += 1 + assert target.is_label, "invoke target must be a label" + assembly.extend( + [ + f"_sym_label_ret_{label_counter}", + f"_sym_{target.value}", + "JUMP", + f"_sym_label_ret_{label_counter}", + "JUMPDEST", + ] + ) elif opcode == "call": assembly.append("CALL") elif opcode == "ret": - if len(inst.operands) == 1: - assembly.append("SWAP1") - assembly.append("JUMP") - else: - assembly.append("RETURN") + assert len(inst.operands) == 1, "ret instruction takes one operand" + assembly.append("SWAP1") + assembly.append("JUMP") + elif opcode == "return": + assembly.append("RETURN") elif opcode == "select": pass elif opcode == "sha3": diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 1b5cdf199c..3a24616142 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -322,7 +322,7 @@ def _convert_ir_basicblock( new_var.mem_type = IRVariable.MemType.MEMORY new_var.mem_addr = ret_ir.value new_op = IROperand(new_var, True) - inst = IRInstruction("ret", [last_ir, new_op]) + inst = IRInstruction("return", [last_ir, new_op]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) From 82459764b3e6018ede11991350521702a13c95ca Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 28 Aug 2023 14:20:28 +0300 Subject: [PATCH 096/471] gas, returndatasize, returndatacopy --- vyper/codegen/dfg.py | 2 +- vyper/ir/bb_optimizer.py | 4 ++-- vyper/ir/ir_to_bb_pass.py | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index c66ce2c2c2..42059e029b 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -104,7 +104,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: fen = 0 for inst in bb.instructions: inst.fen = fen - if inst.opcode in ["call", "sload", "sstore", "assert"]: + if inst.opcode in ["call", "invoke", "sload", "sstore", "assert"]: fen += 1 for inst in bb.instructions: diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index f2fcb4a88e..5fa5b32950 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -34,7 +34,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: removeList = [] for bb in ctx.basic_blocks: for i, inst in enumerate(bb.instructions[:-1]): - if inst.opcode in ["call", "sload", "sstore"]: + if inst.opcode in ["call", "invoke", "sload", "sstore"]: continue if inst.ret and inst.ret.target not in bb.instructions[i + 1].liveness: removeList.append(inst) @@ -92,7 +92,7 @@ def _calculate_in_set(ctx: IRFunction) -> None: ), "Last instruction should be a terminator" + str(bb) for inst in bb.instructions: - if inst.opcode in ["jmp", "jnz", "call"]: + if inst.opcode in ["jmp", "jnz", "call", "invoke"]: ops = inst.get_label_operands() for op in ops: ctx.get_basic_block(op.value).add_in(bb) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 3a24616142..c9eec46136 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -458,6 +458,20 @@ def _convert_ir_basicblock( inst = IRInstruction("jmp", [_continue_target.label]) 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", []) + elif ir.value == "returndatasize": + return ctx.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) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + size = _convert_ir_basicblock(ctx, ir.args[2], symbols) + + new_var = ctx.append_instruction("returndatacopy", [arg_1, size]) + + symbols[f"&{arg_0.value}"] = new_var + return new_var elif isinstance(ir.value, str) and ir.value.startswith("log"): # count = int(ir.value[3:]) args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] From 64fcc945ab7db264ffa41642fcb83c83c6121ecb Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 28 Aug 2023 14:21:41 +0300 Subject: [PATCH 097/471] emit asm --- vyper/codegen/dfg.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 42059e029b..9167a036cd 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -9,6 +9,9 @@ "calldatasize", "calldatacopy", "calldataload", + "gas", + "returndatasize", + "returndatacopy", "callvalue", "selfbalance", "sload", From 19cd53b38290e8a2aa264494bf0b7612e9b02f97 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 10:22:15 +0300 Subject: [PATCH 098/471] IRVariable constructor update, IROperant support address offset --- vyper/codegen/ir_basicblock.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 92eda7c234..a3b95cf9a1 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -68,8 +68,12 @@ class IRVariable(IRValueBase): mem_type: MemType = MemType.OPERAND_STACK mem_addr: int = -1 - def __init__(self, value: IRValueBaseValue) -> None: + def __init__( + self, value: IRValueBaseValue, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: int = -1 + ) -> None: super().__init__(value) + self.mem_type = mem_type + self.mem_addr = mem_addr class IRLabel(IRValueBase): @@ -94,11 +98,15 @@ class IROperand: target: IRValueBase address_access: bool = False + address_offset: int = 0 use_count: int = 0 - def __init__(self, target: IRValueBase, address_access: bool = False) -> None: + def __init__( + self, target: IRValueBase, address_access: bool = False, address_offset: int = 0 + ) -> None: assert isinstance(target, IRValueBase), "value must be an IRValueBase" self.address_access = address_access + self.address_offset = address_offset self.target = target def is_targeting(self, target: IRValueBase) -> bool: @@ -121,7 +129,8 @@ def is_label(self) -> bool: return isinstance(self.target, IRLabel) def __repr__(self) -> str: - return f"{'ptr ' if self.address_access else ''}{self.target}" + offsetStr = f"{self.address_offset:+}" if self.address_offset else "" + return f"{'ptr ' if self.address_access else ''}{self.target}{offsetStr}" class IRInstruction: From c12a8b5498ae0b85d2d821cd0f3c501533fac604 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 10:22:45 +0300 Subject: [PATCH 099/471] create variable directly inmemory --- vyper/codegen/ir_function.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 2346d3e6ae..193bbcdbb0 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -87,9 +87,11 @@ def get_next_label(self) -> IRLabel: self.last_label += 1 return IRLabel(f"{self.last_label}") - def get_next_variable(self) -> IRVariable: + def get_next_variable( + self, mem_type: IRVariable.MemType = IRVariable.MemType.OPERAND_STACK, mem_addr: int = -1 + ) -> IRVariable: self.last_variable += 1 - return IRVariable(f"%{self.last_variable}") + return IRVariable(f"%{self.last_variable}", mem_type, mem_addr) def get_last_variable(self) -> str: return f"%{self.last_variable}" From 85f05d51c05df981a64059dbffa10f57944e258b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 10:22:56 +0300 Subject: [PATCH 100/471] call translator --- vyper/ir/ir_to_bb_pass.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index c9eec46136..5894cef231 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -188,11 +188,27 @@ def _convert_ir_basicblock( return ret elif ir.value == "call": # external call - args = [] - for arg in ir.args: - args.append(_convert_ir_basicblock(ctx, arg, symbols)) - - return ctx.append_instruction("call", args) + gas = _convert_ir_basicblock(ctx, ir.args[0], symbols) + address = _convert_ir_basicblock(ctx, ir.args[1], symbols) + value = _convert_ir_basicblock(ctx, ir.args[2], symbols) + argsOffset = _convert_ir_basicblock(ctx, ir.args[3], symbols) + argsSize = _convert_ir_basicblock(ctx, ir.args[4], symbols) + retOffset = _convert_ir_basicblock(ctx, ir.args[5], symbols) + retSize = _convert_ir_basicblock(ctx, ir.args[6], symbols) + + if argsOffset.is_literal: + addr = argsOffset.value - 32 + 4 + argsOffsetVar = symbols.get(f"&{addr}", argsOffset.value) + argsOffsetOp = IROperand(argsOffsetVar, True, +4) + + retVar = ctx.get_next_variable(IRVariable.MemType.MEMORY, retOffset.value) + symbols[f"&{retOffset.value}"] = retVar + + inst = IRInstruction( + "call", [gas, address, value, argsOffsetOp, argsSize, retOffset, retSize], retVar + ) + ctx.get_basic_block().append_instruction(inst) + return retVar elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() From e8b5764d30044daebdc30cea4c8c0284d85554b7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 10:30:54 +0300 Subject: [PATCH 101/471] fix order --- vyper/ir/ir_to_bb_pass.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 5894cef231..649cf0763b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -199,13 +199,13 @@ def _convert_ir_basicblock( if argsOffset.is_literal: addr = argsOffset.value - 32 + 4 argsOffsetVar = symbols.get(f"&{addr}", argsOffset.value) - argsOffsetOp = IROperand(argsOffsetVar, True, +4) + argsOffsetOp = IROperand(argsOffsetVar, True, -4) retVar = ctx.get_next_variable(IRVariable.MemType.MEMORY, retOffset.value) symbols[f"&{retOffset.value}"] = retVar inst = IRInstruction( - "call", [gas, address, value, argsOffsetOp, argsSize, retOffset, retSize], retVar + "call", [gas, address, value, argsOffsetOp, argsSize, retOffset, retSize][::-1], retVar ) ctx.get_basic_block().append_instruction(inst) return retVar From e8d8e726e58b4f98b7c981fa3c0ccd7ed69f1935 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 11:06:48 +0300 Subject: [PATCH 102/471] proper address access --- vyper/codegen/dfg.py | 6 +++--- vyper/codegen/ir_basicblock.py | 18 +++++++++++++++--- vyper/ir/ir_to_bb_pass.py | 16 +++++++++------- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 9167a036cd..da8abf6512 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -198,7 +198,7 @@ def _generate_evm_for_instruction_r( assembly.append(opcode.upper()) elif opcode == "alloca": pass - elif opcode == "load": + elif opcode == "store": pass elif opcode == "jnz": assembly.append(f"_sym_{inst.operands[1].value}") @@ -275,7 +275,7 @@ def _emit_input_operands( _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) if op.is_variable and op.target.mem_type == IRVariable.MemType.MEMORY: if op.address_access: - assembly.extend([*PUSH(op.target.mem_addr)]) + assembly.extend([*PUSH(op.addr)]) else: - assembly.extend([*PUSH(op.target.mem_addr)]) + assembly.extend([*PUSH(op.addr)]) assembly.append("MLOAD") diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index a3b95cf9a1..76bb73936e 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -105,8 +105,12 @@ def __init__( self, target: IRValueBase, address_access: bool = False, address_offset: int = 0 ) -> None: assert isinstance(target, IRValueBase), "value must be an IRValueBase" - self.address_access = address_access - self.address_offset = address_offset + if address_access: + assert ( + isinstance(target, IRVariable) and target.mem_type == IRVariable.MemType.MEMORY + ), "address access can only be used for memory variables" + self.address_access = address_access + self.address_offset = address_offset self.target = target def is_targeting(self, target: IRValueBase) -> bool: @@ -116,6 +120,12 @@ def is_targeting(self, target: IRValueBase) -> bool: def value(self) -> IRValueBaseValue: return self.target.value + @property + def addr(self) -> int: + assert self.is_variable, "address can only be accessed for variables" + target: IRVariable = self.target + return target.mem_addr + self.address_offset + @property def is_literal(self) -> bool: return isinstance(self.target, IRLiteral) @@ -197,7 +207,9 @@ def __repr__(self) -> str: if self.ret: s += f"{self.ret} = " s += f"{self.opcode} " - operands = ", ".join([(f"label %{op}" if op.is_label else str(op)) for op in self.operands]) + operands = ", ".join( + [(f"label %{op}" if op.is_label else str(op)) for op in self.operands[::-1]] + ) s += operands if self.dbg: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 649cf0763b..abae0da4b9 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -199,7 +199,9 @@ def _convert_ir_basicblock( if argsOffset.is_literal: addr = argsOffset.value - 32 + 4 argsOffsetVar = symbols.get(f"&{addr}", argsOffset.value) - argsOffsetOp = IROperand(argsOffsetVar, True, -4) + argsOffsetVar.mem_type = IRVariable.MemType.MEMORY + argsOffsetVar.mem_addr = addr + argsOffsetOp = IROperand(argsOffsetVar, True, 32 - 4) retVar = ctx.get_next_variable(IRVariable.MemType.MEMORY, retOffset.value) symbols[f"&{retOffset.value}"] = retVar @@ -262,7 +264,7 @@ def _convert_ir_basicblock( sym = ir.args[0] if ret.is_literal: - new_var = ctx.append_instruction("load", [ret]) + new_var = ctx.append_instruction("store", [ret]) symbols[sym.value] = new_var else: symbols[sym.value] = ret @@ -278,7 +280,7 @@ def _convert_ir_basicblock( elif ir.value == "set": sym = ir.args[0] arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) - new_var = ctx.append_instruction("load", [arg_1]) + new_var = ctx.append_instruction("store", [arg_1]) symbols[sym.value] = new_var elif ir.value == "calldatacopy": @@ -359,7 +361,7 @@ def _convert_ir_basicblock( symbols[f"&{sym.value}"] = new_var v = _convert_ir_basicblock(ctx, sym, symbols) op = IROperand(v, True) - inst = IRInstruction("load", [op], new_var) + inst = IRInstruction("store", [op], new_var) ctx.get_basic_block().append_instruction(inst) return new_var else: @@ -372,11 +374,11 @@ def _convert_ir_basicblock( sym = symbols.get(f"&{arg_1.value}", None) if sym_ir.is_literal: - new_var = ctx.append_instruction("load", [arg_1]) + new_var = ctx.append_instruction("store", [arg_1]) symbols[f"&{sym_ir.value}"] = new_var return new_var else: - new_var = ctx.append_instruction("load", [arg_1]) + new_var = ctx.append_instruction("store", [arg_1]) symbols[sym_ir.value] = new_var return new_var elif ir.value == "sload": @@ -429,7 +431,7 @@ def _convert_ir_basicblock( ) ) - inst = IRInstruction("load", [start], counter_var) + inst = IRInstruction("store", [start], counter_var) ctx.get_basic_block().append_instruction(inst) symbols[sym.value] = counter_var inst = IRInstruction("jmp", [cond_block.label]) From 5d62843b10c65f2243bb2d6bf44b32434cb538a7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 15:05:38 +0300 Subject: [PATCH 103/471] integrade bb to ir_runtime with experimental --- vyper/compiler/__init__.py | 1 - vyper/compiler/output.py | 4 ---- vyper/compiler/phases.py | 11 +++++------ 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/vyper/compiler/__init__.py b/vyper/compiler/__init__.py index c089bc63c4..3fcd473568 100644 --- a/vyper/compiler/__init__.py +++ b/vyper/compiler/__init__.py @@ -33,7 +33,6 @@ "ir_runtime_dict": output.build_ir_runtime_dict_output, "method_identifiers": output.build_method_identifiers_output, "metadata": output.build_metadata_output, - "bb": output.build_basicblock_output, # requires assembly "abi": output.build_abi_output, "asm": output.build_asm_output, diff --git a/vyper/compiler/output.py b/vyper/compiler/output.py index 4202390763..69fcbf1f1f 100644 --- a/vyper/compiler/output.py +++ b/vyper/compiler/output.py @@ -216,10 +216,6 @@ def _build_asm(asm_list): return output_string -def build_basicblock_output(compiler_data: CompilerData) -> str: - return compiler_data.bb_output - - def build_source_map_output(compiler_data: CompilerData) -> OrderedDict: _, line_number_map = compile_ir.assembly_to_evm( compiler_data.assembly_runtime, insert_vyper_signature=False diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 70040080fa..45dc33bc8a 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -158,12 +158,11 @@ def global_ctx(self) -> GlobalContext: @cached_property def _ir_output(self): # fetch both deployment and runtime IR - return generate_ir_nodes(self.global_ctx, self.settings.optimize) - - @cached_property - def bb_output(self): - # fetch both deployment and runtime IR - return convert_ir_basicblock(self._ir_output[1]) + nodes = generate_ir_nodes(self.global_ctx, self.settings.optimize) + if self.experimental_codegen: + return [None, convert_ir_basicblock(nodes[1])] + else: + return nodes @property def ir_nodes(self) -> IRnode: From a0e2f11c274e7dbc2060e096532d5439228cf6fb Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 16:03:06 +0300 Subject: [PATCH 104/471] deploy is a terminator instructions --- vyper/codegen/ir_basicblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 76bb73936e..5cff1f9ae4 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -2,7 +2,7 @@ from enum import Enum TERMINAL_IR_INSTRUCTIONS = ["ret", "revert", "assert"] -TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "revert"] +TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "revert", "deploy"] if TYPE_CHECKING: from vyper.codegen.ir_function import IRFunction From ed552bb5cf8b77202623f89ef3ee07ce3996e9cc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 16:03:24 +0300 Subject: [PATCH 105/471] stack refactor with out assembly reference --- vyper/compiler/utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 4a8776c5a1..809b42e8d1 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -51,11 +51,9 @@ def _expand_row(row): class StackMap: NOT_IN_STACK = 1 stack_map: list[IRValueBase] - assembly: list[str] - def __init__(self, assembly: list[str]): + def __init__(self): self.stack_map = [] - self.assembly = assembly def get_height(self) -> int: """ @@ -104,20 +102,20 @@ def poke(self, depth: int, op: IRValueBase) -> None: assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" self.stack_map[-depth - 1] = op - def dup(self, depth: int) -> None: + def dup(self, assembly: list[str], depth: int) -> None: """ Duplicates the operand at the given depth in the stack map. """ assert depth <= 0, "Cannot dup positive depth" - self.assembly.append(f"DUP{-depth+1}") + assembly.append(f"DUP{-depth+1}") self.stack_map.append(self.peek(-depth)) - def swap(self, depth: int) -> None: + def swap(self, assembly: list[str], depth: int) -> None: """ Swaps the operand at the given depth in the stack map with the top of the stack. """ assert depth < 0, "Cannot swap positive depth" - self.assembly.append(f"SWAP{-depth}") + assembly.append(f"SWAP{-depth}") self.stack_map[depth - 1], self.stack_map[-1] = ( self.stack_map[-1], self.stack_map[depth - 1], From df90dd539d4ec42ca914c290bb5eb4ff1571272a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 16:03:45 +0300 Subject: [PATCH 106/471] Made RuntimeHeader public --- vyper/ir/compile_ir.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index afef583fb4..791e939b70 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -161,7 +161,7 @@ def _add_postambles(asm_ops): # insert the postambles *before* runtime code # so the data section of the runtime code can't bork the postambles. runtime = None - if isinstance(asm_ops[-1], list) and isinstance(asm_ops[-1][0], _RuntimeHeader): + if isinstance(asm_ops[-1], list) and isinstance(asm_ops[-1][0], RuntimeHeader): runtime = asm_ops.pop() # for some reason there might not be a STOP at the end of asm_ops. @@ -528,7 +528,7 @@ def _height_of(witharg): # since the asm data structures are very primitive, to make sure # assembly_to_evm is able to calculate data offsets correctly, # we pass the memsize via magic opcodes to the subcode - subcode = [_RuntimeHeader(runtime_begin, memsize)] + subcode + subcode = [RuntimeHeader(runtime_begin, memsize)] + subcode # append the runtime code after the ctor code # `append(...)` call here is intentional. @@ -1011,7 +1011,7 @@ def _stack_peephole_opts(assembly): # optimize assembly, in place def optimize_assembly(assembly): for x in assembly: - if isinstance(x, list) and isinstance(x[0], _RuntimeHeader): + if isinstance(x, list) and isinstance(x[0], RuntimeHeader): optimize_assembly(x) for _ in range(1024): @@ -1083,7 +1083,7 @@ def _length_of_data(assembly): return ret -class _RuntimeHeader: +class RuntimeHeader: def __init__(self, label, ctor_mem_size): self.label = label self.ctor_mem_size = ctor_mem_size @@ -1113,7 +1113,7 @@ def _relocate_segments(assembly): data_segments.append(t) else: _relocate_segments(t) # recurse - assert isinstance(t[0], _RuntimeHeader) + assert isinstance(t[0], RuntimeHeader) code_segments.append(t) else: non_data_segments.append(t) @@ -1168,7 +1168,7 @@ def assembly_to_evm_with_symbol_map(assembly, pc_ofst=0, insert_vyper_signature= mem_ofst_size, ctor_mem_size = None, None max_mem_ofst = 0 for i, item in enumerate(assembly): - if isinstance(item, list) and isinstance(item[0], _RuntimeHeader): + if isinstance(item, list) and isinstance(item[0], RuntimeHeader): assert runtime_code is None, "Multiple subcodes" assert ctor_mem_size is None @@ -1229,7 +1229,7 @@ def assembly_to_evm_with_symbol_map(assembly, pc_ofst=0, insert_vyper_signature= # [_OFST, _sym_foo, bar] -> PUSH2 (foo+bar) # [_OFST, _mem_foo, bar] -> PUSHN (foo+bar) pc -= 1 - elif isinstance(item, list) and isinstance(item[0], _RuntimeHeader): + elif isinstance(item, list) and isinstance(item[0], RuntimeHeader): symbol_map[item[0].label] = pc # add source map for all items in the runtime map t = adjust_pc_maps(runtime_map, pc) @@ -1293,7 +1293,7 @@ def assembly_to_evm_with_symbol_map(assembly, pc_ofst=0, insert_vyper_signature= ret.append(DUP_OFFSET + int(item[3:])) elif item[:4] == "SWAP": ret.append(SWAP_OFFSET + int(item[4:])) - elif isinstance(item, list) and isinstance(item[0], _RuntimeHeader): + elif isinstance(item, list) and isinstance(item[0], RuntimeHeader): ret.extend(runtime_code) elif isinstance(item, list) and isinstance(item[0], _DataHeader): ret.extend(_data_to_evm(item, symbol_map)) From 93bf38397ec73a847d4ba622304f01fbd7c1c607 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 16:04:18 +0300 Subject: [PATCH 107/471] phases with new codegen --- vyper/compiler/phases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 45dc33bc8a..06ae521d61 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -160,7 +160,7 @@ def _ir_output(self): # fetch both deployment and runtime IR nodes = generate_ir_nodes(self.global_ctx, self.settings.optimize) if self.experimental_codegen: - return [None, convert_ir_basicblock(nodes[1])] + return [convert_ir_basicblock(nodes[0]), convert_ir_basicblock(nodes[1])] else: return nodes From 851aa8375a386fa8415ff228e81f766118ed001f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 16:04:38 +0300 Subject: [PATCH 108/471] deploy terminator --- vyper/ir/bb_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 5fa5b32950..d9ce143420 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -92,7 +92,7 @@ def _calculate_in_set(ctx: IRFunction) -> None: ), "Last instruction should be a terminator" + str(bb) for inst in bb.instructions: - if inst.opcode in ["jmp", "jnz", "call", "invoke"]: + if inst.opcode in ["jmp", "jnz", "call", "invoke", "deploy"]: ops = inst.get_label_operands() for op in ops: ctx.get_basic_block(op.value).add_in(bb) From a21c30e01958e5923f9fa49931a59b5a8b355310 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 16:05:04 +0300 Subject: [PATCH 109/471] refactor assembly generator to support subcodes --- vyper/codegen/dfg.py | 53 ++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index da8abf6512..85a2617d9a 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,7 +1,7 @@ from vyper.codegen.ir_basicblock import IRInstruction, IRLabel, IROperand, IRVariable from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap -from vyper.ir.compile_ir import PUSH, optimize_assembly +from vyper.ir.compile_ir import PUSH, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions ONE_TO_ONE_INSTRUCTIONS = [ @@ -82,8 +82,10 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: + stack_map = StackMap() assembly = [] - stack_map = StackMap(assembly) + asm = assembly + convert_ir_to_dfg(ctx) for bb in ctx.basic_blocks: @@ -101,8 +103,8 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: for i, bb in enumerate(ctx.basic_blocks): if i != 0: - assembly.append(f"_sym_{bb.label}") - assembly.append("JUMPDEST") + asm.append(f"_sym_{bb.label}") + asm.append("JUMPDEST") fen = 0 for inst in bb.instructions: @@ -111,13 +113,13 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: fen += 1 for inst in bb.instructions: - _generate_evm_for_instruction_r(ctx, assembly, inst, stack_map) + asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) # Append postambles - assembly.extend(["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"]) + asm.extend(["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"]) if no_optimize is False: - optimize_assembly(assembly) + optimize_assembly(asm) return assembly @@ -128,7 +130,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: def _generate_evm_for_instruction_r( ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap -) -> None: +) -> list[str]: global label_counter for op in inst.get_output_operands(): @@ -137,10 +139,10 @@ def _generate_evm_for_instruction_r( continue if target.fen != inst.fen: continue - _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) + assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) if inst in visited_instructions: - return + return assembly visited_instructions.add(inst) # generate EVM for op @@ -158,7 +160,7 @@ def _generate_evm_for_instruction_r( stack_map.push(ret.target) else: stack_map.poke(depth, ret.target) - return + return assembly _emit_input_operands(ctx, assembly, inst, stack_map) @@ -170,7 +172,7 @@ def _generate_evm_for_instruction_r( assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" needs_copy = op.target.use_count - ucc > 1 if needs_copy: - stack_map.dup(depth) + stack_map.dup(assembly, depth) op.target.use_count -= 1 for i in range(len(operands)): @@ -183,12 +185,12 @@ def _generate_evm_for_instruction_r( if not is_in_place: if final_stack_depth == 0 and depth != 0: - stack_map.swap(depth) + stack_map.swap(assembly, depth) elif final_stack_depth != 0 and depth == 0: - stack_map.swap(final_stack_depth) + stack_map.swap(assembly, final_stack_depth) else: - stack_map.swap(depth) - stack_map.swap(final_stack_depth) + stack_map.swap(assembly, depth) + stack_map.swap(assembly, final_stack_depth) stack_map.pop(len(operands)) if inst.ret is not None: @@ -253,6 +255,19 @@ def _generate_evm_for_instruction_r( 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 + assembly.clear() + 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)]) + assembly = assembly[-1] + pass else: raise Exception(f"Unknown opcode: {opcode}") @@ -262,6 +277,8 @@ def _generate_evm_for_instruction_r( assembly.extend([*PUSH(inst.ret.target.mem_addr)]) assembly.append("MSTORE") + return assembly + def _emit_input_operands( ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap @@ -272,10 +289,12 @@ def _emit_input_operands( assembly.extend([*PUSH(op.value)]) stack_map.push(op.target) continue - _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) + assembly = _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) if op.is_variable and op.target.mem_type == IRVariable.MemType.MEMORY: if op.address_access: assembly.extend([*PUSH(op.addr)]) else: assembly.extend([*PUSH(op.addr)]) assembly.append("MLOAD") + + return assembly From 17bd04b77a3928cbc385afa23d54731b54c47195 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 16:05:21 +0300 Subject: [PATCH 110/471] implement deploy opcode --- vyper/ir/ir_to_bb_pass.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index abae0da4b9..0839efb3e3 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -49,8 +49,7 @@ def _get_symbols_common(a: dict, b: dict) -> dict: def generate_assembly_experimental( ir: IRnode, optimize: Optional[OptimizationLevel] = None ) -> list[str]: - global_function = convert_ir_basicblock(ir) - return generate_evm(global_function, optimize is OptimizationLevel.NONE) + return generate_evm(ir, optimize is OptimizationLevel.NONE) def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: @@ -173,7 +172,21 @@ def _convert_ir_basicblock( pass elif ir.value == "deploy": - _convert_ir_basicblock(ctx, ir.args[1], symbols) + 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 = ctx.get_next_label() + + inst = IRInstruction("deploy", [IRLiteral(memsize), runtimeLabel, IRLiteral(padding)]) + ctx.get_basic_block().append_instruction(inst) + + bb = IRBasicBlock(runtimeLabel, ctx) + ctx.append_basic_block(bb) + + _convert_ir_basicblock(ctx, ir_runtime, symbols) elif ir.value == "seq": if ir.is_self_call: return _handle_self_call(ctx, ir, symbols) From 0001dc7f0f95112d14e65176224188e1570284c3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 29 Aug 2023 18:16:39 +0300 Subject: [PATCH 111/471] bugfix --- vyper/codegen/dfg.py | 3 ++- vyper/ir/ir_to_bb_pass.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 85a2617d9a..0df92df36d 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -227,6 +227,7 @@ def _generate_evm_for_instruction_r( "JUMPDEST", ] ) + label_counter += 1 elif opcode == "call": assembly.append("CALL") elif opcode == "ret": @@ -274,7 +275,7 @@ def _generate_evm_for_instruction_r( if inst.ret is not None: assert inst.ret.is_variable, "Return value must be a variable" if inst.ret.target.mem_type == IRVariable.MemType.MEMORY: - assembly.extend([*PUSH(inst.ret.target.mem_addr)]) + assembly.extend([*PUSH(inst.ret.addr)]) assembly.append("MSTORE") return assembly diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 0839efb3e3..cafbf4902d 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -373,7 +373,7 @@ def _convert_ir_basicblock( new_var = ctx.get_next_variable() symbols[f"&{sym.value}"] = new_var v = _convert_ir_basicblock(ctx, sym, symbols) - op = IROperand(v, True) + op = IROperand(v, not v.is_literal) inst = IRInstruction("store", [op], new_var) ctx.get_basic_block().append_instruction(inst) return new_var From 502d76bb33200ccf12f2cb7c3afbd4fd04cc98e3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 30 Aug 2023 11:47:58 +0300 Subject: [PATCH 112/471] add return as a terminator instruction --- vyper/codegen/ir_basicblock.py | 2 +- vyper/ir/ir_to_bb_pass.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 5cff1f9ae4..f17cb31f26 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -2,7 +2,7 @@ from enum import Enum TERMINAL_IR_INSTRUCTIONS = ["ret", "revert", "assert"] -TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "revert", "deploy"] +TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "return", "revert", "deploy"] if TYPE_CHECKING: from vyper.codegen.ir_function import IRFunction diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index cafbf4902d..2cef9dc7da 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -355,6 +355,7 @@ def _convert_ir_basicblock( new_op = IROperand(new_var, True) inst = IRInstruction("return", [last_ir, new_op]) ctx.get_basic_block().append_instruction(inst) + ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) elif ir.value == "revert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) From 7f18c105048283cf46cd15a2123537962937208e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 30 Aug 2023 14:32:50 +0300 Subject: [PATCH 113/471] bugfixes --- vyper/codegen/dfg.py | 5 ++--- vyper/ir/ir_to_bb_pass.py | 10 +++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 0df92df36d..31d3ae88f1 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -109,7 +109,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: fen = 0 for inst in bb.instructions: inst.fen = fen - if inst.opcode in ["call", "invoke", "sload", "sstore", "assert"]: + if inst.opcode in ["alloca", "call", "invoke", "sload", "sstore", "assert"]: fen += 1 for inst in bb.instructions: @@ -231,8 +231,7 @@ def _generate_evm_for_instruction_r( elif opcode == "call": assembly.append("CALL") elif opcode == "ret": - assert len(inst.operands) == 1, "ret instruction takes one operand" - assembly.append("SWAP1") + assert len(inst.operands) == 2, "ret instruction takes one operand" assembly.append("JUMP") elif opcode == "return": assembly.append("RETURN") diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 2cef9dc7da..d3fb873949 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -130,6 +130,12 @@ def _handle_internal_func( symbols[f"&{old_ir_mempos}"] = new_var old_ir_mempos += 32 + # return address + new_var = ctx.get_next_variable() + alloca_inst = IRInstruction("alloca", [], new_var) + bb.append_instruction(alloca_inst) + symbols[f"return_pc"] = new_var + return ir.args[0].args[2] @@ -158,8 +164,10 @@ def _convert_ir_basicblock( return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3", "sha3_64"]) elif ir.value in MAPPED_IR_INSTRUCTIONS.keys(): + org_value = ir.value ir.value = MAPPED_IR_INSTRUCTIONS[ir.value] new_var = _convert_binary_op(ctx, ir, symbols) + ir.value = org_value return ctx.append_instruction("iszero", [new_var]) elif ir.value in ["iszero", "ceil32", "calldataload"]: @@ -337,7 +345,7 @@ def _convert_ir_basicblock( elif len(ir.args) >= 2: ret_var = ir.args[1] if ret_var.value == "return_pc": - inst = IRInstruction("ret", [symbols["return_buffer"]]) + inst = IRInstruction("ret", [symbols["return_buffer"], symbols["return_pc"]]) ctx.get_basic_block().append_instruction(inst) return None # else: From 5c5699fceb6ae5f8c3938f99577b56f7cef8207a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 31 Aug 2023 23:10:00 +0300 Subject: [PATCH 114/471] load internal call parameters --- vyper/ir/ir_to_bb_pass.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index d3fb873949..213a5b0e7a 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -102,12 +102,13 @@ def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> None target_label = goto_ir.args[0].value # goto ret_values = [IRLabel(target_label)] for arg in args_ir: - if arg.value != "with": + if arg.is_literal: ret = _convert_ir_basicblock(ctx, arg, symbols) - new_var = ctx.append_instruction("calldataload", [ret]) - ret_values.append(new_var) + ret_values.append(ret) else: - ret = _convert_ir_basicblock(ctx, arg, symbols) + ret = _convert_ir_basicblock(ctx, arg._optimized, symbols) + if arg.location and arg.location.load_op == "calldataload": + ret = ctx.append_instruction(arg.location.load_op, [ret]) ret_values.append(ret) return ctx.append_instruction("invoke", ret_values) From 8d92d3c4dbd1a5463a1effc4cfb48f162efab813 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Sep 2023 00:15:13 +0300 Subject: [PATCH 115/471] fix with nesting bug --- vyper/ir/ir_to_bb_pass.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 213a5b0e7a..3b8c6224a3 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -284,14 +284,17 @@ def _convert_ir_basicblock( elif ir.value == "with": ret = _convert_ir_basicblock(ctx, ir.args[1], symbols) # initialization + # Handle with nesting with same symbol + with_symbols = symbols.copy() + sym = ir.args[0] if ret.is_literal: new_var = ctx.append_instruction("store", [ret]) - symbols[sym.value] = new_var + with_symbols[sym.value] = new_var else: - symbols[sym.value] = ret + with_symbols[sym.value] = ret - return _convert_ir_basicblock(ctx, ir.args[2], symbols) # body + return _convert_ir_basicblock(ctx, ir.args[2], with_symbols) # body elif ir.value == "goto": return _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "jump": From 8683a0768f8a27b46b3e7889e6fbc55110dc9b55 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Sep 2023 00:45:48 +0300 Subject: [PATCH 116/471] fully reoptimize --- vyper/ir/bb_optimizer.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index d9ce143420..be4eebe42e 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -8,18 +8,18 @@ def optimize_function(ctx: IRFunction): - while _optimize_empty_basicblocks(ctx): - pass + while True: + while _optimize_empty_basicblocks(ctx): + pass - _calculate_in_set(ctx) + _calculate_in_set(ctx) - while ctx.remove_unreachable_blocks(): - pass + while ctx.remove_unreachable_blocks(): + pass - if len(ctx.basic_blocks) == 0: - return ctx + if len(ctx.basic_blocks) == 0: + return ctx - while True: _calculate_liveness(ctx.basic_blocks[0], {}) removed = _optimize_unused_variables(ctx) if len(removed) == 0: @@ -84,6 +84,12 @@ def _calculate_in_set(ctx: IRFunction) -> None: """ Calculate in set for each basic block. """ + for bb in ctx.basic_blocks: + bb.in_set = set() + bb.out_set = set() + bb.out_vars = set() + bb.phi_vars = {} + for bb in ctx.basic_blocks: assert len(bb.instructions) > 0, "Basic block should not be empty" last_inst = bb.instructions[-1] From 0723829b3327c056438e268506159daee10b8984 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Sep 2023 10:18:21 +0300 Subject: [PATCH 117/471] stackmap copy-able --- vyper/compiler/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 809b42e8d1..1db68f41da 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -55,6 +55,11 @@ class StackMap: def __init__(self): self.stack_map = [] + def copy(self): + new = StackMap() + new.stack_map = self.stack_map.copy() + return new + def get_height(self) -> int: """ Returns the height of the stack map. From b3c3c10d7a3e487b279151feefc5b529f6d267ba Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Sep 2023 11:17:21 +0300 Subject: [PATCH 118/471] refactor code emition --- vyper/codegen/dfg.py | 97 +++++++++++++++++++++++++++++++++--------- vyper/ir/compile_ir.py | 1 + 2 files changed, 79 insertions(+), 19 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 31d3ae88f1..be0b833a8a 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,4 +1,4 @@ -from vyper.codegen.ir_basicblock import IRInstruction, IRLabel, IROperand, IRVariable +from vyper.codegen.ir_basicblock import IRInstruction, IROperand, IRVariable, IRBasicBlock from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap from vyper.ir.compile_ir import PUSH, RuntimeHeader, optimize_assembly @@ -79,12 +79,15 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: visited_instructions = {IRInstruction} +visited_basicblocks = {IRBasicBlock} def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: + global visited_instructions, visited_basicblocks stack_map = StackMap() - assembly = [] - asm = assembly + asm = [] + visited_instructions = set() + visited_basicblocks = set() convert_ir_to_dfg(ctx) @@ -101,27 +104,83 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: block_a.phi_vars[ret_op.value] = inst.operands[3] block_b.phi_vars[ret_op.value] = inst.operands[1] - for i, bb in enumerate(ctx.basic_blocks): - if i != 0: - asm.append(f"_sym_{bb.label}") - asm.append("JUMPDEST") - - fen = 0 - for inst in bb.instructions: - inst.fen = fen - if inst.opcode in ["alloca", "call", "invoke", "sload", "sstore", "assert"]: - fen += 1 - - for inst in bb.instructions: - asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) + _generate_evm_for_basicblock(ctx, asm, ctx.basic_blocks[0], stack_map) + + # for i, bb in enumerate(ctx.basic_blocks): + # if i != 0: + # asm.append(f"_sym_{bb.label}") + # asm.append("JUMPDEST") + + # # values to pop from stack + # in_vars = set() + # for in_bb in bb.in_set: + # in_vars |= in_bb.out_vars.difference(bb.in_vars_for(in_bb)) + # print(f"IN_VARS: {in_vars}") + + # # for var in in_vars: + # # depth = stack_map.get_depth_in(var) + # # assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" + # # if depth != 0: + # # stack_map.swap(asm, depth) + # # stack_map.pop() + # # asm.append("POP") + + # fen = 0 + # for inst in bb.instructions: + # inst.fen = fen + # if inst.opcode in ["alloca", "call", "invoke", "sload", "sstore", "assert"]: + # fen += 1 + + # # for inst in bb.instructions: + # # asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) + # asm = _generate_evm_for_instruction_r(ctx, asm, bb.instructions[-1], stack_map) # Append postambles - asm.extend(["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"]) + asm.extend(["_sym___revert__contract", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"]) if no_optimize is False: optimize_assembly(asm) - return assembly + return asm + + +def _generate_evm_for_basicblock( + ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackMap +): + if basicblock in visited_basicblocks: + return asm + visited_basicblocks.add(basicblock) + + asm.append(f"_sym_{basicblock.label}") + asm.append("JUMPDEST") + + # values to pop from stack + in_vars = set() + for in_bb in basicblock.in_set: + in_vars |= in_bb.out_vars.difference(basicblock.in_vars_for(in_bb)) + # print(f"IN_VARS: {in_vars}") + + # for var in in_vars: + # depth = stack_map.get_depth_in(var) + # assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" + # if depth != 0: + # stack_map.swap(asm, depth) + # stack_map.pop() + # asm.append("POP") + + fen = 0 + for inst in basicblock.instructions: + inst.fen = fen + if inst.opcode in ["alloca", "call", "invoke", "sload", "sstore", "assert"]: + fen += 1 + + for inst in basicblock.instructions: + asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) + + for bb in basicblock.out_set: + _generate_evm_for_basicblock(ctx, asm, bb, stack_map.copy()) + + return asm # TODO: refactor this @@ -254,7 +313,7 @@ def _generate_evm_for_instruction_r( elif opcode == "ceil32": assembly.extend([*PUSH(31), "ADD", *PUSH(31), "NOT", "AND"]) elif opcode == "assert": - assembly.extend(["ISZERO", "_sym___revert", "JUMPI"]) + assembly.extend(["ISZERO", "_sym___revert__contract", "JUMPI"]) elif opcode == "deploy": memsize = inst.operands[0].value padding = inst.operands[2].value diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 791e939b70..ad13986f57 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -1215,6 +1215,7 @@ def assembly_to_evm_with_symbol_map(assembly, pc_ofst=0, insert_vyper_signature= if is_symbol_map_indicator(assembly[i + 1]): # Don't increment pc as the symbol itself doesn't go into code if item in symbol_map: + print(assembly) raise CompilerPanic(f"duplicate jumpdest {item}") symbol_map[item] = pc From e32cf0a802a97677cf012ca27900a4d2d97afe9f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Sep 2023 11:23:14 +0300 Subject: [PATCH 119/471] pop values unsused in block --- vyper/codegen/dfg.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index be0b833a8a..62fa357ee1 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -158,15 +158,14 @@ def _generate_evm_for_basicblock( in_vars = set() for in_bb in basicblock.in_set: in_vars |= in_bb.out_vars.difference(basicblock.in_vars_for(in_bb)) - # print(f"IN_VARS: {in_vars}") - - # for var in in_vars: - # depth = stack_map.get_depth_in(var) - # assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" - # if depth != 0: - # stack_map.swap(asm, depth) - # stack_map.pop() - # asm.append("POP") + + for var in in_vars: + depth = stack_map.get_depth_in(var) + assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" + if depth != 0: + stack_map.swap(asm, depth) + stack_map.pop() + asm.append("POP") fen = 0 for inst in basicblock.instructions: From 93f9e473bada39273736879c9e6896518af54bd3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Sep 2023 12:41:03 +0300 Subject: [PATCH 120/471] refactor tested --- vyper/codegen/dfg.py | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 62fa357ee1..0c4140f238 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -104,36 +104,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: block_a.phi_vars[ret_op.value] = inst.operands[3] block_b.phi_vars[ret_op.value] = inst.operands[1] - _generate_evm_for_basicblock(ctx, asm, ctx.basic_blocks[0], stack_map) - - # for i, bb in enumerate(ctx.basic_blocks): - # if i != 0: - # asm.append(f"_sym_{bb.label}") - # asm.append("JUMPDEST") - - # # values to pop from stack - # in_vars = set() - # for in_bb in bb.in_set: - # in_vars |= in_bb.out_vars.difference(bb.in_vars_for(in_bb)) - # print(f"IN_VARS: {in_vars}") - - # # for var in in_vars: - # # depth = stack_map.get_depth_in(var) - # # assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" - # # if depth != 0: - # # stack_map.swap(asm, depth) - # # stack_map.pop() - # # asm.append("POP") - - # fen = 0 - # for inst in bb.instructions: - # inst.fen = fen - # if inst.opcode in ["alloca", "call", "invoke", "sload", "sstore", "assert"]: - # fen += 1 - - # # for inst in bb.instructions: - # # asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) - # asm = _generate_evm_for_instruction_r(ctx, asm, bb.instructions[-1], stack_map) + _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], stack_map) # Append postambles asm.extend(["_sym___revert__contract", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"]) @@ -144,7 +115,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: return asm -def _generate_evm_for_basicblock( +def _generate_evm_for_basicblock_r( ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackMap ): if basicblock in visited_basicblocks: @@ -177,7 +148,7 @@ def _generate_evm_for_basicblock( asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) for bb in basicblock.out_set: - _generate_evm_for_basicblock(ctx, asm, bb, stack_map.copy()) + _generate_evm_for_basicblock_r(ctx, asm, bb, stack_map.copy()) return asm From a0a5a396950ef61be58edf159fd51db6ca826008 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Sep 2023 12:57:28 +0300 Subject: [PATCH 121/471] postambles in deploy mode --- vyper/codegen/dfg.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 0c4140f238..dd8d6f51a1 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -107,7 +107,8 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], stack_map) # Append postambles - asm.extend(["_sym___revert__contract", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"]) + extent_point = asm if not isinstance(asm[-1], list) else asm[-1] + extent_point.extend(["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"]) if no_optimize is False: optimize_assembly(asm) @@ -283,7 +284,7 @@ def _generate_evm_for_instruction_r( elif opcode == "ceil32": assembly.extend([*PUSH(31), "ADD", *PUSH(31), "NOT", "AND"]) elif opcode == "assert": - assembly.extend(["ISZERO", "_sym___revert__contract", "JUMPI"]) + assembly.extend(["ISZERO", "_sym___revert", "JUMPI"]) elif opcode == "deploy": memsize = inst.operands[0].value padding = inst.operands[2].value From aab7d0a46e0ed7ce69c93d2861b1662b17b269c1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Sep 2023 13:27:35 +0300 Subject: [PATCH 122/471] optimize doubleswap --- vyper/ir/compile_ir.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index ad13986f57..e3049b06c8 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -1003,6 +1003,9 @@ def _stack_peephole_opts(assembly): changed = True del assembly[i] continue + if assembly[i : i + 2] == ["SWAP1", "SWAP1"]: + changed = True + del assembly[i : i + 2] i += 1 return changed From 214a8ebdaf97b40e6176362d327a767f7a758527 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Sep 2023 13:40:56 +0300 Subject: [PATCH 123/471] optimize swap1 followed by commutative instruction --- vyper/codegen/dfg.py | 2 -- vyper/ir/compile_ir.py | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index dd8d6f51a1..55ef9be0fd 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -40,8 +40,6 @@ "log4", ] -OPERAND_ORDER_IRELEVANT_INSTRUCTIONS = ["xor", "or", "add", "mul", "eq"] - class DFGNode: value: IRInstruction | IROperand diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index e3049b06c8..810377a487 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -2,6 +2,7 @@ import functools import math +from vyper.ir.optimizer import COMMUTATIVE_OPS from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel from vyper.evm.opcodes import get_opcodes, version_check @@ -1006,6 +1007,9 @@ def _stack_peephole_opts(assembly): if assembly[i : i + 2] == ["SWAP1", "SWAP1"]: changed = True del assembly[i : i + 2] + if assembly[i] == "SWAP1" and assembly[i + 1] is COMMUTATIVE_OPS: + changed = True + del assembly[i] i += 1 return changed From 88c6c368e6c13ef365c276c6d46af6fa18b90ebd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Sep 2023 21:31:10 +0300 Subject: [PATCH 124/471] temp --- vyper/codegen/ir_function.py | 7 +++++++ vyper/ir/compile_ir.py | 16 ++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 193bbcdbb0..bceb8dd79a 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -30,6 +30,7 @@ class IRFunction(IRFunctionBase): """ basic_blocks: list["IRBasicBlock"] + data_segment: list["IRInstruction"] last_label: int last_variable: int @@ -118,6 +119,12 @@ def append_instruction(self, opcode: str, args: list[IROperand | IRValueBase]): self.get_basic_block().append_instruction(inst) return ret + def append_data(self, opcode: str, args: list[IROperand | IRValueBase]): + """ + Append data + """ + self.data_segment.append(IRInstruction(opcode, args)) + def __repr__(self) -> str: str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 810377a487..f1affeb4e9 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -673,7 +673,7 @@ def _height_of(witharg): ) elif code.value == "data": - data_node = [_DataHeader("_sym_" + code.args[0].value)] + data_node = [DataHeader("_sym_" + code.args[0].value)] for c in code.args[1:]: if isinstance(c.value, int): @@ -961,7 +961,7 @@ def _prune_unused_jumpdests(assembly): used_jumpdests.add(assembly[i]) for item in assembly: - if isinstance(item, list) and isinstance(item[0], _DataHeader): + if isinstance(item, list) and isinstance(item[0], DataHeader): # add symbols used in data sections as they are likely # used for a jumptable. for t in item: @@ -1057,7 +1057,7 @@ def adjust_pc_maps(pc_maps, ofst): def _data_to_evm(assembly, symbol_map): ret = bytearray() - assert isinstance(assembly[0], _DataHeader) + assert isinstance(assembly[0], DataHeader) for item in assembly[1:]: if is_symbol(item): symbol = symbol_map[item].to_bytes(SYMBOL_SIZE, "big") @@ -1075,7 +1075,7 @@ def _data_to_evm(assembly, symbol_map): # predict what length of an assembly [data] node will be in bytecode def _length_of_data(assembly): ret = 0 - assert isinstance(assembly[0], _DataHeader) + assert isinstance(assembly[0], DataHeader) for item in assembly[1:]: if is_symbol(item): ret += SYMBOL_SIZE @@ -1099,7 +1099,7 @@ def __repr__(self): return f"" -class _DataHeader: +class DataHeader: def __init__(self, label): self.label = label @@ -1116,7 +1116,7 @@ def _relocate_segments(assembly): code_segments = [] for t in assembly: if isinstance(t, list): - if isinstance(t[0], _DataHeader): + if isinstance(t[0], DataHeader): data_segments.append(t) else: _relocate_segments(t) # recurse @@ -1244,7 +1244,7 @@ def assembly_to_evm_with_symbol_map(assembly, pc_ofst=0, insert_vyper_signature= for key in line_number_map: line_number_map[key].update(t[key]) pc += len(runtime_code) - elif isinstance(item, list) and isinstance(item[0], _DataHeader): + elif isinstance(item, list) and isinstance(item[0], DataHeader): symbol_map[item[0].label] = pc pc += _length_of_data(item) else: @@ -1303,7 +1303,7 @@ def assembly_to_evm_with_symbol_map(assembly, pc_ofst=0, insert_vyper_signature= ret.append(SWAP_OFFSET + int(item[4:])) elif isinstance(item, list) and isinstance(item[0], RuntimeHeader): ret.extend(runtime_code) - elif isinstance(item, list) and isinstance(item[0], _DataHeader): + elif isinstance(item, list) and isinstance(item[0], DataHeader): ret.extend(_data_to_evm(item, symbol_map)) else: # pragma: no cover # unreachable From 43fc8235b23e3cb64a668227df2d5d9b8221d228 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 3 Sep 2023 21:27:27 +0300 Subject: [PATCH 125/471] emit data --- vyper/codegen/dfg.py | 16 +++++++++++++++- vyper/codegen/ir_function.py | 10 ++++++++-- vyper/ir/ir_to_bb_pass.py | 14 ++++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 55ef9be0fd..3947870a8b 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,7 +1,7 @@ from vyper.codegen.ir_basicblock import IRInstruction, IROperand, IRVariable, IRBasicBlock from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap -from vyper.ir.compile_ir import PUSH, RuntimeHeader, optimize_assembly +from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions ONE_TO_ONE_INSTRUCTIONS = [ @@ -108,6 +108,18 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: extent_point = asm if not isinstance(asm[-1], list) else asm[-1] extent_point.extend(["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"]) + # Append data segment + data_segments = {} + for bb in ctx.basic_blocks: + for inst in bb.instructions: + if inst.opcode == "dbname": + label = inst.operands[0].value + data_segments[label] = [DataHeader(f"_sym_{label}")] + elif inst.opcode == "db": + data_segments[label].append(inst.operands[0].value) + + asm.extend([data_segments[label] for label in data_segments]) + if no_optimize is False: optimize_assembly(asm) @@ -230,6 +242,8 @@ def _generate_evm_for_instruction_r( pass elif opcode == "store": pass + elif opcode == "dbname": + pass elif opcode == "jnz": assembly.append(f"_sym_{inst.operands[1].value}") assembly.append("JUMPI") diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index bceb8dd79a..1d879c48f0 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -37,6 +37,7 @@ class IRFunction(IRFunctionBase): def __init__(self, name: IRLabel) -> None: super().__init__(name) self.basic_blocks = [] + self.data_segment = [] self.last_label = 0 self.last_variable = 0 @@ -110,11 +111,13 @@ def remove_unreachable_blocks(self) -> int: self.basic_blocks = new_basic_blocks return removed - def append_instruction(self, opcode: str, args: list[IROperand | IRValueBase]): + def append_instruction( + self, opcode: str, args: list[IROperand | IRValueBase], do_ret: bool = True + ): """ Append instruction to last basic block. """ - ret = self.get_next_variable() + ret = self.get_next_variable() if do_ret else None inst = IRInstruction(opcode, args, ret) self.get_basic_block().append_instruction(inst) return ret @@ -129,6 +132,9 @@ def __repr__(self) -> str: str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: str += f"{bb}\n" + str += f"Data segment:\n" + for inst in self.data_segment: + str += f"{inst}\n" return str diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 3b8c6224a3..5a5d4021f3 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -326,9 +326,19 @@ def _convert_ir_basicblock( symbols[f"&{arg_0.value}"] = new_var elif ir.value == "symbol": - return IRLabel(ir.args[0].value) + return IRLabel(ir.args[0].value, True) elif ir.value == "data": - pass + label = IRLabel(ir.args[0].value) + ctx.append_instruction("dbname", [label], False) + for c in ir.args[1:]: + if isinstance(c, int): + assert 0 <= c <= 255, "data with invalid size" + ctx.append_instruction("db", [c], False) + elif isinstance(c, bytes): + ctx.append_instruction("db", [c], False) + elif isinstance(c, IRnode): + data = _convert_ir_basicblock(ctx, c, symbols) + ctx.append_instruction("db", [data], False) elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) current_bb = ctx.get_basic_block() From 11967dc32dceda4e3c1f3158f786e5e7d1730223 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 00:20:46 +0300 Subject: [PATCH 126/471] codecpy --- vyper/codegen/dfg.py | 11 +++++++++-- vyper/codegen/ir_basicblock.py | 3 ++- vyper/ir/ir_to_bb_pass.py | 12 ++++++++---- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 3947870a8b..bb75b5b5ba 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -9,6 +9,7 @@ "calldatasize", "calldatacopy", "calldataload", + "codecopy", "gas", "returndatasize", "returndatacopy", @@ -27,6 +28,7 @@ "sub", "mul", "div", + "mod", "eq", "iszero", "lg", @@ -116,9 +118,10 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: label = inst.operands[0].value data_segments[label] = [DataHeader(f"_sym_{label}")] elif inst.opcode == "db": - data_segments[label].append(inst.operands[0].value) + data_segments[label].append(f"_sym_{inst.operands[0].value}") - asm.extend([data_segments[label] for label in data_segments]) + extent_point = asm if not isinstance(asm[-1], list) else asm[-1] + extent_point.extend([data_segments[label] for label in data_segments]) if no_optimize is False: optimize_assembly(asm) @@ -327,6 +330,10 @@ def _emit_input_operands( ) -> None: ops = inst.get_input_operands() for op in ops: + if op.is_label: + assembly.append(f"_sym_{op.value}") + stack_map.push(op.target) + continue if op.is_literal: assembly.extend([*PUSH(op.value)]) stack_map.push(op.target) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index f17cb31f26..5bb8151f2d 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -184,7 +184,8 @@ def get_input_operands(self) -> list[IROperand]: """ Get all input operands in instruction. """ - return [op for op in self.operands if not op.is_label] + return self.operands + # return [op for op in self.operands if not op.is_label] def get_input_variables(self) -> list[IRVariable]: """ diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 5a5d4021f3..4e37f0dbfb 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -319,12 +319,16 @@ def _convert_ir_basicblock( return new_var elif ir.value == "codecopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + if arg_0.is_literal and arg_0.value == 30: + arg_0_var = IRVariable("%ccd") + symbols[f"&0"] = arg_0_var + else: + arg_0_var = ctx.get_next_variable() + symbols[f"&{arg_0.value}"] = arg_0_var arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) size = _convert_ir_basicblock(ctx, ir.args[2], symbols) - - new_var = ctx.append_instruction("codecopy", [arg_1, size]) - - symbols[f"&{arg_0.value}"] = new_var + inst = IRInstruction("codecopy", [arg_1, size], arg_0_var) + ctx.get_basic_block().append_instruction(inst) elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) elif ir.value == "data": From 20197b50e7603c4ad372587a52365e5d127a2377 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 00:57:15 +0300 Subject: [PATCH 127/471] append deps in proper block --- vyper/codegen/dfg.py | 6 +++--- vyper/ir/bb_optimizer.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index bb75b5b5ba..50f72ddc83 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -84,7 +84,6 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: global visited_instructions, visited_basicblocks - stack_map = StackMap() asm = [] visited_instructions = set() visited_basicblocks = set() @@ -104,7 +103,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: block_a.phi_vars[ret_op.value] = inst.operands[3] block_b.phi_vars[ret_op.value] = inst.operands[1] - _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], stack_map) + _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackMap()) # Append postambles extent_point = asm if not isinstance(asm[-1], list) else asm[-1] @@ -146,7 +145,8 @@ def _generate_evm_for_basicblock_r( for var in in_vars: depth = stack_map.get_depth_in(var) - assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" + if depth == StackMap.NOT_IN_STACK: + continue if depth != 0: stack_map.swap(asm, depth) stack_map.pop() diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index be4eebe42e..3c578e8074 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -34,7 +34,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: removeList = [] for bb in ctx.basic_blocks: for i, inst in enumerate(bb.instructions[:-1]): - if inst.opcode in ["call", "invoke", "sload", "sstore"]: + if inst.opcode in ["call", "invoke", "sload", "sstore", "dbname"]: continue if inst.ret and inst.ret.target not in bb.instructions[i + 1].liveness: removeList.append(inst) @@ -90,6 +90,15 @@ def _calculate_in_set(ctx: IRFunction) -> None: bb.out_vars = set() bb.phi_vars = {} + entry_block = ( + ctx.basic_blocks[0] + if ctx.basic_blocks[0].instructions[0].opcode != "deploy" + else ctx.basic_blocks[1] + ) + for bb in ctx.basic_blocks: + if "selector_bucket_" in bb.label.value or bb.label.value == "fallback": + bb.add_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] From c479f046d5823e55796a0fdd4ed7a0f845d3ae3f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 10:19:32 +0300 Subject: [PATCH 128/471] ds --- vyper/codegen/dfg.py | 13 ++++++------- vyper/ir/ir_to_bb_pass.py | 8 ++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 50f72ddc83..9f5f29e753 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -111,13 +111,12 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: # Append data segment data_segments = {} - for bb in ctx.basic_blocks: - for inst in bb.instructions: - 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}") + 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]) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 4e37f0dbfb..e3ff90dba0 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -333,16 +333,16 @@ def _convert_ir_basicblock( return IRLabel(ir.args[0].value, True) elif ir.value == "data": label = IRLabel(ir.args[0].value) - ctx.append_instruction("dbname", [label], False) + ctx.append_data("dbname", [label]) for c in ir.args[1:]: if isinstance(c, int): assert 0 <= c <= 255, "data with invalid size" - ctx.append_instruction("db", [c], False) + ctx.append_data("db", [c]) elif isinstance(c, bytes): - ctx.append_instruction("db", [c], False) + ctx.append_data("db", [c]) elif isinstance(c, IRnode): data = _convert_ir_basicblock(ctx, c, symbols) - ctx.append_instruction("db", [data], False) + ctx.append_data("db", [data]) elif ir.value == "assert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) current_bb = ctx.get_basic_block() From 1d30d50692af305099708ba7b0c7f12f4a2a6324 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 10:45:50 +0300 Subject: [PATCH 129/471] assert --- vyper/codegen/dfg.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 9f5f29e753..87d8ee8b43 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -144,8 +144,9 @@ def _generate_evm_for_basicblock_r( for var in in_vars: depth = stack_map.get_depth_in(var) - if depth == StackMap.NOT_IN_STACK: - continue + assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" + # if depth == StackMap.NOT_IN_STACK: + # continue if depth != 0: stack_map.swap(asm, depth) stack_map.pop() From 7ecf6360cf95b3a23453f8dfc7a6f6bf7a7fe2f1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 10:49:10 +0300 Subject: [PATCH 130/471] no liveness printout --- vyper/codegen/ir_basicblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 5bb8151f2d..dfaa1c6bbf 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -216,8 +216,8 @@ def __repr__(self) -> str: if self.dbg: return s + f" {self.dbg}" - if self.liveness: - return f"{s: <30} # {self.liveness}" + # if self.liveness: + # return f"{s: <30} # {self.liveness}" return s From 077fbc37f272ee8ce9093a83bd0575acc7c3c40a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 11:09:59 +0300 Subject: [PATCH 131/471] single works again --- vyper/codegen/dfg.py | 8 ++++---- vyper/codegen/ir_basicblock.py | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 87d8ee8b43..1dc53b105d 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -144,9 +144,9 @@ def _generate_evm_for_basicblock_r( for var in in_vars: depth = stack_map.get_depth_in(var) - assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" - # if depth == StackMap.NOT_IN_STACK: - # continue + # assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" + if depth == StackMap.NOT_IN_STACK: + continue if depth != 0: stack_map.swap(asm, depth) stack_map.pop() @@ -328,7 +328,7 @@ def _generate_evm_for_instruction_r( def _emit_input_operands( ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap ) -> None: - ops = inst.get_input_operands() + ops = inst.operands for op in ops: if op.is_label: assembly.append(f"_sym_{op.value}") diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index dfaa1c6bbf..f17cb31f26 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -184,8 +184,7 @@ def get_input_operands(self) -> list[IROperand]: """ Get all input operands in instruction. """ - return self.operands - # return [op for op in self.operands if not op.is_label] + return [op for op in self.operands if not op.is_label] def get_input_variables(self) -> list[IRVariable]: """ @@ -216,8 +215,8 @@ def __repr__(self) -> str: if self.dbg: return s + f" {self.dbg}" - # if self.liveness: - # return f"{s: <30} # {self.liveness}" + if self.liveness: + return f"{s: <30} # {self.liveness}" return s From fc9fcfbede5f53ec41b73160551a59d03bdfbea8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 11:11:22 +0300 Subject: [PATCH 132/471] conditionaly print ds --- vyper/codegen/ir_function.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 1d879c48f0..8ed38043cb 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -132,9 +132,10 @@ def __repr__(self) -> str: str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: str += f"{bb}\n" - str += f"Data segment:\n" - for inst in self.data_segment: - str += f"{inst}\n" + if len(self.data_segment) > 0: + str += f"Data segment:\n" + for inst in self.data_segment: + str += f"{inst}\n" return str From e5b263c668ed734cf747d946e0b90473aa2856cf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 12:13:10 +0300 Subject: [PATCH 133/471] ds close --- vyper/codegen/dfg.py | 4 +++- vyper/ir/ir_to_bb_pass.py | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 1dc53b105d..c2439142cd 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -9,7 +9,7 @@ "calldatasize", "calldatacopy", "calldataload", - "codecopy", + # "codecopy", "gas", "returndatasize", "returndatacopy", @@ -247,6 +247,8 @@ def _generate_evm_for_instruction_r( pass elif opcode == "dbname": pass + elif opcode == "codecopy": + assembly.append("CODECOPY") elif opcode == "jnz": assembly.append(f"_sym_{inst.operands[1].value}") assembly.append("JUMPI") diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index e3ff90dba0..c2ff648510 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -320,14 +320,17 @@ def _convert_ir_basicblock( elif ir.value == "codecopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) if arg_0.is_literal and arg_0.value == 30: - arg_0_var = IRVariable("%ccd") + arg_0_var = IRVariable("%ccd", IRVariable.MemType.MEMORY, 0) symbols[f"&0"] = arg_0_var + arg_0_op = IROperand(arg_0_var, True, 30) else: arg_0_var = ctx.get_next_variable() symbols[f"&{arg_0.value}"] = arg_0_var + arg_0_op = IROperand(arg_0_var, True, 0) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) size = _convert_ir_basicblock(ctx, ir.args[2], symbols) - inst = IRInstruction("codecopy", [arg_1, size], arg_0_var) + inst = IRInstruction("codecopy", [arg_1, size], arg_0_op) ctx.get_basic_block().append_instruction(inst) elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) From 2317022ce384bc397d16380a7f7ec5a0d62a3e36 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 12:28:36 +0300 Subject: [PATCH 134/471] remove not needed --- vyper/ir/bb_optimizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 3c578e8074..7580e40d11 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -34,7 +34,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: removeList = [] for bb in ctx.basic_blocks: for i, inst in enumerate(bb.instructions[:-1]): - if inst.opcode in ["call", "invoke", "sload", "sstore", "dbname"]: + if inst.opcode in ["call", "invoke", "sload", "sstore"]: continue if inst.ret and inst.ret.target not in bb.instructions[i + 1].liveness: removeList.append(inst) From 0899aed50db28e35d87b40f1af1dfab8756ed495 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 13:29:01 +0300 Subject: [PATCH 135/471] operand direction --- vyper/codegen/ir_basicblock.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index f17cb31f26..f0886fa13f 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -96,7 +96,9 @@ class IROperand: IROperand represents an operand of an IR instuction. An operand can be a variable, label, or a constant. """ + Direction = Enum("Direction", ["IN", "OUT"]) target: IRValueBase + direction: Direction = Direction.IN address_access: bool = False address_offset: int = 0 use_count: int = 0 @@ -112,6 +114,7 @@ def __init__( self.address_access = address_access self.address_offset = address_offset self.target = target + self.direction = IROperand.Direction.IN def is_targeting(self, target: IRValueBase) -> bool: return self.target.value == target.value @@ -186,14 +189,22 @@ def get_input_operands(self) -> list[IROperand]: """ return [op for op in self.operands if not op.is_label] - def get_input_variables(self) -> list[IRVariable]: + def get_input_operands(self) -> list[IROperand]: """ - Get all input variables in instruction. + Get all input operands for instruction. """ - return [op.target for op in self.operands if op.is_variable] + return [ + op.target + for op in self.operands + if op.is_variable # and op.direction == IROperand.Direction.IN + ] def get_output_operands(self) -> list[IROperand]: - return [self.ret] if self.ret else [] + output_operands = [self.ret] if self.ret else [] + for op in self.operands: + if op.direction == IROperand.Direction.OUT: + output_operands.append(op) + return output_operands def get_use_count_correction(self, op: IROperand) -> int: use_count_correction = 0 @@ -328,7 +339,7 @@ def calculate_liveness(self) -> None: """ liveness = self.out_vars.copy() for instruction in self.instructions[::-1]: - liveness = liveness.union(instruction.get_input_variables()) + liveness = liveness.union(instruction.get_input_operands()) out = ( instruction.get_output_operands()[0].target if len(instruction.get_output_operands()) > 0 From 58f949c8d322f17217869a141f6f0aeb0963cb71 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 13:29:44 +0300 Subject: [PATCH 136/471] name refactor --- vyper/codegen/dfg.py | 4 ++-- vyper/ir/bb_optimizer.py | 1 - vyper/ir/ir_to_bb_pass.py | 10 +++++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index c2439142cd..197344b15e 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -65,7 +65,7 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: dfg_outputs = {} for bb in ctx.basic_blocks: for inst in bb.instructions: - variables = inst.get_input_variables() + variables = inst.get_input_operands() res = inst.get_output_operands() for v in variables: @@ -194,7 +194,7 @@ def _generate_evm_for_instruction_r( if opcode == "select": ret = inst.get_output_operands()[0] - inputs = inst.get_input_variables() + inputs = inst.get_input_operands() depth = stack_map.get_depth_in(inputs) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" to_be_replaced = stack_map.peek(depth) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 7580e40d11..8fd29c35bf 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -2,7 +2,6 @@ TERMINATOR_IR_INSTRUCTIONS, IRBasicBlock, IRInstruction, - IRLabel, ) from vyper.codegen.ir_function import IRFunction diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index c2ff648510..aeb9996192 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -313,9 +313,12 @@ def _convert_ir_basicblock( arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) size = _convert_ir_basicblock(ctx, ir.args[2], symbols) - new_var = ctx.append_instruction("calldatacopy", [arg_1, size]) + arg_0_var = ctx.get_next_variable() + arg_0_op = IROperand(arg_0, True, arg_0.value) + arg_0_op.direction = IROperand.Direction.OUT + ctx.append_instruction("calldatacopy", [arg_0_op, arg_1, size], False) - symbols[f"&{arg_0.value}"] = new_var + symbols[f"&{arg_0.value}"] = arg_0_var return new_var elif ir.value == "codecopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) @@ -327,10 +330,11 @@ def _convert_ir_basicblock( arg_0_var = ctx.get_next_variable() symbols[f"&{arg_0.value}"] = arg_0_var arg_0_op = IROperand(arg_0_var, True, 0) + arg_0_op.direction = IROperand.Direction.OUT arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) size = _convert_ir_basicblock(ctx, ir.args[2], symbols) - inst = IRInstruction("codecopy", [arg_1, size], arg_0_op) + inst = IRInstruction("codecopy", [arg_0_op, arg_1, size]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) From 28b189ce244ec624dcc6c469ea8895916be6bc4e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 19:03:16 +0300 Subject: [PATCH 137/471] ok --- vyper/codegen/dfg.py | 50 ++++++++++++++++++++++++---------- vyper/codegen/ir_basicblock.py | 7 +++-- vyper/ir/ir_to_bb_pass.py | 27 +++++++++++------- 3 files changed, 56 insertions(+), 28 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 197344b15e..542b14e94b 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,4 +1,10 @@ -from vyper.codegen.ir_basicblock import IRInstruction, IROperand, IRVariable, IRBasicBlock +from vyper.codegen.ir_basicblock import ( + IRInstruction, + IRLiteral, + IROperand, + IRVariable, + IRBasicBlock, +) from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly @@ -69,13 +75,15 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: res = inst.get_output_operands() for v in variables: - v.use_count += 1 + v.target.use_count += 1 dfg_inputs[v.value] = ( - [inst] if dfg_inputs.get(v.value) is None else dfg_inputs[v.value] + [inst] + [inst] + if dfg_inputs.get(v.target.value) is None + else dfg_inputs[v.target.value] + [inst] ) for op in res: - dfg_outputs[op.value] = inst + dfg_outputs[op.target.value] = inst visited_instructions = {IRInstruction} @@ -143,7 +151,7 @@ def _generate_evm_for_basicblock_r( in_vars |= in_bb.out_vars.difference(basicblock.in_vars_for(in_bb)) for var in in_vars: - depth = stack_map.get_depth_in(var) + depth = stack_map.get_depth_in(IROperand(var)) # assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" if depth == StackMap.NOT_IN_STACK: continue @@ -155,7 +163,7 @@ def _generate_evm_for_basicblock_r( fen = 0 for inst in basicblock.instructions: inst.fen = fen - if inst.opcode in ["alloca", "call", "invoke", "sload", "sstore", "assert"]: + if inst.opcode in ["param", "call", "invoke", "sload", "sstore", "assert"]: fen += 1 for inst in basicblock.instructions: @@ -190,7 +198,10 @@ def _generate_evm_for_instruction_r( # generate EVM for op opcode = inst.opcode - operands = inst.get_input_operands() + if opcode in ["jmp", "jnz"]: + operands = inst.get_non_label_operands() + else: + operands = inst.operands if opcode == "select": ret = inst.get_output_operands()[0] @@ -205,13 +216,13 @@ def _generate_evm_for_instruction_r( stack_map.poke(depth, ret.target) return assembly - _emit_input_operands(ctx, assembly, inst, stack_map) + _emit_input_operands(ctx, assembly, inst, operands, stack_map) for op in operands: # final_stack_depth = -(len(operands) - i - 1) ucc = inst.get_use_count_correction(op) assert op.target.use_count >= ucc, "Operand used up" - depth = stack_map.get_depth_in(op.target) + depth = stack_map.get_depth_in(op) assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" needs_copy = op.target.use_count - ucc > 1 if needs_copy: @@ -221,7 +232,7 @@ def _generate_evm_for_instruction_r( for i in range(len(operands)): op = operands[i] final_stack_depth = -(len(operands) - i - 1) - depth = stack_map.get_depth_in(op.target) + depth = stack_map.get_depth_in(op) assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" in_place_var = stack_map.peek(-final_stack_depth) is_in_place = in_place_var.value == op.target.value @@ -243,6 +254,8 @@ def _generate_evm_for_instruction_r( assembly.append(opcode.upper()) elif opcode == "alloca": pass + elif opcode == "param": + pass elif opcode == "store": pass elif opcode == "dbname": @@ -321,16 +334,22 @@ def _generate_evm_for_instruction_r( if inst.ret is not None: assert inst.ret.is_variable, "Return value must be a variable" if inst.ret.target.mem_type == IRVariable.MemType.MEMORY: - assembly.extend([*PUSH(inst.ret.addr)]) - assembly.append("MSTORE") + if inst.ret.address_access: + if inst.opcode != "alloca": # FIXMEEEE + if inst.opcode != "codecopy": + assembly.extend([*PUSH(inst.ret.addr)]) + else: + assembly.extend([*PUSH(inst.ret.addr + 30)]) + else: + assembly.extend([*PUSH(inst.ret.addr)]) + assembly.append("MSTORE") return assembly def _emit_input_operands( - ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap + ctx: IRFunction, assembly: list, inst: IRInstruction, ops: list[IROperand], stack_map: StackMap ) -> None: - ops = inst.operands for op in ops: if op.is_label: assembly.append(f"_sym_{op.value}") @@ -343,7 +362,8 @@ def _emit_input_operands( assembly = _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) if op.is_variable and op.target.mem_type == IRVariable.MemType.MEMORY: if op.address_access: - assembly.extend([*PUSH(op.addr)]) + if inst.opcode != "codecopy": + assembly.extend([*PUSH(op.addr)]) else: assembly.extend([*PUSH(op.addr)]) assembly.append("MLOAD") diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index f0886fa13f..c78cae22fa 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -183,7 +183,7 @@ def get_label_operands(self) -> list[IRLabel]: """ return [op for op in self.operands if op.is_label] - def get_input_operands(self) -> list[IROperand]: + def get_non_label_operands(self) -> list[IROperand]: """ Get all input operands in instruction. """ @@ -194,7 +194,7 @@ def get_input_operands(self) -> list[IROperand]: Get all input operands for instruction. """ return [ - op.target + op for op in self.operands if op.is_variable # and op.direction == IROperand.Direction.IN ] @@ -339,7 +339,8 @@ def calculate_liveness(self) -> None: """ liveness = self.out_vars.copy() for instruction in self.instructions[::-1]: - liveness = liveness.union(instruction.get_input_operands()) + ops = instruction.get_input_operands() + liveness = liveness.union([op.target for op in ops]) out = ( instruction.get_output_operands()[0].target if len(instruction.get_output_operands()) > 0 diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index aeb9996192..36d469700c 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -126,14 +126,14 @@ def _handle_internal_func( for _ in func_t.arguments: new_var = ctx.get_next_variable() - alloca_inst = IRInstruction("alloca", [], new_var) + alloca_inst = IRInstruction("param", [], new_var) bb.append_instruction(alloca_inst) symbols[f"&{old_ir_mempos}"] = new_var old_ir_mempos += 32 # return address new_var = ctx.get_next_variable() - alloca_inst = IRInstruction("alloca", [], new_var) + alloca_inst = IRInstruction("param", [], new_var) bb.append_instruction(alloca_inst) symbols[f"return_pc"] = new_var @@ -315,7 +315,7 @@ def _convert_ir_basicblock( arg_0_var = ctx.get_next_variable() arg_0_op = IROperand(arg_0, True, arg_0.value) - arg_0_op.direction = IROperand.Direction.OUT + # arg_0_op.direction = IROperand.Direction.OUT ctx.append_instruction("calldatacopy", [arg_0_op, arg_1, size], False) symbols[f"&{arg_0.value}"] = arg_0_var @@ -323,18 +323,25 @@ def _convert_ir_basicblock( elif ir.value == "codecopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) if arg_0.is_literal and arg_0.value == 30: - arg_0_var = IRVariable("%ccd", IRVariable.MemType.MEMORY, 0) - symbols[f"&0"] = arg_0_var + arg_0_var = ctx.get_next_variable() + arg_0_var.mem_type = IRVariable.MemType.MEMORY + arg_0_var.mem_addr = 0 + alloca_op = IROperand(arg_0_var, True, 0) + ctx.get_basic_block().append_instruction(IRInstruction("alloca", [], alloca_op)) arg_0_op = IROperand(arg_0_var, True, 30) else: - arg_0_var = ctx.get_next_variable() - symbols[f"&{arg_0.value}"] = arg_0_var - arg_0_op = IROperand(arg_0_var, True, 0) - arg_0_op.direction = IROperand.Direction.OUT + arg_0_op = IROperand(arg_0, True, 0) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) size = _convert_ir_basicblock(ctx, ir.args[2], symbols) - inst = IRInstruction("codecopy", [arg_0_op, arg_1, size]) + ret_var = IRVariable("%ccd", IRVariable.MemType.MEMORY, 0) + ret_op = IROperand(ret_var, True) + symbols[f"&0"] = ret_var + inst = IRInstruction( + "codecopy", + [size, arg_1, arg_0_op], + ret_op, + ) ctx.get_basic_block().append_instruction(inst) elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) From 2b2731c12ca6f16f7ab32f34e6f9f978b12a1279 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 19:33:05 +0300 Subject: [PATCH 138/471] get_depth_in take operands --- vyper/compiler/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 1db68f41da..ef2035558c 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -1,6 +1,6 @@ from typing import Dict -from vyper.codegen.ir_basicblock import IRValueBase +from vyper.codegen.ir_basicblock import IROperand, IRValueBase from vyper.semantics.types.function import ContractFunctionT @@ -76,19 +76,19 @@ def push(self, op: IRValueBase) -> None: def pop(self, num: int = 1) -> None: del self.stack_map[len(self.stack_map) - num :] - def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: + def get_depth_in(self, op: IROperand | list[IROperand]) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ - assert isinstance(op, IRValueBase) or isinstance( + assert isinstance(op, IROperand) or isinstance( op, list - ), f"get_depth_in takes IRValueBase or list, got '{op}'" + ), f"get_depth_in takes IROperand or list, got '{op}'" for i, stack_op in enumerate(self.stack_map[::-1]): if isinstance(stack_op, IRValueBase): - if isinstance(op, IRValueBase) and stack_op.value == op.value: + if isinstance(op, IROperand) and stack_op.value == op.target.value: return -i - elif isinstance(op, list) and stack_op in op: + elif isinstance(op, list) and stack_op in [v.target for v in op]: return -i return StackMap.NOT_IN_STACK From 75ae306e65d64c983217a1adea3362f079124dbc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 4 Sep 2023 19:33:30 +0300 Subject: [PATCH 139/471] selfdestruct --- vyper/codegen/dfg.py | 3 +++ vyper/ir/bb_optimizer.py | 2 +- vyper/ir/ir_to_bb_pass.py | 12 ++++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 542b14e94b..7a0f1e5860 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -25,6 +25,7 @@ "sstore", "timestamp", "caller", + "selfdestruct", "shr", "shl", "and", @@ -203,6 +204,8 @@ def _generate_evm_for_instruction_r( else: operands = inst.operands + operands = operands[::-1] + if opcode == "select": ret = inst.get_output_operands()[0] inputs = inst.get_input_operands() diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 8fd29c35bf..29878c65eb 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -33,7 +33,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: removeList = [] for bb in ctx.basic_blocks: for i, inst in enumerate(bb.instructions[:-1]): - if inst.opcode in ["call", "invoke", "sload", "sstore"]: + if inst.opcode in ["call", "selfdestruct", "invoke", "sload", "sstore"]: continue if inst.ret and inst.ret.target not in bb.instructions[i + 1].liveness: removeList.append(inst) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 36d469700c..62a759f606 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -219,11 +219,11 @@ def _convert_ir_basicblock( retSize = _convert_ir_basicblock(ctx, ir.args[6], symbols) if argsOffset.is_literal: - addr = argsOffset.value - 32 + 4 + addr = argsOffset.value - 32 + 4 if argsOffset.value > 0 else 0 argsOffsetVar = symbols.get(f"&{addr}", argsOffset.value) argsOffsetVar.mem_type = IRVariable.MemType.MEMORY argsOffsetVar.mem_addr = addr - argsOffsetOp = IROperand(argsOffsetVar, True, 32 - 4) + argsOffsetOp = IROperand(argsOffsetVar, True, 32 - 4 if argsOffset.value > 0 else 0) retVar = ctx.get_next_variable(IRVariable.MemType.MEMORY, retOffset.value) symbols[f"&{retOffset.value}"] = retVar @@ -544,6 +544,9 @@ def _convert_ir_basicblock( symbols[f"&{arg_0.value}"] = new_var return new_var + elif ir.value == "selfdestruct": + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + ctx.append_instruction("selfdestruct", [arg_0], False) elif isinstance(ir.value, str) and ir.value.startswith("log"): # count = int(ir.value[3:]) args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] @@ -563,8 +566,9 @@ def _convert_ir_basicblock( def _convert_ir_opcode(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> None: opcode = str(ir.value).upper() + inst_args = [] for arg in ir.args: if isinstance(arg, IRnode): - _convert_ir_basicblock(ctx, arg, symbols) - instruction = IRInstruction(opcode, ir.args) + inst_args.append(_convert_ir_basicblock(ctx, arg, symbols)) + instruction = IRInstruction(opcode, inst_args) ctx.get_basic_block().append_instruction(instruction) From 25a885a2bac61f6b570cc05c40c1b51c65c8f758 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 5 Sep 2023 00:05:40 +0300 Subject: [PATCH 140/471] exit_to detour --- vyper/codegen/dfg.py | 1 + vyper/codegen/ir_basicblock.py | 2 +- vyper/codegen/return_.py | 4 +++- vyper/ir/ir_to_bb_pass.py | 9 +++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 7a0f1e5860..941dad9be6 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -26,6 +26,7 @@ "timestamp", "caller", "selfdestruct", + "stop", "shr", "shl", "and", diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index c78cae22fa..215c70ea43 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -2,7 +2,7 @@ from enum import Enum TERMINAL_IR_INSTRUCTIONS = ["ret", "revert", "assert"] -TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "return", "revert", "deploy"] +TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"] if TYPE_CHECKING: from vyper.codegen.ir_function import IRFunction diff --git a/vyper/codegen/return_.py b/vyper/codegen/return_.py index 56bea2b8da..41fa11ab56 100644 --- a/vyper/codegen/return_.py +++ b/vyper/codegen/return_.py @@ -40,7 +40,9 @@ def finalize(fill_return_buffer): cleanup_loops = "cleanup_repeat" if context.forvars else "seq" # NOTE: because stack analysis is incomplete, cleanup_repeat must # come after fill_return_buffer otherwise the stack will break - return IRnode.from_list(["seq", fill_return_buffer, cleanup_loops, jump_to_exit]) + jump_to_exit_ir = IRnode.from_list(jump_to_exit) + jump_to_exit_ir.passthrough_metadata["func_t"] = func_t + return IRnode.from_list(["seq", fill_return_buffer, cleanup_loops, jump_to_exit_ir]) if context.return_type is None: if context.is_internal: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 62a759f606..adc9081048 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -371,6 +371,15 @@ def _convert_ir_basicblock( ctx.append_basic_block(bb) _convert_ir_basicblock(ctx, ir.args[2], symbols) elif ir.value == "exit_to": + func_t = ir.passthrough_metadata.get("func_t", None) + assert func_t is not None, "exit_to without func_t" + + if func_t.is_external: + if func_t.return_type == None: + inst = IRInstruction("stop", []) + ctx.get_basic_block().append_instruction(inst) + return + if len(ir.args) == 1: inst = IRInstruction("ret", []) ctx.get_basic_block().append_instruction(inst) From daab79b8e52bd841ad83feb9171860ca68ce88a9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 5 Sep 2023 00:06:48 +0300 Subject: [PATCH 141/471] constructor support --- vyper/ir/bb_optimizer.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 29878c65eb..aa0b6ea489 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -89,11 +89,22 @@ def _calculate_in_set(ctx: IRFunction) -> None: bb.out_vars = set() bb.phi_vars = {} - entry_block = ( - ctx.basic_blocks[0] - if ctx.basic_blocks[0].instructions[0].opcode != "deploy" - else ctx.basic_blocks[1] - ) + 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: + entry_block = after_deploy_bb + has_constructor = True if ctx.basic_blocks[0].instructions[0].opcode != "deploy" else False + if has_constructor: + deploy_bb.add_in(ctx.basic_blocks[0]) + entry_block.add_in(deploy_bb) + else: + entry_block = ctx.basic_blocks[0] + for bb in ctx.basic_blocks: if "selector_bucket_" in bb.label.value or bb.label.value == "fallback": bb.add_in(entry_block) From a5862c0546a3885abe39c0313229ec77c7e1c52e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 5 Sep 2023 17:49:33 +0300 Subject: [PATCH 142/471] safe_remote_purchase --- vyper/codegen/dfg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 941dad9be6..f8444fd329 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -205,7 +205,7 @@ def _generate_evm_for_instruction_r( else: operands = inst.operands - operands = operands[::-1] + # operands = operands[::-1] if opcode == "select": ret = inst.get_output_operands()[0] @@ -220,7 +220,7 @@ def _generate_evm_for_instruction_r( stack_map.poke(depth, ret.target) return assembly - _emit_input_operands(ctx, assembly, inst, operands, stack_map) + _emit_input_operands(ctx, assembly, inst, operands[::-1], stack_map) for op in operands: # final_stack_depth = -(len(operands) - i - 1) From e302f1077d01d348b2563957c07d0a197a1f7bc4 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 5 Sep 2023 12:09:47 -0400 Subject: [PATCH 143/471] replace set with OrderedSet() --- vyper/codegen/dfg.py | 10 +++++----- vyper/codegen/ir_basicblock.py | 19 ++++++++++--------- vyper/compiler/utils.py | 2 +- vyper/ir/bb_optimizer.py | 13 +++++-------- vyper/ir/ir_to_bb_pass.py | 16 +++------------- vyper/utils.py | 19 +++++++++++++++++++ 6 files changed, 43 insertions(+), 36 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index f8444fd329..e9867b2e4a 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -8,7 +8,7 @@ from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly -from vyper.utils import MemoryPositions +from vyper.utils import MemoryPositions, OrderedSet ONE_TO_ONE_INSTRUCTIONS = [ "revert", @@ -148,14 +148,14 @@ def _generate_evm_for_basicblock_r( asm.append("JUMPDEST") # values to pop from stack - in_vars = set() + in_vars = OrderedSet() for in_bb in basicblock.in_set: in_vars |= in_bb.out_vars.difference(basicblock.in_vars_for(in_bb)) for var in in_vars: depth = stack_map.get_depth_in(IROperand(var)) # assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" - if depth == StackMap.NOT_IN_STACK: + if depth is StackMap.NOT_IN_STACK: continue if depth != 0: stack_map.swap(asm, depth) @@ -227,7 +227,7 @@ def _generate_evm_for_instruction_r( ucc = inst.get_use_count_correction(op) assert op.target.use_count >= ucc, "Operand used up" depth = stack_map.get_depth_in(op) - assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" + assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" needs_copy = op.target.use_count - ucc > 1 if needs_copy: stack_map.dup(assembly, depth) @@ -237,7 +237,7 @@ def _generate_evm_for_instruction_r( op = operands[i] final_stack_depth = -(len(operands) - i - 1) depth = stack_map.get_depth_in(op) - assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" + assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" in_place_var = stack_map.peek(-final_stack_depth) is_in_place = in_place_var.value == op.target.value diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 215c70ea43..8b8decf9f8 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Optional from enum import Enum +from vyper.utils import OrderedSet TERMINAL_IR_INSTRUCTIONS = ["ret", "revert", "assert"] TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"] @@ -258,9 +259,9 @@ class IRBasicBlock: label: IRLabel parent: "IRFunction" instructions: list[IRInstruction] - in_set: set["IRBasicBlock"] - out_set: set["IRBasicBlock"] - out_vars: set[IRVariable] + in_set: OrderedSet["IRBasicBlock"] + out_set: OrderedSet["IRBasicBlock"] + out_vars: OrderedSet[IRVariable] phi_vars: dict[str, IRVariable] def __init__(self, label: IRLabel, parent: "IRFunction") -> None: @@ -268,15 +269,15 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.label = label self.parent = parent self.instructions = [] - self.in_set = set() - self.out_set = set() - self.out_vars = set() + self.in_set = OrderedSet() + self.out_set = OrderedSet() + self.out_vars = OrderedSet() self.phi_vars = {} def add_in(self, bb: "IRBasicBlock") -> None: self.in_set.add(bb) - def union_in(self, bb_set: set["IRBasicBlock"]) -> None: + def union_in(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: self.in_set = self.in_set.union(bb_set) def remove_in(self, bb: "IRBasicBlock") -> None: @@ -285,7 +286,7 @@ def remove_in(self, bb: "IRBasicBlock") -> None: def add_out(self, bb: "IRBasicBlock") -> None: self.out_set.add(bb) - def union_out(self, bb_set: set["IRBasicBlock"]) -> None: + def union_out(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: self.out_set = self.out_set.union(bb_set) def remove_out(self, bb: "IRBasicBlock") -> None: @@ -340,7 +341,7 @@ def calculate_liveness(self) -> None: liveness = self.out_vars.copy() for instruction in self.instructions[::-1]: ops = instruction.get_input_operands() - liveness = liveness.union([op.target for op in ops]) + liveness = liveness.union(OrderedSet.fromkeys([op.target for op in ops])) out = ( instruction.get_output_operands()[0].target if len(instruction.get_output_operands()) > 0 diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index ef2035558c..866a7eefda 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -49,7 +49,7 @@ def _expand_row(row): class StackMap: - NOT_IN_STACK = 1 + NOT_IN_STACK = object() stack_map: list[IRValueBase] def __init__(self): diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index aa0b6ea489..39a0e8c095 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -1,9 +1,6 @@ -from vyper.codegen.ir_basicblock import ( - TERMINATOR_IR_INSTRUCTIONS, - IRBasicBlock, - IRInstruction, -) +from vyper.codegen.ir_basicblock import TERMINATOR_IR_INSTRUCTIONS, IRBasicBlock, IRInstruction from vyper.codegen.ir_function import IRFunction +from vyper.utils import OrderedSet def optimize_function(ctx: IRFunction): @@ -84,9 +81,9 @@ def _calculate_in_set(ctx: IRFunction) -> None: Calculate in set for each basic block. """ for bb in ctx.basic_blocks: - bb.in_set = set() - bb.out_set = set() - bb.out_vars = set() + bb.in_set = OrderedSet() + bb.out_set = OrderedSet() + bb.out_vars = OrderedSet() bb.phi_vars = {} deploy_bb = None diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index adc9081048..4aa142c00c 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -140,11 +140,7 @@ def _handle_internal_func( return ir.args[0].args[2] -def _convert_ir_simple_node( - ctx: IRFunction, - ir: IRnode, - symbols: SymbolTable, -) -> IRVariable: +def _convert_ir_simple_node(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> IRVariable: args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] return ctx.append_instruction(ir.value, args) @@ -154,9 +150,7 @@ def _convert_ir_simple_node( def _convert_ir_basicblock( - ctx: IRFunction, - ir: IRnode, - symbols: SymbolTable, + ctx: IRFunction, ir: IRnode, symbols: SymbolTable ) -> Optional[IRVariable]: global _break_target, _continue_target # symbols = symbols.copy() @@ -337,11 +331,7 @@ def _convert_ir_basicblock( ret_var = IRVariable("%ccd", IRVariable.MemType.MEMORY, 0) ret_op = IROperand(ret_var, True) symbols[f"&0"] = ret_var - inst = IRInstruction( - "codecopy", - [size, arg_1, arg_0_op], - ret_op, - ) + inst = IRInstruction("codecopy", [size, arg_1, arg_0_op], ret_op) ctx.get_basic_block().append_instruction(inst) elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) diff --git a/vyper/utils.py b/vyper/utils.py index 3d9d9cb416..82fa0e2656 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -20,9 +20,28 @@ class OrderedSet(dict): functionality as needed. """ + def __repr__(self): + keys = ", ".join(repr(k) for k in self.keys()) + return f"OrderedSet({{{keys}}})" + def add(self, item): self[item] = None + def remove(self, item): + del self[item] + + def difference(self, other): + ret = self.copy() + for k in other.keys(): + ret.pop(k, None) + return ret + + def union(self, other): + return self.__class__(self | other) + + def copy(self): + return self.__class__(super().copy()) + class DecimalContextOverride(decimal.Context): def __setattr__(self, name, value): From 9ce9a1731e5650a35483a8ab62e944b2623bc340 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 7 Sep 2023 16:14:45 +0300 Subject: [PATCH 144/471] fix return external, dload --- vyper/codegen/ir_basicblock.py | 4 ++ vyper/ir/ir_to_bb_pass.py | 77 ++++++++++++++++++++++------------ 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 8b8decf9f8..f424a5d2ae 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -72,6 +72,8 @@ class IRVariable(IRValueBase): def __init__( self, value: IRValueBaseValue, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: int = -1 ) -> None: + if isinstance(value, IRLiteral): + value = value.value super().__init__(value) self.mem_type = mem_type self.mem_addr = mem_addr @@ -108,6 +110,8 @@ def __init__( self, target: IRValueBase, address_access: bool = False, address_offset: int = 0 ) -> None: assert isinstance(target, IRValueBase), "value must be an IRValueBase" + assert isinstance(address_access, bool), "address_access must be a bool" + assert isinstance(address_offset, int), "address_offset must be an int" if address_access: assert ( isinstance(target, IRVariable) and target.mem_type == IRVariable.MemType.MEMORY diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 4aa142c00c..c1cfaf8aea 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -15,6 +15,7 @@ from vyper.compiler.settings import OptimizationLevel from vyper.evm.opcodes import get_opcodes from vyper.ir.bb_optimizer import optimize_function +from vyper.ir.compile_ir import is_mem_sym, is_symbol from vyper.semantics.types.function import ContractFunctionT BINARY_IR_INSTRUCTIONS = [ @@ -33,6 +34,7 @@ "mul", "div", "mod", + "exp", "sha3", "sha3_64", ] @@ -304,16 +306,20 @@ def _convert_ir_basicblock( elif ir.value == "calldatacopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_0.mem_type = IRVariable.MemType.MEMORY + arg_0.mem_addr = int(arg_0.value[1:]) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) size = _convert_ir_basicblock(ctx, ir.args[2], symbols) arg_0_var = ctx.get_next_variable() - arg_0_op = IROperand(arg_0, True, arg_0.value) + arg_0_var.mem_type = IRVariable.MemType.MEMORY + arg_0_var.mem_addr = arg_0.mem_addr + arg_0_op = IROperand(arg_0, True) # arg_0_op.direction = IROperand.Direction.OUT ctx.append_instruction("calldatacopy", [arg_0_op, arg_1, size], False) - symbols[f"&{arg_0.value}"] = arg_0_var - return new_var + symbols[f"&{arg_0_var.mem_addr}"] = arg_0_var + return arg_0_var elif ir.value == "codecopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) if arg_0.is_literal and arg_0.value == 30: @@ -369,32 +375,31 @@ def _convert_ir_basicblock( inst = IRInstruction("stop", []) ctx.get_basic_block().append_instruction(inst) return + else: + last_ir = None + ret_var = ir.args[1] + for arg in ir.args[2:]: + last_ir = _convert_ir_basicblock(ctx, arg, symbols) + + ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols) + new_var = symbols.get(f"&{ret_ir.value}", IRVariable(ret_ir)) + new_var.mem_type = IRVariable.MemType.MEMORY + new_var.mem_addr = ret_ir.value + new_op = IROperand(new_var, True) + inst = IRInstruction("return", [last_ir, new_op]) + ctx.get_basic_block().append_instruction(inst) + ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) - if len(ir.args) == 1: - inst = IRInstruction("ret", []) - ctx.get_basic_block().append_instruction(inst) - elif len(ir.args) >= 2: - ret_var = ir.args[1] - if ret_var.value == "return_pc": + if func_t.is_internal: + assert ir.args[1].value == "return_pc", "return_pc not found" + if func_t.return_type == None: + inst = IRInstruction("ret", [symbols["return_pc"]]) + else: + ret_var = ir.args[1] inst = IRInstruction("ret", [symbols["return_buffer"], symbols["return_pc"]]) - ctx.get_basic_block().append_instruction(inst) - return None - # else: - # new_var = ctx.get_next_variable() - # symbols[f"&{ret_var.value}"] = new_var - - last_ir = None - for arg in ir.args[2:]: - last_ir = _convert_ir_basicblock(ctx, arg, symbols) - - ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols) - new_var = symbols.get(f"&{ret_ir.value}", ret_ir) - new_var.mem_type = IRVariable.MemType.MEMORY - new_var.mem_addr = ret_ir.value - new_op = IROperand(new_var, True) - inst = IRInstruction("return", [last_ir, new_op]) + ctx.get_basic_block().append_instruction(inst) - ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) + elif ir.value == "revert": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) @@ -404,7 +409,15 @@ def _convert_ir_basicblock( elif ir.value == "dload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) return ctx.append_instruction("calldataload", [arg_0]) + elif ir.value == "dloadbytes": + src = _convert_ir_basicblock(ctx, ir.args[0], symbols) + dst = _convert_ir_basicblock(ctx, ir.args[1], symbols) + len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols) + ret = ctx.get_next_variable() + inst = IRInstruction("codecopy", [len_, src, dst], ret) + ctx.get_basic_block().append_instruction(inst) + return ret elif ir.value == "mload": sym = ir.args[0] if sym.is_literal: @@ -571,3 +584,15 @@ def _convert_ir_opcode(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> Non inst_args.append(_convert_ir_basicblock(ctx, arg, symbols)) instruction = IRInstruction(opcode, inst_args) ctx.get_basic_block().append_instruction(instruction) + + +def _data_ofst_of(sym, ofst, height_): + # e.g. _OFST _sym_foo 32 + assert is_symbol(sym) or is_mem_sym(sym) + if isinstance(ofst.value, int): + # resolve at compile time using magic _OFST op + return ["_OFST", sym, ofst.value] + else: + # if we can't resolve at compile time, resolve at runtime + # ofst = _compile_to_assembly(ofst, withargs, existing_labels, break_dest, height_) + return ofst + [sym, "ADD"] From 1f1fe2ca57789617980a77efe3fdad33f2b100c7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 7 Sep 2023 16:18:39 +0300 Subject: [PATCH 145/471] less verbose orderedset --- vyper/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/utils.py b/vyper/utils.py index 82fa0e2656..0e6573e250 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -22,7 +22,7 @@ class OrderedSet(dict): def __repr__(self): keys = ", ".join(repr(k) for k in self.keys()) - return f"OrderedSet({{{keys}}})" + return f"{{{keys}}}" def add(self, item): self[item] = None From ee0c2a16759dbb89a3bd55af6bc3c8a499fe188a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 8 Sep 2023 15:55:21 +0300 Subject: [PATCH 146/471] cleanup phi compute --- vyper/codegen/dfg.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index e9867b2e4a..0fbbc7a0e8 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -37,6 +37,7 @@ "mul", "div", "mod", + "exp", "eq", "iszero", "lg", @@ -88,18 +89,7 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: dfg_outputs[op.target.value] = inst -visited_instructions = {IRInstruction} -visited_basicblocks = {IRBasicBlock} - - -def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: - global visited_instructions, visited_basicblocks - asm = [] - visited_instructions = set() - visited_basicblocks = set() - - convert_ir_to_dfg(ctx) - +def compute_phi_vars(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: for inst in bb.instructions: if inst.opcode != "select": @@ -113,6 +103,20 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: block_a.phi_vars[ret_op.value] = inst.operands[3] block_b.phi_vars[ret_op.value] = inst.operands[1] + +visited_instructions = {IRInstruction} +visited_basicblocks = {IRBasicBlock} + + +def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: + global visited_instructions, visited_basicblocks + asm = [] + visited_instructions = set() + visited_basicblocks = set() + + convert_ir_to_dfg(ctx) + compute_phi_vars(ctx) + _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackMap()) # Append postambles @@ -205,8 +209,6 @@ def _generate_evm_for_instruction_r( else: operands = inst.operands - # operands = operands[::-1] - if opcode == "select": ret = inst.get_output_operands()[0] inputs = inst.get_input_operands() From 4c9204751ee7219dbc6f511867679bbc70353400 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 10 Sep 2023 19:44:14 +0300 Subject: [PATCH 147/471] fixups --- vyper/codegen/ir_basicblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index f424a5d2ae..ce0ec47f4e 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -37,6 +37,7 @@ class IRValueBase: def __init__(self, value: IRValueBaseValue) -> None: assert isinstance(value, IRValueBaseValue), "value must be an IRValueBaseValue" self.value = value + self.use_count = 0 @property def is_literal(self) -> bool: @@ -53,7 +54,7 @@ class IRLiteral(IRValueBase): def __init__(self, value: IRValueBaseValue) -> None: super().__init__(value) - self.use_count = 1 + self.use_count = 0 @property def is_literal(self) -> bool: @@ -104,7 +105,6 @@ class IROperand: direction: Direction = Direction.IN address_access: bool = False address_offset: int = 0 - use_count: int = 0 def __init__( self, target: IRValueBase, address_access: bool = False, address_offset: int = 0 From ae4d78fbedcf37c295165133c852a4b9a549f23b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 10 Sep 2023 19:44:39 +0300 Subject: [PATCH 148/471] fix handling of dynamic jumps --- vyper/codegen/dfg.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 0fbbc7a0e8..075db6fb3b 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -78,11 +78,12 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: res = inst.get_output_operands() for v in variables: - v.target.use_count += 1 - dfg_inputs[v.value] = ( + target = v.target + target.use_count += 1 + dfg_inputs[target.value] = ( [inst] - if dfg_inputs.get(v.target.value) is None - else dfg_inputs[v.target.value] + [inst] + if dfg_inputs.get(target.value) is None + else dfg_inputs[target.value] + [inst] ) for op in res: @@ -215,8 +216,8 @@ def _generate_evm_for_instruction_r( depth = stack_map.get_depth_in(inputs) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" to_be_replaced = stack_map.peek(depth) + to_be_replaced.use_count -= 1 if to_be_replaced.use_count > 1: - to_be_replaced.use_count -= 1 stack_map.push(ret.target) else: stack_map.poke(depth, ret.target) @@ -224,7 +225,9 @@ def _generate_evm_for_instruction_r( _emit_input_operands(ctx, assembly, inst, operands[::-1], stack_map) - for op in operands: + stack_ops = [op for op in operands if op.is_label == False or inst.opcode == "jmp"] + + for op in stack_ops: # final_stack_depth = -(len(operands) - i - 1) ucc = inst.get_use_count_correction(op) assert op.target.use_count >= ucc, "Operand used up" @@ -235,9 +238,9 @@ def _generate_evm_for_instruction_r( stack_map.dup(assembly, depth) op.target.use_count -= 1 - for i in range(len(operands)): - op = operands[i] - final_stack_depth = -(len(operands) - i - 1) + for i in range(len(stack_ops)): + op = stack_ops[i] + final_stack_depth = -(len(stack_ops) - i - 1) depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" in_place_var = stack_map.peek(-final_stack_depth) @@ -252,7 +255,7 @@ def _generate_evm_for_instruction_r( stack_map.swap(assembly, depth) stack_map.swap(assembly, final_stack_depth) - stack_map.pop(len(operands)) + stack_map.pop(len(stack_ops)) if inst.ret is not None: stack_map.push(inst.ret.target) @@ -300,6 +303,9 @@ def _generate_evm_for_instruction_r( assert len(inst.operands) == 2, "ret instruction takes one operand" assembly.append("JUMP") elif opcode == "return": + assert ( + inst.operands[1].target.mem_type == IRVariable.MemType.MEMORY + ), "return value must be a variable" assembly.append("RETURN") elif opcode == "select": pass @@ -358,8 +364,9 @@ def _emit_input_operands( ) -> None: for op in ops: if op.is_label: - assembly.append(f"_sym_{op.value}") - stack_map.push(op.target) + if inst.opcode == "jmp": + assembly.append(f"_sym_{op.value}") + stack_map.push(op.target) continue if op.is_literal: assembly.extend([*PUSH(op.value)]) From c58be9c1d88a41c9edd8e49ec5fc115cd65c6a2e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Sep 2023 14:49:59 +0300 Subject: [PATCH 149/471] stack manipulations --- vyper/codegen/dfg.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 075db6fb3b..a8e1d4d002 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -89,6 +89,15 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: for op in res: dfg_outputs[op.target.value] = inst + ## DEBUGING REMOVE + for bb in ctx.basic_blocks: + in_set = bb.in_set + if len(in_set) <= 1: + continue + print("IN", bb.label) + for inbb in in_set: + print(inbb.label, inbb.get_liveness()) + def compute_phi_vars(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: @@ -218,18 +227,24 @@ def _generate_evm_for_instruction_r( to_be_replaced = stack_map.peek(depth) to_be_replaced.use_count -= 1 if to_be_replaced.use_count > 1: - stack_map.push(ret.target) - else: - stack_map.poke(depth, ret.target) + stack_map.dup(assembly, depth) + # stack_map.push(ret.target) + + stack_map.poke(0, ret.target) return assembly - _emit_input_operands(ctx, assembly, inst, operands[::-1], stack_map) + if opcode in ["jnz", "jmp"] and stack_map.get_height() >= 2: + op1 = stack_map.peek(0) + op2 = stack_map.peek(1) + if op2.value in ["%15", "%21"] and op1.value == "%14": + stack_map.swap(assembly, -1) stack_ops = [op for op in operands if op.is_label == False or inst.opcode == "jmp"] + _emit_input_operands(ctx, assembly, inst, stack_ops, stack_map) for op in stack_ops: # final_stack_depth = -(len(operands) - i - 1) - ucc = inst.get_use_count_correction(op) + ucc = 0 # inst.get_use_count_correction(op) assert op.target.use_count >= ucc, "Operand used up" depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" From 0ebb02601c0d293940f67825260b6bb0a1690f0a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Sep 2023 21:41:47 +0300 Subject: [PATCH 150/471] pull out stack rearange function --- vyper/codegen/dfg.py | 55 ++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index a8e1d4d002..ec82caa80e 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -110,8 +110,10 @@ def compute_phi_vars(ctx: IRFunction) -> None: block_a = ctx.get_basic_block(inst.operands[0].value) block_b = ctx.get_basic_block(inst.operands[2].value) - block_a.phi_vars[ret_op.value] = inst.operands[3] - block_b.phi_vars[ret_op.value] = inst.operands[1] + block_a.phi_vars[inst.operands[1].value] = ret_op + block_a.phi_vars[inst.operands[3].value] = ret_op + block_b.phi_vars[inst.operands[1].value] = ret_op + block_b.phi_vars[inst.operands[3].value] = ret_op visited_instructions = {IRInstruction} @@ -151,6 +153,25 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: return asm +def _reorder_stack(assembly: list, stack_map: StackMap, stack_ops: list[IROperand]) -> None: + for i in range(len(stack_ops)): + op = stack_ops[i] + final_stack_depth = -(len(stack_ops) - i - 1) + depth = stack_map.get_depth_in(op) + assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" + in_place_var = stack_map.peek(-final_stack_depth) + is_in_place = in_place_var.value == op.target.value + + if not is_in_place: + if final_stack_depth == 0 and depth != 0: + stack_map.swap(assembly, depth) + elif final_stack_depth != 0 and depth == 0: + stack_map.swap(assembly, final_stack_depth) + else: + stack_map.swap(assembly, depth) + stack_map.swap(assembly, final_stack_depth) + + def _generate_evm_for_basicblock_r( ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackMap ): @@ -228,16 +249,19 @@ def _generate_evm_for_instruction_r( to_be_replaced.use_count -= 1 if to_be_replaced.use_count > 1: stack_map.dup(assembly, depth) - # stack_map.push(ret.target) stack_map.poke(0, ret.target) return assembly if opcode in ["jnz", "jmp"] and stack_map.get_height() >= 2: - op1 = stack_map.peek(0) - op2 = stack_map.peek(1) - if op2.value in ["%15", "%21"] and op1.value == "%14": - stack_map.swap(assembly, -1) + for _, b in enumerate(inst.parent.out_set): + target_stack = b.get_liveness().keys() + target_stack = [b.phi_vars.get(v.value, v) for v in target_stack] + print(target_stack) + op1 = b.phi_vars.get(stack_map.peek(0).value, stack_map.peek(0)) + op2 = b.phi_vars.get(stack_map.peek(1).value, stack_map.peek(1)) + if op2.value in ["%15", "%21"] and op1.value == "%14": + stack_map.swap(assembly, -1) stack_ops = [op for op in operands if op.is_label == False or inst.opcode == "jmp"] _emit_input_operands(ctx, assembly, inst, stack_ops, stack_map) @@ -253,22 +277,7 @@ def _generate_evm_for_instruction_r( stack_map.dup(assembly, depth) op.target.use_count -= 1 - for i in range(len(stack_ops)): - op = stack_ops[i] - final_stack_depth = -(len(stack_ops) - i - 1) - depth = stack_map.get_depth_in(op) - assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" - in_place_var = stack_map.peek(-final_stack_depth) - is_in_place = in_place_var.value == op.target.value - - if not is_in_place: - if final_stack_depth == 0 and depth != 0: - stack_map.swap(assembly, depth) - elif final_stack_depth != 0 and depth == 0: - stack_map.swap(assembly, final_stack_depth) - else: - stack_map.swap(assembly, depth) - stack_map.swap(assembly, final_stack_depth) + _reorder_stack(assembly, stack_map, stack_ops) stack_map.pop(len(stack_ops)) if inst.ret is not None: From e3c3bd46faf1d35b520c97a3da49c95c3c899495 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 11 Sep 2023 23:15:54 +0300 Subject: [PATCH 151/471] stack reordering wip --- vyper/codegen/dfg.py | 49 +++++++++++++++++++--------------- vyper/codegen/ir_basicblock.py | 2 +- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index ec82caa80e..abf9f3ef4e 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -153,14 +153,30 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: return asm -def _reorder_stack(assembly: list, stack_map: StackMap, stack_ops: list[IROperand]) -> None: +def _stack_duplications(assembly: list, stack_map: StackMap, stack_ops: list[IROperand]) -> None: + for op in stack_ops: + # final_stack_depth = -(len(operands) - i - 1) + ucc = 0 # inst.get_use_count_correction(op) + assert op.target.use_count >= ucc, "Operand used up" + depth = stack_map.get_depth_in(op) + assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" + needs_copy = op.target.use_count - ucc > 1 + if needs_copy: + stack_map.dup(assembly, depth) + op.target.use_count -= 1 + + +def _stack_reorder( + assembly: list, stack_map: StackMap, stack_ops: list[IROperand], phi_vars: dict = {} +) -> None: for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) - depth = stack_map.get_depth_in(op) + depth = stack_map.get_depth_in(op, phi_vars) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" in_place_var = stack_map.peek(-final_stack_depth) - is_in_place = in_place_var.value == op.target.value + # is_in_place = in_place_var.value == op.value + is_in_place = depth == final_stack_depth if not is_in_place: if final_stack_depth == 0 and depth != 0: @@ -255,29 +271,20 @@ def _generate_evm_for_instruction_r( if opcode in ["jnz", "jmp"] and stack_map.get_height() >= 2: for _, b in enumerate(inst.parent.out_set): - target_stack = b.get_liveness().keys() - target_stack = [b.phi_vars.get(v.value, v) for v in target_stack] + target_stack = [*b.get_liveness().keys()] + # target_stack = [b.phi_vars.get(v.value, v) for v in target_stack] print(target_stack) - op1 = b.phi_vars.get(stack_map.peek(0).value, stack_map.peek(0)) - op2 = b.phi_vars.get(stack_map.peek(1).value, stack_map.peek(1)) - if op2.value in ["%15", "%21"] and op1.value == "%14": - stack_map.swap(assembly, -1) + _stack_reorder(assembly, stack_map, target_stack, b.phi_vars) + # op1 = b.phi_vars.get(stack_map.peek(0).value, stack_map.peek(0)) + # op2 = b.phi_vars.get(stack_map.peek(1).value, stack_map.peek(1)) + # if op2.value in ["%15", "%21"] and op1.value == "%14": + # stack_map.swap(assembly, -1) stack_ops = [op for op in operands if op.is_label == False or inst.opcode == "jmp"] _emit_input_operands(ctx, assembly, inst, stack_ops, stack_map) - for op in stack_ops: - # final_stack_depth = -(len(operands) - i - 1) - ucc = 0 # inst.get_use_count_correction(op) - assert op.target.use_count >= ucc, "Operand used up" - depth = stack_map.get_depth_in(op) - assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" - needs_copy = op.target.use_count - ucc > 1 - if needs_copy: - stack_map.dup(assembly, depth) - op.target.use_count -= 1 - - _reorder_stack(assembly, stack_map, stack_ops) + _stack_duplications(assembly, stack_map, stack_ops) + _stack_reorder(assembly, stack_map, stack_ops) stack_map.pop(len(stack_ops)) if inst.ret is not None: diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index ce0ec47f4e..2ae0e3855b 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -359,7 +359,7 @@ def get_liveness(self) -> set[IRVariable]: """ Get liveness of basic block. """ - return self.instructions[-1].liveness + return self.instructions[0].liveness def __repr__(self) -> str: s = ( From dbd5d0ba0a1d4aacc48030a16a7de9847af80f96 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 00:05:12 +0300 Subject: [PATCH 152/471] draft works --- vyper/codegen/dfg.py | 7 ++++++- vyper/compiler/utils.py | 26 +++++++++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index abf9f3ef4e..c5f6d91c46 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -169,6 +169,11 @@ def _stack_duplications(assembly: list, stack_map: StackMap, stack_ops: list[IRO def _stack_reorder( assembly: list, stack_map: StackMap, stack_ops: list[IROperand], phi_vars: dict = {} ) -> None: + def f(x): + return phi_vars.get(str(x), x) + + stack_ops = [f(x.value) for x in stack_ops] + stack_ops = list(dict.fromkeys(stack_ops)) for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) @@ -274,7 +279,7 @@ def _generate_evm_for_instruction_r( target_stack = [*b.get_liveness().keys()] # target_stack = [b.phi_vars.get(v.value, v) for v in target_stack] print(target_stack) - _stack_reorder(assembly, stack_map, target_stack, b.phi_vars) + _stack_reorder(assembly, stack_map, target_stack, inst.parent.phi_vars) # op1 = b.phi_vars.get(stack_map.peek(0).value, stack_map.peek(0)) # op2 = b.phi_vars.get(stack_map.peek(1).value, stack_map.peek(1)) # if op2.value in ["%15", "%21"] and op1.value == "%14": diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 866a7eefda..4d3e33bf36 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -76,19 +76,35 @@ def push(self, op: IRValueBase) -> None: def pop(self, num: int = 1) -> None: del self.stack_map[len(self.stack_map) - num :] - def get_depth_in(self, op: IROperand | list[IROperand]) -> int: + def get_depth_in(self, op: IROperand | list[IROperand], phi_vars: dict = {}) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ - assert isinstance(op, IROperand) or isinstance( - op, list + assert ( + isinstance(op, str) + or isinstance(op, int) + or isinstance(op, IRValueBase) + or isinstance(op, IROperand) + or isinstance(op, list) ), f"get_depth_in takes IROperand or list, got '{op}'" + + def f(x): + return str(phi_vars.get(str(x), x)) + + print(type(op)) for i, stack_op in enumerate(self.stack_map[::-1]): + stack_op = stack_op.target if isinstance(stack_op, IROperand) else stack_op if isinstance(stack_op, IRValueBase): - if isinstance(op, IROperand) and stack_op.value == op.target.value: + if isinstance(op, str) and f(stack_op.value) == f(op): + return -i + if isinstance(op, int) and f(stack_op.value) == f(op): + return -i + if isinstance(op, IRValueBase) and f(stack_op.value) == f(op.value): + return -i + if isinstance(op, IROperand) and f(stack_op.value) == f(op.target.value): return -i - elif isinstance(op, list) and stack_op in [v.target for v in op]: + elif isinstance(op, list) and f(stack_op) in [f(v.target) for v in op]: return -i return StackMap.NOT_IN_STACK From d763f65458bd2a4f4363486b609dfb33e49e8382 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 00:09:51 +0300 Subject: [PATCH 153/471] cleanup --- vyper/codegen/dfg.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index c5f6d91c46..ff99deb7f3 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -179,8 +179,6 @@ def f(x): final_stack_depth = -(len(stack_ops) - i - 1) depth = stack_map.get_depth_in(op, phi_vars) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" - in_place_var = stack_map.peek(-final_stack_depth) - # is_in_place = in_place_var.value == op.value is_in_place = depth == final_stack_depth if not is_in_place: @@ -276,14 +274,9 @@ def _generate_evm_for_instruction_r( if opcode in ["jnz", "jmp"] and stack_map.get_height() >= 2: for _, b in enumerate(inst.parent.out_set): - target_stack = [*b.get_liveness().keys()] - # target_stack = [b.phi_vars.get(v.value, v) for v in target_stack] - print(target_stack) + target_stack = b.get_liveness() _stack_reorder(assembly, stack_map, target_stack, inst.parent.phi_vars) - # op1 = b.phi_vars.get(stack_map.peek(0).value, stack_map.peek(0)) - # op2 = b.phi_vars.get(stack_map.peek(1).value, stack_map.peek(1)) - # if op2.value in ["%15", "%21"] and op1.value == "%14": - # stack_map.swap(assembly, -1) + break # FIXME stack_ops = [op for op in operands if op.is_label == False or inst.opcode == "jmp"] _emit_input_operands(ctx, assembly, inst, stack_ops, stack_map) From 6a51230a5f621bd8054d8843754ce4c0540fc160 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 00:12:15 +0300 Subject: [PATCH 154/471] add comments for emition --- vyper/codegen/dfg.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index ff99deb7f3..d44565a585 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -253,6 +253,8 @@ def _generate_evm_for_instruction_r( visited_instructions.add(inst) # generate EVM for op + + # Step 1: Manipulate stack opcode = inst.opcode if opcode in ["jmp", "jnz"]: operands = inst.get_non_label_operands() @@ -278,16 +280,19 @@ def _generate_evm_for_instruction_r( _stack_reorder(assembly, stack_map, target_stack, inst.parent.phi_vars) break # FIXME + # Step 2: Emit instructions input operands stack_ops = [op for op in operands if op.is_label == False or inst.opcode == "jmp"] _emit_input_operands(ctx, assembly, inst, stack_ops, stack_map) _stack_duplications(assembly, stack_map, stack_ops) _stack_reorder(assembly, stack_map, stack_ops) + # Step 3: Push instruction's return value to stack stack_map.pop(len(stack_ops)) if inst.ret is not None: stack_map.push(inst.ret.target) + # Step 4: Emit the EVM instruction(s) if opcode in ONE_TO_ONE_INSTRUCTIONS: assembly.append(opcode.upper()) elif opcode == "alloca": @@ -372,6 +377,7 @@ def _generate_evm_for_instruction_r( else: raise Exception(f"Unknown opcode: {opcode}") + # Step 5: Emit instructions output operands (if any) if inst.ret is not None: assert inst.ret.is_variable, "Return value must be a variable" if inst.ret.target.mem_type == IRVariable.MemType.MEMORY: From 84b2d762f601f4b45affc10740fa0a7cabeca890 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 10:32:12 +0300 Subject: [PATCH 155/471] remove debug info --- vyper/codegen/dfg.py | 9 --------- vyper/compiler/utils.py | 1 - 2 files changed, 10 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index d44565a585..9c1f38dccd 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -89,15 +89,6 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: for op in res: dfg_outputs[op.target.value] = inst - ## DEBUGING REMOVE - for bb in ctx.basic_blocks: - in_set = bb.in_set - if len(in_set) <= 1: - continue - print("IN", bb.label) - for inbb in in_set: - print(inbb.label, inbb.get_liveness()) - def compute_phi_vars(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 4d3e33bf36..2378586ded 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -92,7 +92,6 @@ def get_depth_in(self, op: IROperand | list[IROperand], phi_vars: dict = {}) -> def f(x): return str(phi_vars.get(str(x), x)) - print(type(op)) for i, stack_op in enumerate(self.stack_map[::-1]): stack_op = stack_op.target if isinstance(stack_op, IROperand) else stack_op if isinstance(stack_op, IRValueBase): From df485c7dcdcdfeb1635ba7664623ba7a9b4b1317 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 12:02:35 +0300 Subject: [PATCH 156/471] fix phi stack replacements --- vyper/codegen/dfg.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 9c1f38dccd..b3be65b805 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -257,24 +257,24 @@ def _generate_evm_for_instruction_r( inputs = inst.get_input_operands() depth = stack_map.get_depth_in(inputs) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" - to_be_replaced = stack_map.peek(depth) - to_be_replaced.use_count -= 1 + to_be_replaced = stack_map.peek(-depth) if to_be_replaced.use_count > 1: stack_map.dup(assembly, depth) - - stack_map.poke(0, ret.target) + to_be_replaced.use_count -= 1 + stack_map.poke(0, ret.target) + else: + stack_map.poke(depth, ret.target) return assembly - if opcode in ["jnz", "jmp"] and stack_map.get_height() >= 2: - for _, b in enumerate(inst.parent.out_set): - target_stack = b.get_liveness() - _stack_reorder(assembly, stack_map, target_stack, inst.parent.phi_vars) - break # FIXME - # Step 2: Emit instructions input operands stack_ops = [op for op in operands if op.is_label == False or inst.opcode == "jmp"] _emit_input_operands(ctx, assembly, inst, stack_ops, stack_map) + if opcode in ["jnz", "jmp"] and stack_map.get_height() >= 2: + _, b = next(enumerate(inst.parent.out_set)) + target_stack = b.get_liveness() + _stack_reorder(assembly, stack_map, target_stack, inst.parent.phi_vars) + _stack_duplications(assembly, stack_map, stack_ops) _stack_reorder(assembly, stack_map, stack_ops) From 2dfeefc05a5327e3bff6f3ac786a5fcec71de0c6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 12:17:30 +0300 Subject: [PATCH 157/471] consume unsused return variable from invoke --- vyper/codegen/dfg.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index b3be65b805..03a5d3580a 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -322,6 +322,9 @@ def _generate_evm_for_instruction_r( ] ) label_counter += 1 + if stack_map.peek(-1).use_count == 0: + stack_map.pop() + assembly.append("POP") elif opcode == "call": assembly.append("CALL") elif opcode == "ret": From f2d7a2da98bde5daa40b0c0fefe827e3bd52d5bd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 12:52:08 +0300 Subject: [PATCH 158/471] sanitize stack manipulation utils --- vyper/codegen/dfg.py | 4 ++-- vyper/compiler/utils.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 03a5d3580a..e5a1442ee5 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -257,7 +257,7 @@ def _generate_evm_for_instruction_r( inputs = inst.get_input_operands() depth = stack_map.get_depth_in(inputs) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" - to_be_replaced = stack_map.peek(-depth) + to_be_replaced = stack_map.peek(depth) if to_be_replaced.use_count > 1: stack_map.dup(assembly, depth) to_be_replaced.use_count -= 1 @@ -322,7 +322,7 @@ def _generate_evm_for_instruction_r( ] ) label_counter += 1 - if stack_map.peek(-1).use_count == 0: + if stack_map.peek(0).use_count == 0: stack_map.pop() assembly.append("POP") elif opcode == "call": diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 2378586ded..2e55d506e2 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -112,7 +112,7 @@ def peek(self, depth: int) -> IRValueBase: """ Returns the top of the stack map. """ - return self.stack_map[-depth - 1] + return self.stack_map[depth - 1] def poke(self, depth: int, op: IRValueBase) -> None: """ @@ -120,15 +120,15 @@ def poke(self, depth: int, op: IRValueBase) -> None: """ assert depth <= 0, "Bad depth" assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" - self.stack_map[-depth - 1] = op + self.stack_map[depth - 1] = op def dup(self, assembly: list[str], depth: int) -> None: """ Duplicates the operand at the given depth in the stack map. """ assert depth <= 0, "Cannot dup positive depth" - assembly.append(f"DUP{-depth+1}") - self.stack_map.append(self.peek(-depth)) + assembly.append(f"DUP{-(depth-1)}") + self.stack_map.append(self.peek(depth)) def swap(self, assembly: list[str], depth: int) -> None: """ From d84fc15a358babb9552f002cda219662d26e9117 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 13:53:23 +0300 Subject: [PATCH 159/471] finaly remove use count corrention --- vyper/codegen/dfg.py | 8 +++----- vyper/codegen/ir_basicblock.py | 7 ------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index e5a1442ee5..e2b66b12d9 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -146,13 +146,11 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: def _stack_duplications(assembly: list, stack_map: StackMap, stack_ops: list[IROperand]) -> None: for op in stack_ops: - # final_stack_depth = -(len(operands) - i - 1) - ucc = 0 # inst.get_use_count_correction(op) - assert op.target.use_count >= ucc, "Operand used up" + assert op.target.use_count >= 0, "Operand used up" depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" - needs_copy = op.target.use_count - ucc > 1 - if needs_copy: + if op.target.use_count > 1: + # Operand need duplication stack_map.dup(assembly, depth) op.target.use_count -= 1 diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 2ae0e3855b..424b106bbc 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -211,13 +211,6 @@ def get_output_operands(self) -> list[IROperand]: output_operands.append(op) return output_operands - def get_use_count_correction(self, op: IROperand) -> int: - use_count_correction = 0 - for _, phi in self.parent.phi_vars.items(): - if phi.value == op.target.value: - use_count_correction += 1 - return use_count_correction - def __repr__(self) -> str: s = "" if self.ret: From db1eaae7205d7ad895ae8833f8a0bbe05b8dc7b1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 16:02:28 +0300 Subject: [PATCH 160/471] refactor deployment and constructor emition --- vyper/codegen/dfg.py | 12 +++++++++--- vyper/ir/bb_optimizer.py | 1 - vyper/ir/ir_to_bb_pass.py | 7 ++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index e2b66b12d9..17ad9e2b9a 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -123,8 +123,14 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackMap()) # Append postambles - extent_point = asm if not isinstance(asm[-1], list) else asm[-1] - extent_point.extend(["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"]) + revert_postamble = ["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"] + if isinstance(asm[-1], list) and isinstance(asm[-1][0], RuntimeHeader): + runtime = asm.pop() + + asm.extend(revert_postamble) + if runtime: + runtime.extend(revert_postamble) + asm.append(runtime) # Append data segment data_segments = {} @@ -356,7 +362,7 @@ def _generate_evm_for_instruction_r( elif opcode == "deploy": memsize = inst.operands[0].value padding = inst.operands[2].value - assembly.clear() + assembly.extend( ["_sym_subcode_size", "_sym_runtime_begin", "_mem_deploy_start", "CODECOPY"] ) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 39a0e8c095..0757ef1dc0 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -9,7 +9,6 @@ def optimize_function(ctx: IRFunction): pass _calculate_in_set(ctx) - while ctx.remove_unreachable_blocks(): pass diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index c1cfaf8aea..66dd21d815 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -155,7 +155,6 @@ def _convert_ir_basicblock( ctx: IRFunction, ir: IRnode, symbols: SymbolTable ) -> Optional[IRVariable]: global _break_target, _continue_target - # symbols = symbols.copy() if ir.value in BINARY_IR_INSTRUCTIONS: return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3", "sha3_64"]) @@ -371,6 +370,12 @@ def _convert_ir_basicblock( assert func_t is not None, "exit_to without func_t" if func_t.is_external: + # Hardcoded contructor special case + if func_t.name == "__init__": + label = IRLabel(ir.args[0].value, True) + inst = IRInstruction("jmp", [label]) + ctx.get_basic_block().append_instruction(inst) + return if func_t.return_type == None: inst = IRInstruction("stop", []) ctx.get_basic_block().append_instruction(inst) From 8339800f648ade9ef9fa21dc2454b5051412fc53 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 16:06:37 +0300 Subject: [PATCH 161/471] correct sstore operant order --- vyper/ir/ir_to_bb_pass.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 66dd21d815..d658d1a1ae 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -458,7 +458,7 @@ def _convert_ir_basicblock( elif ir.value == "sstore": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) - inst = IRInstruction("sstore", [arg_0, arg_1]) + inst = IRInstruction("sstore", [arg_1, arg_0]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "unique_symbol": sym = ir.args[0] From 44a88b1dcdec0d22696ea0d549e056ec84d44653 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 18:15:48 +0300 Subject: [PATCH 162/471] deploy special case (temporary) --- test2.py | 82 ++++++++++++++++++++++++++++++++++++++++++++ vyper/codegen/dfg.py | 7 ++-- 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 test2.py diff --git a/test2.py b/test2.py new file mode 100644 index 0000000000..bb6e7ddbce --- /dev/null +++ b/test2.py @@ -0,0 +1,82 @@ +# define the function +def my_func(N): + # define variables + a, b, c = 0, 0, 0 + # define statements + a = 0 + while a < N: + b = a + 1 + c = c + b + a = b * 2 + return c + + +# perform SSA-form analysis using dictionary +ssa_defs = {} +b_count = {} + +for const_obj in my_func.co_consts: + if isinstance(const_obj, type(my_func)): + for i, ss in enumerate(const_obj.co_names): + if ss == "LOAD_GLOBAL" and i + 1 < len(const_obj.co_code): + var_name = const_obj.co_names[const_obj.co_code[i + 1]] + if var_name not in b_count: + b_count[var_name] = 1 + ssa_defs[var_name] = f"{var_name}_{b_count[var_name]}" + else: + b_count[var_name] += 1 + ssa_defs[var_name] = f"{var_name}_{b_count[var_name]}" + const_obj.co_names[i + 1] = ssa_defs[var_name] + + +def ssa_rewrite(var): + if var not in ssa_defs: + b_count[var] = 1 + ssa_defs[var] = f"{var}_{b_count[var]}" + else: + b_count[var] += 1 + ssa_defs[var] = f"{var}_{b_count[var]}" + + +# perform liveness analysis +liveness = {} + + +def calc_liveness(statement, in_set, out_set): + for var in statement.used_variables(): + out_set.add(var) + for var in statement.defined_variables(): + if var in out_set: + out_set.remove(var) + in_set.add(var) + liveness[var] = out_set.copy() + + +# compute SSA-form and liveness information +ssa_defs.clear() +b_count.clear() +liveness.clear() +count = 0 +for stmt in my_func.__code__.co_consts[1].co_consts: + ss = stmt.__class__.__name__ + if ss == "ASSIGNMENT": + ssa_rewrite(stmt.target) + stmt.target = ssa_defs[stmt.target] + ssa_defs[stmt.target] = stmt.source + elif ss == "RETURN": + ssa_rewrite(f"RET{count}") + stmt.value = ssa_defs[stmt.value] + ssa_defs[f"RET{count}"] = stmt.value + count += 1 +ssa_defs["N"] = "N" +for stmt in my_func.__code__.co_consts[1].co_consts: + in_set, out_set = set(), set() + calc_liveness(stmt, in_set, out_set) + +# print the results +print("SSA form:") +for var, defn in ssa_defs.items(): + print(f"{var} : {defn}") +print("Liveness analysis:") +for var, live_set in liveness.items(): + print(f"{var} : {live_set}") diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 17ad9e2b9a..55a6e65c3e 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -124,6 +124,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: # 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() @@ -190,7 +191,7 @@ def _generate_evm_for_basicblock_r( ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackMap ): if basicblock in visited_basicblocks: - return asm + return visited_basicblocks.add(basicblock) asm.append(f"_sym_{basicblock.label}") @@ -362,7 +363,9 @@ def _generate_evm_for_instruction_r( 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"] ) From f0512c9ec6db00823f61574a2a7e2a53ac6e48ae Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 12 Sep 2023 18:51:49 +0300 Subject: [PATCH 163/471] pass immutables size --- vyper/codegen/dfg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 55a6e65c3e..a15e08cf42 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -372,7 +372,7 @@ def _generate_evm_for_instruction_r( 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)]) + assembly.append([RuntimeHeader("_sym_runtime_begin", memsize, padding)]) assembly = assembly[-1] pass else: From 42d586d47ad7cc69396addd8229dfed6567369ab Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 13 Sep 2023 12:57:15 +0300 Subject: [PATCH 164/471] bugfix label emition --- vyper/codegen/dfg.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index a15e08cf42..1a306e3c9e 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -272,19 +272,18 @@ def _generate_evm_for_instruction_r( return assembly # Step 2: Emit instructions input operands - stack_ops = [op for op in operands if op.is_label == False or inst.opcode == "jmp"] - _emit_input_operands(ctx, assembly, inst, stack_ops, stack_map) + _emit_input_operands(ctx, assembly, inst, operands, stack_map) if opcode in ["jnz", "jmp"] and stack_map.get_height() >= 2: _, b = next(enumerate(inst.parent.out_set)) target_stack = b.get_liveness() _stack_reorder(assembly, stack_map, target_stack, inst.parent.phi_vars) - _stack_duplications(assembly, stack_map, stack_ops) - _stack_reorder(assembly, stack_map, stack_ops) + _stack_duplications(assembly, stack_map, operands) + _stack_reorder(assembly, stack_map, operands) # Step 3: Push instruction's return value to stack - stack_map.pop(len(stack_ops)) + stack_map.pop(len(operands)) if inst.ret is not None: stack_map.push(inst.ret.target) @@ -400,9 +399,8 @@ def _emit_input_operands( ) -> None: for op in ops: if op.is_label: - if inst.opcode == "jmp": - assembly.append(f"_sym_{op.value}") - stack_map.push(op.target) + assembly.append(f"_sym_{op.value}") + stack_map.push(op.target) continue if op.is_literal: assembly.extend([*PUSH(op.value)]) From 76ab738b01337702de3ed2f169196476ab3c9cfc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 14 Sep 2023 08:48:31 +0300 Subject: [PATCH 165/471] remove accidentally added file --- test2.py | 82 -------------------------------------------------------- 1 file changed, 82 deletions(-) delete mode 100644 test2.py diff --git a/test2.py b/test2.py deleted file mode 100644 index bb6e7ddbce..0000000000 --- a/test2.py +++ /dev/null @@ -1,82 +0,0 @@ -# define the function -def my_func(N): - # define variables - a, b, c = 0, 0, 0 - # define statements - a = 0 - while a < N: - b = a + 1 - c = c + b - a = b * 2 - return c - - -# perform SSA-form analysis using dictionary -ssa_defs = {} -b_count = {} - -for const_obj in my_func.co_consts: - if isinstance(const_obj, type(my_func)): - for i, ss in enumerate(const_obj.co_names): - if ss == "LOAD_GLOBAL" and i + 1 < len(const_obj.co_code): - var_name = const_obj.co_names[const_obj.co_code[i + 1]] - if var_name not in b_count: - b_count[var_name] = 1 - ssa_defs[var_name] = f"{var_name}_{b_count[var_name]}" - else: - b_count[var_name] += 1 - ssa_defs[var_name] = f"{var_name}_{b_count[var_name]}" - const_obj.co_names[i + 1] = ssa_defs[var_name] - - -def ssa_rewrite(var): - if var not in ssa_defs: - b_count[var] = 1 - ssa_defs[var] = f"{var}_{b_count[var]}" - else: - b_count[var] += 1 - ssa_defs[var] = f"{var}_{b_count[var]}" - - -# perform liveness analysis -liveness = {} - - -def calc_liveness(statement, in_set, out_set): - for var in statement.used_variables(): - out_set.add(var) - for var in statement.defined_variables(): - if var in out_set: - out_set.remove(var) - in_set.add(var) - liveness[var] = out_set.copy() - - -# compute SSA-form and liveness information -ssa_defs.clear() -b_count.clear() -liveness.clear() -count = 0 -for stmt in my_func.__code__.co_consts[1].co_consts: - ss = stmt.__class__.__name__ - if ss == "ASSIGNMENT": - ssa_rewrite(stmt.target) - stmt.target = ssa_defs[stmt.target] - ssa_defs[stmt.target] = stmt.source - elif ss == "RETURN": - ssa_rewrite(f"RET{count}") - stmt.value = ssa_defs[stmt.value] - ssa_defs[f"RET{count}"] = stmt.value - count += 1 -ssa_defs["N"] = "N" -for stmt in my_func.__code__.co_consts[1].co_consts: - in_set, out_set = set(), set() - calc_liveness(stmt, in_set, out_set) - -# print the results -print("SSA form:") -for var, defn in ssa_defs.items(): - print(f"{var} : {defn}") -print("Liveness analysis:") -for var, live_set in liveness.items(): - print(f"{var} : {live_set}") From 6d652f4a6849564c564c2f414d703b3275c28117 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 14 Sep 2023 08:50:51 +0300 Subject: [PATCH 166/471] remove unused IRFunctionIntrinsic --- vyper/codegen/ir_function.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 8ed38043cb..f9cc25923b 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -13,7 +13,7 @@ class IRFunctionBase: """ - Base class for IRFunction and IRFunctionIntrinsic + Base class for IRFunction """ name: IRLabel # symbol name @@ -137,14 +137,3 @@ def __repr__(self) -> str: for inst in self.data_segment: str += f"{inst}\n" return str - - -class IRFunctionIntrinsic(IRFunctionBase): - """ - Intrinsic function, to represent sertain instructions of EVM that - are directly emmitted by the compiler frontend to the s-expression IR - """ - - def __repr__(self) -> str: - args = ", ".join([str(arg) for arg in self.args]) - return f"{self.name}({args})" From 826cc7c08076b78767901e3b5f5c9ead145e42eb Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 19 Sep 2023 12:33:54 +0300 Subject: [PATCH 167/471] leftover --- vyper/ir/bb_optimizer.py | 1 + vyper/ir/ir_to_bb_pass.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 0757ef1dc0..94f09073da 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -16,6 +16,7 @@ def optimize_function(ctx: IRFunction): return ctx _calculate_liveness(ctx.basic_blocks[0], {}) + removed = _optimize_unused_variables(ctx) if len(removed) == 0: break diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index d658d1a1ae..252de4815b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -306,14 +306,17 @@ def _convert_ir_basicblock( elif ir.value == "calldatacopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) arg_0.mem_type = IRVariable.MemType.MEMORY - arg_0.mem_addr = int(arg_0.value[1:]) + if arg_0.is_literal: + arg_0.mem_addr = arg_0.value + else: + arg_0.mem_addr = int(arg_0.value[1:]) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) size = _convert_ir_basicblock(ctx, ir.args[2], symbols) arg_0_var = ctx.get_next_variable() arg_0_var.mem_type = IRVariable.MemType.MEMORY arg_0_var.mem_addr = arg_0.mem_addr - arg_0_op = IROperand(arg_0, True) + arg_0_op = IROperand(arg_0_var, True) # arg_0_op.direction = IROperand.Direction.OUT ctx.append_instruction("calldatacopy", [arg_0_op, arg_1, size], False) From 7f0c62691a157b3a0c8753d236b9a5136882c43e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 19 Sep 2023 12:39:03 +0300 Subject: [PATCH 168/471] cleanup --- vyper/codegen/dfg.py | 8 +------- vyper/codegen/ir_basicblock.py | 6 ++++-- vyper/codegen/ir_function.py | 7 +++---- vyper/ir/compile_ir.py | 2 +- vyper/ir/ir_to_bb_pass.py | 6 +++--- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 1a306e3c9e..5362812dad 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,10 +1,4 @@ -from vyper.codegen.ir_basicblock import ( - IRInstruction, - IRLiteral, - IROperand, - IRVariable, - IRBasicBlock, -) +from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction, IROperand, IRVariable from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 424b106bbc..ca80b258a1 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -1,5 +1,6 @@ -from typing import TYPE_CHECKING, Optional from enum import Enum +from typing import TYPE_CHECKING, Optional + from vyper.utils import OrderedSet TERMINAL_IR_INSTRUCTIONS = ["ret", "revert", "assert"] @@ -97,7 +98,8 @@ def __init__(self, value: str, is_symbol: bool = False) -> None: class IROperand: """ - IROperand represents an operand of an IR instuction. An operand can be a variable, label, or a constant. + IROperand represents an operand of an IR instuction. An operand can be a variable, + label, or a constant. """ Direction = Enum("Direction", ["IN", "OUT"]) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index f9cc25923b..3123d91130 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -1,13 +1,12 @@ -from symtable import SymbolTable from typing import Optional from vyper.codegen.ir_basicblock import ( IRBasicBlock, IRInstruction, - IRValueBase, IRLabel, - IRVariable, IROperand, + IRValueBase, + IRVariable, ) @@ -133,7 +132,7 @@ def __repr__(self) -> str: for bb in self.basic_blocks: str += f"{bb}\n" if len(self.data_segment) > 0: - str += f"Data segment:\n" + str += "Data segment:\n" for inst in self.data_segment: str += f"{inst}\n" return str diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 77af6bf0e5..6c807b948c 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -5,11 +5,11 @@ import cbor2 -from vyper.ir.optimizer import COMMUTATIVE_OPS from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel from vyper.evm.opcodes import get_opcodes, version_check from vyper.exceptions import CodegenPanic, CompilerPanic +from vyper.ir.optimizer import COMMUTATIVE_OPS from vyper.utils import MemoryPositions from vyper.version import version_tuple diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 252de4815b..8520028e21 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -6,9 +6,9 @@ IRInstruction, IRLabel, IRLiteral, + IROperand, IRValueBase, IRVariable, - IROperand, ) from vyper.codegen.ir_function import IRFunction from vyper.codegen.ir_node import IRnode @@ -137,7 +137,7 @@ def _handle_internal_func( new_var = ctx.get_next_variable() alloca_inst = IRInstruction("param", [], new_var) bb.append_instruction(alloca_inst) - symbols[f"return_pc"] = new_var + symbols["return_pc"] = new_var return ir.args[0].args[2] @@ -400,7 +400,7 @@ def _convert_ir_basicblock( if func_t.is_internal: assert ir.args[1].value == "return_pc", "return_pc not found" - if func_t.return_type == None: + if func_t.return_type is None: inst = IRInstruction("ret", [symbols["return_pc"]]) else: ret_var = ir.args[1] From 258cc4053a8e58f4062934e44edaafd01fea8601 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 19 Sep 2023 14:39:29 +0300 Subject: [PATCH 169/471] wishing for a good linter --- vyper/codegen/dfg.py | 32 +++++++++++++++++--------------- vyper/codegen/ir_basicblock.py | 30 +++++++++++------------------- vyper/ir/ir_to_bb_pass.py | 12 ++++++------ 3 files changed, 34 insertions(+), 40 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 5362812dad..8203b7c9d6 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -372,18 +372,19 @@ def _generate_evm_for_instruction_r( raise Exception(f"Unknown opcode: {opcode}") # Step 5: Emit instructions output operands (if any) + # FIXME: WHOLE THING NEEDS REFACTOR if inst.ret is not None: assert inst.ret.is_variable, "Return value must be a variable" if inst.ret.target.mem_type == IRVariable.MemType.MEMORY: - if inst.ret.address_access: - if inst.opcode != "alloca": # FIXMEEEE - if inst.opcode != "codecopy": - assembly.extend([*PUSH(inst.ret.addr)]) - else: - assembly.extend([*PUSH(inst.ret.addr + 30)]) - else: - assembly.extend([*PUSH(inst.ret.addr)]) - assembly.append("MSTORE") + # if inst.ret.address_access: FIXME: MEMORY REFACTOR + # if inst.opcode != "alloca": # FIXMEEEE + # if inst.opcode != "codecopy": + # assembly.extend([*PUSH(inst.ret.addr)]) + # else: + assembly.extend([*PUSH(inst.ret.target.mem_addr + 30)]) + else: + assembly.extend([*PUSH(inst.ret.target.mem_addr)]) + assembly.append("MSTORE") return assembly @@ -402,11 +403,12 @@ def _emit_input_operands( continue assembly = _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) if op.is_variable and op.target.mem_type == IRVariable.MemType.MEMORY: - if op.address_access: - if inst.opcode != "codecopy": - assembly.extend([*PUSH(op.addr)]) - else: - assembly.extend([*PUSH(op.addr)]) - assembly.append("MLOAD") + # FIXME: MEMORY REFACTOR + # if op.address_access: + # if inst.opcode != "codecopy": + # assembly.extend([*PUSH(op.addr)]) + # else: + assembly.extend([*PUSH(op.target.mem_addr)]) + assembly.append("MLOAD") return assembly diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index ca80b258a1..0ea1f2d9d1 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -95,6 +95,9 @@ def __init__(self, value: str, is_symbol: bool = False) -> None: IROperandTarget = IRLiteral | IRVariable | IRLabel +DataType = Enum("Type", ["VALUE", "PTR"]) +DataType_to_str = {DataType.VALUE: "", DataType.PTR: "ptr"} + class IROperand: """ @@ -103,24 +106,18 @@ class IROperand: """ Direction = Enum("Direction", ["IN", "OUT"]) + type: DataType = DataType.VALUE target: IRValueBase direction: Direction = Direction.IN - address_access: bool = False - address_offset: int = 0 def __init__( - self, target: IRValueBase, address_access: bool = False, address_offset: int = 0 + self, + target: IRValueBase, + type: DataType = DataType.VALUE, ) -> None: assert isinstance(target, IRValueBase), "value must be an IRValueBase" - assert isinstance(address_access, bool), "address_access must be a bool" - assert isinstance(address_offset, int), "address_offset must be an int" - if address_access: - assert ( - isinstance(target, IRVariable) and target.mem_type == IRVariable.MemType.MEMORY - ), "address access can only be used for memory variables" - self.address_access = address_access - self.address_offset = address_offset self.target = target + self.type = type self.direction = IROperand.Direction.IN def is_targeting(self, target: IRValueBase) -> bool: @@ -130,12 +127,6 @@ def is_targeting(self, target: IRValueBase) -> bool: def value(self) -> IRValueBaseValue: return self.target.value - @property - def addr(self) -> int: - assert self.is_variable, "address can only be accessed for variables" - target: IRVariable = self.target - return target.mem_addr + self.address_offset - @property def is_literal(self) -> bool: return isinstance(self.target, IRLiteral) @@ -149,8 +140,9 @@ def is_label(self) -> bool: return isinstance(self.target, IRLabel) def __repr__(self) -> str: - offsetStr = f"{self.address_offset:+}" if self.address_offset else "" - return f"{'ptr ' if self.address_access else ''}{self.target}{offsetStr}" + if self.type == DataType.VALUE: + return f"{self.target}" + return f"{DataType_to_str[self.type]} '{self.target}" class IRInstruction: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 8520028e21..2074445d1b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -316,7 +316,7 @@ def _convert_ir_basicblock( arg_0_var = ctx.get_next_variable() arg_0_var.mem_type = IRVariable.MemType.MEMORY arg_0_var.mem_addr = arg_0.mem_addr - arg_0_op = IROperand(arg_0_var, True) + arg_0_op = IROperand(arg_0_var) # arg_0_op.direction = IROperand.Direction.OUT ctx.append_instruction("calldatacopy", [arg_0_op, arg_1, size], False) @@ -328,16 +328,16 @@ def _convert_ir_basicblock( arg_0_var = ctx.get_next_variable() arg_0_var.mem_type = IRVariable.MemType.MEMORY arg_0_var.mem_addr = 0 - alloca_op = IROperand(arg_0_var, True, 0) + alloca_op = IROperand(arg_0_var) ctx.get_basic_block().append_instruction(IRInstruction("alloca", [], alloca_op)) - arg_0_op = IROperand(arg_0_var, True, 30) + arg_0_op = IROperand(arg_0_var) # THis had offset else: - arg_0_op = IROperand(arg_0, True, 0) + arg_0_op = IROperand(arg_0) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) size = _convert_ir_basicblock(ctx, ir.args[2], symbols) ret_var = IRVariable("%ccd", IRVariable.MemType.MEMORY, 0) - ret_op = IROperand(ret_var, True) + ret_op = IROperand(ret_var) symbols[f"&0"] = ret_var inst = IRInstruction("codecopy", [size, arg_1, arg_0_op], ret_op) ctx.get_basic_block().append_instruction(inst) @@ -393,7 +393,7 @@ def _convert_ir_basicblock( new_var = symbols.get(f"&{ret_ir.value}", IRVariable(ret_ir)) new_var.mem_type = IRVariable.MemType.MEMORY new_var.mem_addr = ret_ir.value - new_op = IROperand(new_var, True) + new_op = IROperand(new_var) inst = IRInstruction("return", [last_ir, new_op]) ctx.get_basic_block().append_instruction(inst) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) From b34a14f853b326604c5ae5b343120e39b5ab25d4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Sep 2023 15:07:36 +0300 Subject: [PATCH 170/471] recurse variables --- vyper/codegen/function_definitions/common.py | 3 + vyper/ir/ir_to_bb_pass.py | 132 ++++++++++--------- 2 files changed, 71 insertions(+), 64 deletions(-) diff --git a/vyper/codegen/function_definitions/common.py b/vyper/codegen/function_definitions/common.py index 3fd5ce0b29..f0dbcb9e9d 100644 --- a/vyper/codegen/function_definitions/common.py +++ b/vyper/codegen/function_definitions/common.py @@ -156,4 +156,7 @@ def generate_ir_for_function( mem_expansion_cost = calc_mem_gas(func_t._ir_info.frame_info.mem_used) # type: ignore ret.common_ir.add_gas_estimate += mem_expansion_cost # type: ignore + ret.common_ir.passthrough_metadata["func_t"] = func_t + ret.common_ir.passthrough_metadata["frame_info"] = frame_info + return ret diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 2074445d1b..e346cf743b 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -69,11 +69,11 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No def _convert_binary_op( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable, swap: bool = False + ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables, swap: bool = False ) -> IRVariable: ir_args = ir.args[::-1] if swap else ir.args - arg_0 = _convert_ir_basicblock(ctx, ir_args[0], symbols) - arg_1 = _convert_ir_basicblock(ctx, ir_args[1], symbols) + arg_0 = _convert_ir_basicblock(ctx, ir_args[0], symbols, variables) + arg_1 = _convert_ir_basicblock(ctx, ir_args[1], symbols, variables) args = [arg_1, arg_0] ret = ctx.get_next_variable() @@ -98,17 +98,17 @@ def _new_block(ctx: IRFunction) -> IRBasicBlock: return bb -def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> None: +def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set) -> None: args_ir = ir.passthrough_metadata["args_ir"] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto ret_values = [IRLabel(target_label)] for arg in args_ir: if arg.is_literal: - ret = _convert_ir_basicblock(ctx, arg, symbols) + ret = _convert_ir_basicblock(ctx, arg, symbols, variables) ret_values.append(ret) else: - ret = _convert_ir_basicblock(ctx, arg._optimized, symbols) + ret = _convert_ir_basicblock(ctx, arg._optimized, symbols, variables) if arg.location and arg.location.load_op == "calldataload": ret = ctx.append_instruction(arg.location.load_op, [ret]) ret_values.append(ret) @@ -142,8 +142,10 @@ def _handle_internal_func( return ir.args[0].args[2] -def _convert_ir_simple_node(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> IRVariable: - args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] +def _convert_ir_simple_node( + ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set +) -> IRVariable: + args = [_convert_ir_basicblock(ctx, arg, symbols, variables) for arg in ir.args] return ctx.append_instruction(ir.value, args) @@ -152,22 +154,24 @@ def _convert_ir_simple_node(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) - def _convert_ir_basicblock( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable + ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set = set() ) -> Optional[IRVariable]: global _break_target, _continue_target + variables |= ir.referenced_variables + if ir.value in BINARY_IR_INSTRUCTIONS: - return _convert_binary_op(ctx, ir, symbols, ir.value in ["sha3", "sha3_64"]) + return _convert_binary_op(ctx, ir, symbols, variables, ir.value in ["sha3", "sha3_64"]) elif ir.value in MAPPED_IR_INSTRUCTIONS.keys(): org_value = ir.value ir.value = MAPPED_IR_INSTRUCTIONS[ir.value] - new_var = _convert_binary_op(ctx, ir, symbols) + new_var = _convert_binary_op(ctx, ir, symbols, variables) ir.value = org_value return ctx.append_instruction("iszero", [new_var]) elif ir.value in ["iszero", "ceil32", "calldataload"]: - return _convert_ir_simple_node(ctx, ir, symbols) + return _convert_ir_simple_node(ctx, ir, symbols, variables) elif ir.value in ["timestamp", "caller", "selfbalance", "calldatasize", "callvalue"]: return ctx.append_instruction(ir.value, []) @@ -190,10 +194,10 @@ def _convert_ir_basicblock( bb = IRBasicBlock(runtimeLabel, ctx) ctx.append_basic_block(bb) - _convert_ir_basicblock(ctx, ir_runtime, symbols) + _convert_ir_basicblock(ctx, ir_runtime, symbols, variables) elif ir.value == "seq": if ir.is_self_call: - return _handle_self_call(ctx, ir, symbols) + return _handle_self_call(ctx, ir, symbols, variables) elif ir.passthrough_metadata.get("func_t", None) is not None: func_t = ir.passthrough_metadata["func_t"] ir = _handle_internal_func(ctx, ir, func_t, symbols) @@ -201,17 +205,17 @@ def _convert_ir_basicblock( ret = None for ir_node in ir.args: # NOTE: skip the last one - ret = _convert_ir_basicblock(ctx, ir_node, symbols) + ret = _convert_ir_basicblock(ctx, ir_node, symbols, variables) return ret elif ir.value == "call": # external call - gas = _convert_ir_basicblock(ctx, ir.args[0], symbols) - address = _convert_ir_basicblock(ctx, ir.args[1], symbols) - value = _convert_ir_basicblock(ctx, ir.args[2], symbols) - argsOffset = _convert_ir_basicblock(ctx, ir.args[3], symbols) - argsSize = _convert_ir_basicblock(ctx, ir.args[4], symbols) - retOffset = _convert_ir_basicblock(ctx, ir.args[5], symbols) - retSize = _convert_ir_basicblock(ctx, ir.args[6], symbols) + gas = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + address = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + value = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) + argsOffset = _convert_ir_basicblock(ctx, ir.args[3], symbols, variables) + argsSize = _convert_ir_basicblock(ctx, ir.args[4], symbols, variables) + retOffset = _convert_ir_basicblock(ctx, ir.args[5], symbols, variables) + retSize = _convert_ir_basicblock(ctx, ir.args[6], symbols, variables) if argsOffset.is_literal: addr = argsOffset.value - 32 + 4 if argsOffset.value > 0 else 0 @@ -233,21 +237,21 @@ def _convert_ir_basicblock( current_bb = ctx.get_basic_block() # convert the condition - cont_ret = _convert_ir_basicblock(ctx, cond, symbols) + cont_ret = _convert_ir_basicblock(ctx, cond, symbols, variables) else_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(else_block) # convert "else" if len(ir.args) == 3: - _convert_ir_basicblock(ctx, ir.args[2], symbols) + _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) after_else_syms = symbols.copy() # convert "then" then_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(then_block) - _convert_ir_basicblock(ctx, ir.args[1], symbols) + _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) current_bb.append_instruction(inst) @@ -277,7 +281,7 @@ def _convert_ir_basicblock( then_block.append_instruction(exit_inst) elif ir.value == "with": - ret = _convert_ir_basicblock(ctx, ir.args[1], symbols) # initialization + ret = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) # initialization # Handle with nesting with same symbol with_symbols = symbols.copy() @@ -293,25 +297,25 @@ def _convert_ir_basicblock( elif ir.value == "goto": return _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "jump": - arg_1 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_1 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) inst = IRInstruction("jmp", [arg_1]) ctx.get_basic_block().append_instruction(inst) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) new_var = ctx.append_instruction("store", [arg_1]) symbols[sym.value] = new_var elif ir.value == "calldatacopy": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) arg_0.mem_type = IRVariable.MemType.MEMORY if arg_0.is_literal: arg_0.mem_addr = arg_0.value else: arg_0.mem_addr = int(arg_0.value[1:]) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) arg_0_var = ctx.get_next_variable() arg_0_var.mem_type = IRVariable.MemType.MEMORY @@ -323,7 +327,7 @@ def _convert_ir_basicblock( symbols[f"&{arg_0_var.mem_addr}"] = arg_0_var return arg_0_var elif ir.value == "codecopy": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) if arg_0.is_literal and arg_0.value == 30: arg_0_var = ctx.get_next_variable() arg_0_var.mem_type = IRVariable.MemType.MEMORY @@ -334,8 +338,8 @@ def _convert_ir_basicblock( else: arg_0_op = IROperand(arg_0) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) ret_var = IRVariable("%ccd", IRVariable.MemType.MEMORY, 0) ret_op = IROperand(ret_var) symbols[f"&0"] = ret_var @@ -353,10 +357,10 @@ def _convert_ir_basicblock( elif isinstance(c, bytes): ctx.append_data("db", [c]) elif isinstance(c, IRnode): - data = _convert_ir_basicblock(ctx, c, symbols) + data = _convert_ir_basicblock(ctx, c, symbols, variables) ctx.append_data("db", [data]) elif ir.value == "assert": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) current_bb = ctx.get_basic_block() inst = IRInstruction("assert", [arg_0]) current_bb.append_instruction(inst) @@ -367,7 +371,7 @@ def _convert_ir_basicblock( ctx.get_basic_block().append_instruction(inst) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) - _convert_ir_basicblock(ctx, ir.args[2], symbols) + _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) elif ir.value == "exit_to": func_t = ir.passthrough_metadata.get("func_t", None) assert func_t is not None, "exit_to without func_t" @@ -387,9 +391,9 @@ def _convert_ir_basicblock( last_ir = None ret_var = ir.args[1] for arg in ir.args[2:]: - last_ir = _convert_ir_basicblock(ctx, arg, symbols) + last_ir = _convert_ir_basicblock(ctx, arg, symbols, variables) - ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols) + ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols, variables) new_var = symbols.get(f"&{ret_ir.value}", IRVariable(ret_ir)) new_var.mem_type = IRVariable.MemType.MEMORY new_var.mem_addr = ret_ir.value @@ -409,18 +413,18 @@ def _convert_ir_basicblock( ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) inst = IRInstruction("revert", [arg_0, arg_1]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "dload": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) return ctx.append_instruction("calldataload", [arg_0]) elif ir.value == "dloadbytes": - src = _convert_ir_basicblock(ctx, ir.args[0], symbols) - dst = _convert_ir_basicblock(ctx, ir.args[1], symbols) - len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols) + src = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + dst = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) ret = ctx.get_next_variable() inst = IRInstruction("codecopy", [len_, src, dst], ret) @@ -433,18 +437,18 @@ def _convert_ir_basicblock( if new_var is None: new_var = ctx.get_next_variable() symbols[f"&{sym.value}"] = new_var - v = _convert_ir_basicblock(ctx, sym, symbols) + v = _convert_ir_basicblock(ctx, sym, symbols, variables) op = IROperand(v, not v.is_literal) inst = IRInstruction("store", [op], new_var) ctx.get_basic_block().append_instruction(inst) return new_var else: - new_var = _convert_ir_basicblock(ctx, sym, symbols) + new_var = _convert_ir_basicblock(ctx, sym, symbols, variables) return new_var elif ir.value == "mstore": - sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) sym = symbols.get(f"&{arg_1.value}", None) if sym_ir.is_literal: @@ -456,11 +460,11 @@ def _convert_ir_basicblock( symbols[sym_ir.value] = new_var return new_var elif ir.value == "sload": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) return ctx.append_instruction("sload", [arg_0]) elif ir.value == "sstore": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) inst = IRInstruction("sstore", [arg_1, arg_0]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "unique_symbol": @@ -481,9 +485,9 @@ def _convert_ir_basicblock( # 6) exit block # TODO: Add the extra bounds check after clarify sym = ir.args[0] - start = _convert_ir_basicblock(ctx, ir.args[1], symbols) - end = _convert_ir_basicblock(ctx, ir.args[2], symbols) - bound = _convert_ir_basicblock(ctx, ir.args[3], symbols) + start = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + end = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) + bound = _convert_ir_basicblock(ctx, ir.args[3], symbols, variables) body = ir.args[4] entry_block = ctx.get_basic_block() @@ -519,7 +523,7 @@ def _convert_ir_basicblock( ctx.append_basic_block(body_block) old_targets = _break_target, _continue_target _break_target, _continue_target = exit_block, increment_block - _convert_ir_basicblock(ctx, body, symbols) + _convert_ir_basicblock(ctx, body, symbols, variables) _break_target, _continue_target = old_targets body_end = ctx.get_basic_block() if body_end.is_terminal() is False: @@ -556,24 +560,24 @@ def _convert_ir_basicblock( return ctx.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) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) new_var = ctx.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) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) ctx.append_instruction("selfdestruct", [arg_0], False) elif isinstance(ir.value, str) and ir.value.startswith("log"): # count = int(ir.value[3:]) - args = [_convert_ir_basicblock(ctx, arg, symbols) for arg in ir.args] + args = [_convert_ir_basicblock(ctx, arg, symbols, variables) for arg in ir.args] inst = IRInstruction(ir.value, args) ctx.get_basic_block().append_instruction(inst) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): - _convert_ir_opcode(ctx, ir, symbols) + _convert_ir_opcode(ctx, ir, symbols, variables) elif isinstance(ir.value, str) and ir.value in symbols: return symbols[ir.value] elif ir.is_literal: @@ -584,12 +588,12 @@ def _convert_ir_basicblock( return None -def _convert_ir_opcode(ctx: IRFunction, ir: IRnode, symbols: SymbolTable) -> None: +def _convert_ir_opcode(ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set) -> None: opcode = str(ir.value).upper() inst_args = [] for arg in ir.args: if isinstance(arg, IRnode): - inst_args.append(_convert_ir_basicblock(ctx, arg, symbols)) + inst_args.append(_convert_ir_basicblock(ctx, arg, symbols, variables)) instruction = IRInstruction(opcode, inst_args) ctx.get_basic_block().append_instruction(instruction) From c4749fbbe628742af7a58a16173e42cb4b5b8154 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Sep 2023 20:19:27 +0300 Subject: [PATCH 171/471] var --- vyper/codegen/dfg.py | 20 +++++++++++--------- vyper/codegen/ir_basicblock.py | 11 +++++++++++ vyper/ir/ir_to_bb_pass.py | 12 ++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 8203b7c9d6..c52d406162 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -17,6 +17,8 @@ "selfbalance", "sload", "sstore", + "mload", + "mstore", "timestamp", "caller", "selfdestruct", @@ -209,7 +211,7 @@ def _generate_evm_for_basicblock_r( fen = 0 for inst in basicblock.instructions: inst.fen = fen - if inst.opcode in ["param", "call", "invoke", "sload", "sstore", "assert"]: + if inst.volatile: fen += 1 for inst in basicblock.instructions: @@ -376,15 +378,15 @@ def _generate_evm_for_instruction_r( if inst.ret is not None: assert inst.ret.is_variable, "Return value must be a variable" if inst.ret.target.mem_type == IRVariable.MemType.MEMORY: - # if inst.ret.address_access: FIXME: MEMORY REFACTOR - # if inst.opcode != "alloca": # FIXMEEEE - # if inst.opcode != "codecopy": - # assembly.extend([*PUSH(inst.ret.addr)]) - # else: - assembly.extend([*PUSH(inst.ret.target.mem_addr + 30)]) - else: + # # if inst.ret.address_access: FIXME: MEMORY REFACTOR + # # if inst.opcode != "alloca": # FIXMEEEE + # # if inst.opcode != "codecopy": + # # assembly.extend([*PUSH(inst.ret.addr)]) + # # else: + # assembly.extend([*PUSH(inst.ret.target.mem_addr + 30)]) + # else: assembly.extend([*PUSH(inst.ret.target.mem_addr)]) - assembly.append("MSTORE") + # assembly.append("MSTORE") return assembly diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 0ea1f2d9d1..09ec5f0d4f 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -154,6 +154,7 @@ class IRInstruction: """ opcode: str + volatile: bool operands: list[IROperand] ret: Optional[IROperand] dbg: Optional[IRDebugInfo] @@ -169,6 +170,16 @@ def __init__( dbg: IRDebugInfo = None, ): self.opcode = opcode + self.volatile = opcode in [ + "param", + "call", + "invoke", + "sload", + "sstore", + "assert", + "mstore", + "mload", + ] self.operands = [op if isinstance(op, IROperand) else IROperand(op) for op in operands] self.ret = ret if isinstance(ret, IROperand) else IROperand(ret) if ret else None self.dbg = dbg diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index e346cf743b..84ac97e84f 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -153,6 +153,13 @@ def _convert_ir_simple_node( _continue_target: IRBasicBlock = None +def get_variable_from_address(variables: set, addr: int) -> IRVariable: + for var in variables: + if addr >= var.pos and addr < var.pos + var.size: + return var + return None + + def _convert_ir_basicblock( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set = set() ) -> Optional[IRVariable]: @@ -449,6 +456,11 @@ def _convert_ir_basicblock( elif ir.value == "mstore": sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + + var = get_variable_from_address(variables, sym_ir.value) + if var.size > 32: + return ctx.append_instruction("mstore", [arg_1, sym_ir], False) + sym = symbols.get(f"&{arg_1.value}", None) if sym_ir.is_literal: From 7d6ce10ba195fee75f6fb0106c184f41051b3bab Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Sep 2023 20:56:35 +0300 Subject: [PATCH 172/471] g --- vyper/codegen/function_definitions/common.py | 1 - vyper/ir/ir_to_bb_pass.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/codegen/function_definitions/common.py b/vyper/codegen/function_definitions/common.py index f0dbcb9e9d..a27aa9a8dd 100644 --- a/vyper/codegen/function_definitions/common.py +++ b/vyper/codegen/function_definitions/common.py @@ -156,7 +156,6 @@ def generate_ir_for_function( mem_expansion_cost = calc_mem_gas(func_t._ir_info.frame_info.mem_used) # type: ignore ret.common_ir.add_gas_estimate += mem_expansion_cost # type: ignore - ret.common_ir.passthrough_metadata["func_t"] = func_t ret.common_ir.passthrough_metadata["frame_info"] = frame_info return ret diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 84ac97e84f..0fc4125d5e 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -461,7 +461,7 @@ def _convert_ir_basicblock( if var.size > 32: return ctx.append_instruction("mstore", [arg_1, sym_ir], False) - sym = symbols.get(f"&{arg_1.value}", None) + sym = symbols.get(f"&{arg_1.value}", var) if sym_ir.is_literal: new_var = ctx.append_instruction("store", [arg_1]) From 611766f97c347ffed1868075d845c72440896989 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 20 Sep 2023 20:57:28 +0300 Subject: [PATCH 173/471] back --- vyper/ir/ir_to_bb_pass.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 0fc4125d5e..84ac97e84f 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -461,7 +461,7 @@ def _convert_ir_basicblock( if var.size > 32: return ctx.append_instruction("mstore", [arg_1, sym_ir], False) - sym = symbols.get(f"&{arg_1.value}", var) + sym = symbols.get(f"&{arg_1.value}", None) if sym_ir.is_literal: new_var = ctx.append_instruction("store", [arg_1]) From f480ff4b9ffdb22e9042cfac8e80762dfc8cbc69 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Sep 2023 12:04:32 +0300 Subject: [PATCH 174/471] =?UTF-8?q?=CE=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vyper/codegen/dfg.py | 11 ++++-- vyper/codegen/ir_basicblock.py | 3 +- vyper/ir/bb_optimizer.py | 2 +- vyper/ir/ir_to_bb_pass.py | 70 +++++++++++++++++++++++++--------- 4 files changed, 63 insertions(+), 23 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index c52d406162..6af839dbd2 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -244,12 +244,18 @@ def _generate_evm_for_instruction_r( return assembly visited_instructions.add(inst) + opcode = inst.opcode + + # # generate EVM for op + # # Step 1: Manipulate stack - opcode = inst.opcode + if opcode in ["jmp", "jnz"]: operands = inst.get_non_label_operands() + if opcode == "alloca": + operands = inst.operands[1:2] else: operands = inst.operands @@ -331,9 +337,6 @@ def _generate_evm_for_instruction_r( assert len(inst.operands) == 2, "ret instruction takes one operand" assembly.append("JUMP") elif opcode == "return": - assert ( - inst.operands[1].target.mem_type == IRVariable.MemType.MEMORY - ), "return value must be a variable" assembly.append("RETURN") elif opcode == "select": pass diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 09ec5f0d4f..1213718ef8 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -142,7 +142,7 @@ def is_label(self) -> bool: def __repr__(self) -> str: if self.type == DataType.VALUE: return f"{self.target}" - return f"{DataType_to_str[self.type]} '{self.target}" + return f"{DataType_to_str[self.type]} {self.target}" class IRInstruction: @@ -172,6 +172,7 @@ def __init__( self.opcode = opcode self.volatile = opcode in [ "param", + "alloca", "call", "invoke", "sload", diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 94f09073da..500c4ea139 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -30,7 +30,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: removeList = [] for bb in ctx.basic_blocks: for i, inst in enumerate(bb.instructions[:-1]): - if inst.opcode in ["call", "selfdestruct", "invoke", "sload", "sstore"]: + if inst.volatile: continue if inst.ret and inst.ret.target not in bb.instructions[i + 1].liveness: removeList.append(inst) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 84ac97e84f..6c34640dbb 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -2,6 +2,7 @@ from vyper.codegen.dfg import generate_evm from vyper.codegen.ir_basicblock import ( + DataType, IRBasicBlock, IRInstruction, IRLabel, @@ -54,7 +55,12 @@ def generate_assembly_experimental( return generate_evm(ir, optimize is OptimizationLevel.NONE) +_allocated_variables: dict[str, IRVariable] = {} + + def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: + global _allocated_variables + _allocated_variables = {} global_function = IRFunction(IRLabel("global")) _convert_ir_basicblock(global_function, ir, {}) @@ -163,7 +169,7 @@ def get_variable_from_address(variables: set, addr: int) -> IRVariable: def _convert_ir_basicblock( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set = set() ) -> Optional[IRVariable]: - global _break_target, _continue_target + global _break_target, _continue_target, _allocated_variables variables |= ir.referenced_variables @@ -401,11 +407,24 @@ def _convert_ir_basicblock( last_ir = _convert_ir_basicblock(ctx, arg, symbols, variables) ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols, variables) - new_var = symbols.get(f"&{ret_ir.value}", IRVariable(ret_ir)) - new_var.mem_type = IRVariable.MemType.MEMORY - new_var.mem_addr = ret_ir.value - new_op = IROperand(new_var) - inst = IRInstruction("return", [last_ir, new_op]) + + var = get_variable_from_address(variables, ret_ir.value) + if var is not None: + allocated_var = _allocated_variables.get(var.name, None) + assert allocated_var is not None, "unallocated variable" + if var.size == 32: + inst = IRInstruction("return", [last_ir, allocated_var]) + else: + new_var = symbols.get(f"&{ret_ir.value}", IRVariable(ret_ir)) + new_op = IROperand(allocated_var, DataType.PTR) + + inst = IRInstruction("return", [last_ir, new_op]) + else: + sym = symbols.get(f"&{ret_ir.value}", None) + new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) + ctx.append_instruction("mstore", [sym, IROperand(new_var, DataType.PTR)], False) + inst = IRInstruction("return", [last_ir, IROperand(new_var, DataType.PTR)]) + ctx.get_basic_block().append_instruction(inst) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) @@ -457,20 +476,37 @@ def _convert_ir_basicblock( sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) - var = get_variable_from_address(variables, sym_ir.value) - if var.size > 32: - return ctx.append_instruction("mstore", [arg_1, sym_ir], False) + assert sym_ir.is_literal, "mstore with non-literal address" # TEMP - sym = symbols.get(f"&{arg_1.value}", None) + var = get_variable_from_address(variables, sym_ir.value) + if var is not None 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)] + ) + + offset = sym_ir.value - var.pos + if offset > 0: + ptr_var = ctx.append_instruction("add", [IRLiteral(var.pos), IRLiteral(offset)]) + else: + ptr_var = _allocated_variables[var.name] - if sym_ir.is_literal: - new_var = ctx.append_instruction("store", [arg_1]) - symbols[f"&{sym_ir.value}"] = new_var - return new_var + return ctx.append_instruction( + "mstore", [arg_1, IROperand(ptr_var, DataType.PTR)], False + ) else: - new_var = ctx.append_instruction("store", [arg_1]) - symbols[sym_ir.value] = new_var - return new_var + sym = symbols.get(f"&{sym_ir.value}", None) + if sym is None: + new_var = ctx.append_instruction("store", [arg_1], sym_ir) + symbols[f"&{sym_ir.value}"] = new_var + return new_var + + if sym_ir.is_literal: + return arg_1 + else: + symbols[sym_ir.value] = arg_1 + return arg_1 + elif ir.value == "sload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) return ctx.append_instruction("sload", [arg_0]) From 029d88a49f5184c9580109079a74eff78d42f32f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Sep 2023 13:32:56 +0300 Subject: [PATCH 175/471] handle calldatacopy and out of bounds zeros --- vyper/codegen/function_definitions/common.py | 5 +- vyper/ir/ir_to_bb_pass.py | 91 ++++++++++++-------- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/vyper/codegen/function_definitions/common.py b/vyper/codegen/function_definitions/common.py index a27aa9a8dd..0be014e76b 100644 --- a/vyper/codegen/function_definitions/common.py +++ b/vyper/codegen/function_definitions/common.py @@ -155,7 +155,8 @@ def generate_ir_for_function( # (note: internal functions do not need to adjust gas estimate since mem_expansion_cost = calc_mem_gas(func_t._ir_info.frame_info.mem_used) # type: ignore ret.common_ir.add_gas_estimate += mem_expansion_cost # type: ignore - - ret.common_ir.passthrough_metadata["frame_info"] = frame_info + ret.common_ir.passthrough_metadata["frame_info"] = frame_info + else: + ret.func_ir.passthrough_metadata["frame_info"] = frame_info return ret diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 6c34640dbb..daff86788e 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -159,13 +159,22 @@ def _convert_ir_simple_node( _continue_target: IRBasicBlock = None -def get_variable_from_address(variables: set, addr: int) -> IRVariable: +def _get_variable_from_address(variables: set, addr: int) -> IRVariable: for var in variables: if addr >= var.pos and addr < var.pos + var.size: return var return None +def _get_return_for_stack_operand( + ctx: IRFunction, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable +): + sym = symbols.get(f"&{ret_ir.value}", None) + new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) + ctx.append_instruction("mstore", [sym, IROperand(new_var, DataType.PTR)], False) + return IRInstruction("return", [last_ir, IROperand(new_var, DataType.PTR)]) + + def _convert_ir_basicblock( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set = set() ) -> Optional[IRVariable]: @@ -322,23 +331,23 @@ def _convert_ir_basicblock( elif ir.value == "calldatacopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) - arg_0.mem_type = IRVariable.MemType.MEMORY - if arg_0.is_literal: - arg_0.mem_addr = arg_0.value - else: - arg_0.mem_addr = int(arg_0.value[1:]) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) - arg_0_var = ctx.get_next_variable() - arg_0_var.mem_type = IRVariable.MemType.MEMORY - arg_0_var.mem_addr = arg_0.mem_addr - arg_0_op = IROperand(arg_0_var) - # arg_0_op.direction = IROperand.Direction.OUT - ctx.append_instruction("calldatacopy", [arg_0_op, arg_1, size], False) + var = _get_variable_from_address(variables, arg_0.value) + if var is not None: + if _allocated_variables.get(var.name, None) is None: + new_var = ctx.append_instruction( + "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] + ) + _allocated_variables[var.name] = new_var + ctx.append_instruction("calldatacopy", [new_var, arg_1, size], False) + else: + new_var = arg_0 + ctx.append_instruction("calldatacopy", [new_var, arg_1, size], False) - symbols[f"&{arg_0_var.mem_addr}"] = arg_0_var - return arg_0_var + symbols[f"&{var.pos}"] = new_var + return new_var elif ir.value == "codecopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) if arg_0.is_literal and arg_0.value == 30: @@ -408,22 +417,19 @@ def _convert_ir_basicblock( ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols, variables) - var = get_variable_from_address(variables, ret_ir.value) + var = _get_variable_from_address(variables, ret_ir.value) if var is not None: allocated_var = _allocated_variables.get(var.name, None) assert allocated_var is not None, "unallocated variable" - if var.size == 32: - inst = IRInstruction("return", [last_ir, allocated_var]) - else: - new_var = symbols.get(f"&{ret_ir.value}", IRVariable(ret_ir)) - new_op = IROperand(allocated_var, DataType.PTR) + new_var = symbols.get(f"&{ret_ir.value}", IRVariable(ret_ir)) + if var.size > 32: + new_op = IROperand(allocated_var, DataType.PTR) inst = IRInstruction("return", [last_ir, new_op]) + else: + inst = _get_return_for_stack_operand(ctx, symbols, ret_ir, last_ir) else: - sym = symbols.get(f"&{ret_ir.value}", None) - new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) - ctx.append_instruction("mstore", [sym, IROperand(new_var, DataType.PTR)], False) - inst = IRInstruction("return", [last_ir, IROperand(new_var, DataType.PTR)]) + inst = _get_return_for_stack_operand(ctx, symbols, ret_ir, last_ir) ctx.get_basic_block().append_instruction(inst) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) @@ -478,22 +484,31 @@ def _convert_ir_basicblock( assert sym_ir.is_literal, "mstore with non-literal address" # TEMP - var = get_variable_from_address(variables, sym_ir.value) - if var is not None 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)] - ) + var = _get_variable_from_address(variables, sym_ir.value) + if var is not None: + if 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)] + ) + + offset = sym_ir.value - var.pos + if offset > 0: + ptr_var = ctx.append_instruction("add", [IRLiteral(var.pos), IRLiteral(offset)]) + else: + ptr_var = _allocated_variables[var.name] - offset = sym_ir.value - var.pos - if offset > 0: - ptr_var = ctx.append_instruction("add", [IRLiteral(var.pos), IRLiteral(offset)]) + return ctx.append_instruction( + "mstore", [arg_1, IROperand(ptr_var, DataType.PTR)], False + ) else: - ptr_var = _allocated_variables[var.name] - - return ctx.append_instruction( - "mstore", [arg_1, IROperand(ptr_var, DataType.PTR)], False - ) + if sym_ir.is_literal: + new_var = ctx.append_instruction("store", [arg_1], sym_ir) + 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 + return arg_1 else: sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: From cd3b42d48ebbc4b9c4f87c0bfafcb1cbd6a10f02 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Sep 2023 14:09:53 +0300 Subject: [PATCH 176/471] proper order --- vyper/codegen/ir_basicblock.py | 1 + vyper/ir/ir_to_bb_pass.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 1213718ef8..5d38cb2b9a 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -180,6 +180,7 @@ def __init__( "assert", "mstore", "mload", + "calldatacopy", ] self.operands = [op if isinstance(op, IROperand) else IROperand(op) for op in operands] self.ret = ret if isinstance(ret, IROperand) else IROperand(ret) if ret else None diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index daff86788e..f0aa5185c0 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -334,6 +334,7 @@ def _convert_ir_basicblock( arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) + new_var = arg_0 var = _get_variable_from_address(variables, arg_0.value) if var is not None: if _allocated_variables.get(var.name, None) is None: @@ -341,10 +342,9 @@ def _convert_ir_basicblock( "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] ) _allocated_variables[var.name] = new_var - ctx.append_instruction("calldatacopy", [new_var, arg_1, size], False) + ctx.append_instruction("calldatacopy", [size, arg_1, new_var], False) else: - new_var = arg_0 - ctx.append_instruction("calldatacopy", [new_var, arg_1, size], False) + ctx.append_instruction("calldatacopy", [size, arg_1, new_var], False) symbols[f"&{var.pos}"] = new_var return new_var From cc589482d944ee0e1d0506634b393dcc0e67fbe6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Sep 2023 15:20:14 +0300 Subject: [PATCH 177/471] internal return case --- vyper/ir/ir_to_bb_pass.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index f0aa5185c0..157c3b94dd 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -482,7 +482,14 @@ def _convert_ir_basicblock( sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) - assert sym_ir.is_literal, "mstore with non-literal address" # TEMP + if sym_ir.is_literal is False: + if sym_ir.value == "return_buffer": + sym = symbols.get(f"{sym_ir.value}", None) + if sym is None: + new_var = ctx.append_instruction("store", [arg_1], sym_ir) + symbols[f"{sym_ir.value}"] = new_var + return new_var + return var = _get_variable_from_address(variables, sym_ir.value) if var is not None: From 29be3b517017bc41b73e6655b77f0022de8f259c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 21 Sep 2023 20:14:41 +0300 Subject: [PATCH 178/471] invoke special operand emition case --- vyper/codegen/dfg.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 6af839dbd2..21b168e61d 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -254,7 +254,7 @@ def _generate_evm_for_instruction_r( if opcode in ["jmp", "jnz"]: operands = inst.get_non_label_operands() - if opcode == "alloca": + elif opcode == "alloca": operands = inst.operands[1:2] else: operands = inst.operands @@ -399,7 +399,10 @@ def _emit_input_operands( ) -> None: for op in ops: if op.is_label: - assembly.append(f"_sym_{op.value}") + # invoke emits the actual instruction itself so we don't need to emit it here + # but we need to add it to the stack map + if inst.opcode != "invoke": + assembly.append(f"_sym_{op.value}") stack_map.push(op.target) continue if op.is_literal: From 6aeb8acc1058b57b30cadede924e646b5d4643dd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Sep 2023 13:18:19 +0300 Subject: [PATCH 179/471] assert datatype --- vyper/codegen/ir_basicblock.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 5d38cb2b9a..4d2271ed52 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -116,6 +116,7 @@ def __init__( type: DataType = DataType.VALUE, ) -> None: assert isinstance(target, IRValueBase), "value must be an IRValueBase" + assert isinstance(type, DataType), "type must be a DataType" self.target = target self.type = type self.direction = IROperand.Direction.IN From 9fb94b5034763e63b5dfd3517a33ed079c625488 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Sep 2023 13:18:27 +0300 Subject: [PATCH 180/471] refactor --- vyper/ir/ir_to_bb_pass.py | 307 ++++++++++++++++++++++++-------------- 1 file changed, 197 insertions(+), 110 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 157c3b94dd..fc90a0495d 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -18,6 +18,7 @@ from vyper.ir.bb_optimizer import optimize_function from vyper.ir.compile_ir import is_mem_sym, is_symbol from vyper.semantics.types.function import ContractFunctionT +from vyper.utils import OrderedSet BINARY_IR_INSTRUCTIONS = [ "eq", @@ -55,14 +56,9 @@ def generate_assembly_experimental( return generate_evm(ir, optimize is OptimizationLevel.NONE) -_allocated_variables: dict[str, IRVariable] = {} - - def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: - global _allocated_variables - _allocated_variables = {} global_function = IRFunction(IRLabel("global")) - _convert_ir_basicblock(global_function, ir, {}) + _convert_ir_basicblock(global_function, ir, {}, {}) revert_bb = IRBasicBlock(IRLabel("__revert"), global_function) revert_bb = global_function.append_basic_block(revert_bb) @@ -75,11 +71,16 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No def _convert_binary_op( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables, swap: bool = False + ctx: IRFunction, + ir: IRnode, + symbols: SymbolTable, + variables, + allocated_variables: dict[str, IRVariable], + swap: bool = False, ) -> IRVariable: ir_args = ir.args[::-1] if swap else ir.args - arg_0 = _convert_ir_basicblock(ctx, ir_args[0], symbols, variables) - arg_1 = _convert_ir_basicblock(ctx, ir_args[1], symbols, variables) + 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() @@ -104,17 +105,25 @@ def _new_block(ctx: IRFunction) -> IRBasicBlock: return bb -def _handle_self_call(ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set) -> None: +def _handle_self_call( + ctx: IRFunction, + ir: IRnode, + symbols: SymbolTable, + variables: OrderedSet, + allocated_variables: dict[str, IRVariable], +) -> None: args_ir = ir.passthrough_metadata["args_ir"] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto ret_values = [IRLabel(target_label)] for arg in args_ir: if arg.is_literal: - ret = _convert_ir_basicblock(ctx, arg, symbols, variables) + ret = _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) ret_values.append(ret) else: - ret = _convert_ir_basicblock(ctx, arg._optimized, symbols, variables) + ret = _convert_ir_basicblock( + 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]) ret_values.append(ret) @@ -137,7 +146,7 @@ def _handle_internal_func( alloca_inst = IRInstruction("param", [], new_var) bb.append_instruction(alloca_inst) symbols[f"&{old_ir_mempos}"] = new_var - old_ir_mempos += 32 + old_ir_mempos += 32 # arg.typ.memory_bytes_required # return address new_var = ctx.get_next_variable() @@ -149,9 +158,15 @@ def _handle_internal_func( def _convert_ir_simple_node( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set + ctx: IRFunction, + ir: IRnode, + symbols: SymbolTable, + variables: OrderedSet, + allocated_variables: dict[str, IRVariable], ) -> IRVariable: - args = [_convert_ir_basicblock(ctx, arg, symbols, variables) for arg in ir.args] + args = [ + _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args + ] return ctx.append_instruction(ir.value, args) @@ -159,8 +174,11 @@ def _convert_ir_simple_node( _continue_target: IRBasicBlock = None -def _get_variable_from_address(variables: set, addr: int) -> IRVariable: +def _get_variable_from_address(variables: OrderedSet, addr: int) -> IRVariable: + assert isinstance(addr, int), "non-int address" for var in variables: + if var.location.name != "memory": + continue if addr >= var.pos and addr < var.pos + var.size: return var return None @@ -176,24 +194,33 @@ def _get_return_for_stack_operand( def _convert_ir_basicblock( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set = set() + ctx: IRFunction, + ir: IRnode, + symbols: SymbolTable, + variables: OrderedSet = {}, + allocated_variables: dict[str, IRVariable] = {}, ) -> Optional[IRVariable]: - global _break_target, _continue_target, _allocated_variables + global _break_target, _continue_target - variables |= ir.referenced_variables + frame_info = ir.passthrough_metadata.get("frame_info", None) + if frame_info is not None: + vars = {v: True for v in frame_info.frame_vars.values()} # FIXME + variables |= vars if ir.value in BINARY_IR_INSTRUCTIONS: - return _convert_binary_op(ctx, ir, symbols, variables, ir.value in ["sha3", "sha3_64"]) + return _convert_binary_op( + ctx, ir, symbols, variables, allocated_variables, ir.value in ["sha3", "sha3_64"] + ) elif ir.value in MAPPED_IR_INSTRUCTIONS.keys(): org_value = ir.value ir.value = MAPPED_IR_INSTRUCTIONS[ir.value] - new_var = _convert_binary_op(ctx, ir, symbols, variables) + new_var = _convert_binary_op(ctx, ir, symbols, variables, allocated_variables) ir.value = org_value return ctx.append_instruction("iszero", [new_var]) elif ir.value in ["iszero", "ceil32", "calldataload"]: - return _convert_ir_simple_node(ctx, ir, symbols, variables) + return _convert_ir_simple_node(ctx, ir, symbols, variables, allocated_variables) elif ir.value in ["timestamp", "caller", "selfbalance", "calldatasize", "callvalue"]: return ctx.append_instruction(ir.value, []) @@ -216,28 +243,35 @@ def _convert_ir_basicblock( bb = IRBasicBlock(runtimeLabel, ctx) ctx.append_basic_block(bb) - _convert_ir_basicblock(ctx, ir_runtime, symbols, variables) + _convert_ir_basicblock(ctx, ir_runtime, symbols, variables, allocated_variables) elif ir.value == "seq": if ir.is_self_call: - return _handle_self_call(ctx, ir, symbols, variables) + return _handle_self_call(ctx, ir, symbols, variables, allocated_variables) elif ir.passthrough_metadata.get("func_t", None) is not None: + symbols = {} + variables = OrderedSet( + {v: True for v in ir.passthrough_metadata["frame_info"].frame_vars.values()} + ) + # variables = OrderedSet() func_t = ir.passthrough_metadata["func_t"] ir = _handle_internal_func(ctx, ir, func_t, symbols) # fallthrough ret = None for ir_node in ir.args: # NOTE: skip the last one - ret = _convert_ir_basicblock(ctx, ir_node, symbols, variables) + ret = _convert_ir_basicblock(ctx, ir_node, symbols, variables, allocated_variables) return ret elif ir.value == "call": # external call - gas = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) - address = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) - value = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) - argsOffset = _convert_ir_basicblock(ctx, ir.args[3], symbols, variables) - argsSize = _convert_ir_basicblock(ctx, ir.args[4], symbols, variables) - retOffset = _convert_ir_basicblock(ctx, ir.args[5], symbols, variables) - retSize = _convert_ir_basicblock(ctx, ir.args[6], symbols, variables) + gas = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + address = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + value = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + argsOffset = _convert_ir_basicblock( + ctx, ir.args[3], symbols, variables, allocated_variables + ) + argsSize = _convert_ir_basicblock(ctx, ir.args[4], symbols, variables, allocated_variables) + retOffset = _convert_ir_basicblock(ctx, ir.args[5], symbols, variables, allocated_variables) + retSize = _convert_ir_basicblock(ctx, ir.args[6], symbols, variables, allocated_variables) if argsOffset.is_literal: addr = argsOffset.value - 32 + 4 if argsOffset.value > 0 else 0 @@ -259,21 +293,21 @@ def _convert_ir_basicblock( current_bb = ctx.get_basic_block() # convert the condition - cont_ret = _convert_ir_basicblock(ctx, cond, symbols, variables) + cont_ret = _convert_ir_basicblock(ctx, cond, symbols, variables, allocated_variables) else_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(else_block) # convert "else" if len(ir.args) == 3: - _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) + _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) after_else_syms = symbols.copy() # convert "then" then_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(then_block) - _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) current_bb.append_instruction(inst) @@ -285,8 +319,6 @@ def _convert_ir_basicblock( bb = IRBasicBlock(exit_label, ctx) bb = ctx.append_basic_block(bb) - # _emit_selects(ctx, after_then_syms, after_else_syms, then_block, else_block, bb) - for sym, val in _get_symbols_common(after_then_syms, after_else_syms).items(): ret = ctx.get_next_variable() symbols[sym] = ret @@ -303,7 +335,9 @@ def _convert_ir_basicblock( then_block.append_instruction(exit_inst) elif ir.value == "with": - ret = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) # initialization + ret = _convert_ir_basicblock( + ctx, ir.args[1], symbols, variables, allocated_variables + ) # initialization # Handle with nesting with same symbol with_symbols = symbols.copy() @@ -319,29 +353,29 @@ def _convert_ir_basicblock( elif ir.value == "goto": return _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "jump": - arg_1 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + 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) _new_block(ctx) elif ir.value == "set": sym = ir.args[0] - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) new_var = ctx.append_instruction("store", [arg_1]) symbols[sym.value] = new_var elif ir.value == "calldatacopy": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) + 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 = arg_0 var = _get_variable_from_address(variables, arg_0.value) if var is not None: - if _allocated_variables.get(var.name, None) is None: + if allocated_variables.get(var.name, None) is None: new_var = ctx.append_instruction( "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] ) - _allocated_variables[var.name] = new_var + allocated_variables[var.name] = new_var ctx.append_instruction("calldatacopy", [size, arg_1, new_var], False) else: ctx.append_instruction("calldatacopy", [size, arg_1, new_var], False) @@ -349,7 +383,7 @@ def _convert_ir_basicblock( symbols[f"&{var.pos}"] = new_var return new_var elif ir.value == "codecopy": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) if arg_0.is_literal and arg_0.value == 30: arg_0_var = ctx.get_next_variable() arg_0_var.mem_type = IRVariable.MemType.MEMORY @@ -360,8 +394,8 @@ def _convert_ir_basicblock( else: arg_0_op = IROperand(arg_0) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols, 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) ret_var = IRVariable("%ccd", IRVariable.MemType.MEMORY, 0) ret_op = IROperand(ret_var) symbols[f"&0"] = ret_var @@ -379,10 +413,10 @@ def _convert_ir_basicblock( elif isinstance(c, bytes): ctx.append_data("db", [c]) elif isinstance(c, IRnode): - data = _convert_ir_basicblock(ctx, c, symbols, variables) + data = _convert_ir_basicblock(ctx, c, symbols, variables, allocated_variables) ctx.append_data("db", [data]) elif ir.value == "assert": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) current_bb = ctx.get_basic_block() inst = IRInstruction("assert", [arg_0]) current_bb.append_instruction(inst) @@ -393,7 +427,7 @@ def _convert_ir_basicblock( ctx.get_basic_block().append_instruction(inst) bb = IRBasicBlock(label, ctx) ctx.append_basic_block(bb) - _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) + _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) elif ir.value == "exit_to": func_t = ir.passthrough_metadata.get("func_t", None) assert func_t is not None, "exit_to without func_t" @@ -413,13 +447,17 @@ def _convert_ir_basicblock( last_ir = None ret_var = ir.args[1] for arg in ir.args[2:]: - last_ir = _convert_ir_basicblock(ctx, arg, symbols, variables) + last_ir = _convert_ir_basicblock( + ctx, arg, symbols, variables, allocated_variables + ) - ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols, variables) + ret_ir = _convert_ir_basicblock( + ctx, ret_var, symbols, variables, allocated_variables + ) var = _get_variable_from_address(variables, ret_ir.value) if var is not None: - allocated_var = _allocated_variables.get(var.name, None) + allocated_var = allocated_variables.get(var.name, None) assert allocated_var is not None, "unallocated variable" new_var = symbols.get(f"&{ret_ir.value}", IRVariable(ret_ir)) @@ -445,57 +483,96 @@ def _convert_ir_basicblock( ctx.get_basic_block().append_instruction(inst) elif ir.value == "revert": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + 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_0, arg_1]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "dload": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) return ctx.append_instruction("calldataload", [arg_0]) elif ir.value == "dloadbytes": - src = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) - dst = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) - len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) + src = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + dst = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) ret = ctx.get_next_variable() inst = IRInstruction("codecopy", [len_, src, dst], ret) ctx.get_basic_block().append_instruction(inst) return ret elif ir.value == "mload": - sym = ir.args[0] - if sym.is_literal: - new_var = symbols.get(f"&{sym.value}", None) - if new_var is None: - new_var = ctx.get_next_variable() - symbols[f"&{sym.value}"] = new_var - v = _convert_ir_basicblock(ctx, sym, symbols, variables) - op = IROperand(v, not v.is_literal) - inst = IRInstruction("store", [op], new_var) - ctx.get_basic_block().append_instruction(inst) - return new_var - else: - new_var = _convert_ir_basicblock(ctx, sym, symbols, variables) - return new_var + sym_ir = ir.args[0] + var = _get_variable_from_address(variables, sym_ir.value) if sym_ir.is_literal else None + if var is not None: + if 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)] + ) - elif ir.value == "mstore": - sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + offset = sym_ir.value - var.pos + if offset > 0: + ptr_var = ctx.append_instruction("add", [IRLiteral(var.pos), IRLiteral(offset)]) + else: + ptr_var = allocated_variables[var.name] - if sym_ir.is_literal is False: - if sym_ir.value == "return_buffer": - sym = symbols.get(f"{sym_ir.value}", None) - if sym is None: - new_var = ctx.append_instruction("store", [arg_1], sym_ir) - symbols[f"{sym_ir.value}"] = new_var - return new_var - return + return ctx.append_instruction("mload", [IROperand(ptr_var, DataType.PTR)]) + else: + if sym_ir.is_literal: + sym = symbols.get(f"&{sym_ir.value}", None) + if sym is None: + new_var = ctx.append_instruction("store", [IRValueBase(sym_ir.value)]) + symbols[f"&{sym_ir.value}"] = new_var + if allocated_variables.get(var.name, None) is None: + allocated_variables[var.name] = new_var + return new_var + else: + return sym - var = _get_variable_from_address(variables, sym_ir.value) + sym = symbols.get(f"&{sym_ir.value}", None) + assert sym is not None, "unallocated variable" + return sym + else: + if sym_ir.is_literal: + new_var = symbols.get(f"&{sym_ir.value}", None) + if new_var is None: + new_var = ctx.get_next_variable() + symbols[f"&{sym_ir.value}"] = new_var + v = _convert_ir_basicblock(ctx, sym_ir, symbols, variables, allocated_variables) + op = IROperand(v) + inst = IRInstruction("store", [op], new_var) + ctx.get_basic_block().append_instruction(inst) + return new_var + else: + new_var = _convert_ir_basicblock( + ctx, sym_ir, symbols, variables, allocated_variables + ) + return new_var + + elif ir.value == "mstore": + if ir.args[0].value == "return_buffer": + sym = symbols.get("return_buffer", None) + if sym is None: + # return_buffer_var = ctx.append_instruction("alloca", [IRLiteral(32), IRLiteral(0)]) + symbols["return_buffer"] = ctx.get_next_variable() + + 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) + + # if sym_ir.is_literal is False: + # sym = symbols.get(f"{sym_ir.value}", None) + # if sym_ir.value == "return_buffer": + # if sym is None: + # new_var = ctx.append_instruction("store", [arg_1], sym_ir) + # symbols[f"{sym_ir.value}"] = new_var + # return new_var + # return sym + + var = _get_variable_from_address(variables, sym_ir.value) if sym_ir.is_literal else None if var is not None: if var.size > 32: - if _allocated_variables.get(var.name, None) is None: - _allocated_variables[var.name] = ctx.append_instruction( + if allocated_variables.get(var.name, None) is None: + allocated_variables[var.name] = ctx.append_instruction( "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] ) @@ -503,7 +580,7 @@ def _convert_ir_basicblock( if offset > 0: ptr_var = ctx.append_instruction("add", [IRLiteral(var.pos), IRLiteral(offset)]) else: - ptr_var = _allocated_variables[var.name] + ptr_var = allocated_variables[var.name] return ctx.append_instruction( "mstore", [arg_1, IROperand(ptr_var, DataType.PTR)], False @@ -512,14 +589,19 @@ def _convert_ir_basicblock( if sym_ir.is_literal: new_var = ctx.append_instruction("store", [arg_1], sym_ir) symbols[f"&{sym_ir.value}"] = new_var - if _allocated_variables.get(var.name, None) is None: - _allocated_variables[var.name] = new_var + if allocated_variables.get(var.name, None) is None: + allocated_variables[var.name] = new_var return new_var return arg_1 else: - sym = symbols.get(f"&{sym_ir.value}", None) + if sym_ir.is_literal is False: + inst = IRInstruction("store", [arg_1], sym_ir) + ctx.get_basic_block().append_instruction(inst) + return sym_ir + + sym = symbols.get(f"&{sym_ir.value}", sym_ir) if sym is None: - new_var = ctx.append_instruction("store", [arg_1], sym_ir) + new_var = ctx.append_instruction("store", [arg_1]) symbols[f"&{sym_ir.value}"] = new_var return new_var @@ -530,11 +612,11 @@ def _convert_ir_basicblock( return arg_1 elif ir.value == "sload": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) return ctx.append_instruction("sload", [arg_0]) elif ir.value == "sstore": - arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) + 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("sstore", [arg_1, arg_0]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "unique_symbol": @@ -542,8 +624,6 @@ def _convert_ir_basicblock( new_var = ctx.get_next_variable() symbols[f"&{sym.value}"] = new_var return new_var - elif ir.value == "return_buffer": - return IRLabel("return_buffer", True) elif ir.value == "repeat": # # repeat(sym, start, end, bound, body) @@ -555,9 +635,9 @@ def _convert_ir_basicblock( # 6) exit block # TODO: Add the extra bounds check after clarify sym = ir.args[0] - start = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) - end = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) - bound = _convert_ir_basicblock(ctx, ir.args[3], symbols, variables) + start = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + end = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + bound = _convert_ir_basicblock(ctx, ir.args[3], symbols, variables, allocated_variables) body = ir.args[4] entry_block = ctx.get_basic_block() @@ -593,7 +673,7 @@ def _convert_ir_basicblock( ctx.append_basic_block(body_block) old_targets = _break_target, _continue_target _break_target, _continue_target = exit_block, increment_block - _convert_ir_basicblock(ctx, body, symbols, variables) + _convert_ir_basicblock(ctx, body, symbols, variables, allocated_variables) _break_target, _continue_target = old_targets body_end = ctx.get_basic_block() if body_end.is_terminal() is False: @@ -630,24 +710,27 @@ def _convert_ir_basicblock( return ctx.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) - arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables) - size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables) + 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]) 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) + arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) ctx.append_instruction("selfdestruct", [arg_0], False) elif isinstance(ir.value, str) and ir.value.startswith("log"): # count = int(ir.value[3:]) - args = [_convert_ir_basicblock(ctx, arg, symbols, variables) for arg in ir.args] + args = [ + _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) + for arg in ir.args + ] inst = IRInstruction(ir.value, args) ctx.get_basic_block().append_instruction(inst) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): - _convert_ir_opcode(ctx, ir, symbols, variables) + _convert_ir_opcode(ctx, ir, symbols, variables, allocated_variables) elif isinstance(ir.value, str) and ir.value in symbols: return symbols[ir.value] elif ir.is_literal: @@ -658,12 +741,16 @@ def _convert_ir_basicblock( return None -def _convert_ir_opcode(ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: set) -> None: +def _convert_ir_opcode( + ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: OrderedSet +) -> None: opcode = str(ir.value).upper() inst_args = [] for arg in ir.args: if isinstance(arg, IRnode): - inst_args.append(_convert_ir_basicblock(ctx, arg, symbols, variables)) + inst_args.append( + _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) + ) instruction = IRInstruction(opcode, inst_args) ctx.get_basic_block().append_instruction(instruction) From 8acfa2d67c4ff7dbc8d065d63e3726858579b608 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 22 Sep 2023 16:32:34 +0300 Subject: [PATCH 181/471] reset allocated variables for each function --- vyper/codegen/function_definitions/common.py | 1 + vyper/ir/ir_to_bb_pass.py | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/function_definitions/common.py b/vyper/codegen/function_definitions/common.py index 0be014e76b..cced2046e7 100644 --- a/vyper/codegen/function_definitions/common.py +++ b/vyper/codegen/function_definitions/common.py @@ -155,6 +155,7 @@ def generate_ir_for_function( # (note: internal functions do not need to adjust gas estimate since mem_expansion_cost = calc_mem_gas(func_t._ir_info.frame_info.mem_used) # type: ignore ret.common_ir.add_gas_estimate += mem_expansion_cost # type: ignore + ret.common_ir.passthrough_metadata["func_t"] = func_t ret.common_ir.passthrough_metadata["frame_info"] = frame_info else: ret.func_ir.passthrough_metadata["frame_info"] = frame_info diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index fc90a0495d..91eeff0ac1 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -58,7 +58,7 @@ def generate_assembly_experimental( def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: global_function = IRFunction(IRLabel("global")) - _convert_ir_basicblock(global_function, ir, {}, {}) + _convert_ir_basicblock(global_function, ir, {}, {}, {}) revert_bb = IRBasicBlock(IRLabel("__revert"), global_function) revert_bb = global_function.append_basic_block(revert_bb) @@ -245,16 +245,17 @@ def _convert_ir_basicblock( _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: return _handle_self_call(ctx, ir, symbols, variables, allocated_variables) - elif ir.passthrough_metadata.get("func_t", None) is not None: + elif func_t is not None: symbols = {} + allocated_variables = {} variables = OrderedSet( {v: True for v in ir.passthrough_metadata["frame_info"].frame_vars.values()} ) - # variables = OrderedSet() - func_t = ir.passthrough_metadata["func_t"] - ir = _handle_internal_func(ctx, ir, func_t, symbols) + if func_t.is_internal: + ir = _handle_internal_func(ctx, ir, func_t, symbols) # fallthrough ret = None @@ -349,7 +350,9 @@ def _convert_ir_basicblock( else: with_symbols[sym.value] = ret - return _convert_ir_basicblock(ctx, ir.args[2], with_symbols) # body + return _convert_ir_basicblock( + ctx, ir.args[2], with_symbols, variables, allocated_variables + ) # body elif ir.value == "goto": return _append_jmp(ctx, IRLabel(ir.args[0].value)) elif ir.value == "jump": @@ -547,7 +550,7 @@ def _convert_ir_basicblock( new_var = _convert_ir_basicblock( ctx, sym_ir, symbols, variables, allocated_variables ) - return new_var + return ctx.append_instruction("mload", [new_var]) elif ir.value == "mstore": if ir.args[0].value == "return_buffer": From 715a533be8a46bd0ef918637bdff4d3ebf5dc5a6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 25 Sep 2023 14:42:55 +0300 Subject: [PATCH 182/471] fix --- vyper/ir/ir_to_bb_pass.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 157c3b94dd..4f1a91366f 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -169,9 +169,12 @@ def _get_variable_from_address(variables: set, addr: int) -> IRVariable: def _get_return_for_stack_operand( ctx: IRFunction, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable ): - sym = symbols.get(f"&{ret_ir.value}", None) - new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) - ctx.append_instruction("mstore", [sym, IROperand(new_var, DataType.PTR)], False) + if ret_ir.is_literal: + sym = symbols.get(f"&{ret_ir.value}", None) + new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) + ctx.append_instruction("mstore", [sym, IROperand(new_var, DataType.PTR)], False) + else: + new_var = ret_ir return IRInstruction("return", [last_ir, IROperand(new_var, DataType.PTR)]) @@ -417,7 +420,11 @@ def _convert_ir_basicblock( ret_ir = _convert_ir_basicblock(ctx, ret_var, symbols, variables) - var = _get_variable_from_address(variables, ret_ir.value) + var = ( + _get_variable_from_address(variables, ret_ir.value) + if ret_ir.is_literal + else None + ) if var is not None: allocated_var = _allocated_variables.get(var.name, None) assert allocated_var is not None, "unallocated variable" From ab7642698a53fe08c3b825e70ee6e5b9f1d74ab2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 25 Sep 2023 15:23:41 +0300 Subject: [PATCH 183/471] fix mstore of internal call --- vyper/ir/ir_to_bb_pass.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index bb0cbbd064..cd775ef720 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -557,6 +557,8 @@ def _convert_ir_basicblock( new_var = _convert_ir_basicblock( ctx, sym_ir, symbols, variables, allocated_variables ) + if sym_ir.is_self_call: + return new_var return ctx.append_instruction("mload", [new_var]) elif ir.value == "mstore": From b5cd0d7b8d56325acd4a7fbe7da77bd8d4ea4c71 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 25 Sep 2023 17:28:49 +0300 Subject: [PATCH 184/471] direct return --- vyper/ir/ir_to_bb_pass.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index cd775ef720..3e5ca7d64d 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -115,7 +115,9 @@ def _handle_self_call( args_ir = ir.passthrough_metadata["args_ir"] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto + return_buf = goto_ir.args[1] # return buffer ret_values = [IRLabel(target_label)] + for arg in args_ir: if arg.is_literal: ret = _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) @@ -192,7 +194,13 @@ def _get_return_for_stack_operand( new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) ctx.append_instruction("mstore", [sym, IROperand(new_var, DataType.PTR)], False) else: - new_var = ret_ir + 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, IROperand(new_var, DataType.PTR)], False) + else: + new_var = ret_ir return IRInstruction("return", [last_ir, IROperand(new_var, DataType.PTR)]) @@ -557,6 +565,12 @@ def _convert_ir_basicblock( new_var = _convert_ir_basicblock( ctx, sym_ir, symbols, variables, allocated_variables ) + # + # Old IR gets it's return value as a reference in the stack + # New IR gets it's return value in stack in case of 32 bytes or less + # So here we detect ahead of time if this mload leads a self call and + # and we skip the mload + # if sym_ir.is_self_call: return new_var return ctx.append_instruction("mload", [new_var]) From f5fb8f5f4a57c557b43c11ce9f83dcde5f8d01c3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Sep 2023 11:57:55 +0300 Subject: [PATCH 185/471] comment updates --- vyper/codegen/dfg.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 21b168e61d..f9dad93f05 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -250,7 +250,7 @@ def _generate_evm_for_instruction_r( # generate EVM for op # - # Step 1: Manipulate stack + # Step 1: Apply instruction special stack manipulations if opcode in ["jmp", "jnz"]: operands = inst.get_non_label_operands() @@ -276,6 +276,7 @@ def _generate_evm_for_instruction_r( # Step 2: Emit instructions input operands _emit_input_operands(ctx, assembly, inst, operands, stack_map) + # Step 3: Reorder stack if opcode in ["jnz", "jmp"] and stack_map.get_height() >= 2: _, b = next(enumerate(inst.parent.out_set)) target_stack = b.get_liveness() @@ -284,12 +285,12 @@ def _generate_evm_for_instruction_r( _stack_duplications(assembly, stack_map, operands) _stack_reorder(assembly, stack_map, operands) - # Step 3: Push instruction's return value to stack + # Step 4: Push instruction's return value to stack stack_map.pop(len(operands)) if inst.ret is not None: stack_map.push(inst.ret.target) - # Step 4: Emit the EVM instruction(s) + # Step 5: Emit the EVM instruction(s) if opcode in ONE_TO_ONE_INSTRUCTIONS: assembly.append(opcode.upper()) elif opcode == "alloca": @@ -334,7 +335,7 @@ def _generate_evm_for_instruction_r( elif opcode == "call": assembly.append("CALL") elif opcode == "ret": - assert len(inst.operands) == 2, "ret instruction takes one operand" + assert len(inst.operands) == 2, "ret instruction takes two operands" assembly.append("JUMP") elif opcode == "return": assembly.append("RETURN") @@ -376,7 +377,7 @@ def _generate_evm_for_instruction_r( else: raise Exception(f"Unknown opcode: {opcode}") - # Step 5: Emit instructions output operands (if any) + # Step 6: Emit instructions output operands (if any) # FIXME: WHOLE THING NEEDS REFACTOR if inst.ret is not None: assert inst.ret.is_variable, "Return value must be a variable" From 5ee0204efa0fd4727d0af2e3befe147118d2b358 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Sep 2023 11:58:05 +0300 Subject: [PATCH 186/471] pass return buffer --- vyper/ir/ir_to_bb_pass.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 3e5ca7d64d..c2b79a89a4 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -116,21 +116,21 @@ def _handle_self_call( goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto return_buf = goto_ir.args[1] # return buffer - ret_values = [IRLabel(target_label)] + ret_args = [IRLabel(target_label), IRLiteral(return_buf.value)] for arg in args_ir: if arg.is_literal: ret = _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) - ret_values.append(ret) + ret_args.append(ret) else: ret = _convert_ir_basicblock( 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]) - ret_values.append(ret) + ret_args.append(ret) - return ctx.append_instruction("invoke", ret_values) + return ctx.append_instruction("invoke", ret_args) def _handle_internal_func( @@ -156,6 +156,12 @@ def _handle_internal_func( bb.append_instruction(alloca_inst) symbols["return_pc"] = new_var + # return buffer + new_var = ctx.get_next_variable() + alloca_inst = IRInstruction("param", [], new_var) + bb.append_instruction(alloca_inst) + symbols["return_buffer"] = new_var + return ir.args[0].args[2] @@ -496,7 +502,13 @@ def _convert_ir_basicblock( inst = IRInstruction("ret", [symbols["return_pc"]]) else: ret_var = ir.args[1] - inst = IRInstruction("ret", [symbols["return_buffer"], symbols["return_pc"]]) + inst = IRInstruction( + "ret", + [ + symbols["return_pc"], + symbols["return_buffer"], + ], + ) ctx.get_basic_block().append_instruction(inst) @@ -576,12 +588,6 @@ def _convert_ir_basicblock( return ctx.append_instruction("mload", [new_var]) elif ir.value == "mstore": - if ir.args[0].value == "return_buffer": - sym = symbols.get("return_buffer", None) - if sym is None: - # return_buffer_var = ctx.append_instruction("alloca", [IRLiteral(32), IRLiteral(0)]) - symbols["return_buffer"] = ctx.get_next_variable() - 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) @@ -621,7 +627,7 @@ def _convert_ir_basicblock( return arg_1 else: if sym_ir.is_literal is False: - inst = IRInstruction("store", [arg_1], sym_ir) + inst = IRInstruction("mstore", [arg_1, sym_ir]) ctx.get_basic_block().append_instruction(inst) return sym_ir From 43d9a6dc57c7c0ff76cb9250cb086a9966e8979c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Sep 2023 14:37:48 +0300 Subject: [PATCH 187/471] annotations --- vyper/codegen/ir_basicblock.py | 5 +++++ vyper/ir/ir_to_bb_pass.py | 14 +++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 4d2271ed52..b04ca1df48 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -162,6 +162,7 @@ class IRInstruction: liveness: set[IRVariable] parent: Optional["IRBasicBlock"] fen: int + annotation: Optional[str] def __init__( self, @@ -189,6 +190,7 @@ def __init__( self.liveness = set() self.parent = None self.fen = -1 + self.annotation = None def get_label_operands(self) -> list[IRLabel]: """ @@ -232,6 +234,9 @@ def __repr__(self) -> str: if self.dbg: return s + f" {self.dbg}" + if self.annotation: + s += f" <{self.annotation}>" + if self.liveness: return f"{s: <30} # {self.liveness}" diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index c2b79a89a4..3d2c9ddd31 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -116,7 +116,7 @@ def _handle_self_call( goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto return_buf = goto_ir.args[1] # return buffer - ret_args = [IRLabel(target_label), IRLiteral(return_buf.value)] + ret_args = [IRLabel(target_label)] for arg in args_ir: if arg.is_literal: @@ -130,7 +130,12 @@ def _handle_self_call( ret = ctx.append_instruction(arg.location.load_op, [ret]) ret_args.append(ret) - return ctx.append_instruction("invoke", ret_args) + if return_buf.is_literal: + ret_args.append(IRLiteral(return_buf.value)) + + invoke_ret = ctx.append_instruction("invoke", ret_args) + allocated_variables["return_buffer"] = invoke_ret + return invoke_ret def _handle_internal_func( @@ -142,10 +147,11 @@ def _handle_internal_func( old_ir_mempos = 0 old_ir_mempos += 64 - for _ in func_t.arguments: + 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 old_ir_mempos += 32 # arg.typ.memory_bytes_required @@ -154,12 +160,14 @@ def _handle_internal_func( 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 # return buffer 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 return ir.args[0].args[2] From 1c4aa1cd5138bbff882a53bd18782c9a823446b7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Sep 2023 14:51:18 +0300 Subject: [PATCH 188/471] return buffer --- vyper/ir/ir_to_bb_pass.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 3d2c9ddd31..fd0c58780a 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -116,7 +116,9 @@ def _handle_self_call( goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto return_buf = goto_ir.args[1] # return buffer - ret_args = [IRLabel(target_label)] + ret_args = [ + IRLabel(target_label), + ] for arg in args_ir: if arg.is_literal: @@ -156,19 +158,19 @@ def _handle_internal_func( symbols[f"&{old_ir_mempos}"] = new_var old_ir_mempos += 32 # arg.typ.memory_bytes_required - # return address + # return buffer 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 + alloca_inst.annotation = "return_buffer" + symbols["return_buffer"] = new_var - # 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_buffer" - symbols["return_buffer"] = new_var + alloca_inst.annotation = "return_pc" + symbols["return_pc"] = new_var return ir.args[0].args[2] @@ -513,8 +515,8 @@ def _convert_ir_basicblock( inst = IRInstruction( "ret", [ - symbols["return_pc"], symbols["return_buffer"], + symbols["return_pc"], ], ) From 249f62a617be35265e73b24d92d2244f27b4d9e7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Sep 2023 14:59:19 +0300 Subject: [PATCH 189/471] proper indexing in return --- vyper/ir/ir_to_bb_pass.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index fd0c58780a..e9fe3b927c 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -496,7 +496,14 @@ def _convert_ir_basicblock( new_var = symbols.get(f"&{ret_ir.value}", IRVariable(ret_ir)) if var.size > 32: - new_op = IROperand(allocated_var, DataType.PTR) + offset = ret_ir.value - var.pos + if offset > 0: + ptr_var = ctx.append_instruction( + "add", [IRLiteral(var.pos), IRLiteral(offset)] + ) + else: + ptr_var = allocated_var + new_op = IROperand(ptr_var, DataType.PTR) inst = IRInstruction("return", [last_ir, new_op]) else: inst = _get_return_for_stack_operand(ctx, symbols, ret_ir, last_ir) From 20b4a260bdebeeab01b55e7ea60b222e091bddd9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Sep 2023 15:18:59 +0300 Subject: [PATCH 190/471] by value return --- vyper/codegen/dfg.py | 2 +- vyper/ir/ir_to_bb_pass.py | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index f9dad93f05..2f6f04a159 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -335,7 +335,7 @@ def _generate_evm_for_instruction_r( elif opcode == "call": assembly.append("CALL") elif opcode == "ret": - assert len(inst.operands) == 2, "ret instruction takes two operands" + # assert len(inst.operands) == 2, "ret instruction takes two operands" assembly.append("JUMP") elif opcode == "return": assembly.append("RETURN") diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index e9fe3b927c..7dba2f8070 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -519,13 +519,23 @@ def _convert_ir_basicblock( inst = IRInstruction("ret", [symbols["return_pc"]]) else: ret_var = ir.args[1] - inst = IRInstruction( - "ret", - [ - symbols["return_buffer"], - symbols["return_pc"], - ], - ) + if func_t.return_type.memory_bytes_required > 32: + inst = IRInstruction( + "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) From 0b2172cc76471e72184faebf54beaee211fe4539 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 26 Sep 2023 16:08:47 +0300 Subject: [PATCH 191/471] handle no return case --- vyper/codegen/dfg.py | 2 +- vyper/ir/ir_to_bb_pass.py | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 2f6f04a159..c03b1742e0 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -329,7 +329,7 @@ def _generate_evm_for_instruction_r( ] ) label_counter += 1 - if stack_map.peek(0).use_count == 0: + if stack_map.get_height() > 0 and stack_map.peek(0).use_count == 0: stack_map.pop() assembly.append("POP") elif opcode == "call": diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 7dba2f8070..12b3f256b5 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -112,6 +112,7 @@ def _handle_self_call( variables: OrderedSet, allocated_variables: dict[str, IRVariable], ) -> None: + func_t = ir.passthrough_metadata.get("func_t", None) args_ir = ir.passthrough_metadata["args_ir"] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto @@ -135,7 +136,7 @@ def _handle_self_call( if return_buf.is_literal: ret_args.append(IRLiteral(return_buf.value)) - invoke_ret = ctx.append_instruction("invoke", ret_args) + invoke_ret = ctx.append_instruction("invoke", ret_args, func_t.return_type is not None) allocated_variables["return_buffer"] = invoke_ret return invoke_ret @@ -159,11 +160,12 @@ def _handle_internal_func( old_ir_mempos += 32 # arg.typ.memory_bytes_required # return buffer - 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 + 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 # return address new_var = ctx.get_next_variable() @@ -658,11 +660,10 @@ def _convert_ir_basicblock( ctx.get_basic_block().append_instruction(inst) return sym_ir - sym = symbols.get(f"&{sym_ir.value}", sym_ir) + sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: - new_var = ctx.append_instruction("store", [arg_1]) - symbols[f"&{sym_ir.value}"] = new_var - return new_var + symbols[f"&{sym_ir.value}"] = arg_1 + return arg_1 if sym_ir.is_literal: return arg_1 From cfc9436f0744fc994864cf253eedb458f3fd1a52 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 26 Sep 2023 11:56:09 -0400 Subject: [PATCH 192/471] add a missing param --- vyper/ir/ir_to_bb_pass.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 12b3f256b5..58a3f9c146 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -802,7 +802,7 @@ def _convert_ir_basicblock( def _convert_ir_opcode( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: OrderedSet + ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: OrderedSet, allocated_variables: dict[str, IRVariable] ) -> None: opcode = str(ir.value).upper() inst_args = [] From e5e457852dd3802e229cc64ebe7fdc29e25a74ba Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 27 Sep 2023 14:36:34 +0300 Subject: [PATCH 193/471] dynamic arrays support --- vyper/ir/ir_to_bb_pass.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 58a3f9c146..6a93204b09 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -510,7 +510,18 @@ def _convert_ir_basicblock( else: inst = _get_return_for_stack_operand(ctx, symbols, ret_ir, last_ir) else: - inst = _get_return_for_stack_operand(ctx, symbols, ret_ir, last_ir) + if ret_ir.is_literal: + sym = symbols.get(f"&{ret_ir.value}", None) + if sym is None: + inst = IRInstruction("return", [last_ir, ret_ir]) + else: + new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) + ctx.append_instruction( + "mstore", [sym, IROperand(new_var, DataType.PTR)], False + ) + inst = IRInstruction("return", [last_ir, new_var]) + else: + inst = IRInstruction("return", [last_ir, ret_ir]) ctx.get_basic_block().append_instruction(inst) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) @@ -802,7 +813,11 @@ def _convert_ir_basicblock( def _convert_ir_opcode( - ctx: IRFunction, ir: IRnode, symbols: SymbolTable, variables: OrderedSet, allocated_variables: dict[str, IRVariable] + ctx: IRFunction, + ir: IRnode, + symbols: SymbolTable, + variables: OrderedSet, + allocated_variables: dict[str, IRVariable], ) -> None: opcode = str(ir.value).upper() inst_args = [] From d9e72e948e599d58d083e070c5d3e09c07a5d85c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 27 Sep 2023 14:44:19 +0300 Subject: [PATCH 194/471] staticcall --- vyper/codegen/ir_basicblock.py | 1 + vyper/ir/bb_optimizer.py | 2 +- vyper/ir/ir_to_bb_pass.py | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index b04ca1df48..9181bc21fd 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -176,6 +176,7 @@ def __init__( "param", "alloca", "call", + "staticcall", "invoke", "sload", "sstore", diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 500c4ea139..87498cbe29 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -114,7 +114,7 @@ def _calculate_in_set(ctx: IRFunction) -> None: ), "Last instruction should be a terminator" + str(bb) for inst in bb.instructions: - if inst.opcode in ["jmp", "jnz", "call", "invoke", "deploy"]: + if inst.opcode in ["jmp", "jnz", "call", "staticcall", "invoke", "deploy"]: ops = inst.get_label_operands() for op in ops: ctx.get_basic_block(op.value).add_in(bb) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 6a93204b09..0d134ee241 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -292,7 +292,7 @@ def _convert_ir_basicblock( ret = _convert_ir_basicblock(ctx, ir_node, symbols, variables, allocated_variables) return ret - elif ir.value == "call": # external call + elif ir.value in ["staticcall", "call"]: # external call gas = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) address = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) value = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) @@ -314,7 +314,9 @@ def _convert_ir_basicblock( symbols[f"&{retOffset.value}"] = retVar inst = IRInstruction( - "call", [gas, address, value, argsOffsetOp, argsSize, retOffset, retSize][::-1], retVar + ir.value, + [gas, address, value, argsOffsetOp, argsSize, retOffset, retSize][::-1], + retVar, ) ctx.get_basic_block().append_instruction(inst) return retVar From af34290ca8c8980787d4e21404400107235ad350 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 27 Sep 2023 15:00:15 +0300 Subject: [PATCH 195/471] datacopy fix --- vyper/ir/ir_to_bb_pass.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 0d134ee241..318fa35431 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -403,7 +403,7 @@ def _convert_ir_basicblock( size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) new_var = arg_0 - var = _get_variable_from_address(variables, arg_0.value) + var = _get_variable_from_address(variables, arg_0.value) if arg_0.is_literal else None if var is not None: if allocated_variables.get(var.name, None) is None: new_var = ctx.append_instruction( @@ -411,10 +411,10 @@ def _convert_ir_basicblock( ) allocated_variables[var.name] = new_var ctx.append_instruction("calldatacopy", [size, arg_1, new_var], False) + symbols[f"&{var.pos}"] = new_var else: ctx.append_instruction("calldatacopy", [size, arg_1, new_var], False) - symbols[f"&{var.pos}"] = new_var return new_var elif ir.value == "codecopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) From 9414766c8adc1e9e7b513763d932f75f4a90a8cb Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 27 Sep 2023 15:24:55 +0300 Subject: [PATCH 196/471] refactor our IROperand --- vyper/codegen/dfg.py | 18 ++++---- vyper/codegen/ir_basicblock.py | 77 ++++++---------------------------- vyper/codegen/ir_function.py | 7 +--- vyper/compiler/utils.py | 11 ++--- vyper/ir/ir_to_bb_pass.py | 37 ++++++---------- 5 files changed, 43 insertions(+), 107 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index c03b1742e0..c9f484dc74 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,4 +1,4 @@ -from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction, IROperand, IRVariable +from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction, IRVariable, IRValueBase from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly @@ -49,11 +49,11 @@ class DFGNode: - value: IRInstruction | IROperand + value: IRInstruction | IRValueBase predecessors: list["DFGNode"] successors: list["DFGNode"] - def __init__(self, value: IRInstruction | IROperand): + def __init__(self, value: IRInstruction | IRValueBase): self.value = value self.predecessors = [] self.successors = [] @@ -147,7 +147,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: return asm -def _stack_duplications(assembly: list, stack_map: StackMap, stack_ops: list[IROperand]) -> None: +def _stack_duplications(assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase]) -> None: for op in stack_ops: assert op.target.use_count >= 0, "Operand used up" depth = stack_map.get_depth_in(op) @@ -159,7 +159,7 @@ def _stack_duplications(assembly: list, stack_map: StackMap, stack_ops: list[IRO def _stack_reorder( - assembly: list, stack_map: StackMap, stack_ops: list[IROperand], phi_vars: dict = {} + assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase], phi_vars: dict = {} ) -> None: def f(x): return phi_vars.get(str(x), x) @@ -199,7 +199,7 @@ def _generate_evm_for_basicblock_r( in_vars |= in_bb.out_vars.difference(basicblock.in_vars_for(in_bb)) for var in in_vars: - depth = stack_map.get_depth_in(IROperand(var)) + depth = stack_map.get_depth_in(IRValueBase(var)) # assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" if depth is StackMap.NOT_IN_STACK: continue @@ -396,7 +396,11 @@ def _generate_evm_for_instruction_r( def _emit_input_operands( - ctx: IRFunction, assembly: list, inst: IRInstruction, ops: list[IROperand], stack_map: StackMap + ctx: IRFunction, + assembly: list, + inst: IRInstruction, + ops: list[IRValueBase], + stack_map: StackMap, ) -> None: for op in ops: if op.is_label: diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 9181bc21fd..fcc03ccaca 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -67,6 +67,7 @@ class IRVariable(IRValueBase): IRVariable represents a variable in IR. A variable is a string that starts with a %. """ + offset: int = 0 MemType = Enum("MemType", ["OPERAND_STACK", "MEMORY"]) mem_type: MemType = MemType.OPERAND_STACK mem_addr: int = -1 @@ -77,6 +78,7 @@ def __init__( if isinstance(value, IRLiteral): value = value.value super().__init__(value) + self.offset = 0 self.mem_type = mem_type self.mem_addr = mem_addr @@ -93,59 +95,6 @@ def __init__(self, value: str, is_symbol: bool = False) -> None: self.is_symbol = is_symbol -IROperandTarget = IRLiteral | IRVariable | IRLabel - -DataType = Enum("Type", ["VALUE", "PTR"]) -DataType_to_str = {DataType.VALUE: "", DataType.PTR: "ptr"} - - -class IROperand: - """ - IROperand represents an operand of an IR instuction. An operand can be a variable, - label, or a constant. - """ - - Direction = Enum("Direction", ["IN", "OUT"]) - type: DataType = DataType.VALUE - target: IRValueBase - direction: Direction = Direction.IN - - def __init__( - self, - target: IRValueBase, - type: DataType = DataType.VALUE, - ) -> None: - assert isinstance(target, IRValueBase), "value must be an IRValueBase" - assert isinstance(type, DataType), "type must be a DataType" - self.target = target - self.type = type - self.direction = IROperand.Direction.IN - - def is_targeting(self, target: IRValueBase) -> bool: - return self.target.value == target.value - - @property - def value(self) -> IRValueBaseValue: - return self.target.value - - @property - def is_literal(self) -> bool: - return isinstance(self.target, IRLiteral) - - @property - def is_variable(self) -> bool: - return isinstance(self.target, IRVariable) - - @property - def is_label(self) -> bool: - return isinstance(self.target, IRLabel) - - def __repr__(self) -> str: - if self.type == DataType.VALUE: - return f"{self.target}" - return f"{DataType_to_str[self.type]} {self.target}" - - class IRInstruction: """ IRInstruction represents an instruction in IR. Each instruction has an opcode, @@ -156,8 +105,8 @@ class IRInstruction: opcode: str volatile: bool - operands: list[IROperand] - ret: Optional[IROperand] + operands: list[IRValueBase] + ret: Optional[IRValueBase] dbg: Optional[IRDebugInfo] liveness: set[IRVariable] parent: Optional["IRBasicBlock"] @@ -167,8 +116,8 @@ class IRInstruction: def __init__( self, opcode: str, - operands: list[IROperand | IRValueBase], - ret: IROperand = None, + operands: list[IRValueBase], + ret: IRValueBase = None, dbg: IRDebugInfo = None, ): self.opcode = opcode @@ -185,8 +134,8 @@ def __init__( "mload", "calldatacopy", ] - self.operands = [op if isinstance(op, IROperand) else IROperand(op) for op in operands] - self.ret = ret if isinstance(ret, IROperand) else IROperand(ret) if ret else None + self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] + self.ret = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None self.dbg = dbg self.liveness = set() self.parent = None @@ -199,26 +148,26 @@ def get_label_operands(self) -> list[IRLabel]: """ return [op for op in self.operands if op.is_label] - def get_non_label_operands(self) -> list[IROperand]: + def get_non_label_operands(self) -> list[IRValueBase]: """ Get all input operands in instruction. """ return [op for op in self.operands if not op.is_label] - def get_input_operands(self) -> list[IROperand]: + def get_input_operands(self) -> list[IRValueBase]: """ Get all input operands for instruction. """ return [ op for op in self.operands - if op.is_variable # and op.direction == IROperand.Direction.IN + if op.is_variable # and op.direction == IRValueBase.Direction.IN ] - def get_output_operands(self) -> list[IROperand]: + def get_output_operands(self) -> list[IRValueBase]: output_operands = [self.ret] if self.ret else [] for op in self.operands: - if op.direction == IROperand.Direction.OUT: + if op.direction == IRValueBase.Direction.OUT: output_operands.append(op) return output_operands diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 3123d91130..33be497e50 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -4,7 +4,6 @@ IRBasicBlock, IRInstruction, IRLabel, - IROperand, IRValueBase, IRVariable, ) @@ -110,9 +109,7 @@ def remove_unreachable_blocks(self) -> int: self.basic_blocks = new_basic_blocks return removed - def append_instruction( - self, opcode: str, args: list[IROperand | IRValueBase], do_ret: bool = True - ): + def append_instruction(self, opcode: str, args: list[IRValueBase], do_ret: bool = True): """ Append instruction to last basic block. """ @@ -121,7 +118,7 @@ def append_instruction( self.get_basic_block().append_instruction(inst) return ret - def append_data(self, opcode: str, args: list[IROperand | IRValueBase]): + def append_data(self, opcode: str, args: list[IRValueBase]): """ Append data """ diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 2e55d506e2..516c818846 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -1,6 +1,6 @@ from typing import Dict -from vyper.codegen.ir_basicblock import IROperand, IRValueBase +from vyper.codegen.ir_basicblock import IRValueBase from vyper.semantics.types.function import ContractFunctionT @@ -76,7 +76,7 @@ def push(self, op: IRValueBase) -> None: def pop(self, num: int = 1) -> None: del self.stack_map[len(self.stack_map) - num :] - def get_depth_in(self, op: IROperand | list[IROperand], phi_vars: dict = {}) -> int: + def get_depth_in(self, op: IRValueBase | list[IRValueBase], phi_vars: dict = {}) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. @@ -85,15 +85,14 @@ def get_depth_in(self, op: IROperand | list[IROperand], phi_vars: dict = {}) -> isinstance(op, str) or isinstance(op, int) or isinstance(op, IRValueBase) - or isinstance(op, IROperand) or isinstance(op, list) - ), f"get_depth_in takes IROperand or list, got '{op}'" + ), f"get_depth_in takes IRValueBase or list, got '{op}'" def f(x): return str(phi_vars.get(str(x), x)) for i, stack_op in enumerate(self.stack_map[::-1]): - stack_op = stack_op.target if isinstance(stack_op, IROperand) else stack_op + stack_op = stack_op.target if isinstance(stack_op, IRValueBase) else stack_op if isinstance(stack_op, IRValueBase): if isinstance(op, str) and f(stack_op.value) == f(op): return -i @@ -101,8 +100,6 @@ def f(x): return -i if isinstance(op, IRValueBase) and f(stack_op.value) == f(op.value): return -i - if isinstance(op, IROperand) and f(stack_op.value) == f(op.target.value): - return -i elif isinstance(op, list) and f(stack_op) in [f(v.target) for v in op]: return -i diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 318fa35431..71537bcd74 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -2,12 +2,10 @@ from vyper.codegen.dfg import generate_evm from vyper.codegen.ir_basicblock import ( - DataType, IRBasicBlock, IRInstruction, IRLabel, IRLiteral, - IROperand, IRValueBase, IRVariable, ) @@ -210,16 +208,16 @@ def _get_return_for_stack_operand( if ret_ir.is_literal: sym = symbols.get(f"&{ret_ir.value}", None) new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) - ctx.append_instruction("mstore", [sym, IROperand(new_var, DataType.PTR)], False) + ctx.append_instruction("mstore", [sym, new_var], False) 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, IROperand(new_var, DataType.PTR)], False) + ctx.append_instruction("mstore", [ret_ir, new_var], False) else: new_var = ret_ir - return IRInstruction("return", [last_ir, IROperand(new_var, DataType.PTR)]) + return IRInstruction("return", [last_ir, new_var]) def _convert_ir_basicblock( @@ -308,14 +306,14 @@ def _convert_ir_basicblock( argsOffsetVar = symbols.get(f"&{addr}", argsOffset.value) argsOffsetVar.mem_type = IRVariable.MemType.MEMORY argsOffsetVar.mem_addr = addr - argsOffsetOp = IROperand(argsOffsetVar, True, 32 - 4 if argsOffset.value > 0 else 0) + argsOffsetVar.offset = 32 - 4 if argsOffset.value > 0 else 0 retVar = ctx.get_next_variable(IRVariable.MemType.MEMORY, retOffset.value) symbols[f"&{retOffset.value}"] = retVar inst = IRInstruction( ir.value, - [gas, address, value, argsOffsetOp, argsSize, retOffset, retSize][::-1], + [gas, address, value, argsOffsetVar, argsSize, retOffset, retSize][::-1], retVar, ) ctx.get_basic_block().append_instruction(inst) @@ -422,18 +420,15 @@ def _convert_ir_basicblock( arg_0_var = ctx.get_next_variable() arg_0_var.mem_type = IRVariable.MemType.MEMORY arg_0_var.mem_addr = 0 - alloca_op = IROperand(arg_0_var) - ctx.get_basic_block().append_instruction(IRInstruction("alloca", [], alloca_op)) - arg_0_op = IROperand(arg_0_var) # THis had offset + ctx.get_basic_block().append_instruction(IRInstruction("alloca", [], arg_0_var)) else: - arg_0_op = IROperand(arg_0) + arg_0_var = arg_0 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) ret_var = IRVariable("%ccd", IRVariable.MemType.MEMORY, 0) - ret_op = IROperand(ret_var) symbols[f"&0"] = ret_var - inst = IRInstruction("codecopy", [size, arg_1, arg_0_op], ret_op) + inst = IRInstruction("codecopy", [size, arg_1, arg_0_var], ret_var) ctx.get_basic_block().append_instruction(inst) elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) @@ -507,8 +502,7 @@ def _convert_ir_basicblock( ) else: ptr_var = allocated_var - new_op = IROperand(ptr_var, DataType.PTR) - inst = IRInstruction("return", [last_ir, new_op]) + inst = IRInstruction("return", [last_ir, ptr_var]) else: inst = _get_return_for_stack_operand(ctx, symbols, ret_ir, last_ir) else: @@ -518,9 +512,7 @@ def _convert_ir_basicblock( inst = IRInstruction("return", [last_ir, ret_ir]) else: new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) - ctx.append_instruction( - "mstore", [sym, IROperand(new_var, DataType.PTR)], False - ) + ctx.append_instruction("mstore", [sym, new_var], False) inst = IRInstruction("return", [last_ir, new_var]) else: inst = IRInstruction("return", [last_ir, ret_ir]) @@ -588,7 +580,7 @@ def _convert_ir_basicblock( else: ptr_var = allocated_variables[var.name] - return ctx.append_instruction("mload", [IROperand(ptr_var, DataType.PTR)]) + return ctx.append_instruction("mload", [ptr_var]) else: if sym_ir.is_literal: sym = symbols.get(f"&{sym_ir.value}", None) @@ -611,8 +603,7 @@ def _convert_ir_basicblock( new_var = ctx.get_next_variable() symbols[f"&{sym_ir.value}"] = new_var v = _convert_ir_basicblock(ctx, sym_ir, symbols, variables, allocated_variables) - op = IROperand(v) - inst = IRInstruction("store", [op], new_var) + inst = IRInstruction("store", [v], new_var) ctx.get_basic_block().append_instruction(inst) return new_var else: @@ -656,9 +647,7 @@ def _convert_ir_basicblock( else: ptr_var = allocated_variables[var.name] - return ctx.append_instruction( - "mstore", [arg_1, IROperand(ptr_var, DataType.PTR)], False - ) + return ctx.append_instruction("mstore", [arg_1, ptr_var], False) else: if sym_ir.is_literal: new_var = ctx.append_instruction("store", [arg_1], sym_ir) From a4cea2d8cb0e3473350fbc8af91f2f1f92b9a7b5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 27 Sep 2023 15:56:35 +0300 Subject: [PATCH 197/471] remove .target --- vyper/codegen/dfg.py | 60 ++++++++++++++++++---------------- vyper/codegen/ir_basicblock.py | 22 ++++--------- vyper/compiler/utils.py | 3 +- vyper/ir/bb_optimizer.py | 13 +++++--- 4 files changed, 48 insertions(+), 50 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index c9f484dc74..f0592a5090 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,4 +1,10 @@ -from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction, IRVariable, IRValueBase +from vyper.codegen.ir_basicblock import ( + IRBasicBlock, + IRInstruction, + IRVariable, + IRValueBase, + IRLabel, +) from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly @@ -70,20 +76,17 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: dfg_outputs = {} for bb in ctx.basic_blocks: for inst in bb.instructions: - variables = inst.get_input_operands() + operands = inst.get_input_operands() res = inst.get_output_operands() - for v in variables: - target = v.target - target.use_count += 1 - dfg_inputs[target.value] = ( - [inst] - if dfg_inputs.get(target.value) is None - else dfg_inputs[target.value] + [inst] + for op in operands: + op.use_count += 1 + dfg_inputs[op.value] = ( + [inst] if dfg_inputs.get(op.value) is None else dfg_inputs[op.value] + [inst] ) for op in res: - dfg_outputs[op.target.value] = inst + dfg_outputs[op.value] = inst def compute_phi_vars(ctx: IRFunction) -> None: @@ -149,13 +152,13 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: def _stack_duplications(assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase]) -> None: for op in stack_ops: - assert op.target.use_count >= 0, "Operand used up" + assert op.use_count >= 0, "Operand used up" depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" - if op.target.use_count > 1: + if op.use_count > 1: # Operand need duplication stack_map.dup(assembly, depth) - op.target.use_count -= 1 + op.use_count -= 1 def _stack_reorder( @@ -199,8 +202,7 @@ def _generate_evm_for_basicblock_r( in_vars |= in_bb.out_vars.difference(basicblock.in_vars_for(in_bb)) for var in in_vars: - depth = stack_map.get_depth_in(IRValueBase(var)) - # assert depth != StackMap.NOT_IN_STACK, "Operand not in stack" + depth = stack_map.get_depth_in(IRValueBase(var.value)) if depth is StackMap.NOT_IN_STACK: continue if depth != 0: @@ -268,9 +270,9 @@ def _generate_evm_for_instruction_r( if to_be_replaced.use_count > 1: stack_map.dup(assembly, depth) to_be_replaced.use_count -= 1 - stack_map.poke(0, ret.target) + stack_map.poke(0, ret) else: - stack_map.poke(depth, ret.target) + stack_map.poke(depth, ret) return assembly # Step 2: Emit instructions input operands @@ -288,7 +290,7 @@ def _generate_evm_for_instruction_r( # Step 4: Push instruction's return value to stack stack_map.pop(len(operands)) if inst.ret is not None: - stack_map.push(inst.ret.target) + stack_map.push(inst.ret) # Step 5: Emit the EVM instruction(s) if opcode in ONE_TO_ONE_INSTRUCTIONS: @@ -307,7 +309,7 @@ def _generate_evm_for_instruction_r( assembly.append(f"_sym_{inst.operands[1].value}") assembly.append("JUMPI") elif opcode == "jmp": - if inst.operands[0].is_label: + if isinstance(inst.operands[0], IRLabel): assembly.append(f"_sym_{inst.operands[0].value}") assembly.append("JUMP") else: @@ -318,7 +320,7 @@ def _generate_evm_for_instruction_r( assembly.append("LT") elif opcode == "invoke": target = inst.operands[0] - assert target.is_label, "invoke target must be a label" + assert isinstance(target, IRLabel), "invoke target must be a label" assembly.extend( [ f"_sym_label_ret_{label_counter}", @@ -380,16 +382,16 @@ def _generate_evm_for_instruction_r( # Step 6: Emit instructions output operands (if any) # FIXME: WHOLE THING NEEDS REFACTOR if inst.ret is not None: - assert inst.ret.is_variable, "Return value must be a variable" - if inst.ret.target.mem_type == IRVariable.MemType.MEMORY: + assert isinstance(inst.ret, IRVariable), "Return value must be a variable" + if inst.ret.mem_type == IRVariable.MemType.MEMORY: # # if inst.ret.address_access: FIXME: MEMORY REFACTOR # # if inst.opcode != "alloca": # FIXMEEEE # # if inst.opcode != "codecopy": # # assembly.extend([*PUSH(inst.ret.addr)]) # # else: - # assembly.extend([*PUSH(inst.ret.target.mem_addr + 30)]) + # assembly.extend([*PUSH(inst.ret.mem_addr + 30)]) # else: - assembly.extend([*PUSH(inst.ret.target.mem_addr)]) + assembly.extend([*PUSH(inst.ret.mem_addr)]) # assembly.append("MSTORE") return assembly @@ -403,25 +405,25 @@ def _emit_input_operands( stack_map: StackMap, ) -> None: for op in ops: - if op.is_label: + if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here # but we need to add it to the stack map if inst.opcode != "invoke": assembly.append(f"_sym_{op.value}") - stack_map.push(op.target) + stack_map.push(op) continue if op.is_literal: assembly.extend([*PUSH(op.value)]) - stack_map.push(op.target) + stack_map.push(op) continue assembly = _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) - if op.is_variable and op.target.mem_type == IRVariable.MemType.MEMORY: + if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: # FIXME: MEMORY REFACTOR # if op.address_access: # if inst.opcode != "codecopy": # assembly.extend([*PUSH(op.addr)]) # else: - assembly.extend([*PUSH(op.target.mem_addr)]) + assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") return assembly diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index fcc03ccaca..3326fa7c65 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -146,30 +146,22 @@ def get_label_operands(self) -> list[IRLabel]: """ Get all labels in instruction. """ - return [op for op in self.operands if op.is_label] + return [op for op in self.operands if isinstance(op, IRLabel)] def get_non_label_operands(self) -> list[IRValueBase]: """ Get all input operands in instruction. """ - return [op for op in self.operands if not op.is_label] + return [op for op in self.operands if not isinstance(op, IRLabel)] def get_input_operands(self) -> list[IRValueBase]: """ Get all input operands for instruction. """ - return [ - op - for op in self.operands - if op.is_variable # and op.direction == IRValueBase.Direction.IN - ] + return [op for op in self.operands if isinstance(op, IRVariable)] def get_output_operands(self) -> list[IRValueBase]: - output_operands = [self.ret] if self.ret else [] - for op in self.operands: - if op.direction == IRValueBase.Direction.OUT: - output_operands.append(op) - return output_operands + return [self.ret] if self.ret else [] def __repr__(self) -> str: s = "" @@ -177,7 +169,7 @@ def __repr__(self) -> str: s += f"{self.ret} = " s += f"{self.opcode} " operands = ", ".join( - [(f"label %{op}" if op.is_label else str(op)) for op in self.operands[::-1]] + [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in self.operands[::-1]] ) s += operands @@ -301,9 +293,9 @@ def calculate_liveness(self) -> None: liveness = self.out_vars.copy() for instruction in self.instructions[::-1]: ops = instruction.get_input_operands() - liveness = liveness.union(OrderedSet.fromkeys([op.target for op in ops])) + liveness = liveness.union(OrderedSet.fromkeys(ops)) out = ( - instruction.get_output_operands()[0].target + instruction.get_output_operands()[0] if len(instruction.get_output_operands()) > 0 else None ) diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 516c818846..8f53dbd103 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -92,7 +92,6 @@ def f(x): return str(phi_vars.get(str(x), x)) for i, stack_op in enumerate(self.stack_map[::-1]): - stack_op = stack_op.target if isinstance(stack_op, IRValueBase) else stack_op if isinstance(stack_op, IRValueBase): if isinstance(op, str) and f(stack_op.value) == f(op): return -i @@ -100,7 +99,7 @@ def f(x): return -i if isinstance(op, IRValueBase) and f(stack_op.value) == f(op.value): return -i - elif isinstance(op, list) and f(stack_op) in [f(v.target) for v in op]: + elif isinstance(op, list) and f(stack_op) in [f(v) for v in op]: return -i return StackMap.NOT_IN_STACK diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 87498cbe29..7022333574 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -1,4 +1,9 @@ -from vyper.codegen.ir_basicblock import TERMINATOR_IR_INSTRUCTIONS, IRBasicBlock, IRInstruction +from vyper.codegen.ir_basicblock import ( + TERMINATOR_IR_INSTRUCTIONS, + IRBasicBlock, + IRInstruction, + IRLabel, +) from vyper.codegen.ir_function import IRFunction from vyper.utils import OrderedSet @@ -32,7 +37,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: for i, inst in enumerate(bb.instructions[:-1]): if inst.volatile: continue - if inst.ret and inst.ret.target not in bb.instructions[i + 1].liveness: + if inst.ret and inst.ret not in bb.instructions[i + 1].liveness: removeList.append(inst) count += 1 @@ -66,8 +71,8 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> None: for bb2 in ctx.basic_blocks: for inst in bb2.instructions: for op in inst.operands: - if op.is_label and op.value == replaced_label.value: - op.target = replacement_label + if isinstance(op, IRLabel) and op.value == replaced_label.value: + op.value = replacement_label.value ctx.basic_blocks.remove(bb) i -= 1 From 3839531c57d5d5811e3efebb7193e27067eb5e2c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 28 Sep 2023 16:51:36 +0300 Subject: [PATCH 198/471] new dispatcher fix --- vyper/codegen/ir_basicblock.py | 1 + vyper/ir/ir_to_bb_pass.py | 25 ++++++------------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 3326fa7c65..0ab4829e1f 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -133,6 +133,7 @@ def __init__( "mstore", "mload", "calldatacopy", + "codecopy", ] self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] self.ret = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 71537bcd74..db5b2f4d5e 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -416,20 +416,10 @@ def _convert_ir_basicblock( return new_var elif ir.value == "codecopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - if arg_0.is_literal and arg_0.value == 30: - arg_0_var = ctx.get_next_variable() - arg_0_var.mem_type = IRVariable.MemType.MEMORY - arg_0_var.mem_addr = 0 - ctx.get_basic_block().append_instruction(IRInstruction("alloca", [], arg_0_var)) - else: - arg_0_var = arg_0 - 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) - ret_var = IRVariable("%ccd", IRVariable.MemType.MEMORY, 0) - symbols[f"&0"] = ret_var - inst = IRInstruction("codecopy", [size, arg_1, arg_0_var], ret_var) - ctx.get_basic_block().append_instruction(inst) + + ctx.append_instruction("codecopy", [size, arg_1, arg_0], False) elif ir.value == "symbol": return IRLabel(ir.args[0].value, True) elif ir.value == "data": @@ -599,13 +589,10 @@ def _convert_ir_basicblock( else: if sym_ir.is_literal: new_var = symbols.get(f"&{sym_ir.value}", None) - if new_var is None: - new_var = ctx.get_next_variable() - symbols[f"&{sym_ir.value}"] = new_var - v = _convert_ir_basicblock(ctx, sym_ir, symbols, variables, allocated_variables) - inst = IRInstruction("store", [v], new_var) - ctx.get_basic_block().append_instruction(inst) - return new_var + if new_var is not None: + return ctx.append_instruction("mload", [new_var]) + else: + return ctx.append_instruction("mload", [IRLiteral(sym_ir.value)]) else: new_var = _convert_ir_basicblock( ctx, sym_ir, symbols, variables, allocated_variables From 74fef10733c2fb76fdc8a95ef564d3106ae8ebf6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 29 Sep 2023 00:36:30 +0300 Subject: [PATCH 199/471] fix --- vyper/ir/ir_to_bb_pass.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index db5b2f4d5e..f3156d098d 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -651,8 +651,10 @@ def _convert_ir_basicblock( 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) symbols[f"&{sym_ir.value}"] = arg_1 - return arg_1 + return None if sym_ir.is_literal: return arg_1 From 52818f339a6f986828b36d1d8aaa2377589f81b4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 1 Oct 2023 22:54:16 +0300 Subject: [PATCH 200/471] dload, dloadbytes --- vyper/codegen/dfg.py | 2 +- vyper/codegen/ir_basicblock.py | 2 ++ vyper/ir/ir_to_bb_pass.py | 23 +++++++++++++++-------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index f0592a5090..d4ceadba87 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -303,7 +303,7 @@ def _generate_evm_for_instruction_r( pass elif opcode == "dbname": pass - elif opcode == "codecopy": + elif opcode in ["codecopy", "dloadbytes"]: assembly.append("CODECOPY") elif opcode == "jnz": assembly.append(f"_sym_{inst.operands[1].value}") diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 0ab4829e1f..38f486004f 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -134,6 +134,8 @@ def __init__( "mload", "calldatacopy", "codecopy", + "dloadbytes", + "dload", ] self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] self.ret = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index f3156d098d..b4be71b8c0 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -16,7 +16,7 @@ from vyper.ir.bb_optimizer import optimize_function from vyper.ir.compile_ir import is_mem_sym, is_symbol from vyper.semantics.types.function import ContractFunctionT -from vyper.utils import OrderedSet +from vyper.utils import OrderedSet, MemoryPositions BINARY_IR_INSTRUCTIONS = [ "eq", @@ -544,16 +544,23 @@ def _convert_ir_basicblock( elif ir.value == "dload": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - return ctx.append_instruction("calldataload", [arg_0]) + src = ctx.append_instruction("add", [arg_0, IRLabel("code_end")]) + + ctx.append_instruction( + "dloadbytes", [IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE)], False + ) + return ctx.append_instruction("mload", [IRLiteral(MemoryPositions.FREE_VAR_SPACE)]) elif ir.value == "dloadbytes": - src = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - dst = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + 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) - ret = ctx.get_next_variable() - inst = IRInstruction("codecopy", [len_, src, dst], ret) - ctx.get_basic_block().append_instruction(inst) - return ret + src = ctx.append_instruction("add", [src_offset, IRLabel("code_end")]) + + inst = IRInstruction("dloadbytes", [len_, src, dst]) + return ctx.get_basic_block().append_instruction(inst) elif ir.value == "mload": sym_ir = ir.args[0] var = _get_variable_from_address(variables, sym_ir.value) if sym_ir.is_literal else None From d2884ba2025b81781f9538d88ff68f695d033b58 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 3 Oct 2023 18:43:05 +0300 Subject: [PATCH 201/471] fix logs --- vyper/ir/ir_to_bb_pass.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index b4be71b8c0..eeee5a3505 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -785,7 +785,7 @@ def _convert_ir_basicblock( _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args ] - inst = IRInstruction(ir.value, args) + inst = IRInstruction(ir.value, args[::-1]) ctx.get_basic_block().append_instruction(inst) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): _convert_ir_opcode(ctx, ir, symbols, variables, allocated_variables) From 0912b5921f218e86e6c9e5dba83b1c70837d9a16 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 6 Oct 2023 15:04:38 +0300 Subject: [PATCH 202/471] proper sha order --- vyper/ir/ir_to_bb_pass.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index eeee5a3505..d8591b4ced 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -235,9 +235,7 @@ def _convert_ir_basicblock( variables |= vars if ir.value in BINARY_IR_INSTRUCTIONS: - return _convert_binary_op( - ctx, ir, symbols, variables, allocated_variables, ir.value in ["sha3", "sha3_64"] - ) + return _convert_binary_op(ctx, ir, symbols, variables, allocated_variables, False) elif ir.value in MAPPED_IR_INSTRUCTIONS.keys(): org_value = ir.value From f412a2c8d880522488ce5f40dc84b295413fc019 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 6 Oct 2023 20:15:47 +0300 Subject: [PATCH 203/471] gg --- vyper/ir/ir_to_bb_pass.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index d8591b4ced..069d101317 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -662,7 +662,9 @@ def _convert_ir_basicblock( return None if sym_ir.is_literal: - return arg_1 + inst = IRInstruction("mstore", [arg_1, sym]) + ctx.get_basic_block().append_instruction(inst) + return None else: symbols[sym_ir.value] = arg_1 return arg_1 @@ -778,7 +780,6 @@ def _convert_ir_basicblock( arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) ctx.append_instruction("selfdestruct", [arg_0], False) elif isinstance(ir.value, str) and ir.value.startswith("log"): - # count = int(ir.value[3:]) args = [ _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args From 41efef0afcb907fb5d0c6a94a6d223bdeb14eea4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 6 Oct 2023 20:16:28 +0300 Subject: [PATCH 204/471] experiment --- vyper/ir/ir_to_bb_pass.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 069d101317..0106ca4947 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -499,9 +499,12 @@ def _convert_ir_basicblock( if sym is None: inst = IRInstruction("return", [last_ir, ret_ir]) else: - 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]) + 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]) + else: + inst = IRInstruction("return", [last_ir, sym]) else: inst = IRInstruction("return", [last_ir, ret_ir]) From 3db019c9e87dbdeab20df18d65bf8bd2efb78561 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 7 Oct 2023 01:10:19 +0300 Subject: [PATCH 205/471] dont emit label in case of invoke --- vyper/codegen/dfg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index d4ceadba87..ec7ccce381 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -254,7 +254,7 @@ def _generate_evm_for_instruction_r( # Step 1: Apply instruction special stack manipulations - if opcode in ["jmp", "jnz"]: + if opcode in ["jmp", "jnz", "invoke"]: operands = inst.get_non_label_operands() elif opcode == "alloca": operands = inst.operands[1:2] From 135a358c67d4f1b635b1ddf97ce09371e31ec44e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 7 Oct 2023 01:11:14 +0300 Subject: [PATCH 206/471] erc20 passes all tests --- vyper/ir/ir_to_bb_pass.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 0106ca4947..b1ea4453a8 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -121,8 +121,12 @@ def _handle_self_call( for arg in args_ir: if arg.is_literal: - ret = _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) - ret_args.append(ret) + sym = symbols.get(f"&{arg.value}", None) + if sym is None: + ret = _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) + ret_args.append(ret) + else: + ret_args.append(sym) else: ret = _convert_ir_basicblock( ctx, arg._optimized, symbols, variables, allocated_variables @@ -235,7 +239,9 @@ def _convert_ir_basicblock( variables |= vars if ir.value in BINARY_IR_INSTRUCTIONS: - return _convert_binary_op(ctx, ir, symbols, variables, allocated_variables, False) + return _convert_binary_op( + ctx, ir, symbols, variables, allocated_variables, ir.value in ["sha3", "sha3_64"] + ) elif ir.value in MAPPED_IR_INSTRUCTIONS.keys(): org_value = ir.value @@ -463,6 +469,8 @@ def _convert_ir_basicblock( else: last_ir = None ret_var = ir.args[1] + if ret_var.is_literal and symbols.get(f"&{ret_var.value}", None) is not None: + del symbols[f"&{ret_var.value}"] for arg in ir.args[2:]: last_ir = _convert_ir_basicblock( ctx, arg, symbols, variables, allocated_variables @@ -504,7 +512,7 @@ def _convert_ir_basicblock( ctx.append_instruction("mstore", [sym, new_var], False) inst = IRInstruction("return", [last_ir, new_var]) else: - inst = IRInstruction("return", [last_ir, sym]) + inst = IRInstruction("return", [last_ir, ret_ir]) else: inst = IRInstruction("return", [last_ir, ret_ir]) @@ -645,11 +653,9 @@ def _convert_ir_basicblock( return ctx.append_instruction("mstore", [arg_1, ptr_var], False) else: if sym_ir.is_literal: - new_var = ctx.append_instruction("store", [arg_1], sym_ir) - symbols[f"&{sym_ir.value}"] = new_var + symbols[f"&{sym_ir.value}"] = arg_1 if allocated_variables.get(var.name, None) is None: - allocated_variables[var.name] = new_var - return new_var + allocated_variables[var.name] = arg_1 return arg_1 else: if sym_ir.is_literal is False: From 493fbc5ba1dd374692b9ba77480d734df4ea0887 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 7 Oct 2023 01:22:25 +0300 Subject: [PATCH 207/471] signextend and ne --- vyper/ir/ir_to_bb_pass.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index b1ea4453a8..722376cf7d 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -37,9 +37,10 @@ "exp", "sha3", "sha3_64", + "signextend", ] -MAPPED_IR_INSTRUCTIONS = {"le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} +MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} SymbolTable = dict[str, IRValueBase] From a445e43843265b31590ede1918f3a0c8f1289a87 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 7 Oct 2023 19:22:06 +0300 Subject: [PATCH 208/471] simple auction tests pass --- vyper/codegen/dfg.py | 1 - vyper/ir/ir_to_bb_pass.py | 11 +++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index ec7ccce381..23431fa496 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -168,7 +168,6 @@ def f(x): return phi_vars.get(str(x), x) stack_ops = [f(x.value) for x in stack_ops] - stack_ops = list(dict.fromkeys(stack_ops)) for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 722376cf7d..5368ab244d 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -308,10 +308,13 @@ def _convert_ir_basicblock( if argsOffset.is_literal: addr = argsOffset.value - 32 + 4 if argsOffset.value > 0 else 0 - argsOffsetVar = symbols.get(f"&{addr}", argsOffset.value) - argsOffsetVar.mem_type = IRVariable.MemType.MEMORY - argsOffsetVar.mem_addr = addr - argsOffsetVar.offset = 32 - 4 if argsOffset.value > 0 else 0 + argsOffsetVar = symbols.get(f"&{addr}", None) + if argsOffsetVar is None: + argsOffsetVar = argsOffset + else: + argsOffsetVar.mem_type = IRVariable.MemType.MEMORY + argsOffsetVar.mem_addr = addr + argsOffsetVar.offset = 32 - 4 if argsOffset.value > 0 else 0 retVar = ctx.get_next_variable(IRVariable.MemType.MEMORY, retOffset.value) symbols[f"&{retOffset.value}"] = retVar From 77f2a44a9f4d5219b9bd0d954da62856e1d43a96 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 7 Oct 2023 21:48:37 +0300 Subject: [PATCH 209/471] More tests pass * tests/examples/company/test_company.py * tests/examples/tokens/test_erc20.py * tests/examples/auctions/test_simple_open_auction.py * tests/examples/storage/test_storage.py --- vyper/codegen/dfg.py | 1 + vyper/codegen/ir_basicblock.py | 16 ++++++++-------- vyper/ir/ir_to_bb_pass.py | 12 +++++++++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 23431fa496..a83033411a 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -28,6 +28,7 @@ "timestamp", "caller", "selfdestruct", + "signextend", "stop", "shr", "shl", diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 38f486004f..c023f1a15a 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -253,14 +253,14 @@ def in_vars_for(self, bb: "IRBasicBlock" = None) -> set[IRVariable]: if bb: for inst in self.instructions: if inst.opcode == "select": - if inst.operands[0].target == bb.label: - liveness.add(inst.operands[1].target) - if inst.operands[3].target in liveness: - liveness.remove(inst.operands[3].target) - if inst.operands[2].target == bb.label: - liveness.add(inst.operands[3].target) - if inst.operands[1].target in liveness: - liveness.remove(inst.operands[1].target) + if inst.operands[0] == bb.label: + liveness.add(inst.operands[1]) + if inst.operands[3] in liveness: + liveness.remove(inst.operands[3]) + if inst.operands[2] == bb.label: + liveness.add(inst.operands[3]) + if inst.operands[1] in liveness: + liveness.remove(inst.operands[1]) return liveness diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 5368ab244d..6e95cac76c 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -492,7 +492,7 @@ def _convert_ir_basicblock( if var is not None: allocated_var = allocated_variables.get(var.name, None) assert allocated_var is not None, "unallocated variable" - new_var = symbols.get(f"&{ret_ir.value}", IRVariable(ret_ir)) + new_var = symbols.get(f"&{ret_ir.value}", allocated_var) if var.size > 32: offset = ret_ir.value - var.pos @@ -504,7 +504,7 @@ def _convert_ir_basicblock( ptr_var = allocated_var inst = IRInstruction("return", [last_ir, ptr_var]) else: - inst = _get_return_for_stack_operand(ctx, symbols, ret_ir, last_ir) + inst = _get_return_for_stack_operand(ctx, symbols, new_var, last_ir) else: if ret_ir.is_literal: sym = symbols.get(f"&{ret_ir.value}", None) @@ -518,7 +518,13 @@ def _convert_ir_basicblock( else: inst = IRInstruction("return", [last_ir, ret_ir]) else: - inst = IRInstruction("return", [last_ir, ret_ir]) + if last_ir.value > 32: + inst = IRInstruction("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]) ctx.get_basic_block().append_instruction(inst) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) From 24f641e29d0982b80e4245a61bd25bd21d25bed0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 9 Oct 2023 20:53:48 +0300 Subject: [PATCH 210/471] fixes assert - advanced storage test pass --- vyper/ir/ir_to_bb_pass.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 6e95cac76c..77815e9526 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -473,12 +473,16 @@ def _convert_ir_basicblock( else: last_ir = None ret_var = ir.args[1] + deleted = None if ret_var.is_literal and symbols.get(f"&{ret_var.value}", None) is not None: + deleted = symbols[f"&{ret_var.value}"] del symbols[f"&{ret_var.value}"] for arg in ir.args[2:]: last_ir = _convert_ir_basicblock( ctx, arg, symbols, variables, allocated_variables ) + if deleted is not None: + symbols[f"&{ret_var.value}"] = deleted ret_ir = _convert_ir_basicblock( ctx, ret_var, symbols, variables, allocated_variables @@ -558,7 +562,7 @@ def _convert_ir_basicblock( 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_0, arg_1]) + inst = IRInstruction("revert", [arg_1, arg_0]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "dload": @@ -677,7 +681,8 @@ def _convert_ir_basicblock( if sym is None: inst = IRInstruction("mstore", [arg_1, sym_ir]) ctx.get_basic_block().append_instruction(inst) - symbols[f"&{sym_ir.value}"] = arg_1 + if not arg_1.is_literal: + symbols[f"&{sym_ir.value}"] = arg_1 return None if sym_ir.is_literal: From 1c9093a44ced8e5f9c91a30a0540b2c7ee7297bf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 9 Oct 2023 21:12:42 +0300 Subject: [PATCH 211/471] sha3 no reverse --- vyper/ir/ir_to_bb_pass.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 77815e9526..2a3fea87f1 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -241,7 +241,7 @@ def _convert_ir_basicblock( if ir.value in BINARY_IR_INSTRUCTIONS: return _convert_binary_op( - ctx, ir, symbols, variables, allocated_variables, ir.value in ["sha3", "sha3_64"] + ctx, ir, symbols, variables, allocated_variables, ir.value in ["sha3_64"] ) elif ir.value in MAPPED_IR_INSTRUCTIONS.keys(): From 9d89eabeda4700f27082857c685b0c3cba01459e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 9 Oct 2023 21:35:18 +0300 Subject: [PATCH 212/471] bulk instructions add --- vyper/codegen/dfg.py | 11 ++++++- vyper/ir/ir_to_bb_pass.py | 68 +++++++++++++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index a83033411a..087f7e8f90 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -12,11 +12,18 @@ ONE_TO_ONE_INSTRUCTIONS = [ "revert", + "coinbase", "calldatasize", "calldatacopy", "calldataload", - # "codecopy", "gas", + "gasprice", + "gaslimit", + "address", + "origin", + "number", + "extcodesize", + "extcodehash", "returndatasize", "returndatacopy", "callvalue", @@ -336,6 +343,8 @@ def _generate_evm_for_instruction_r( assembly.append("POP") elif opcode == "call": assembly.append("CALL") + elif opcode == "staticcall": + assembly.append("STATICCALL") elif opcode == "ret": # assert len(inst.operands) == 2, "ret instruction takes two operands" assembly.append("JUMP") diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 2a3fea87f1..74edf7ad1f 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -251,10 +251,27 @@ def _convert_ir_basicblock( ir.value = org_value return ctx.append_instruction("iszero", [new_var]) - elif ir.value in ["iszero", "ceil32", "calldataload"]: + elif ir.value in ["iszero", "ceil32", "calldataload", "extcodesize", "extcodehash", "balance"]: return _convert_ir_simple_node(ctx, ir, symbols, variables, allocated_variables) - elif ir.value in ["timestamp", "caller", "selfbalance", "calldatasize", "callvalue"]: + elif ir.value in [ + "chainid", + "basefee", + "timestamp", + "caller", + "selfbalance", + "calldatasize", + "callvalue", + "address", + "origin", + "codesize", + "gas", + "gasprice", + "gaslimit", + "returndatasize", + "coinbase", + "number", + ]: return ctx.append_instruction(ir.value, []) elif ir.value in ["pass", "stop", "return"]: @@ -296,15 +313,31 @@ def _convert_ir_basicblock( return ret elif ir.value in ["staticcall", "call"]: # external call - gas = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - address = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - value = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + idx = 0 + gas = _convert_ir_basicblock(ctx, ir.args[idx], symbols, variables, allocated_variables) + address = _convert_ir_basicblock( + ctx, ir.args[idx + 1], symbols, variables, allocated_variables + ) + + if ir.value == "call": + value = _convert_ir_basicblock( + ctx, ir.args[idx + 2], symbols, variables, allocated_variables + ) + else: + idx -= 1 + argsOffset = _convert_ir_basicblock( - ctx, ir.args[3], symbols, variables, allocated_variables + ctx, ir.args[idx + 3], symbols, variables, allocated_variables + ) + argsSize = _convert_ir_basicblock( + ctx, ir.args[idx + 4], symbols, variables, allocated_variables + ) + retOffset = _convert_ir_basicblock( + ctx, ir.args[idx + 5], symbols, variables, allocated_variables + ) + retSize = _convert_ir_basicblock( + ctx, ir.args[idx + 6], symbols, variables, allocated_variables ) - argsSize = _convert_ir_basicblock(ctx, ir.args[4], symbols, variables, allocated_variables) - retOffset = _convert_ir_basicblock(ctx, ir.args[5], symbols, variables, allocated_variables) - retSize = _convert_ir_basicblock(ctx, ir.args[6], symbols, variables, allocated_variables) if argsOffset.is_literal: addr = argsOffset.value - 32 + 4 if argsOffset.value > 0 else 0 @@ -319,11 +352,18 @@ def _convert_ir_basicblock( retVar = ctx.get_next_variable(IRVariable.MemType.MEMORY, retOffset.value) symbols[f"&{retOffset.value}"] = retVar - inst = IRInstruction( - ir.value, - [gas, address, value, argsOffsetVar, argsSize, retOffset, retSize][::-1], - retVar, - ) + if ir.value == "call": + inst = IRInstruction( + ir.value, + [gas, address, value, argsOffsetVar, argsSize, retOffset, retSize][::-1], + retVar, + ) + else: + inst = IRInstruction( + ir.value, + [gas, address, argsOffsetVar, argsSize, retOffset, retSize][::-1], + retVar, + ) ctx.get_basic_block().append_instruction(inst) return retVar elif ir.value == "if": From 4da69573a1f780bd01cc352128cafa196f4e0ca9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 9 Oct 2023 23:44:55 +0300 Subject: [PATCH 213/471] handle if as expression --- vyper/ir/ir_to_bb_pass.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 74edf7ad1f..afd9efbb37 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -377,15 +377,24 @@ def _convert_ir_basicblock( ctx.append_basic_block(else_block) # convert "else" + else_ret_val = None if len(ir.args) == 3: - _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) + else_ret_val = _convert_ir_basicblock( + ctx, ir.args[2], symbols, variables, allocated_variables + ) + if else_ret_val.is_literal: + else_ret_val = ctx.append_instruction("store", [IRLiteral(else_ret_val.value)]) after_else_syms = symbols.copy() # convert "then" then_block = IRBasicBlock(ctx.get_next_label(), ctx) ctx.append_basic_block(then_block) - _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) + then_ret_val = _convert_ir_basicblock( + ctx, ir.args[1], symbols, variables, allocated_variables + ) + if then_ret_val is not None and then_ret_val.is_literal: + then_ret_val = ctx.append_instruction("store", [IRLiteral(then_ret_val.value)]) inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) current_bb.append_instruction(inst) @@ -397,6 +406,17 @@ def _convert_ir_basicblock( bb = IRBasicBlock(exit_label, ctx) bb = ctx.append_basic_block(bb) + 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( + "select", + [then_block.label, then_ret_val, else_block.label, else_ret_val], + if_ret, + ) + ) + for sym, val in _get_symbols_common(after_then_syms, after_else_syms).items(): ret = ctx.get_next_variable() symbols[sym] = ret @@ -412,6 +432,8 @@ def _convert_ir_basicblock( exit_inst = IRInstruction("jmp", [bb.label]) then_block.append_instruction(exit_inst) + return if_ret + elif ir.value == "with": ret = _convert_ir_basicblock( ctx, ir.args[1], symbols, variables, allocated_variables From 525d1d6749298d0da7875cb8b9d86d41fd8cc497 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 10 Oct 2023 09:53:11 +0300 Subject: [PATCH 214/471] remove comment and small bugfix --- vyper/codegen/dfg.py | 5 ----- vyper/ir/ir_to_bb_pass.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 087f7e8f90..b336b1e712 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -427,11 +427,6 @@ def _emit_input_operands( continue assembly = _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: - # FIXME: MEMORY REFACTOR - # if op.address_access: - # if inst.opcode != "codecopy": - # assembly.extend([*PUSH(op.addr)]) - # else: assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index afd9efbb37..4b633a75eb 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -382,7 +382,7 @@ def _convert_ir_basicblock( else_ret_val = _convert_ir_basicblock( ctx, ir.args[2], symbols, variables, allocated_variables ) - if else_ret_val.is_literal: + if else_ret_val is not None and else_ret_val.is_literal: else_ret_val = ctx.append_instruction("store", [IRLiteral(else_ret_val.value)]) after_else_syms = symbols.copy() From bd4f065a30f47f5e612e353f5b31701c293fd3cf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 10 Oct 2023 10:43:10 +0300 Subject: [PATCH 215/471] return call success --- vyper/ir/ir_to_bb_pass.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 4b633a75eb..0be7dab731 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -353,19 +353,13 @@ def _convert_ir_basicblock( symbols[f"&{retOffset.value}"] = retVar if ir.value == "call": - inst = IRInstruction( - ir.value, - [gas, address, value, argsOffsetVar, argsSize, retOffset, retSize][::-1], - retVar, + return ctx.append_instruction( + ir.value, [gas, address, value, argsOffsetVar, argsSize, retOffset, retSize][::-1] ) else: - inst = IRInstruction( - ir.value, - [gas, address, argsOffsetVar, argsSize, retOffset, retSize][::-1], - retVar, + return ctx.append_instruction( + ir.value, [gas, address, argsOffsetVar, argsSize, retOffset, retSize][::-1] ) - ctx.get_basic_block().append_instruction(inst) - return retVar elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() From 91e646fa90e531e9d17244527ff2c63d41114dd0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 10 Oct 2023 16:33:51 +0300 Subject: [PATCH 216/471] remove assert from terminal instructions --- vyper/codegen/ir_basicblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index c023f1a15a..645f113287 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -3,7 +3,7 @@ from vyper.utils import OrderedSet -TERMINAL_IR_INSTRUCTIONS = ["ret", "revert", "assert"] +TERMINAL_IR_INSTRUCTIONS = ["ret", "revert"] TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"] if TYPE_CHECKING: From bff88424291c73eda4a8ad8f02121f31a52a468a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 10 Oct 2023 17:42:57 +0300 Subject: [PATCH 217/471] loop control bringing up to date [wip] --- vyper/codegen/dfg.py | 3 ++- vyper/codegen/ir_basicblock.py | 3 ++- vyper/ir/ir_to_bb_pass.py | 15 ++++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index b336b1e712..a00ef8875b 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -288,7 +288,8 @@ def _generate_evm_for_instruction_r( # Step 3: Reorder stack if opcode in ["jnz", "jmp"] and stack_map.get_height() >= 2: _, b = next(enumerate(inst.parent.out_set)) - target_stack = b.get_liveness() + t_liveness = b.in_vars_for(inst.parent) + target_stack = [inst.parent.phi_vars.get(v.value, v) for v in t_liveness] _stack_reorder(assembly, stack_map, target_stack, inst.parent.phi_vars) _stack_duplications(assembly, stack_map, operands) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 645f113287..f4176b743b 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -170,7 +170,8 @@ def __repr__(self) -> str: s = "" if self.ret: s += f"{self.ret} = " - s += f"{self.opcode} " + opcode = f"{self.opcode} " if self.opcode != "store" else "" + s += opcode operands = ", ".join( [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in self.operands[::-1]] ) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 0be7dab731..0088a1f496 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -788,6 +788,13 @@ def _convert_ir_basicblock( 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) + symbols[sym.value] = counter_var + inst = IRInstruction("jmp", [cond_block.label]) + ctx.get_basic_block().append_instruction(inst) + symbols[sym.value] = ret cond_block.append_instruction( IRInstruction( @@ -797,12 +804,6 @@ def _convert_ir_basicblock( ) ) - inst = IRInstruction("store", [start], counter_var) - ctx.get_basic_block().append_instruction(inst) - symbols[sym.value] = counter_var - inst = IRInstruction("jmp", [cond_block.label]) - ctx.get_basic_block().append_instruction(inst) - cont_ret = ctx.get_next_variable() inst = IRInstruction("xor", [ret, end], cont_ret) cond_block.append_instruction(inst) @@ -822,7 +823,7 @@ def _convert_ir_basicblock( ctx.append_basic_block(jump_up_block) increment_block.append_instruction( - IRInstruction("add", [counter_var, IRLiteral(1)], counter_inc_var) + IRInstruction("add", [ret, IRLiteral(1)], counter_inc_var) ) increment_block.append_instruction(IRInstruction("jmp", [cond_block.label])) ctx.append_basic_block(increment_block) From f3e86f0919b5221b7df8919ebb6e1cb7db617073 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 11 Oct 2023 11:13:06 +0300 Subject: [PATCH 218/471] more --- vyper/ir/ir_to_bb_pass.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 0088a1f496..d2c599a257 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -723,10 +723,11 @@ def _convert_ir_basicblock( return ctx.append_instruction("mstore", [arg_1, ptr_var], False) else: if sym_ir.is_literal: - symbols[f"&{sym_ir.value}"] = arg_1 + new_var = ctx.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] = arg_1 - return arg_1 + allocated_variables[var.name] = new_var + return new_var else: if sym_ir.is_literal is False: inst = IRInstruction("mstore", [arg_1, sym_ir]) @@ -812,7 +813,9 @@ def _convert_ir_basicblock( ctx.append_basic_block(body_block) old_targets = _break_target, _continue_target _break_target, _continue_target = exit_block, increment_block + _convert_ir_basicblock(ctx, body, symbols, variables, allocated_variables) + _break_target, _continue_target = old_targets body_end = ctx.get_basic_block() if body_end.is_terminal() is False: From 854dfd45c69da690a0c18e450a9bafa34dcabc40 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 16 Oct 2023 11:14:45 +0300 Subject: [PATCH 219/471] if fixes --- vyper/ir/ir_to_bb_pass.py | 49 ++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index d2c599a257..4466c87a88 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -372,13 +372,14 @@ def _convert_ir_basicblock( # convert "else" else_ret_val = None + else_syms = symbols.copy() if len(ir.args) == 3: else_ret_val = _convert_ir_basicblock( - ctx, ir.args[2], symbols, variables, allocated_variables + ctx, ir.args[2], else_syms, variables, allocated_variables.copy() ) if else_ret_val is not None and else_ret_val.is_literal: else_ret_val = ctx.append_instruction("store", [IRLiteral(else_ret_val.value)]) - after_else_syms = symbols.copy() + after_else_syms = else_syms.copy() # convert "then" then_block = IRBasicBlock(ctx.get_next_label(), ctx) @@ -413,7 +414,12 @@ def _convert_ir_basicblock( for sym, val in _get_symbols_common(after_then_syms, after_else_syms).items(): ret = ctx.get_next_variable() + old_var = symbols.get(sym, None) symbols[sym] = ret + if old_var is not None: + for idx, var in allocated_variables.items(): + if var.value == old_var.value: + allocated_variables[idx] = ret bb.append_instruction( IRInstruction("select", [then_block.label, val[0], else_block.label, val[1]], ret) ) @@ -725,8 +731,8 @@ def _convert_ir_basicblock( if sym_ir.is_literal: new_var = ctx.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 + # if allocated_variables.get(var.name, None) is None: + allocated_variables[var.name] = new_var return new_var else: if sym_ir.is_literal is False: @@ -773,6 +779,13 @@ def _convert_ir_basicblock( # 5) increment block # 6) exit block # TODO: Add the extra bounds check after clarify + def emit_body_block(): + global _break_target, _continue_target + old_targets = _break_target, _continue_target + _break_target, _continue_target = exit_block, increment_block + _convert_ir_basicblock(ctx, body, symbols, variables, allocated_variables) + _break_target, _continue_target = old_targets + sym = ir.args[0] start = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) end = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) @@ -810,13 +823,33 @@ def _convert_ir_basicblock( cond_block.append_instruction(inst) ctx.append_basic_block(cond_block) + # Do a dry run to get the symbols needing phi nodes + start_syms = symbols.copy() ctx.append_basic_block(body_block) - old_targets = _break_target, _continue_target - _break_target, _continue_target = exit_block, increment_block + old_counters = ctx.last_variable, ctx.last_label + emit_body_block() + # ctx.last_variable, ctx.last_label = old_counters + end_syms = symbols.copy() + diff_syms = _get_symbols_common(start_syms, end_syms) + + replacements = {} + for sym, val in diff_syms.items(): + new_var = ctx.get_next_variable() + symbols[sym] = new_var + replacements[val[0]] = new_var + replacements[val[1]] = new_var + cond_block.insert_instruction( + IRInstruction( + "select", [entry_block.label, val[0], increment_block.label, val[1]], new_var + ), + 1, + ) + + body_block.update_operands(replacements) - _convert_ir_basicblock(ctx, body, symbols, variables, allocated_variables) + # body_block.clear_instructions() + # emit_body_block() - _break_target, _continue_target = old_targets body_end = ctx.get_basic_block() if body_end.is_terminal() is False: body_end.append_instruction(IRInstruction("jmp", [jump_up_block.label])) From 0b51c789f11f7b3c6b0a9465ff70f5a63d200226 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 16 Oct 2023 14:48:22 +0300 Subject: [PATCH 220/471] update operands --- vyper/codegen/ir_basicblock.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index f4176b743b..c49ea7cf85 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -166,6 +166,14 @@ def get_input_operands(self) -> list[IRValueBase]: def get_output_operands(self) -> list[IRValueBase]: return [self.ret] if self.ret else [] + def update_operands(self, replacements: dict) -> None: + """ + Update operands with replacements. + """ + for i, operand in enumerate(self.operands): + if operand in replacements.keys(): + self.operands[i] = replacements[operand] + def __repr__(self) -> str: s = "" if self.ret: @@ -274,6 +282,21 @@ def append_instruction(self, instruction: IRInstruction) -> None: instruction.parent = self self.instructions.append(instruction) + def insert_instruction(self, instruction: IRInstruction, index: int) -> None: + assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" + instruction.parent = self + self.instructions.insert(index, instruction) + + def clear_instructions(self) -> None: + self.instructions = [] + + def update_operands(self, replacements: dict) -> None: + """ + Update operands with replacements. + """ + for instruction in self.instructions: + instruction.update_operands(replacements) + def is_terminal(self) -> bool: """ Check if the basic block is terminal, i.e. the last instruction is a terminator. From c2ad789fbc6b730cc5eb892b2faad0d60e37472d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 16 Oct 2023 16:25:13 +0300 Subject: [PATCH 221/471] phi mappings --- vyper/codegen/ir_basicblock.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index c49ea7cf85..5609ea14f7 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -256,6 +256,19 @@ def union_out(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: def remove_out(self, bb: "IRBasicBlock") -> None: self.out_set.remove(bb) + def get_phi_mappings(self) -> dict[str, IRVariable]: + """ + Get phi mappings for basic block. + """ + phi_mappings = {} + + for inst in self.instructions: + if inst.opcode == "select": + phi_mappings[inst.operands[1].value] = inst.ret + phi_mappings[inst.operands[3].value] = inst.ret + + return phi_mappings + def in_vars_for(self, bb: "IRBasicBlock" = None) -> set[IRVariable]: liveness = self.instructions[0].liveness.copy() From 446082c850e33295d80da561cc667ddbfd067edf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 16 Oct 2023 16:25:29 +0300 Subject: [PATCH 222/471] invert logic --- vyper/ir/ir_to_bb_pass.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 4466c87a88..a29016dcae 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -818,9 +818,11 @@ def emit_body_block(): ) ) + xor_ret = ctx.get_next_variable() cont_ret = ctx.get_next_variable() - inst = IRInstruction("xor", [ret, end], cont_ret) + inst = IRInstruction("xor", [ret, end], xor_ret) cond_block.append_instruction(inst) + cond_block.append_instruction(IRInstruction("iszero", [xor_ret], cont_ret)) ctx.append_basic_block(cond_block) # Do a dry run to get the symbols needing phi nodes From b78454f0ff06b4057052c69595cf049584532e64 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 16 Oct 2023 16:26:30 +0300 Subject: [PATCH 223/471] stack edge cases fix --- vyper/codegen/dfg.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index a00ef8875b..609a35ec50 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -275,8 +275,9 @@ def _generate_evm_for_instruction_r( assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" to_be_replaced = stack_map.peek(depth) if to_be_replaced.use_count > 1: - stack_map.dup(assembly, depth) to_be_replaced.use_count -= 1 + if to_be_replaced.use_count > 1: + stack_map.dup(assembly, depth) stack_map.poke(0, ret) else: stack_map.poke(depth, ret) @@ -286,11 +287,14 @@ def _generate_evm_for_instruction_r( _emit_input_operands(ctx, assembly, inst, operands, stack_map) # Step 3: Reorder stack - if opcode in ["jnz", "jmp"] and stack_map.get_height() >= 2: + if opcode in ["jnz", "jmp"]: # and stack_map.get_height() >= 2: _, b = next(enumerate(inst.parent.out_set)) t_liveness = b.in_vars_for(inst.parent) - target_stack = [inst.parent.phi_vars.get(v.value, v) for v in t_liveness] - _stack_reorder(assembly, stack_map, target_stack, inst.parent.phi_vars) + target_stack = OrderedSet(t_liveness) # [b.phi_vars.get(v.value, v) for v in t_liveness] + current_stack = OrderedSet(stack_map.stack_map) + need_pop = current_stack.difference(target_stack) + phi_mappings = inst.parent.get_phi_mappings() + _stack_reorder(assembly, stack_map, target_stack, phi_mappings) _stack_duplications(assembly, stack_map, operands) _stack_reorder(assembly, stack_map, operands) From e177c23d4b94a87198ce10b6c3cff55aff34197c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 17 Oct 2023 10:45:13 +0300 Subject: [PATCH 224/471] balance branches --- vyper/codegen/dfg.py | 13 ++++++++++++- vyper/codegen/ir_basicblock.py | 8 ++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 609a35ec50..5502676cc3 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -292,7 +292,18 @@ def _generate_evm_for_instruction_r( t_liveness = b.in_vars_for(inst.parent) target_stack = OrderedSet(t_liveness) # [b.phi_vars.get(v.value, v) for v in t_liveness] current_stack = OrderedSet(stack_map.stack_map) - need_pop = current_stack.difference(target_stack) + if opcode == "jmp": + need_pop = current_stack.difference(target_stack) + for op in need_pop: + if op.use_count > 0: + continue + depth = stack_map.get_depth_in(op) + if depth is StackMap.NOT_IN_STACK: + continue + if depth != 0: + stack_map.swap(assembly, depth) + stack_map.pop() + assembly.append("POP") phi_mappings = inst.parent.get_phi_mappings() _stack_reorder(assembly, stack_map, target_stack, phi_mappings) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 5609ea14f7..59928c4103 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -349,6 +349,14 @@ def get_liveness(self) -> set[IRVariable]: """ return self.instructions[0].liveness + def get_use_count(self, var: IRVariable) -> int: + """ + Get use count of variable in basic block. + """ + if var.value not in self.use_counts.keys(): + self.use_counts[var.value] = 0 if var.value not in self.instructions[-1].liveness else 1 + return self.use_counts[var.value] + def __repr__(self) -> str: s = ( f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}" From 74f597bc2db7d0d9c62cbe57ca49727b3102ec4f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 17 Oct 2023 11:22:41 +0300 Subject: [PATCH 225/471] get last operand usage --- vyper/codegen/ir_basicblock.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 59928c4103..57b15b9f85 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -357,6 +357,25 @@ def get_use_count(self, var: IRVariable) -> int: self.use_counts[var.value] = 0 if var.value not in self.instructions[-1].liveness else 1 return self.use_counts[var.value] + def get_last_used_operands(self, _inst: IRInstruction) -> OrderedSet[IRVariable]: + """ + Get last used operands of instruction. + """ + for i, inst in enumerate(self.instructions[:-1]): + if inst == _inst: + next_liveness = ( + self.instructions[i + 1].liveness + if i + 1 < len(self.instructions) + else OrderedSet() + ) + last_used = inst.liveness.difference(next_liveness) + return last_used + # Last instruction looksup into branch out basic blocks + if self.instructions[-1] == _inst: + last_used = _inst.liveness.difference(self.out_vars) + return last_used + return OrderedSet() + def __repr__(self) -> str: s = ( f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}" From c4a83fcd6e5c6e3e57bde50a10cf7b83f50c44d4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 17 Oct 2023 17:02:23 +0300 Subject: [PATCH 226/471] recursive dependency liveness --- vyper/codegen/dfg.py | 88 +++++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 21 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 5502676cc3..b5c6ddba3c 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -158,13 +158,45 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: return asm -def _stack_duplications(assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase]) -> None: +def __stack_duplications( + assembly: list, + dep_liveness: OrderedSet[IRValueBase], + inst: IRInstruction, + stack_map: StackMap, + stack_ops: list[IRValueBase], +) -> None: + last_used = inst.parent.get_last_used_operands(inst) for op in stack_ops: + if op.is_literal or isinstance(op, IRLabel): + continue assert op.use_count >= 0, "Operand used up" depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" if op.use_count > 1: - # Operand need duplication + print(inst, op) + # Operand needs duplication + stack_map.dup(assembly, depth) + op.use_count -= 1 + + +def _stack_duplications( + assembly: list, + dep_liveness: OrderedSet[IRValueBase], + inst: IRInstruction, + stack_map: StackMap, + stack_ops: list[IRValueBase], +) -> None: + last_used2 = inst.parent.get_last_used_operands(inst) + last_used = inst.liveness.difference(dep_liveness) + for op in stack_ops: + if op.is_literal or isinstance(op, IRLabel): + continue + assert op.use_count >= 0, "Operand used up" + depth = stack_map.get_depth_in(op) + assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" + if op not in last_used: + print(inst, op) + # Operand needs duplication stack_map.dup(assembly, depth) op.use_count -= 1 @@ -223,8 +255,13 @@ def _generate_evm_for_basicblock_r( if inst.volatile: fen += 1 - for inst in basicblock.instructions: - asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) + for idx, inst in enumerate(basicblock.instructions): + dep_liveness = ( + basicblock.instructions[idx + 1].liveness + if idx + 1 < len(basicblock.instructions) + else OrderedSet() + ) + asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map, dep_liveness) for bb in basicblock.out_set: _generate_evm_for_basicblock_r(ctx, asm, bb, stack_map.copy()) @@ -237,7 +274,11 @@ def _generate_evm_for_basicblock_r( def _generate_evm_for_instruction_r( - ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap + ctx: IRFunction, + assembly: list, + inst: IRInstruction, + stack_map: StackMap, + dep_inst: OrderedSet[IRValueBase], ) -> list[str]: global label_counter @@ -247,7 +288,9 @@ def _generate_evm_for_instruction_r( continue if target.fen != inst.fen: continue - assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) + assembly = _generate_evm_for_instruction_r( + ctx, assembly, target, stack_map, inst.liveness + ) if inst in visited_instructions: return assembly @@ -284,7 +327,7 @@ def _generate_evm_for_instruction_r( return assembly # Step 2: Emit instructions input operands - _emit_input_operands(ctx, assembly, inst, operands, stack_map) + _emit_input_operands(ctx, assembly, inst, operands, stack_map, dep_inst) # Step 3: Reorder stack if opcode in ["jnz", "jmp"]: # and stack_map.get_height() >= 2: @@ -292,22 +335,22 @@ def _generate_evm_for_instruction_r( t_liveness = b.in_vars_for(inst.parent) target_stack = OrderedSet(t_liveness) # [b.phi_vars.get(v.value, v) for v in t_liveness] current_stack = OrderedSet(stack_map.stack_map) - if opcode == "jmp": - need_pop = current_stack.difference(target_stack) - for op in need_pop: - if op.use_count > 0: - continue - depth = stack_map.get_depth_in(op) - if depth is StackMap.NOT_IN_STACK: - continue - if depth != 0: - stack_map.swap(assembly, depth) - stack_map.pop() - assembly.append("POP") + # if opcode == "jmp": + # need_pop = current_stack.difference(target_stack) + # for op in need_pop: + # if op.use_count > 0: + # continue + # depth = stack_map.get_depth_in(op) + # if depth is StackMap.NOT_IN_STACK: + # continue + # if depth != 0: + # stack_map.swap(assembly, depth) + # stack_map.pop() + # assembly.append("POP") phi_mappings = inst.parent.get_phi_mappings() _stack_reorder(assembly, stack_map, target_stack, phi_mappings) - _stack_duplications(assembly, stack_map, operands) + _stack_duplications(assembly, dep_inst, inst, stack_map, operands) _stack_reorder(assembly, stack_map, operands) # Step 4: Push instruction's return value to stack @@ -428,6 +471,7 @@ def _emit_input_operands( inst: IRInstruction, ops: list[IRValueBase], stack_map: StackMap, + dep_liveness: OrderedSet[IRValueBase], ) -> None: for op in ops: if isinstance(op, IRLabel): @@ -441,7 +485,9 @@ def _emit_input_operands( assembly.extend([*PUSH(op.value)]) stack_map.push(op) continue - assembly = _generate_evm_for_instruction_r(ctx, assembly, dfg_outputs[op.value], stack_map) + assembly = _generate_evm_for_instruction_r( + ctx, assembly, dfg_outputs[op.value], stack_map, dep_liveness + ) if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") From 88e04497047d937f8fed244e9d946f4e8d3babb2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 17 Oct 2023 17:39:02 +0300 Subject: [PATCH 227/471] forward --- vyper/codegen/dfg.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index b5c6ddba3c..88b4b31a9c 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -226,7 +226,10 @@ def f(x): def _generate_evm_for_basicblock_r( - ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackMap + ctx: IRFunction, + asm: list, + basicblock: IRBasicBlock, + stack_map: StackMap, ): if basicblock in visited_basicblocks: return @@ -256,12 +259,10 @@ def _generate_evm_for_basicblock_r( fen += 1 for idx, inst in enumerate(basicblock.instructions): - dep_liveness = ( - basicblock.instructions[idx + 1].liveness - if idx + 1 < len(basicblock.instructions) - else OrderedSet() + orig_inst = ( + basicblock.instructions[idx + 1] if idx + 1 < len(basicblock.instructions) else None ) - asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map, dep_liveness) + asm = _generate_evm_for_instruction_r(ctx, asm, inst, orig_inst, stack_map) for bb in basicblock.out_set: _generate_evm_for_basicblock_r(ctx, asm, bb, stack_map.copy()) @@ -277,8 +278,8 @@ def _generate_evm_for_instruction_r( ctx: IRFunction, assembly: list, inst: IRInstruction, + origin_inst: IRInstruction, stack_map: StackMap, - dep_inst: OrderedSet[IRValueBase], ) -> list[str]: global label_counter @@ -288,9 +289,7 @@ def _generate_evm_for_instruction_r( continue if target.fen != inst.fen: continue - assembly = _generate_evm_for_instruction_r( - ctx, assembly, target, stack_map, inst.liveness - ) + assembly = _generate_evm_for_instruction_r(ctx, assembly, target, inst, stack_map) if inst in visited_instructions: return assembly @@ -327,7 +326,7 @@ def _generate_evm_for_instruction_r( return assembly # Step 2: Emit instructions input operands - _emit_input_operands(ctx, assembly, inst, operands, stack_map, dep_inst) + _emit_input_operands(ctx, assembly, inst, operands, stack_map) # Step 3: Reorder stack if opcode in ["jnz", "jmp"]: # and stack_map.get_height() >= 2: @@ -350,7 +349,8 @@ def _generate_evm_for_instruction_r( phi_mappings = inst.parent.get_phi_mappings() _stack_reorder(assembly, stack_map, target_stack, phi_mappings) - _stack_duplications(assembly, dep_inst, inst, stack_map, operands) + liveness = origin_inst.liveness if origin_inst else OrderedSet() + _stack_duplications(assembly, liveness, inst, stack_map, operands) _stack_reorder(assembly, stack_map, operands) # Step 4: Push instruction's return value to stack @@ -471,8 +471,7 @@ def _emit_input_operands( inst: IRInstruction, ops: list[IRValueBase], stack_map: StackMap, - dep_liveness: OrderedSet[IRValueBase], -) -> None: +) -> OrderedSet[IRValueBase]: for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -486,10 +485,10 @@ def _emit_input_operands( stack_map.push(op) continue assembly = _generate_evm_for_instruction_r( - ctx, assembly, dfg_outputs[op.value], stack_map, dep_liveness + ctx, assembly, dfg_outputs[op.value], inst, stack_map ) if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") - return assembly + return From 4c5965f567278d391746f002de394881b3b9e8ad Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 17 Oct 2023 18:25:29 +0300 Subject: [PATCH 228/471] get next instruction --- vyper/codegen/ir_basicblock.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 57b15b9f85..2a3a85446f 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -376,6 +376,15 @@ def get_last_used_operands(self, _inst: IRInstruction) -> OrderedSet[IRVariable] return last_used return OrderedSet() + def get_next_instruction(self, inst: IRInstruction) -> IRInstruction: + """ + Get next instruction after inst. + """ + for i, instruction in enumerate(self.instructions[:-1]): + if instruction == inst: + return self.instructions[i + 1] + return None + def __repr__(self) -> str: s = ( f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}" From fd5f49e9f6079bde1cfce05adaaad3e63d6026a8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 17 Oct 2023 18:25:39 +0300 Subject: [PATCH 229/471] forward --- vyper/codegen/dfg.py | 46 ++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 88b4b31a9c..4e982e2000 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -158,7 +158,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: return asm -def __stack_duplications( +def _stack_duplications( assembly: list, dep_liveness: OrderedSet[IRValueBase], inst: IRInstruction, @@ -173,13 +173,13 @@ def __stack_duplications( depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" if op.use_count > 1: - print(inst, op) + # print(inst, op) # Operand needs duplication stack_map.dup(assembly, depth) op.use_count -= 1 -def _stack_duplications( +def __stack_duplications( assembly: list, dep_liveness: OrderedSet[IRValueBase], inst: IRInstruction, @@ -195,7 +195,7 @@ def _stack_duplications( depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" if op not in last_used: - print(inst, op) + # print(inst, op) # Operand needs duplication stack_map.dup(assembly, depth) op.use_count -= 1 @@ -259,10 +259,10 @@ def _generate_evm_for_basicblock_r( fen += 1 for idx, inst in enumerate(basicblock.instructions): - orig_inst = ( - basicblock.instructions[idx + 1] if idx + 1 < len(basicblock.instructions) else None - ) - asm = _generate_evm_for_instruction_r(ctx, asm, inst, orig_inst, stack_map) + # orig_inst = ( + # basicblock.instructions[idx + 1] if idx + 1 < len(basicblock.instructions) else None + # ) + asm, _ = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) for bb in basicblock.out_set: _generate_evm_for_basicblock_r(ctx, asm, bb, stack_map.copy()) @@ -278,21 +278,33 @@ def _generate_evm_for_instruction_r( ctx: IRFunction, assembly: list, inst: IRInstruction, - origin_inst: IRInstruction, stack_map: StackMap, -) -> list[str]: +) -> (list[str], IRInstruction): global label_counter + origin_inst = None for op in inst.get_output_operands(): for target in dfg_inputs.get(op.value, []): if target.parent != inst.parent: continue if target.fen != inst.fen: continue - assembly = _generate_evm_for_instruction_r(ctx, assembly, target, inst, stack_map) + assembly, origin_inst = _generate_evm_for_instruction_r( + ctx, assembly, target, stack_map + ) + + if origin_inst is None: + if inst.opcode in ["jmp", "jnz"]: + origin_inst = OrderedSet() + for bb in inst.parent.out_set: + l = bb.in_vars_for(inst.parent) + origin_inst |= l + else: + next_inst = inst.parent.get_next_instruction(inst) + origin_inst = next_inst.liveness if next_inst else OrderedSet() if inst in visited_instructions: - return assembly + return assembly, inst.liveness visited_instructions.add(inst) opcode = inst.opcode @@ -323,7 +335,7 @@ def _generate_evm_for_instruction_r( stack_map.poke(0, ret) else: stack_map.poke(depth, ret) - return assembly + return assembly, inst # Step 2: Emit instructions input operands _emit_input_operands(ctx, assembly, inst, operands, stack_map) @@ -349,7 +361,7 @@ def _generate_evm_for_instruction_r( phi_mappings = inst.parent.get_phi_mappings() _stack_reorder(assembly, stack_map, target_stack, phi_mappings) - liveness = origin_inst.liveness if origin_inst else OrderedSet() + liveness = origin_inst if origin_inst else OrderedSet() _stack_duplications(assembly, liveness, inst, stack_map, operands) _stack_reorder(assembly, stack_map, operands) @@ -462,7 +474,7 @@ def _generate_evm_for_instruction_r( assembly.extend([*PUSH(inst.ret.mem_addr)]) # assembly.append("MSTORE") - return assembly + return assembly, inst.liveness def _emit_input_operands( @@ -484,8 +496,8 @@ def _emit_input_operands( assembly.extend([*PUSH(op.value)]) stack_map.push(op) continue - assembly = _generate_evm_for_instruction_r( - ctx, assembly, dfg_outputs[op.value], inst, stack_map + assembly, origin_inst = _generate_evm_for_instruction_r( + ctx, assembly, dfg_outputs[op.value], stack_map ) if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) From 8c4ce2bf227478db75b6480d3dcc2e4d82732ff5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 17 Oct 2023 18:56:26 +0300 Subject: [PATCH 230/471] ordered set constructor not sure if needed will check again --- vyper/compiler/utils.py | 3 +++ vyper/utils.py | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 8f53dbd103..32f7dfe9bd 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -2,6 +2,7 @@ from vyper.codegen.ir_basicblock import IRValueBase from vyper.semantics.types.function import ContractFunctionT +from vyper.utils import OrderedSet def build_gas_estimates(func_ts: Dict[str, ContractFunctionT]) -> dict: @@ -51,9 +52,11 @@ def _expand_row(row): class StackMap: NOT_IN_STACK = object() stack_map: list[IRValueBase] + dependant_liveness: OrderedSet[IRValueBase] def __init__(self): self.stack_map = [] + self.dependant_liveness = OrderedSet() def copy(self): new = StackMap() diff --git a/vyper/utils.py b/vyper/utils.py index 0e6573e250..0870f8ef31 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -20,6 +20,12 @@ class OrderedSet(dict): functionality as needed. """ + def __init__(self, iterable=None): + super().__init__() + if iterable is not None: + for item in iterable: + self.add(item) + def __repr__(self): keys = ", ".join(repr(k) for k in self.keys()) return f"{{{keys}}}" @@ -33,7 +39,8 @@ def remove(self, item): def difference(self, other): ret = self.copy() for k in other.keys(): - ret.pop(k, None) + if k in ret.keys(): + ret.remove(k) return ret def union(self, other): From de2e2b521dab034f804c556a0743ea8d7d70ad55 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 17 Oct 2023 21:21:55 +0300 Subject: [PATCH 231/471] passes refactor --- vyper/codegen/dfg.py | 50 +++++++++++++++------------- vyper/codegen/ir_basicblock.py | 10 ++++++ vyper/codegen/ir_function.py | 14 ++++++++ vyper/codegen/ir_pass_dft.py | 60 ++++++++++++++++++++++++++++++++++ vyper/ir/ir_to_bb_pass.py | 8 +++++ 5 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 vyper/codegen/ir_pass_dft.py diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 4e982e2000..3d8b2343d6 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -73,15 +73,18 @@ def __init__(self, value: IRInstruction | IRValueBase): self.successors = [] -dfg_inputs = {str: [IRInstruction]} -dfg_outputs = {str: IRInstruction} - - def convert_ir_to_dfg(ctx: IRFunction) -> None: - global dfg_inputs - global dfg_outputs - dfg_inputs = {} - dfg_outputs = {} + # Reset DFG + ctx.dfg_inputs = {} + ctx.dfg_outputs = {} + for bb in ctx.basic_blocks: + for inst in bb.instructions: + operands = inst.get_input_operands() + operands.extend(inst.get_output_operands()) + for op in operands: + op.use_count = 0 + + # Build DFG for bb in ctx.basic_blocks: for inst in bb.instructions: operands = inst.get_input_operands() @@ -89,12 +92,14 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: for op in operands: op.use_count += 1 - dfg_inputs[op.value] = ( - [inst] if dfg_inputs.get(op.value) is None else dfg_inputs[op.value] + [inst] + ctx.dfg_inputs[op.value] = ( + [inst] + if ctx.dfg_inputs.get(op.value) is None + else ctx.dfg_inputs[op.value] + [inst] ) for op in res: - dfg_outputs[op.value] = inst + ctx.dfg_outputs[op.value] = inst def compute_phi_vars(ctx: IRFunction) -> None: @@ -124,7 +129,6 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: visited_instructions = set() visited_basicblocks = set() - convert_ir_to_dfg(ctx) compute_phi_vars(ctx) _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackMap()) @@ -283,15 +287,15 @@ def _generate_evm_for_instruction_r( global label_counter origin_inst = None - for op in inst.get_output_operands(): - for target in dfg_inputs.get(op.value, []): - if target.parent != inst.parent: - continue - if target.fen != inst.fen: - continue - assembly, origin_inst = _generate_evm_for_instruction_r( - ctx, assembly, target, stack_map - ) + # for op in inst.get_output_operands(): + # for target in ctx.dfg_inputs.get(op.value, []): + # if target.parent != inst.parent: + # continue + # if target.fen != inst.fen: + # continue + # assembly, origin_inst = _generate_evm_for_instruction_r( + # ctx, assembly, target, stack_map + # ) if origin_inst is None: if inst.opcode in ["jmp", "jnz"]: @@ -337,7 +341,7 @@ def _generate_evm_for_instruction_r( stack_map.poke(depth, ret) return assembly, inst - # Step 2: Emit instructions input operands + # Step 2: Emit instruction's input operands _emit_input_operands(ctx, assembly, inst, operands, stack_map) # Step 3: Reorder stack @@ -497,7 +501,7 @@ def _emit_input_operands( stack_map.push(op) continue assembly, origin_inst = _generate_evm_for_instruction_r( - ctx, assembly, dfg_outputs[op.value], stack_map + ctx, assembly, ctx.dfg_outputs[op.value], stack_map ) if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 2a3a85446f..5fefd7a1a3 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -136,6 +136,7 @@ def __init__( "codecopy", "dloadbytes", "dload", + "return", ] self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] self.ret = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None @@ -385,6 +386,15 @@ def get_next_instruction(self, inst: IRInstruction) -> IRInstruction: return self.instructions[i + 1] return None + def copy(self): + bb = IRBasicBlock(self.label, self.parent) + bb.instructions = self.instructions.copy() + bb.in_set = self.in_set.copy() + bb.out_set = self.out_set.copy() + bb.out_vars = self.out_vars.copy() + bb.phi_vars = self.phi_vars.copy() + return bb + def __repr__(self) -> str: s = ( f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}" diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 33be497e50..f306d64a1c 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -29,6 +29,8 @@ class IRFunction(IRFunctionBase): basic_blocks: list["IRBasicBlock"] data_segment: list["IRInstruction"] + dfg_inputs = {str: [IRInstruction]} + dfg_outputs = {str: IRInstruction} last_label: int last_variable: int @@ -38,6 +40,8 @@ def __init__(self, name: IRLabel) -> None: self.data_segment = [] self.last_label = 0 self.last_variable = 0 + self.dfg_inputs = {} + self.dfg_outputs = {} self.append_basic_block(IRBasicBlock(name, self)) @@ -124,6 +128,16 @@ def append_data(self, opcode: str, args: list[IRValueBase]): """ self.data_segment.append(IRInstruction(opcode, args)) + def copy(self): + new = IRFunction(self.name) + new.basic_blocks = self.basic_blocks.copy() + new.data_segment = self.data_segment.copy() + new.last_label = self.last_label + new.last_variable = self.last_variable + new.dfg_inputs = self.dfg_inputs.copy() + new.dfg_outputs = self.dfg_outputs.copy() + return new + def __repr__(self) -> str: str = f"IRFunction: {self.name}\n" for bb in self.basic_blocks: diff --git a/vyper/codegen/ir_pass_dft.py b/vyper/codegen/ir_pass_dft.py new file mode 100644 index 0000000000..66c618ad2d --- /dev/null +++ b/vyper/codegen/ir_pass_dft.py @@ -0,0 +1,60 @@ +from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction +from vyper.codegen.ir_function import IRFunction +from vyper.utils import OrderedSet + +visited_instructions = OrderedSet() + + +def __process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction): + for op in inst.get_output_operands(): + for target in ctx.dfg_inputs.get(op.value, []): + if target.parent.label != bb.label: + continue + if target.volatile: + continue + _process_instruction(ctx, bb, target) + + if inst in visited_instructions: + return + visited_instructions.add(inst) + + bb.append_instruction(inst) + + +def _process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction): + for op in inst.get_input_operands(): + target = ctx.dfg_outputs.get(op.value, None) + if target.parent.label != bb.label: + continue + if target.volatile: + continue + _process_instruction(ctx, bb, target) + + if inst in visited_instructions: + return + visited_instructions.add(inst) + + bb.append_instruction(inst) + + +def _process_basic_block(ctx: IRFunction, _bb: IRBasicBlock): + bb = _bb.copy() + bb.parent = ctx + ctx.append_basic_block(bb) + bb.instructions = [] + for inst in _bb.instructions: + _process_instruction(ctx, bb, inst) + + +def ir_pass_dft(_ctx: IRFunction): + global visited_instructions + + visited_instructions = OrderedSet() + + ctx = _ctx.copy() + ctx.basic_blocks = [] + + for i in range(len(_ctx.basic_blocks)): + _process_basic_block(ctx, _ctx.basic_blocks[i]) + + return ctx diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index a29016dcae..263006c875 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -17,6 +17,9 @@ from vyper.ir.compile_ir import is_mem_sym, is_symbol from vyper.semantics.types.function import ContractFunctionT from vyper.utils import OrderedSet, MemoryPositions +from vyper.codegen.dfg import convert_ir_to_dfg +from vyper.codegen.ir_pass_dft import ir_pass_dft + BINARY_IR_INSTRUCTIONS = [ "eq", @@ -66,6 +69,11 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No if optimize is not OptimizationLevel.NONE: optimize_function(global_function) + convert_ir_to_dfg(global_function) + + global_function = ir_pass_dft(global_function) + convert_ir_to_dfg(global_function) + return global_function From 84f8e7577b835d4a6586f3360c19fcef670edb55 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 18 Oct 2023 00:18:15 +0300 Subject: [PATCH 232/471] passes --- vyper/codegen/ir_basicblock.py | 3 +++ vyper/codegen/ir_pass_dft.py | 45 +++++++++++++--------------------- vyper/ir/bb_optimizer.py | 22 ++++++++++++++--- vyper/ir/ir_to_bb_pass.py | 22 +++++++++++++---- 4 files changed, 56 insertions(+), 36 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 5fefd7a1a3..0a326fcf5f 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -137,6 +137,9 @@ def __init__( "dloadbytes", "dload", "return", + "ret", + "jmp", + "jnz", ] self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] self.ret = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None diff --git a/vyper/codegen/ir_pass_dft.py b/vyper/codegen/ir_pass_dft.py index 66c618ad2d..369b768f53 100644 --- a/vyper/codegen/ir_pass_dft.py +++ b/vyper/codegen/ir_pass_dft.py @@ -5,7 +5,15 @@ visited_instructions = OrderedSet() -def __process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction): +def _emit_operands_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction): + for op in inst.get_input_operands(): + target = ctx.dfg_outputs.get(op.value, None) + if target is None: + continue + _process_instruction(ctx, bb, target) + + +def _process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction): for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): if target.parent.label != bb.label: @@ -18,43 +26,24 @@ def __process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction return visited_instructions.add(inst) + _emit_operands_instruction(ctx, bb, inst) bb.append_instruction(inst) -def _process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction): - for op in inst.get_input_operands(): - target = ctx.dfg_outputs.get(op.value, None) - if target.parent.label != bb.label: - continue - if target.volatile: - continue - _process_instruction(ctx, bb, target) - - if inst in visited_instructions: - return - visited_instructions.add(inst) - - bb.append_instruction(inst) - - -def _process_basic_block(ctx: IRFunction, _bb: IRBasicBlock): - bb = _bb.copy() - bb.parent = ctx +def _process_basic_block(ctx: IRFunction, bb: IRBasicBlock): ctx.append_basic_block(bb) + instructions = bb.instructions bb.instructions = [] - for inst in _bb.instructions: + for inst in instructions: _process_instruction(ctx, bb, inst) -def ir_pass_dft(_ctx: IRFunction): +def ir_pass_dft(ctx: IRFunction): global visited_instructions - visited_instructions = OrderedSet() - ctx = _ctx.copy() + basic_blocks = ctx.basic_blocks ctx.basic_blocks = [] - for i in range(len(_ctx.basic_blocks)): - _process_basic_block(ctx, _ctx.basic_blocks[i]) - - return ctx + for bb in basic_blocks: + _process_basic_block(ctx, bb) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 7022333574..95036aab42 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -13,14 +13,14 @@ def optimize_function(ctx: IRFunction): while _optimize_empty_basicblocks(ctx): pass - _calculate_in_set(ctx) + calculate_in_set(ctx) while ctx.remove_unreachable_blocks(): pass if len(ctx.basic_blocks) == 0: return ctx - _calculate_liveness(ctx.basic_blocks[0], {}) + calculate_liveness(ctx) removed = _optimize_unused_variables(ctx) if len(removed) == 0: @@ -81,7 +81,7 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> None: return count -def _calculate_in_set(ctx: IRFunction) -> None: +def calculate_in_set(ctx: IRFunction) -> None: """ Calculate in set for each basic block. """ @@ -130,6 +130,12 @@ def _calculate_in_set(ctx: IRFunction) -> None: in_bb.add_out(bb) +def _reset_liveness(ctx: IRFunction) -> None: + for bb in ctx.basic_blocks: + for inst in bb.instructions: + inst.liveness = OrderedSet() + + def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: for out_bb in bb.out_set: if liveness_visited.get(bb, None) == out_bb: @@ -140,3 +146,13 @@ def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: bb.out_vars = bb.out_vars.union(in_vars) bb.calculate_liveness() + + +def calculate_liveness(ctx: IRFunction) -> None: + _reset_liveness(ctx) + _calculate_liveness(ctx.basic_blocks[0], {}) + + +def optimize_empty_blocks(ctx: IRFunction) -> None: + while _optimize_empty_basicblocks(ctx): + pass diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 263006c875..027240aa33 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -13,7 +13,12 @@ from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel from vyper.evm.opcodes import get_opcodes -from vyper.ir.bb_optimizer import optimize_function +from vyper.ir.bb_optimizer import ( + calculate_in_set, + calculate_liveness, + optimize_empty_blocks, + optimize_function, +) from vyper.ir.compile_ir import is_mem_sym, is_symbol from vyper.semantics.types.function import ContractFunctionT from vyper.utils import OrderedSet, MemoryPositions @@ -66,12 +71,19 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No revert_bb = global_function.append_basic_block(revert_bb) revert_bb.append_instruction(IRInstruction("revert", [IRLiteral(0), IRLiteral(0)])) - if optimize is not OptimizationLevel.NONE: - optimize_function(global_function) - + optimize_empty_blocks(global_function) + calculate_in_set(global_function) + calculate_liveness(global_function) convert_ir_to_dfg(global_function) - global_function = ir_pass_dft(global_function) + ir_pass_dft(global_function) + + optimize_empty_blocks(global_function) + calculate_in_set(global_function) + calculate_liveness(global_function) + # if optimize is not OptimizationLevel.NONE: + # optimize_function(global_function) + convert_ir_to_dfg(global_function) return global_function From 4cb921d9b7a4883fd8934404fae209508554cfa0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 18 Oct 2023 00:37:05 +0300 Subject: [PATCH 233/471] optimization --- vyper/codegen/dfg.py | 18 +++++++++--------- vyper/codegen/ir_pass_dft.py | 10 +--------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 3d8b2343d6..eb77a38a97 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -287,15 +287,15 @@ def _generate_evm_for_instruction_r( global label_counter origin_inst = None - # for op in inst.get_output_operands(): - # for target in ctx.dfg_inputs.get(op.value, []): - # if target.parent != inst.parent: - # continue - # if target.fen != inst.fen: - # continue - # assembly, origin_inst = _generate_evm_for_instruction_r( - # ctx, assembly, target, stack_map - # ) + for op in inst.get_output_operands(): + for target in ctx.dfg_inputs.get(op.value, []): + if target.parent != inst.parent: + continue + if target.fen != inst.fen: + continue + assembly, origin_inst = _generate_evm_for_instruction_r( + ctx, assembly, target, stack_map + ) if origin_inst is None: if inst.opcode in ["jmp", "jnz"]: diff --git a/vyper/codegen/ir_pass_dft.py b/vyper/codegen/ir_pass_dft.py index 369b768f53..c292836ebf 100644 --- a/vyper/codegen/ir_pass_dft.py +++ b/vyper/codegen/ir_pass_dft.py @@ -14,18 +14,10 @@ def _emit_operands_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstru def _process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction): - for op in inst.get_output_operands(): - for target in ctx.dfg_inputs.get(op.value, []): - if target.parent.label != bb.label: - continue - if target.volatile: - continue - _process_instruction(ctx, bb, target) - + global visited_instructions if inst in visited_instructions: return visited_instructions.add(inst) - _emit_operands_instruction(ctx, bb, inst) bb.append_instruction(inst) From ac54557aa82f26b36adfc38d0a3eb383e1615a98 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 18 Oct 2023 00:43:36 +0300 Subject: [PATCH 234/471] constant propagation --- vyper/codegen/ir_pass_constant_propagation.py | 15 +++++++++++++++ vyper/ir/ir_to_bb_pass.py | 8 +++++--- 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 vyper/codegen/ir_pass_constant_propagation.py diff --git a/vyper/codegen/ir_pass_constant_propagation.py b/vyper/codegen/ir_pass_constant_propagation.py new file mode 100644 index 0000000000..77e800baf1 --- /dev/null +++ b/vyper/codegen/ir_pass_constant_propagation.py @@ -0,0 +1,15 @@ +from vyper.codegen.ir_basicblock import IRBasicBlock +from vyper.codegen.ir_function import IRFunction +from vyper.utils import OrderedSet + + +def _process_basic_block(ctx: IRFunction, bb: IRBasicBlock): + pass + + +def ir_pass_constant_propagation(ctx: IRFunction): + global visited_instructions + visited_instructions = OrderedSet() + + for bb in ctx.basic_blocks: + _process_basic_block(ctx, bb) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 027240aa33..565075d943 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -24,6 +24,7 @@ from vyper.utils import OrderedSet, MemoryPositions from vyper.codegen.dfg import convert_ir_to_dfg from vyper.codegen.ir_pass_dft import ir_pass_dft +from vyper.codegen.ir_pass_constant_propagation import ir_pass_constant_propagation BINARY_IR_INSTRUCTIONS = [ @@ -71,18 +72,19 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No revert_bb = global_function.append_basic_block(revert_bb) revert_bb.append_instruction(IRInstruction("revert", [IRLiteral(0), IRLiteral(0)])) + if optimize is not OptimizationLevel.NONE: + optimize_function(global_function) + optimize_empty_blocks(global_function) calculate_in_set(global_function) calculate_liveness(global_function) convert_ir_to_dfg(global_function) + ir_pass_constant_propagation(global_function) ir_pass_dft(global_function) - optimize_empty_blocks(global_function) calculate_in_set(global_function) calculate_liveness(global_function) - # if optimize is not OptimizationLevel.NONE: - # optimize_function(global_function) convert_ir_to_dfg(global_function) From 5dd39b0db172ed54fd0256986c0caf9284d18941 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 18 Oct 2023 15:00:22 +0300 Subject: [PATCH 235/471] more refactor --- vyper/codegen/ir.py | 38 ++++++++++++++++++++++++++++++++++++++ vyper/compiler/phases.py | 5 +++-- vyper/ir/ir_to_bb_pass.py | 16 ---------------- 3 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 vyper/codegen/ir.py diff --git a/vyper/codegen/ir.py b/vyper/codegen/ir.py new file mode 100644 index 0000000000..4891042ec4 --- /dev/null +++ b/vyper/codegen/ir.py @@ -0,0 +1,38 @@ +from typing import Optional +from vyper.codegen.ir_pass_constant_propagation import ir_pass_constant_propagation +from vyper.codegen.dfg import convert_ir_to_dfg +from vyper.codegen.ir_function import IRFunctionBase +from vyper.codegen.ir_node import IRnode +from vyper.codegen.ir_pass_dft import ir_pass_dft +from vyper.compiler.settings import OptimizationLevel +from vyper.ir.bb_optimizer import ( + calculate_in_set, + calculate_liveness, + optimize_empty_blocks, + optimize_function, +) +from vyper.ir.ir_to_bb_pass import convert_ir_basicblock + + +def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunctionBase: + # Convert "old" IR to "new" IR + ctx = convert_ir_basicblock(ir, optimize) + + # Run passes on "new" IR + if optimize is not OptimizationLevel.NONE: + optimize_function(ctx) + + optimize_empty_blocks(ctx) + calculate_in_set(ctx) + calculate_liveness(ctx) + convert_ir_to_dfg(ctx) + + ir_pass_constant_propagation(ctx) + ir_pass_dft(ctx) + + calculate_in_set(ctx) + calculate_liveness(ctx) + + convert_ir_to_dfg(ctx) + + return ctx diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 58a8325d41..587820d80d 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -11,7 +11,8 @@ from vyper.compiler.settings import OptimizationLevel, Settings from vyper.exceptions import StructureException from vyper.ir import compile_ir, optimizer -from vyper.ir.ir_to_bb_pass import convert_ir_basicblock, generate_assembly_experimental +from vyper.ir.ir_to_bb_pass import generate_assembly_experimental +from vyper.codegen.ir import generate_ir from vyper.semantics import set_data_positions, validate_semantics from vyper.semantics.types.function import ContractFunctionT from vyper.typing import InterfaceImports, StorageLayout @@ -165,7 +166,7 @@ def _ir_output(self): # fetch both deployment and runtime IR nodes = generate_ir_nodes(self.global_ctx, self.settings.optimize) if self.experimental_codegen: - return [convert_ir_basicblock(nodes[0]), convert_ir_basicblock(nodes[1])] + return [generate_ir(nodes[0]), generate_ir(nodes[1])] else: return nodes diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 565075d943..3f74f120aa 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -72,22 +72,6 @@ def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = No revert_bb = global_function.append_basic_block(revert_bb) revert_bb.append_instruction(IRInstruction("revert", [IRLiteral(0), IRLiteral(0)])) - if optimize is not OptimizationLevel.NONE: - optimize_function(global_function) - - optimize_empty_blocks(global_function) - calculate_in_set(global_function) - calculate_liveness(global_function) - convert_ir_to_dfg(global_function) - - ir_pass_constant_propagation(global_function) - ir_pass_dft(global_function) - - calculate_in_set(global_function) - calculate_liveness(global_function) - - convert_ir_to_dfg(global_function) - return global_function From 38e4a0f9e0bd1f053cda1916fc41d7dd9b9a2768 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 18 Oct 2023 16:00:51 +0300 Subject: [PATCH 236/471] cleanup --- vyper/ir/ir_to_bb_pass.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 3f74f120aa..ad962f4d96 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -13,12 +13,6 @@ from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel from vyper.evm.opcodes import get_opcodes -from vyper.ir.bb_optimizer import ( - calculate_in_set, - calculate_liveness, - optimize_empty_blocks, - optimize_function, -) from vyper.ir.compile_ir import is_mem_sym, is_symbol from vyper.semantics.types.function import ContractFunctionT from vyper.utils import OrderedSet, MemoryPositions @@ -64,7 +58,7 @@ def generate_assembly_experimental( return generate_evm(ir, optimize is OptimizationLevel.NONE) -def convert_ir_basicblock(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: +def convert_ir_basicblock(ir: IRnode) -> IRFunction: global_function = IRFunction(IRLabel("global")) _convert_ir_basicblock(global_function, ir, {}, {}, {}) From 58cbd126d7bd43f9830b38f44adb2864832ed7a8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 18 Oct 2023 16:00:59 +0300 Subject: [PATCH 237/471] cleanup --- vyper/ir/bb_optimizer.py | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 95036aab42..7a42d12f1b 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -6,25 +6,7 @@ ) from vyper.codegen.ir_function import IRFunction from vyper.utils import OrderedSet - - -def optimize_function(ctx: IRFunction): - while True: - while _optimize_empty_basicblocks(ctx): - pass - - calculate_in_set(ctx) - while ctx.remove_unreachable_blocks(): - pass - - if len(ctx.basic_blocks) == 0: - return ctx - - calculate_liveness(ctx) - - removed = _optimize_unused_variables(ctx) - if len(removed) == 0: - break +from vyper.utils import ir_pass def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: @@ -46,7 +28,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: return removeList -def _optimize_empty_basicblocks(ctx: IRFunction) -> None: +def _optimize_empty_basicblocks(ctx: IRFunction) -> int: """ Remove empty basic blocks. """ @@ -153,6 +135,18 @@ def calculate_liveness(ctx: IRFunction) -> None: _calculate_liveness(ctx.basic_blocks[0], {}) -def optimize_empty_blocks(ctx: IRFunction) -> None: - while _optimize_empty_basicblocks(ctx): - pass +@ir_pass +def ir_pass_optimize_empty_blocks(ctx: IRFunction) -> int: + changes = _optimize_empty_basicblocks(ctx) + calculate_in_set(ctx) + return changes + + +@ir_pass +def ir_pass_remove_unreachable_blocks(ctx: IRFunction) -> int: + return ctx.remove_unreachable_blocks() + + +@ir_pass +def ir_pass_optimize_unused_variables(ctx: IRFunction) -> int: + return _optimize_unused_variables(ctx) From 4a6a7d9a2080123df194f0534b83e8d4cac1b530 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 18 Oct 2023 16:01:19 +0300 Subject: [PATCH 238/471] ir_pass decorator --- vyper/utils.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/vyper/utils.py b/vyper/utils.py index 0870f8ef31..db5ec2c8c7 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -462,3 +462,25 @@ def annotate_source_code( cleanup_lines += [""] * (num_lines - len(cleanup_lines)) return "\n".join(cleanup_lines) + + +def ir_pass(func): + """ + Decorator for IR passes. This decorator will run the pass repeatedly until + no more changes are made. + """ + + def wrapper(*args, **kwargs): + count = 0 + + while True: + changes = func(*args, **kwargs) or 0 + if isinstance(changes, list): + changes = len(changes) + count += changes + if changes == 0: + break + + return count + + return wrapper From 6db1d87e0d43504227784587022a7d4c17fba2de Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 18 Oct 2023 16:01:34 +0300 Subject: [PATCH 239/471] make passes --- vyper/codegen/ir_pass_constant_propagation.py | 3 ++- vyper/codegen/ir_pass_dft.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/ir_pass_constant_propagation.py b/vyper/codegen/ir_pass_constant_propagation.py index 77e800baf1..ea879df617 100644 --- a/vyper/codegen/ir_pass_constant_propagation.py +++ b/vyper/codegen/ir_pass_constant_propagation.py @@ -1,12 +1,13 @@ from vyper.codegen.ir_basicblock import IRBasicBlock from vyper.codegen.ir_function import IRFunction -from vyper.utils import OrderedSet +from vyper.utils import OrderedSet, ir_pass def _process_basic_block(ctx: IRFunction, bb: IRBasicBlock): pass +@ir_pass def ir_pass_constant_propagation(ctx: IRFunction): global visited_instructions visited_instructions = OrderedSet() diff --git a/vyper/codegen/ir_pass_dft.py b/vyper/codegen/ir_pass_dft.py index c292836ebf..0968219205 100644 --- a/vyper/codegen/ir_pass_dft.py +++ b/vyper/codegen/ir_pass_dft.py @@ -1,6 +1,6 @@ from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction from vyper.codegen.ir_function import IRFunction -from vyper.utils import OrderedSet +from vyper.utils import OrderedSet, ir_pass visited_instructions = OrderedSet() @@ -30,6 +30,7 @@ def _process_basic_block(ctx: IRFunction, bb: IRBasicBlock): _process_instruction(ctx, bb, inst) +@ir_pass def ir_pass_dft(ctx: IRFunction): global visited_instructions visited_instructions = OrderedSet() From d7e2bbf02e224a5a958099f47f218094ffdf0d4b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 18 Oct 2023 16:01:50 +0300 Subject: [PATCH 240/471] ir passes calling --- vyper/codegen/ir.py | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/vyper/codegen/ir.py b/vyper/codegen/ir.py index 4891042ec4..00a326c6ea 100644 --- a/vyper/codegen/ir.py +++ b/vyper/codegen/ir.py @@ -8,31 +8,41 @@ from vyper.ir.bb_optimizer import ( calculate_in_set, calculate_liveness, - optimize_empty_blocks, - optimize_function, + ir_pass_optimize_empty_blocks, + ir_pass_remove_unreachable_blocks, + ir_pass_optimize_unused_variables, ) from vyper.ir.ir_to_bb_pass import convert_ir_basicblock def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunctionBase: # Convert "old" IR to "new" IR - ctx = convert_ir_basicblock(ir, optimize) + ctx = convert_ir_basicblock(ir) # Run passes on "new" IR - if optimize is not OptimizationLevel.NONE: - optimize_function(ctx) + # if optimize is not OptimizationLevel.NONE: + # optimize_function(ctx) - optimize_empty_blocks(ctx) - calculate_in_set(ctx) - calculate_liveness(ctx) - convert_ir_to_dfg(ctx) + while True: + changes = 0 - ir_pass_constant_propagation(ctx) - ir_pass_dft(ctx) + changes += ir_pass_optimize_empty_blocks(ctx) + changes += ir_pass_remove_unreachable_blocks(ctx) - calculate_in_set(ctx) - calculate_liveness(ctx) + calculate_liveness(ctx) - convert_ir_to_dfg(ctx) + changes += ir_pass_optimize_unused_variables(ctx) + + convert_ir_to_dfg(ctx) + + changes += ir_pass_constant_propagation(ctx) + changes += ir_pass_dft(ctx) + + calculate_in_set(ctx) + calculate_liveness(ctx) + convert_ir_to_dfg(ctx) + + if changes == 0: + break return ctx From bb5aa8cbc3e9a686e1a60ba80c90ebe0a8ada51b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 18 Oct 2023 16:52:04 +0300 Subject: [PATCH 241/471] immutables cherry-pick --- vyper/codegen/dfg.py | 20 ++++++++++---------- vyper/codegen/ir.py | 4 +--- vyper/codegen/ir_basicblock.py | 2 ++ vyper/ir/ir_to_bb_pass.py | 8 ++++---- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index eb77a38a97..d9519c64cd 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -323,6 +323,10 @@ def _generate_evm_for_instruction_r( operands = inst.get_non_label_operands() elif opcode == "alloca": operands = inst.operands[1:2] + elif opcode == "iload": + operands = [] + elif opcode == "istore": + operands = inst.operands[0:1] else: operands = inst.operands @@ -459,24 +463,20 @@ def _generate_evm_for_instruction_r( assembly.extend(["RETURN"]) assembly.append([RuntimeHeader("_sym_runtime_begin", memsize, padding)]) assembly = assembly[-1] - pass + elif opcode == "iload": + loc = inst.operands[0].value + assembly.extend(["_OFST", "_mem_deploy_end", loc, "MLOAD"]) + elif opcode == "istore": + loc = inst.operands[1].value + assembly.extend(["_OFST", "_mem_deploy_end", loc, "MSTORE"]) else: raise Exception(f"Unknown opcode: {opcode}") # Step 6: Emit instructions output operands (if any) - # FIXME: WHOLE THING NEEDS REFACTOR if inst.ret is not None: assert isinstance(inst.ret, IRVariable), "Return value must be a variable" if inst.ret.mem_type == IRVariable.MemType.MEMORY: - # # if inst.ret.address_access: FIXME: MEMORY REFACTOR - # # if inst.opcode != "alloca": # FIXMEEEE - # # if inst.opcode != "codecopy": - # # assembly.extend([*PUSH(inst.ret.addr)]) - # # else: - # assembly.extend([*PUSH(inst.ret.mem_addr + 30)]) - # else: assembly.extend([*PUSH(inst.ret.mem_addr)]) - # assembly.append("MSTORE") return assembly, inst.liveness diff --git a/vyper/codegen/ir.py b/vyper/codegen/ir.py index 00a326c6ea..a7634369fa 100644 --- a/vyper/codegen/ir.py +++ b/vyper/codegen/ir.py @@ -20,9 +20,7 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF ctx = convert_ir_basicblock(ir) # Run passes on "new" IR - # if optimize is not OptimizationLevel.NONE: - # optimize_function(ctx) - + # TODO: Add support for optimization levels while True: changes = 0 diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 0a326fcf5f..d889990840 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -129,6 +129,8 @@ def __init__( "invoke", "sload", "sstore", + "iload", + "istore", "assert", "mstore", "mload", diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index ad962f4d96..d6688547d7 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -756,13 +756,13 @@ def _convert_ir_basicblock( symbols[sym_ir.value] = arg_1 return arg_1 - elif ir.value == "sload": + elif ir.value in ["sload", "iload"]: arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) - return ctx.append_instruction("sload", [arg_0]) - elif ir.value == "sstore": + return ctx.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) - inst = IRInstruction("sstore", [arg_1, arg_0]) + inst = IRInstruction(ir.value, [arg_1, arg_0]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "unique_symbol": sym = ir.args[0] From caad6dfcd813cf338a9662fa5f33be2b0f1c67a4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 00:09:18 +0300 Subject: [PATCH 242/471] compute dup requirements --- vyper/codegen/dfg.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index d9519c64cd..2de13ad365 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -101,6 +101,28 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: for op in res: ctx.dfg_outputs[op.value] = inst + # Build DUP requirements + _compute_dup_requirements(ctx, OrderedSet(), {}, ctx.basic_blocks[0]) + + +def _compute_dup_requirements( + ctx: IRFunction, visited: OrderedSet, last_seen: dict, bb: IRBasicBlock +) -> None: + if bb in visited: + return + visited.add(bb) + + for inst in bb.instructions: + operands = inst.get_input_operands() + for op in operands: + l = last_seen.get(op.value, None) + if l: + l.dup_requirements.add(op) + last_seen[op.value] = inst + + for out_bb in bb.out_set: + _compute_dup_requirements(ctx, visited, last_seen.copy(), out_bb) + def compute_phi_vars(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: @@ -176,7 +198,8 @@ def _stack_duplications( assert op.use_count >= 0, "Operand used up" depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" - if op.use_count > 1: + if op in inst.dup_requirements: + # if op.use_count > 1: # print(inst, op) # Operand needs duplication stack_map.dup(assembly, depth) From 1d4cd36ac47c902b7da7760f58bbfb7e738d37e5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 00:09:45 +0300 Subject: [PATCH 243/471] add dup_requirements to IRInstruction --- vyper/codegen/ir_basicblock.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index d889990840..8aab2fd9a4 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -108,7 +108,8 @@ class IRInstruction: operands: list[IRValueBase] ret: Optional[IRValueBase] dbg: Optional[IRDebugInfo] - liveness: set[IRVariable] + liveness: OrderedSet[IRVariable] + dup_requirements: OrderedSet[IRVariable] parent: Optional["IRBasicBlock"] fen: int annotation: Optional[str] @@ -146,7 +147,8 @@ def __init__( self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] self.ret = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None self.dbg = dbg - self.liveness = set() + self.liveness = OrderedSet() + self.dup_requirements = OrderedSet() self.parent = None self.fen = -1 self.annotation = None From 24a78e60acee936f52c1cb93a4fb8a3fe02b13ed Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 00:09:59 +0300 Subject: [PATCH 244/471] recalculations --- vyper/codegen/ir.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/codegen/ir.py b/vyper/codegen/ir.py index a7634369fa..e764c54640 100644 --- a/vyper/codegen/ir.py +++ b/vyper/codegen/ir.py @@ -31,6 +31,8 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF changes += ir_pass_optimize_unused_variables(ctx) + calculate_in_set(ctx) + calculate_liveness(ctx) convert_ir_to_dfg(ctx) changes += ir_pass_constant_propagation(ctx) From 574d52375f18c56284c30f17fa0cea1fdf6abb6c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 11:17:43 +0300 Subject: [PATCH 245/471] recursive dup insertion --- vyper/codegen/dfg.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 2de13ad365..44544b0fcf 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -105,6 +105,29 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: _compute_dup_requirements(ctx, OrderedSet(), {}, ctx.basic_blocks[0]) +def _compute_inst_dup_requirements_r( + ctx: IRFunction, inst: IRInstruction, visited: OrderedSet, last_seen: dict +): + for op in inst.get_output_operands(): + for target in ctx.dfg_inputs.get(op.value, []): + if target.parent != inst.parent: + continue + if target.fen != inst.fen: + continue + _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) + + if inst in visited: + return + visited.add(inst) + + operands = inst.get_input_operands() + for op in operands: + l = last_seen.get(op.value, None) + if l: + l.dup_requirements.add(op) + last_seen[op.value] = inst + + def _compute_dup_requirements( ctx: IRFunction, visited: OrderedSet, last_seen: dict, bb: IRBasicBlock ) -> None: @@ -113,12 +136,7 @@ def _compute_dup_requirements( visited.add(bb) for inst in bb.instructions: - operands = inst.get_input_operands() - for op in operands: - l = last_seen.get(op.value, None) - if l: - l.dup_requirements.add(op) - last_seen[op.value] = inst + _compute_inst_dup_requirements_r(ctx, inst, visited, last_seen) for out_bb in bb.out_set: _compute_dup_requirements(ctx, visited, last_seen.copy(), out_bb) @@ -359,10 +377,8 @@ def _generate_evm_for_instruction_r( depth = stack_map.get_depth_in(inputs) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" to_be_replaced = stack_map.peek(depth) - if to_be_replaced.use_count > 1: - to_be_replaced.use_count -= 1 - if to_be_replaced.use_count > 1: - stack_map.dup(assembly, depth) + if to_be_replaced in inst.dup_requirements: + stack_map.dup(assembly, depth) stack_map.poke(0, ret) else: stack_map.poke(depth, ret) From d39028121b129a61097e7770ba64315d4a94b2c6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 15:15:15 +0300 Subject: [PATCH 246/471] temporary --- vyper/codegen/dfg.py | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 44544b0fcf..e6d614d49c 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -79,6 +79,7 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: ctx.dfg_outputs = {} for bb in ctx.basic_blocks: for inst in bb.instructions: + inst.dup_requirements = OrderedSet() operands = inst.get_input_operands() operands.extend(inst.get_output_operands()) for op in operands: @@ -102,44 +103,54 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: ctx.dfg_outputs[op.value] = inst # Build DUP requirements + print("Building DUP requirements") _compute_dup_requirements(ctx, OrderedSet(), {}, ctx.basic_blocks[0]) def _compute_inst_dup_requirements_r( - ctx: IRFunction, inst: IRInstruction, visited: OrderedSet, last_seen: dict + ctx: IRFunction, inst: IRInstruction, visited: OrderedSet, accessed: OrderedSet, last_seen: dict ): + if inst in visited: + return accessed + + print(inst) + for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): if target.parent != inst.parent: continue + if inst.opcode in ["jmp", "jnz"]: + labels = inst.get_label_operands() + for l in labels: + _compute_dup_requirements(ctx, l, visited, accessed, last_seen.copy()) + continue if target.fen != inst.fen: continue - _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) - if inst in visited: - return + accessed = _compute_inst_dup_requirements_r(ctx, target, visited, accessed, last_seen) + visited.add(inst) - operands = inst.get_input_operands() - for op in operands: + for op in inst.get_input_operands(): l = last_seen.get(op.value, None) if l: l.dup_requirements.add(op) last_seen[op.value] = inst + accessed.add(op) + # if op in accessed: + # inst.dup_requirements.add(op) + # else: + # accessed.add(op) + + return accessed def _compute_dup_requirements( ctx: IRFunction, visited: OrderedSet, last_seen: dict, bb: IRBasicBlock ) -> None: - if bb in visited: - return - visited.add(bb) - for inst in bb.instructions: - _compute_inst_dup_requirements_r(ctx, inst, visited, last_seen) - - for out_bb in bb.out_set: - _compute_dup_requirements(ctx, visited, last_seen.copy(), out_bb) + accessed = OrderedSet() + _compute_inst_dup_requirements_r(ctx, inst, visited, accessed, last_seen) def compute_phi_vars(ctx: IRFunction) -> None: @@ -210,6 +221,7 @@ def _stack_duplications( stack_ops: list[IRValueBase], ) -> None: last_used = inst.parent.get_last_used_operands(inst) + print(inst) for op in stack_ops: if op.is_literal or isinstance(op, IRLabel): continue From da042a1d5ef1ed8db79b6f2170fe1799942d6459 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 16:19:34 +0300 Subject: [PATCH 247/471] temporary --- vyper/codegen/dfg.py | 62 +++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index e6d614d49c..d6bbb72d3f 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -80,6 +80,7 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: for inst in bb.instructions: inst.dup_requirements = OrderedSet() + inst.fen = -1 operands = inst.get_input_operands() operands.extend(inst.get_output_operands()) for op in operands: @@ -104,53 +105,60 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: # Build DUP requirements print("Building DUP requirements") - _compute_dup_requirements(ctx, OrderedSet(), {}, ctx.basic_blocks[0]) + _compute_dup_requirements(ctx) def _compute_inst_dup_requirements_r( - ctx: IRFunction, inst: IRInstruction, visited: OrderedSet, accessed: OrderedSet, last_seen: dict + ctx: IRFunction, inst: IRInstruction, visited: OrderedSet, last_seen: dict ): - if inst in visited: - return accessed - - print(inst) - for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): if target.parent != inst.parent: continue - if inst.opcode in ["jmp", "jnz"]: - labels = inst.get_label_operands() - for l in labels: - _compute_dup_requirements(ctx, l, visited, accessed, last_seen.copy()) - continue if target.fen != inst.fen: continue - accessed = _compute_inst_dup_requirements_r(ctx, target, visited, accessed, last_seen) + _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) + if inst in visited: + return visited.add(inst) + if inst.opcode == "select": + return + for op in inst.get_input_operands(): - l = last_seen.get(op.value, None) + last_seen[op.value] = inst + + old_last_seen = last_seen.copy() + + for op in inst.get_input_operands(): + _compute_inst_dup_requirements_r(ctx, ctx.dfg_outputs[op.value], visited, last_seen) + l = old_last_seen.get(op.value, None) if l: l.dup_requirements.add(op) - last_seen[op.value] = inst - accessed.add(op) - # if op in accessed: - # inst.dup_requirements.add(op) - # else: - # accessed.add(op) - return accessed + print("**", inst) + + return -def _compute_dup_requirements( - ctx: IRFunction, visited: OrderedSet, last_seen: dict, bb: IRBasicBlock -) -> None: - for inst in bb.instructions: - accessed = OrderedSet() - _compute_inst_dup_requirements_r(ctx, inst, visited, accessed, last_seen) +def _compute_dup_requirements(ctx: IRFunction) -> None: + print("***************Computing DUP requirements") + + for bb in ctx.basic_blocks: + visited = OrderedSet() + last_seen = {} + + fen = 0 + for inst in bb.instructions: + inst.fen = fen + if inst.volatile: + fen += 1 + + for inst in bb.instructions: + _compute_inst_dup_requirements_r(ctx, inst, visited, last_seen) + print("*************** DONE: Computing DUP requirements") def compute_phi_vars(ctx: IRFunction) -> None: From cfa2f547378c50639e46834c9d963c0dc4947afe Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 16:58:39 +0300 Subject: [PATCH 248/471] fixed --- vyper/codegen/dfg.py | 32 ++++++++++++++++++-------------- vyper/codegen/ir.py | 4 ++-- vyper/codegen/ir_basicblock.py | 7 +++++-- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index d6bbb72d3f..f29b0a777c 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -104,7 +104,6 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: ctx.dfg_outputs[op.value] = inst # Build DUP requirements - print("Building DUP requirements") _compute_dup_requirements(ctx) @@ -128,37 +127,43 @@ def _compute_inst_dup_requirements_r( return for op in inst.get_input_operands(): - last_seen[op.value] = inst - - old_last_seen = last_seen.copy() + target = ctx.dfg_outputs[op.value] + if target.parent != inst.parent: + continue + old_last_seen = last_seen.copy() + _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) for op in inst.get_input_operands(): - _compute_inst_dup_requirements_r(ctx, ctx.dfg_outputs[op.value], visited, last_seen) - l = old_last_seen.get(op.value, None) + l = last_seen.get(op.value, None) if l: l.dup_requirements.add(op) - - print("**", inst) + last_seen[op.value] = inst return def _compute_dup_requirements(ctx: IRFunction) -> None: - print("***************Computing DUP requirements") + # print("***************Computing DUP requirements") for bb in ctx.basic_blocks: - visited = OrderedSet() - last_seen = {} - fen = 0 for inst in bb.instructions: inst.fen = fen if inst.volatile: fen += 1 + visited = OrderedSet() + last_seen = {} for inst in bb.instructions: _compute_inst_dup_requirements_r(ctx, inst, visited, last_seen) - print("*************** DONE: Computing DUP requirements") + + out_vars = bb.out_vars + for inst in bb.instructions[::-1]: + for op in inst.get_input_operands(): + if op in out_vars: + inst.dup_requirements.add(op) + + # print("*************** DONE: Computing DUP requirements") def compute_phi_vars(ctx: IRFunction) -> None: @@ -229,7 +234,6 @@ def _stack_duplications( stack_ops: list[IRValueBase], ) -> None: last_used = inst.parent.get_last_used_operands(inst) - print(inst) for op in stack_ops: if op.is_literal or isinstance(op, IRLabel): continue diff --git a/vyper/codegen/ir.py b/vyper/codegen/ir.py index e764c54640..e8f1180429 100644 --- a/vyper/codegen/ir.py +++ b/vyper/codegen/ir.py @@ -31,8 +31,8 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF changes += ir_pass_optimize_unused_variables(ctx) - calculate_in_set(ctx) - calculate_liveness(ctx) + # calculate_in_set(ctx) + # calculate_liveness(ctx) convert_ir_to_dfg(ctx) changes += ir_pass_constant_propagation(ctx) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 8aab2fd9a4..c50a7b1c94 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -199,8 +199,11 @@ def __repr__(self) -> str: if self.annotation: s += f" <{self.annotation}>" - if self.liveness: - return f"{s: <30} # {self.liveness}" + if len(self.dup_requirements): + return f"{s: <30} # {self.dup_requirements}" + + # if self.liveness: + # return f"{s: <30} # {self.liveness}" return s From 466f393946fa5cdfb8d0900df53748473f6aa86c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 21:31:59 +0300 Subject: [PATCH 249/471] disable dfg pass for now --- vyper/codegen/dfg.py | 8 ++------ vyper/codegen/ir.py | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index f29b0a777c..e9512af037 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -110,13 +110,13 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: def _compute_inst_dup_requirements_r( ctx: IRFunction, inst: IRInstruction, visited: OrderedSet, last_seen: dict ): + print(inst) for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): if target.parent != inst.parent: continue if target.fen != inst.fen: continue - _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) if inst in visited: @@ -143,10 +143,8 @@ def _compute_inst_dup_requirements_r( def _compute_dup_requirements(ctx: IRFunction) -> None: - # print("***************Computing DUP requirements") - + fen = 0 for bb in ctx.basic_blocks: - fen = 0 for inst in bb.instructions: inst.fen = fen if inst.volatile: @@ -163,8 +161,6 @@ def _compute_dup_requirements(ctx: IRFunction) -> None: if op in out_vars: inst.dup_requirements.add(op) - # print("*************** DONE: Computing DUP requirements") - def compute_phi_vars(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: diff --git a/vyper/codegen/ir.py b/vyper/codegen/ir.py index e8f1180429..561152934b 100644 --- a/vyper/codegen/ir.py +++ b/vyper/codegen/ir.py @@ -31,12 +31,12 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF changes += ir_pass_optimize_unused_variables(ctx) - # calculate_in_set(ctx) - # calculate_liveness(ctx) + calculate_in_set(ctx) + calculate_liveness(ctx) convert_ir_to_dfg(ctx) changes += ir_pass_constant_propagation(ctx) - changes += ir_pass_dft(ctx) + # changes += ir_pass_dft(ctx) calculate_in_set(ctx) calculate_liveness(ctx) From 19917df97c7b9b9a69a2a37f3c06e25c312f7489 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 21:36:03 +0300 Subject: [PATCH 250/471] remove the use of .use_count --- vyper/codegen/dfg.py | 16 +--------------- vyper/codegen/ir_basicblock.py | 11 ----------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index e9512af037..190e3e574b 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -83,8 +83,6 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: inst.fen = -1 operands = inst.get_input_operands() operands.extend(inst.get_output_operands()) - for op in operands: - op.use_count = 0 # Build DFG for bb in ctx.basic_blocks: @@ -93,7 +91,6 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: res = inst.get_output_operands() for op in operands: - op.use_count += 1 ctx.dfg_inputs[op.value] = ( [inst] if ctx.dfg_inputs.get(op.value) is None @@ -110,7 +107,6 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: def _compute_inst_dup_requirements_r( ctx: IRFunction, inst: IRInstruction, visited: OrderedSet, last_seen: dict ): - print(inst) for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): if target.parent != inst.parent: @@ -233,15 +229,10 @@ def _stack_duplications( for op in stack_ops: if op.is_literal or isinstance(op, IRLabel): continue - assert op.use_count >= 0, "Operand used up" depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" if op in inst.dup_requirements: - # if op.use_count > 1: - # print(inst, op) - # Operand needs duplication stack_map.dup(assembly, depth) - op.use_count -= 1 def __stack_duplications( @@ -251,19 +242,14 @@ def __stack_duplications( stack_map: StackMap, stack_ops: list[IRValueBase], ) -> None: - last_used2 = inst.parent.get_last_used_operands(inst) last_used = inst.liveness.difference(dep_liveness) for op in stack_ops: if op.is_literal or isinstance(op, IRLabel): continue - assert op.use_count >= 0, "Operand used up" depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" if op not in last_used: - # print(inst, op) - # Operand needs duplication stack_map.dup(assembly, depth) - op.use_count -= 1 def _stack_reorder( @@ -476,7 +462,7 @@ def _generate_evm_for_instruction_r( ] ) label_counter += 1 - if stack_map.get_height() > 0 and stack_map.peek(0).use_count == 0: + if stack_map.get_height() > 0 and stack_map.peek(0) in inst.dup_requirements: stack_map.pop() assembly.append("POP") elif opcode == "call": diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index c50a7b1c94..1d9bfd8a76 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -33,12 +33,10 @@ def __repr__(self) -> str: class IRValueBase: value: IRValueBaseValue - use_count: int = 0 def __init__(self, value: IRValueBaseValue) -> None: assert isinstance(value, IRValueBaseValue), "value must be an IRValueBaseValue" self.value = value - self.use_count = 0 @property def is_literal(self) -> bool: @@ -55,7 +53,6 @@ class IRLiteral(IRValueBase): def __init__(self, value: IRValueBaseValue) -> None: super().__init__(value) - self.use_count = 0 @property def is_literal(self) -> bool: @@ -360,14 +357,6 @@ def get_liveness(self) -> set[IRVariable]: """ return self.instructions[0].liveness - def get_use_count(self, var: IRVariable) -> int: - """ - Get use count of variable in basic block. - """ - if var.value not in self.use_counts.keys(): - self.use_counts[var.value] = 0 if var.value not in self.instructions[-1].liveness else 1 - return self.use_counts[var.value] - def get_last_used_operands(self, _inst: IRInstruction) -> OrderedSet[IRVariable]: """ Get last used operands of instruction. From c5a38ee7c3320cc8b2e5f96e84e57fb1f248f219 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 21:39:02 +0300 Subject: [PATCH 251/471] more cleanup of unused code after the stack handling update --- vyper/codegen/dfg.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 190e3e574b..2f1560158f 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -394,23 +394,10 @@ def _generate_evm_for_instruction_r( _emit_input_operands(ctx, assembly, inst, operands, stack_map) # Step 3: Reorder stack - if opcode in ["jnz", "jmp"]: # and stack_map.get_height() >= 2: + if opcode in ["jnz", "jmp"]: _, b = next(enumerate(inst.parent.out_set)) t_liveness = b.in_vars_for(inst.parent) - target_stack = OrderedSet(t_liveness) # [b.phi_vars.get(v.value, v) for v in t_liveness] - current_stack = OrderedSet(stack_map.stack_map) - # if opcode == "jmp": - # need_pop = current_stack.difference(target_stack) - # for op in need_pop: - # if op.use_count > 0: - # continue - # depth = stack_map.get_depth_in(op) - # if depth is StackMap.NOT_IN_STACK: - # continue - # if depth != 0: - # stack_map.swap(assembly, depth) - # stack_map.pop() - # assembly.append("POP") + target_stack = OrderedSet(t_liveness) phi_mappings = inst.parent.get_phi_mappings() _stack_reorder(assembly, stack_map, target_stack, phi_mappings) From 6dbea929bc55b23e32492c7122c9eba1aa2262b8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 23:39:34 +0300 Subject: [PATCH 252/471] more clean up --- vyper/codegen/dfg.py | 5 +---- vyper/codegen/ir_basicblock.py | 8 ++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 2f1560158f..9d74d5a0f6 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -309,10 +309,7 @@ def _generate_evm_for_basicblock_r( if inst.volatile: fen += 1 - for idx, inst in enumerate(basicblock.instructions): - # orig_inst = ( - # basicblock.instructions[idx + 1] if idx + 1 < len(basicblock.instructions) else None - # ) + for inst in basicblock.instructions: asm, _ = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) for bb in basicblock.out_set: diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 1d9bfd8a76..e56232edb2 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -196,11 +196,11 @@ def __repr__(self) -> str: if self.annotation: s += f" <{self.annotation}>" - if len(self.dup_requirements): - return f"{s: <30} # {self.dup_requirements}" + # if len(self.dup_requirements): + # return f"{s: <30} # {self.dup_requirements}" - # if self.liveness: - # return f"{s: <30} # {self.liveness}" + if self.liveness: + return f"{s: <30} # {self.liveness}" return s From 3b2ebaa8cc818420124e262c7651a3769eae5f9f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 23:50:19 +0300 Subject: [PATCH 253/471] Remove all phi_var related code Not required after the refactor --- vyper/codegen/dfg.py | 36 +++++----------------------------- vyper/codegen/ir_basicblock.py | 16 --------------- vyper/compiler/utils.py | 13 +++++------- vyper/ir/bb_optimizer.py | 1 - 4 files changed, 10 insertions(+), 56 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 9d74d5a0f6..b5328dc815 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -158,23 +158,6 @@ def _compute_dup_requirements(ctx: IRFunction) -> None: inst.dup_requirements.add(op) -def compute_phi_vars(ctx: IRFunction) -> None: - for bb in ctx.basic_blocks: - for inst in bb.instructions: - if inst.opcode != "select": - continue - - ret_op = inst.get_output_operands()[0] - - block_a = ctx.get_basic_block(inst.operands[0].value) - block_b = ctx.get_basic_block(inst.operands[2].value) - - block_a.phi_vars[inst.operands[1].value] = ret_op - block_a.phi_vars[inst.operands[3].value] = ret_op - block_b.phi_vars[inst.operands[1].value] = ret_op - block_b.phi_vars[inst.operands[3].value] = ret_op - - visited_instructions = {IRInstruction} visited_basicblocks = {IRBasicBlock} @@ -185,8 +168,6 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: visited_instructions = set() visited_basicblocks = set() - compute_phi_vars(ctx) - _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackMap()) # Append postambles @@ -252,17 +233,12 @@ def __stack_duplications( stack_map.dup(assembly, depth) -def _stack_reorder( - assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase], phi_vars: dict = {} -) -> None: - def f(x): - return phi_vars.get(str(x), x) - - stack_ops = [f(x.value) for x in stack_ops] +def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase]) -> None: + stack_ops = [x.value for x in stack_ops] for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) - depth = stack_map.get_depth_in(op, phi_vars) + depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" is_in_place = depth == final_stack_depth @@ -393,10 +369,8 @@ def _generate_evm_for_instruction_r( # Step 3: Reorder stack if opcode in ["jnz", "jmp"]: _, b = next(enumerate(inst.parent.out_set)) - t_liveness = b.in_vars_for(inst.parent) - target_stack = OrderedSet(t_liveness) - phi_mappings = inst.parent.get_phi_mappings() - _stack_reorder(assembly, stack_map, target_stack, phi_mappings) + target_stack = OrderedSet(b.in_vars_for(inst.parent)) + _stack_reorder(assembly, stack_map, target_stack) liveness = origin_inst if origin_inst else OrderedSet() _stack_duplications(assembly, liveness, inst, stack_map, operands) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index e56232edb2..46e44f8dd8 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -234,7 +234,6 @@ class IRBasicBlock: in_set: OrderedSet["IRBasicBlock"] out_set: OrderedSet["IRBasicBlock"] out_vars: OrderedSet[IRVariable] - phi_vars: dict[str, IRVariable] def __init__(self, label: IRLabel, parent: "IRFunction") -> None: assert isinstance(label, IRLabel), "label must be an IRLabel" @@ -244,7 +243,6 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.in_set = OrderedSet() self.out_set = OrderedSet() self.out_vars = OrderedSet() - self.phi_vars = {} def add_in(self, bb: "IRBasicBlock") -> None: self.in_set.add(bb) @@ -264,19 +262,6 @@ def union_out(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: def remove_out(self, bb: "IRBasicBlock") -> None: self.out_set.remove(bb) - def get_phi_mappings(self) -> dict[str, IRVariable]: - """ - Get phi mappings for basic block. - """ - phi_mappings = {} - - for inst in self.instructions: - if inst.opcode == "select": - phi_mappings[inst.operands[1].value] = inst.ret - phi_mappings[inst.operands[3].value] = inst.ret - - return phi_mappings - def in_vars_for(self, bb: "IRBasicBlock" = None) -> set[IRVariable]: liveness = self.instructions[0].liveness.copy() @@ -391,7 +376,6 @@ def copy(self): bb.in_set = self.in_set.copy() bb.out_set = self.out_set.copy() bb.out_vars = self.out_vars.copy() - bb.phi_vars = self.phi_vars.copy() return bb def __repr__(self) -> str: diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 32f7dfe9bd..fc86fdcf38 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -79,7 +79,7 @@ def push(self, op: IRValueBase) -> None: def pop(self, num: int = 1) -> None: del self.stack_map[len(self.stack_map) - num :] - def get_depth_in(self, op: IRValueBase | list[IRValueBase], phi_vars: dict = {}) -> int: + def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. @@ -91,18 +91,15 @@ def get_depth_in(self, op: IRValueBase | list[IRValueBase], phi_vars: dict = {}) or isinstance(op, list) ), f"get_depth_in takes IRValueBase or list, got '{op}'" - def f(x): - return str(phi_vars.get(str(x), x)) - for i, stack_op in enumerate(self.stack_map[::-1]): if isinstance(stack_op, IRValueBase): - if isinstance(op, str) and f(stack_op.value) == f(op): + if isinstance(op, str) and stack_op.value == op: return -i - if isinstance(op, int) and f(stack_op.value) == f(op): + if isinstance(op, int) and stack_op.value == op: return -i - if isinstance(op, IRValueBase) and f(stack_op.value) == f(op.value): + if isinstance(op, IRValueBase) and stack_op.value == op.value: return -i - elif isinstance(op, list) and f(stack_op) in [f(v) for v in op]: + elif isinstance(op, list) and stack_op in op: return -i return StackMap.NOT_IN_STACK diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 7a42d12f1b..7bc99af8a1 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -71,7 +71,6 @@ def calculate_in_set(ctx: IRFunction) -> None: bb.in_set = OrderedSet() bb.out_set = OrderedSet() bb.out_vars = OrderedSet() - bb.phi_vars = {} deploy_bb = None for i, bb in enumerate(ctx.basic_blocks): From 770b5ebbee0c1ba817184d375754283e9b1701fa Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 23 Oct 2023 23:58:07 +0300 Subject: [PATCH 254/471] refactor out "last instr" dup code --- vyper/codegen/dfg.py | 49 ++++++++------------------------------------ 1 file changed, 8 insertions(+), 41 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index b5328dc815..6060df3f33 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -201,12 +201,10 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: def _stack_duplications( assembly: list, - dep_liveness: OrderedSet[IRValueBase], inst: IRInstruction, stack_map: StackMap, stack_ops: list[IRValueBase], ) -> None: - last_used = inst.parent.get_last_used_operands(inst) for op in stack_ops: if op.is_literal or isinstance(op, IRLabel): continue @@ -216,23 +214,6 @@ def _stack_duplications( stack_map.dup(assembly, depth) -def __stack_duplications( - assembly: list, - dep_liveness: OrderedSet[IRValueBase], - inst: IRInstruction, - stack_map: StackMap, - stack_ops: list[IRValueBase], -) -> None: - last_used = inst.liveness.difference(dep_liveness) - for op in stack_ops: - if op.is_literal or isinstance(op, IRLabel): - continue - depth = stack_map.get_depth_in(op) - assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" - if op not in last_used: - stack_map.dup(assembly, depth) - - def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase]) -> None: stack_ops = [x.value for x in stack_ops] for i in range(len(stack_ops)): @@ -286,7 +267,7 @@ def _generate_evm_for_basicblock_r( fen += 1 for inst in basicblock.instructions: - asm, _ = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) + asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) for bb in basicblock.out_set: _generate_evm_for_basicblock_r(ctx, asm, bb, stack_map.copy()) @@ -303,32 +284,19 @@ def _generate_evm_for_instruction_r( assembly: list, inst: IRInstruction, stack_map: StackMap, -) -> (list[str], IRInstruction): +) -> list[str]: global label_counter - origin_inst = None for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): if target.parent != inst.parent: continue if target.fen != inst.fen: continue - assembly, origin_inst = _generate_evm_for_instruction_r( - ctx, assembly, target, stack_map - ) - - if origin_inst is None: - if inst.opcode in ["jmp", "jnz"]: - origin_inst = OrderedSet() - for bb in inst.parent.out_set: - l = bb.in_vars_for(inst.parent) - origin_inst |= l - else: - next_inst = inst.parent.get_next_instruction(inst) - origin_inst = next_inst.liveness if next_inst else OrderedSet() + assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) if inst in visited_instructions: - return assembly, inst.liveness + return assembly visited_instructions.add(inst) opcode = inst.opcode @@ -361,7 +329,7 @@ def _generate_evm_for_instruction_r( stack_map.poke(0, ret) else: stack_map.poke(depth, ret) - return assembly, inst + return assembly # Step 2: Emit instruction's input operands _emit_input_operands(ctx, assembly, inst, operands, stack_map) @@ -372,8 +340,7 @@ def _generate_evm_for_instruction_r( target_stack = OrderedSet(b.in_vars_for(inst.parent)) _stack_reorder(assembly, stack_map, target_stack) - liveness = origin_inst if origin_inst else OrderedSet() - _stack_duplications(assembly, liveness, inst, stack_map, operands) + _stack_duplications(assembly, inst, stack_map, operands) _stack_reorder(assembly, stack_map, operands) # Step 4: Push instruction's return value to stack @@ -481,7 +448,7 @@ def _generate_evm_for_instruction_r( if inst.ret.mem_type == IRVariable.MemType.MEMORY: assembly.extend([*PUSH(inst.ret.mem_addr)]) - return assembly, inst.liveness + return assembly def _emit_input_operands( @@ -503,7 +470,7 @@ def _emit_input_operands( assembly.extend([*PUSH(op.value)]) stack_map.push(op) continue - assembly, origin_inst = _generate_evm_for_instruction_r( + assembly = _generate_evm_for_instruction_r( ctx, assembly, ctx.dfg_outputs[op.value], stack_map ) if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: From 14dd24bc668fe958e78c065bc9de642fe73a0447 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 24 Oct 2023 00:03:54 +0300 Subject: [PATCH 255/471] fix formating --- vyper/codegen/dfg.py | 15 +++------------ vyper/ir/ir_to_bb_pass.py | 20 +++----------------- 2 files changed, 6 insertions(+), 29 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 6060df3f33..35ce2e669b 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -200,10 +200,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: def _stack_duplications( - assembly: list, - inst: IRInstruction, - stack_map: StackMap, - stack_ops: list[IRValueBase], + assembly: list, inst: IRInstruction, stack_map: StackMap, stack_ops: list[IRValueBase] ) -> None: for op in stack_ops: if op.is_literal or isinstance(op, IRLabel): @@ -234,10 +231,7 @@ def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueB def _generate_evm_for_basicblock_r( - ctx: IRFunction, - asm: list, - basicblock: IRBasicBlock, - stack_map: StackMap, + ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackMap ): if basicblock in visited_basicblocks: return @@ -280,10 +274,7 @@ def _generate_evm_for_basicblock_r( def _generate_evm_for_instruction_r( - ctx: IRFunction, - assembly: list, - inst: IRInstruction, - stack_map: StackMap, + ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap ) -> list[str]: global label_counter diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index d6688547d7..2296d090f5 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -116,9 +116,7 @@ def _handle_self_call( goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto return_buf = goto_ir.args[1] # return buffer - ret_args = [ - IRLabel(target_label), - ] + ret_args = [IRLabel(target_label)] for arg in args_ir: if arg.is_literal: @@ -602,22 +600,10 @@ def _convert_ir_basicblock( else: ret_var = ir.args[1] if func_t.return_type.memory_bytes_required > 32: - inst = IRInstruction( - "ret", - [ - symbols["return_buffer"], - symbols["return_pc"], - ], - ) + inst = IRInstruction("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"], - ], - ) + inst = IRInstruction("ret", [ret_by_value, symbols["return_pc"]]) ctx.get_basic_block().append_instruction(inst) From a6c8a976375d1eb6ce0052dc4d1ad517e656a69d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 24 Oct 2023 00:13:33 +0300 Subject: [PATCH 256/471] cleanups, code style --- vyper/codegen/dfg.py | 9 ++++----- vyper/codegen/ir.py | 1 - vyper/ir/ir_to_bb_pass.py | 24 +++++++++--------------- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 35ce2e669b..ed0faab404 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -105,7 +105,7 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: def _compute_inst_dup_requirements_r( - ctx: IRFunction, inst: IRInstruction, visited: OrderedSet, last_seen: dict + ctx: IRFunction, inst: IRInstruction, visited: OrderedSet, last_seen: dict[str, IRInstruction] ): for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): @@ -126,13 +126,12 @@ def _compute_inst_dup_requirements_r( target = ctx.dfg_outputs[op.value] if target.parent != inst.parent: continue - old_last_seen = last_seen.copy() _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) for op in inst.get_input_operands(): - l = last_seen.get(op.value, None) - if l: - l.dup_requirements.add(op) + inst = last_seen.get(op.value, None) + if inst: + inst.dup_requirements.add(op) last_seen[op.value] = inst return diff --git a/vyper/codegen/ir.py b/vyper/codegen/ir.py index 561152934b..bec1257c07 100644 --- a/vyper/codegen/ir.py +++ b/vyper/codegen/ir.py @@ -3,7 +3,6 @@ from vyper.codegen.dfg import convert_ir_to_dfg from vyper.codegen.ir_function import IRFunctionBase from vyper.codegen.ir_node import IRnode -from vyper.codegen.ir_pass_dft import ir_pass_dft from vyper.compiler.settings import OptimizationLevel from vyper.ir.bb_optimizer import ( calculate_in_set, diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 2296d090f5..b931eb2004 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,4 +1,4 @@ -from typing import Optional, Union +from typing import Optional from vyper.codegen.dfg import generate_evm from vyper.codegen.ir_basicblock import ( @@ -16,10 +16,6 @@ from vyper.ir.compile_ir import is_mem_sym, is_symbol from vyper.semantics.types.function import ContractFunctionT from vyper.utils import OrderedSet, MemoryPositions -from vyper.codegen.dfg import convert_ir_to_dfg -from vyper.codegen.ir_pass_dft import ir_pass_dft -from vyper.codegen.ir_pass_constant_propagation import ir_pass_constant_propagation - BINARY_IR_INSTRUCTIONS = [ "eq", @@ -227,10 +223,12 @@ def _convert_ir_basicblock( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, - variables: OrderedSet = {}, - allocated_variables: dict[str, IRVariable] = {}, + variables: OrderedSet, + allocated_variables: dict[str, IRVariable], ) -> Optional[IRVariable]: global _break_target, _continue_target + variables = variables or OrderedSet() + allocated_variables = allocated_variables or {} frame_info = ir.passthrough_metadata.get("frame_info", None) if frame_info is not None: @@ -526,7 +524,7 @@ def _convert_ir_basicblock( inst = IRInstruction("jmp", [label]) ctx.get_basic_block().append_instruction(inst) return - if func_t.return_type == None: + if func_t.return_type is None: inst = IRInstruction("stop", []) ctx.get_basic_block().append_instruction(inst) return @@ -585,7 +583,7 @@ def _convert_ir_basicblock( if last_ir.value > 32: inst = IRInstruction("return", [last_ir, ret_ir]) else: - ret_buf = IRLiteral(128) ## TODO: need allocator + 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]) @@ -775,7 +773,8 @@ def emit_body_block(): sym = ir.args[0] start = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) end = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) - bound = _convert_ir_basicblock(ctx, ir.args[3], symbols, variables, allocated_variables) + # "bound" is not used + _ = _convert_ir_basicblock(ctx, ir.args[3], symbols, variables, allocated_variables) body = ir.args[4] entry_block = ctx.get_basic_block() @@ -814,9 +813,7 @@ def emit_body_block(): # Do a dry run to get the symbols needing phi nodes start_syms = symbols.copy() ctx.append_basic_block(body_block) - old_counters = ctx.last_variable, ctx.last_label emit_body_block() - # ctx.last_variable, ctx.last_label = old_counters end_syms = symbols.copy() diff_syms = _get_symbols_common(start_syms, end_syms) @@ -835,9 +832,6 @@ def emit_body_block(): body_block.update_operands(replacements) - # body_block.clear_instructions() - # emit_body_block() - body_end = ctx.get_basic_block() if body_end.is_terminal() is False: body_end.append_instruction(IRInstruction("jmp", [jump_up_block.label])) From 99df45ed1a6a109d317870b672d589173e0a964c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 24 Oct 2023 00:14:10 +0300 Subject: [PATCH 257/471] isort --- vyper/codegen/dfg.py | 4 ++-- vyper/codegen/ir.py | 5 +++-- vyper/compiler/phases.py | 2 +- vyper/ir/bb_optimizer.py | 3 +-- vyper/ir/ir_to_bb_pass.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index ed0faab404..f66bd0e2a6 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -1,9 +1,9 @@ from vyper.codegen.ir_basicblock import ( IRBasicBlock, IRInstruction, - IRVariable, - IRValueBase, IRLabel, + IRValueBase, + IRVariable, ) from vyper.codegen.ir_function import IRFunction from vyper.compiler.utils import StackMap diff --git a/vyper/codegen/ir.py b/vyper/codegen/ir.py index bec1257c07..5c7984ae6a 100644 --- a/vyper/codegen/ir.py +++ b/vyper/codegen/ir.py @@ -1,15 +1,16 @@ from typing import Optional -from vyper.codegen.ir_pass_constant_propagation import ir_pass_constant_propagation + from vyper.codegen.dfg import convert_ir_to_dfg from vyper.codegen.ir_function import IRFunctionBase from vyper.codegen.ir_node import IRnode +from vyper.codegen.ir_pass_constant_propagation import ir_pass_constant_propagation from vyper.compiler.settings import OptimizationLevel from vyper.ir.bb_optimizer import ( calculate_in_set, calculate_liveness, ir_pass_optimize_empty_blocks, - ir_pass_remove_unreachable_blocks, ir_pass_optimize_unused_variables, + ir_pass_remove_unreachable_blocks, ) from vyper.ir.ir_to_bb_pass import convert_ir_basicblock diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 1c93470212..7be68ae8b4 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -7,12 +7,12 @@ from vyper.codegen import module from vyper.codegen.core import anchor_opt_level from vyper.codegen.global_context import GlobalContext +from vyper.codegen.ir import generate_ir from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel, Settings from vyper.exceptions import StructureException from vyper.ir import compile_ir, optimizer from vyper.ir.ir_to_bb_pass import generate_assembly_experimental -from vyper.codegen.ir import generate_ir from vyper.semantics import set_data_positions, validate_semantics from vyper.semantics.types.function import ContractFunctionT from vyper.typing import InterfaceImports, StorageLayout diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 7bc99af8a1..5ed731d5d8 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -5,8 +5,7 @@ IRLabel, ) from vyper.codegen.ir_function import IRFunction -from vyper.utils import OrderedSet -from vyper.utils import ir_pass +from vyper.utils import OrderedSet, ir_pass def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index b931eb2004..b9de7ac1e6 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -15,7 +15,7 @@ from vyper.evm.opcodes import get_opcodes from vyper.ir.compile_ir import is_mem_sym, is_symbol from vyper.semantics.types.function import ContractFunctionT -from vyper.utils import OrderedSet, MemoryPositions +from vyper.utils import MemoryPositions, OrderedSet BINARY_IR_INSTRUCTIONS = [ "eq", From 3308fbeea37d21984e7137aa7854e370580aba7e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 24 Oct 2023 00:33:39 +0300 Subject: [PATCH 258/471] type checks --- vyper/codegen/ir_function.py | 2 +- vyper/codegen/ir_node.py | 4 ++++ vyper/codegen/ir_pass_dft.py | 10 +++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index f306d64a1c..3fec15ff73 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -30,7 +30,7 @@ class IRFunction(IRFunctionBase): basic_blocks: list["IRBasicBlock"] data_segment: list["IRInstruction"] dfg_inputs = {str: [IRInstruction]} - dfg_outputs = {str: IRInstruction} + dfg_outputs = dict[str, IRInstruction] last_label: int last_variable: int diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index 72801ecdcf..69b10bc21b 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -173,6 +173,8 @@ class IRnode: value: Union[str, int] is_self_call: bool passthrough_metadata: dict[str, Any] + func_ir: Any + common_ir: Any def __init__( self, @@ -207,6 +209,8 @@ def __init__( self.as_hex = AS_HEX_DEFAULT self.is_self_call = is_self_call self.passthrough_metadata = passthrough_metadata or {} + self.func_ir = None + self.common_ir = None def _check(condition, err): if not condition: diff --git a/vyper/codegen/ir_pass_dft.py b/vyper/codegen/ir_pass_dft.py index 0968219205..d0ad80f546 100644 --- a/vyper/codegen/ir_pass_dft.py +++ b/vyper/codegen/ir_pass_dft.py @@ -5,15 +5,15 @@ visited_instructions = OrderedSet() -def _emit_operands_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction): +def _emit_operands_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction) -> None: for op in inst.get_input_operands(): - target = ctx.dfg_outputs.get(op.value, None) + target = ctx.dfg_outputs.get(op.value) if target is None: continue _process_instruction(ctx, bb, target) -def _process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction): +def _process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction) -> None: global visited_instructions if inst in visited_instructions: return @@ -22,7 +22,7 @@ def _process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction) bb.append_instruction(inst) -def _process_basic_block(ctx: IRFunction, bb: IRBasicBlock): +def _process_basic_block(ctx: IRFunction, bb: IRBasicBlock) -> None: ctx.append_basic_block(bb) instructions = bb.instructions bb.instructions = [] @@ -31,7 +31,7 @@ def _process_basic_block(ctx: IRFunction, bb: IRBasicBlock): @ir_pass -def ir_pass_dft(ctx: IRFunction): +def ir_pass_dft(ctx: IRFunction) -> None: global visited_instructions visited_instructions = OrderedSet() From 994d7429712f4bbf885558df6b576c01a90260e9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 24 Oct 2023 00:36:34 +0300 Subject: [PATCH 259/471] typofix --- vyper/codegen/dfg.py | 19 +++++++++++-------- vyper/codegen/ir_function.py | 6 +++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index f66bd0e2a6..3d645c3aef 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -75,8 +75,8 @@ def __init__(self, value: IRInstruction | IRValueBase): def convert_ir_to_dfg(ctx: IRFunction) -> None: # Reset DFG - ctx.dfg_inputs = {} - ctx.dfg_outputs = {} + ctx.dfg_inputs = dict() + ctx.dfg_outputs = dict() for bb in ctx.basic_blocks: for inst in bb.instructions: inst.dup_requirements = OrderedSet() @@ -105,8 +105,11 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: def _compute_inst_dup_requirements_r( - ctx: IRFunction, inst: IRInstruction, visited: OrderedSet, last_seen: dict[str, IRInstruction] -): + ctx: IRFunction, + inst: IRInstruction, + visited: OrderedSet, + last_seen: dict, +) -> None: for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): if target.parent != inst.parent: @@ -129,9 +132,9 @@ def _compute_inst_dup_requirements_r( _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) for op in inst.get_input_operands(): - inst = last_seen.get(op.value, None) - if inst: - inst.dup_requirements.add(op) + target = last_seen.get(op.value, None) + if target: + target.dup_requirements.add(op) last_seen[op.value] = inst return @@ -146,7 +149,7 @@ def _compute_dup_requirements(ctx: IRFunction) -> None: fen += 1 visited = OrderedSet() - last_seen = {} + last_seen = dict() for inst in bb.instructions: _compute_inst_dup_requirements_r(ctx, inst, visited, last_seen) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 3fec15ff73..1cf1848f48 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -27,9 +27,9 @@ class IRFunction(IRFunctionBase): Function that contains basic blocks. """ - basic_blocks: list["IRBasicBlock"] - data_segment: list["IRInstruction"] - dfg_inputs = {str: [IRInstruction]} + basic_blocks: list[IRBasicBlock] + data_segment: list[IRInstruction] + dfg_inputs = dict[str, IRInstruction] dfg_outputs = dict[str, IRInstruction] last_label: int last_variable: int From ddf4bb0b001b6290c619c69870b86d5c9368cfc9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 24 Oct 2023 12:35:33 +0300 Subject: [PATCH 260/471] amend unterminated blocks with jmps --- vyper/ir/ir_to_bb_pass.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index b9de7ac1e6..161b7bcb3f 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -58,6 +58,10 @@ def convert_ir_basicblock(ir: IRnode) -> IRFunction: global_function = IRFunction(IRLabel("global")) _convert_ir_basicblock(global_function, ir, {}, {}, {}) + for i, bb in enumerate(global_function.basic_blocks): + if bb.is_terminated is False and i < len(global_function.basic_blocks) - 1: + bb.append_instruction(IRInstruction("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)])) @@ -344,6 +348,8 @@ def _convert_ir_basicblock( argsOffsetVar.mem_type = IRVariable.MemType.MEMORY argsOffsetVar.mem_addr = addr argsOffsetVar.offset = 32 - 4 if argsOffset.value > 0 else 0 + else: + argsOffsetVar = argsOffset retVar = ctx.get_next_variable(IRVariable.MemType.MEMORY, retOffset.value) symbols[f"&{retOffset.value}"] = retVar From d63d73a17f381a047f502010f7bba19955ba932d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 24 Oct 2023 18:36:14 +0300 Subject: [PATCH 261/471] fix typo --- vyper/ir/compile_ir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 3d2e93fa5d..fa38b07a6e 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -1010,7 +1010,7 @@ def _stack_peephole_opts(assembly): if assembly[i : i + 2] == ["SWAP1", "SWAP1"]: changed = True del assembly[i : i + 2] - if assembly[i] == "SWAP1" and assembly[i + 1] is COMMUTATIVE_OPS: + if assembly[i] == "SWAP1" and assembly[i + 1] in COMMUTATIVE_OPS: changed = True del assembly[i] i += 1 From 5eab14737537a3b6f12cf19e25dd443baa988185 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 24 Oct 2023 18:45:35 +0300 Subject: [PATCH 262/471] linter fixes --- vyper/codegen/dfg.py | 2 -- vyper/ir/bb_optimizer.py | 1 + vyper/ir/ir_to_bb_pass.py | 13 ++----------- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 3d645c3aef..a48ee19a52 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -268,8 +268,6 @@ def _generate_evm_for_basicblock_r( for bb in basicblock.out_set: _generate_evm_for_basicblock_r(ctx, asm, bb, stack_map.copy()) - return asm - # TODO: refactor this label_counter = 0 diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 5ed731d5d8..28db1a043b 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -72,6 +72,7 @@ def calculate_in_set(ctx: IRFunction) -> None: bb.out_vars = OrderedSet() deploy_bb = None + after_deploy_bb = None for i, bb in enumerate(ctx.basic_blocks): if bb.instructions[0].opcode == "deploy": deploy_bb = bb diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 161b7bcb3f..907fd7e7d8 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -319,6 +319,7 @@ def _convert_ir_basicblock( ctx, ir.args[idx + 1], symbols, variables, allocated_variables ) + value = None if ir.value == "call": value = _convert_ir_basicblock( ctx, ir.args[idx + 2], symbols, variables, allocated_variables @@ -455,7 +456,7 @@ def _convert_ir_basicblock( ctx, ir.args[2], with_symbols, variables, allocated_variables ) # body elif ir.value == "goto": - return _append_jmp(ctx, IRLabel(ir.args[0].value)) + _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]) @@ -693,15 +694,6 @@ def _convert_ir_basicblock( 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) - # if sym_ir.is_literal is False: - # sym = symbols.get(f"{sym_ir.value}", None) - # if sym_ir.value == "return_buffer": - # if sym is None: - # new_var = ctx.append_instruction("store", [arg_1], sym_ir) - # symbols[f"{sym_ir.value}"] = new_var - # return new_var - # return sym - var = _get_variable_from_address(variables, sym_ir.value) if sym_ir.is_literal else None if var is not None: if var.size > 32: @@ -862,7 +854,6 @@ def emit_body_block(): ctx.get_basic_block().append_instruction(inst) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) elif ir.value == "continue": - pass assert _continue_target is not None, "Continue with no contrinue target" inst = IRInstruction("jmp", [_continue_target.label]) ctx.get_basic_block().append_instruction(inst) From 87fa45c1e9d33cb14c221c23f36e19af3e9f9fff Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 24 Oct 2023 18:52:34 +0300 Subject: [PATCH 263/471] remove unused code --- vyper/codegen/ir_basicblock.py | 3 --- vyper/codegen/ir_pass_constant_propagation.py | 3 --- vyper/ir/ir_to_bb_pass.py | 2 -- 3 files changed, 8 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 46e44f8dd8..179924f0ad 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -196,9 +196,6 @@ def __repr__(self) -> str: if self.annotation: s += f" <{self.annotation}>" - # if len(self.dup_requirements): - # return f"{s: <30} # {self.dup_requirements}" - if self.liveness: return f"{s: <30} # {self.liveness}" diff --git a/vyper/codegen/ir_pass_constant_propagation.py b/vyper/codegen/ir_pass_constant_propagation.py index ea879df617..77e68d28bb 100644 --- a/vyper/codegen/ir_pass_constant_propagation.py +++ b/vyper/codegen/ir_pass_constant_propagation.py @@ -9,8 +9,5 @@ def _process_basic_block(ctx: IRFunction, bb: IRBasicBlock): @ir_pass def ir_pass_constant_propagation(ctx: IRFunction): - global visited_instructions - visited_instructions = OrderedSet() - for bb in ctx.basic_blocks: _process_basic_block(ctx, bb) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 907fd7e7d8..583a6d0095 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -276,7 +276,6 @@ def _convert_ir_basicblock( elif ir.value in ["pass", "stop", "return"]: pass - elif ir.value == "deploy": memsize = ir.args[0].value ir_runtime = ir.args[1] @@ -603,7 +602,6 @@ def _convert_ir_basicblock( if func_t.return_type is None: inst = IRInstruction("ret", [symbols["return_pc"]]) else: - ret_var = ir.args[1] if func_t.return_type.memory_bytes_required > 32: inst = IRInstruction("ret", [symbols["return_buffer"], symbols["return_pc"]]) else: From c56f67c2e83f7513addd63253e57ab9ecabf3cb2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 25 Oct 2023 00:32:25 +0300 Subject: [PATCH 264/471] remove unnecessary defaults --- vyper/ir/ir_to_bb_pass.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 583a6d0095..21d0667caf 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -231,8 +231,6 @@ def _convert_ir_basicblock( allocated_variables: dict[str, IRVariable], ) -> Optional[IRVariable]: global _break_target, _continue_target - variables = variables or OrderedSet() - allocated_variables = allocated_variables or {} frame_info = ir.passthrough_metadata.get("frame_info", None) if frame_info is not None: From 3dceb384865faa38b07f6bd3d5dd695ecdd4886e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 25 Oct 2023 17:18:30 +0300 Subject: [PATCH 265/471] no poping required with new scheduling --- vyper/codegen/dfg.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index a48ee19a52..eb13ddd85c 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -242,20 +242,6 @@ def _generate_evm_for_basicblock_r( asm.append(f"_sym_{basicblock.label}") asm.append("JUMPDEST") - # values to pop from stack - in_vars = OrderedSet() - for in_bb in basicblock.in_set: - in_vars |= in_bb.out_vars.difference(basicblock.in_vars_for(in_bb)) - - for var in in_vars: - depth = stack_map.get_depth_in(IRValueBase(var.value)) - if depth is StackMap.NOT_IN_STACK: - continue - if depth != 0: - stack_map.swap(asm, depth) - stack_map.pop() - asm.append("POP") - fen = 0 for inst in basicblock.instructions: inst.fen = fen From 3504d807986ac7efa229ef7fccb74a84fb952158 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 26 Oct 2023 10:40:18 +0300 Subject: [PATCH 266/471] remove return type and statement _emit_input_operants no longer returns anything --- vyper/codegen/dfg.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index eb13ddd85c..3eecd2999a 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -434,7 +434,7 @@ def _emit_input_operands( inst: IRInstruction, ops: list[IRValueBase], stack_map: StackMap, -) -> OrderedSet[IRValueBase]: +): for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -453,5 +453,3 @@ def _emit_input_operands( if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") - - return From fb273dbc9d1d440dfaf6c12bf3f10126cfa822e4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 26 Oct 2023 17:11:18 +0300 Subject: [PATCH 267/471] remove IRFunctionBase --- vyper/codegen/ir.py | 4 ++-- vyper/codegen/ir_function.py | 18 ++++-------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/vyper/codegen/ir.py b/vyper/codegen/ir.py index 5c7984ae6a..dfbefcee5a 100644 --- a/vyper/codegen/ir.py +++ b/vyper/codegen/ir.py @@ -1,7 +1,7 @@ from typing import Optional from vyper.codegen.dfg import convert_ir_to_dfg -from vyper.codegen.ir_function import IRFunctionBase +from vyper.codegen.ir_function import IRFunction from vyper.codegen.ir_node import IRnode from vyper.codegen.ir_pass_constant_propagation import ir_pass_constant_propagation from vyper.compiler.settings import OptimizationLevel @@ -15,7 +15,7 @@ from vyper.ir.ir_to_bb_pass import convert_ir_basicblock -def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunctionBase: +def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: # Convert "old" IR to "new" IR ctx = convert_ir_basicblock(ir) diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 1cf1848f48..b7c5cad1f1 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -9,24 +9,13 @@ ) -class IRFunctionBase: +class IRFunction: """ - Base class for IRFunction + Function that contains basic blocks. """ name: IRLabel # symbol name args: list - - def __init__(self, name: IRLabel, args: list = []) -> None: - self.name = name - self.args = args - - -class IRFunction(IRFunctionBase): - """ - Function that contains basic blocks. - """ - basic_blocks: list[IRBasicBlock] data_segment: list[IRInstruction] dfg_inputs = dict[str, IRInstruction] @@ -35,7 +24,8 @@ class IRFunction(IRFunctionBase): last_variable: int def __init__(self, name: IRLabel) -> None: - super().__init__(name) + self.name = name + self.args = [] self.basic_blocks = [] self.data_segment = [] self.last_label = 0 From 980642b1e595ddd401539221a99be0b70ba70561 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 26 Oct 2023 10:43:30 -0400 Subject: [PATCH 268/471] add some review comments, rename in_set/out_set to cfg_in/cfg_out --- vyper/codegen/dfg.py | 14 +++---- vyper/codegen/ir.py | 6 +-- vyper/codegen/ir_basicblock.py | 71 +++++++++++++++++++++------------- vyper/codegen/ir_function.py | 7 ++-- vyper/ir/bb_optimizer.py | 25 ++++++------ 5 files changed, 71 insertions(+), 52 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 3eecd2999a..6e3837b3fc 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -160,15 +160,15 @@ def _compute_dup_requirements(ctx: IRFunction) -> None: inst.dup_requirements.add(op) -visited_instructions = {IRInstruction} -visited_basicblocks = {IRBasicBlock} +visited_instructions = None # {IRInstruction} +visited_basicblocks = None # {IRBasicBlock} def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: global visited_instructions, visited_basicblocks asm = [] - visited_instructions = set() - visited_basicblocks = set() + visited_instructions = OrderedSet() + visited_basicblocks = OrderedSet() _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackMap()) @@ -251,7 +251,7 @@ def _generate_evm_for_basicblock_r( for inst in basicblock.instructions: asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) - for bb in basicblock.out_set: + for bb in basicblock.cfg_out: _generate_evm_for_basicblock_r(ctx, asm, bb, stack_map.copy()) @@ -295,7 +295,7 @@ def _generate_evm_for_instruction_r( else: operands = inst.operands - if opcode == "select": + if opcode == "select": # REVIEW: maybe call this 'phi' ret = inst.get_output_operands()[0] inputs = inst.get_input_operands() depth = stack_map.get_depth_in(inputs) @@ -313,7 +313,7 @@ def _generate_evm_for_instruction_r( # Step 3: Reorder stack if opcode in ["jnz", "jmp"]: - _, b = next(enumerate(inst.parent.out_set)) + _, b = next(enumerate(inst.parent.cfg_out)) target_stack = OrderedSet(b.in_vars_for(inst.parent)) _stack_reorder(assembly, stack_map, target_stack) diff --git a/vyper/codegen/ir.py b/vyper/codegen/ir.py index dfbefcee5a..9f36df3768 100644 --- a/vyper/codegen/ir.py +++ b/vyper/codegen/ir.py @@ -6,7 +6,7 @@ from vyper.codegen.ir_pass_constant_propagation import ir_pass_constant_propagation from vyper.compiler.settings import OptimizationLevel from vyper.ir.bb_optimizer import ( - calculate_in_set, + calculate_cfg_in, calculate_liveness, ir_pass_optimize_empty_blocks, ir_pass_optimize_unused_variables, @@ -31,14 +31,14 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF changes += ir_pass_optimize_unused_variables(ctx) - calculate_in_set(ctx) + calculate_cfg_in(ctx) calculate_liveness(ctx) convert_ir_to_dfg(ctx) changes += ir_pass_constant_propagation(ctx) # changes += ir_pass_dft(ctx) - calculate_in_set(ctx) + calculate_cfg_in(ctx) calculate_liveness(ctx) convert_ir_to_dfg(ctx) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 179924f0ad..f39d82c82f 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -65,9 +65,10 @@ class IRVariable(IRValueBase): """ offset: int = 0 + # REVIEW: make this toplevel definition MemType = Enum("MemType", ["OPERAND_STACK", "MEMORY"]) mem_type: MemType = MemType.OPERAND_STACK - mem_addr: int = -1 + mem_addr: int = -1 # REVIEW should this be None? def __init__( self, value: IRValueBaseValue, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: int = -1 @@ -81,6 +82,7 @@ def __init__( class IRLabel(IRValueBase): + # REVIEW: what do the values of is_symbol mean? """ IRLabel represents a label in IR. A label is a string that starts with a %. """ @@ -103,11 +105,14 @@ class IRInstruction: opcode: str volatile: bool operands: list[IRValueBase] + # REVIEW: rename to lhs? ret: Optional[IRValueBase] + # REVIEW: rename to source_info? dbg: Optional[IRDebugInfo] liveness: OrderedSet[IRVariable] dup_requirements: OrderedSet[IRVariable] parent: Optional["IRBasicBlock"] + # REVIEW: rename to `fence` fen: int annotation: Optional[str] @@ -119,6 +124,7 @@ def __init__( dbg: IRDebugInfo = None, ): self.opcode = opcode + # REVIEW nit: make this global definition self.volatile = opcode in [ "param", "alloca", @@ -168,9 +174,12 @@ def get_input_operands(self) -> list[IRValueBase]: """ return [op for op in self.operands if isinstance(op, IRVariable)] + # REVIEW suggestion: rename to `get_outputs` def get_output_operands(self) -> list[IRValueBase]: return [self.ret] if self.ret else [] + # REVIEW: rename to `replace_operands` + # use of `dict` here seems a bit weird (what is equality on operands?) def update_operands(self, replacements: dict) -> None: """ Update operands with replacements. @@ -228,8 +237,13 @@ class IRBasicBlock: label: IRLabel parent: "IRFunction" instructions: list[IRInstruction] - in_set: OrderedSet["IRBasicBlock"] - out_set: OrderedSet["IRBasicBlock"] + # REVIEW: "in_set" -> "cfg_in" + # (basic blocks which can jump to this basic block) + cfg_in: OrderedSet["IRBasicBlock"] + # REVIEW: "out_set" -> "cfg_out" + # (basic blocks which this basic block can jump to) + cfg_out: OrderedSet["IRBasicBlock"] + # stack items which this basic block produces out_vars: OrderedSet[IRVariable] def __init__(self, label: IRLabel, parent: "IRFunction") -> None: @@ -237,39 +251,41 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.label = label self.parent = parent self.instructions = [] - self.in_set = OrderedSet() - self.out_set = OrderedSet() + self.cfg_in = OrderedSet() + self.cfg_out = OrderedSet() self.out_vars = OrderedSet() - def add_in(self, bb: "IRBasicBlock") -> None: - self.in_set.add(bb) + def add_cfg_in(self, bb: "IRBasicBlock") -> None: + self.cfg_in.add(bb) - def union_in(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: - self.in_set = self.in_set.union(bb_set) + def union_cfg_in(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: + self.cfg_in = self.cfg_in.union(bb_set) - def remove_in(self, bb: "IRBasicBlock") -> None: - self.in_set.remove(bb) + def remove_cfg_in(self, bb: "IRBasicBlock") -> None: + self.cfg_in.remove(bb) - def add_out(self, bb: "IRBasicBlock") -> None: - self.out_set.add(bb) + def add_cfg_out(self, bb: "IRBasicBlock") -> None: + self.cfg_out.add(bb) - def union_out(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: - self.out_set = self.out_set.union(bb_set) + def union_cfg_out(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: + self.cfg_out = self.cfg_out.union(bb_set) - def remove_out(self, bb: "IRBasicBlock") -> None: - self.out_set.remove(bb) + def remove_cfg_out(self, bb: "IRBasicBlock") -> None: + self.cfg_out.remove(bb) - def in_vars_for(self, bb: "IRBasicBlock" = None) -> set[IRVariable]: + # calculate the input variables for the target bb + def in_vars_for(self, target: "IRBasicBlock" = None) -> OrderedSet[IRVariable]: + assert target is not None liveness = self.instructions[0].liveness.copy() - if bb: + if target: for inst in self.instructions: if inst.opcode == "select": - if inst.operands[0] == bb.label: + if inst.operands[0] == target.label: liveness.add(inst.operands[1]) if inst.operands[3] in liveness: liveness.remove(inst.operands[3]) - if inst.operands[2] == bb.label: + if inst.operands[2] == target.label: liveness.add(inst.operands[3]) if inst.operands[1] in liveness: liveness.remove(inst.operands[1]) @@ -278,7 +294,7 @@ def in_vars_for(self, bb: "IRBasicBlock" = None) -> set[IRVariable]: @property def is_reachable(self) -> bool: - return len(self.in_set) > 0 + return len(self.cfg_in) > 0 def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" @@ -321,6 +337,7 @@ def calculate_liveness(self) -> None: Compute liveness of each instruction in the basic block. """ liveness = self.out_vars.copy() + # REVIEW: use `reversed()` here for instruction in self.instructions[::-1]: ops = instruction.get_input_operands() liveness = liveness.union(OrderedSet.fromkeys(ops)) @@ -333,7 +350,7 @@ def calculate_liveness(self) -> None: liveness.remove(out) instruction.liveness = liveness - def get_liveness(self) -> set[IRVariable]: + def get_liveness(self) -> OrderedSet[IRVariable]: """ Get liveness of basic block. """ @@ -370,15 +387,15 @@ def get_next_instruction(self, inst: IRInstruction) -> IRInstruction: def copy(self): bb = IRBasicBlock(self.label, self.parent) bb.instructions = self.instructions.copy() - bb.in_set = self.in_set.copy() - bb.out_set = self.out_set.copy() + bb.cfg_in = self.cfg_in.copy() + bb.cfg_out = self.cfg_out.copy() bb.out_vars = self.out_vars.copy() return bb def __repr__(self) -> str: s = ( - f"{repr(self.label)}: IN={[bb.label for bb in self.in_set]}" - f" OUT={[bb.label for bb in self.out_set]} \n" + f"{repr(self.label)}: IN={[bb.label for bb in self.cfg_in]}" + f" OUT={[bb.label for bb in self.cfg_out]} \n" ) for instruction in self.instructions: s += f" {instruction}\n" diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index b7c5cad1f1..7e73042b0b 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -69,7 +69,7 @@ def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: """ Get basic blocks that contain label. """ - return [bb for bb in self.basic_blocks if basic_block.label in bb.in_set] + return [bb for bb in self.basic_blocks if basic_block.label in bb.cfg_in] def get_terminal_basicblocks(self) -> list[IRBasicBlock]: """ @@ -95,8 +95,9 @@ def remove_unreachable_blocks(self) -> int: new_basic_blocks = [] for bb in self.basic_blocks: if not bb.is_reachable and bb.label.value != "global": - for bb2 in bb.out_set: - bb2.in_set.remove(bb) + for bb2 in bb.cfg_out: + # REVIEW: use cfg_remove_in + bb2.cfg_in.remove(bb) removed += 1 else: new_basic_blocks.append(bb) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 28db1a043b..28d00e33a5 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -62,13 +62,13 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> int: return count -def calculate_in_set(ctx: IRFunction) -> None: +def calculate_cfg_in(ctx: IRFunction) -> None: """ - Calculate in set for each basic block. + Calculate (cfg) inputs for each basic block. """ for bb in ctx.basic_blocks: - bb.in_set = OrderedSet() - bb.out_set = OrderedSet() + bb.cfg_in = OrderedSet() + bb.cfg_out = OrderedSet() bb.out_vars = OrderedSet() deploy_bb = None @@ -83,14 +83,14 @@ def calculate_in_set(ctx: IRFunction) -> None: entry_block = after_deploy_bb has_constructor = True if ctx.basic_blocks[0].instructions[0].opcode != "deploy" else False if has_constructor: - deploy_bb.add_in(ctx.basic_blocks[0]) - entry_block.add_in(deploy_bb) + 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: if "selector_bucket_" in bb.label.value or bb.label.value == "fallback": - bb.add_in(entry_block) + bb.add_cfg_in(entry_block) for bb in ctx.basic_blocks: assert len(bb.instructions) > 0, "Basic block should not be empty" @@ -103,12 +103,12 @@ def calculate_in_set(ctx: IRFunction) -> None: if inst.opcode in ["jmp", "jnz", "call", "staticcall", "invoke", "deploy"]: ops = inst.get_label_operands() for op in ops: - ctx.get_basic_block(op.value).add_in(bb) + ctx.get_basic_block(op.value).add_cfg_in(bb) # Fill in the "out" set for each basic block for bb in ctx.basic_blocks: - for in_bb in bb.in_set: - in_bb.add_out(bb) + for in_bb in bb.cfg_in: + in_bb.add_cfg_out(bb) def _reset_liveness(ctx: IRFunction) -> None: @@ -118,7 +118,8 @@ def _reset_liveness(ctx: IRFunction) -> None: def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: - for out_bb in bb.out_set: + for out_bb in bb.cfg_out: + # REVIEW: .get() already defaults to None if liveness_visited.get(bb, None) == out_bb: continue liveness_visited[bb] = out_bb @@ -137,7 +138,7 @@ def calculate_liveness(ctx: IRFunction) -> None: @ir_pass def ir_pass_optimize_empty_blocks(ctx: IRFunction) -> int: changes = _optimize_empty_basicblocks(ctx) - calculate_in_set(ctx) + calculate_cfg_in(ctx) return changes From 024d891183900ab56bb621650059e664a5862049 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 26 Oct 2023 10:45:19 -0400 Subject: [PATCH 269/471] remove None target for in_vars_for, add review comments --- vyper/codegen/ir_basicblock.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index f39d82c82f..67a2aec7f0 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -224,14 +224,14 @@ class IRBasicBlock: bb.append_instruction(IRInstruction("add", ["%0", "1"], "%1")) bb.append_instruction(IRInstruction("mul", ["%1", "2"], "%2")) - The label of a basic block is used to refer to it from other basic blocks in order - to branch to it. + The label of a basic block is used to refer to it from other basic blocks + in order to branch to it. The parent of a basic block is the function it belongs to. - The instructions of a basic block are executed sequentially, and the last instruction - of a basic block is always a terminator instruction, which is used to branch to other - basic blocks. + The instructions of a basic block are executed sequentially, and the last + instruction of a basic block is always a terminator instruction, which is + used to branch to other basic blocks. """ label: IRLabel @@ -274,21 +274,19 @@ def remove_cfg_out(self, bb: "IRBasicBlock") -> None: self.cfg_out.remove(bb) # calculate the input variables for the target bb - def in_vars_for(self, target: "IRBasicBlock" = None) -> OrderedSet[IRVariable]: - assert target is not None + def in_vars_for(self, target: "IRBasicBlock") -> OrderedSet[IRVariable]: liveness = self.instructions[0].liveness.copy() - if target: - for inst in self.instructions: - if inst.opcode == "select": - if inst.operands[0] == target.label: - liveness.add(inst.operands[1]) - if inst.operands[3] in liveness: - liveness.remove(inst.operands[3]) - if inst.operands[2] == target.label: - liveness.add(inst.operands[3]) - if inst.operands[1] in liveness: - liveness.remove(inst.operands[1]) + for inst in self.instructions: + if inst.opcode == "select": + if inst.operands[0] == target.label: + liveness.add(inst.operands[1]) + if inst.operands[3] in liveness: + liveness.remove(inst.operands[3]) + if inst.operands[2] == target.label: + liveness.add(inst.operands[3]) + if inst.operands[1] in liveness: + liveness.remove(inst.operands[1]) return liveness From a5df0080008acec4b1e58db5828618bfdd58319b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 26 Oct 2023 10:55:38 -0400 Subject: [PATCH 270/471] add more comments, rename TERMINATOR_INSTRUCTIONS --- vyper/codegen/ir_basicblock.py | 20 ++++++++++++++++---- vyper/codegen/ir_function.py | 2 +- vyper/ir/bb_optimizer.py | 4 ++-- vyper/ir/ir_to_bb_pass.py | 2 ++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 67a2aec7f0..a5dc98286c 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -3,8 +3,14 @@ from vyper.utils import OrderedSet -TERMINAL_IR_INSTRUCTIONS = ["ret", "revert"] -TERMINATOR_IR_INSTRUCTIONS = ["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"] + +# instructions which terminate instruction +# REVIEW: this seems dead +EXEC_TERMINATORS = ["return", "revert", "invalid", "stop"] + + +# instructions which can terminate a basic block +BB_TERMINATORS = ["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"] if TYPE_CHECKING: from vyper.codegen.ir_function import IRFunction @@ -109,6 +115,7 @@ class IRInstruction: ret: Optional[IRValueBase] # REVIEW: rename to source_info? dbg: Optional[IRDebugInfo] + # set of live variables at this instruction liveness: OrderedSet[IRVariable] dup_requirements: OrderedSet[IRVariable] parent: Optional["IRBasicBlock"] @@ -278,6 +285,8 @@ def in_vars_for(self, target: "IRBasicBlock") -> OrderedSet[IRVariable]: liveness = self.instructions[0].liveness.copy() for inst in self.instructions: + # REVIEW: might be nice if some of these instructions + # were more structured. if inst.opcode == "select": if inst.operands[0] == target.label: liveness.add(inst.operands[1]) @@ -307,6 +316,7 @@ def insert_instruction(self, instruction: IRInstruction, index: int) -> None: def clear_instructions(self) -> None: self.instructions = [] + # REVIEW: rename to replace_operands def update_operands(self, replacements: dict) -> None: """ Update operands with replacements. @@ -314,21 +324,23 @@ def update_operands(self, replacements: dict) -> None: for instruction in self.instructions: instruction.update_operands(replacements) + # REVIEW: this seems to be dead def is_terminal(self) -> bool: """ Check if the basic block is terminal, i.e. the last instruction is a terminator. """ assert len(self.instructions) > 0, "basic block must have at least one instruction" - return self.instructions[-1].opcode in TERMINAL_IR_INSTRUCTIONS + return self.instructions[-1].opcode in EXEC_TERMINATORS @property def is_terminated(self) -> bool: """ Check if the basic block is terminal, i.e. the last instruction is a terminator. """ + # REVIEW: should this be an assert (like `is_terminal()`)? if len(self.instructions) == 0: return False - return self.instructions[-1].opcode in TERMINATOR_IR_INSTRUCTIONS + return self.instructions[-1].opcode in BB_TERMINATORS def calculate_liveness(self) -> None: """ diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 7e73042b0b..1d2cb5b7f7 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -71,7 +71,7 @@ def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: """ return [bb for bb in self.basic_blocks if basic_block.label in bb.cfg_in] - def get_terminal_basicblocks(self) -> list[IRBasicBlock]: + def DEAD_get_terminal_basicblocks(self) -> list[IRBasicBlock]: """ Get basic blocks that contain label. """ diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 28d00e33a5..1614c2ca27 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -1,5 +1,5 @@ from vyper.codegen.ir_basicblock import ( - TERMINATOR_IR_INSTRUCTIONS, + BB_TERMINATORS, IRBasicBlock, IRInstruction, IRLabel, @@ -96,7 +96,7 @@ def calculate_cfg_in(ctx: IRFunction) -> None: assert len(bb.instructions) > 0, "Basic block should not be empty" last_inst = bb.instructions[-1] assert ( - last_inst.opcode in TERMINATOR_IR_INSTRUCTIONS + last_inst.opcode in BB_TERMINATORS ), "Last instruction should be a terminator" + str(bb) for inst in bb.instructions: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 21d0667caf..aea4265a46 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -827,6 +827,8 @@ def emit_body_block(): body_block.update_operands(replacements) body_end = ctx.get_basic_block() + # REVIEW: should be is_terminated()? + # if so, is_terminal() is a dead function if body_end.is_terminal() is False: body_end.append_instruction(IRInstruction("jmp", [jump_up_block.label])) From fded6146294731fcc2ec405cc0ec2ff1447ba144 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 26 Oct 2023 11:26:23 -0400 Subject: [PATCH 271/471] add review comments --- vyper/codegen/dfg.py | 5 +++++ vyper/compiler/utils.py | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 6e3837b3fc..c604982036 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -259,6 +259,7 @@ def _generate_evm_for_basicblock_r( label_counter = 0 +# REVIEW: would this be better as a class? def _generate_evm_for_instruction_r( ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap ) -> list[str]: @@ -266,8 +267,10 @@ def _generate_evm_for_instruction_r( for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): + # REVIEW: what does this line do? if target.parent != inst.parent: continue + # REVIEW: what does this line do? if target.fen != inst.fen: continue assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) @@ -298,6 +301,8 @@ def _generate_evm_for_instruction_r( if opcode == "select": # REVIEW: maybe call this 'phi' ret = inst.get_output_operands()[0] inputs = inst.get_input_operands() + # REVIEW: the special handling in get_depth_in for lists + # seems cursed, refactor depth = stack_map.get_depth_in(inputs) assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" to_be_replaced = stack_map.peek(depth) diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index fc86fdcf38..4056575260 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -49,9 +49,12 @@ def _expand_row(row): return result +# REVIEW: move this to vyper/ir/ or vyper/venom/ +# rename to StackModel class StackMap: NOT_IN_STACK = object() - stack_map: list[IRValueBase] + stack_map: list[IRValueBase] # REVIEW: rename to stack + # REVIEW: dead variable dependant_liveness: OrderedSet[IRValueBase] def __init__(self): @@ -93,12 +96,15 @@ def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: for i, stack_op in enumerate(self.stack_map[::-1]): if isinstance(stack_op, IRValueBase): + # REVIEW: handling literals this way seems a bit cursed, + # why not use IRLiteral, so it is always IRValueBase? if isinstance(op, str) and stack_op.value == op: return -i if isinstance(op, int) and stack_op.value == op: return -i if isinstance(op, IRValueBase) and stack_op.value == op.value: return -i + # REVIEW: this branch seems cursed elif isinstance(op, list) and stack_op in op: return -i From 21f36ea9391a3c3fb48d49264981f8a67fa19b8b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 26 Oct 2023 12:23:49 -0400 Subject: [PATCH 272/471] remove dead code --- vyper/codegen/ir_basicblock.py | 34 ---------------------------------- vyper/compiler/phases.py | 2 ++ 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index a5dc98286c..8de5e75b32 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -360,40 +360,6 @@ def calculate_liveness(self) -> None: liveness.remove(out) instruction.liveness = liveness - def get_liveness(self) -> OrderedSet[IRVariable]: - """ - Get liveness of basic block. - """ - return self.instructions[0].liveness - - def get_last_used_operands(self, _inst: IRInstruction) -> OrderedSet[IRVariable]: - """ - Get last used operands of instruction. - """ - for i, inst in enumerate(self.instructions[:-1]): - if inst == _inst: - next_liveness = ( - self.instructions[i + 1].liveness - if i + 1 < len(self.instructions) - else OrderedSet() - ) - last_used = inst.liveness.difference(next_liveness) - return last_used - # Last instruction looksup into branch out basic blocks - if self.instructions[-1] == _inst: - last_used = _inst.liveness.difference(self.out_vars) - return last_used - return OrderedSet() - - def get_next_instruction(self, inst: IRInstruction) -> IRInstruction: - """ - Get next instruction after inst. - """ - for i, instruction in enumerate(self.instructions[:-1]): - if instruction == inst: - return self.instructions[i + 1] - return None - def copy(self): bb = IRBasicBlock(self.label, self.parent) bb.instructions = self.instructions.copy() diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 7be68ae8b4..bda00640d8 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -84,6 +84,8 @@ def __init__( experimental_codegen: bool, optional Use experimental codegen. Defaults to False """ + # to force experimental codegen, uncomment: + # experimental_codegen = True self.contract_name = contract_name self.source_code = source_code self.interface_codes = interface_codes From 9cf815ce2ef06224d94f5295f375c210e098a7e6 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Oct 2023 11:36:07 -0400 Subject: [PATCH 273/471] rename `in_vars_for` to `in_vars_from` --- vyper/codegen/dfg.py | 7 ++++--- vyper/codegen/ir_basicblock.py | 9 +++++---- vyper/ir/bb_optimizer.py | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index c604982036..5fb4d77191 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -219,7 +219,7 @@ def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueB op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) depth = stack_map.get_depth_in(op) - assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" + assert depth is not StackMap.NOT_IN_STACK, f"{op} not in stack: {stack_map.stack_map}" is_in_place = depth == final_stack_depth if not is_in_place: @@ -318,8 +318,9 @@ def _generate_evm_for_instruction_r( # Step 3: Reorder stack if opcode in ["jnz", "jmp"]: - _, b = next(enumerate(inst.parent.cfg_out)) - target_stack = OrderedSet(b.in_vars_for(inst.parent)) + assert isinstance(inst.parent.cfg_out, OrderedSet) + b = next(iter(inst.parent.cfg_out)) + target_stack = OrderedSet(b.in_vars_from(inst.parent)) _stack_reorder(assembly, stack_map, target_stack) _stack_duplications(assembly, inst, stack_map, operands) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 8de5e75b32..48bb6e44b2 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -280,19 +280,20 @@ def union_cfg_out(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: def remove_cfg_out(self, bb: "IRBasicBlock") -> None: self.cfg_out.remove(bb) - # calculate the input variables for the target bb - def in_vars_for(self, target: "IRBasicBlock") -> OrderedSet[IRVariable]: + # calculate the input variables into self from source + def in_vars_from(self, source: "IRBasicBlock") -> OrderedSet[IRVariable]: liveness = self.instructions[0].liveness.copy() + assert isinstance(liveness, OrderedSet) for inst in self.instructions: # REVIEW: might be nice if some of these instructions # were more structured. if inst.opcode == "select": - if inst.operands[0] == target.label: + if inst.operands[0] == source.label: liveness.add(inst.operands[1]) if inst.operands[3] in liveness: liveness.remove(inst.operands[3]) - if inst.operands[2] == target.label: + if inst.operands[2] == source.label: liveness.add(inst.operands[3]) if inst.operands[1] in liveness: liveness.remove(inst.operands[1]) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 1614c2ca27..4ad6b226e2 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -124,7 +124,7 @@ def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: continue liveness_visited[bb] = out_bb _calculate_liveness(out_bb, liveness_visited) - in_vars = out_bb.in_vars_for(bb) + in_vars = out_bb.in_vars_from(bb) bb.out_vars = bb.out_vars.union(in_vars) bb.calculate_liveness() From 5b430391a92ae9a8aa5e918870c64a9537da0c8a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Oct 2023 11:38:37 -0400 Subject: [PATCH 274/471] add some OrderedSet hygiene --- vyper/ir/bb_optimizer.py | 5 +++-- vyper/ir/ir_to_bb_pass.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 4ad6b226e2..451ed153ee 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -117,7 +117,8 @@ def _reset_liveness(ctx: IRFunction) -> None: inst.liveness = OrderedSet() -def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: +def _calculate_liveness(bb: IRBasicBlock, liveness_visited: OrderedSet) -> None: + assert isinstance(liveness_visited, OrderedSet) for out_bb in bb.cfg_out: # REVIEW: .get() already defaults to None if liveness_visited.get(bb, None) == out_bb: @@ -132,7 +133,7 @@ def _calculate_liveness(bb: IRBasicBlock, liveness_visited: set) -> None: def calculate_liveness(ctx: IRFunction) -> None: _reset_liveness(ctx) - _calculate_liveness(ctx.basic_blocks[0], {}) + _calculate_liveness(ctx.basic_blocks[0], OrderedSet()) @ir_pass diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index aea4265a46..8c1a1717e9 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -56,7 +56,7 @@ def generate_assembly_experimental( def convert_ir_basicblock(ir: IRnode) -> IRFunction: global_function = IRFunction(IRLabel("global")) - _convert_ir_basicblock(global_function, ir, {}, {}, {}) + _convert_ir_basicblock(global_function, ir, {}, OrderedSet(), {}) for i, bb in enumerate(global_function.basic_blocks): if bb.is_terminated is False and i < len(global_function.basic_blocks) - 1: @@ -230,6 +230,7 @@ def _convert_ir_basicblock( variables: OrderedSet, allocated_variables: dict[str, IRVariable], ) -> Optional[IRVariable]: + assert isinstance(variables, OrderedSet) global _break_target, _continue_target frame_info = ir.passthrough_metadata.get("frame_info", None) From 4554e6f7242c5da44a90ea3a3a6c1651cdf0453a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Oct 2023 12:42:49 -0400 Subject: [PATCH 275/471] fix stability of _get_symbols_common this affects liveness analysis downstream. --- vyper/ir/ir_to_bb_pass.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 8c1a1717e9..8c7c8f2442 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -45,7 +45,15 @@ def _get_symbols_common(a: dict, b: dict) -> dict: - return {k: [a[k], b[k]] for k in a.keys() & b.keys() if a[k] != b[k]} + ret = {} + # preserves the ordering in `a` + for k in a.keys(): + if k not in b: + continue + if a[k] == b[k]: + continue + ret[k] = a[k], b[k] + return ret def generate_assembly_experimental( @@ -413,7 +421,8 @@ def _convert_ir_basicblock( ) ) - for sym, val in _get_symbols_common(after_then_syms, after_else_syms).items(): + common_symbols = _get_symbols_common(after_then_syms, after_else_syms) + for sym, val in common_symbols.items(): ret = ctx.get_next_variable() old_var = symbols.get(sym, None) symbols[sym] = ret From 0eea288cb81251c1f36453ba55e6658a6678f30e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Oct 2023 12:43:48 -0400 Subject: [PATCH 276/471] add more review comments, OrderedSet hygiene --- vyper/ir/ir_to_bb_pass.py | 8 ++++++-- vyper/utils.py | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 8c7c8f2442..20587b5003 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -243,14 +243,17 @@ def _convert_ir_basicblock( frame_info = ir.passthrough_metadata.get("frame_info", None) if frame_info is not None: - vars = {v: True for v in frame_info.frame_vars.values()} # FIXME - variables |= vars + local_vars = OrderedSet(frame_info.frame_vars.values()) + variables |= local_vars + + assert isinstance(variables, OrderedSet) if ir.value in BINARY_IR_INSTRUCTIONS: return _convert_binary_op( ctx, ir, symbols, variables, allocated_variables, ir.value in ["sha3_64"] ) + # REVIEW: no need for .keys() elif ir.value in MAPPED_IR_INSTRUCTIONS.keys(): org_value = ir.value ir.value = MAPPED_IR_INSTRUCTIONS[ir.value] @@ -261,6 +264,7 @@ def _convert_ir_basicblock( elif ir.value in ["iszero", "ceil32", "calldataload", "extcodesize", "extcodehash", "balance"]: return _convert_ir_simple_node(ctx, ir, symbols, variables, allocated_variables) + # REVIEW: make this a global constant elif ir.value in [ "chainid", "basefee", diff --git a/vyper/utils.py b/vyper/utils.py index db5ec2c8c7..5018fc7192 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -44,7 +44,10 @@ def difference(self, other): return ret def union(self, other): - return self.__class__(self | other) + return self | other + + def __or__(self, other): + return self.__class__(super().__or__(other)) def copy(self): return self.__class__(super().copy()) From c10e96440f64b2ccc959e12d03bb187f95b320c6 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Oct 2023 13:04:50 -0400 Subject: [PATCH 277/471] simplify _stack_reorder add some tracing things in the comments --- vyper/codegen/dfg.py | 18 +++++++++--------- vyper/compiler/utils.py | 4 ++++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 5fb4d77191..773a96aaef 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -215,22 +215,22 @@ def _stack_duplications( def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase]) -> None: stack_ops = [x.value for x in stack_ops] + #print("ENTER reorder", stack_map.stack_map, stack_ops) + #start_len = len(assembly) for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) depth = stack_map.get_depth_in(op) assert depth is not StackMap.NOT_IN_STACK, f"{op} not in stack: {stack_map.stack_map}" - is_in_place = depth == final_stack_depth + if depth == final_stack_depth: + continue - if not is_in_place: - if final_stack_depth == 0 and depth != 0: - stack_map.swap(assembly, depth) - elif final_stack_depth != 0 and depth == 0: - stack_map.swap(assembly, final_stack_depth) - else: - stack_map.swap(assembly, depth) - stack_map.swap(assembly, final_stack_depth) + #print("trace", depth, final_stack_depth) + stack_map.swap(assembly, depth) + stack_map.swap(assembly, final_stack_depth) + #print("INSTRUCTIONS", assembly[start_len:]) + #print("EXIT reorder", stack_map.stack_map, stack_ops) def _generate_evm_for_basicblock_r( ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackMap diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 4056575260..572a339e6a 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -136,6 +136,10 @@ def swap(self, assembly: list[str], depth: int) -> None: """ Swaps the operand at the given depth in the stack map with the top of the stack. """ + # convenience, avoids branching in caller + if depth == 0: + return + assert depth < 0, "Cannot swap positive depth" assembly.append(f"SWAP{-depth}") self.stack_map[depth - 1], self.stack_map[-1] = ( From 2d90a0ce4cba246bce41e097d22befb95db8d00d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Oct 2023 13:36:20 -0400 Subject: [PATCH 278/471] always use experimental_codegen - for now --- vyper/compiler/phases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index bda00640d8..2cb6d1eeac 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -85,7 +85,7 @@ def __init__( Use experimental codegen. Defaults to False """ # to force experimental codegen, uncomment: - # experimental_codegen = True + experimental_codegen = True self.contract_name = contract_name self.source_code = source_code self.interface_codes = interface_codes From e1f4e4f60f4ec376ff6b888d071a0c0f545d9dee Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Oct 2023 13:52:27 -0400 Subject: [PATCH 279/471] fix an optimization --- vyper/ir/compile_ir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index fa38b07a6e..1d3df8becb 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -1010,7 +1010,7 @@ def _stack_peephole_opts(assembly): if assembly[i : i + 2] == ["SWAP1", "SWAP1"]: changed = True del assembly[i : i + 2] - if assembly[i] == "SWAP1" and assembly[i + 1] in COMMUTATIVE_OPS: + if assembly[i] == "SWAP1" and assembly[i + 1].lower() in COMMUTATIVE_OPS: changed = True del assembly[i] i += 1 From a97d3522f25807e20c84fd43c81e1236a7747e6e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Oct 2023 13:58:40 -0400 Subject: [PATCH 280/471] add more tracing --- vyper/codegen/dfg.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 773a96aaef..d62c57912e 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -215,8 +215,8 @@ def _stack_duplications( def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase]) -> None: stack_ops = [x.value for x in stack_ops] - #print("ENTER reorder", stack_map.stack_map, stack_ops) - #start_len = len(assembly) + # print("ENTER reorder", stack_map.stack_map, stack_ops) + # start_len = len(assembly) for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) @@ -276,6 +276,7 @@ def _generate_evm_for_instruction_r( assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) if inst in visited_instructions: + #print("seen:", inst) return assembly visited_instructions.add(inst) @@ -441,6 +442,7 @@ def _emit_input_operands( ops: list[IRValueBase], stack_map: StackMap, ): + #print("EMIT INPUTS FOR", inst) for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -453,6 +455,7 @@ def _emit_input_operands( assembly.extend([*PUSH(op.value)]) stack_map.push(op) continue + #print("RECURSE FOR", op, "TO:", ctx.dfg_outputs[op.value]) assembly = _generate_evm_for_instruction_r( ctx, assembly, ctx.dfg_outputs[op.value], stack_map ) From 7d4088e4d5be0dfecd5c900910f9ae97924fffb0 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Oct 2023 15:20:02 -0400 Subject: [PATCH 281/471] add some review comments --- vyper/codegen/dfg.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index d62c57912e..4c4f7795d0 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -75,7 +75,9 @@ def __init__(self, value: IRInstruction | IRValueBase): def convert_ir_to_dfg(ctx: IRFunction) -> None: # Reset DFG + # REVIEW: dfg inputs is all, flattened inputs to a given variable ctx.dfg_inputs = dict() + # REVIEW: dfg outputs is the instruction which produces a variable ctx.dfg_outputs = dict() for bb in ctx.basic_blocks: for inst in bb.instructions: @@ -113,6 +115,7 @@ def _compute_inst_dup_requirements_r( for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): if target.parent != inst.parent: + # REVIEW: produced by parent.out_vars continue if target.fen != inst.fen: continue @@ -215,8 +218,8 @@ def _stack_duplications( def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase]) -> None: stack_ops = [x.value for x in stack_ops] - # print("ENTER reorder", stack_map.stack_map, stack_ops) - # start_len = len(assembly) + #print("ENTER reorder", stack_map.stack_map, operands) + #start_len = len(assembly) for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) @@ -273,6 +276,9 @@ def _generate_evm_for_instruction_r( # REVIEW: what does this line do? if target.fen != inst.fen: continue + # REVIEW: I think it would be better to have an explicit step, + # `reorder instructions per DFG`, and then `generate_evm_for_instruction` + # does not need to recurse (or be co-recursive with `emit_input_operands`). assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) if inst in visited_instructions: @@ -325,6 +331,8 @@ def _generate_evm_for_instruction_r( _stack_reorder(assembly, stack_map, target_stack) _stack_duplications(assembly, inst, stack_map, operands) + + #print("(inst)", inst) _stack_reorder(assembly, stack_map, operands) # Step 4: Push instruction's return value to stack From 0bf2277408656efaba39faabc32f1fe7d33a2f8d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Oct 2023 15:20:08 -0400 Subject: [PATCH 282/471] clean up recursion into emit_instruction --- vyper/codegen/dfg.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 4c4f7795d0..7ba05f9969 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -464,8 +464,10 @@ def _emit_input_operands( stack_map.push(op) continue #print("RECURSE FOR", op, "TO:", ctx.dfg_outputs[op.value]) - assembly = _generate_evm_for_instruction_r( - ctx, assembly, ctx.dfg_outputs[op.value], stack_map + assembly.extend( + _generate_evm_for_instruction_r( + ctx, [], ctx.dfg_outputs[op.value], stack_map + ) ) if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) From b0ad061903aa4e9dad90f671eaf451d8087facd5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 13:27:06 +0200 Subject: [PATCH 283/471] remove no longer used is_terminal() --- vyper/codegen/ir_basicblock.py | 8 -------- vyper/codegen/ir_function.py | 6 ------ 2 files changed, 14 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 48bb6e44b2..2f9d429041 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -325,14 +325,6 @@ def update_operands(self, replacements: dict) -> None: for instruction in self.instructions: instruction.update_operands(replacements) - # REVIEW: this seems to be dead - def is_terminal(self) -> bool: - """ - Check if the basic block is terminal, i.e. the last instruction is a terminator. - """ - assert len(self.instructions) > 0, "basic block must have at least one instruction" - return self.instructions[-1].opcode in EXEC_TERMINATORS - @property def is_terminated(self) -> bool: """ diff --git a/vyper/codegen/ir_function.py b/vyper/codegen/ir_function.py index 1d2cb5b7f7..cbcba331ce 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/codegen/ir_function.py @@ -71,12 +71,6 @@ def get_basicblocks_in(self, basic_block: IRBasicBlock) -> list[IRBasicBlock]: """ return [bb for bb in self.basic_blocks if basic_block.label in bb.cfg_in] - def DEAD_get_terminal_basicblocks(self) -> list[IRBasicBlock]: - """ - Get basic blocks that contain label. - """ - return [bb for bb in self.basic_blocks if bb.is_terminal()] - def get_next_label(self) -> IRLabel: self.last_label += 1 return IRLabel(f"{self.last_label}") From 0e50f7874b7de2331971694fcd59f6b01da3273d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 13:27:27 +0200 Subject: [PATCH 284/471] correct typo --- vyper/ir/ir_to_bb_pass.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 20587b5003..c95b177a8e 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -841,9 +841,7 @@ def emit_body_block(): body_block.update_operands(replacements) body_end = ctx.get_basic_block() - # REVIEW: should be is_terminated()? - # if so, is_terminal() is a dead function - if body_end.is_terminal() is False: + if body_end.is_terminated() is False: body_end.append_instruction(IRInstruction("jmp", [jump_up_block.label])) jump_cond = IRInstruction("jmp", [increment_block.label]) From 3729aeea13e0d1de3f3dd4d180e718b673d6282e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 13:28:09 +0200 Subject: [PATCH 285/471] remove unused definition --- vyper/codegen/ir_basicblock.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index 2f9d429041..d809fbec02 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -3,12 +3,6 @@ from vyper.utils import OrderedSet - -# instructions which terminate instruction -# REVIEW: this seems dead -EXEC_TERMINATORS = ["return", "revert", "invalid", "stop"] - - # instructions which can terminate a basic block BB_TERMINATORS = ["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"] From 1fd8728aaa3189ba224c09377963dc597f86bd14 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 13:29:26 +0200 Subject: [PATCH 286/471] remove unneeded .keys() --- vyper/ir/ir_to_bb_pass.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index c95b177a8e..e52d99bb71 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -253,8 +253,7 @@ def _convert_ir_basicblock( ctx, ir, symbols, variables, allocated_variables, ir.value in ["sha3_64"] ) - # REVIEW: no need for .keys() - elif ir.value in MAPPED_IR_INSTRUCTIONS.keys(): + elif ir.value in MAPPED_IR_INSTRUCTIONS: org_value = ir.value ir.value = MAPPED_IR_INSTRUCTIONS[ir.value] new_var = _convert_binary_op(ctx, ir, symbols, variables, allocated_variables) From 24854b4fb683757c34c4a6e3ae9bd340a97c111b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 13:47:22 +0200 Subject: [PATCH 287/471] various small review tweaks --- vyper/codegen/dfg.py | 35 +++++++++++++++++----------------- vyper/codegen/ir_basicblock.py | 17 +++++++---------- vyper/compiler/utils.py | 3 --- vyper/ir/bb_optimizer.py | 9 ++++----- vyper/ir/ir_to_bb_pass.py | 2 +- 5 files changed, 29 insertions(+), 37 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 7ba05f9969..1632d57e81 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -82,7 +82,7 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: for inst in bb.instructions: inst.dup_requirements = OrderedSet() - inst.fen = -1 + inst.fence_id = -1 operands = inst.get_input_operands() operands.extend(inst.get_output_operands()) @@ -117,7 +117,7 @@ def _compute_inst_dup_requirements_r( if target.parent != inst.parent: # REVIEW: produced by parent.out_vars continue - if target.fen != inst.fen: + if target.fence_id != inst.fence_id: continue _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) @@ -147,7 +147,7 @@ def _compute_dup_requirements(ctx: IRFunction) -> None: fen = 0 for bb in ctx.basic_blocks: for inst in bb.instructions: - inst.fen = fen + inst.fence_id = fen if inst.volatile: fen += 1 @@ -164,7 +164,7 @@ def _compute_dup_requirements(ctx: IRFunction) -> None: visited_instructions = None # {IRInstruction} -visited_basicblocks = None # {IRBasicBlock} +visited_basicblocks = None # {IRBasicBlock} def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: @@ -218,8 +218,8 @@ def _stack_duplications( def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase]) -> None: stack_ops = [x.value for x in stack_ops] - #print("ENTER reorder", stack_map.stack_map, operands) - #start_len = len(assembly) + # print("ENTER reorder", stack_map.stack_map, operands) + # start_len = len(assembly) for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) @@ -228,12 +228,13 @@ def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueB if depth == final_stack_depth: continue - #print("trace", depth, final_stack_depth) + # print("trace", depth, final_stack_depth) stack_map.swap(assembly, depth) stack_map.swap(assembly, final_stack_depth) - #print("INSTRUCTIONS", assembly[start_len:]) - #print("EXIT reorder", stack_map.stack_map, stack_ops) + # print("INSTRUCTIONS", assembly[start_len:]) + # print("EXIT reorder", stack_map.stack_map, stack_ops) + def _generate_evm_for_basicblock_r( ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackMap @@ -247,7 +248,7 @@ def _generate_evm_for_basicblock_r( fen = 0 for inst in basicblock.instructions: - inst.fen = fen + inst.fence_id = fen if inst.volatile: fen += 1 @@ -274,7 +275,7 @@ def _generate_evm_for_instruction_r( if target.parent != inst.parent: continue # REVIEW: what does this line do? - if target.fen != inst.fen: + if target.fence_id != inst.fence_id: continue # REVIEW: I think it would be better to have an explicit step, # `reorder instructions per DFG`, and then `generate_evm_for_instruction` @@ -282,7 +283,7 @@ def _generate_evm_for_instruction_r( assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) if inst in visited_instructions: - #print("seen:", inst) + # print("seen:", inst) return assembly visited_instructions.add(inst) @@ -332,7 +333,7 @@ def _generate_evm_for_instruction_r( _stack_duplications(assembly, inst, stack_map, operands) - #print("(inst)", inst) + # print("(inst)", inst) _stack_reorder(assembly, stack_map, operands) # Step 4: Push instruction's return value to stack @@ -450,7 +451,7 @@ def _emit_input_operands( ops: list[IRValueBase], stack_map: StackMap, ): - #print("EMIT INPUTS FOR", inst) + # print("EMIT INPUTS FOR", inst) for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -463,11 +464,9 @@ def _emit_input_operands( assembly.extend([*PUSH(op.value)]) stack_map.push(op) continue - #print("RECURSE FOR", op, "TO:", ctx.dfg_outputs[op.value]) + # print("RECURSE FOR", op, "TO:", ctx.dfg_outputs[op.value]) assembly.extend( - _generate_evm_for_instruction_r( - ctx, [], ctx.dfg_outputs[op.value], stack_map - ) + _generate_evm_for_instruction_r(ctx, [], ctx.dfg_outputs[op.value], stack_map) ) if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index d809fbec02..d75093a3d9 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -113,8 +113,7 @@ class IRInstruction: liveness: OrderedSet[IRVariable] dup_requirements: OrderedSet[IRVariable] parent: Optional["IRBasicBlock"] - # REVIEW: rename to `fence` - fen: int + fence_id: int annotation: Optional[str] def __init__( @@ -154,7 +153,7 @@ def __init__( self.liveness = OrderedSet() self.dup_requirements = OrderedSet() self.parent = None - self.fen = -1 + self.fence_id = -1 self.annotation = None def get_label_operands(self) -> list[IRLabel]: @@ -179,9 +178,8 @@ def get_input_operands(self) -> list[IRValueBase]: def get_output_operands(self) -> list[IRValueBase]: return [self.ret] if self.ret else [] - # REVIEW: rename to `replace_operands` - # use of `dict` here seems a bit weird (what is equality on operands?) - def update_operands(self, replacements: dict) -> None: + # REVIEW: use of `dict` here seems a bit weird (what is equality on operands?) + def replace_operands(self, replacements: dict) -> None: """ Update operands with replacements. """ @@ -312,12 +310,12 @@ def clear_instructions(self) -> None: self.instructions = [] # REVIEW: rename to replace_operands - def update_operands(self, replacements: dict) -> None: + def replace_operands(self, replacements: dict) -> None: """ Update operands with replacements. """ for instruction in self.instructions: - instruction.update_operands(replacements) + instruction.replace_operands(replacements) @property def is_terminated(self) -> bool: @@ -334,8 +332,7 @@ def calculate_liveness(self) -> None: Compute liveness of each instruction in the basic block. """ liveness = self.out_vars.copy() - # REVIEW: use `reversed()` here - for instruction in self.instructions[::-1]: + for instruction in reversed(self.instructions): ops = instruction.get_input_operands() liveness = liveness.union(OrderedSet.fromkeys(ops)) out = ( diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 572a339e6a..beac373788 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -54,12 +54,9 @@ def _expand_row(row): class StackMap: NOT_IN_STACK = object() stack_map: list[IRValueBase] # REVIEW: rename to stack - # REVIEW: dead variable - dependant_liveness: OrderedSet[IRValueBase] def __init__(self): self.stack_map = [] - self.dependant_liveness = OrderedSet() def copy(self): new = StackMap() diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 451ed153ee..cef2b1ffc8 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -95,9 +95,9 @@ def calculate_cfg_in(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: assert len(bb.instructions) > 0, "Basic block should not be empty" last_inst = bb.instructions[-1] - assert ( - last_inst.opcode in BB_TERMINATORS - ), "Last instruction should be a terminator" + str(bb) + assert last_inst.opcode in BB_TERMINATORS, "Last instruction should be a terminator" + str( + bb + ) for inst in bb.instructions: if inst.opcode in ["jmp", "jnz", "call", "staticcall", "invoke", "deploy"]: @@ -120,8 +120,7 @@ def _reset_liveness(ctx: IRFunction) -> None: def _calculate_liveness(bb: IRBasicBlock, liveness_visited: OrderedSet) -> None: assert isinstance(liveness_visited, OrderedSet) for out_bb in bb.cfg_out: - # REVIEW: .get() already defaults to None - if liveness_visited.get(bb, None) == out_bb: + if liveness_visited.get(bb) == out_bb: continue liveness_visited[bb] = out_bb _calculate_liveness(out_bb, liveness_visited) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index e52d99bb71..f90f3a196e 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -837,7 +837,7 @@ def emit_body_block(): 1, ) - body_block.update_operands(replacements) + body_block.replace_operands(replacements) body_end = ctx.get_basic_block() if body_end.is_terminated() is False: From 7f33aacb633e2903c9db267f2a687cd522b6504f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 13:48:18 +0200 Subject: [PATCH 288/471] remove unused instruction dbg info --- vyper/codegen/ir_basicblock.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vyper/codegen/ir_basicblock.py b/vyper/codegen/ir_basicblock.py index d75093a3d9..faa12a8e9c 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/codegen/ir_basicblock.py @@ -121,7 +121,6 @@ def __init__( opcode: str, operands: list[IRValueBase], ret: IRValueBase = None, - dbg: IRDebugInfo = None, ): self.opcode = opcode # REVIEW nit: make this global definition @@ -149,7 +148,6 @@ def __init__( ] self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] self.ret = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None - self.dbg = dbg self.liveness = OrderedSet() self.dup_requirements = OrderedSet() self.parent = None @@ -198,9 +196,6 @@ def __repr__(self) -> str: ) s += operands - if self.dbg: - return s + f" {self.dbg}" - if self.annotation: s += f" <{self.annotation}>" From 9f5d32c06ec3d851faa2db748f674ea1ff5b216c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 13:56:07 +0200 Subject: [PATCH 289/471] group passthrough instructions --- vyper/ir/ir_to_bb_pass.py | 57 ++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index f90f3a196e..ba3df6522f 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -39,7 +39,35 @@ "signextend", ] -MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} +# Instuctions that are mapped to their inverse +INVERSE_MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} + +# Instructions that have a direct EVM opcode equivalent and can +# be passed through to the EVM assembly without special handling +PASS_THROUGH_INSTRUCTIONS = [ + "chainid", + "basefee", + "timestamp", + "caller", + "selfbalance", + "calldatasize", + "callvalue", + "address", + "origin", + "codesize", + "gas", + "gasprice", + "gaslimit", + "returndatasize", + "coinbase", + "number", + "iszero", + "ceil32", + "calldataload", + "extcodesize", + "extcodehash", + "balance", +] SymbolTable = dict[str, IRValueBase] @@ -253,37 +281,16 @@ def _convert_ir_basicblock( ctx, ir, symbols, variables, allocated_variables, ir.value in ["sha3_64"] ) - elif ir.value in MAPPED_IR_INSTRUCTIONS: + elif ir.value in INVERSE_MAPPED_IR_INSTRUCTIONS: org_value = ir.value - ir.value = MAPPED_IR_INSTRUCTIONS[ir.value] + 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]) - elif ir.value in ["iszero", "ceil32", "calldataload", "extcodesize", "extcodehash", "balance"]: + elif ir.value in PASS_THROUGH_INSTRUCTIONS: return _convert_ir_simple_node(ctx, ir, symbols, variables, allocated_variables) - # REVIEW: make this a global constant - elif ir.value in [ - "chainid", - "basefee", - "timestamp", - "caller", - "selfbalance", - "calldatasize", - "callvalue", - "address", - "origin", - "codesize", - "gas", - "gasprice", - "gaslimit", - "returndatasize", - "coinbase", - "number", - ]: - return ctx.append_instruction(ir.value, []) - elif ir.value in ["pass", "stop", "return"]: pass elif ir.value == "deploy": From e0351d85dfbb447adf12a2dc4705b3b4a59511b7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 13:58:07 +0200 Subject: [PATCH 290/471] StackModel and stack_map rename --- vyper/codegen/dfg.py | 20 ++++++++++---------- vyper/compiler/utils.py | 33 ++++++++++++++++----------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 1632d57e81..765304834a 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -6,7 +6,7 @@ IRVariable, ) from vyper.codegen.ir_function import IRFunction -from vyper.compiler.utils import StackMap +from vyper.compiler.utils import StackModel from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet @@ -173,7 +173,7 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: visited_instructions = OrderedSet() visited_basicblocks = OrderedSet() - _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackMap()) + _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackModel()) # Append postambles revert_postamble = ["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"] @@ -205,18 +205,18 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: def _stack_duplications( - assembly: list, inst: IRInstruction, stack_map: StackMap, stack_ops: list[IRValueBase] + assembly: list, inst: IRInstruction, stack_map: StackModel, stack_ops: list[IRValueBase] ) -> None: for op in stack_ops: if op.is_literal or isinstance(op, IRLabel): continue depth = stack_map.get_depth_in(op) - assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" + assert depth is not StackModel.NOT_IN_STACK, "Operand not in stack" if op in inst.dup_requirements: stack_map.dup(assembly, depth) -def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueBase]) -> None: +def _stack_reorder(assembly: list, stack_map: StackModel, stack_ops: list[IRValueBase]) -> None: stack_ops = [x.value for x in stack_ops] # print("ENTER reorder", stack_map.stack_map, operands) # start_len = len(assembly) @@ -224,7 +224,7 @@ def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueB op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) depth = stack_map.get_depth_in(op) - assert depth is not StackMap.NOT_IN_STACK, f"{op} not in stack: {stack_map.stack_map}" + assert depth is not StackModel.NOT_IN_STACK, f"{op} not in stack: {stack_map.stack}" if depth == final_stack_depth: continue @@ -237,7 +237,7 @@ def _stack_reorder(assembly: list, stack_map: StackMap, stack_ops: list[IRValueB def _generate_evm_for_basicblock_r( - ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackMap + ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackModel ): if basicblock in visited_basicblocks: return @@ -265,7 +265,7 @@ def _generate_evm_for_basicblock_r( # REVIEW: would this be better as a class? def _generate_evm_for_instruction_r( - ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackMap + ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackModel ) -> list[str]: global label_counter @@ -312,7 +312,7 @@ def _generate_evm_for_instruction_r( # REVIEW: the special handling in get_depth_in for lists # seems cursed, refactor depth = stack_map.get_depth_in(inputs) - assert depth is not StackMap.NOT_IN_STACK, "Operand not in stack" + assert depth is not StackModel.NOT_IN_STACK, "Operand not in stack" to_be_replaced = stack_map.peek(depth) if to_be_replaced in inst.dup_requirements: stack_map.dup(assembly, depth) @@ -449,7 +449,7 @@ def _emit_input_operands( assembly: list, inst: IRInstruction, ops: list[IRValueBase], - stack_map: StackMap, + stack_map: StackModel, ): # print("EMIT INPUTS FOR", inst) for op in ops: diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index beac373788..1f7aab04f8 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -50,34 +50,33 @@ def _expand_row(row): # REVIEW: move this to vyper/ir/ or vyper/venom/ -# rename to StackModel -class StackMap: +class StackModel: NOT_IN_STACK = object() - stack_map: list[IRValueBase] # REVIEW: rename to stack + stack: list[IRValueBase] def __init__(self): - self.stack_map = [] + self.stack = [] def copy(self): - new = StackMap() - new.stack_map = self.stack_map.copy() + new = StackModel() + new.stack = self.stack.copy() return new def get_height(self) -> int: """ Returns the height of the stack map. """ - return len(self.stack_map) + return len(self.stack) def push(self, op: IRValueBase) -> None: """ Pushes an operand onto the stack map. """ assert isinstance(op, IRValueBase), f"push takes IRValueBase, got '{op}'" - self.stack_map.append(op) + self.stack.append(op) def pop(self, num: int = 1) -> None: - del self.stack_map[len(self.stack_map) - num :] + del self.stack[len(self.stack) - num :] def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: """ @@ -91,7 +90,7 @@ def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: or isinstance(op, list) ), f"get_depth_in takes IRValueBase or list, got '{op}'" - for i, stack_op in enumerate(self.stack_map[::-1]): + for i, stack_op in enumerate(self.stack[::-1]): if isinstance(stack_op, IRValueBase): # REVIEW: handling literals this way seems a bit cursed, # why not use IRLiteral, so it is always IRValueBase? @@ -105,13 +104,13 @@ def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: elif isinstance(op, list) and stack_op in op: return -i - return StackMap.NOT_IN_STACK + return StackModel.NOT_IN_STACK def peek(self, depth: int) -> IRValueBase: """ Returns the top of the stack map. """ - return self.stack_map[depth - 1] + return self.stack[depth - 1] def poke(self, depth: int, op: IRValueBase) -> None: """ @@ -119,7 +118,7 @@ def poke(self, depth: int, op: IRValueBase) -> None: """ assert depth <= 0, "Bad depth" assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" - self.stack_map[depth - 1] = op + self.stack[depth - 1] = op def dup(self, assembly: list[str], depth: int) -> None: """ @@ -127,7 +126,7 @@ def dup(self, assembly: list[str], depth: int) -> None: """ assert depth <= 0, "Cannot dup positive depth" assembly.append(f"DUP{-(depth-1)}") - self.stack_map.append(self.peek(depth)) + self.stack.append(self.peek(depth)) def swap(self, assembly: list[str], depth: int) -> None: """ @@ -139,7 +138,7 @@ def swap(self, assembly: list[str], depth: int) -> None: assert depth < 0, "Cannot swap positive depth" assembly.append(f"SWAP{-depth}") - self.stack_map[depth - 1], self.stack_map[-1] = ( - self.stack_map[-1], - self.stack_map[depth - 1], + self.stack[depth - 1], self.stack[-1] = ( + self.stack[-1], + self.stack[depth - 1], ) From 48aac0f36694a6bfae031868c8bdac9063b4812a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 14:00:59 +0200 Subject: [PATCH 291/471] move stack model to venom directory --- vyper/codegen/dfg.py | 2 +- vyper/compiler/utils.py | 95 -------------------------------------- vyper/venom/stack_model.py | 95 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 96 deletions(-) create mode 100644 vyper/venom/stack_model.py diff --git a/vyper/codegen/dfg.py b/vyper/codegen/dfg.py index 765304834a..52eab79943 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/codegen/dfg.py @@ -6,7 +6,7 @@ IRVariable, ) from vyper.codegen.ir_function import IRFunction -from vyper.compiler.utils import StackModel +from vyper.venom.stack_model import StackModel from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index 1f7aab04f8..a1d6c6aecd 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -47,98 +47,3 @@ def _expand_row(row): if value: result[i] = value if i == 3 else int(value) return result - - -# REVIEW: move this to vyper/ir/ or vyper/venom/ -class StackModel: - NOT_IN_STACK = object() - stack: list[IRValueBase] - - def __init__(self): - self.stack = [] - - def copy(self): - new = StackModel() - new.stack = self.stack.copy() - return new - - def get_height(self) -> int: - """ - Returns the height of the stack map. - """ - return len(self.stack) - - def push(self, op: IRValueBase) -> None: - """ - Pushes an operand onto the stack map. - """ - assert isinstance(op, IRValueBase), f"push takes IRValueBase, got '{op}'" - self.stack.append(op) - - def pop(self, num: int = 1) -> None: - del self.stack[len(self.stack) - num :] - - def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: - """ - Returns the depth of the first matching operand in the stack map. - If the operand is not in the stack map, returns NOT_IN_STACK. - """ - assert ( - isinstance(op, str) - or isinstance(op, int) - or isinstance(op, IRValueBase) - or isinstance(op, list) - ), f"get_depth_in takes IRValueBase or list, got '{op}'" - - for i, stack_op in enumerate(self.stack[::-1]): - if isinstance(stack_op, IRValueBase): - # REVIEW: handling literals this way seems a bit cursed, - # why not use IRLiteral, so it is always IRValueBase? - if isinstance(op, str) and stack_op.value == op: - return -i - if isinstance(op, int) and stack_op.value == op: - return -i - if isinstance(op, IRValueBase) and stack_op.value == op.value: - return -i - # REVIEW: this branch seems cursed - elif isinstance(op, list) and stack_op in op: - return -i - - return StackModel.NOT_IN_STACK - - def peek(self, depth: int) -> IRValueBase: - """ - Returns the top of the stack map. - """ - return self.stack[depth - 1] - - def poke(self, depth: int, op: IRValueBase) -> None: - """ - Pokes an operand at the given depth in the stack map. - """ - assert depth <= 0, "Bad depth" - assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" - self.stack[depth - 1] = op - - def dup(self, assembly: list[str], depth: int) -> None: - """ - Duplicates the operand at the given depth in the stack map. - """ - assert depth <= 0, "Cannot dup positive depth" - assembly.append(f"DUP{-(depth-1)}") - self.stack.append(self.peek(depth)) - - def swap(self, assembly: list[str], depth: int) -> None: - """ - Swaps the operand at the given depth in the stack map with the top of the stack. - """ - # convenience, avoids branching in caller - if depth == 0: - return - - assert depth < 0, "Cannot swap positive depth" - assembly.append(f"SWAP{-depth}") - self.stack[depth - 1], self.stack[-1] = ( - self.stack[-1], - self.stack[depth - 1], - ) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py new file mode 100644 index 0000000000..7d86d16831 --- /dev/null +++ b/vyper/venom/stack_model.py @@ -0,0 +1,95 @@ +from vyper.codegen.ir_basicblock import IRValueBase + + +class StackModel: + NOT_IN_STACK = object() + stack: list[IRValueBase] + + def __init__(self): + self.stack = [] + + def copy(self): + new = StackModel() + new.stack = self.stack.copy() + return new + + def get_height(self) -> int: + """ + Returns the height of the stack map. + """ + return len(self.stack) + + def push(self, op: IRValueBase) -> None: + """ + Pushes an operand onto the stack map. + """ + assert isinstance(op, IRValueBase), f"push takes IRValueBase, got '{op}'" + self.stack.append(op) + + def pop(self, num: int = 1) -> None: + del self.stack[len(self.stack) - num :] + + def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: + """ + Returns the depth of the first matching operand in the stack map. + If the operand is not in the stack map, returns NOT_IN_STACK. + """ + assert ( + isinstance(op, str) + or isinstance(op, int) + or isinstance(op, IRValueBase) + or isinstance(op, list) + ), f"get_depth_in takes IRValueBase or list, got '{op}'" + + for i, stack_op in enumerate(self.stack[::-1]): + if isinstance(stack_op, IRValueBase): + # REVIEW: handling literals this way seems a bit cursed, + # why not use IRLiteral, so it is always IRValueBase? + if isinstance(op, str) and stack_op.value == op: + return -i + if isinstance(op, int) and stack_op.value == op: + return -i + if isinstance(op, IRValueBase) and stack_op.value == op.value: + return -i + # REVIEW: this branch seems cursed + elif isinstance(op, list) and stack_op in op: + return -i + + return StackModel.NOT_IN_STACK + + def peek(self, depth: int) -> IRValueBase: + """ + Returns the top of the stack map. + """ + return self.stack[depth - 1] + + def poke(self, depth: int, op: IRValueBase) -> None: + """ + Pokes an operand at the given depth in the stack map. + """ + assert depth <= 0, "Bad depth" + assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" + self.stack[depth - 1] = op + + def dup(self, assembly: list[str], depth: int) -> None: + """ + Duplicates the operand at the given depth in the stack map. + """ + assert depth <= 0, "Cannot dup positive depth" + assembly.append(f"DUP{-(depth-1)}") + self.stack.append(self.peek(depth)) + + def swap(self, assembly: list[str], depth: int) -> None: + """ + Swaps the operand at the given depth in the stack map with the top of the stack. + """ + # convenience, avoids branching in caller + if depth == 0: + return + + assert depth < 0, "Cannot swap positive depth" + assembly.append(f"SWAP{-depth}") + self.stack[depth - 1], self.stack[-1] = ( + self.stack[-1], + self.stack[depth - 1], + ) From 3133fb65a2190e04280e804a6261cca92ef7fce4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 14:08:26 +0200 Subject: [PATCH 292/471] Refactor directory structure Moved venom related files under /vyper/venom --- vyper/compiler/phases.py | 2 +- vyper/compiler/utils.py | 2 -- vyper/ir/bb_optimizer.py | 4 ++-- vyper/ir/ir_to_bb_pass.py | 6 +++--- vyper/{codegen => venom}/dfg.py | 4 ++-- vyper/{codegen => venom}/ir.py | 6 +++--- vyper/{codegen => venom}/ir_basicblock.py | 2 +- vyper/{codegen => venom}/ir_function.py | 2 +- vyper/{codegen => venom}/ir_pass_constant_propagation.py | 4 ++-- vyper/{codegen => venom}/ir_pass_dft.py | 4 ++-- vyper/venom/stack_model.py | 2 +- 11 files changed, 18 insertions(+), 20 deletions(-) rename vyper/{codegen => venom}/dfg.py (99%) rename vyper/{codegen => venom}/ir.py (86%) rename vyper/{codegen => venom}/ir_basicblock.py (99%) rename vyper/{codegen => venom}/ir_function.py (99%) rename vyper/{codegen => venom}/ir_pass_constant_propagation.py (70%) rename vyper/{codegen => venom}/ir_pass_dft.py (90%) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 2cb6d1eeac..0e8498ec56 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -7,7 +7,7 @@ from vyper.codegen import module from vyper.codegen.core import anchor_opt_level from vyper.codegen.global_context import GlobalContext -from vyper.codegen.ir import generate_ir +from vyper.venom.ir import generate_ir from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel, Settings from vyper.exceptions import StructureException diff --git a/vyper/compiler/utils.py b/vyper/compiler/utils.py index a1d6c6aecd..8de2589367 100644 --- a/vyper/compiler/utils.py +++ b/vyper/compiler/utils.py @@ -1,8 +1,6 @@ from typing import Dict -from vyper.codegen.ir_basicblock import IRValueBase from vyper.semantics.types.function import ContractFunctionT -from vyper.utils import OrderedSet def build_gas_estimates(func_ts: Dict[str, ContractFunctionT]) -> dict: diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index cef2b1ffc8..d154f8cd58 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -1,10 +1,10 @@ -from vyper.codegen.ir_basicblock import ( +from vyper.venom.ir_basicblock import ( BB_TERMINATORS, IRBasicBlock, IRInstruction, IRLabel, ) -from vyper.codegen.ir_function import IRFunction +from vyper.venom.ir_function import IRFunction from vyper.utils import OrderedSet, ir_pass diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index ba3df6522f..78a4de4146 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,7 +1,7 @@ from typing import Optional -from vyper.codegen.dfg import generate_evm -from vyper.codegen.ir_basicblock import ( +from vyper.venom.dfg import generate_evm +from vyper.venom.ir_basicblock import ( IRBasicBlock, IRInstruction, IRLabel, @@ -9,7 +9,7 @@ IRValueBase, IRVariable, ) -from vyper.codegen.ir_function import IRFunction +from vyper.venom.ir_function import IRFunction from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel from vyper.evm.opcodes import get_opcodes diff --git a/vyper/codegen/dfg.py b/vyper/venom/dfg.py similarity index 99% rename from vyper/codegen/dfg.py rename to vyper/venom/dfg.py index 52eab79943..5116ebbcaf 100644 --- a/vyper/codegen/dfg.py +++ b/vyper/venom/dfg.py @@ -1,11 +1,11 @@ -from vyper.codegen.ir_basicblock import ( +from vyper.venom.ir_basicblock import ( IRBasicBlock, IRInstruction, IRLabel, IRValueBase, IRVariable, ) -from vyper.codegen.ir_function import IRFunction +from vyper.venom.ir_function import IRFunction from vyper.venom.stack_model import StackModel from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet diff --git a/vyper/codegen/ir.py b/vyper/venom/ir.py similarity index 86% rename from vyper/codegen/ir.py rename to vyper/venom/ir.py index 9f36df3768..8b8d92bf55 100644 --- a/vyper/codegen/ir.py +++ b/vyper/venom/ir.py @@ -1,9 +1,9 @@ from typing import Optional -from vyper.codegen.dfg import convert_ir_to_dfg -from vyper.codegen.ir_function import IRFunction +from vyper.venom.dfg import convert_ir_to_dfg +from vyper.venom.ir_function import IRFunction from vyper.codegen.ir_node import IRnode -from vyper.codegen.ir_pass_constant_propagation import ir_pass_constant_propagation +from vyper.venom.ir_pass_constant_propagation import ir_pass_constant_propagation from vyper.compiler.settings import OptimizationLevel from vyper.ir.bb_optimizer import ( calculate_cfg_in, diff --git a/vyper/codegen/ir_basicblock.py b/vyper/venom/ir_basicblock.py similarity index 99% rename from vyper/codegen/ir_basicblock.py rename to vyper/venom/ir_basicblock.py index faa12a8e9c..f5976ead66 100644 --- a/vyper/codegen/ir_basicblock.py +++ b/vyper/venom/ir_basicblock.py @@ -7,7 +7,7 @@ BB_TERMINATORS = ["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"] if TYPE_CHECKING: - from vyper.codegen.ir_function import IRFunction + from vyper.venom.ir_function import IRFunction class IRDebugInfo: diff --git a/vyper/codegen/ir_function.py b/vyper/venom/ir_function.py similarity index 99% rename from vyper/codegen/ir_function.py rename to vyper/venom/ir_function.py index cbcba331ce..5d1aa89c5e 100644 --- a/vyper/codegen/ir_function.py +++ b/vyper/venom/ir_function.py @@ -1,6 +1,6 @@ from typing import Optional -from vyper.codegen.ir_basicblock import ( +from vyper.venom.ir_basicblock import ( IRBasicBlock, IRInstruction, IRLabel, diff --git a/vyper/codegen/ir_pass_constant_propagation.py b/vyper/venom/ir_pass_constant_propagation.py similarity index 70% rename from vyper/codegen/ir_pass_constant_propagation.py rename to vyper/venom/ir_pass_constant_propagation.py index 77e68d28bb..b1675f2202 100644 --- a/vyper/codegen/ir_pass_constant_propagation.py +++ b/vyper/venom/ir_pass_constant_propagation.py @@ -1,5 +1,5 @@ -from vyper.codegen.ir_basicblock import IRBasicBlock -from vyper.codegen.ir_function import IRFunction +from vyper.venom.ir_basicblock import IRBasicBlock +from vyper.venom.ir_function import IRFunction from vyper.utils import OrderedSet, ir_pass diff --git a/vyper/codegen/ir_pass_dft.py b/vyper/venom/ir_pass_dft.py similarity index 90% rename from vyper/codegen/ir_pass_dft.py rename to vyper/venom/ir_pass_dft.py index d0ad80f546..b673c2b8cd 100644 --- a/vyper/codegen/ir_pass_dft.py +++ b/vyper/venom/ir_pass_dft.py @@ -1,5 +1,5 @@ -from vyper.codegen.ir_basicblock import IRBasicBlock, IRInstruction -from vyper.codegen.ir_function import IRFunction +from vyper.venom.ir_basicblock import IRBasicBlock, IRInstruction +from vyper.venom.ir_function import IRFunction from vyper.utils import OrderedSet, ir_pass visited_instructions = OrderedSet() diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 7d86d16831..c9b25d9a19 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -1,4 +1,4 @@ -from vyper.codegen.ir_basicblock import IRValueBase +from vyper.venom.ir_basicblock import IRValueBase class StackModel: From 31f042629bcb42bcf1e55d6f62f53a03530aea88 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 14:10:26 +0200 Subject: [PATCH 293/471] remove ir_ prefix from filenames in venom directory --- vyper/ir/bb_optimizer.py | 4 ++-- vyper/ir/ir_to_bb_pass.py | 4 ++-- vyper/venom/{ir_basicblock.py => basicblock.py} | 2 +- vyper/venom/dfg.py | 4 ++-- vyper/venom/{ir_function.py => function.py} | 2 +- vyper/venom/ir.py | 4 ++-- ...s_constant_propagation.py => pass_constant_propagation.py} | 4 ++-- vyper/venom/{ir_pass_dft.py => pass_dft.py} | 4 ++-- vyper/venom/stack_model.py | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) rename vyper/venom/{ir_basicblock.py => basicblock.py} (99%) rename vyper/venom/{ir_function.py => function.py} (99%) rename vyper/venom/{ir_pass_constant_propagation.py => pass_constant_propagation.py} (71%) rename vyper/venom/{ir_pass_dft.py => pass_dft.py} (90%) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index d154f8cd58..609d82ca8f 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -1,10 +1,10 @@ -from vyper.venom.ir_basicblock import ( +from vyper.venom.basicblock import ( BB_TERMINATORS, IRBasicBlock, IRInstruction, IRLabel, ) -from vyper.venom.ir_function import IRFunction +from vyper.venom.function import IRFunction from vyper.utils import OrderedSet, ir_pass diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 78a4de4146..f12da87042 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,7 +1,7 @@ from typing import Optional from vyper.venom.dfg import generate_evm -from vyper.venom.ir_basicblock import ( +from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, IRLabel, @@ -9,7 +9,7 @@ IRValueBase, IRVariable, ) -from vyper.venom.ir_function import IRFunction +from vyper.venom.function import IRFunction from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel from vyper.evm.opcodes import get_opcodes diff --git a/vyper/venom/ir_basicblock.py b/vyper/venom/basicblock.py similarity index 99% rename from vyper/venom/ir_basicblock.py rename to vyper/venom/basicblock.py index f5976ead66..8cd0262c10 100644 --- a/vyper/venom/ir_basicblock.py +++ b/vyper/venom/basicblock.py @@ -7,7 +7,7 @@ BB_TERMINATORS = ["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"] if TYPE_CHECKING: - from vyper.venom.ir_function import IRFunction + from vyper.venom.function import IRFunction class IRDebugInfo: diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 5116ebbcaf..88dc5a28af 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -1,11 +1,11 @@ -from vyper.venom.ir_basicblock import ( +from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, IRLabel, IRValueBase, IRVariable, ) -from vyper.venom.ir_function import IRFunction +from vyper.venom.function import IRFunction from vyper.venom.stack_model import StackModel from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet diff --git a/vyper/venom/ir_function.py b/vyper/venom/function.py similarity index 99% rename from vyper/venom/ir_function.py rename to vyper/venom/function.py index 5d1aa89c5e..f960af0105 100644 --- a/vyper/venom/ir_function.py +++ b/vyper/venom/function.py @@ -1,6 +1,6 @@ from typing import Optional -from vyper.venom.ir_basicblock import ( +from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, IRLabel, diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index 8b8d92bf55..6e9d0d42a7 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -1,9 +1,9 @@ from typing import Optional from vyper.venom.dfg import convert_ir_to_dfg -from vyper.venom.ir_function import IRFunction +from vyper.venom.function import IRFunction from vyper.codegen.ir_node import IRnode -from vyper.venom.ir_pass_constant_propagation import ir_pass_constant_propagation +from vyper.venom.pass_constant_propagation import ir_pass_constant_propagation from vyper.compiler.settings import OptimizationLevel from vyper.ir.bb_optimizer import ( calculate_cfg_in, diff --git a/vyper/venom/ir_pass_constant_propagation.py b/vyper/venom/pass_constant_propagation.py similarity index 71% rename from vyper/venom/ir_pass_constant_propagation.py rename to vyper/venom/pass_constant_propagation.py index b1675f2202..75552331e9 100644 --- a/vyper/venom/ir_pass_constant_propagation.py +++ b/vyper/venom/pass_constant_propagation.py @@ -1,5 +1,5 @@ -from vyper.venom.ir_basicblock import IRBasicBlock -from vyper.venom.ir_function import IRFunction +from vyper.venom.basicblock import IRBasicBlock +from vyper.venom.function import IRFunction from vyper.utils import OrderedSet, ir_pass diff --git a/vyper/venom/ir_pass_dft.py b/vyper/venom/pass_dft.py similarity index 90% rename from vyper/venom/ir_pass_dft.py rename to vyper/venom/pass_dft.py index b673c2b8cd..7760577b5e 100644 --- a/vyper/venom/ir_pass_dft.py +++ b/vyper/venom/pass_dft.py @@ -1,5 +1,5 @@ -from vyper.venom.ir_basicblock import IRBasicBlock, IRInstruction -from vyper.venom.ir_function import IRFunction +from vyper.venom.basicblock import IRBasicBlock, IRInstruction +from vyper.venom.function import IRFunction from vyper.utils import OrderedSet, ir_pass visited_instructions = OrderedSet() diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index c9b25d9a19..3113166284 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -1,4 +1,4 @@ -from vyper.venom.ir_basicblock import IRValueBase +from vyper.venom.basicblock import IRValueBase class StackModel: From 8026205a375b482c065a9b190a9c462695708ca7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 14:25:22 +0200 Subject: [PATCH 294/471] use remove_cfg_in() --- 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 f960af0105..91a6ae949d 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -90,8 +90,7 @@ def remove_unreachable_blocks(self) -> int: for bb in self.basic_blocks: if not bb.is_reachable and bb.label.value != "global": for bb2 in bb.cfg_out: - # REVIEW: use cfg_remove_in - bb2.cfg_in.remove(bb) + bb2.remove_cfg_in(bb) removed += 1 else: new_basic_blocks.append(bb) From 9c1fbc30f481205d5b38eb2e91fa0d9137aedf96 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 14:25:58 +0200 Subject: [PATCH 295/471] select -> phi, and various small review replies --- vyper/ir/ir_to_bb_pass.py | 8 ++++---- vyper/venom/basicblock.py | 14 ++++++-------- vyper/venom/dfg.py | 11 ++++++++--- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index f12da87042..894d5feedd 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -425,7 +425,7 @@ def _convert_ir_basicblock( if_ret = ctx.get_next_variable() bb.append_instruction( IRInstruction( - "select", + "phi", [then_block.label, then_ret_val, else_block.label, else_ret_val], if_ret, ) @@ -441,7 +441,7 @@ def _convert_ir_basicblock( if var.value == old_var.value: allocated_variables[idx] = ret bb.append_instruction( - IRInstruction("select", [then_block.label, val[0], else_block.label, val[1]], ret) + IRInstruction("phi", [then_block.label, val[0], else_block.label, val[1]], ret) ) if else_block.is_terminated is False: @@ -811,7 +811,7 @@ def emit_body_block(): symbols[sym.value] = ret cond_block.append_instruction( IRInstruction( - "select", + "phi", [entry_block.label, counter_var, increment_block.label, counter_inc_var], ret, ) @@ -839,7 +839,7 @@ def emit_body_block(): replacements[val[1]] = new_var cond_block.insert_instruction( IRInstruction( - "select", [entry_block.label, val[0], increment_block.label, val[1]], new_var + "phi", [entry_block.label, val[0], increment_block.label, val[1]], new_var ), 1, ) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 8cd0262c10..f3819502ef 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -107,8 +107,6 @@ class IRInstruction: operands: list[IRValueBase] # REVIEW: rename to lhs? ret: Optional[IRValueBase] - # REVIEW: rename to source_info? - dbg: Optional[IRDebugInfo] # set of live variables at this instruction liveness: OrderedSet[IRVariable] dup_requirements: OrderedSet[IRVariable] @@ -231,11 +229,9 @@ class IRBasicBlock: label: IRLabel parent: "IRFunction" instructions: list[IRInstruction] - # REVIEW: "in_set" -> "cfg_in" - # (basic blocks which can jump to this basic block) + # basic blocks which can jump to this basic block cfg_in: OrderedSet["IRBasicBlock"] - # REVIEW: "out_set" -> "cfg_out" - # (basic blocks which this basic block can jump to) + # basic blocks which this basic block can jump to cfg_out: OrderedSet["IRBasicBlock"] # stack items which this basic block produces out_vars: OrderedSet[IRVariable] @@ -275,7 +271,8 @@ def in_vars_from(self, source: "IRBasicBlock") -> OrderedSet[IRVariable]: for inst in self.instructions: # REVIEW: might be nice if some of these instructions # were more structured. - if inst.opcode == "select": + # HK: Can you elaborate on this? I'm not sure what you mean. + if inst.opcode == "phi": if inst.operands[0] == source.label: liveness.add(inst.operands[1]) if inst.operands[3] in liveness: @@ -304,7 +301,6 @@ def insert_instruction(self, instruction: IRInstruction, index: int) -> None: def clear_instructions(self) -> None: self.instructions = [] - # REVIEW: rename to replace_operands def replace_operands(self, replacements: dict) -> None: """ Update operands with replacements. @@ -318,6 +314,8 @@ def is_terminated(self) -> bool: Check if the basic block is terminal, i.e. the last instruction is a terminator. """ # REVIEW: should this be an assert (like `is_terminal()`)? + # HK: I think it's fine to return False here, since we use this to check + # if we can/needto append instructions to the basic block. if len(self.instructions) == 0: return False return self.instructions[-1].opcode in BB_TERMINATORS diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 88dc5a28af..282d8dfa2f 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -125,7 +125,7 @@ def _compute_inst_dup_requirements_r( return visited.add(inst) - if inst.opcode == "select": + if inst.opcode == "phi": return for op in inst.get_input_operands(): @@ -272,14 +272,19 @@ def _generate_evm_for_instruction_r( for op in inst.get_output_operands(): for target in ctx.dfg_inputs.get(op.value, []): # REVIEW: what does this line do? + # HK: it skips instructions that are not in the same basic block + # so we don't cross basic block boundaries if target.parent != inst.parent: continue # REVIEW: what does this line do? + # HK: it skips instructions that are not in the same fence if target.fence_id != inst.fence_id: continue # REVIEW: I think it would be better to have an explicit step, # `reorder instructions per DFG`, and then `generate_evm_for_instruction` # does not need to recurse (or be co-recursive with `emit_input_operands`). + # HK: Indeed, this is eventualy the idea. Especialy now that I have implemented + # the "needs duplication" algorithm that needs the same traversal and it's duplicated assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) if inst in visited_instructions: @@ -306,7 +311,7 @@ def _generate_evm_for_instruction_r( else: operands = inst.operands - if opcode == "select": # REVIEW: maybe call this 'phi' + if opcode == "phi": ret = inst.get_output_operands()[0] inputs = inst.get_input_operands() # REVIEW: the special handling in get_depth_in for lists @@ -392,7 +397,7 @@ def _generate_evm_for_instruction_r( assembly.append("JUMP") elif opcode == "return": assembly.append("RETURN") - elif opcode == "select": + elif opcode == "phi": pass elif opcode == "sha3": assembly.append("SHA3") From 87e67b26c8937955a1c13939570873f423c2c4ae Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 14:27:52 +0200 Subject: [PATCH 296/471] rename get_input_operands and get_output_operands --- vyper/venom/basicblock.py | 13 ++++--------- vyper/venom/dfg.py | 22 +++++++++++----------- vyper/venom/pass_dft.py | 2 +- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index f3819502ef..32009dea3e 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -164,14 +164,13 @@ def get_non_label_operands(self) -> list[IRValueBase]: """ return [op for op in self.operands if not isinstance(op, IRLabel)] - def get_input_operands(self) -> list[IRValueBase]: + def get_inputs(self) -> list[IRValueBase]: """ Get all input operands for instruction. """ return [op for op in self.operands if isinstance(op, IRVariable)] - # REVIEW suggestion: rename to `get_outputs` - def get_output_operands(self) -> list[IRValueBase]: + def get_outputs(self) -> list[IRValueBase]: return [self.ret] if self.ret else [] # REVIEW: use of `dict` here seems a bit weird (what is equality on operands?) @@ -326,13 +325,9 @@ def calculate_liveness(self) -> None: """ liveness = self.out_vars.copy() for instruction in reversed(self.instructions): - ops = instruction.get_input_operands() + ops = instruction.get_inputs() liveness = liveness.union(OrderedSet.fromkeys(ops)) - out = ( - instruction.get_output_operands()[0] - if len(instruction.get_output_operands()) > 0 - else None - ) + out = instruction.get_outputs()[0] if len(instruction.get_outputs()) > 0 else None if out in liveness: liveness.remove(out) instruction.liveness = liveness diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 282d8dfa2f..5edfbb9549 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -83,14 +83,14 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: for inst in bb.instructions: inst.dup_requirements = OrderedSet() inst.fence_id = -1 - operands = inst.get_input_operands() - operands.extend(inst.get_output_operands()) + operands = inst.get_inputs() + operands.extend(inst.get_outputs()) # Build DFG for bb in ctx.basic_blocks: for inst in bb.instructions: - operands = inst.get_input_operands() - res = inst.get_output_operands() + operands = inst.get_inputs() + res = inst.get_outputs() for op in operands: ctx.dfg_inputs[op.value] = ( @@ -112,7 +112,7 @@ def _compute_inst_dup_requirements_r( visited: OrderedSet, last_seen: dict, ) -> None: - for op in inst.get_output_operands(): + for op in inst.get_outputs(): for target in ctx.dfg_inputs.get(op.value, []): if target.parent != inst.parent: # REVIEW: produced by parent.out_vars @@ -128,13 +128,13 @@ def _compute_inst_dup_requirements_r( if inst.opcode == "phi": return - for op in inst.get_input_operands(): + for op in inst.get_inputs(): target = ctx.dfg_outputs[op.value] if target.parent != inst.parent: continue _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) - for op in inst.get_input_operands(): + for op in inst.get_inputs(): target = last_seen.get(op.value, None) if target: target.dup_requirements.add(op) @@ -158,7 +158,7 @@ def _compute_dup_requirements(ctx: IRFunction) -> None: out_vars = bb.out_vars for inst in bb.instructions[::-1]: - for op in inst.get_input_operands(): + for op in inst.get_inputs(): if op in out_vars: inst.dup_requirements.add(op) @@ -269,7 +269,7 @@ def _generate_evm_for_instruction_r( ) -> list[str]: global label_counter - for op in inst.get_output_operands(): + for op in inst.get_outputs(): for target in ctx.dfg_inputs.get(op.value, []): # REVIEW: what does this line do? # HK: it skips instructions that are not in the same basic block @@ -312,8 +312,8 @@ def _generate_evm_for_instruction_r( operands = inst.operands if opcode == "phi": - ret = inst.get_output_operands()[0] - inputs = inst.get_input_operands() + ret = inst.get_outputs()[0] + inputs = inst.get_inputs() # REVIEW: the special handling in get_depth_in for lists # seems cursed, refactor depth = stack_map.get_depth_in(inputs) diff --git a/vyper/venom/pass_dft.py b/vyper/venom/pass_dft.py index 7760577b5e..564558ee87 100644 --- a/vyper/venom/pass_dft.py +++ b/vyper/venom/pass_dft.py @@ -6,7 +6,7 @@ def _emit_operands_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction) -> None: - for op in inst.get_input_operands(): + for op in inst.get_inputs(): target = ctx.dfg_outputs.get(op.value) if target is None: continue From a867abddd8d9792202e61e3446cdbf5f31b9be7f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 14:42:45 +0200 Subject: [PATCH 297/471] replace [::-1] with reversed where posible --- vyper/ir/ir_to_bb_pass.py | 7 ++++--- vyper/venom/dfg.py | 2 +- vyper/venom/stack_model.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 894d5feedd..4c0a5c0c44 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -373,11 +373,12 @@ def _convert_ir_basicblock( if ir.value == "call": return ctx.append_instruction( - ir.value, [gas, address, value, argsOffsetVar, argsSize, retOffset, retSize][::-1] + ir.value, + reversed([gas, address, value, argsOffsetVar, argsSize, retOffset, retSize]), ) else: return ctx.append_instruction( - ir.value, [gas, address, argsOffsetVar, argsSize, retOffset, retSize][::-1] + reversed(ir.value, [gas, address, argsOffsetVar, argsSize, retOffset, retSize]), ) elif ir.value == "if": cond = ir.args[0] @@ -896,7 +897,7 @@ def emit_body_block(): _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args ] - inst = IRInstruction(ir.value, args[::-1]) + inst = IRInstruction(ir.value, reversed(args)) ctx.get_basic_block().append_instruction(inst) elif isinstance(ir.value, str) and ir.value.upper() in get_opcodes(): _convert_ir_opcode(ctx, ir, symbols, variables, allocated_variables) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 5edfbb9549..f82b5594a0 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -157,7 +157,7 @@ def _compute_dup_requirements(ctx: IRFunction) -> None: _compute_inst_dup_requirements_r(ctx, inst, visited, last_seen) out_vars = bb.out_vars - for inst in bb.instructions[::-1]: + for inst in reversed(bb.instructions): for op in inst.get_inputs(): if op in out_vars: inst.dup_requirements.add(op) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 3113166284..a2fafccb69 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -41,7 +41,7 @@ def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: or isinstance(op, list) ), f"get_depth_in takes IRValueBase or list, got '{op}'" - for i, stack_op in enumerate(self.stack[::-1]): + for i, stack_op in enumerate(reversed(self.stack)): if isinstance(stack_op, IRValueBase): # REVIEW: handling literals this way seems a bit cursed, # why not use IRLiteral, so it is always IRValueBase? From 48842483b62d30874cfb5a99a8fa4bd4e30477d2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 14:43:03 +0200 Subject: [PATCH 298/471] volatile instructions global --- vyper/venom/basicblock.py | 59 ++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 32009dea3e..fc017d0c87 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -6,6 +6,30 @@ # instructions which can terminate a basic block BB_TERMINATORS = ["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"] +VOLATILE_INSTRUCTIONS = [ + "param", + "alloca", + "call", + "staticcall", + "invoke", + "sload", + "sstore", + "iload", + "istore", + "assert", + "mstore", + "mload", + "calldatacopy", + "codecopy", + "dloadbytes", + "dload", + "return", + "ret", + "jmp", + "jnz", +] + + if TYPE_CHECKING: from vyper.venom.function import IRFunction @@ -83,6 +107,8 @@ def __init__( class IRLabel(IRValueBase): # REVIEW: what do the values of is_symbol mean? + # HK: is_symbol is used to indicate if the label is a symbol coming from the initial IR. Like a function name, + # that I try to preserve when optimizing so that the final IR is easier to read. """ IRLabel represents a label in IR. A label is a string that starts with a %. """ @@ -106,6 +132,7 @@ class IRInstruction: volatile: bool operands: list[IRValueBase] # REVIEW: rename to lhs? + # HK: Maybe outputs is better? ret: Optional[IRValueBase] # set of live variables at this instruction liveness: OrderedSet[IRVariable] @@ -121,29 +148,7 @@ def __init__( ret: IRValueBase = None, ): self.opcode = opcode - # REVIEW nit: make this global definition - self.volatile = opcode in [ - "param", - "alloca", - "call", - "staticcall", - "invoke", - "sload", - "sstore", - "iload", - "istore", - "assert", - "mstore", - "mload", - "calldatacopy", - "codecopy", - "dloadbytes", - "dload", - "return", - "ret", - "jmp", - "jnz", - ] + self.volatile = opcode in VOLATILE_INSTRUCTIONS self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] self.ret = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None self.liveness = OrderedSet() @@ -174,12 +179,13 @@ def get_outputs(self) -> list[IRValueBase]: return [self.ret] if self.ret else [] # REVIEW: use of `dict` here seems a bit weird (what is equality on operands?) + # HK: replacements happen "key" replaced by "value", dict is used as a way to represent this. def replace_operands(self, replacements: dict) -> None: """ Update operands with replacements. """ for i, operand in enumerate(self.operands): - if operand in replacements.keys(): + if operand in replacements: self.operands[i] = replacements[operand] def __repr__(self) -> str: @@ -189,7 +195,10 @@ def __repr__(self) -> str: opcode = f"{self.opcode} " if self.opcode != "store" else "" s += opcode operands = ", ".join( - [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in self.operands[::-1]] + [ + (f"label %{op}" if isinstance(op, IRLabel) else str(op)) + for op in reversed(self.operands) + ] ) s += operands From ccd1bf6142ad42cfa8fabefc05540d44fbdb61de Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 14:45:46 +0200 Subject: [PATCH 299/471] MemType top level --- vyper/ir/ir_to_bb_pass.py | 5 +++-- vyper/venom/basicblock.py | 5 +++-- vyper/venom/dfg.py | 5 +++-- vyper/venom/function.py | 3 ++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 4c0a5c0c44..47074da8ec 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -8,6 +8,7 @@ IRLiteral, IRValueBase, IRVariable, + MemType, ) from vyper.venom.function import IRFunction from vyper.codegen.ir_node import IRnode @@ -362,13 +363,13 @@ def _convert_ir_basicblock( if argsOffsetVar is None: argsOffsetVar = argsOffset else: - argsOffsetVar.mem_type = IRVariable.MemType.MEMORY + argsOffsetVar.mem_type = MemType.MEMORY argsOffsetVar.mem_addr = addr argsOffsetVar.offset = 32 - 4 if argsOffset.value > 0 else 0 else: argsOffsetVar = argsOffset - retVar = ctx.get_next_variable(IRVariable.MemType.MEMORY, retOffset.value) + retVar = ctx.get_next_variable(MemType.MEMORY, retOffset.value) symbols[f"&{retOffset.value}"] = retVar if ir.value == "call": diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index fc017d0c87..7e4cf1ae43 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -83,14 +83,15 @@ def is_literal(self) -> bool: return True +MemType = Enum("MemType", ["OPERAND_STACK", "MEMORY"]) + + class IRVariable(IRValueBase): """ IRVariable represents a variable in IR. A variable is a string that starts with a %. """ offset: int = 0 - # REVIEW: make this toplevel definition - MemType = Enum("MemType", ["OPERAND_STACK", "MEMORY"]) mem_type: MemType = MemType.OPERAND_STACK mem_addr: int = -1 # REVIEW should this be None? diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index f82b5594a0..d1a7dfe156 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -4,6 +4,7 @@ IRLabel, IRValueBase, IRVariable, + MemType, ) from vyper.venom.function import IRFunction from vyper.venom.stack_model import StackModel @@ -443,7 +444,7 @@ def _generate_evm_for_instruction_r( # Step 6: Emit instructions output operands (if any) if inst.ret is not None: assert isinstance(inst.ret, IRVariable), "Return value must be a variable" - if inst.ret.mem_type == IRVariable.MemType.MEMORY: + if inst.ret.mem_type == MemType.MEMORY: assembly.extend([*PUSH(inst.ret.mem_addr)]) return assembly @@ -473,6 +474,6 @@ def _emit_input_operands( assembly.extend( _generate_evm_for_instruction_r(ctx, [], ctx.dfg_outputs[op.value], stack_map) ) - if isinstance(op, IRVariable) and op.mem_type == IRVariable.MemType.MEMORY: + if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 91a6ae949d..22751d61b0 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -6,6 +6,7 @@ IRLabel, IRValueBase, IRVariable, + MemType, ) @@ -76,7 +77,7 @@ def get_next_label(self) -> IRLabel: return IRLabel(f"{self.last_label}") def get_next_variable( - self, mem_type: IRVariable.MemType = IRVariable.MemType.OPERAND_STACK, mem_addr: int = -1 + self, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: int = -1 ) -> IRVariable: self.last_variable += 1 return IRVariable(f"%{self.last_variable}", mem_type, mem_addr) From 91256e6bc449ae6c928da72845f2032fa8422cf0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 15:24:16 +0200 Subject: [PATCH 300/471] stack_map -> stack --- vyper/venom/dfg.py | 66 ++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index d1a7dfe156..94ae38da1c 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -206,39 +206,39 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: def _stack_duplications( - assembly: list, inst: IRInstruction, stack_map: StackModel, stack_ops: list[IRValueBase] + assembly: list, inst: IRInstruction, stack: StackModel, stack_ops: list[IRValueBase] ) -> None: for op in stack_ops: if op.is_literal or isinstance(op, IRLabel): continue - depth = stack_map.get_depth_in(op) + depth = stack.get_depth_in(op) assert depth is not StackModel.NOT_IN_STACK, "Operand not in stack" if op in inst.dup_requirements: - stack_map.dup(assembly, depth) + stack.dup(assembly, depth) -def _stack_reorder(assembly: list, stack_map: StackModel, stack_ops: list[IRValueBase]) -> None: +def _stack_reorder(assembly: list, stack: StackModel, stack_ops: list[IRValueBase]) -> None: stack_ops = [x.value for x in stack_ops] - # print("ENTER reorder", stack_map.stack_map, operands) + # print("ENTER reorder", stack.stack, operands) # start_len = len(assembly) for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) - depth = stack_map.get_depth_in(op) - assert depth is not StackModel.NOT_IN_STACK, f"{op} not in stack: {stack_map.stack}" + depth = stack.get_depth_in(op) + assert depth is not StackModel.NOT_IN_STACK, f"{op} not in stack: {stack.stack}" if depth == final_stack_depth: continue # print("trace", depth, final_stack_depth) - stack_map.swap(assembly, depth) - stack_map.swap(assembly, final_stack_depth) + stack.swap(assembly, depth) + stack.swap(assembly, final_stack_depth) # print("INSTRUCTIONS", assembly[start_len:]) - # print("EXIT reorder", stack_map.stack_map, stack_ops) + # print("EXIT reorder", stack.stack, stack_ops) def _generate_evm_for_basicblock_r( - ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack_map: StackModel + ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack: StackModel ): if basicblock in visited_basicblocks: return @@ -254,10 +254,10 @@ def _generate_evm_for_basicblock_r( fen += 1 for inst in basicblock.instructions: - asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack_map) + asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack) for bb in basicblock.cfg_out: - _generate_evm_for_basicblock_r(ctx, asm, bb, stack_map.copy()) + _generate_evm_for_basicblock_r(ctx, asm, bb, stack.copy()) # TODO: refactor this @@ -266,7 +266,7 @@ def _generate_evm_for_basicblock_r( # REVIEW: would this be better as a class? def _generate_evm_for_instruction_r( - ctx: IRFunction, assembly: list, inst: IRInstruction, stack_map: StackModel + ctx: IRFunction, assembly: list, inst: IRInstruction, stack: StackModel ) -> list[str]: global label_counter @@ -286,7 +286,7 @@ def _generate_evm_for_instruction_r( # does not need to recurse (or be co-recursive with `emit_input_operands`). # HK: Indeed, this is eventualy the idea. Especialy now that I have implemented # the "needs duplication" algorithm that needs the same traversal and it's duplicated - assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack_map) + assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack) if inst in visited_instructions: # print("seen:", inst) @@ -317,35 +317,35 @@ def _generate_evm_for_instruction_r( inputs = inst.get_inputs() # REVIEW: the special handling in get_depth_in for lists # seems cursed, refactor - depth = stack_map.get_depth_in(inputs) + depth = stack.get_depth_in(inputs) assert depth is not StackModel.NOT_IN_STACK, "Operand not in stack" - to_be_replaced = stack_map.peek(depth) + to_be_replaced = stack.peek(depth) if to_be_replaced in inst.dup_requirements: - stack_map.dup(assembly, depth) - stack_map.poke(0, ret) + stack.dup(assembly, depth) + stack.poke(0, ret) else: - stack_map.poke(depth, ret) + stack.poke(depth, ret) return assembly # Step 2: Emit instruction's input operands - _emit_input_operands(ctx, assembly, inst, operands, stack_map) + _emit_input_operands(ctx, assembly, inst, operands, stack) # Step 3: Reorder stack if opcode in ["jnz", "jmp"]: assert isinstance(inst.parent.cfg_out, OrderedSet) b = next(iter(inst.parent.cfg_out)) target_stack = OrderedSet(b.in_vars_from(inst.parent)) - _stack_reorder(assembly, stack_map, target_stack) + _stack_reorder(assembly, stack, target_stack) - _stack_duplications(assembly, inst, stack_map, operands) + _stack_duplications(assembly, inst, stack, operands) # print("(inst)", inst) - _stack_reorder(assembly, stack_map, operands) + _stack_reorder(assembly, stack, operands) # Step 4: Push instruction's return value to stack - stack_map.pop(len(operands)) + stack.pop(len(operands)) if inst.ret is not None: - stack_map.push(inst.ret) + stack.push(inst.ret) # Step 5: Emit the EVM instruction(s) if opcode in ONE_TO_ONE_INSTRUCTIONS: @@ -386,8 +386,8 @@ def _generate_evm_for_instruction_r( ] ) label_counter += 1 - if stack_map.get_height() > 0 and stack_map.peek(0) in inst.dup_requirements: - stack_map.pop() + if stack.get_height() > 0 and stack.peek(0) in inst.dup_requirements: + stack.pop() assembly.append("POP") elif opcode == "call": assembly.append("CALL") @@ -455,7 +455,7 @@ def _emit_input_operands( assembly: list, inst: IRInstruction, ops: list[IRValueBase], - stack_map: StackModel, + stack: StackModel, ): # print("EMIT INPUTS FOR", inst) for op in ops: @@ -464,16 +464,14 @@ def _emit_input_operands( # but we need to add it to the stack map if inst.opcode != "invoke": assembly.append(f"_sym_{op.value}") - stack_map.push(op) + stack.push(op) continue if op.is_literal: assembly.extend([*PUSH(op.value)]) - stack_map.push(op) + stack.push(op) continue # print("RECURSE FOR", op, "TO:", ctx.dfg_outputs[op.value]) - assembly.extend( - _generate_evm_for_instruction_r(ctx, [], ctx.dfg_outputs[op.value], stack_map) - ) + assembly.extend(_generate_evm_for_instruction_r(ctx, [], ctx.dfg_outputs[op.value], stack)) if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") From 17f7fbf277dc4b17689c445e994a5e5ce0ac5299 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 15:33:21 +0200 Subject: [PATCH 301/471] get_depth cleanup --- vyper/venom/dfg.py | 8 +++----- vyper/venom/stack_model.py | 25 ++++++++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 94ae38da1c..c9e755d078 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -211,7 +211,7 @@ def _stack_duplications( for op in stack_ops: if op.is_literal or isinstance(op, IRLabel): continue - depth = stack.get_depth_in(op) + depth = stack.get_depth(op) assert depth is not StackModel.NOT_IN_STACK, "Operand not in stack" if op in inst.dup_requirements: stack.dup(assembly, depth) @@ -224,7 +224,7 @@ def _stack_reorder(assembly: list, stack: StackModel, stack_ops: list[IRValueBas for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) - depth = stack.get_depth_in(op) + depth = stack.get_depth(op) assert depth is not StackModel.NOT_IN_STACK, f"{op} not in stack: {stack.stack}" if depth == final_stack_depth: continue @@ -315,9 +315,7 @@ def _generate_evm_for_instruction_r( if opcode == "phi": ret = inst.get_outputs()[0] inputs = inst.get_inputs() - # REVIEW: the special handling in get_depth_in for lists - # seems cursed, refactor - depth = stack.get_depth_in(inputs) + depth = stack.get_shallowest_depth(inputs) assert depth is not StackModel.NOT_IN_STACK, "Operand not in stack" to_be_replaced = stack.peek(depth) if to_be_replaced in inst.dup_requirements: diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index a2fafccb69..913acd2e02 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -29,17 +29,14 @@ def push(self, op: IRValueBase) -> None: def pop(self, num: int = 1) -> None: del self.stack[len(self.stack) - num :] - def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: + def get_depth(self, op: IRValueBase | list[IRValueBase]) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ assert ( - isinstance(op, str) - or isinstance(op, int) - or isinstance(op, IRValueBase) - or isinstance(op, list) - ), f"get_depth_in takes IRValueBase or list, got '{op}'" + isinstance(op, str) or isinstance(op, int) or isinstance(op, IRValueBase) + ), f"get_depth takes IRValueBase or list, got '{op}'" for i, stack_op in enumerate(reversed(self.stack)): if isinstance(stack_op, IRValueBase): @@ -51,9 +48,19 @@ def get_depth_in(self, op: IRValueBase | list[IRValueBase]) -> int: return -i if isinstance(op, IRValueBase) and stack_op.value == op.value: return -i - # REVIEW: this branch seems cursed - elif isinstance(op, list) and stack_op in op: - return -i + + return StackModel.NOT_IN_STACK + + def get_shallowest_depth(self, ops: list[IRValueBase]) -> int: + """ + Returns the depth of the first matching operand in the stack map. + If the none of the operands in is `ops` is in the stack, returns NOT_IN_STACK. + """ + assert isinstance(ops, list), f"get_shallowest_depth takes list, got '{op}'" + + for i, stack_op in enumerate(reversed(self.stack)): + if isinstance(stack_op, IRValueBase) and stack_op in ops: + return -i return StackModel.NOT_IN_STACK From 08955f2a2bac08b0a3ec09a19e8224944a83c87e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 15:52:45 +0200 Subject: [PATCH 302/471] stack reorder and duplication always operate on IRValues --- vyper/venom/dfg.py | 8 ++++---- vyper/venom/stack_model.py | 19 +++++-------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index c9e755d078..f0f5d45034 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -217,8 +217,9 @@ def _stack_duplications( stack.dup(assembly, depth) -def _stack_reorder(assembly: list, stack: StackModel, stack_ops: list[IRValueBase]) -> None: - stack_ops = [x.value for x in stack_ops] +def _stack_reorder(assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVariable]) -> None: + # make a list so we can index it + stack_ops = [x for x in stack_ops] # print("ENTER reorder", stack.stack, operands) # start_len = len(assembly) for i in range(len(stack_ops)): @@ -332,7 +333,7 @@ def _generate_evm_for_instruction_r( if opcode in ["jnz", "jmp"]: assert isinstance(inst.parent.cfg_out, OrderedSet) b = next(iter(inst.parent.cfg_out)) - target_stack = OrderedSet(b.in_vars_from(inst.parent)) + target_stack = b.in_vars_from(inst.parent) _stack_reorder(assembly, stack, target_stack) _stack_duplications(assembly, inst, stack, operands) @@ -392,7 +393,6 @@ def _generate_evm_for_instruction_r( elif opcode == "staticcall": assembly.append("STATICCALL") elif opcode == "ret": - # assert len(inst.operands) == 2, "ret instruction takes two operands" assembly.append("JUMP") elif opcode == "return": assembly.append("RETURN") diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 913acd2e02..59c0ce5bd3 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -29,25 +29,16 @@ def push(self, op: IRValueBase) -> None: def pop(self, num: int = 1) -> None: del self.stack[len(self.stack) - num :] - def get_depth(self, op: IRValueBase | list[IRValueBase]) -> int: + def get_depth(self, op: IRValueBase) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ - assert ( - isinstance(op, str) or isinstance(op, int) or isinstance(op, IRValueBase) - ), f"get_depth takes IRValueBase or list, got '{op}'" + assert isinstance(op, IRValueBase), f"get_depth takes IRValueBase or list, got '{op}'" for i, stack_op in enumerate(reversed(self.stack)): - if isinstance(stack_op, IRValueBase): - # REVIEW: handling literals this way seems a bit cursed, - # why not use IRLiteral, so it is always IRValueBase? - if isinstance(op, str) and stack_op.value == op: - return -i - if isinstance(op, int) and stack_op.value == op: - return -i - if isinstance(op, IRValueBase) and stack_op.value == op.value: - return -i + if stack_op.value == op.value: + return -i return StackModel.NOT_IN_STACK @@ -59,7 +50,7 @@ def get_shallowest_depth(self, ops: list[IRValueBase]) -> int: assert isinstance(ops, list), f"get_shallowest_depth takes list, got '{op}'" for i, stack_op in enumerate(reversed(self.stack)): - if isinstance(stack_op, IRValueBase) and stack_op in ops: + if stack_op in ops: return -i return StackModel.NOT_IN_STACK From 9f6913d5794a7bfe84f9fbd62033da78ad7e5321 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Oct 2023 15:57:16 +0200 Subject: [PATCH 303/471] cleanups --- vyper/compiler/phases.py | 2 +- vyper/ir/bb_optimizer.py | 9 ++------- vyper/ir/ir_to_bb_pass.py | 14 +++++++------- vyper/venom/basicblock.py | 5 +++-- vyper/venom/dfg.py | 9 +++++---- vyper/venom/ir.py | 6 +++--- vyper/venom/pass_constant_propagation.py | 2 +- vyper/venom/pass_dft.py | 2 +- vyper/venom/stack_model.py | 2 +- 9 files changed, 24 insertions(+), 27 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 0e8498ec56..6c73719eda 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -7,7 +7,6 @@ from vyper.codegen import module from vyper.codegen.core import anchor_opt_level from vyper.codegen.global_context import GlobalContext -from vyper.venom.ir import generate_ir from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel, Settings from vyper.exceptions import StructureException @@ -16,6 +15,7 @@ from vyper.semantics import set_data_positions, validate_semantics from vyper.semantics.types.function import ContractFunctionT from vyper.typing import InterfaceImports, StorageLayout +from vyper.venom.ir import generate_ir class CompilerData: diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index 609d82ca8f..c4f5ca56a4 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -1,11 +1,6 @@ -from vyper.venom.basicblock import ( - BB_TERMINATORS, - IRBasicBlock, - IRInstruction, - IRLabel, -) -from vyper.venom.function import IRFunction from vyper.utils import OrderedSet, ir_pass +from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRLabel +from vyper.venom.function import IRFunction def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index 47074da8ec..df3522a2c6 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,6 +1,11 @@ from typing import Optional -from vyper.venom.dfg import generate_evm +from vyper.codegen.ir_node import IRnode +from vyper.compiler.settings import OptimizationLevel +from vyper.evm.opcodes import get_opcodes +from vyper.ir.compile_ir import is_mem_sym, is_symbol +from vyper.semantics.types.function import ContractFunctionT +from vyper.utils import MemoryPositions, OrderedSet from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, @@ -10,13 +15,8 @@ IRVariable, MemType, ) +from vyper.venom.dfg import generate_evm from vyper.venom.function import IRFunction -from vyper.codegen.ir_node import IRnode -from vyper.compiler.settings import OptimizationLevel -from vyper.evm.opcodes import get_opcodes -from vyper.ir.compile_ir import is_mem_sym, is_symbol -from vyper.semantics.types.function import ContractFunctionT -from vyper.utils import MemoryPositions, OrderedSet BINARY_IR_INSTRUCTIONS = [ "eq", diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 7e4cf1ae43..81fb2a9b3a 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -108,8 +108,9 @@ def __init__( class IRLabel(IRValueBase): # REVIEW: what do the values of is_symbol mean? - # HK: is_symbol is used to indicate if the label is a symbol coming from the initial IR. Like a function name, - # that I try to preserve when optimizing so that the final IR is easier to read. + # HK: is_symbol is used to indicate if the label is a symbol coming from + # the initial IR. Like a function name, that I try to preserve when + # optimizing so that the final IR is easier to read. """ IRLabel represents a label in IR. A label is a string that starts with a %. """ diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index f0f5d45034..b591668be8 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -1,3 +1,5 @@ +from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly +from vyper.utils import MemoryPositions, OrderedSet from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, @@ -8,8 +10,6 @@ ) from vyper.venom.function import IRFunction from vyper.venom.stack_model import StackModel -from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly -from vyper.utils import MemoryPositions, OrderedSet ONE_TO_ONE_INSTRUCTIONS = [ "revert", @@ -285,8 +285,9 @@ def _generate_evm_for_instruction_r( # REVIEW: I think it would be better to have an explicit step, # `reorder instructions per DFG`, and then `generate_evm_for_instruction` # does not need to recurse (or be co-recursive with `emit_input_operands`). - # HK: Indeed, this is eventualy the idea. Especialy now that I have implemented - # the "needs duplication" algorithm that needs the same traversal and it's duplicated + # HK: Indeed, this is eventualy the idea. Especialy now that I have + # implemented the "needs duplication" algorithm that needs the same + # traversal and it's duplicated assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack) if inst in visited_instructions: diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index 6e9d0d42a7..dcfa5e3e1a 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -1,9 +1,6 @@ from typing import Optional -from vyper.venom.dfg import convert_ir_to_dfg -from vyper.venom.function import IRFunction from vyper.codegen.ir_node import IRnode -from vyper.venom.pass_constant_propagation import ir_pass_constant_propagation from vyper.compiler.settings import OptimizationLevel from vyper.ir.bb_optimizer import ( calculate_cfg_in, @@ -13,6 +10,9 @@ ir_pass_remove_unreachable_blocks, ) from vyper.ir.ir_to_bb_pass import convert_ir_basicblock +from vyper.venom.dfg import convert_ir_to_dfg +from vyper.venom.function import IRFunction +from vyper.venom.pass_constant_propagation import ir_pass_constant_propagation def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: diff --git a/vyper/venom/pass_constant_propagation.py b/vyper/venom/pass_constant_propagation.py index 75552331e9..94b556124e 100644 --- a/vyper/venom/pass_constant_propagation.py +++ b/vyper/venom/pass_constant_propagation.py @@ -1,6 +1,6 @@ +from vyper.utils import ir_pass from vyper.venom.basicblock import IRBasicBlock from vyper.venom.function import IRFunction -from vyper.utils import OrderedSet, ir_pass def _process_basic_block(ctx: IRFunction, bb: IRBasicBlock): diff --git a/vyper/venom/pass_dft.py b/vyper/venom/pass_dft.py index 564558ee87..c8c01e11f3 100644 --- a/vyper/venom/pass_dft.py +++ b/vyper/venom/pass_dft.py @@ -1,6 +1,6 @@ +from vyper.utils import OrderedSet, ir_pass from vyper.venom.basicblock import IRBasicBlock, IRInstruction from vyper.venom.function import IRFunction -from vyper.utils import OrderedSet, ir_pass visited_instructions = OrderedSet() diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 59c0ce5bd3..0d54bc91dd 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -47,7 +47,7 @@ def get_shallowest_depth(self, ops: list[IRValueBase]) -> int: Returns the depth of the first matching operand in the stack map. If the none of the operands in is `ops` is in the stack, returns NOT_IN_STACK. """ - assert isinstance(ops, list), f"get_shallowest_depth takes list, got '{op}'" + assert isinstance(ops, list), f"get_shallowest_depth takes list, got '{ops}'" for i, stack_op in enumerate(reversed(self.stack)): if stack_op in ops: From c26539646caace89c23edf232e46ffb44404887b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Mon, 30 Oct 2023 16:25:36 -0400 Subject: [PATCH 304/471] further review --- vyper/venom/basicblock.py | 19 ++++++++++--------- vyper/venom/dfg.py | 9 ++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 81fb2a9b3a..a7516b179b 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -107,14 +107,12 @@ def __init__( class IRLabel(IRValueBase): - # REVIEW: what do the values of is_symbol mean? - # HK: is_symbol is used to indicate if the label is a symbol coming from - # the initial IR. Like a function name, that I try to preserve when - # optimizing so that the final IR is easier to read. """ IRLabel represents a label in IR. A label is a string that starts with a %. """ + # is_symbol is used to indicate if the label came from upstream + # (like a function name, try to preserve it in optimization passes) is_symbol: bool = False def __init__(self, value: str, is_symbol: bool = False) -> None: @@ -135,6 +133,7 @@ class IRInstruction: operands: list[IRValueBase] # REVIEW: rename to lhs? # HK: Maybe outputs is better? + # REVIEW: hmm not sure. to discuss offline ret: Optional[IRValueBase] # set of live variables at this instruction liveness: OrderedSet[IRVariable] @@ -180,11 +179,10 @@ def get_inputs(self) -> list[IRValueBase]: def get_outputs(self) -> list[IRValueBase]: return [self.ret] if self.ret else [] - # REVIEW: use of `dict` here seems a bit weird (what is equality on operands?) - # HK: replacements happen "key" replaced by "value", dict is used as a way to represent this. def replace_operands(self, replacements: dict) -> None: """ Update operands with replacements. + replacements are represented using a dict: "key" is replaced by "value". """ for i, operand in enumerate(self.operands): if operand in replacements: @@ -282,6 +280,10 @@ def in_vars_from(self, source: "IRBasicBlock") -> OrderedSet[IRVariable]: # REVIEW: might be nice if some of these instructions # were more structured. # HK: Can you elaborate on this? I'm not sure what you mean. + # REVIEW: I meant, if there were classs like + # IRPhi(IRInstruction) + # IRStore(IRInstruction) + # etc. if inst.opcode == "phi": if inst.operands[0] == source.label: liveness.add(inst.operands[1]) @@ -323,9 +325,8 @@ def is_terminated(self) -> bool: """ Check if the basic block is terminal, i.e. the last instruction is a terminator. """ - # REVIEW: should this be an assert (like `is_terminal()`)? - # HK: I think it's fine to return False here, since we use this to check - # if we can/needto append instructions to the basic block. + # it's ok to return False here, since we use this to check + # if we can/need to append instructions to the basic block. if len(self.instructions) == 0: return False return self.instructions[-1].opcode in BB_TERMINATORS diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index b591668be8..5149964f5d 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -273,13 +273,11 @@ def _generate_evm_for_instruction_r( for op in inst.get_outputs(): for target in ctx.dfg_inputs.get(op.value, []): - # REVIEW: what does this line do? - # HK: it skips instructions that are not in the same basic block - # so we don't cross basic block boundaries + # skip instructions that are not in the same basic block + # so we don't cross basic block boundaries if target.parent != inst.parent: continue - # REVIEW: what does this line do? - # HK: it skips instructions that are not in the same fence + # it skip instructions that are not in the same fence group if target.fence_id != inst.fence_id: continue # REVIEW: I think it would be better to have an explicit step, @@ -288,6 +286,7 @@ def _generate_evm_for_instruction_r( # HK: Indeed, this is eventualy the idea. Especialy now that I have # implemented the "needs duplication" algorithm that needs the same # traversal and it's duplicated + # REVIEW: OK. to discuss further offline assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack) if inst in visited_instructions: From f185ecd9e2b1afda787bcaccd3485b2aecc863b4 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Mon, 30 Oct 2023 16:27:52 -0400 Subject: [PATCH 305/471] some organizational comments --- vyper/ir/bb_optimizer.py | 1 + vyper/ir/ir_to_bb_pass.py | 1 + 2 files changed, 2 insertions(+) diff --git a/vyper/ir/bb_optimizer.py b/vyper/ir/bb_optimizer.py index c4f5ca56a4..ff4fb00dee 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/ir/bb_optimizer.py @@ -1,3 +1,4 @@ +# REVIEW: move this to vyper/venom/ from vyper.utils import OrderedSet, ir_pass from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRLabel from vyper.venom.function import IRFunction diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/ir/ir_to_bb_pass.py index df3522a2c6..170ebe2369 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/ir/ir_to_bb_pass.py @@ -1,3 +1,4 @@ +# REVIEW: move to vyper/venom/ from typing import Optional from vyper.codegen.ir_node import IRnode From af5819f7799e648635bc7ecf19f1fab3532987c3 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Mon, 30 Oct 2023 16:29:46 -0400 Subject: [PATCH 306/471] a couple more comments --- vyper/venom/pass_constant_propagation.py | 1 + vyper/venom/pass_dft.py | 1 + 2 files changed, 2 insertions(+) diff --git a/vyper/venom/pass_constant_propagation.py b/vyper/venom/pass_constant_propagation.py index 94b556124e..b1474d8fb7 100644 --- a/vyper/venom/pass_constant_propagation.py +++ b/vyper/venom/pass_constant_propagation.py @@ -1,3 +1,4 @@ +# REVIEW: maybe this can be moved to vyper/venom/passes/ from vyper.utils import ir_pass from vyper.venom.basicblock import IRBasicBlock from vyper.venom.function import IRFunction diff --git a/vyper/venom/pass_dft.py b/vyper/venom/pass_dft.py index c8c01e11f3..8ace7d07ee 100644 --- a/vyper/venom/pass_dft.py +++ b/vyper/venom/pass_dft.py @@ -1,3 +1,4 @@ +# REVIEW: maybe this can be moved to vyper/venom/passes/ from vyper.utils import OrderedSet, ir_pass from vyper.venom.basicblock import IRBasicBlock, IRInstruction from vyper.venom.function import IRFunction From 4769aa4a218743d469126f8925101ac5dcdeafac Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 31 Oct 2023 11:42:35 +0200 Subject: [PATCH 307/471] restructure file tree --- vyper/compiler/phases.py | 2 +- vyper/{ir => venom}/bb_optimizer.py | 1 - vyper/venom/ir.py | 6 +++--- vyper/{ir => venom}/ir_to_bb_pass.py | 1 - vyper/venom/{ => passes}/pass_constant_propagation.py | 1 - vyper/venom/{ => passes}/pass_dft.py | 1 - 6 files changed, 4 insertions(+), 8 deletions(-) rename vyper/{ir => venom}/bb_optimizer.py (99%) rename vyper/{ir => venom}/ir_to_bb_pass.py (99%) rename vyper/venom/{ => passes}/pass_constant_propagation.py (85%) rename vyper/venom/{ => passes}/pass_dft.py (95%) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 6c73719eda..61365dbc62 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -11,7 +11,7 @@ from vyper.compiler.settings import OptimizationLevel, Settings from vyper.exceptions import StructureException from vyper.ir import compile_ir, optimizer -from vyper.ir.ir_to_bb_pass import generate_assembly_experimental +from vyper.venom.ir_to_bb_pass import generate_assembly_experimental from vyper.semantics import set_data_positions, validate_semantics from vyper.semantics.types.function import ContractFunctionT from vyper.typing import InterfaceImports, StorageLayout diff --git a/vyper/ir/bb_optimizer.py b/vyper/venom/bb_optimizer.py similarity index 99% rename from vyper/ir/bb_optimizer.py rename to vyper/venom/bb_optimizer.py index ff4fb00dee..c4f5ca56a4 100644 --- a/vyper/ir/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -1,4 +1,3 @@ -# REVIEW: move this to vyper/venom/ from vyper.utils import OrderedSet, ir_pass from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRLabel from vyper.venom.function import IRFunction diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index dcfa5e3e1a..cda2d7b68d 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -2,17 +2,17 @@ from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel -from vyper.ir.bb_optimizer import ( +from vyper.venom.bb_optimizer import ( calculate_cfg_in, calculate_liveness, ir_pass_optimize_empty_blocks, ir_pass_optimize_unused_variables, ir_pass_remove_unreachable_blocks, ) -from vyper.ir.ir_to_bb_pass import convert_ir_basicblock +from vyper.venom.ir_to_bb_pass import convert_ir_basicblock from vyper.venom.dfg import convert_ir_to_dfg from vyper.venom.function import IRFunction -from vyper.venom.pass_constant_propagation import ir_pass_constant_propagation +from vyper.venom.passes.pass_constant_propagation import ir_pass_constant_propagation def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: diff --git a/vyper/ir/ir_to_bb_pass.py b/vyper/venom/ir_to_bb_pass.py similarity index 99% rename from vyper/ir/ir_to_bb_pass.py rename to vyper/venom/ir_to_bb_pass.py index 170ebe2369..df3522a2c6 100644 --- a/vyper/ir/ir_to_bb_pass.py +++ b/vyper/venom/ir_to_bb_pass.py @@ -1,4 +1,3 @@ -# REVIEW: move to vyper/venom/ from typing import Optional from vyper.codegen.ir_node import IRnode diff --git a/vyper/venom/pass_constant_propagation.py b/vyper/venom/passes/pass_constant_propagation.py similarity index 85% rename from vyper/venom/pass_constant_propagation.py rename to vyper/venom/passes/pass_constant_propagation.py index b1474d8fb7..94b556124e 100644 --- a/vyper/venom/pass_constant_propagation.py +++ b/vyper/venom/passes/pass_constant_propagation.py @@ -1,4 +1,3 @@ -# REVIEW: maybe this can be moved to vyper/venom/passes/ from vyper.utils import ir_pass from vyper.venom.basicblock import IRBasicBlock from vyper.venom.function import IRFunction diff --git a/vyper/venom/pass_dft.py b/vyper/venom/passes/pass_dft.py similarity index 95% rename from vyper/venom/pass_dft.py rename to vyper/venom/passes/pass_dft.py index 8ace7d07ee..c8c01e11f3 100644 --- a/vyper/venom/pass_dft.py +++ b/vyper/venom/passes/pass_dft.py @@ -1,4 +1,3 @@ -# REVIEW: maybe this can be moved to vyper/venom/passes/ from vyper.utils import OrderedSet, ir_pass from vyper.venom.basicblock import IRBasicBlock, IRInstruction from vyper.venom.function import IRFunction From 56a08de5c5b11a69288aa85f9271fcd18f8e7606 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 31 Oct 2023 12:38:12 +0200 Subject: [PATCH 308/471] add review comment --- vyper/venom/dfg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 5149964f5d..0b56e79783 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -266,6 +266,7 @@ def _generate_evm_for_basicblock_r( # REVIEW: would this be better as a class? +# HK: Let's consider it after the pass_dft refactor def _generate_evm_for_instruction_r( ctx: IRFunction, assembly: list, inst: IRInstruction, stack: StackModel ) -> list[str]: From 5927a76be95216f91e5382ec509f3845b0f549fb Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 31 Oct 2023 10:24:16 -0400 Subject: [PATCH 309/471] api hygiene --- vyper/venom/dfg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 0b56e79783..d2254028d2 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -288,7 +288,7 @@ def _generate_evm_for_instruction_r( # implemented the "needs duplication" algorithm that needs the same # traversal and it's duplicated # REVIEW: OK. to discuss further offline - assembly = _generate_evm_for_instruction_r(ctx, assembly, target, stack) + assembly.extend(_generate_evm_for_instruction_r(ctx, [], target, stack)) if inst in visited_instructions: # print("seen:", inst) From 2c725cb05cb6886b40458e75f23430cf9e2f8bfa Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 31 Oct 2023 10:42:22 -0400 Subject: [PATCH 310/471] use temp var in StackModel.swap --- vyper/venom/stack_model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 0d54bc91dd..b9348088ab 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -87,7 +87,7 @@ def swap(self, assembly: list[str], depth: int) -> None: assert depth < 0, "Cannot swap positive depth" assembly.append(f"SWAP{-depth}") - self.stack[depth - 1], self.stack[-1] = ( - self.stack[-1], - self.stack[depth - 1], - ) + top = self.stack[-1] + self.stack[-1] = self.stack[depth - 1] + self.stack[depth - 1] = top + # REVIEW: maybe have a convenience function which swaps depth1 and depth2 From 666aea1196118c8ff61013a04b03926ec5d3ab80 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 31 Oct 2023 10:43:07 -0400 Subject: [PATCH 311/471] add a couple review comments --- vyper/venom/dfg.py | 15 ++++++++++++++- vyper/venom/stack_model.py | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index d2254028d2..232190c54c 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -113,6 +113,8 @@ def _compute_inst_dup_requirements_r( visited: OrderedSet, last_seen: dict, ) -> None: + # print("DUP REQUIREMENTS (inst)", inst) + for op in inst.get_outputs(): for target in ctx.dfg_inputs.get(op.value, []): if target.parent != inst.parent: @@ -127,6 +129,7 @@ def _compute_inst_dup_requirements_r( visited.add(inst) if inst.opcode == "phi": + # special handling for phi downstream return for op in inst.get_inputs(): @@ -141,6 +144,11 @@ def _compute_inst_dup_requirements_r( target.dup_requirements.add(op) last_seen[op.value] = inst + # for op in inst.get_inputs(): + # target = ctx.dfg_outputs[op.value] + # if target.dup_requirements: + # print("DUP REQUIREMENTS (target)", op, target.dup_requirements, target) + return @@ -337,10 +345,15 @@ def _generate_evm_for_instruction_r( target_stack = b.in_vars_from(inst.parent) _stack_reorder(assembly, stack, target_stack) + # print("pre-dups (inst)", inst.dup_requirements, stack.stack, inst) + + # REVIEW: doing duplications and then reorder in + # separate steps can result in less optimal code _stack_duplications(assembly, inst, stack, operands) + # print("post-dups (inst)", stack.stack, inst) - # print("(inst)", inst) _stack_reorder(assembly, stack, operands) + # print("post-reorder (inst)", stack.stack, inst) # Step 4: Push instruction's return value to stack stack.pop(len(operands)) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index b9348088ab..1b0552d92c 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -77,6 +77,7 @@ def dup(self, assembly: list[str], depth: int) -> None: assembly.append(f"DUP{-(depth-1)}") self.stack.append(self.peek(depth)) + # REVIEW: use positive indices (and hide the negation inside StackModel implementation) def swap(self, assembly: list[str], depth: int) -> None: """ Swaps the operand at the given depth in the stack map with the top of the stack. From a194747492e4d3fe061fe4848986e28f473a0d0f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 31 Oct 2023 16:57:14 +0200 Subject: [PATCH 312/471] index outputs and inputs by pointer of variable --- vyper/venom/dfg.py | 20 +++++++++----------- vyper/venom/passes/pass_dft.py | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 232190c54c..97dc2d82bd 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -94,14 +94,12 @@ def convert_ir_to_dfg(ctx: IRFunction) -> None: res = inst.get_outputs() for op in operands: - ctx.dfg_inputs[op.value] = ( - [inst] - if ctx.dfg_inputs.get(op.value) is None - else ctx.dfg_inputs[op.value] + [inst] + ctx.dfg_inputs[op] = ( + [inst] if ctx.dfg_inputs.get(op) is None else ctx.dfg_inputs[op] + [inst] ) for op in res: - ctx.dfg_outputs[op.value] = inst + ctx.dfg_outputs[op] = inst # Build DUP requirements _compute_dup_requirements(ctx) @@ -116,7 +114,7 @@ def _compute_inst_dup_requirements_r( # print("DUP REQUIREMENTS (inst)", inst) for op in inst.get_outputs(): - for target in ctx.dfg_inputs.get(op.value, []): + for target in ctx.dfg_inputs.get(op, []): if target.parent != inst.parent: # REVIEW: produced by parent.out_vars continue @@ -133,7 +131,7 @@ def _compute_inst_dup_requirements_r( return for op in inst.get_inputs(): - target = ctx.dfg_outputs[op.value] + target = ctx.dfg_outputs[op] if target.parent != inst.parent: continue _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) @@ -145,7 +143,7 @@ def _compute_inst_dup_requirements_r( last_seen[op.value] = inst # for op in inst.get_inputs(): - # target = ctx.dfg_outputs[op.value] + # target = ctx.dfg_outputs[op] # if target.dup_requirements: # print("DUP REQUIREMENTS (target)", op, target.dup_requirements, target) @@ -281,7 +279,7 @@ def _generate_evm_for_instruction_r( global label_counter for op in inst.get_outputs(): - for target in ctx.dfg_inputs.get(op.value, []): + for target in ctx.dfg_inputs.get(op, []): # skip instructions that are not in the same basic block # so we don't cross basic block boundaries if target.parent != inst.parent: @@ -482,8 +480,8 @@ def _emit_input_operands( assembly.extend([*PUSH(op.value)]) stack.push(op) continue - # print("RECURSE FOR", op, "TO:", ctx.dfg_outputs[op.value]) - assembly.extend(_generate_evm_for_instruction_r(ctx, [], ctx.dfg_outputs[op.value], stack)) + # print("RECURSE FOR", op, "TO:", ctx.dfg_outputs[op]) + assembly.extend(_generate_evm_for_instruction_r(ctx, [], ctx.dfg_outputs[op], stack)) if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") diff --git a/vyper/venom/passes/pass_dft.py b/vyper/venom/passes/pass_dft.py index c8c01e11f3..9b2d452936 100644 --- a/vyper/venom/passes/pass_dft.py +++ b/vyper/venom/passes/pass_dft.py @@ -7,7 +7,7 @@ def _emit_operands_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction) -> None: for op in inst.get_inputs(): - target = ctx.dfg_outputs.get(op.value) + target = ctx.dfg_outputs.get(op) if target is None: continue _process_instruction(ctx, bb, target) From 6ea9e24e01aa56dddd4de4b73cca1cf2588f42ba Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 31 Oct 2023 10:58:14 -0400 Subject: [PATCH 313/471] add organizational comment --- vyper/venom/ir.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index cda2d7b68d..9ca6a37cd2 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -1,3 +1,4 @@ +# REVIEW: maybe this should be __init__.py (or some name less generic than 'ir.py') from typing import Optional from vyper.codegen.ir_node import IRnode From cc9a70b5d336939c9fec1a20fcb7b113f59d6d69 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 31 Oct 2023 17:04:03 +0200 Subject: [PATCH 314/471] remove dead DFGNode --- vyper/venom/dfg.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 97dc2d82bd..241a75c5cc 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -63,17 +63,6 @@ ] -class DFGNode: - value: IRInstruction | IRValueBase - predecessors: list["DFGNode"] - successors: list["DFGNode"] - - def __init__(self, value: IRInstruction | IRValueBase): - self.value = value - self.predecessors = [] - self.successors = [] - - def convert_ir_to_dfg(ctx: IRFunction) -> None: # Reset DFG # REVIEW: dfg inputs is all, flattened inputs to a given variable From d738776b22054aa18b8657845aac9eeb95de2eac Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 31 Oct 2023 17:13:19 +0200 Subject: [PATCH 315/471] remove comments --- vyper/venom/dfg.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 241a75c5cc..2af61cc786 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -131,11 +131,6 @@ def _compute_inst_dup_requirements_r( target.dup_requirements.add(op) last_seen[op.value] = inst - # for op in inst.get_inputs(): - # target = ctx.dfg_outputs[op] - # if target.dup_requirements: - # print("DUP REQUIREMENTS (target)", op, target.dup_requirements, target) - return From 6057325bc7311a491034908bb4cff3d7e1b82716 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 31 Oct 2023 11:34:26 -0400 Subject: [PATCH 316/471] factor out DFG data structure into class additionally, - clarity: rename all `fen` variables to `fence_id` - organizational: move generate_evm to ir.py --- vyper/compiler/phases.py | 3 +- vyper/venom/basicblock.py | 2 +- vyper/venom/dfg.py | 144 +++++++++++++++++++---------------- vyper/venom/function.py | 6 +- vyper/venom/ir.py | 12 ++- vyper/venom/ir_to_bb_pass.py | 8 -- 6 files changed, 93 insertions(+), 82 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 61365dbc62..f950ddc6f2 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -11,11 +11,10 @@ from vyper.compiler.settings import OptimizationLevel, Settings from vyper.exceptions import StructureException from vyper.ir import compile_ir, optimizer -from vyper.venom.ir_to_bb_pass import generate_assembly_experimental from vyper.semantics import set_data_positions, validate_semantics from vyper.semantics.types.function import ContractFunctionT from vyper.typing import InterfaceImports, StorageLayout -from vyper.venom.ir import generate_ir +from vyper.venom.ir import generate_ir, generate_assembly_experimental class CompilerData: diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index a7516b179b..2a188679ae 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -176,7 +176,7 @@ def get_inputs(self) -> list[IRValueBase]: """ return [op for op in self.operands if isinstance(op, IRVariable)] - def get_outputs(self) -> list[IRValueBase]: + def get_outputs(self) -> list[IRVariable]: return [self.ret] if self.ret else [] def replace_operands(self, replacements: dict) -> None: diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 97dc2d82bd..19a3808301 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -63,50 +63,84 @@ ] -class DFGNode: - value: IRInstruction | IRValueBase - predecessors: list["DFGNode"] - successors: list["DFGNode"] +# DataFlow Graph +class DFG: + # REVIEW: dfg inputs is all, flattened inputs to a given variable + _dfg_inputs: dict[IRVariable, list[IRInstruction]] + # REVIEW: dfg outputs is the instruction which produces a variable + _dfg_outputs: dict[IRVariable, IRInstruction] - def __init__(self, value: IRInstruction | IRValueBase): - self.value = value - self.predecessors = [] - self.successors = [] + def __init__(self): + self._dfg_inputs = dict() + self._dfg_outputs = dict() + def get_inputs(self, op: IRVariable) -> list[IRInstruction]: + return self._dfg_inputs.get(op, []) -def convert_ir_to_dfg(ctx: IRFunction) -> None: - # Reset DFG - # REVIEW: dfg inputs is all, flattened inputs to a given variable - ctx.dfg_inputs = dict() - # REVIEW: dfg outputs is the instruction which produces a variable - ctx.dfg_outputs = dict() - for bb in ctx.basic_blocks: - for inst in bb.instructions: - inst.dup_requirements = OrderedSet() - inst.fence_id = -1 - operands = inst.get_inputs() - operands.extend(inst.get_outputs()) + def get_output(self, op: IRVariable) -> IRInstruction: + return self._dfg_outputs[op] + + @classmethod + def from_ir_function(cls, ctx: IRFunction): + dfg = cls() + + for bb in ctx.basic_blocks: + for inst in bb.instructions: + inst.dup_requirements = OrderedSet() + inst.fence_id = -1 + operands = inst.get_inputs() + operands.extend(inst.get_outputs()) + + # Build DFG + for bb in ctx.basic_blocks: + # %15 = add %13 %14 + # dfg_outputs of %15 is (%15 = add %13 %14) + # dfg_inputs of %15 is [(%13 = ...), (%14 = ...), ... None: + dfg = DFG.from_ir_function(ctx) + ctx.dfg = dfg + + _compute_dup_requirements(ctx) + + +def _compute_dup_requirements(ctx: IRFunction) -> None: + fence_id = 0 for bb in ctx.basic_blocks: for inst in bb.instructions: - operands = inst.get_inputs() - res = inst.get_outputs() + inst.fence_id = fence_id + if inst.volatile: + fence_id += 1 - for op in operands: - ctx.dfg_inputs[op] = ( - [inst] if ctx.dfg_inputs.get(op) is None else ctx.dfg_inputs[op] + [inst] - ) + visited = OrderedSet() + last_seen = dict() - for op in res: - ctx.dfg_outputs[op] = inst + dfg = ctx.dfg + for inst in bb.instructions: + _compute_inst_dup_requirements_r(dfg, inst, visited, last_seen) - # Build DUP requirements - _compute_dup_requirements(ctx) + out_vars = bb.out_vars + for inst in reversed(bb.instructions): + for op in inst.get_inputs(): + if op in out_vars: + inst.dup_requirements.add(op) def _compute_inst_dup_requirements_r( - ctx: IRFunction, + dfg: DFG, inst: IRInstruction, visited: OrderedSet, last_seen: dict, @@ -114,13 +148,13 @@ def _compute_inst_dup_requirements_r( # print("DUP REQUIREMENTS (inst)", inst) for op in inst.get_outputs(): - for target in ctx.dfg_inputs.get(op, []): + for target in dfg.get_inputs(op): if target.parent != inst.parent: # REVIEW: produced by parent.out_vars continue if target.fence_id != inst.fence_id: continue - _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) + _compute_inst_dup_requirements_r(dfg, target, visited, last_seen) if inst in visited: return @@ -131,49 +165,29 @@ def _compute_inst_dup_requirements_r( return for op in inst.get_inputs(): - target = ctx.dfg_outputs[op] + target = dfg.get_output(op) if target.parent != inst.parent: continue - _compute_inst_dup_requirements_r(ctx, target, visited, last_seen) + _compute_inst_dup_requirements_r(dfg, target, visited, last_seen) for op in inst.get_inputs(): - target = last_seen.get(op.value, None) + target = last_seen.get(op.value) if target: target.dup_requirements.add(op) last_seen[op.value] = inst # for op in inst.get_inputs(): - # target = ctx.dfg_outputs[op] + # target = dfg.get_output(op) # if target.dup_requirements: # print("DUP REQUIREMENTS (target)", op, target.dup_requirements, target) - return - - -def _compute_dup_requirements(ctx: IRFunction) -> None: - fen = 0 - for bb in ctx.basic_blocks: - for inst in bb.instructions: - inst.fence_id = fen - if inst.volatile: - fen += 1 - - visited = OrderedSet() - last_seen = dict() - for inst in bb.instructions: - _compute_inst_dup_requirements_r(ctx, inst, visited, last_seen) - - out_vars = bb.out_vars - for inst in reversed(bb.instructions): - for op in inst.get_inputs(): - if op in out_vars: - inst.dup_requirements.add(op) - visited_instructions = None # {IRInstruction} visited_basicblocks = None # {IRBasicBlock} +# REVIEW: might be cleaner if evm generation were separate from DFG computation +# (but i can see why it was done this way) def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: global visited_instructions, visited_basicblocks asm = [] @@ -254,11 +268,11 @@ def _generate_evm_for_basicblock_r( asm.append(f"_sym_{basicblock.label}") asm.append("JUMPDEST") - fen = 0 + fence_id = 0 for inst in basicblock.instructions: - inst.fence_id = fen + inst.fence_id = fence_id if inst.volatile: - fen += 1 + fence_id += 1 for inst in basicblock.instructions: asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack) @@ -279,7 +293,7 @@ def _generate_evm_for_instruction_r( global label_counter for op in inst.get_outputs(): - for target in ctx.dfg_inputs.get(op, []): + for target in ctx.dfg.get_inputs(op): # skip instructions that are not in the same basic block # so we don't cross basic block boundaries if target.parent != inst.parent: @@ -481,7 +495,7 @@ def _emit_input_operands( stack.push(op) continue # print("RECURSE FOR", op, "TO:", ctx.dfg_outputs[op]) - assembly.extend(_generate_evm_for_instruction_r(ctx, [], ctx.dfg_outputs[op], stack)) + assembly.extend(_generate_evm_for_instruction_r(ctx, [], ctx.dfg.get_output(op), stack)) if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 22751d61b0..981c4f59f5 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Any from vyper.venom.basicblock import ( IRBasicBlock, @@ -19,11 +19,11 @@ class IRFunction: args: list basic_blocks: list[IRBasicBlock] data_segment: list[IRInstruction] - dfg_inputs = dict[str, IRInstruction] - dfg_outputs = dict[str, IRInstruction] last_label: int last_variable: int + dfg: Any = None # dfg will be added in convert_ir_to_dfg pass + def __init__(self, name: IRLabel) -> None: self.name = name self.args = [] diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index 9ca6a37cd2..05277edd78 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -11,11 +11,17 @@ ir_pass_remove_unreachable_blocks, ) from vyper.venom.ir_to_bb_pass import convert_ir_basicblock -from vyper.venom.dfg import convert_ir_to_dfg +from vyper.venom.dfg import calculate_dfg, generate_evm from vyper.venom.function import IRFunction from vyper.venom.passes.pass_constant_propagation import ir_pass_constant_propagation +def generate_assembly_experimental( + ir: IRFunction, optimize: Optional[OptimizationLevel] = None +) -> list[str]: + return generate_evm(ir, 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) @@ -34,14 +40,14 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF calculate_cfg_in(ctx) calculate_liveness(ctx) - convert_ir_to_dfg(ctx) + calculate_dfg(ctx) changes += ir_pass_constant_propagation(ctx) # changes += ir_pass_dft(ctx) calculate_cfg_in(ctx) calculate_liveness(ctx) - convert_ir_to_dfg(ctx) + calculate_dfg(ctx) if changes == 0: break diff --git a/vyper/venom/ir_to_bb_pass.py b/vyper/venom/ir_to_bb_pass.py index df3522a2c6..1a3ba3798e 100644 --- a/vyper/venom/ir_to_bb_pass.py +++ b/vyper/venom/ir_to_bb_pass.py @@ -15,7 +15,6 @@ IRVariable, MemType, ) -from vyper.venom.dfg import generate_evm from vyper.venom.function import IRFunction BINARY_IR_INSTRUCTIONS = [ @@ -84,13 +83,6 @@ def _get_symbols_common(a: dict, b: dict) -> dict: ret[k] = a[k], b[k] return ret - -def generate_assembly_experimental( - ir: IRnode, optimize: Optional[OptimizationLevel] = None -) -> list[str]: - return generate_evm(ir, optimize is OptimizationLevel.NONE) - - def convert_ir_basicblock(ir: IRnode) -> IRFunction: global_function = IRFunction(IRLabel("global")) _convert_ir_basicblock(global_function, ir, {}, OrderedSet(), {}) From 339a79c2b5c1c15e18167ffd01c5c232221595e8 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 31 Oct 2023 11:55:43 -0400 Subject: [PATCH 317/471] rename get_output to get_assignment_instruction also, - slight cleanup of dup requirement reset subroutine --- vyper/venom/dfg.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 19a3808301..a4db1747ce 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -65,37 +65,32 @@ # DataFlow Graph class DFG: - # REVIEW: dfg inputs is all, flattened inputs to a given variable _dfg_inputs: dict[IRVariable, list[IRInstruction]] - # REVIEW: dfg outputs is the instruction which produces a variable + _dfg_outputs: dict[IRVariable, IRInstruction] def __init__(self): self._dfg_inputs = dict() self._dfg_outputs = dict() + # return all, flattened inputs to a given variable def get_inputs(self, op: IRVariable) -> list[IRInstruction]: return self._dfg_inputs.get(op, []) - def get_output(self, op: IRVariable) -> IRInstruction: + # the instruction which produces this variable. + def get_producing_instruction(self, op: IRVariable) -> IRInstruction: return self._dfg_outputs[op] @classmethod def from_ir_function(cls, ctx: IRFunction): dfg = cls() - for bb in ctx.basic_blocks: - for inst in bb.instructions: - inst.dup_requirements = OrderedSet() - inst.fence_id = -1 - operands = inst.get_inputs() - operands.extend(inst.get_outputs()) - # Build DFG + + # %15 = add %13 %14 + # dfg_outputs of %15 is (%15 = add %13 %14) + # dfg_inputs of %15 is [(%13 = ...), (%14 = ...), ... None: def _compute_dup_requirements(ctx: IRFunction) -> None: + # recompute fences + # reset dup_requirements data structure fence_id = 0 for bb in ctx.basic_blocks: for inst in bb.instructions: @@ -125,6 +122,9 @@ def _compute_dup_requirements(ctx: IRFunction) -> None: if inst.volatile: fence_id += 1 + # reset dup_requirements + inst.dup_requirements = OrderedSet() + visited = OrderedSet() last_seen = dict() @@ -165,7 +165,7 @@ def _compute_inst_dup_requirements_r( return for op in inst.get_inputs(): - target = dfg.get_output(op) + target = dfg.get_producing_instruction(op) if target.parent != inst.parent: continue _compute_inst_dup_requirements_r(dfg, target, visited, last_seen) @@ -495,7 +495,7 @@ def _emit_input_operands( stack.push(op) continue # print("RECURSE FOR", op, "TO:", ctx.dfg_outputs[op]) - assembly.extend(_generate_evm_for_instruction_r(ctx, [], ctx.dfg.get_output(op), stack)) + assembly.extend(_generate_evm_for_instruction_r(ctx, [], ctx.dfg.get_producing_instruction(op), stack)) if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") From 58f212a6507af8a6f70641d8e3145cdd7196ec6e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 31 Oct 2023 13:34:20 -0400 Subject: [PATCH 318/471] partially refactor out dft pass so the instructions are reordered. note emit_instruction* and compute_dup_requirements* still do the traversal, but that can probably be removed now (so they traverse linearly) --- vyper/venom/bb_optimizer.py | 10 ++++ vyper/venom/dfg.py | 20 +++++--- vyper/venom/ir.py | 3 +- vyper/venom/passes/pass_dft.py | 89 +++++++++++++++++++++++----------- 4 files changed, 84 insertions(+), 38 deletions(-) diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index c4f5ca56a4..d6ca369219 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -2,6 +2,9 @@ from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRLabel from vyper.venom.function import IRFunction +# maybe rename vyper.venom.passes.pass_dft to vyper.venom.passes.dft +from vyper.venom.passes.pass_dft import DFTPass + def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: """ @@ -145,3 +148,10 @@ def ir_pass_remove_unreachable_blocks(ctx: IRFunction) -> int: @ir_pass def ir_pass_optimize_unused_variables(ctx: IRFunction) -> int: return _optimize_unused_variables(ctx) + +def ir_pass_dft(ctx: IRFunction) -> int: + """ + Reorder instructions to move production of variables as close to use + as possible + """ + return DFTPass.run_pass(ctx) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index a4db1747ce..058a03b4b6 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -66,7 +66,6 @@ # DataFlow Graph class DFG: _dfg_inputs: dict[IRVariable, list[IRInstruction]] - _dfg_outputs: dict[IRVariable, IRInstruction] def __init__(self): @@ -74,7 +73,7 @@ def __init__(self): self._dfg_outputs = dict() # return all, flattened inputs to a given variable - def get_inputs(self, op: IRVariable) -> list[IRInstruction]: + def get_uses(self, op: IRVariable) -> list[IRInstruction]: return self._dfg_inputs.get(op, []) # the instruction which produces this variable. @@ -88,8 +87,9 @@ def from_ir_function(cls, ctx: IRFunction): # Build DFG # %15 = add %13 %14 + # %16 = iszero %15 # dfg_outputs of %15 is (%15 = add %13 %14) - # dfg_inputs of %15 is [(%13 = ...), (%14 = ...), ... None: # print("DUP REQUIREMENTS (inst)", inst) + # traverse down through the DFG for op in inst.get_outputs(): - for target in dfg.get_inputs(op): + # print("USES OF", op) + for target in dfg.get_uses(op): if target.parent != inst.parent: # REVIEW: produced by parent.out_vars continue if target.fence_id != inst.fence_id: continue + # print("(use)", target) _compute_inst_dup_requirements_r(dfg, target, visited, last_seen) if inst in visited: @@ -164,6 +167,7 @@ def _compute_inst_dup_requirements_r( # special handling for phi downstream return + # traverse through dependencies of this instruction for op in inst.get_inputs(): target = dfg.get_producing_instruction(op) if target.parent != inst.parent: @@ -171,10 +175,10 @@ def _compute_inst_dup_requirements_r( _compute_inst_dup_requirements_r(dfg, target, visited, last_seen) for op in inst.get_inputs(): - target = last_seen.get(op.value) + target = last_seen.get(op) if target: target.dup_requirements.add(op) - last_seen[op.value] = inst + last_seen[op] = inst # for op in inst.get_inputs(): # target = dfg.get_output(op) @@ -293,7 +297,7 @@ def _generate_evm_for_instruction_r( global label_counter for op in inst.get_outputs(): - for target in ctx.dfg.get_inputs(op): + for target in ctx.dfg.get_uses(op): # skip instructions that are not in the same basic block # so we don't cross basic block boundaries if target.parent != inst.parent: diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index 05277edd78..e70c3767ce 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -9,6 +9,7 @@ ir_pass_optimize_empty_blocks, ir_pass_optimize_unused_variables, ir_pass_remove_unreachable_blocks, + ir_pass_dft, ) from vyper.venom.ir_to_bb_pass import convert_ir_basicblock from vyper.venom.dfg import calculate_dfg, generate_evm @@ -43,7 +44,7 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF calculate_dfg(ctx) changes += ir_pass_constant_propagation(ctx) - # changes += ir_pass_dft(ctx) + changes += ir_pass_dft(ctx) calculate_cfg_in(ctx) calculate_liveness(ctx) diff --git a/vyper/venom/passes/pass_dft.py b/vyper/venom/passes/pass_dft.py index 9b2d452936..12f339771e 100644 --- a/vyper/venom/passes/pass_dft.py +++ b/vyper/venom/passes/pass_dft.py @@ -1,42 +1,73 @@ -from vyper.utils import OrderedSet, ir_pass +from vyper.utils import OrderedSet from vyper.venom.basicblock import IRBasicBlock, IRInstruction from vyper.venom.function import IRFunction +from vyper.venom.ir_pass import IRPass -visited_instructions = OrderedSet() +# DataFlow Transformation +class DFTPass(IRPass): + # recurse "down" into all the uses of `inst`, and then recurse "up" through + # all of its dependencies, to try to product an ordering of instructions + # which tries to optimize production of stack items to be as close as + # possible to uses of stack items. + def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): + # print("(inst)", inst) + for op in inst.get_outputs(): + for target in self.ctx.dfg.get_uses(op): + # print("(target)", target) + if target.parent != inst.parent: + # don't reorder across basic block boundaries + continue + if target.fence_id != inst.fence_id: + # don't reorder across fence groups + continue + # try to find the last use + self._process_instruction_r(bb, target) -def _emit_operands_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction) -> None: - for op in inst.get_inputs(): - target = ctx.dfg_outputs.get(op) - if target is None: - continue - _process_instruction(ctx, bb, target) + if inst in self.visited_instructions: + return + # print("VISITING", inst) + self.visited_instructions.add(inst) + if inst.opcode == "phi": + # don't try to reorder inputs of phi + bb.instructions.append(inst) + return -def _process_instruction(ctx: IRFunction, bb: IRBasicBlock, inst: IRInstruction) -> None: - global visited_instructions - if inst in visited_instructions: - return - visited_instructions.add(inst) - _emit_operands_instruction(ctx, bb, inst) - bb.append_instruction(inst) + # print(inst.get_inputs(), inst) + for op in inst.get_inputs(): + target = self.ctx.dfg.get_producing_instruction(op) + if target.parent != inst.parent: + continue + # REVIEW: should there be a check for fence here? + self._process_instruction_r(bb, target) + bb.instructions.append(inst) -def _process_basic_block(ctx: IRFunction, bb: IRBasicBlock) -> None: - ctx.append_basic_block(bb) - instructions = bb.instructions - bb.instructions = [] - for inst in instructions: - _process_instruction(ctx, bb, inst) + def _process_basic_block(self, bb: IRBasicBlock) -> None: + self.ctx.append_basic_block(bb) + instructions = bb.instructions + bb.instructions = [] -@ir_pass -def ir_pass_dft(ctx: IRFunction) -> None: - global visited_instructions - visited_instructions = OrderedSet() + for inst in instructions: + inst.fence_id = self.fence_id + if inst.volatile: + self.fence_id += 1 - basic_blocks = ctx.basic_blocks - ctx.basic_blocks = [] + for inst in instructions: + self._process_instruction_r(bb, inst) - for bb in basic_blocks: - _process_basic_block(ctx, bb) + def _run_pass(self, ctx: IRFunction) -> None: + # print(ctx) + self.ctx = ctx + self.fence_id = 0 + self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet() + + basic_blocks = ctx.basic_blocks + ctx.basic_blocks = [] + + for bb in basic_blocks: + self._process_basic_block(bb) + + # print(ctx) From 8d4104dbcaee5b383577ef5e70d7e47170d7cfad Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 31 Oct 2023 18:15:30 -0400 Subject: [PATCH 319/471] add missing file --- vyper/venom/ir_pass.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 vyper/venom/ir_pass.py diff --git a/vyper/venom/ir_pass.py b/vyper/venom/ir_pass.py new file mode 100644 index 0000000000..a5edba40e1 --- /dev/null +++ b/vyper/venom/ir_pass.py @@ -0,0 +1,23 @@ + +class IRPass: + """ + Decorator for IR passes. This decorator will run the pass repeatedly + until no more changes are made. + """ + @classmethod + def run_pass(cls, *args, **kwargs): + t = cls() + count = 0 + + while True: + changes = t._run_pass(*args, **kwargs) or 0 + if isinstance(changes, list): + changes = len(changes) + count += changes + if changes == 0: + break + + return count + + def _run_pass(self, *args, **kwargs): + raise NotImplementedError(f"Not implemented! {self.__class__}.run_pass()") From 5e4deb34d9bc8af731bcc9101c4e0ced5654e4ac Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 31 Oct 2023 18:15:46 -0400 Subject: [PATCH 320/471] simplify compute_dup_requirements and emit_evm_instruction they don't need to recurse now because of the dataflow traversal transformation --- vyper/venom/dfg.py | 181 ++++++++++----------------------------------- 1 file changed, 41 insertions(+), 140 deletions(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 058a03b4b6..ede551214f 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -113,78 +113,22 @@ def calculate_dfg(ctx: IRFunction) -> None: def _compute_dup_requirements(ctx: IRFunction) -> None: - # recompute fences - # reset dup_requirements data structure - fence_id = 0 for bb in ctx.basic_blocks: - for inst in bb.instructions: - inst.fence_id = fence_id - if inst.volatile: - fence_id += 1 - - # reset dup_requirements - inst.dup_requirements = OrderedSet() - - visited = OrderedSet() last_seen = dict() - dfg = ctx.dfg for inst in bb.instructions: - _compute_inst_dup_requirements_r(dfg, inst, visited, last_seen) + # reset dup_requirements + inst.dup_requirements = OrderedSet() - out_vars = bb.out_vars - for inst in reversed(bb.instructions): for op in inst.get_inputs(): - if op in out_vars: - inst.dup_requirements.add(op) - - -def _compute_inst_dup_requirements_r( - dfg: DFG, - inst: IRInstruction, - visited: OrderedSet, - last_seen: dict[IRVariable, IRInstruction], -) -> None: - # print("DUP REQUIREMENTS (inst)", inst) - - # traverse down through the DFG - for op in inst.get_outputs(): - # print("USES OF", op) - for target in dfg.get_uses(op): - if target.parent != inst.parent: - # REVIEW: produced by parent.out_vars - continue - if target.fence_id != inst.fence_id: - continue - # print("(use)", target) - _compute_inst_dup_requirements_r(dfg, target, visited, last_seen) - - if inst in visited: - return - visited.add(inst) - - if inst.opcode == "phi": - # special handling for phi downstream - return - - # traverse through dependencies of this instruction - for op in inst.get_inputs(): - target = dfg.get_producing_instruction(op) - if target.parent != inst.parent: - continue - _compute_inst_dup_requirements_r(dfg, target, visited, last_seen) - - for op in inst.get_inputs(): - target = last_seen.get(op) - if target: - target.dup_requirements.add(op) - last_seen[op] = inst + if op in last_seen: + target = last_seen[op] + target.dup_requirements.add(op) - # for op in inst.get_inputs(): - # target = dfg.get_output(op) - # if target.dup_requirements: - # print("DUP REQUIREMENTS (target)", op, target.dup_requirements, target) + last_seen[op] = inst + if op in bb.out_vars: + inst.dup_requirements.add(op) visited_instructions = None # {IRInstruction} visited_basicblocks = None # {IRBasicBlock} @@ -229,21 +173,10 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: return asm -def _stack_duplications( - assembly: list, inst: IRInstruction, stack: StackModel, stack_ops: list[IRValueBase] -) -> None: - for op in stack_ops: - if op.is_literal or isinstance(op, IRLabel): - continue - depth = stack.get_depth(op) - assert depth is not StackModel.NOT_IN_STACK, "Operand not in stack" - if op in inst.dup_requirements: - stack.dup(assembly, depth) - - def _stack_reorder(assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVariable]) -> None: # make a list so we can index it stack_ops = [x for x in stack_ops] + # print("ENTER reorder", stack.stack, operands) # start_len = len(assembly) for i in range(len(stack_ops)): @@ -262,6 +195,36 @@ def _stack_reorder(assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVa # print("EXIT reorder", stack.stack, stack_ops) +def _emit_input_operands( + ctx: IRFunction, + assembly: list, + inst: IRInstruction, + ops: list[IRValueBase], + stack: StackModel, +): + # print("EMIT INPUTS FOR", inst) + for op in ops: + if isinstance(op, IRLabel): + # invoke emits the actual instruction itself so we don't need to emit it here + # but we need to add it to the stack map + if inst.opcode != "invoke": + assembly.append(f"_sym_{op.value}") + stack.push(op) + continue + if op.is_literal: + assembly.extend([*PUSH(op.value)]) + stack.push(op) + continue + + if op in inst.dup_requirements: + depth = stack.get_depth(op) + assert depth is not StackModel.NOT_IN_STACK + stack.dup(assembly, depth) + + if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: + assembly.extend([*PUSH(op.mem_addr)]) + assembly.append("MLOAD") + def _generate_evm_for_basicblock_r( ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack: StackModel ): @@ -272,14 +235,8 @@ def _generate_evm_for_basicblock_r( asm.append(f"_sym_{basicblock.label}") asm.append("JUMPDEST") - fence_id = 0 for inst in basicblock.instructions: - inst.fence_id = fence_id - if inst.volatile: - fence_id += 1 - - for inst in basicblock.instructions: - asm = _generate_evm_for_instruction_r(ctx, asm, inst, stack) + asm = _generate_evm_for_instruction(ctx, asm, inst, stack) for bb in basicblock.cfg_out: _generate_evm_for_basicblock_r(ctx, asm, bb, stack.copy()) @@ -291,34 +248,10 @@ def _generate_evm_for_basicblock_r( # REVIEW: would this be better as a class? # HK: Let's consider it after the pass_dft refactor -def _generate_evm_for_instruction_r( +def _generate_evm_for_instruction( ctx: IRFunction, assembly: list, inst: IRInstruction, stack: StackModel ) -> list[str]: global label_counter - - for op in inst.get_outputs(): - for target in ctx.dfg.get_uses(op): - # skip instructions that are not in the same basic block - # so we don't cross basic block boundaries - if target.parent != inst.parent: - continue - # it skip instructions that are not in the same fence group - if target.fence_id != inst.fence_id: - continue - # REVIEW: I think it would be better to have an explicit step, - # `reorder instructions per DFG`, and then `generate_evm_for_instruction` - # does not need to recurse (or be co-recursive with `emit_input_operands`). - # HK: Indeed, this is eventualy the idea. Especialy now that I have - # implemented the "needs duplication" algorithm that needs the same - # traversal and it's duplicated - # REVIEW: OK. to discuss further offline - assembly.extend(_generate_evm_for_instruction_r(ctx, [], target, stack)) - - if inst in visited_instructions: - # print("seen:", inst) - return assembly - visited_instructions.add(inst) - opcode = inst.opcode # @@ -363,11 +296,6 @@ def _generate_evm_for_instruction_r( # print("pre-dups (inst)", inst.dup_requirements, stack.stack, inst) - # REVIEW: doing duplications and then reorder in - # separate steps can result in less optimal code - _stack_duplications(assembly, inst, stack, operands) - # print("post-dups (inst)", stack.stack, inst) - _stack_reorder(assembly, stack, operands) # print("post-reorder (inst)", stack.stack, inst) @@ -476,30 +404,3 @@ def _generate_evm_for_instruction_r( assembly.extend([*PUSH(inst.ret.mem_addr)]) return assembly - - -def _emit_input_operands( - ctx: IRFunction, - assembly: list, - inst: IRInstruction, - ops: list[IRValueBase], - stack: StackModel, -): - # print("EMIT INPUTS FOR", inst) - for op in ops: - if isinstance(op, IRLabel): - # invoke emits the actual instruction itself so we don't need to emit it here - # but we need to add it to the stack map - if inst.opcode != "invoke": - assembly.append(f"_sym_{op.value}") - stack.push(op) - continue - if op.is_literal: - assembly.extend([*PUSH(op.value)]) - stack.push(op) - continue - # print("RECURSE FOR", op, "TO:", ctx.dfg_outputs[op]) - assembly.extend(_generate_evm_for_instruction_r(ctx, [], ctx.dfg.get_producing_instruction(op), stack)) - if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: - assembly.extend([*PUSH(op.mem_addr)]) - assembly.append("MLOAD") From a82045b15f9055de73f5234c0dd59c663634de26 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 31 Oct 2023 18:23:55 -0400 Subject: [PATCH 321/471] add more review comments --- vyper/venom/dfg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index ede551214f..39443368cd 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -112,6 +112,8 @@ def calculate_dfg(ctx: IRFunction) -> None: _compute_dup_requirements(ctx) +# REVIEW: i'm not sure this does the right thing if an operand is repeated +# during an instruction. e.g., `add %15 %15` def _compute_dup_requirements(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: last_seen = dict() From 28e487f8cd8da71e3a5b45bc97bdf315b1cda3ef Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 1 Nov 2023 10:29:20 -0400 Subject: [PATCH 322/471] add a comment --- vyper/venom/dfg.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 39443368cd..909e728f4f 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -204,7 +204,12 @@ def _emit_input_operands( ops: list[IRValueBase], stack: StackModel, ): + # PRE: we already have all the items on the stack that have + # been scheduled to be killed. now it's just a matter of emitting + # SWAPs, DUPs and PUSHes until we match the `ops` argument + # print("EMIT INPUTS FOR", inst) + for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -213,6 +218,7 @@ def _emit_input_operands( assembly.append(f"_sym_{op.value}") stack.push(op) continue + if op.is_literal: assembly.extend([*PUSH(op.value)]) stack.push(op) From 88dcd5df922b1fb94a54f0b70c279d1aeac33cb4 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 1 Nov 2023 11:07:51 -0400 Subject: [PATCH 323/471] add a bit of review --- vyper/venom/dfg.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 909e728f4f..e412da7aae 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -72,7 +72,7 @@ def __init__(self): self._dfg_inputs = dict() self._dfg_outputs = dict() - # return all, flattened inputs to a given variable + # return uses of a given variable def get_uses(self, op: IRVariable) -> list[IRInstruction]: return self._dfg_inputs.get(op, []) @@ -307,6 +307,9 @@ def _generate_evm_for_instruction( _stack_reorder(assembly, stack, operands) # print("post-reorder (inst)", stack.stack, inst) + # REVIEW: it would be clearer if the order of steps 4 and 5 were + # switched (so that the runtime order matches the order they appear + # below). # Step 4: Push instruction's return value to stack stack.pop(len(operands)) if inst.ret is not None: From 829580dc418a1cec8a3386939ebe1aae1ba88bce Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 1 Nov 2023 11:07:57 -0400 Subject: [PATCH 324/471] add a heuristic to swap out top of stack --- vyper/venom/dfg.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index e412da7aae..3cc296966a 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -210,6 +210,16 @@ def _emit_input_operands( # print("EMIT INPUTS FOR", inst) + # dumb heuristic: if the top of stack is not wanted here, swap + # it with something that is wanted + if ops and stack.stack and stack.stack[-1] not in ops: + for op in ops: + if isinstance(op, IRVariable) and op not in inst.dup_requirements: + depth = stack.get_depth(op) + assert depth is not StackModel.NOT_IN_STACK + stack.swap(assembly, depth) + break + for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here From a0cbf857b55f6273ade419ef7adbc3cfe01427d4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 2 Nov 2023 13:40:33 +0200 Subject: [PATCH 325/471] Default constructor name for functions -> "global" --- vyper/venom/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 981c4f59f5..5744ff6336 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -24,7 +24,7 @@ class IRFunction: dfg: Any = None # dfg will be added in convert_ir_to_dfg pass - def __init__(self, name: IRLabel) -> None: + def __init__(self, name: IRLabel = IRLabel("global")) -> None: self.name = name self.args = [] self.basic_blocks = [] From a7a25e229e11d478897acf9ccf0ca79951c7d558 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 2 Nov 2023 13:57:28 +0200 Subject: [PATCH 326/471] Fix the case where operands are used multiple times from an instruction * Updated the _emit_input_operands() to keep track of emited operands for the current instruction and if the operands was already emited it simply produces a DUP instruction. * Implemented a test for the case --- .../compiler/venom/test_duplicate_operands.py | 26 +++++++++++++++++++ vyper/venom/dfg.py | 7 ++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/compiler/venom/test_duplicate_operands.py diff --git a/tests/compiler/venom/test_duplicate_operands.py b/tests/compiler/venom/test_duplicate_operands.py new file mode 100644 index 0000000000..b5f7a69434 --- /dev/null +++ b/tests/compiler/venom/test_duplicate_operands.py @@ -0,0 +1,26 @@ +from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRLiteral + +from vyper.venom.function import IRFunction +from vyper.venom.ir import generate_assembly_experimental + + +def test_duplicate_operands(): + """ + Test the duplicate operands code generation. + The venom code: + + %1 = 10 + %2 = add %1, %1 + stop + + Should compile to: [PUSH1, 10, DUP1, ADD, STOP] + """ + ctx = IRFunction() + + op = ctx.append_instruction("store", [IRLiteral(10)]) + ctx.append_instruction("add", [op, op]) + ctx.append_instruction("stop", []) + + asm = generate_assembly_experimental(ctx) + + assert asm[:5] == ["PUSH1", 10, "DUP1", "ADD", "STOP"] diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 3cc296966a..093d4552c7 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -132,6 +132,7 @@ def _compute_dup_requirements(ctx: IRFunction) -> None: if op in bb.out_vars: inst.dup_requirements.add(op) + visited_instructions = None # {IRInstruction} visited_basicblocks = None # {IRBasicBlock} @@ -220,6 +221,7 @@ def _emit_input_operands( stack.swap(assembly, depth) break + emited_ops = [] for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -234,7 +236,7 @@ def _emit_input_operands( stack.push(op) continue - if op in inst.dup_requirements: + if op in inst.dup_requirements or op in emited_ops: depth = stack.get_depth(op) assert depth is not StackModel.NOT_IN_STACK stack.dup(assembly, depth) @@ -243,6 +245,9 @@ def _emit_input_operands( assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") + emited_ops.append(op) + + def _generate_evm_for_basicblock_r( ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack: StackModel ): From 396ad00cf88b95d515962e7a3f46895a418e1b9d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 2 Nov 2023 15:44:37 +0200 Subject: [PATCH 327/471] Amendment to the previous commit to handle a special case * handle the case where an operand needs both duplication and its also appearing multiple times in an instuction --- tests/compiler/venom/test_duplicate_operands.py | 13 ++++++++----- vyper/venom/dfg.py | 12 +++++++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/compiler/venom/test_duplicate_operands.py b/tests/compiler/venom/test_duplicate_operands.py index b5f7a69434..d3af416674 100644 --- a/tests/compiler/venom/test_duplicate_operands.py +++ b/tests/compiler/venom/test_duplicate_operands.py @@ -1,3 +1,4 @@ +from vyper.compiler.settings import OptimizationLevel from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRLiteral from vyper.venom.function import IRFunction @@ -11,16 +12,18 @@ def test_duplicate_operands(): %1 = 10 %2 = add %1, %1 + %3 = mul %1, %2 stop - Should compile to: [PUSH1, 10, DUP1, ADD, STOP] + Should compile to: [PUSH1, 10, DUP1, DUP1, DUP1, ADD, MUL, STOP] """ ctx = IRFunction() op = ctx.append_instruction("store", [IRLiteral(10)]) - ctx.append_instruction("add", [op, op]) - ctx.append_instruction("stop", []) + sum = ctx.append_instruction("add", [op, op]) + ctx.append_instruction("mul", [sum, op]) + ctx.append_instruction("stop", [], False) - asm = generate_assembly_experimental(ctx) + asm = generate_assembly_experimental(ctx, OptimizationLevel.CODESIZE) - assert asm[:5] == ["PUSH1", 10, "DUP1", "ADD", "STOP"] + assert asm == ["PUSH1", 10, "DUP1", "DUP1", "DUP1", "ADD", "MUL", "STOP", "REVERT"] diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 093d4552c7..44ca48888b 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -8,6 +8,7 @@ IRVariable, MemType, ) +from vyper.venom.bb_optimizer import calculate_cfg_in, calculate_liveness from vyper.venom.function import IRFunction from vyper.venom.stack_model import StackModel @@ -145,6 +146,10 @@ def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: visited_instructions = OrderedSet() visited_basicblocks = OrderedSet() + calculate_cfg_in(ctx) + calculate_liveness(ctx) + calculate_dfg(ctx) + _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackModel()) # Append postambles @@ -236,7 +241,12 @@ def _emit_input_operands( stack.push(op) continue - if op in inst.dup_requirements or op in emited_ops: + if op in inst.dup_requirements: + depth = stack.get_depth(op) + assert depth is not StackModel.NOT_IN_STACK + stack.dup(assembly, depth) + + if op in emited_ops: depth = stack.get_depth(op) assert depth is not StackModel.NOT_IN_STACK stack.dup(assembly, depth) From 979383a91b2dcc60855ed31510cd8b327860b6f1 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 2 Nov 2023 09:50:37 -0400 Subject: [PATCH 328/471] add review --- vyper/venom/dfg.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 44ca48888b..39cf8ab6f6 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -191,6 +191,8 @@ def _stack_reorder(assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVa op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) depth = stack.get_depth(op) + + # REVIEW: move this assertion into stack.swap assert depth is not StackModel.NOT_IN_STACK, f"{op} not in stack: {stack.stack}" if depth == final_stack_depth: continue @@ -222,6 +224,7 @@ def _emit_input_operands( for op in ops: if isinstance(op, IRVariable) and op not in inst.dup_requirements: depth = stack.get_depth(op) + # REVIEW: move this assertion into StackModel implementation assert depth is not StackModel.NOT_IN_STACK stack.swap(assembly, depth) break @@ -248,6 +251,7 @@ def _emit_input_operands( if op in emited_ops: depth = stack.get_depth(op) + # REVIEW: move this assertion into StackModel implementation assert depth is not StackModel.NOT_IN_STACK stack.dup(assembly, depth) @@ -308,6 +312,7 @@ def _generate_evm_for_instruction( ret = inst.get_outputs()[0] inputs = inst.get_inputs() depth = stack.get_shallowest_depth(inputs) + # REVIEW: move this into StackModel implementation assert depth is not StackModel.NOT_IN_STACK, "Operand not in stack" to_be_replaced = stack.peek(depth) if to_be_replaced in inst.dup_requirements: From 84aa4614b6fedbbf8eeb5efca457cb6dad7cfee8 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 2 Nov 2023 10:02:41 -0400 Subject: [PATCH 329/471] more review --- vyper/venom/bb_optimizer.py | 2 +- vyper/venom/dfg.py | 3 +++ vyper/venom/ir_pass.py | 1 + vyper/venom/passes/pass_dft.py | 6 +++++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index d6ca369219..ce57e2f5a0 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -11,7 +11,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: Remove unused variables. """ count = 0 - removeList = [] + removeList = [] # REVIEW: performance, could be `set()` for bb in ctx.basic_blocks: for i, inst in enumerate(bb.instructions[:-1]): if inst.volatile: diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 39cf8ab6f6..43e64c98ab 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -255,6 +255,9 @@ def _emit_input_operands( assert depth is not StackModel.NOT_IN_STACK stack.dup(assembly, depth) + # REVIEW: this seems like it can be reordered across volatile + # boundaries (which includes memory fences). maybe just + # remove it entirely at this point if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") diff --git a/vyper/venom/ir_pass.py b/vyper/venom/ir_pass.py index a5edba40e1..11b3c98c85 100644 --- a/vyper/venom/ir_pass.py +++ b/vyper/venom/ir_pass.py @@ -1,3 +1,4 @@ +# REVIEW: can move this to `vyper/venom/passes/base_pass.py` or something class IRPass: """ diff --git a/vyper/venom/passes/pass_dft.py b/vyper/venom/passes/pass_dft.py index 12f339771e..bd03f573be 100644 --- a/vyper/venom/passes/pass_dft.py +++ b/vyper/venom/passes/pass_dft.py @@ -39,7 +39,11 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): target = self.ctx.dfg.get_producing_instruction(op) if target.parent != inst.parent: continue - # REVIEW: should there be a check for fence here? + # REVIEW: should there be a check for fence here? i.e., + # ``` + # if target.fence_id != inst.fence_id: + # continue + # ``` self._process_instruction_r(bb, target) bb.instructions.append(inst) From d1689011ac59cdccaab271cb2cb9411235d12049 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 2 Nov 2023 16:07:53 +0200 Subject: [PATCH 330/471] Move NOT_IN_STACK asserts to StackModel --- vyper/venom/dfg.py | 9 --------- vyper/venom/stack_model.py | 5 +++++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 43e64c98ab..7080bcf904 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -192,8 +192,6 @@ def _stack_reorder(assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVa final_stack_depth = -(len(stack_ops) - i - 1) depth = stack.get_depth(op) - # REVIEW: move this assertion into stack.swap - assert depth is not StackModel.NOT_IN_STACK, f"{op} not in stack: {stack.stack}" if depth == final_stack_depth: continue @@ -224,8 +222,6 @@ def _emit_input_operands( for op in ops: if isinstance(op, IRVariable) and op not in inst.dup_requirements: depth = stack.get_depth(op) - # REVIEW: move this assertion into StackModel implementation - assert depth is not StackModel.NOT_IN_STACK stack.swap(assembly, depth) break @@ -246,13 +242,10 @@ def _emit_input_operands( if op in inst.dup_requirements: depth = stack.get_depth(op) - assert depth is not StackModel.NOT_IN_STACK stack.dup(assembly, depth) if op in emited_ops: depth = stack.get_depth(op) - # REVIEW: move this assertion into StackModel implementation - assert depth is not StackModel.NOT_IN_STACK stack.dup(assembly, depth) # REVIEW: this seems like it can be reordered across volatile @@ -315,8 +308,6 @@ def _generate_evm_for_instruction( ret = inst.get_outputs()[0] inputs = inst.get_inputs() depth = stack.get_shallowest_depth(inputs) - # REVIEW: move this into StackModel implementation - assert depth is not StackModel.NOT_IN_STACK, "Operand not in stack" to_be_replaced = stack.peek(depth) if to_be_replaced in inst.dup_requirements: stack.dup(assembly, depth) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 1b0552d92c..3513718b3f 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -59,12 +59,14 @@ def peek(self, depth: int) -> IRValueBase: """ Returns the top of the stack map. """ + assert depth is not StackModel.NOT_IN_STACK, "Cannot peek non-in-stack depth" return self.stack[depth - 1] def poke(self, depth: int, op: IRValueBase) -> None: """ Pokes an operand at the given depth in the stack map. """ + assert depth is not StackModel.NOT_IN_STACK, "Cannot poke non-in-stack depth" assert depth <= 0, "Bad depth" assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" self.stack[depth - 1] = op @@ -73,6 +75,7 @@ def dup(self, assembly: list[str], depth: int) -> None: """ Duplicates the operand at the given depth in the stack map. """ + assert depth is not StackModel.NOT_IN_STACK, "Cannot dup non-existent operand" assert depth <= 0, "Cannot dup positive depth" assembly.append(f"DUP{-(depth-1)}") self.stack.append(self.peek(depth)) @@ -82,6 +85,7 @@ def swap(self, assembly: list[str], depth: int) -> None: """ Swaps the operand at the given depth in the stack map with the top of the stack. """ + assert depth is not StackModel.NOT_IN_STACK, "Cannot swap non-existent operand" # convenience, avoids branching in caller if depth == 0: return @@ -91,4 +95,5 @@ def swap(self, assembly: list[str], depth: int) -> None: top = self.stack[-1] self.stack[-1] = self.stack[depth - 1] self.stack[depth - 1] = top + # REVIEW: maybe have a convenience function which swaps depth1 and depth2 From 748b0aa2ab569ec7fb4d93655ac0787bad132ae1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 2 Nov 2023 16:12:42 +0200 Subject: [PATCH 331/471] Code simplification by providing stack operations dub_op and swap_op Directly pass an operand to be swaped or duped to the stack model --- vyper/venom/dfg.py | 9 +++------ vyper/venom/stack_model.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py index 7080bcf904..b2fe86e321 100644 --- a/vyper/venom/dfg.py +++ b/vyper/venom/dfg.py @@ -221,8 +221,7 @@ def _emit_input_operands( if ops and stack.stack and stack.stack[-1] not in ops: for op in ops: if isinstance(op, IRVariable) and op not in inst.dup_requirements: - depth = stack.get_depth(op) - stack.swap(assembly, depth) + stack.swap_op(assembly, op) break emited_ops = [] @@ -241,12 +240,10 @@ def _emit_input_operands( continue if op in inst.dup_requirements: - depth = stack.get_depth(op) - stack.dup(assembly, depth) + stack.dup_op(assembly, op) if op in emited_ops: - depth = stack.get_depth(op) - stack.dup(assembly, depth) + stack.dup_op(assembly, op) # REVIEW: this seems like it can be reordered across volatile # boundaries (which includes memory fences). maybe just diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 3513718b3f..c9fb520ea7 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -80,6 +80,13 @@ def dup(self, assembly: list[str], depth: int) -> None: assembly.append(f"DUP{-(depth-1)}") self.stack.append(self.peek(depth)) + def dup_op(self, assembly: list[str], op: IRValueBase) -> None: + """ + Convinience method: duplicates the given operand in the stack map. + """ + depth = self.get_depth(op) + self.dup(assembly, depth) + # REVIEW: use positive indices (and hide the negation inside StackModel implementation) def swap(self, assembly: list[str], depth: int) -> None: """ @@ -96,4 +103,12 @@ def swap(self, assembly: list[str], depth: int) -> None: self.stack[-1] = self.stack[depth - 1] self.stack[depth - 1] = top + def swap_op(self, assembly: list[str], op: IRValueBase) -> None: + """ + Convinience method: swaps the given operand in the stack map with the + top of the stack. + """ + depth = self.get_depth(op) + self.swap(assembly, depth) + # REVIEW: maybe have a convenience function which swaps depth1 and depth2 From ffe7659b78a1855c61ac1d4b5e38ad233ae56f0b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 2 Nov 2023 16:47:16 +0200 Subject: [PATCH 332/471] refactoring --- .../compiler/venom/test_duplicate_operands.py | 1 - vyper/compiler/phases.py | 2 +- vyper/venom/bb_optimizer.py | 10 - vyper/venom/dfg.py | 438 ------------------ vyper/venom/function.py | 2 +- vyper/venom/ir.py | 373 ++++++++++++++- vyper/venom/{ir_pass.py => pass_base.py} | 2 + vyper/venom/passes/pass_dft.py | 80 +++- 8 files changed, 445 insertions(+), 463 deletions(-) delete mode 100644 vyper/venom/dfg.py rename vyper/venom/{ir_pass.py => pass_base.py} (99%) diff --git a/tests/compiler/venom/test_duplicate_operands.py b/tests/compiler/venom/test_duplicate_operands.py index d3af416674..40d6486e8d 100644 --- a/tests/compiler/venom/test_duplicate_operands.py +++ b/tests/compiler/venom/test_duplicate_operands.py @@ -1,6 +1,5 @@ from vyper.compiler.settings import OptimizationLevel from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRLiteral - from vyper.venom.function import IRFunction from vyper.venom.ir import generate_assembly_experimental diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index f950ddc6f2..fdfb12d8fd 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -14,7 +14,7 @@ from vyper.semantics import set_data_positions, validate_semantics from vyper.semantics.types.function import ContractFunctionT from vyper.typing import InterfaceImports, StorageLayout -from vyper.venom.ir import generate_ir, generate_assembly_experimental +from vyper.venom.ir import generate_assembly_experimental, generate_ir class CompilerData: diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index ce57e2f5a0..16ce8d972d 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -2,9 +2,6 @@ from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRLabel from vyper.venom.function import IRFunction -# maybe rename vyper.venom.passes.pass_dft to vyper.venom.passes.dft -from vyper.venom.passes.pass_dft import DFTPass - def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: """ @@ -148,10 +145,3 @@ def ir_pass_remove_unreachable_blocks(ctx: IRFunction) -> int: @ir_pass def ir_pass_optimize_unused_variables(ctx: IRFunction) -> int: return _optimize_unused_variables(ctx) - -def ir_pass_dft(ctx: IRFunction) -> int: - """ - Reorder instructions to move production of variables as close to use - as possible - """ - return DFTPass.run_pass(ctx) diff --git a/vyper/venom/dfg.py b/vyper/venom/dfg.py deleted file mode 100644 index b2fe86e321..0000000000 --- a/vyper/venom/dfg.py +++ /dev/null @@ -1,438 +0,0 @@ -from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly -from vyper.utils import MemoryPositions, OrderedSet -from vyper.venom.basicblock import ( - IRBasicBlock, - IRInstruction, - IRLabel, - IRValueBase, - IRVariable, - MemType, -) -from vyper.venom.bb_optimizer import calculate_cfg_in, calculate_liveness -from vyper.venom.function import IRFunction -from vyper.venom.stack_model import StackModel - -ONE_TO_ONE_INSTRUCTIONS = [ - "revert", - "coinbase", - "calldatasize", - "calldatacopy", - "calldataload", - "gas", - "gasprice", - "gaslimit", - "address", - "origin", - "number", - "extcodesize", - "extcodehash", - "returndatasize", - "returndatacopy", - "callvalue", - "selfbalance", - "sload", - "sstore", - "mload", - "mstore", - "timestamp", - "caller", - "selfdestruct", - "signextend", - "stop", - "shr", - "shl", - "and", - "xor", - "or", - "add", - "sub", - "mul", - "div", - "mod", - "exp", - "eq", - "iszero", - "lg", - "lt", - "slt", - "sgt", - "log0", - "log1", - "log2", - "log3", - "log4", -] - - -# DataFlow Graph -class DFG: - _dfg_inputs: dict[IRVariable, list[IRInstruction]] - _dfg_outputs: dict[IRVariable, IRInstruction] - - def __init__(self): - self._dfg_inputs = dict() - self._dfg_outputs = dict() - - # return uses of a given variable - def get_uses(self, op: IRVariable) -> list[IRInstruction]: - return self._dfg_inputs.get(op, []) - - # the instruction which produces this variable. - def get_producing_instruction(self, op: IRVariable) -> IRInstruction: - return self._dfg_outputs[op] - - @classmethod - def from_ir_function(cls, ctx: IRFunction): - dfg = cls() - - # Build DFG - - # %15 = add %13 %14 - # %16 = iszero %15 - # dfg_outputs of %15 is (%15 = add %13 %14) - # dfg_inputs of %15 is all the instructions which *use* %15, ex. [(%16 = iszero %15), ...] - for bb in ctx.basic_blocks: - for inst in bb.instructions: - operands = inst.get_inputs() - res = inst.get_outputs() - - for op in operands: - inputs = dfg._dfg_inputs.setdefault(op, []) - inputs.append(inst) - - for op in res: - dfg._dfg_outputs[op] = inst - - return dfg - - -def calculate_dfg(ctx: IRFunction) -> None: - dfg = DFG.from_ir_function(ctx) - ctx.dfg = dfg - - _compute_dup_requirements(ctx) - - -# REVIEW: i'm not sure this does the right thing if an operand is repeated -# during an instruction. e.g., `add %15 %15` -def _compute_dup_requirements(ctx: IRFunction) -> None: - for bb in ctx.basic_blocks: - last_seen = dict() - - for inst in bb.instructions: - # reset dup_requirements - inst.dup_requirements = OrderedSet() - - for op in inst.get_inputs(): - if op in last_seen: - target = last_seen[op] - target.dup_requirements.add(op) - - last_seen[op] = inst - - if op in bb.out_vars: - inst.dup_requirements.add(op) - - -visited_instructions = None # {IRInstruction} -visited_basicblocks = None # {IRBasicBlock} - - -# REVIEW: might be cleaner if evm generation were separate from DFG computation -# (but i can see why it was done this way) -def generate_evm(ctx: IRFunction, no_optimize: bool = False) -> list[str]: - global visited_instructions, visited_basicblocks - asm = [] - visited_instructions = OrderedSet() - visited_basicblocks = OrderedSet() - - calculate_cfg_in(ctx) - calculate_liveness(ctx) - calculate_dfg(ctx) - - _generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackModel()) - - # 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() - - asm.extend(revert_postamble) - if runtime: - runtime.extend(revert_postamble) - asm.append(runtime) - - # Append data segment - data_segments = {} - 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]) - - if no_optimize is False: - optimize_assembly(asm) - - return asm - - -def _stack_reorder(assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVariable]) -> None: - # make a list so we can index it - stack_ops = [x for x in stack_ops] - - # print("ENTER reorder", stack.stack, operands) - # start_len = len(assembly) - for i in range(len(stack_ops)): - op = stack_ops[i] - final_stack_depth = -(len(stack_ops) - i - 1) - depth = stack.get_depth(op) - - if depth == final_stack_depth: - continue - - # print("trace", depth, final_stack_depth) - stack.swap(assembly, depth) - stack.swap(assembly, final_stack_depth) - - # print("INSTRUCTIONS", assembly[start_len:]) - # print("EXIT reorder", stack.stack, stack_ops) - - -def _emit_input_operands( - ctx: IRFunction, - assembly: list, - inst: IRInstruction, - ops: list[IRValueBase], - stack: StackModel, -): - # PRE: we already have all the items on the stack that have - # been scheduled to be killed. now it's just a matter of emitting - # SWAPs, DUPs and PUSHes until we match the `ops` argument - - # print("EMIT INPUTS FOR", inst) - - # dumb heuristic: if the top of stack is not wanted here, swap - # it with something that is wanted - if ops and stack.stack and stack.stack[-1] not in ops: - for op in ops: - if isinstance(op, IRVariable) and op not in inst.dup_requirements: - stack.swap_op(assembly, op) - break - - emited_ops = [] - for op in ops: - if isinstance(op, IRLabel): - # invoke emits the actual instruction itself so we don't need to emit it here - # but we need to add it to the stack map - if inst.opcode != "invoke": - assembly.append(f"_sym_{op.value}") - stack.push(op) - continue - - if op.is_literal: - assembly.extend([*PUSH(op.value)]) - stack.push(op) - continue - - if op in inst.dup_requirements: - stack.dup_op(assembly, op) - - if op in emited_ops: - stack.dup_op(assembly, op) - - # REVIEW: this seems like it can be reordered across volatile - # boundaries (which includes memory fences). maybe just - # remove it entirely at this point - if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: - assembly.extend([*PUSH(op.mem_addr)]) - assembly.append("MLOAD") - - emited_ops.append(op) - - -def _generate_evm_for_basicblock_r( - ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack: StackModel -): - if basicblock in visited_basicblocks: - return - visited_basicblocks.add(basicblock) - - asm.append(f"_sym_{basicblock.label}") - asm.append("JUMPDEST") - - for inst in basicblock.instructions: - asm = _generate_evm_for_instruction(ctx, asm, inst, stack) - - for bb in basicblock.cfg_out: - _generate_evm_for_basicblock_r(ctx, asm, bb, stack.copy()) - - -# TODO: refactor this -label_counter = 0 - - -# REVIEW: would this be better as a class? -# HK: Let's consider it after the pass_dft refactor -def _generate_evm_for_instruction( - ctx: IRFunction, assembly: list, inst: IRInstruction, stack: StackModel -) -> list[str]: - global label_counter - opcode = inst.opcode - - # - # generate EVM for op - # - - # Step 1: Apply instruction special stack manipulations - - if opcode in ["jmp", "jnz", "invoke"]: - operands = inst.get_non_label_operands() - elif opcode == "alloca": - operands = inst.operands[1:2] - elif opcode == "iload": - operands = [] - elif opcode == "istore": - operands = inst.operands[0:1] - else: - operands = inst.operands - - if opcode == "phi": - ret = inst.get_outputs()[0] - inputs = inst.get_inputs() - depth = stack.get_shallowest_depth(inputs) - to_be_replaced = stack.peek(depth) - if to_be_replaced in inst.dup_requirements: - stack.dup(assembly, depth) - stack.poke(0, ret) - else: - stack.poke(depth, ret) - return assembly - - # Step 2: Emit instruction's input operands - _emit_input_operands(ctx, assembly, inst, operands, stack) - - # Step 3: Reorder stack - if opcode in ["jnz", "jmp"]: - assert isinstance(inst.parent.cfg_out, OrderedSet) - b = next(iter(inst.parent.cfg_out)) - target_stack = b.in_vars_from(inst.parent) - _stack_reorder(assembly, stack, target_stack) - - # print("pre-dups (inst)", inst.dup_requirements, stack.stack, inst) - - _stack_reorder(assembly, stack, operands) - # print("post-reorder (inst)", stack.stack, inst) - - # REVIEW: it would be clearer if the order of steps 4 and 5 were - # switched (so that the runtime order matches the order they appear - # below). - # Step 4: Push instruction's return value to stack - stack.pop(len(operands)) - if inst.ret is not None: - stack.push(inst.ret) - - # Step 5: Emit the EVM instruction(s) - if opcode in ONE_TO_ONE_INSTRUCTIONS: - assembly.append(opcode.upper()) - elif opcode == "alloca": - pass - elif opcode == "param": - pass - elif opcode == "store": - pass - elif opcode == "dbname": - pass - elif opcode in ["codecopy", "dloadbytes"]: - assembly.append("CODECOPY") - elif opcode == "jnz": - assembly.append(f"_sym_{inst.operands[1].value}") - assembly.append("JUMPI") - elif opcode == "jmp": - if isinstance(inst.operands[0], IRLabel): - assembly.append(f"_sym_{inst.operands[0].value}") - assembly.append("JUMP") - else: - assembly.append("JUMP") - elif opcode == "gt": - assembly.append("GT") - elif opcode == "lt": - assembly.append("LT") - elif opcode == "invoke": - target = inst.operands[0] - assert isinstance(target, IRLabel), "invoke target must be a label" - assembly.extend( - [ - f"_sym_label_ret_{label_counter}", - f"_sym_{target.value}", - "JUMP", - f"_sym_label_ret_{label_counter}", - "JUMPDEST", - ] - ) - label_counter += 1 - if stack.get_height() > 0 and stack.peek(0) in inst.dup_requirements: - stack.pop() - assembly.append("POP") - elif opcode == "call": - assembly.append("CALL") - elif opcode == "staticcall": - assembly.append("STATICCALL") - elif opcode == "ret": - assembly.append("JUMP") - elif opcode == "return": - assembly.append("RETURN") - elif opcode == "phi": - pass - elif opcode == "sha3": - assembly.append("SHA3") - elif opcode == "sha3_64": - assembly.extend( - [ - *PUSH(MemoryPositions.FREE_VAR_SPACE2), - "MSTORE", - *PUSH(MemoryPositions.FREE_VAR_SPACE), - "MSTORE", - *PUSH(64), - *PUSH(MemoryPositions.FREE_VAR_SPACE), - "SHA3", - ] - ) - elif opcode == "ceil32": - 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)]) - assembly = assembly[-1] - elif opcode == "iload": - loc = inst.operands[0].value - assembly.extend(["_OFST", "_mem_deploy_end", loc, "MLOAD"]) - elif opcode == "istore": - loc = inst.operands[1].value - assembly.extend(["_OFST", "_mem_deploy_end", loc, "MSTORE"]) - else: - raise Exception(f"Unknown opcode: {opcode}") - - # Step 6: Emit instructions output operands (if any) - if inst.ret is not None: - assert isinstance(inst.ret, IRVariable), "Return value must be a variable" - if inst.ret.mem_type == MemType.MEMORY: - assembly.extend([*PUSH(inst.ret.mem_addr)]) - - return assembly diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 5744ff6336..0525b2625a 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,4 +1,4 @@ -from typing import Optional, Any +from typing import Any, Optional from vyper.venom.basicblock import ( IRBasicBlock, diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index e70c3767ce..8e8589134e 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -3,24 +3,35 @@ from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel +from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly +from vyper.utils import MemoryPositions, OrderedSet +from vyper.venom.basicblock import ( + IRBasicBlock, + IRInstruction, + IRLabel, + IRValueBase, + IRVariable, + MemType, +) from vyper.venom.bb_optimizer import ( calculate_cfg_in, calculate_liveness, ir_pass_optimize_empty_blocks, ir_pass_optimize_unused_variables, ir_pass_remove_unreachable_blocks, - ir_pass_dft, ) -from vyper.venom.ir_to_bb_pass import convert_ir_basicblock -from vyper.venom.dfg import calculate_dfg, generate_evm from vyper.venom.function import IRFunction +from vyper.venom.ir_to_bb_pass import convert_ir_basicblock from vyper.venom.passes.pass_constant_propagation import ir_pass_constant_propagation +from vyper.venom.passes.pass_dft import DFG, DFTPass +from vyper.venom.stack_model import StackModel def generate_assembly_experimental( ir: IRFunction, optimize: Optional[OptimizationLevel] = None ) -> list[str]: - return generate_evm(ir, optimize is OptimizationLevel.NONE) + compiler = VenomCompiler() + return compiler.generate_evm(ir, optimize is OptimizationLevel.NONE) def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: @@ -41,16 +52,364 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF calculate_cfg_in(ctx) calculate_liveness(ctx) - calculate_dfg(ctx) + DFG.calculate_dfg(ctx) changes += ir_pass_constant_propagation(ctx) - changes += ir_pass_dft(ctx) + changes += DFTPass.run_pass(ctx) calculate_cfg_in(ctx) calculate_liveness(ctx) - calculate_dfg(ctx) + DFG.calculate_dfg(ctx) if changes == 0: break return ctx + + +class VenomCompiler: + ONE_TO_ONE_INSTRUCTIONS = [ + "revert", + "coinbase", + "calldatasize", + "calldatacopy", + "calldataload", + "gas", + "gasprice", + "gaslimit", + "address", + "origin", + "number", + "extcodesize", + "extcodehash", + "returndatasize", + "returndatacopy", + "callvalue", + "selfbalance", + "sload", + "sstore", + "mload", + "mstore", + "timestamp", + "caller", + "selfdestruct", + "signextend", + "stop", + "shr", + "shl", + "and", + "xor", + "or", + "add", + "sub", + "mul", + "div", + "mod", + "exp", + "eq", + "iszero", + "lg", + "lt", + "slt", + "sgt", + "log0", + "log1", + "log2", + "log3", + "log4", + ] + + label_counter = 0 + visited_instructions = None # {IRInstruction} + visited_basicblocks = None # {IRBasicBlock} + + def generate_evm(self, ctx: IRFunction, no_optimize: bool = False) -> list[str]: + asm = [] + self.visited_instructions = OrderedSet() + self.visited_basicblocks = OrderedSet() + self.label_counter = 0 + + calculate_cfg_in(ctx) + calculate_liveness(ctx) + DFG.calculate_dfg(ctx) + + self._generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackModel()) + + # 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() + + asm.extend(revert_postamble) + if runtime: + runtime.extend(revert_postamble) + asm.append(runtime) + + # Append data segment + data_segments = {} + 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]) + + if no_optimize is False: + optimize_assembly(asm) + + return asm + + def _stack_reorder( + self, assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVariable] + ) -> None: + # make a list so we can index it + stack_ops = [x for x in stack_ops] + + # print("ENTER reorder", stack.stack, operands) + # start_len = len(assembly) + for i in range(len(stack_ops)): + op = stack_ops[i] + final_stack_depth = -(len(stack_ops) - i - 1) + depth = stack.get_depth(op) + + if depth == final_stack_depth: + continue + + # print("trace", depth, final_stack_depth) + stack.swap(assembly, depth) + stack.swap(assembly, final_stack_depth) + + # print("INSTRUCTIONS", assembly[start_len:]) + # print("EXIT reorder", stack.stack, stack_ops) + + def _emit_input_operands( + self, + ctx: IRFunction, + assembly: list, + inst: IRInstruction, + ops: list[IRValueBase], + stack: StackModel, + ): + # PRE: we already have all the items on the stack that have + # been scheduled to be killed. now it's just a matter of emitting + # SWAPs, DUPs and PUSHes until we match the `ops` argument + + # print("EMIT INPUTS FOR", inst) + + # dumb heuristic: if the top of stack is not wanted here, swap + # it with something that is wanted + if ops and stack.stack and stack.stack[-1] not in ops: + for op in ops: + if isinstance(op, IRVariable) and op not in inst.dup_requirements: + stack.swap_op(assembly, op) + break + + emited_ops = [] + for op in ops: + if isinstance(op, IRLabel): + # invoke emits the actual instruction itself so we don't need to emit it here + # but we need to add it to the stack map + if inst.opcode != "invoke": + assembly.append(f"_sym_{op.value}") + stack.push(op) + continue + + if op.is_literal: + assembly.extend([*PUSH(op.value)]) + stack.push(op) + continue + + if op in inst.dup_requirements: + stack.dup_op(assembly, op) + + if op in emited_ops: + stack.dup_op(assembly, op) + + # REVIEW: this seems like it can be reordered across volatile + # boundaries (which includes memory fences). maybe just + # remove it entirely at this point + if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: + assembly.extend([*PUSH(op.mem_addr)]) + assembly.append("MLOAD") + + emited_ops.append(op) + + def _generate_evm_for_basicblock_r( + self, ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack: StackModel + ): + if basicblock in self.visited_basicblocks: + return + self.visited_basicblocks.add(basicblock) + + asm.append(f"_sym_{basicblock.label}") + asm.append("JUMPDEST") + + for inst in basicblock.instructions: + asm = self._generate_evm_for_instruction(ctx, asm, inst, stack) + + for bb in basicblock.cfg_out: + self._generate_evm_for_basicblock_r(ctx, asm, bb, stack.copy()) + + # REVIEW: would this be better as a class? + # HK: Let's consider it after the pass_dft refactor + def _generate_evm_for_instruction( + self, ctx: IRFunction, assembly: list, inst: IRInstruction, stack: StackModel + ) -> list[str]: + opcode = inst.opcode + + # + # generate EVM for op + # + + # Step 1: Apply instruction special stack manipulations + + if opcode in ["jmp", "jnz", "invoke"]: + operands = inst.get_non_label_operands() + elif opcode == "alloca": + operands = inst.operands[1:2] + elif opcode == "iload": + operands = [] + elif opcode == "istore": + operands = inst.operands[0:1] + else: + operands = inst.operands + + if opcode == "phi": + ret = inst.get_outputs()[0] + inputs = inst.get_inputs() + depth = stack.get_shallowest_depth(inputs) + to_be_replaced = stack.peek(depth) + if to_be_replaced in inst.dup_requirements: + stack.dup(assembly, depth) + stack.poke(0, ret) + else: + stack.poke(depth, ret) + return assembly + + # Step 2: Emit instruction's input operands + self._emit_input_operands(ctx, assembly, inst, operands, stack) + + # Step 3: Reorder stack + if opcode in ["jnz", "jmp"]: + assert isinstance(inst.parent.cfg_out, OrderedSet) + b = next(iter(inst.parent.cfg_out)) + target_stack = b.in_vars_from(inst.parent) + self._stack_reorder(assembly, stack, target_stack) + + # print("pre-dups (inst)", inst.dup_requirements, stack.stack, inst) + + self._stack_reorder(assembly, stack, operands) + # print("post-reorder (inst)", stack.stack, inst) + + # REVIEW: it would be clearer if the order of steps 4 and 5 were + # switched (so that the runtime order matches the order they appear + # below). + # Step 4: Push instruction's return value to stack + stack.pop(len(operands)) + if inst.ret is not None: + stack.push(inst.ret) + + # Step 5: Emit the EVM instruction(s) + if opcode in self.ONE_TO_ONE_INSTRUCTIONS: + assembly.append(opcode.upper()) + elif opcode == "alloca": + pass + elif opcode == "param": + pass + elif opcode == "store": + pass + elif opcode == "dbname": + pass + elif opcode in ["codecopy", "dloadbytes"]: + assembly.append("CODECOPY") + elif opcode == "jnz": + assembly.append(f"_sym_{inst.operands[1].value}") + assembly.append("JUMPI") + elif opcode == "jmp": + if isinstance(inst.operands[0], IRLabel): + assembly.append(f"_sym_{inst.operands[0].value}") + assembly.append("JUMP") + else: + assembly.append("JUMP") + elif opcode == "gt": + assembly.append("GT") + elif opcode == "lt": + assembly.append("LT") + elif opcode == "invoke": + target = inst.operands[0] + assert isinstance(target, IRLabel), "invoke target must be a label" + assembly.extend( + [ + f"_sym_label_ret_{self.label_counter}", + f"_sym_{target.value}", + "JUMP", + f"_sym_label_ret_{self.label_counter}", + "JUMPDEST", + ] + ) + self.label_counter += 1 + if stack.get_height() > 0 and stack.peek(0) in inst.dup_requirements: + stack.pop() + assembly.append("POP") + elif opcode == "call": + assembly.append("CALL") + elif opcode == "staticcall": + assembly.append("STATICCALL") + elif opcode == "ret": + assembly.append("JUMP") + elif opcode == "return": + assembly.append("RETURN") + elif opcode == "phi": + pass + elif opcode == "sha3": + assembly.append("SHA3") + elif opcode == "sha3_64": + assembly.extend( + [ + *PUSH(MemoryPositions.FREE_VAR_SPACE2), + "MSTORE", + *PUSH(MemoryPositions.FREE_VAR_SPACE), + "MSTORE", + *PUSH(64), + *PUSH(MemoryPositions.FREE_VAR_SPACE), + "SHA3", + ] + ) + elif opcode == "ceil32": + 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)]) + assembly = assembly[-1] + elif opcode == "iload": + loc = inst.operands[0].value + assembly.extend(["_OFST", "_mem_deploy_end", loc, "MLOAD"]) + elif opcode == "istore": + loc = inst.operands[1].value + assembly.extend(["_OFST", "_mem_deploy_end", loc, "MSTORE"]) + else: + raise Exception(f"Unknown opcode: {opcode}") + + # Step 6: Emit instructions output operands (if any) + if inst.ret is not None: + assert isinstance(inst.ret, IRVariable), "Return value must be a variable" + if inst.ret.mem_type == MemType.MEMORY: + assembly.extend([*PUSH(inst.ret.mem_addr)]) + + return assembly diff --git a/vyper/venom/ir_pass.py b/vyper/venom/pass_base.py similarity index 99% rename from vyper/venom/ir_pass.py rename to vyper/venom/pass_base.py index 11b3c98c85..b66da7789b 100644 --- a/vyper/venom/ir_pass.py +++ b/vyper/venom/pass_base.py @@ -1,10 +1,12 @@ # REVIEW: can move this to `vyper/venom/passes/base_pass.py` or something + class IRPass: """ Decorator for IR passes. This decorator will run the pass repeatedly until no more changes are made. """ + @classmethod def run_pass(cls, *args, **kwargs): t = cls() diff --git a/vyper/venom/passes/pass_dft.py b/vyper/venom/passes/pass_dft.py index bd03f573be..3de6f3ad0d 100644 --- a/vyper/venom/passes/pass_dft.py +++ b/vyper/venom/passes/pass_dft.py @@ -1,7 +1,80 @@ from vyper.utils import OrderedSet -from vyper.venom.basicblock import IRBasicBlock, IRInstruction +from vyper.venom.basicblock import ( + IRBasicBlock, + IRInstruction, + IRVariable, + MemType, +) from vyper.venom.function import IRFunction -from vyper.venom.ir_pass import IRPass +from vyper.venom.pass_base import IRPass + + +# DataFlow Graph +class DFG: + _dfg_inputs: dict[IRVariable, list[IRInstruction]] + _dfg_outputs: dict[IRVariable, IRInstruction] + + def __init__(self): + self._dfg_inputs = dict() + self._dfg_outputs = dict() + + # return uses of a given variable + def get_uses(self, op: IRVariable) -> list[IRInstruction]: + return self._dfg_inputs.get(op, []) + + # the instruction which produces this variable. + def get_producing_instruction(self, op: IRVariable) -> IRInstruction: + return self._dfg_outputs[op] + + @classmethod + def calculate_dfg(cls, ctx: IRFunction) -> None: + dfg = DFG.from_ir_function(ctx) + ctx.dfg = dfg + + dfg._compute_dup_requirements(ctx) + + def _compute_dup_requirements(self, ctx: IRFunction) -> None: + for bb in ctx.basic_blocks: + last_seen = dict() + + for inst in bb.instructions: + # reset dup_requirements + inst.dup_requirements = OrderedSet() + + for op in inst.get_inputs(): + if op in last_seen: + target = last_seen[op] + target.dup_requirements.add(op) + + last_seen[op] = inst + + if op in bb.out_vars: + inst.dup_requirements.add(op) + + @classmethod + def from_ir_function(cls, ctx: IRFunction): + dfg = cls() + + # Build DFG + + # %15 = add %13 %14 + # %16 = iszero %15 + # dfg_outputs of %15 is (%15 = add %13 %14) + # dfg_inputs of %15 is all the instructions which *use* %15, ex. [(%16 = iszero %15), ...] + for bb in ctx.basic_blocks: + for inst in bb.instructions: + operands = inst.get_inputs() + res = inst.get_outputs() + + for op in operands: + inputs = dfg._dfg_inputs.setdefault(op, []) + inputs.append(inst) + + for op in res: + dfg._dfg_outputs[op] = inst + + return dfg + # DataFlow Transformation class DFTPass(IRPass): @@ -63,7 +136,6 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: self._process_instruction_r(bb, inst) def _run_pass(self, ctx: IRFunction) -> None: - # print(ctx) self.ctx = ctx self.fence_id = 0 self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet() @@ -73,5 +145,3 @@ def _run_pass(self, ctx: IRFunction) -> None: for bb in basic_blocks: self._process_basic_block(bb) - - # print(ctx) From 00fde3f428c4232270796963b845b5c30eec3656 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 2 Nov 2023 11:08:35 -0400 Subject: [PATCH 333/471] add some review --- vyper/venom/ir.py | 22 ++++++++++++++----- .../{pass_base.py => passes/base_pass.py} | 0 ...propagation.py => constant_propagation.py} | 0 vyper/venom/passes/{pass_dft.py => dft.py} | 2 +- vyper/venom/stack_model.py | 1 + 5 files changed, 19 insertions(+), 6 deletions(-) rename vyper/venom/{pass_base.py => passes/base_pass.py} (100%) rename vyper/venom/passes/{pass_constant_propagation.py => constant_propagation.py} (100%) rename vyper/venom/passes/{pass_dft.py => dft.py} (98%) diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index 8e8589134e..bfad1af6ed 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -22,8 +22,8 @@ ) from vyper.venom.function import IRFunction from vyper.venom.ir_to_bb_pass import convert_ir_basicblock -from vyper.venom.passes.pass_constant_propagation import ir_pass_constant_propagation -from vyper.venom.passes.pass_dft import DFG, DFTPass +from vyper.venom.passes.constant_propagation import ir_pass_constant_propagation +from vyper.venom.passes.dft import DFG, DFTPass from vyper.venom.stack_model import StackModel @@ -59,6 +59,7 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF calculate_cfg_in(ctx) calculate_liveness(ctx) + # REVIEW: i think we can move calculate_dfg inside of DFTPass (so it's an implementation detail) DFG.calculate_dfg(ctx) if changes == 0: @@ -68,6 +69,7 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF class VenomCompiler: + # REVIEW: this could be a global, also for performance, could be set or frozenset ONE_TO_ONE_INSTRUCTIONS = [ "revert", "coinbase", @@ -186,8 +188,16 @@ def _stack_reorder( # print("INSTRUCTIONS", assembly[start_len:]) # print("EXIT reorder", stack.stack, stack_ops) + # REVIEW: possible swap implementation + # def swap(self, op): + # depth = self.stack.get_depth(op) + # assert depth is not StackModel.NOT_IN_STACK, f"not in stack: {op}" + # self.stack.swap(depth) + # self.assembly.append(_evm_swap_for(depth)) # f"SWAP{-depth}") + def _emit_input_operands( self, + # REVIEW: ctx, assembly and stack could be moved onto the VenomCompiler instance ctx: IRFunction, assembly: list, inst: IRInstruction, @@ -205,10 +215,12 @@ def _emit_input_operands( if ops and stack.stack and stack.stack[-1] not in ops: for op in ops: if isinstance(op, IRVariable) and op not in inst.dup_requirements: + # REVIEW: maybe move swap_op and dup_op onto this class, so that + # StackModel doesn't need to know about the assembly list stack.swap_op(assembly, op) break - emited_ops = [] + emitted_ops = [] for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -226,7 +238,7 @@ def _emit_input_operands( if op in inst.dup_requirements: stack.dup_op(assembly, op) - if op in emited_ops: + if op in emitted_ops: stack.dup_op(assembly, op) # REVIEW: this seems like it can be reordered across volatile @@ -236,7 +248,7 @@ def _emit_input_operands( assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") - emited_ops.append(op) + emitted_ops.append(op) def _generate_evm_for_basicblock_r( self, ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack: StackModel diff --git a/vyper/venom/pass_base.py b/vyper/venom/passes/base_pass.py similarity index 100% rename from vyper/venom/pass_base.py rename to vyper/venom/passes/base_pass.py diff --git a/vyper/venom/passes/pass_constant_propagation.py b/vyper/venom/passes/constant_propagation.py similarity index 100% rename from vyper/venom/passes/pass_constant_propagation.py rename to vyper/venom/passes/constant_propagation.py diff --git a/vyper/venom/passes/pass_dft.py b/vyper/venom/passes/dft.py similarity index 98% rename from vyper/venom/passes/pass_dft.py rename to vyper/venom/passes/dft.py index 3de6f3ad0d..6712209007 100644 --- a/vyper/venom/passes/pass_dft.py +++ b/vyper/venom/passes/dft.py @@ -6,7 +6,7 @@ MemType, ) from vyper.venom.function import IRFunction -from vyper.venom.pass_base import IRPass +from vyper.venom.passes.base_pass import IRPass # DataFlow Graph diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index c9fb520ea7..2250d8ed99 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -98,6 +98,7 @@ def swap(self, assembly: list[str], depth: int) -> None: return assert depth < 0, "Cannot swap positive depth" + # REVIEW: move EVM details into EVM generation pass assembly.append(f"SWAP{-depth}") top = self.stack[-1] self.stack[-1] = self.stack[depth - 1] From d637f2c23ed7a380b02f2df87888074772b903e1 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 2 Nov 2023 11:31:35 -0400 Subject: [PATCH 334/471] review --- vyper/venom/ir.py | 9 +++++++-- vyper/venom/stack_model.py | 1 - 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index bfad1af6ed..fd8b39c70e 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -68,6 +68,8 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF return ctx + +# REVIEW: move to ir_to_assembly.py for max modularity class VenomCompiler: # REVIEW: this could be a global, also for performance, could be set or frozenset ONE_TO_ONE_INSTRUCTIONS = [ @@ -126,16 +128,18 @@ class VenomCompiler: visited_basicblocks = None # {IRBasicBlock} def generate_evm(self, ctx: IRFunction, no_optimize: bool = False) -> list[str]: - asm = [] self.visited_instructions = OrderedSet() self.visited_basicblocks = OrderedSet() self.label_counter = 0 + stack = StackModel() + asm = [] + calculate_cfg_in(ctx) calculate_liveness(ctx) DFG.calculate_dfg(ctx) - self._generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], StackModel()) + self._generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], stack) # Append postambles revert_postamble = ["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"] @@ -250,6 +254,7 @@ def _emit_input_operands( emitted_ops.append(op) + # REVIEW: remove asm and stack from recursion, move to self. def _generate_evm_for_basicblock_r( self, ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack: StackModel ): diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 2250d8ed99..b0bc79c939 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -87,7 +87,6 @@ def dup_op(self, assembly: list[str], op: IRValueBase) -> None: depth = self.get_depth(op) self.dup(assembly, depth) - # REVIEW: use positive indices (and hide the negation inside StackModel implementation) def swap(self, assembly: list[str], depth: int) -> None: """ Swaps the operand at the given depth in the stack map with the top of the stack. From 01ce696db6db178cf86fbe72037a5265a97b9f20 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 2 Nov 2023 11:59:06 -0400 Subject: [PATCH 335/471] add explanation for `phi` magic --- vyper/venom/basicblock.py | 36 +++++++++++++++++++++--------------- vyper/venom/ir.py | 8 ++++++-- vyper/venom/stack_model.py | 22 +++++++++++++--------- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 2a188679ae..e6a65a9117 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -277,22 +277,28 @@ def in_vars_from(self, source: "IRBasicBlock") -> OrderedSet[IRVariable]: assert isinstance(liveness, OrderedSet) for inst in self.instructions: - # REVIEW: might be nice if some of these instructions - # were more structured. - # HK: Can you elaborate on this? I'm not sure what you mean. - # REVIEW: I meant, if there were classs like - # IRPhi(IRInstruction) - # IRStore(IRInstruction) - # etc. if inst.opcode == "phi": - if inst.operands[0] == source.label: - liveness.add(inst.operands[1]) - if inst.operands[3] in liveness: - liveness.remove(inst.operands[3]) - if inst.operands[2] == source.label: - liveness.add(inst.operands[3]) - if inst.operands[1] in liveness: - liveness.remove(inst.operands[1]) + # we arbitrarily choose one of the arguments to be in the + # live variables set (dependent on how we traversed into this + # basic block). the argument will be replaced by the destination + # operand during instruction selection. + # for instance, `%56 = phi %label1 %12 %label2 %14` + # will arbitrarily choose either %12 or %14 to be in the liveness + # set, and then during instruction selection, after this instruction, + # %12 will be replaced by %56 in the liveness set + source1, source2 = inst.operands[0], inst.operands[2] + phi1, phi2 = inst.operands[1], inst.operands[3] + if source.label == source1: + liveness.add(phi1) + if phi2 in liveness: + liveness.remove(phi2) + elif source.label == source2: + liveness.add(phi2) + if phi1 in liveness: + liveness.remove(phi1) + else: + # bad path into this phi node + raise CompilerPanic(f"unreachable: {inst}") return liveness diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index fd8b39c70e..f70fcde2e2 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -297,10 +297,14 @@ def _generate_evm_for_instruction( if opcode == "phi": ret = inst.get_outputs()[0] - inputs = inst.get_inputs() - depth = stack.get_shallowest_depth(inputs) + phi1, phi2 = inst.get_inputs() + depth = stack.get_phi_depth(phi1, phi2) + # collapse the arguments to the phi node in the stack. + # example, for `%56 = %label1 %13 %label2 %14`, we will + # find an instance of %13 *or* %14 in the stack and replace it with %56. to_be_replaced = stack.peek(depth) if to_be_replaced in inst.dup_requirements: + # %13/%14 is still live(!), so we make a copy of it stack.dup(assembly, depth) stack.poke(0, ret) else: diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index b0bc79c939..5b0e1ce44c 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -1,4 +1,4 @@ -from vyper.venom.basicblock import IRValueBase +from vyper.venom.basicblock import IRValueBase, IRVariable class StackModel: @@ -42,18 +42,22 @@ def get_depth(self, op: IRValueBase) -> int: return StackModel.NOT_IN_STACK - def get_shallowest_depth(self, ops: list[IRValueBase]) -> int: + def get_phi_depth(self, phi1: IRVariable, phi2: IRVariable) -> int: """ - Returns the depth of the first matching operand in the stack map. - If the none of the operands in is `ops` is in the stack, returns NOT_IN_STACK. + Returns the depth of the first matching phi variable in the stack map. + If the none of the phi operands are in the stack, returns NOT_IN_STACK. + Asserts that exactly one of phi1 and phi2 is found. """ - assert isinstance(ops, list), f"get_shallowest_depth takes list, got '{ops}'" + assert isinstance(phi1, IRVariable) + assert isinstance(phi2, IRVariable) - for i, stack_op in enumerate(reversed(self.stack)): - if stack_op in ops: - return -i + ret = StackModel.NOT_IN_STACK + for i, stack_item in enumerate(reversed(self.stack)): + if stack_item in (phi1, phi2): + assert ret is StackModel.NOT_IN_STACK, f"phi argument is not unique! {phi1}, {phi2}, {self.stack}" + ret = -i - return StackModel.NOT_IN_STACK + return ret def peek(self, depth: int) -> IRValueBase: """ From 07b8c7c8479c34ab7dd0ea17323dee64055e6096 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 2 Nov 2023 12:03:11 -0400 Subject: [PATCH 336/471] add review --- vyper/venom/bb_optimizer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index 16ce8d972d..3d8b33449e 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -57,6 +57,7 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> int: return count +# REVIEW: rename to `calculate_cfg` def calculate_cfg_in(ctx: IRFunction) -> None: """ Calculate (cfg) inputs for each basic block. From 6c6c883899e491fce23f195e854bc84dc6acc2f7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 10:52:42 +0200 Subject: [PATCH 337/471] ONE_TO_ONE_INSTRUCTIONS refactor * Move to global scope * Convert to frozerset() --- vyper/venom/ir.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index f70fcde2e2..11a6ef30f6 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -68,11 +68,8 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF return ctx - -# REVIEW: move to ir_to_assembly.py for max modularity -class VenomCompiler: - # REVIEW: this could be a global, also for performance, could be set or frozenset - ONE_TO_ONE_INSTRUCTIONS = [ +ONE_TO_ONE_INSTRUCTIONS = frozenset( + [ "revert", "coinbase", "calldatasize", @@ -122,7 +119,11 @@ class VenomCompiler: "log3", "log4", ] +) + +# REVIEW: move to ir_to_assembly.py for max modularity +class VenomCompiler: label_counter = 0 visited_instructions = None # {IRInstruction} visited_basicblocks = None # {IRBasicBlock} @@ -335,7 +336,7 @@ def _generate_evm_for_instruction( stack.push(inst.ret) # Step 5: Emit the EVM instruction(s) - if opcode in self.ONE_TO_ONE_INSTRUCTIONS: + if opcode in ONE_TO_ONE_INSTRUCTIONS: assembly.append(opcode.upper()) elif opcode == "alloca": pass From d2ae3acbb99f6dce94c409c31a0727ccb3dfecc8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 10:58:36 +0200 Subject: [PATCH 338/471] rename calculate_cfg_in to calculate_cfg --- vyper/venom/bb_optimizer.py | 5 ++--- vyper/venom/ir.py | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index 3d8b33449e..f6c656208e 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -57,8 +57,7 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> int: return count -# REVIEW: rename to `calculate_cfg` -def calculate_cfg_in(ctx: IRFunction) -> None: +def calculate_cfg(ctx: IRFunction) -> None: """ Calculate (cfg) inputs for each basic block. """ @@ -134,7 +133,7 @@ def calculate_liveness(ctx: IRFunction) -> None: @ir_pass def ir_pass_optimize_empty_blocks(ctx: IRFunction) -> int: changes = _optimize_empty_basicblocks(ctx) - calculate_cfg_in(ctx) + calculate_cfg(ctx) return changes diff --git a/vyper/venom/ir.py b/vyper/venom/ir.py index 11a6ef30f6..1060d304bb 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/ir.py @@ -14,7 +14,7 @@ MemType, ) from vyper.venom.bb_optimizer import ( - calculate_cfg_in, + calculate_cfg, calculate_liveness, ir_pass_optimize_empty_blocks, ir_pass_optimize_unused_variables, @@ -50,14 +50,14 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF changes += ir_pass_optimize_unused_variables(ctx) - calculate_cfg_in(ctx) + calculate_cfg(ctx) calculate_liveness(ctx) DFG.calculate_dfg(ctx) changes += ir_pass_constant_propagation(ctx) changes += DFTPass.run_pass(ctx) - calculate_cfg_in(ctx) + calculate_cfg(ctx) calculate_liveness(ctx) # REVIEW: i think we can move calculate_dfg inside of DFTPass (so it's an implementation detail) DFG.calculate_dfg(ctx) @@ -136,7 +136,7 @@ def generate_evm(self, ctx: IRFunction, no_optimize: bool = False) -> list[str]: stack = StackModel() asm = [] - calculate_cfg_in(ctx) + calculate_cfg(ctx) calculate_liveness(ctx) DFG.calculate_dfg(ctx) From db598f39a7f1408f60ceed4c98ac60ae5ba9ae6a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 11:01:21 +0200 Subject: [PATCH 339/471] rename ir.py to __init__.py --- tests/compiler/venom/test_duplicate_operands.py | 4 ++-- vyper/compiler/phases.py | 2 +- vyper/venom/{ir.py => __init__.py} | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) rename vyper/venom/{ir.py => __init__.py} (99%) diff --git a/tests/compiler/venom/test_duplicate_operands.py b/tests/compiler/venom/test_duplicate_operands.py index 40d6486e8d..57af1536cc 100644 --- a/tests/compiler/venom/test_duplicate_operands.py +++ b/tests/compiler/venom/test_duplicate_operands.py @@ -1,7 +1,7 @@ from vyper.compiler.settings import OptimizationLevel -from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRLiteral +from vyper.venom.basicblock import IRLiteral from vyper.venom.function import IRFunction -from vyper.venom.ir import generate_assembly_experimental +from vyper.venom import generate_assembly_experimental def test_duplicate_operands(): diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index fdfb12d8fd..f51145ce57 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -14,7 +14,7 @@ from vyper.semantics import set_data_positions, validate_semantics from vyper.semantics.types.function import ContractFunctionT from vyper.typing import InterfaceImports, StorageLayout -from vyper.venom.ir import generate_assembly_experimental, generate_ir +from vyper.venom import generate_assembly_experimental, generate_ir class CompilerData: diff --git a/vyper/venom/ir.py b/vyper/venom/__init__.py similarity index 99% rename from vyper/venom/ir.py rename to vyper/venom/__init__.py index 1060d304bb..29bc6abe05 100644 --- a/vyper/venom/ir.py +++ b/vyper/venom/__init__.py @@ -1,4 +1,3 @@ -# REVIEW: maybe this should be __init__.py (or some name less generic than 'ir.py') from typing import Optional from vyper.codegen.ir_node import IRnode From f40423855481c0d9ec70f2836e7938a13b7c8899 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 11:07:06 +0200 Subject: [PATCH 340/471] move VenomCompiler to own module --- vyper/venom/__init__.py | 381 +-------------------------------- vyper/venom/ir_to_assembly.py | 382 ++++++++++++++++++++++++++++++++++ 2 files changed, 383 insertions(+), 380 deletions(-) create mode 100644 vyper/venom/ir_to_assembly.py diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 29bc6abe05..86597d815c 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -2,16 +2,6 @@ from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel -from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly -from vyper.utils import MemoryPositions, OrderedSet -from vyper.venom.basicblock import ( - IRBasicBlock, - IRInstruction, - IRLabel, - IRValueBase, - IRVariable, - MemType, -) from vyper.venom.bb_optimizer import ( calculate_cfg, calculate_liveness, @@ -20,10 +10,10 @@ ir_pass_remove_unreachable_blocks, ) from vyper.venom.function import IRFunction +from vyper.venom.ir_to_assembly import VenomCompiler from vyper.venom.ir_to_bb_pass import convert_ir_basicblock from vyper.venom.passes.constant_propagation import ir_pass_constant_propagation from vyper.venom.passes.dft import DFG, DFTPass -from vyper.venom.stack_model import StackModel def generate_assembly_experimental( @@ -65,372 +55,3 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF break return ctx - - -ONE_TO_ONE_INSTRUCTIONS = frozenset( - [ - "revert", - "coinbase", - "calldatasize", - "calldatacopy", - "calldataload", - "gas", - "gasprice", - "gaslimit", - "address", - "origin", - "number", - "extcodesize", - "extcodehash", - "returndatasize", - "returndatacopy", - "callvalue", - "selfbalance", - "sload", - "sstore", - "mload", - "mstore", - "timestamp", - "caller", - "selfdestruct", - "signextend", - "stop", - "shr", - "shl", - "and", - "xor", - "or", - "add", - "sub", - "mul", - "div", - "mod", - "exp", - "eq", - "iszero", - "lg", - "lt", - "slt", - "sgt", - "log0", - "log1", - "log2", - "log3", - "log4", - ] -) - - -# REVIEW: move to ir_to_assembly.py for max modularity -class VenomCompiler: - label_counter = 0 - visited_instructions = None # {IRInstruction} - visited_basicblocks = None # {IRBasicBlock} - - def generate_evm(self, ctx: IRFunction, no_optimize: bool = False) -> list[str]: - self.visited_instructions = OrderedSet() - self.visited_basicblocks = OrderedSet() - self.label_counter = 0 - - stack = StackModel() - asm = [] - - calculate_cfg(ctx) - calculate_liveness(ctx) - DFG.calculate_dfg(ctx) - - self._generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], stack) - - # 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() - - asm.extend(revert_postamble) - if runtime: - runtime.extend(revert_postamble) - asm.append(runtime) - - # Append data segment - data_segments = {} - 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]) - - if no_optimize is False: - optimize_assembly(asm) - - return asm - - def _stack_reorder( - self, assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVariable] - ) -> None: - # make a list so we can index it - stack_ops = [x for x in stack_ops] - - # print("ENTER reorder", stack.stack, operands) - # start_len = len(assembly) - for i in range(len(stack_ops)): - op = stack_ops[i] - final_stack_depth = -(len(stack_ops) - i - 1) - depth = stack.get_depth(op) - - if depth == final_stack_depth: - continue - - # print("trace", depth, final_stack_depth) - stack.swap(assembly, depth) - stack.swap(assembly, final_stack_depth) - - # print("INSTRUCTIONS", assembly[start_len:]) - # print("EXIT reorder", stack.stack, stack_ops) - - # REVIEW: possible swap implementation - # def swap(self, op): - # depth = self.stack.get_depth(op) - # assert depth is not StackModel.NOT_IN_STACK, f"not in stack: {op}" - # self.stack.swap(depth) - # self.assembly.append(_evm_swap_for(depth)) # f"SWAP{-depth}") - - def _emit_input_operands( - self, - # REVIEW: ctx, assembly and stack could be moved onto the VenomCompiler instance - ctx: IRFunction, - assembly: list, - inst: IRInstruction, - ops: list[IRValueBase], - stack: StackModel, - ): - # PRE: we already have all the items on the stack that have - # been scheduled to be killed. now it's just a matter of emitting - # SWAPs, DUPs and PUSHes until we match the `ops` argument - - # print("EMIT INPUTS FOR", inst) - - # dumb heuristic: if the top of stack is not wanted here, swap - # it with something that is wanted - if ops and stack.stack and stack.stack[-1] not in ops: - for op in ops: - if isinstance(op, IRVariable) and op not in inst.dup_requirements: - # REVIEW: maybe move swap_op and dup_op onto this class, so that - # StackModel doesn't need to know about the assembly list - stack.swap_op(assembly, op) - break - - emitted_ops = [] - for op in ops: - if isinstance(op, IRLabel): - # invoke emits the actual instruction itself so we don't need to emit it here - # but we need to add it to the stack map - if inst.opcode != "invoke": - assembly.append(f"_sym_{op.value}") - stack.push(op) - continue - - if op.is_literal: - assembly.extend([*PUSH(op.value)]) - stack.push(op) - continue - - if op in inst.dup_requirements: - stack.dup_op(assembly, op) - - if op in emitted_ops: - stack.dup_op(assembly, op) - - # REVIEW: this seems like it can be reordered across volatile - # boundaries (which includes memory fences). maybe just - # remove it entirely at this point - if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: - assembly.extend([*PUSH(op.mem_addr)]) - assembly.append("MLOAD") - - emitted_ops.append(op) - - # REVIEW: remove asm and stack from recursion, move to self. - def _generate_evm_for_basicblock_r( - self, ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack: StackModel - ): - if basicblock in self.visited_basicblocks: - return - self.visited_basicblocks.add(basicblock) - - asm.append(f"_sym_{basicblock.label}") - asm.append("JUMPDEST") - - for inst in basicblock.instructions: - asm = self._generate_evm_for_instruction(ctx, asm, inst, stack) - - for bb in basicblock.cfg_out: - self._generate_evm_for_basicblock_r(ctx, asm, bb, stack.copy()) - - # REVIEW: would this be better as a class? - # HK: Let's consider it after the pass_dft refactor - def _generate_evm_for_instruction( - self, ctx: IRFunction, assembly: list, inst: IRInstruction, stack: StackModel - ) -> list[str]: - opcode = inst.opcode - - # - # generate EVM for op - # - - # Step 1: Apply instruction special stack manipulations - - if opcode in ["jmp", "jnz", "invoke"]: - operands = inst.get_non_label_operands() - elif opcode == "alloca": - operands = inst.operands[1:2] - elif opcode == "iload": - operands = [] - elif opcode == "istore": - operands = inst.operands[0:1] - else: - operands = inst.operands - - if opcode == "phi": - ret = inst.get_outputs()[0] - phi1, phi2 = inst.get_inputs() - depth = stack.get_phi_depth(phi1, phi2) - # collapse the arguments to the phi node in the stack. - # example, for `%56 = %label1 %13 %label2 %14`, we will - # find an instance of %13 *or* %14 in the stack and replace it with %56. - to_be_replaced = stack.peek(depth) - if to_be_replaced in inst.dup_requirements: - # %13/%14 is still live(!), so we make a copy of it - stack.dup(assembly, depth) - stack.poke(0, ret) - else: - stack.poke(depth, ret) - return assembly - - # Step 2: Emit instruction's input operands - self._emit_input_operands(ctx, assembly, inst, operands, stack) - - # Step 3: Reorder stack - if opcode in ["jnz", "jmp"]: - assert isinstance(inst.parent.cfg_out, OrderedSet) - b = next(iter(inst.parent.cfg_out)) - target_stack = b.in_vars_from(inst.parent) - self._stack_reorder(assembly, stack, target_stack) - - # print("pre-dups (inst)", inst.dup_requirements, stack.stack, inst) - - self._stack_reorder(assembly, stack, operands) - # print("post-reorder (inst)", stack.stack, inst) - - # REVIEW: it would be clearer if the order of steps 4 and 5 were - # switched (so that the runtime order matches the order they appear - # below). - # Step 4: Push instruction's return value to stack - stack.pop(len(operands)) - if inst.ret is not None: - stack.push(inst.ret) - - # Step 5: Emit the EVM instruction(s) - if opcode in ONE_TO_ONE_INSTRUCTIONS: - assembly.append(opcode.upper()) - elif opcode == "alloca": - pass - elif opcode == "param": - pass - elif opcode == "store": - pass - elif opcode == "dbname": - pass - elif opcode in ["codecopy", "dloadbytes"]: - assembly.append("CODECOPY") - elif opcode == "jnz": - assembly.append(f"_sym_{inst.operands[1].value}") - assembly.append("JUMPI") - elif opcode == "jmp": - if isinstance(inst.operands[0], IRLabel): - assembly.append(f"_sym_{inst.operands[0].value}") - assembly.append("JUMP") - else: - assembly.append("JUMP") - elif opcode == "gt": - assembly.append("GT") - elif opcode == "lt": - assembly.append("LT") - elif opcode == "invoke": - target = inst.operands[0] - assert isinstance(target, IRLabel), "invoke target must be a label" - assembly.extend( - [ - f"_sym_label_ret_{self.label_counter}", - f"_sym_{target.value}", - "JUMP", - f"_sym_label_ret_{self.label_counter}", - "JUMPDEST", - ] - ) - self.label_counter += 1 - if stack.get_height() > 0 and stack.peek(0) in inst.dup_requirements: - stack.pop() - assembly.append("POP") - elif opcode == "call": - assembly.append("CALL") - elif opcode == "staticcall": - assembly.append("STATICCALL") - elif opcode == "ret": - assembly.append("JUMP") - elif opcode == "return": - assembly.append("RETURN") - elif opcode == "phi": - pass - elif opcode == "sha3": - assembly.append("SHA3") - elif opcode == "sha3_64": - assembly.extend( - [ - *PUSH(MemoryPositions.FREE_VAR_SPACE2), - "MSTORE", - *PUSH(MemoryPositions.FREE_VAR_SPACE), - "MSTORE", - *PUSH(64), - *PUSH(MemoryPositions.FREE_VAR_SPACE), - "SHA3", - ] - ) - elif opcode == "ceil32": - 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)]) - assembly = assembly[-1] - elif opcode == "iload": - loc = inst.operands[0].value - assembly.extend(["_OFST", "_mem_deploy_end", loc, "MLOAD"]) - elif opcode == "istore": - loc = inst.operands[1].value - assembly.extend(["_OFST", "_mem_deploy_end", loc, "MSTORE"]) - else: - raise Exception(f"Unknown opcode: {opcode}") - - # Step 6: Emit instructions output operands (if any) - if inst.ret is not None: - assert isinstance(inst.ret, IRVariable), "Return value must be a variable" - if inst.ret.mem_type == MemType.MEMORY: - assembly.extend([*PUSH(inst.ret.mem_addr)]) - - return assembly diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/ir_to_assembly.py new file mode 100644 index 0000000000..043e90e703 --- /dev/null +++ b/vyper/venom/ir_to_assembly.py @@ -0,0 +1,382 @@ +from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly +from vyper.utils import MemoryPositions, OrderedSet +from vyper.venom.basicblock import ( + IRBasicBlock, + IRInstruction, + IRLabel, + IRValueBase, + IRVariable, + MemType, +) +from vyper.venom.bb_optimizer import calculate_cfg, calculate_liveness +from vyper.venom.function import IRFunction +from vyper.venom.passes.dft import DFG +from vyper.venom.stack_model import StackModel + + +ONE_TO_ONE_INSTRUCTIONS = frozenset( + [ + "revert", + "coinbase", + "calldatasize", + "calldatacopy", + "calldataload", + "gas", + "gasprice", + "gaslimit", + "address", + "origin", + "number", + "extcodesize", + "extcodehash", + "returndatasize", + "returndatacopy", + "callvalue", + "selfbalance", + "sload", + "sstore", + "mload", + "mstore", + "timestamp", + "caller", + "selfdestruct", + "signextend", + "stop", + "shr", + "shl", + "and", + "xor", + "or", + "add", + "sub", + "mul", + "div", + "mod", + "exp", + "eq", + "iszero", + "lg", + "lt", + "slt", + "sgt", + "log0", + "log1", + "log2", + "log3", + "log4", + ] +) + + +class VenomCompiler: + label_counter = 0 + visited_instructions = None # {IRInstruction} + visited_basicblocks = None # {IRBasicBlock} + + def generate_evm(self, ctx: IRFunction, no_optimize: bool = False) -> list[str]: + self.visited_instructions = OrderedSet() + self.visited_basicblocks = OrderedSet() + self.label_counter = 0 + + stack = StackModel() + asm = [] + + calculate_cfg(ctx) + calculate_liveness(ctx) + DFG.calculate_dfg(ctx) + + self._generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], stack) + + # 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() + + asm.extend(revert_postamble) + if runtime: + runtime.extend(revert_postamble) + asm.append(runtime) + + # Append data segment + data_segments = {} + 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]) + + if no_optimize is False: + optimize_assembly(asm) + + return asm + + def _stack_reorder( + self, assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVariable] + ) -> None: + # make a list so we can index it + stack_ops = [x for x in stack_ops] + + # print("ENTER reorder", stack.stack, operands) + # start_len = len(assembly) + for i in range(len(stack_ops)): + op = stack_ops[i] + final_stack_depth = -(len(stack_ops) - i - 1) + depth = stack.get_depth(op) + + if depth == final_stack_depth: + continue + + # print("trace", depth, final_stack_depth) + stack.swap(assembly, depth) + stack.swap(assembly, final_stack_depth) + + # print("INSTRUCTIONS", assembly[start_len:]) + # print("EXIT reorder", stack.stack, stack_ops) + + # REVIEW: possible swap implementation + # def swap(self, op): + # depth = self.stack.get_depth(op) + # assert depth is not StackModel.NOT_IN_STACK, f"not in stack: {op}" + # self.stack.swap(depth) + # self.assembly.append(_evm_swap_for(depth)) # f"SWAP{-depth}") + + def _emit_input_operands( + self, + # REVIEW: ctx, assembly and stack could be moved onto the VenomCompiler instance + ctx: IRFunction, + assembly: list, + inst: IRInstruction, + ops: list[IRValueBase], + stack: StackModel, + ): + # PRE: we already have all the items on the stack that have + # been scheduled to be killed. now it's just a matter of emitting + # SWAPs, DUPs and PUSHes until we match the `ops` argument + + # print("EMIT INPUTS FOR", inst) + + # dumb heuristic: if the top of stack is not wanted here, swap + # it with something that is wanted + if ops and stack.stack and stack.stack[-1] not in ops: + for op in ops: + if isinstance(op, IRVariable) and op not in inst.dup_requirements: + # REVIEW: maybe move swap_op and dup_op onto this class, so that + # StackModel doesn't need to know about the assembly list + stack.swap_op(assembly, op) + break + + emitted_ops = [] + for op in ops: + if isinstance(op, IRLabel): + # invoke emits the actual instruction itself so we don't need to emit it here + # but we need to add it to the stack map + if inst.opcode != "invoke": + assembly.append(f"_sym_{op.value}") + stack.push(op) + continue + + if op.is_literal: + assembly.extend([*PUSH(op.value)]) + stack.push(op) + continue + + if op in inst.dup_requirements: + stack.dup_op(assembly, op) + + if op in emitted_ops: + stack.dup_op(assembly, op) + + # REVIEW: this seems like it can be reordered across volatile + # boundaries (which includes memory fences). maybe just + # remove it entirely at this point + if isinstance(op, IRVariable) and op.mem_type == MemType.MEMORY: + assembly.extend([*PUSH(op.mem_addr)]) + assembly.append("MLOAD") + + emitted_ops.append(op) + + # REVIEW: remove asm and stack from recursion, move to self. + def _generate_evm_for_basicblock_r( + self, ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack: StackModel + ): + if basicblock in self.visited_basicblocks: + return + self.visited_basicblocks.add(basicblock) + + asm.append(f"_sym_{basicblock.label}") + asm.append("JUMPDEST") + + for inst in basicblock.instructions: + asm = self._generate_evm_for_instruction(ctx, asm, inst, stack) + + for bb in basicblock.cfg_out: + self._generate_evm_for_basicblock_r(ctx, asm, bb, stack.copy()) + + # REVIEW: would this be better as a class? + # HK: Let's consider it after the pass_dft refactor + def _generate_evm_for_instruction( + self, ctx: IRFunction, assembly: list, inst: IRInstruction, stack: StackModel + ) -> list[str]: + opcode = inst.opcode + + # + # generate EVM for op + # + + # Step 1: Apply instruction special stack manipulations + + if opcode in ["jmp", "jnz", "invoke"]: + operands = inst.get_non_label_operands() + elif opcode == "alloca": + operands = inst.operands[1:2] + elif opcode == "iload": + operands = [] + elif opcode == "istore": + operands = inst.operands[0:1] + else: + operands = inst.operands + + if opcode == "phi": + ret = inst.get_outputs()[0] + phi1, phi2 = inst.get_inputs() + depth = stack.get_phi_depth(phi1, phi2) + # collapse the arguments to the phi node in the stack. + # example, for `%56 = %label1 %13 %label2 %14`, we will + # find an instance of %13 *or* %14 in the stack and replace it with %56. + to_be_replaced = stack.peek(depth) + if to_be_replaced in inst.dup_requirements: + # %13/%14 is still live(!), so we make a copy of it + stack.dup(assembly, depth) + stack.poke(0, ret) + else: + stack.poke(depth, ret) + return assembly + + # Step 2: Emit instruction's input operands + self._emit_input_operands(ctx, assembly, inst, operands, stack) + + # Step 3: Reorder stack + if opcode in ["jnz", "jmp"]: + assert isinstance(inst.parent.cfg_out, OrderedSet) + b = next(iter(inst.parent.cfg_out)) + target_stack = b.in_vars_from(inst.parent) + self._stack_reorder(assembly, stack, target_stack) + + # print("pre-dups (inst)", inst.dup_requirements, stack.stack, inst) + + self._stack_reorder(assembly, stack, operands) + # print("post-reorder (inst)", stack.stack, inst) + + # REVIEW: it would be clearer if the order of steps 4 and 5 were + # switched (so that the runtime order matches the order they appear + # below). + # Step 4: Push instruction's return value to stack + stack.pop(len(operands)) + if inst.ret is not None: + stack.push(inst.ret) + + # Step 5: Emit the EVM instruction(s) + if opcode in ONE_TO_ONE_INSTRUCTIONS: + assembly.append(opcode.upper()) + elif opcode == "alloca": + pass + elif opcode == "param": + pass + elif opcode == "store": + pass + elif opcode == "dbname": + pass + elif opcode in ["codecopy", "dloadbytes"]: + assembly.append("CODECOPY") + elif opcode == "jnz": + assembly.append(f"_sym_{inst.operands[1].value}") + assembly.append("JUMPI") + elif opcode == "jmp": + if isinstance(inst.operands[0], IRLabel): + assembly.append(f"_sym_{inst.operands[0].value}") + assembly.append("JUMP") + else: + assembly.append("JUMP") + elif opcode == "gt": + assembly.append("GT") + elif opcode == "lt": + assembly.append("LT") + elif opcode == "invoke": + target = inst.operands[0] + assert isinstance(target, IRLabel), "invoke target must be a label" + assembly.extend( + [ + f"_sym_label_ret_{self.label_counter}", + f"_sym_{target.value}", + "JUMP", + f"_sym_label_ret_{self.label_counter}", + "JUMPDEST", + ] + ) + self.label_counter += 1 + if stack.get_height() > 0 and stack.peek(0) in inst.dup_requirements: + stack.pop() + assembly.append("POP") + elif opcode == "call": + assembly.append("CALL") + elif opcode == "staticcall": + assembly.append("STATICCALL") + elif opcode == "ret": + assembly.append("JUMP") + elif opcode == "return": + assembly.append("RETURN") + elif opcode == "phi": + pass + elif opcode == "sha3": + assembly.append("SHA3") + elif opcode == "sha3_64": + assembly.extend( + [ + *PUSH(MemoryPositions.FREE_VAR_SPACE2), + "MSTORE", + *PUSH(MemoryPositions.FREE_VAR_SPACE), + "MSTORE", + *PUSH(64), + *PUSH(MemoryPositions.FREE_VAR_SPACE), + "SHA3", + ] + ) + elif opcode == "ceil32": + 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)]) + assembly = assembly[-1] + elif opcode == "iload": + loc = inst.operands[0].value + assembly.extend(["_OFST", "_mem_deploy_end", loc, "MLOAD"]) + elif opcode == "istore": + loc = inst.operands[1].value + assembly.extend(["_OFST", "_mem_deploy_end", loc, "MSTORE"]) + else: + raise Exception(f"Unknown opcode: {opcode}") + + # Step 6: Emit instructions output operands (if any) + if inst.ret is not None: + assert isinstance(inst.ret, IRVariable), "Return value must be a variable" + if inst.ret.mem_type == MemType.MEMORY: + assembly.extend([*PUSH(inst.ret.mem_addr)]) + + return assembly From 911a1f2164e6e65294c27d28a49e9601870e5283 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 11:09:47 +0200 Subject: [PATCH 341/471] Remove unused parameters * remove unused parameter from _generate_evm_for_instruction() * remove unused parameter from _emit_input_operands() --- vyper/venom/ir_to_assembly.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/ir_to_assembly.py index 043e90e703..6c10711ef6 100644 --- a/vyper/venom/ir_to_assembly.py +++ b/vyper/venom/ir_to_assembly.py @@ -147,8 +147,6 @@ def _stack_reorder( def _emit_input_operands( self, - # REVIEW: ctx, assembly and stack could be moved onto the VenomCompiler instance - ctx: IRFunction, assembly: list, inst: IRInstruction, ops: list[IRValueBase], @@ -212,7 +210,7 @@ def _generate_evm_for_basicblock_r( asm.append("JUMPDEST") for inst in basicblock.instructions: - asm = self._generate_evm_for_instruction(ctx, asm, inst, stack) + asm = self._generate_evm_for_instruction(asm, inst, stack) for bb in basicblock.cfg_out: self._generate_evm_for_basicblock_r(ctx, asm, bb, stack.copy()) @@ -220,7 +218,7 @@ def _generate_evm_for_basicblock_r( # REVIEW: would this be better as a class? # HK: Let's consider it after the pass_dft refactor def _generate_evm_for_instruction( - self, ctx: IRFunction, assembly: list, inst: IRInstruction, stack: StackModel + self, assembly: list, inst: IRInstruction, stack: StackModel ) -> list[str]: opcode = inst.opcode @@ -258,7 +256,7 @@ def _generate_evm_for_instruction( return assembly # Step 2: Emit instruction's input operands - self._emit_input_operands(ctx, assembly, inst, operands, stack) + self._emit_input_operands(assembly, inst, operands, stack) # Step 3: Reorder stack if opcode in ["jnz", "jmp"]: From 7da6d0c23311bf13a53057af33d36b6839dd397c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 11:26:47 +0200 Subject: [PATCH 342/471] Move ctx to the class --- vyper/venom/__init__.py | 6 +++--- vyper/venom/ir_to_assembly.py | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 86597d815c..5936ba3c46 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -17,10 +17,10 @@ def generate_assembly_experimental( - ir: IRFunction, optimize: Optional[OptimizationLevel] = None + ctx: IRFunction, optimize: Optional[OptimizationLevel] = None ) -> list[str]: - compiler = VenomCompiler() - return compiler.generate_evm(ir, optimize is OptimizationLevel.NONE) + compiler = VenomCompiler(ctx) + return compiler.generate_evm(optimize is OptimizationLevel.NONE) def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRFunction: diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/ir_to_assembly.py index 6c10711ef6..2a00747dce 100644 --- a/vyper/venom/ir_to_assembly.py +++ b/vyper/venom/ir_to_assembly.py @@ -69,11 +69,18 @@ class VenomCompiler: + ctx: IRFunction label_counter = 0 visited_instructions = None # {IRInstruction} visited_basicblocks = None # {IRBasicBlock} - def generate_evm(self, ctx: IRFunction, no_optimize: bool = False) -> list[str]: + def __init__(self, ctx: IRFunction): + self.ctx = ctx + self.label_counter = 0 + self.visited_instructions = None + self.visited_basicblocks = None + + def generate_evm(self, no_optimize: bool = False) -> list[str]: self.visited_instructions = OrderedSet() self.visited_basicblocks = OrderedSet() self.label_counter = 0 @@ -81,11 +88,11 @@ def generate_evm(self, ctx: IRFunction, no_optimize: bool = False) -> list[str]: stack = StackModel() asm = [] - calculate_cfg(ctx) - calculate_liveness(ctx) - DFG.calculate_dfg(ctx) + calculate_cfg(self.ctx) + calculate_liveness(self.ctx) + DFG.calculate_dfg(self.ctx) - self._generate_evm_for_basicblock_r(ctx, asm, ctx.basic_blocks[0], stack) + self._generate_evm_for_basicblock_r(asm, self.ctx.basic_blocks[0], stack) # Append postambles revert_postamble = ["_sym___revert", "JUMPDEST", *PUSH(0), "DUP1", "REVERT"] @@ -100,7 +107,7 @@ def generate_evm(self, ctx: IRFunction, no_optimize: bool = False) -> list[str]: # Append data segment data_segments = {} - for inst in ctx.data_segment: + for inst in self.ctx.data_segment: if inst.opcode == "dbname": label = inst.operands[0].value data_segments[label] = [DataHeader(f"_sym_{label}")] @@ -200,7 +207,7 @@ def _emit_input_operands( # REVIEW: remove asm and stack from recursion, move to self. def _generate_evm_for_basicblock_r( - self, ctx: IRFunction, asm: list, basicblock: IRBasicBlock, stack: StackModel + self, asm: list, basicblock: IRBasicBlock, stack: StackModel ): if basicblock in self.visited_basicblocks: return @@ -213,7 +220,7 @@ def _generate_evm_for_basicblock_r( asm = self._generate_evm_for_instruction(asm, inst, stack) for bb in basicblock.cfg_out: - self._generate_evm_for_basicblock_r(ctx, asm, bb, stack.copy()) + self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) # REVIEW: would this be better as a class? # HK: Let's consider it after the pass_dft refactor From ea0eadfd4273166c4808890bbf8580990e04b8ef Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 11:43:39 +0200 Subject: [PATCH 343/471] review comment --- vyper/venom/ir_to_assembly.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/ir_to_assembly.py index 2a00747dce..d7a8945295 100644 --- a/vyper/venom/ir_to_assembly.py +++ b/vyper/venom/ir_to_assembly.py @@ -68,6 +68,14 @@ ) +# REVIEW: "assembly" gets into the recursion due to how the original +# IR was structured recursively in regards with the deploy instruction. +# There, recursing into the deploy instruction was by design, and +# made it easier to make the assembly generated "recursive" (i.e. +# instructions being lists of instructions). We don't have this restriction +# anymore, so we can probably refactor this to be iterative in coordination +# 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 label_counter = 0 @@ -205,7 +213,6 @@ def _emit_input_operands( emitted_ops.append(op) - # REVIEW: remove asm and stack from recursion, move to self. def _generate_evm_for_basicblock_r( self, asm: list, basicblock: IRBasicBlock, stack: StackModel ): From 14d8af89d54d3da0025cc228abf58ab8fbc4d08e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 12:10:29 +0200 Subject: [PATCH 344/471] Make StackMap decoupled from evm --- vyper/venom/ir_to_assembly.py | 40 +++++++++++++++++++++++++++++------ vyper/venom/stack_model.py | 29 ++++++++++++------------- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/ir_to_assembly.py index d7a8945295..e341dfd744 100644 --- a/vyper/venom/ir_to_assembly.py +++ b/vyper/venom/ir_to_assembly.py @@ -147,8 +147,8 @@ def _stack_reorder( continue # print("trace", depth, final_stack_depth) - stack.swap(assembly, depth) - stack.swap(assembly, final_stack_depth) + self.swap(assembly, stack, depth) + self.swap(assembly, stack, final_stack_depth) # print("INSTRUCTIONS", assembly[start_len:]) # print("EXIT reorder", stack.stack, stack_ops) @@ -180,7 +180,7 @@ def _emit_input_operands( if isinstance(op, IRVariable) and op not in inst.dup_requirements: # REVIEW: maybe move swap_op and dup_op onto this class, so that # StackModel doesn't need to know about the assembly list - stack.swap_op(assembly, op) + self.swap_op(assembly, stack, op) break emitted_ops = [] @@ -199,10 +199,10 @@ def _emit_input_operands( continue if op in inst.dup_requirements: - stack.dup_op(assembly, op) + self.dup_op(assembly, stack, op) if op in emitted_ops: - stack.dup_op(assembly, op) + self.dup_op(assembly, stack, op) # REVIEW: this seems like it can be reordered across volatile # boundaries (which includes memory fences). maybe just @@ -263,7 +263,7 @@ def _generate_evm_for_instruction( to_be_replaced = stack.peek(depth) if to_be_replaced in inst.dup_requirements: # %13/%14 is still live(!), so we make a copy of it - stack.dup(assembly, depth) + self.dup(assembly, stack, depth) stack.poke(0, ret) else: stack.poke(depth, ret) @@ -392,3 +392,31 @@ def _generate_evm_for_instruction( assembly.extend([*PUSH(inst.ret.mem_addr)]) return assembly + + def swap(self, assembly, stack, depth): + if depth == 0: + return + stack.swap(depth) + assembly.append(_evm_swap_for(depth)) + + def dup(self, assembly, stack, depth): + stack.dup(depth) + assembly.append(_evm_dup_for(depth)) + + def swap_op(self, assembly, stack, op): + self.swap(assembly, stack, stack.get_depth(op)) + + def dup_op(self, assembly, stack, op): + self.dup(assembly, stack, stack.get_depth(op)) + + +def _evm_swap_for(depth: int) -> str: + swap_idx = -depth + assert swap_idx >= 1 and swap_idx <= 16, "Unsupported swap depth" + return f"SWAP{swap_idx}" + + +def _evm_dup_for(depth: int) -> str: + dup_idx = 1 - depth + assert dup_idx >= 1 and dup_idx <= 16, "Unsupported dup depth" + return f"DUP{dup_idx}" diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 5b0e1ce44c..9cfe06f63a 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -54,7 +54,9 @@ def get_phi_depth(self, phi1: IRVariable, phi2: IRVariable) -> int: ret = StackModel.NOT_IN_STACK for i, stack_item in enumerate(reversed(self.stack)): if stack_item in (phi1, phi2): - assert ret is StackModel.NOT_IN_STACK, f"phi argument is not unique! {phi1}, {phi2}, {self.stack}" + assert ( + ret is StackModel.NOT_IN_STACK + ), f"phi argument is not unique! {phi1}, {phi2}, {self.stack}" ret = -i return ret @@ -75,44 +77,39 @@ def poke(self, depth: int, op: IRValueBase) -> None: assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" self.stack[depth - 1] = op - def dup(self, assembly: list[str], depth: int) -> None: + def dup(self, depth: int) -> None: """ Duplicates the operand at the given depth in the stack map. """ assert depth is not StackModel.NOT_IN_STACK, "Cannot dup non-existent operand" assert depth <= 0, "Cannot dup positive depth" - assembly.append(f"DUP{-(depth-1)}") self.stack.append(self.peek(depth)) - def dup_op(self, assembly: list[str], op: IRValueBase) -> None: + def dup_op(self, op: IRValueBase) -> None: """ Convinience method: duplicates the given operand in the stack map. + Returns the depth of the duplicated operand in the stack map. """ depth = self.get_depth(op) - self.dup(assembly, depth) + self.dup(depth) + return depth - def swap(self, assembly: list[str], depth: int) -> None: + def swap(self, depth: int) -> None: """ Swaps the operand at the given depth in the stack map with the top of the stack. """ assert depth is not StackModel.NOT_IN_STACK, "Cannot swap non-existent operand" - # convenience, avoids branching in caller - if depth == 0: - return - assert depth < 0, "Cannot swap positive depth" - # REVIEW: move EVM details into EVM generation pass - assembly.append(f"SWAP{-depth}") top = self.stack[-1] self.stack[-1] = self.stack[depth - 1] self.stack[depth - 1] = top - def swap_op(self, assembly: list[str], op: IRValueBase) -> None: + def swap_op(self, op: IRValueBase) -> None: """ Convinience method: swaps the given operand in the stack map with the top of the stack. + Returns the depth of the swapped operand in the stack map. """ depth = self.get_depth(op) - self.swap(assembly, depth) - - # REVIEW: maybe have a convenience function which swaps depth1 and depth2 + self.swap(depth) + return depth From 4cef5dd73d988e0fc70749128f4256da32b6e0c6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 12:14:20 +0200 Subject: [PATCH 345/471] review comments --- vyper/venom/__init__.py | 1 - vyper/venom/ir_to_assembly.py | 12 +++--------- vyper/venom/passes/base_pass.py | 3 --- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 5936ba3c46..ac2107e063 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -48,7 +48,6 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF calculate_cfg(ctx) calculate_liveness(ctx) - # REVIEW: i think we can move calculate_dfg inside of DFTPass (so it's an implementation detail) DFG.calculate_dfg(ctx) if changes == 0: diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/ir_to_assembly.py index e341dfd744..c095393fc5 100644 --- a/vyper/venom/ir_to_assembly.py +++ b/vyper/venom/ir_to_assembly.py @@ -153,13 +153,6 @@ def _stack_reorder( # print("INSTRUCTIONS", assembly[start_len:]) # print("EXIT reorder", stack.stack, stack_ops) - # REVIEW: possible swap implementation - # def swap(self, op): - # depth = self.stack.get_depth(op) - # assert depth is not StackModel.NOT_IN_STACK, f"not in stack: {op}" - # self.stack.swap(depth) - # self.assembly.append(_evm_swap_for(depth)) # f"SWAP{-depth}") - def _emit_input_operands( self, assembly: list, @@ -229,8 +222,6 @@ def _generate_evm_for_basicblock_r( for bb in basicblock.cfg_out: self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) - # REVIEW: would this be better as a class? - # HK: Let's consider it after the pass_dft refactor def _generate_evm_for_instruction( self, assembly: list, inst: IRInstruction, stack: StackModel ) -> list[str]: @@ -287,6 +278,9 @@ def _generate_evm_for_instruction( # REVIEW: it would be clearer if the order of steps 4 and 5 were # switched (so that the runtime order matches the order they appear # below). + # HK: Some instructions (e.i. invoke) need to do some stack manipulations + # with the stack containing the return values. + # Step 4: Push instruction's return value to stack stack.pop(len(operands)) if inst.ret is not None: diff --git a/vyper/venom/passes/base_pass.py b/vyper/venom/passes/base_pass.py index b66da7789b..92b333c2aa 100644 --- a/vyper/venom/passes/base_pass.py +++ b/vyper/venom/passes/base_pass.py @@ -1,6 +1,3 @@ -# REVIEW: can move this to `vyper/venom/passes/base_pass.py` or something - - class IRPass: """ Decorator for IR passes. This decorator will run the pass repeatedly From 1e5d885ccc914da6e3b472a28b16f917de8ebe1f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 12:18:34 +0200 Subject: [PATCH 346/471] remove unsused variable --- vyper/venom/bb_optimizer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index f6c656208e..9bdc919558 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -7,7 +7,6 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: """ Remove unused variables. """ - count = 0 removeList = [] # REVIEW: performance, could be `set()` for bb in ctx.basic_blocks: for i, inst in enumerate(bb.instructions[:-1]): @@ -15,7 +14,6 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: continue if inst.ret and inst.ret not in bb.instructions[i + 1].liveness: removeList.append(inst) - count += 1 bb.instructions = [inst for inst in bb.instructions if inst not in removeList] From b9978e6484c864bf6012e7a095890f361c1ff262 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 12:22:10 +0200 Subject: [PATCH 347/471] Optimization: use set instead of list --- vyper/utils.py | 2 +- vyper/venom/bb_optimizer.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/utils.py b/vyper/utils.py index 5018fc7192..c06a8d345b 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -478,7 +478,7 @@ def wrapper(*args, **kwargs): while True: changes = func(*args, **kwargs) or 0 - if isinstance(changes, list): + if isinstance(changes, list) or isinstance(changes, set): changes = len(changes) count += changes if changes == 0: diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index 9bdc919558..ceaff3daeb 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -7,13 +7,13 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: """ Remove unused variables. """ - removeList = [] # REVIEW: performance, could be `set()` + removeList = set() for bb in ctx.basic_blocks: for i, inst in enumerate(bb.instructions[:-1]): if inst.volatile: continue if inst.ret and inst.ret not in bb.instructions[i + 1].liveness: - removeList.append(inst) + removeList.add(inst) bb.instructions = [inst for inst in bb.instructions if inst not in removeList] From 1348ce27c7f55a97098919d85a770cc6bd86ea1a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 12:23:34 +0200 Subject: [PATCH 348/471] comment clean up --- vyper/venom/ir_to_assembly.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/ir_to_assembly.py index c095393fc5..2b3c16c629 100644 --- a/vyper/venom/ir_to_assembly.py +++ b/vyper/venom/ir_to_assembly.py @@ -136,8 +136,6 @@ def _stack_reorder( # make a list so we can index it stack_ops = [x for x in stack_ops] - # print("ENTER reorder", stack.stack, operands) - # start_len = len(assembly) for i in range(len(stack_ops)): op = stack_ops[i] final_stack_depth = -(len(stack_ops) - i - 1) @@ -146,13 +144,9 @@ def _stack_reorder( if depth == final_stack_depth: continue - # print("trace", depth, final_stack_depth) self.swap(assembly, stack, depth) self.swap(assembly, stack, final_stack_depth) - # print("INSTRUCTIONS", assembly[start_len:]) - # print("EXIT reorder", stack.stack, stack_ops) - def _emit_input_operands( self, assembly: list, @@ -164,15 +158,11 @@ def _emit_input_operands( # been scheduled to be killed. now it's just a matter of emitting # SWAPs, DUPs and PUSHes until we match the `ops` argument - # print("EMIT INPUTS FOR", inst) - # dumb heuristic: if the top of stack is not wanted here, swap # it with something that is wanted if ops and stack.stack and stack.stack[-1] not in ops: for op in ops: if isinstance(op, IRVariable) and op not in inst.dup_requirements: - # REVIEW: maybe move swap_op and dup_op onto this class, so that - # StackModel doesn't need to know about the assembly list self.swap_op(assembly, stack, op) break @@ -270,10 +260,7 @@ def _generate_evm_for_instruction( target_stack = b.in_vars_from(inst.parent) self._stack_reorder(assembly, stack, target_stack) - # print("pre-dups (inst)", inst.dup_requirements, stack.stack, inst) - self._stack_reorder(assembly, stack, operands) - # print("post-reorder (inst)", stack.stack, inst) # REVIEW: it would be clearer if the order of steps 4 and 5 were # switched (so that the runtime order matches the order they appear From ff3b145fe13c7d7553701c8bba44b9ebfc1dbc88 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 13:14:17 +0200 Subject: [PATCH 349/471] clean up comments --- vyper/venom/passes/dft.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 6712209007..bb51b60e74 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -83,10 +83,8 @@ class DFTPass(IRPass): # which tries to optimize production of stack items to be as close as # possible to uses of stack items. def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): - # print("(inst)", inst) for op in inst.get_outputs(): for target in self.ctx.dfg.get_uses(op): - # print("(target)", target) if target.parent != inst.parent: # don't reorder across basic block boundaries continue @@ -99,15 +97,14 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): if inst in self.visited_instructions: return - # print("VISITING", inst) self.visited_instructions.add(inst) if inst.opcode == "phi": - # don't try to reorder inputs of phi + # phi instructions stay at the beginning of the basic block + # and no input processing is needed bb.instructions.append(inst) return - # print(inst.get_inputs(), inst) for op in inst.get_inputs(): target = self.ctx.dfg.get_producing_instruction(op) if target.parent != inst.parent: From 90e0891a76a4dff9116664ad4bde3c66a17d11e4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 13:39:57 +0200 Subject: [PATCH 350/471] simplified _process_instruction_r() ->smaller code --- vyper/venom/passes/dft.py | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index bb51b60e74..47e6c427c8 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -78,23 +78,7 @@ def from_ir_function(cls, ctx: IRFunction): # DataFlow Transformation class DFTPass(IRPass): - # recurse "down" into all the uses of `inst`, and then recurse "up" through - # all of its dependencies, to try to product an ordering of instructions - # which tries to optimize production of stack items to be as close as - # possible to uses of stack items. def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): - for op in inst.get_outputs(): - for target in self.ctx.dfg.get_uses(op): - if target.parent != inst.parent: - # don't reorder across basic block boundaries - continue - if target.fence_id != inst.fence_id: - # don't reorder across fence groups - continue - - # try to find the last use - self._process_instruction_r(bb, target) - if inst in self.visited_instructions: return self.visited_instructions.add(inst) @@ -107,13 +91,9 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): for op in inst.get_inputs(): target = self.ctx.dfg.get_producing_instruction(op) - if target.parent != inst.parent: + if target.parent != inst.parent or target.fence_id != inst.fence_id: + # don't reorder across basic block or fence boundaries continue - # REVIEW: should there be a check for fence here? i.e., - # ``` - # if target.fence_id != inst.fence_id: - # continue - # ``` self._process_instruction_r(bb, target) bb.instructions.append(inst) From ba0b51bf5a23e00018153a223156e56cbd819cb9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 16:41:13 +0200 Subject: [PATCH 351/471] commutative handling scafolding --- vyper/venom/ir_to_assembly.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/ir_to_assembly.py index 2b3c16c629..2deece4625 100644 --- a/vyper/venom/ir_to_assembly.py +++ b/vyper/venom/ir_to_assembly.py @@ -13,6 +13,7 @@ from vyper.venom.passes.dft import DFG from vyper.venom.stack_model import StackModel +COMMUTATIVE_INSTRUCTIONS = frozenset(["add", "mul", "and", "or", "xor", "eq"]) ONE_TO_ONE_INSTRUCTIONS = frozenset( [ @@ -131,14 +132,25 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: return asm def _stack_reorder( - self, assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVariable] + self, + assembly: list, + stack: StackModel, + stack_ops: OrderedSet[IRVariable], + commutative: bool = False, ) -> None: # make a list so we can index it stack_ops = [x for x in stack_ops] + stack_ops_count = len(stack_ops) - for i in range(len(stack_ops)): + if commutative: + depth = stack.get_depth(stack_ops[0]) + # TODO: Apply commutative knowledge to optimize stack + # if depth == 0: + # stack_ops = list(reversed(stack_ops)) + + for i in range(stack_ops_count): op = stack_ops[i] - final_stack_depth = -(len(stack_ops) - i - 1) + final_stack_depth = -(stack_ops_count - i - 1) depth = stack.get_depth(op) if depth == final_stack_depth: @@ -147,6 +159,13 @@ def _stack_reorder( self.swap(assembly, stack, depth) self.swap(assembly, stack, final_stack_depth) + def _get_commutative_alternative(self, depth: int) -> int: + if depth == 0: + return -1 + elif depth == -1: + return 0 + assert False, f"Invalid depth {depth}" + def _emit_input_operands( self, assembly: list, @@ -260,7 +279,8 @@ def _generate_evm_for_instruction( target_stack = b.in_vars_from(inst.parent) self._stack_reorder(assembly, stack, target_stack) - self._stack_reorder(assembly, stack, operands) + is_commutative = opcode in COMMUTATIVE_INSTRUCTIONS + self._stack_reorder(assembly, stack, operands, is_commutative) # REVIEW: it would be clearer if the order of steps 4 and 5 were # switched (so that the runtime order matches the order they appear From 0d6d5f626719e67dd805f481fc043cd3d1ec1009 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 16:42:07 +0200 Subject: [PATCH 352/471] imports cleanup --- tests/compiler/venom/test_duplicate_operands.py | 2 +- vyper/venom/passes/dft.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/compiler/venom/test_duplicate_operands.py b/tests/compiler/venom/test_duplicate_operands.py index 57af1536cc..505f01e31b 100644 --- a/tests/compiler/venom/test_duplicate_operands.py +++ b/tests/compiler/venom/test_duplicate_operands.py @@ -1,7 +1,7 @@ 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 -from vyper.venom import generate_assembly_experimental def test_duplicate_operands(): diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 47e6c427c8..6450134202 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -1,10 +1,5 @@ from vyper.utils import OrderedSet -from vyper.venom.basicblock import ( - IRBasicBlock, - IRInstruction, - IRVariable, - MemType, -) +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable, MemType from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass From 43bf549b46d50d2b6fe16419896934c04ba159f3 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 10:54:33 -0400 Subject: [PATCH 353/471] remove dead code --- vyper/venom/stack_model.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 9cfe06f63a..c9a1d0538b 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -85,15 +85,6 @@ def dup(self, depth: int) -> None: assert depth <= 0, "Cannot dup positive depth" self.stack.append(self.peek(depth)) - def dup_op(self, op: IRValueBase) -> None: - """ - Convinience method: duplicates the given operand in the stack map. - Returns the depth of the duplicated operand in the stack map. - """ - depth = self.get_depth(op) - self.dup(depth) - return depth - def swap(self, depth: int) -> None: """ Swaps the operand at the given depth in the stack map with the top of the stack. @@ -103,13 +94,3 @@ def swap(self, depth: int) -> None: top = self.stack[-1] self.stack[-1] = self.stack[depth - 1] self.stack[depth - 1] = top - - def swap_op(self, op: IRValueBase) -> None: - """ - Convinience method: swaps the given operand in the stack map with the - top of the stack. - Returns the depth of the swapped operand in the stack map. - """ - depth = self.get_depth(op) - self.swap(depth) - return depth From cb61ccba4287cae54c7ce596d1debd9d8847fe68 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 11:01:50 -0400 Subject: [PATCH 354/471] review comments --- vyper/venom/__init__.py | 4 ++++ vyper/venom/ir_to_assembly.py | 8 +++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index ac2107e063..a4e8d15bbb 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -1,3 +1,7 @@ +# REVIEW stylistically i don't really like code (besides just imports) +# going into `__init__.py`. maybe `main.py` or `venom.py` +# (can have an `__init__.py` which exposes the API). + from typing import Optional from vyper.codegen.ir_node import IRnode diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/ir_to_assembly.py index 2deece4625..78a1e6a73f 100644 --- a/vyper/venom/ir_to_assembly.py +++ b/vyper/venom/ir_to_assembly.py @@ -282,11 +282,9 @@ def _generate_evm_for_instruction( is_commutative = opcode in COMMUTATIVE_INSTRUCTIONS self._stack_reorder(assembly, stack, operands, is_commutative) - # REVIEW: it would be clearer if the order of steps 4 and 5 were - # switched (so that the runtime order matches the order they appear - # below). - # HK: Some instructions (e.i. invoke) need to do some stack manipulations - # with the stack containing the return values. + # some instructions (i.e. invoke) need to do stack manipulations + # with the stack model containing the return value(s), so we fiddle + # with the stack model beforehand. # Step 4: Push instruction's return value to stack stack.pop(len(operands)) From e811dc981fbffbc67e60dbd23a6febf70b7527e4 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 11:02:01 -0400 Subject: [PATCH 355/471] style: rename some globals with initial underscore --- vyper/venom/ir_to_assembly.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/ir_to_assembly.py index 78a1e6a73f..1761968bbc 100644 --- a/vyper/venom/ir_to_assembly.py +++ b/vyper/venom/ir_to_assembly.py @@ -13,9 +13,11 @@ from vyper.venom.passes.dft import DFG from vyper.venom.stack_model import StackModel -COMMUTATIVE_INSTRUCTIONS = frozenset(["add", "mul", "and", "or", "xor", "eq"]) +# binary instructions which are commutative +_COMMUTATIVE_INSTRUCTIONS = frozenset(["add", "mul", "and", "or", "xor", "eq"]) -ONE_TO_ONE_INSTRUCTIONS = frozenset( +# instructions which map one-to-one from venom to EVM +_ONE_TO_ONE_INSTRUCTIONS = frozenset( [ "revert", "coinbase", @@ -279,7 +281,7 @@ def _generate_evm_for_instruction( target_stack = b.in_vars_from(inst.parent) self._stack_reorder(assembly, stack, target_stack) - is_commutative = opcode in COMMUTATIVE_INSTRUCTIONS + is_commutative = opcode in _COMMUTATIVE_INSTRUCTIONS self._stack_reorder(assembly, stack, operands, is_commutative) # some instructions (i.e. invoke) need to do stack manipulations @@ -292,7 +294,7 @@ def _generate_evm_for_instruction( stack.push(inst.ret) # Step 5: Emit the EVM instruction(s) - if opcode in ONE_TO_ONE_INSTRUCTIONS: + if opcode in _ONE_TO_ONE_INSTRUCTIONS: assembly.append(opcode.upper()) elif opcode == "alloca": pass From ae0a1823707556c4f5dd8a414d8ddcbed69b46e6 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 11:05:23 -0400 Subject: [PATCH 356/471] rename ir_to_ modules for clarification --- vyper/venom/__init__.py | 4 ++-- vyper/venom/{ir_to_bb_pass.py => ir_node_to_venom.py} | 0 vyper/venom/{ir_to_assembly.py => venom_to_assembly.py} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename vyper/venom/{ir_to_bb_pass.py => ir_node_to_venom.py} (100%) rename vyper/venom/{ir_to_assembly.py => venom_to_assembly.py} (100%) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index a4e8d15bbb..07c0305af0 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -14,8 +14,8 @@ ir_pass_remove_unreachable_blocks, ) from vyper.venom.function import IRFunction -from vyper.venom.ir_to_assembly import VenomCompiler -from vyper.venom.ir_to_bb_pass import convert_ir_basicblock +from vyper.venom.venom_to_assembly import VenomCompiler +from vyper.venom.ir_node_to_venom import convert_ir_basicblock from vyper.venom.passes.constant_propagation import ir_pass_constant_propagation from vyper.venom.passes.dft import DFG, DFTPass diff --git a/vyper/venom/ir_to_bb_pass.py b/vyper/venom/ir_node_to_venom.py similarity index 100% rename from vyper/venom/ir_to_bb_pass.py rename to vyper/venom/ir_node_to_venom.py diff --git a/vyper/venom/ir_to_assembly.py b/vyper/venom/venom_to_assembly.py similarity index 100% rename from vyper/venom/ir_to_assembly.py rename to vyper/venom/venom_to_assembly.py From 433598f6fbbe14c1809dd3b5e74e13e031a338be Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 11:16:07 -0400 Subject: [PATCH 357/471] remove dead members on IRFunction, remove calculate_dfg, it's not necessary anymore move compute_dup_requirements to venom_to_assembly.py --- vyper/venom/__init__.py | 2 -- vyper/venom/function.py | 6 ------ vyper/venom/passes/dft.py | 32 +++++--------------------------- vyper/venom/venom_to_assembly.py | 24 ++++++++++++++++++++++-- 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 07c0305af0..670b8ecf2a 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -45,14 +45,12 @@ def generate_ir(ir: IRnode, optimize: Optional[OptimizationLevel] = None) -> IRF calculate_cfg(ctx) calculate_liveness(ctx) - DFG.calculate_dfg(ctx) changes += ir_pass_constant_propagation(ctx) changes += DFTPass.run_pass(ctx) calculate_cfg(ctx) calculate_liveness(ctx) - DFG.calculate_dfg(ctx) if changes == 0: break diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 0525b2625a..c4e624fa6c 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -22,8 +22,6 @@ class IRFunction: last_label: int last_variable: int - dfg: Any = None # dfg will be added in convert_ir_to_dfg pass - def __init__(self, name: IRLabel = IRLabel("global")) -> None: self.name = name self.args = [] @@ -31,8 +29,6 @@ def __init__(self, name: IRLabel = IRLabel("global")) -> None: self.data_segment = [] self.last_label = 0 self.last_variable = 0 - self.dfg_inputs = {} - self.dfg_outputs = {} self.append_basic_block(IRBasicBlock(name, self)) @@ -119,8 +115,6 @@ def copy(self): new.data_segment = self.data_segment.copy() new.last_label = self.last_label new.last_variable = self.last_variable - new.dfg_inputs = self.dfg_inputs.copy() - new.dfg_outputs = self.dfg_outputs.copy() return new def __repr__(self) -> str: diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 6450134202..bf954eb1ba 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -5,6 +5,8 @@ # DataFlow Graph +# this could be refactored into its own file, but it's only used here +# for now class DFG: _dfg_inputs: dict[IRVariable, list[IRInstruction]] _dfg_outputs: dict[IRVariable, IRInstruction] @@ -22,32 +24,7 @@ def get_producing_instruction(self, op: IRVariable) -> IRInstruction: return self._dfg_outputs[op] @classmethod - def calculate_dfg(cls, ctx: IRFunction) -> None: - dfg = DFG.from_ir_function(ctx) - ctx.dfg = dfg - - dfg._compute_dup_requirements(ctx) - - def _compute_dup_requirements(self, ctx: IRFunction) -> None: - for bb in ctx.basic_blocks: - last_seen = dict() - - for inst in bb.instructions: - # reset dup_requirements - inst.dup_requirements = OrderedSet() - - for op in inst.get_inputs(): - if op in last_seen: - target = last_seen[op] - target.dup_requirements.add(op) - - last_seen[op] = inst - - if op in bb.out_vars: - inst.dup_requirements.add(op) - - @classmethod - def from_ir_function(cls, ctx: IRFunction): + def build_dfg(cls, ctx: IRFunction): dfg = cls() # Build DFG @@ -85,7 +62,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): return for op in inst.get_inputs(): - target = self.ctx.dfg.get_producing_instruction(op) + target = self.dfg.get_producing_instruction(op) if target.parent != inst.parent or target.fence_id != inst.fence_id: # don't reorder across basic block or fence boundaries continue @@ -109,6 +86,7 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: def _run_pass(self, ctx: IRFunction) -> None: self.ctx = ctx + self.dfg = DFG.build_dfg(ctx) self.fence_id = 0 self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet() diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 1761968bbc..c77f94bcdc 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -10,7 +10,6 @@ ) from vyper.venom.bb_optimizer import calculate_cfg, calculate_liveness from vyper.venom.function import IRFunction -from vyper.venom.passes.dft import DFG from vyper.venom.stack_model import StackModel # binary instructions which are commutative @@ -70,6 +69,23 @@ ] ) +def _compute_dup_requirements(ctx: IRFunction) -> None: + for bb in ctx.basic_blocks: + last_seen = dict() + + for inst in bb.instructions: + # reset dup_requirements + inst.dup_requirements = OrderedSet() + + for op in inst.get_inputs(): + if op in last_seen: + target = last_seen[op] + target.dup_requirements.add(op) + + last_seen[op] = inst + + if op in bb.out_vars: + inst.dup_requirements.add(op) # REVIEW: "assembly" gets into the recursion due to how the original # IR was structured recursively in regards with the deploy instruction. @@ -100,8 +116,12 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: asm = [] calculate_cfg(self.ctx) + + # REVIEW: calculate_liveness and compute_dup_requirements are really + # related, maybe they can be combined somehow. or maybe they should go + # into vyper/venom/analysis.py calculate_liveness(self.ctx) - DFG.calculate_dfg(self.ctx) + _compute_dup_requirements(self.ctx) self._generate_evm_for_basicblock_r(asm, self.ctx.basic_blocks[0], stack) From a68da3b0f4c817e73245e6742c48d6c1db4e17f1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 18:10:48 +0200 Subject: [PATCH 358/471] fix --- 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 1a3ba3798e..74205bc6f8 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -83,6 +83,7 @@ def _get_symbols_common(a: dict, b: dict) -> dict: ret[k] = a[k], b[k] return ret + def convert_ir_basicblock(ir: IRnode) -> IRFunction: global_function = IRFunction(IRLabel("global")) _convert_ir_basicblock(global_function, ir, {}, OrderedSet(), {}) @@ -841,7 +842,7 @@ def emit_body_block(): body_block.replace_operands(replacements) body_end = ctx.get_basic_block() - if body_end.is_terminated() is False: + if body_end.is_terminated is False: body_end.append_instruction(IRInstruction("jmp", [jump_up_block.label])) jump_cond = IRInstruction("jmp", [increment_block.label]) From d867b8757491852e3b9b33488eba4f7e3927c8c0 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 11:28:36 -0400 Subject: [PATCH 359/471] small style change --- vyper/venom/venom_to_assembly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index c77f94bcdc..3127d4a74b 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -433,11 +433,11 @@ def dup_op(self, assembly, stack, op): def _evm_swap_for(depth: int) -> str: swap_idx = -depth - assert swap_idx >= 1 and swap_idx <= 16, "Unsupported swap depth" + assert 1 <= swap_idx <= 16, "Unsupported swap depth" return f"SWAP{swap_idx}" def _evm_dup_for(depth: int) -> str: dup_idx = 1 - depth - assert dup_idx >= 1 and dup_idx <= 16, "Unsupported dup depth" + assert 1 <= dup_idx <= 16, "Unsupported dup depth" return f"DUP{dup_idx}" From f75f4323969ecc51433cff647d5a85e31fffbfaa Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 11:49:48 -0400 Subject: [PATCH 360/471] change emitted_ops to set --- vyper/venom/venom_to_assembly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 3127d4a74b..641a205886 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -207,7 +207,7 @@ def _emit_input_operands( self.swap_op(assembly, stack, op) break - emitted_ops = [] + emitted_ops = OrderedSet() for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -235,7 +235,7 @@ def _emit_input_operands( assembly.extend([*PUSH(op.mem_addr)]) assembly.append("MLOAD") - emitted_ops.append(op) + emitted_ops.add(op) def _generate_evm_for_basicblock_r( self, asm: list, basicblock: IRBasicBlock, stack: StackModel From b49811eb32a08ac869f357bae131391656263f7d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 12:12:48 -0400 Subject: [PATCH 361/471] small additions to compute_dup_requirements --- vyper/venom/venom_to_assembly.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 641a205886..bf6bb012eb 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -69,23 +69,32 @@ ] ) +# figure out which variables we need to emit DUPs for for this +# instruction (because they are still live after the instruction def _compute_dup_requirements(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: - last_seen = dict() + _compute_dup_requirements_bb(bb: IRBasicBlock) - for inst in bb.instructions: - # reset dup_requirements - inst.dup_requirements = OrderedSet() +def _compute_dup_requirements_bb(bb: IRBasicBlock) -> None: + # the most recent instruction which used this variable + most_recent_use_of = dict() - for op in inst.get_inputs(): - if op in last_seen: - target = last_seen[op] - target.dup_requirements.add(op) + for inst in bb.instructions: + # reset dup_requirements + inst.dup_requirements = OrderedSet() - last_seen[op] = inst + for op in inst.get_inputs(): + # the variable is still live at `inst`, so we look + # back to `most_recent_use_of[op]` and add to its + # dup requirements. + if op in most_recent_use_of: + target = most_recent_use_of[op] + target.dup_requirements.add(op) - if op in bb.out_vars: - inst.dup_requirements.add(op) + most_recent_use_of[op] = inst + + if op in bb.out_vars: + inst.dup_requirements.add(op) # REVIEW: "assembly" gets into the recursion due to how the original # IR was structured recursively in regards with the deploy instruction. From 4202454584accaf6148813082c6f2d2edac304a8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 3 Nov 2023 18:14:37 +0200 Subject: [PATCH 362/471] typo fix --- vyper/venom/venom_to_assembly.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index bf6bb012eb..112a6f7861 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -69,11 +69,13 @@ ] ) + # figure out which variables we need to emit DUPs for for this # instruction (because they are still live after the instruction def _compute_dup_requirements(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: - _compute_dup_requirements_bb(bb: IRBasicBlock) + _compute_dup_requirements_bb(bb) + def _compute_dup_requirements_bb(bb: IRBasicBlock) -> None: # the most recent instruction which used this variable @@ -96,6 +98,7 @@ def _compute_dup_requirements_bb(bb: IRBasicBlock) -> None: if op in bb.out_vars: inst.dup_requirements.add(op) + # REVIEW: "assembly" gets into the recursion due to how the original # IR was structured recursively in regards with the deploy instruction. # There, recursing into the deploy instruction was by design, and From 8dfe14983844de58d536d9c2a2140d2d237eac27 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 14:33:31 -0400 Subject: [PATCH 363/471] add out vars to IRBasicBlock.__repr__() help debugging --- 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 e6a65a9117..58a293be0f 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -361,7 +361,7 @@ def copy(self): def __repr__(self) -> str: s = ( f"{repr(self.label)}: IN={[bb.label for bb in self.cfg_in]}" - f" OUT={[bb.label for bb in self.cfg_out]} \n" + f" OUT={[bb.label for bb in self.cfg_out]} => {self.out_vars} \n" ) for instruction in self.instructions: s += f" {instruction}\n" From b4e9940bc84c55d08eb0df9474c3d1b2d9a6987e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 14:41:32 -0400 Subject: [PATCH 364/471] add review --- vyper/venom/venom_to_assembly.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 112a6f7861..47496c482c 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -311,6 +311,8 @@ def _generate_evm_for_instruction( assert isinstance(inst.parent.cfg_out, OrderedSet) b = next(iter(inst.parent.cfg_out)) target_stack = b.in_vars_from(inst.parent) + # REVIEW: this seems like it generates bad code, because + # the next _stack_reorder will undo the changes to the stack. self._stack_reorder(assembly, stack, target_stack) is_commutative = opcode in _COMMUTATIVE_INSTRUCTIONS From 689cc113753518d1542ac5451fd2754a75992a14 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 14:54:36 -0400 Subject: [PATCH 365/471] more review --- vyper/venom/passes/base_pass.py | 1 + vyper/venom/venom_to_assembly.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/vyper/venom/passes/base_pass.py b/vyper/venom/passes/base_pass.py index 92b333c2aa..201ed7a9e4 100644 --- a/vyper/venom/passes/base_pass.py +++ b/vyper/venom/passes/base_pass.py @@ -11,6 +11,7 @@ def run_pass(cls, *args, **kwargs): while True: changes = t._run_pass(*args, **kwargs) or 0 + # REVIEW: let's make sure all passes return `int`s instead if isinstance(changes, list): changes = len(changes) count += changes diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 47496c482c..726885850e 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -343,6 +343,14 @@ def _generate_evm_for_instruction( elif opcode == "jnz": assembly.append(f"_sym_{inst.operands[1].value}") assembly.append("JUMPI") + # REVIEW: probably need to add + # assembly.append(f"_sym_{inst.operands[0].value}") + # assembly.append("JUMP") + # because we only happen to be guaranteed that the next + # basic block is coming in the CFG is inst.operands[0].value. + # but we should add the jump, in case the CFG traversal + # changes. or, add an assertion that + # `inst.operands[0].value == inst.parent.cfg_out[0]`. elif opcode == "jmp": if isinstance(inst.operands[0], IRLabel): assembly.append(f"_sym_{inst.operands[0].value}") From dd2fdbb633b0fb862ea2e2a0c5bdb66df69a54e5 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 3 Nov 2023 15:16:03 -0400 Subject: [PATCH 366/471] add review --- vyper/venom/venom_to_assembly.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 726885850e..bf701d4a6b 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -193,12 +193,12 @@ def _stack_reorder( self.swap(assembly, stack, depth) self.swap(assembly, stack, final_stack_depth) + # REVIEW: note this is dead code def _get_commutative_alternative(self, depth: int) -> int: + assert depth in (-1, 0), f"Invalid depth {depth}" if depth == 0: return -1 - elif depth == -1: - return 0 - assert False, f"Invalid depth {depth}" + return 0 def _emit_input_operands( self, @@ -249,6 +249,10 @@ def _emit_input_operands( emitted_ops.add(op) + # REVIEW: we don't need to thread the stack through this recursion, + # because the input stack will depend on CFG traversal order. would + # be better to construct a new stack model on entry into this + # function, which uses the stack layout calculated by the CFG inputs. def _generate_evm_for_basicblock_r( self, asm: list, basicblock: IRBasicBlock, stack: StackModel ): @@ -262,6 +266,7 @@ def _generate_evm_for_basicblock_r( for inst in basicblock.instructions: asm = self._generate_evm_for_instruction(asm, inst, stack) + # REVIEW: codegen for `jnz` depends on this traversal order. for bb in basicblock.cfg_out: self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) @@ -313,6 +318,7 @@ def _generate_evm_for_instruction( target_stack = b.in_vars_from(inst.parent) # REVIEW: this seems like it generates bad code, because # the next _stack_reorder will undo the changes to the stack. + # i think we can just remove it entirely. self._stack_reorder(assembly, stack, target_stack) is_commutative = opcode in _COMMUTATIVE_INSTRUCTIONS From aa67349ba0958572b7b3fc86b3d09b2ef9c35419 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 5 Nov 2023 12:53:43 +0200 Subject: [PATCH 367/471] fixed stack leftover regression --- .../venom/test_stack_at_external_return.py | 11 +++++++++++ vyper/venom/venom_to_assembly.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/compiler/venom/test_stack_at_external_return.py diff --git a/tests/compiler/venom/test_stack_at_external_return.py b/tests/compiler/venom/test_stack_at_external_return.py new file mode 100644 index 0000000000..319dc30c5c --- /dev/null +++ b/tests/compiler/venom/test_stack_at_external_return.py @@ -0,0 +1,11 @@ +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 + + +def test_stack_at_external_return(): + """ + TODO: USE BOA DO GENERATE THIS TEST + """ + pass diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index bf701d4a6b..878ea35abb 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -263,6 +263,20 @@ def _generate_evm_for_basicblock_r( asm.append(f"_sym_{basicblock.label}") asm.append("JUMPDEST") + # values to pop from stack + in_vars = OrderedSet() + for in_bb in basicblock.cfg_in: + in_vars |= in_bb.out_vars.difference(basicblock.in_vars_from(in_bb)) + + for var in in_vars: + depth = stack.get_depth(IRValueBase(var.value)) + if depth is StackModel.NOT_IN_STACK: + continue + if depth != 0: + stack.swap(asm, depth) + stack.pop() + asm.append("POP") + for inst in basicblock.instructions: asm = self._generate_evm_for_instruction(asm, inst, stack) From 27cf2b1152cb06b63d4354905137d70db74e60fd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 5 Nov 2023 13:01:58 +0200 Subject: [PATCH 368/471] Refactor BINARY_IR_INSTRUCTIONS * Rename to _BINARY_IR_INSTRUCTIONS * Make into a frosenset --- vyper/venom/ir_node_to_venom.py | 47 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 74205bc6f8..b51c692f57 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,7 +1,6 @@ from typing import Optional from vyper.codegen.ir_node import IRnode -from vyper.compiler.settings import OptimizationLevel from vyper.evm.opcodes import get_opcodes from vyper.ir.compile_ir import is_mem_sym, is_symbol from vyper.semantics.types.function import ContractFunctionT @@ -17,27 +16,29 @@ ) from vyper.venom.function import IRFunction -BINARY_IR_INSTRUCTIONS = [ - "eq", - "gt", - "lt", - "slt", - "sgt", - "shr", - "shl", - "or", - "xor", - "and", - "add", - "sub", - "mul", - "div", - "mod", - "exp", - "sha3", - "sha3_64", - "signextend", -] +_BINARY_IR_INSTRUCTIONS = frozenset( + [ + "eq", + "gt", + "lt", + "slt", + "sgt", + "shr", + "shl", + "or", + "xor", + "and", + "add", + "sub", + "mul", + "div", + "mod", + "exp", + "sha3", + "sha3_64", + "signextend", + ] +) # Instuctions that are mapped to their inverse INVERSE_MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} @@ -270,7 +271,7 @@ def _convert_ir_basicblock( assert isinstance(variables, OrderedSet) - if ir.value in BINARY_IR_INSTRUCTIONS: + if ir.value in _BINARY_IR_INSTRUCTIONS: return _convert_binary_op( ctx, ir, symbols, variables, allocated_variables, ir.value in ["sha3_64"] ) From 1ce0940e0a2dcdb16ec699c6e43e912baad30487 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 5 Nov 2023 13:06:06 +0200 Subject: [PATCH 369/471] all passes return change counts --- vyper/venom/bb_optimizer.py | 2 +- vyper/venom/passes/base_pass.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index ceaff3daeb..916b200290 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -142,4 +142,4 @@ def ir_pass_remove_unreachable_blocks(ctx: IRFunction) -> int: @ir_pass def ir_pass_optimize_unused_variables(ctx: IRFunction) -> int: - return _optimize_unused_variables(ctx) + return len(_optimize_unused_variables(ctx)) diff --git a/vyper/venom/passes/base_pass.py b/vyper/venom/passes/base_pass.py index 201ed7a9e4..11da80ac66 100644 --- a/vyper/venom/passes/base_pass.py +++ b/vyper/venom/passes/base_pass.py @@ -10,12 +10,9 @@ def run_pass(cls, *args, **kwargs): count = 0 while True: - changes = t._run_pass(*args, **kwargs) or 0 - # REVIEW: let's make sure all passes return `int`s instead - if isinstance(changes, list): - changes = len(changes) - count += changes - if changes == 0: + changes_count = t._run_pass(*args, **kwargs) or 0 + count += changes_count + if changes_count == 0: break return count From e9c499a49b3fe3b8a80c5a0574e391d02358c1d6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 5 Nov 2023 13:13:17 +0200 Subject: [PATCH 370/471] review comment --- vyper/venom/venom_to_assembly.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 878ea35abb..776feedc85 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -194,6 +194,7 @@ def _stack_reorder( self.swap(assembly, stack, final_stack_depth) # REVIEW: note this is dead code + # HK: is't for some WIP def _get_commutative_alternative(self, depth: int) -> int: assert depth in (-1, 0), f"Invalid depth {depth}" if depth == 0: @@ -253,6 +254,9 @@ def _emit_input_operands( # because the input stack will depend on CFG traversal order. would # be better to construct a new stack model on entry into this # function, which uses the stack layout calculated by the CFG inputs. + # HK: I think we need to thread the stack through the recursion because + # we need to ensure that the stack is in the correct state for the next + # basic block regardless from where we came from. Let's discuss offline. def _generate_evm_for_basicblock_r( self, asm: list, basicblock: IRBasicBlock, stack: StackModel ): @@ -333,6 +337,13 @@ def _generate_evm_for_instruction( # REVIEW: this seems like it generates bad code, because # the next _stack_reorder will undo the changes to the stack. # i think we can just remove it entirely. + # HK: Can't be removed entirely because we need to ensure that + # the stack is in the correct state for the next basic block + # regardless from where we came from. The next _stack_reorder + # will undo the changes to the stack for the operand only. + # The optimization is to have the _stack_reorder emidiatly below + # stop at the operand lenght. Or make a combined version. + # I am adding a TODO for this. self._stack_reorder(assembly, stack, target_stack) is_commutative = opcode in _COMMUTATIVE_INSTRUCTIONS From 828d6b8418bc3d6e5de5f887224857215d074b2b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 5 Nov 2023 13:17:53 +0200 Subject: [PATCH 371/471] remove slipped code for other branch --- vyper/venom/venom_to_assembly.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 776feedc85..0d7410c3fa 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -193,14 +193,6 @@ def _stack_reorder( self.swap(assembly, stack, depth) self.swap(assembly, stack, final_stack_depth) - # REVIEW: note this is dead code - # HK: is't for some WIP - def _get_commutative_alternative(self, depth: int) -> int: - assert depth in (-1, 0), f"Invalid depth {depth}" - if depth == 0: - return -1 - return 0 - def _emit_input_operands( self, assembly: list, From 0d316e66d6e8a47c490b3af1fb33adf5a82b1f23 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 5 Nov 2023 13:43:45 +0200 Subject: [PATCH 372/471] merge the dup requirements into liveness calculations --- vyper/venom/basicblock.py | 5 +++++ vyper/venom/venom_to_assembly.py | 34 -------------------------------- 2 files changed, 5 insertions(+), 34 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 58a293be0f..24978010f7 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -344,6 +344,11 @@ def calculate_liveness(self) -> None: liveness = self.out_vars.copy() for instruction in reversed(self.instructions): ops = instruction.get_inputs() + + for op in ops: + if op in liveness: + instruction.dup_requirements.add(op) + liveness = liveness.union(OrderedSet.fromkeys(ops)) out = instruction.get_outputs()[0] if len(instruction.get_outputs()) > 0 else None if out in liveness: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 0d7410c3fa..0562623634 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -70,35 +70,6 @@ ) -# figure out which variables we need to emit DUPs for for this -# instruction (because they are still live after the instruction -def _compute_dup_requirements(ctx: IRFunction) -> None: - for bb in ctx.basic_blocks: - _compute_dup_requirements_bb(bb) - - -def _compute_dup_requirements_bb(bb: IRBasicBlock) -> None: - # the most recent instruction which used this variable - most_recent_use_of = dict() - - for inst in bb.instructions: - # reset dup_requirements - inst.dup_requirements = OrderedSet() - - for op in inst.get_inputs(): - # the variable is still live at `inst`, so we look - # back to `most_recent_use_of[op]` and add to its - # dup requirements. - if op in most_recent_use_of: - target = most_recent_use_of[op] - target.dup_requirements.add(op) - - most_recent_use_of[op] = inst - - if op in bb.out_vars: - inst.dup_requirements.add(op) - - # REVIEW: "assembly" gets into the recursion due to how the original # IR was structured recursively in regards with the deploy instruction. # There, recursing into the deploy instruction was by design, and @@ -128,12 +99,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: asm = [] calculate_cfg(self.ctx) - - # REVIEW: calculate_liveness and compute_dup_requirements are really - # related, maybe they can be combined somehow. or maybe they should go - # into vyper/venom/analysis.py calculate_liveness(self.ctx) - _compute_dup_requirements(self.ctx) self._generate_evm_for_basicblock_r(asm, self.ctx.basic_blocks[0], stack) From 63f57e43f8d34d3580658c19b9dfc7e453a14048 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 5 Nov 2023 13:48:28 +0200 Subject: [PATCH 373/471] import reorder, formating --- vyper/venom/__init__.py | 2 +- vyper/venom/basicblock.py | 7 +------ vyper/venom/ir_node_to_venom.py | 10 +++------- vyper/venom/venom_to_assembly.py | 6 +----- 4 files changed, 6 insertions(+), 19 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 670b8ecf2a..787c9f326c 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -14,10 +14,10 @@ ir_pass_remove_unreachable_blocks, ) from vyper.venom.function import IRFunction -from vyper.venom.venom_to_assembly import VenomCompiler from vyper.venom.ir_node_to_venom import convert_ir_basicblock from vyper.venom.passes.constant_propagation import ir_pass_constant_propagation from vyper.venom.passes.dft import DFG, DFTPass +from vyper.venom.venom_to_assembly import VenomCompiler def generate_assembly_experimental( diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 24978010f7..5ae055cbae 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -142,12 +142,7 @@ class IRInstruction: fence_id: int annotation: Optional[str] - def __init__( - self, - opcode: str, - operands: list[IRValueBase], - ret: IRValueBase = None, - ): + def __init__(self, opcode: str, operands: list[IRValueBase], ret: IRValueBase = None): self.opcode = opcode self.volatile = opcode in VOLATILE_INSTRUCTIONS self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index b51c692f57..c543555e2f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -373,7 +373,7 @@ def _convert_ir_basicblock( ) else: return ctx.append_instruction( - reversed(ir.value, [gas, address, argsOffsetVar, argsSize, retOffset, retSize]), + reversed(ir.value, [gas, address, argsOffsetVar, argsSize, retOffset, retSize]) ) elif ir.value == "if": cond = ir.args[0] @@ -421,9 +421,7 @@ def _convert_ir_basicblock( 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, + "phi", [then_block.label, then_ret_val, else_block.label, else_ret_val], if_ret ) ) @@ -807,9 +805,7 @@ def emit_body_block(): symbols[sym.value] = ret cond_block.append_instruction( IRInstruction( - "phi", - [entry_block.label, counter_var, increment_block.label, counter_inc_var], - ret, + "phi", [entry_block.label, counter_var, increment_block.label, counter_inc_var], ret ) ) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 0562623634..db2ef85b05 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -160,11 +160,7 @@ def _stack_reorder( self.swap(assembly, stack, final_stack_depth) def _emit_input_operands( - self, - assembly: list, - inst: IRInstruction, - ops: list[IRValueBase], - stack: StackModel, + self, assembly: list, inst: IRInstruction, ops: list[IRValueBase], stack: StackModel ): # PRE: we already have all the items on the stack that have # been scheduled to be killed. now it's just a matter of emitting From 38188334f2ead40fb7bcc9b3947c39d435b6ec81 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 5 Nov 2023 13:59:07 +0200 Subject: [PATCH 374/471] lint fixes --- vyper/codegen/ir_node.py | 2 +- vyper/venom/basicblock.py | 1 + vyper/venom/function.py | 8 ++++++-- vyper/venom/ir_node_to_venom.py | 2 +- vyper/venom/passes/dft.py | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index 69b10bc21b..b6cca451a9 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -607,7 +607,7 @@ def from_list( mutable: bool = True, add_gas_estimate: int = 0, is_self_call: bool = False, - passthrough_metadata: dict[str, Any] = {}, + passthrough_metadata: dict[str, Any] = None, encoding: Encoding = Encoding.VYPER, ) -> "IRnode": if isinstance(typ, str): diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 5ae055cbae..8841262ca3 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -1,6 +1,7 @@ from enum import Enum from typing import TYPE_CHECKING, Optional +from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet # instructions which can terminate a basic block diff --git a/vyper/venom/function.py b/vyper/venom/function.py index c4e624fa6c..e3cce6a1f0 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,4 +1,4 @@ -from typing import Any, Optional +from typing import Optional from vyper.venom.basicblock import ( IRBasicBlock, @@ -9,6 +9,8 @@ MemType, ) +GLOBAL_LABEL = IRLabel("global") + class IRFunction: """ @@ -22,7 +24,9 @@ class IRFunction: last_label: int last_variable: int - def __init__(self, name: IRLabel = IRLabel("global")) -> None: + def __init__(self, name: IRLabel = None) -> None: + if name is None: + name = GLOBAL_LABEL self.name = name self.args = [] self.basic_blocks = [] diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c543555e2f..a07a4e174a 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -86,7 +86,7 @@ def _get_symbols_common(a: dict, b: dict) -> dict: def convert_ir_basicblock(ir: IRnode) -> IRFunction: - global_function = IRFunction(IRLabel("global")) + global_function = IRFunction() _convert_ir_basicblock(global_function, ir, {}, OrderedSet(), {}) for i, bb in enumerate(global_function.basic_blocks): diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index bf954eb1ba..923564ffea 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -1,5 +1,5 @@ from vyper.utils import OrderedSet -from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable, MemType +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass From f4c4e065ba80b9790bc3e12a46e8dd9e20665a74 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sun, 5 Nov 2023 09:30:44 -0500 Subject: [PATCH 375/471] review: hide stack implementation --- vyper/venom/stack_model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index c9a1d0538b..1db05c2f02 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -3,6 +3,8 @@ class StackModel: NOT_IN_STACK = object() + # REVIEW: rename to `_stack_items` and add a `repr()` to StackModel + # so that the stack is more hidden from observers stack: list[IRValueBase] def __init__(self): From 1ef2ee2ad90b295b9a73b61ba0de42bf9c4496b7 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sun, 5 Nov 2023 09:37:14 -0500 Subject: [PATCH 376/471] add a couple more review comments --- vyper/venom/stack_model.py | 1 + vyper/venom/venom_to_assembly.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 1db05c2f02..36951cc08f 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -15,6 +15,7 @@ def copy(self): new.stack = self.stack.copy() return new + # REVIEW: worth changing to a property? `@property\ndef height(self)` def get_height(self) -> int: """ Returns the height of the stack map. diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index db2ef85b05..6b7920244f 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -228,8 +228,11 @@ def _generate_evm_for_basicblock_r( for var in in_vars: depth = stack.get_depth(IRValueBase(var.value)) + # REVIEW: maybe should be an assertion if depth is StackModel.NOT_IN_STACK: continue + + # REVIEW: self.pop_op() or self.pop() if depth != 0: stack.swap(asm, depth) stack.pop() @@ -360,6 +363,7 @@ def _generate_evm_for_instruction( ) self.label_counter += 1 if stack.get_height() > 0 and stack.peek(0) in inst.dup_requirements: + # REVIEW: self.pop() stack.pop() assembly.append("POP") elif opcode == "call": From 429d75cdffd2f10b7c218c990c92bab1d5f259b2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 6 Nov 2023 09:49:02 +0200 Subject: [PATCH 377/471] review items * get_height() -> property * use a .pop() method for homogeneity --- vyper/venom/stack_model.py | 4 ++-- vyper/venom/venom_to_assembly.py | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 36951cc08f..c055a41b8f 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -15,8 +15,8 @@ def copy(self): new.stack = self.stack.copy() return new - # REVIEW: worth changing to a property? `@property\ndef height(self)` - def get_height(self) -> int: + @property + def height(self) -> int: """ Returns the height of the stack map. """ diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 6b7920244f..738bdaae96 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -362,10 +362,8 @@ def _generate_evm_for_instruction( ] ) self.label_counter += 1 - if stack.get_height() > 0 and stack.peek(0) in inst.dup_requirements: - # REVIEW: self.pop() - stack.pop() - assembly.append("POP") + if stack.height > 0 and stack.peek(0) in inst.dup_requirements: + self.pop(assembly, stack) elif opcode == "call": assembly.append("CALL") elif opcode == "staticcall": @@ -425,6 +423,10 @@ def _generate_evm_for_instruction( return assembly + def pop(self, assembly, stack, num=1): + stack.pop(num) + assembly.extend(["POP"] * num) + def swap(self, assembly, stack, depth): if depth == 0: return From d38f6d9ebd87135c46c9e184a1dd4fb1c017a07e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 6 Nov 2023 09:51:33 +0200 Subject: [PATCH 378/471] Make stack private (_stack) --- vyper/venom/stack_model.py | 30 +++++++++++++++--------------- vyper/venom/venom_to_assembly.py | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index c055a41b8f..aa185a3f5d 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -5,14 +5,14 @@ class StackModel: NOT_IN_STACK = object() # REVIEW: rename to `_stack_items` and add a `repr()` to StackModel # so that the stack is more hidden from observers - stack: list[IRValueBase] + _stack: list[IRValueBase] def __init__(self): - self.stack = [] + self._stack = [] def copy(self): new = StackModel() - new.stack = self.stack.copy() + new._stack = self._stack.copy() return new @property @@ -20,17 +20,17 @@ def height(self) -> int: """ Returns the height of the stack map. """ - return len(self.stack) + return len(self._stack) def push(self, op: IRValueBase) -> None: """ Pushes an operand onto the stack map. """ assert isinstance(op, IRValueBase), f"push takes IRValueBase, got '{op}'" - self.stack.append(op) + self._stack.append(op) def pop(self, num: int = 1) -> None: - del self.stack[len(self.stack) - num :] + del self._stack[len(self._stack) - num :] def get_depth(self, op: IRValueBase) -> int: """ @@ -39,7 +39,7 @@ def get_depth(self, op: IRValueBase) -> int: """ assert isinstance(op, IRValueBase), f"get_depth takes IRValueBase or list, got '{op}'" - for i, stack_op in enumerate(reversed(self.stack)): + for i, stack_op in enumerate(reversed(self._stack)): if stack_op.value == op.value: return -i @@ -55,11 +55,11 @@ def get_phi_depth(self, phi1: IRVariable, phi2: IRVariable) -> int: assert isinstance(phi2, IRVariable) ret = StackModel.NOT_IN_STACK - for i, stack_item in enumerate(reversed(self.stack)): + for i, stack_item in enumerate(reversed(self._stack)): if stack_item in (phi1, phi2): assert ( ret is StackModel.NOT_IN_STACK - ), f"phi argument is not unique! {phi1}, {phi2}, {self.stack}" + ), f"phi argument is not unique! {phi1}, {phi2}, {self._stack}" ret = -i return ret @@ -69,7 +69,7 @@ def peek(self, depth: int) -> IRValueBase: Returns the top of the stack map. """ assert depth is not StackModel.NOT_IN_STACK, "Cannot peek non-in-stack depth" - return self.stack[depth - 1] + return self._stack[depth - 1] def poke(self, depth: int, op: IRValueBase) -> None: """ @@ -78,7 +78,7 @@ def poke(self, depth: int, op: IRValueBase) -> None: assert depth is not StackModel.NOT_IN_STACK, "Cannot poke non-in-stack depth" assert depth <= 0, "Bad depth" assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" - self.stack[depth - 1] = op + self._stack[depth - 1] = op def dup(self, depth: int) -> None: """ @@ -86,7 +86,7 @@ def dup(self, depth: int) -> None: """ assert depth is not StackModel.NOT_IN_STACK, "Cannot dup non-existent operand" assert depth <= 0, "Cannot dup positive depth" - self.stack.append(self.peek(depth)) + self._stack.append(self.peek(depth)) def swap(self, depth: int) -> None: """ @@ -94,6 +94,6 @@ def swap(self, depth: int) -> None: """ assert depth is not StackModel.NOT_IN_STACK, "Cannot swap non-existent operand" assert depth < 0, "Cannot swap positive depth" - top = self.stack[-1] - self.stack[-1] = self.stack[depth - 1] - self.stack[depth - 1] = top + top = self._stack[-1] + self._stack[-1] = self._stack[depth - 1] + self._stack[depth - 1] = top diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 738bdaae96..2f1134334d 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -168,7 +168,7 @@ def _emit_input_operands( # dumb heuristic: if the top of stack is not wanted here, swap # it with something that is wanted - if ops and stack.stack and stack.stack[-1] not in ops: + if ops and stack._stack and stack._stack[-1] not in ops: for op in ops: if isinstance(op, IRVariable) and op not in inst.dup_requirements: self.swap_op(assembly, stack, op) From cdf282bf0c328f61c75a42776db715b512ca9bbf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 6 Nov 2023 09:52:17 +0200 Subject: [PATCH 379/471] Add __repr__() method to stack --- vyper/venom/stack_model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index aa185a3f5d..2a05f9a8a0 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -97,3 +97,6 @@ def swap(self, depth: int) -> None: top = self._stack[-1] self._stack[-1] = self._stack[depth - 1] self._stack[depth - 1] = top + + def __repr__(self) -> str: + return f"" From 5767927e1279dbf0b99f75f9f0df5cde4ca0ff4c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 6 Nov 2023 10:01:47 +0200 Subject: [PATCH 380/471] use self.pop() --- vyper/venom/venom_to_assembly.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 2f1134334d..7a1b2b3244 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -228,15 +228,12 @@ def _generate_evm_for_basicblock_r( for var in in_vars: depth = stack.get_depth(IRValueBase(var.value)) - # REVIEW: maybe should be an assertion if depth is StackModel.NOT_IN_STACK: continue - # REVIEW: self.pop_op() or self.pop() if depth != 0: stack.swap(asm, depth) - stack.pop() - asm.append("POP") + self.pop(asm, stack) for inst in basicblock.instructions: asm = self._generate_evm_for_instruction(asm, inst, stack) From 66ebc8ab4c115e87da4ac7da3b24665a5f888aa8 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Mon, 6 Nov 2023 10:08:29 -0500 Subject: [PATCH 381/471] remove inspection of private `_stack` member --- vyper/venom/venom_to_assembly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 7a1b2b3244..a2e2afee2c 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -168,7 +168,7 @@ def _emit_input_operands( # dumb heuristic: if the top of stack is not wanted here, swap # it with something that is wanted - if ops and stack._stack and stack._stack[-1] not in ops: + if ops and stack.height > 0 and stack.peek(0) not in ops: for op in ops: if isinstance(op, IRVariable) and op not in inst.dup_requirements: self.swap_op(assembly, stack, op) From bcc14cfb5175741e0bbfb818f184b70f2f8134a5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 8 Nov 2023 15:04:34 +0200 Subject: [PATCH 382/471] rename ret to output --- vyper/venom/basicblock.py | 13 +++++-------- vyper/venom/bb_optimizer.py | 2 +- vyper/venom/venom_to_assembly.py | 12 ++++++------ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 8841262ca3..f79df81687 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -132,10 +132,7 @@ class IRInstruction: opcode: str volatile: bool operands: list[IRValueBase] - # REVIEW: rename to lhs? - # HK: Maybe outputs is better? - # REVIEW: hmm not sure. to discuss offline - ret: Optional[IRValueBase] + output: Optional[IRValueBase] # set of live variables at this instruction liveness: OrderedSet[IRVariable] dup_requirements: OrderedSet[IRVariable] @@ -147,7 +144,7 @@ def __init__(self, opcode: str, operands: list[IRValueBase], ret: IRValueBase = self.opcode = opcode self.volatile = opcode in VOLATILE_INSTRUCTIONS self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] - self.ret = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None + self.output = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None self.liveness = OrderedSet() self.dup_requirements = OrderedSet() self.parent = None @@ -173,7 +170,7 @@ def get_inputs(self) -> list[IRValueBase]: return [op for op in self.operands if isinstance(op, IRVariable)] def get_outputs(self) -> list[IRVariable]: - return [self.ret] if self.ret else [] + return [self.output] if self.output else [] def replace_operands(self, replacements: dict) -> None: """ @@ -186,8 +183,8 @@ def replace_operands(self, replacements: dict) -> None: def __repr__(self) -> str: s = "" - if self.ret: - s += f"{self.ret} = " + if self.output: + s += f"{self.output} = " opcode = f"{self.opcode} " if self.opcode != "store" else "" s += opcode operands = ", ".join( diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index 916b200290..d79a8600ac 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -12,7 +12,7 @@ def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: for i, inst in enumerate(bb.instructions[:-1]): if inst.volatile: continue - if inst.ret and inst.ret not in bb.instructions[i + 1].liveness: + if inst.output and inst.output not in bb.instructions[i + 1].liveness: removeList.add(inst) bb.instructions = [inst for inst in bb.instructions if inst not in removeList] diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index a2e2afee2c..9b6a328a05 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -309,8 +309,8 @@ def _generate_evm_for_instruction( # Step 4: Push instruction's return value to stack stack.pop(len(operands)) - if inst.ret is not None: - stack.push(inst.ret) + if inst.output is not None: + stack.push(inst.output) # Step 5: Emit the EVM instruction(s) if opcode in _ONE_TO_ONE_INSTRUCTIONS: @@ -413,10 +413,10 @@ def _generate_evm_for_instruction( raise Exception(f"Unknown opcode: {opcode}") # Step 6: Emit instructions output operands (if any) - if inst.ret is not None: - assert isinstance(inst.ret, IRVariable), "Return value must be a variable" - if inst.ret.mem_type == MemType.MEMORY: - assembly.extend([*PUSH(inst.ret.mem_addr)]) + if inst.output is not None: + assert isinstance(inst.output, IRVariable), "Return value must be a variable" + if inst.output.mem_type == MemType.MEMORY: + assembly.extend([*PUSH(inst.output.mem_addr)]) return assembly From 139affc4ce2be515b41be2be3b337f62b8342769 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 08:08:36 -0500 Subject: [PATCH 383/471] fix a local variable --- vyper/venom/basicblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index f79df81687..4f5bb3231b 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -140,11 +140,11 @@ class IRInstruction: fence_id: int annotation: Optional[str] - def __init__(self, opcode: str, operands: list[IRValueBase], ret: IRValueBase = None): + def __init__(self, opcode: str, operands: list[IRValueBase], output: IRValueBase = None): self.opcode = opcode self.volatile = opcode in VOLATILE_INSTRUCTIONS self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] - self.output = ret if isinstance(ret, IRValueBase) else IRValueBase(ret) if ret else None + self.output = output if isinstance(output, IRValueBase) else IRValueBase(output) if output else None self.liveness = OrderedSet() self.dup_requirements = OrderedSet() self.parent = None From c2a5114e982bf6d565c1c9ad847d3f378de976b2 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 08:08:45 -0500 Subject: [PATCH 384/471] update a couple docstrings --- vyper/venom/basicblock.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 4f5bb3231b..6b632e1f1a 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -159,7 +159,7 @@ def get_label_operands(self) -> list[IRLabel]: def get_non_label_operands(self) -> list[IRValueBase]: """ - Get all input operands in instruction. + Get input operands for instruction which are not labels """ return [op for op in self.operands if not isinstance(op, IRLabel)] @@ -170,6 +170,11 @@ def get_inputs(self) -> list[IRValueBase]: return [op for op in self.operands if isinstance(op, IRVariable)] def get_outputs(self) -> list[IRVariable]: + """ + Get the output item for an instruction. + (Currently all instructions output at most one item, but write + it as a list to be generic for the future) + """ return [self.output] if self.output else [] def replace_operands(self, replacements: dict) -> None: From 92a35caa44b644af2b4b9c490c5ebb6f0a85fa66 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 08:08:52 -0500 Subject: [PATCH 385/471] remove liveness from IRInstruction.__repr__ --- vyper/venom/basicblock.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 6b632e1f1a..782bf9cace 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -203,9 +203,6 @@ def __repr__(self) -> str: if self.annotation: s += f" <{self.annotation}>" - if self.liveness: - return f"{s: <30} # {self.liveness}" - return s From daf3df780bf4747d4999bbad7aa0ec15bd26571d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 08:09:40 -0500 Subject: [PATCH 386/471] fix lint --- vyper/venom/basicblock.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 782bf9cace..1183c2b9fc 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -144,7 +144,9 @@ def __init__(self, opcode: str, operands: list[IRValueBase], output: IRValueBase self.opcode = opcode self.volatile = opcode in VOLATILE_INSTRUCTIONS self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] - self.output = output if isinstance(output, IRValueBase) else IRValueBase(output) if output else None + self.output = ( + output if isinstance(output, IRValueBase) else IRValueBase(output) if output else None + ) self.liveness = OrderedSet() self.dup_requirements = OrderedSet() self.parent = None From 2f0c27f77bd2ed4ae92c250f0d830f0fad1dcba2 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 08:11:51 -0500 Subject: [PATCH 387/471] change default mem_addr --- vyper/venom/basicblock.py | 7 +++++-- vyper/venom/function.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 1183c2b9fc..242bd36de0 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -94,10 +94,13 @@ class IRVariable(IRValueBase): offset: int = 0 mem_type: MemType = MemType.OPERAND_STACK - mem_addr: int = -1 # REVIEW should this be None? + mem_addr: Optional[int] = None def __init__( - self, value: IRValueBaseValue, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: int = -1 + self, + value: IRValueBaseValue, + mem_type: MemType = MemType.OPERAND_STACK, + mem_addr: int = None, ) -> None: if isinstance(value, IRLiteral): value = value.value diff --git a/vyper/venom/function.py b/vyper/venom/function.py index e3cce6a1f0..9cd94cc455 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -77,7 +77,7 @@ def get_next_label(self) -> IRLabel: return IRLabel(f"{self.last_label}") def get_next_variable( - self, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: int = -1 + self, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: Optional[int] = None ) -> IRVariable: self.last_variable += 1 return IRVariable(f"%{self.last_variable}", mem_type, mem_addr) From 8cc542f05fe5dc7c8b4907eafa4aa070188fd361 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 08:12:42 -0500 Subject: [PATCH 388/471] fix lint --- tests/compiler/venom/test_stack_at_external_return.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/compiler/venom/test_stack_at_external_return.py b/tests/compiler/venom/test_stack_at_external_return.py index 319dc30c5c..be9fa66e9a 100644 --- a/tests/compiler/venom/test_stack_at_external_return.py +++ b/tests/compiler/venom/test_stack_at_external_return.py @@ -1,9 +1,3 @@ -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 - - def test_stack_at_external_return(): """ TODO: USE BOA DO GENERATE THIS TEST From 791618532d3cb1772d1d0f9e9f8702522c529097 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 08:16:59 -0500 Subject: [PATCH 389/471] some small cleanup --- vyper/venom/__init__.py | 3 +-- vyper/venom/basicblock.py | 8 ++++++-- vyper/venom/stack_model.py | 2 -- vyper/venom/venom_to_assembly.py | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 787c9f326c..36e4348a08 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -1,5 +1,4 @@ -# REVIEW stylistically i don't really like code (besides just imports) -# going into `__init__.py`. maybe `main.py` or `venom.py` +# maybe rename this `main.py` or `venom.py` # (can have an `__init__.py` which exposes the API). from typing import Optional diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 242bd36de0..a6ba656e0a 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -1,4 +1,4 @@ -from enum import Enum +from enum import Enum, auto from typing import TYPE_CHECKING, Optional from vyper.exceptions import CompilerPanic @@ -84,7 +84,9 @@ def is_literal(self) -> bool: return True -MemType = Enum("MemType", ["OPERAND_STACK", "MEMORY"]) +class MemType(Enum): + OPERAND_STACK = auto() + MEMORY = auto() class IRVariable(IRValueBase): @@ -93,6 +95,8 @@ class IRVariable(IRValueBase): """ offset: int = 0 + + # some variables can be in memory for conversion from legacy IR to venom mem_type: MemType = MemType.OPERAND_STACK mem_addr: Optional[int] = None diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 2a05f9a8a0..e85eecf2b3 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -3,8 +3,6 @@ class StackModel: NOT_IN_STACK = object() - # REVIEW: rename to `_stack_items` and add a `repr()` to StackModel - # so that the stack is more hidden from observers _stack: list[IRValueBase] def __init__(self): diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 9b6a328a05..05cdd72579 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -232,7 +232,7 @@ def _generate_evm_for_basicblock_r( continue if depth != 0: - stack.swap(asm, depth) + stack.swap(depth) self.pop(asm, stack) for inst in basicblock.instructions: From 4e4146032c4086529ea64bf3f630b0dc6033a8ae Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 08:24:04 -0500 Subject: [PATCH 390/471] Revert "remove liveness from IRInstruction.__repr__" This reverts commit 92a35caa44b644af2b4b9c490c5ebb6f0a85fa66. --- vyper/venom/basicblock.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index a6ba656e0a..17d0284ece 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -212,6 +212,9 @@ def __repr__(self) -> str: if self.annotation: s += f" <{self.annotation}>" + if self.liveness: + return f"{s: <30} # {self.liveness}" + return s From 5accc3b8e650d4c5f1e825a20fa9a4321d427d80 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 08:35:57 -0500 Subject: [PATCH 391/471] add another jump for hygiene --- vyper/venom/venom_to_assembly.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 05cdd72579..ed345cc574 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -238,7 +238,6 @@ def _generate_evm_for_basicblock_r( for inst in basicblock.instructions: asm = self._generate_evm_for_instruction(asm, inst, stack) - # REVIEW: codegen for `jnz` depends on this traversal order. for bb in basicblock.cfg_out: self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) @@ -326,16 +325,18 @@ def _generate_evm_for_instruction( elif opcode in ["codecopy", "dloadbytes"]: assembly.append("CODECOPY") elif opcode == "jnz": - assembly.append(f"_sym_{inst.operands[1].value}") + # jump if not zero + if_nonzero_label = inst.operands[1] + if_zero_label = inst.operands[2] + assembly.append(f"_sym_{if_nonzero_label.value}") assembly.append("JUMPI") - # REVIEW: probably need to add - # assembly.append(f"_sym_{inst.operands[0].value}") - # assembly.append("JUMP") - # because we only happen to be guaranteed that the next - # basic block is coming in the CFG is inst.operands[0].value. - # but we should add the jump, in case the CFG traversal - # changes. or, add an assertion that - # `inst.operands[0].value == inst.parent.cfg_out[0]`. + + # make sure the if_zero_label will be optimized out + assert if_zero_label == next(iter(inst.parent.cfg_out)).label + + assembly.append(f"_sym_{if_zero_label.value}") + assembly.append("JUMP") + elif opcode == "jmp": if isinstance(inst.operands[0], IRLabel): assembly.append(f"_sym_{inst.operands[0].value}") From 70b654f4fb57ce4f05cb8a22ae0b1e3327576210 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 09:52:59 -0500 Subject: [PATCH 392/471] factor out bb entry stack cleaning --- vyper/venom/venom_to_assembly.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index ed345cc574..6b36e8248e 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -218,16 +218,27 @@ def _generate_evm_for_basicblock_r( return self.visited_basicblocks.add(basicblock) + # assembly entry point into the block asm.append(f"_sym_{basicblock.label}") asm.append("JUMPDEST") - # values to pop from stack + self.clean_stack_from_cfg_in() + + for inst in basicblock.instructions: + asm = self._generate_evm_for_instruction(asm, inst, stack) + + for bb in basicblock.cfg_out: + self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) + + # pop values from stack at entry to bb + def clean_stack_from_cfg_in(self): in_vars = OrderedSet() for in_bb in basicblock.cfg_in: in_vars |= in_bb.out_vars.difference(basicblock.in_vars_from(in_bb)) for var in in_vars: depth = stack.get_depth(IRValueBase(var.value)) + # don't pop phantom phi inputs if depth is StackModel.NOT_IN_STACK: continue @@ -235,12 +246,6 @@ def _generate_evm_for_basicblock_r( stack.swap(depth) self.pop(asm, stack) - for inst in basicblock.instructions: - asm = self._generate_evm_for_instruction(asm, inst, stack) - - for bb in basicblock.cfg_out: - self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) - def _generate_evm_for_instruction( self, assembly: list, inst: IRInstruction, stack: StackModel ) -> list[str]: From d0b16455464de8e2d89d9562389f2904573abefc Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 10:07:41 -0500 Subject: [PATCH 393/471] fix params for clean_stack_for_cfg_in --- vyper/venom/venom_to_assembly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 6b36e8248e..7c4bd58cc3 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -222,7 +222,7 @@ def _generate_evm_for_basicblock_r( asm.append(f"_sym_{basicblock.label}") asm.append("JUMPDEST") - self.clean_stack_from_cfg_in() + self.clean_stack_from_cfg_in(asm, basicblock, stack) for inst in basicblock.instructions: asm = self._generate_evm_for_instruction(asm, inst, stack) @@ -231,7 +231,7 @@ def _generate_evm_for_basicblock_r( self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) # pop values from stack at entry to bb - def clean_stack_from_cfg_in(self): + def clean_stack_from_cfg_in(self, asm: list, basicblock: IRBasicBlock, stack: StackModel): in_vars = OrderedSet() for in_bb in basicblock.cfg_in: in_vars |= in_bb.out_vars.difference(basicblock.in_vars_from(in_bb)) From 535ee2d6b9597cf7671803d856f06f605bb2b4d7 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 11:20:12 -0500 Subject: [PATCH 394/471] move some functions into analysis.py --- vyper/venom/__init__.py | 5 +- vyper/venom/analysis.py | 120 +++++++++++++++++++++++++++++++ vyper/venom/bb_optimizer.py | 78 +------------------- vyper/venom/passes/dft.py | 47 +----------- vyper/venom/venom_to_assembly.py | 2 +- 5 files changed, 128 insertions(+), 124 deletions(-) create mode 100644 vyper/venom/analysis.py diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 36e4348a08..5a09f8378e 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -5,9 +5,8 @@ from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel +from vyper.venom.analysis import DFG, calculate_cfg, calculate_liveness from vyper.venom.bb_optimizer import ( - calculate_cfg, - calculate_liveness, ir_pass_optimize_empty_blocks, ir_pass_optimize_unused_variables, ir_pass_remove_unreachable_blocks, @@ -15,7 +14,7 @@ from vyper.venom.function import IRFunction from vyper.venom.ir_node_to_venom import convert_ir_basicblock from vyper.venom.passes.constant_propagation import ir_pass_constant_propagation -from vyper.venom.passes.dft import DFG, DFTPass +from vyper.venom.passes.dft import DFTPass from vyper.venom.venom_to_assembly import VenomCompiler diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py new file mode 100644 index 0000000000..a8dd8e9615 --- /dev/null +++ b/vyper/venom/analysis.py @@ -0,0 +1,120 @@ +from vyper.utils import OrderedSet +from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRVariable +from vyper.venom.function import IRFunction + + +def calculate_cfg(ctx: IRFunction) -> None: + """ + Calculate (cfg) inputs for each basic block. + """ + for bb in ctx.basic_blocks: + bb.cfg_in = OrderedSet() + bb.cfg_out = OrderedSet() + bb.out_vars = OrderedSet() + + 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: + entry_block = after_deploy_bb + has_constructor = True if ctx.basic_blocks[0].instructions[0].opcode != "deploy" else False + 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: + 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] + assert last_inst.opcode in BB_TERMINATORS, "Last instruction should be a terminator" + str( + bb + ) + + for inst in bb.instructions: + if inst.opcode in ["jmp", "jnz", "call", "staticcall", "invoke", "deploy"]: + ops = inst.get_label_operands() + for op in ops: + ctx.get_basic_block(op.value).add_cfg_in(bb) + + # Fill in the "out" set for each basic block + for bb in ctx.basic_blocks: + for in_bb in bb.cfg_in: + in_bb.add_cfg_out(bb) + + +def _reset_liveness(ctx: IRFunction) -> None: + for bb in ctx.basic_blocks: + for inst in bb.instructions: + inst.liveness = OrderedSet() + + +def _calculate_liveness(bb: IRBasicBlock, liveness_visited: OrderedSet) -> None: + assert isinstance(liveness_visited, OrderedSet) + for out_bb in bb.cfg_out: + if liveness_visited.get(bb) == out_bb: + continue + liveness_visited[bb] = out_bb + _calculate_liveness(out_bb, liveness_visited) + target_vars = out_bb.in_vars_from(bb) + bb.out_vars = bb.out_vars.union(target_vars) + + bb.calculate_liveness() + + +def calculate_liveness(ctx: IRFunction) -> None: + _reset_liveness(ctx) + _calculate_liveness(ctx.basic_blocks[0], OrderedSet()) + + +# DataFlow Graph +# this could be refactored into its own file, but it's only used here +# for now +class DFG: + _dfg_inputs: dict[IRVariable, list[IRInstruction]] + _dfg_outputs: dict[IRVariable, IRInstruction] + + def __init__(self): + self._dfg_inputs = dict() + self._dfg_outputs = dict() + + # return uses of a given variable + def get_uses(self, op: IRVariable) -> list[IRInstruction]: + return self._dfg_inputs.get(op, []) + + # the instruction which produces this variable. + def get_producing_instruction(self, op: IRVariable) -> IRInstruction: + return self._dfg_outputs[op] + + @classmethod + def build_dfg(cls, ctx: IRFunction): + dfg = cls() + + # Build DFG + + # %15 = add %13 %14 + # %16 = iszero %15 + # dfg_outputs of %15 is (%15 = add %13 %14) + # dfg_inputs of %15 is all the instructions which *use* %15, ex. [(%16 = iszero %15), ...] + for bb in ctx.basic_blocks: + for inst in bb.instructions: + operands = inst.get_inputs() + res = inst.get_outputs() + + for op in operands: + inputs = dfg._dfg_inputs.setdefault(op, []) + inputs.append(inst) + + for op in res: + dfg._dfg_outputs[op] = inst + + return dfg diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index d79a8600ac..6d620b902f 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -1,5 +1,6 @@ -from vyper.utils import OrderedSet, ir_pass -from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRLabel +from vyper.utils import ir_pass +from vyper.venom.analysis import calculate_cfg +from vyper.venom.basicblock import IRInstruction, IRLabel from vyper.venom.function import IRFunction @@ -55,79 +56,6 @@ def _optimize_empty_basicblocks(ctx: IRFunction) -> int: return count -def calculate_cfg(ctx: IRFunction) -> None: - """ - Calculate (cfg) inputs for each basic block. - """ - for bb in ctx.basic_blocks: - bb.cfg_in = OrderedSet() - bb.cfg_out = OrderedSet() - bb.out_vars = OrderedSet() - - 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: - entry_block = after_deploy_bb - has_constructor = True if ctx.basic_blocks[0].instructions[0].opcode != "deploy" else False - 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: - 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] - assert last_inst.opcode in BB_TERMINATORS, "Last instruction should be a terminator" + str( - bb - ) - - for inst in bb.instructions: - if inst.opcode in ["jmp", "jnz", "call", "staticcall", "invoke", "deploy"]: - ops = inst.get_label_operands() - for op in ops: - ctx.get_basic_block(op.value).add_cfg_in(bb) - - # Fill in the "out" set for each basic block - for bb in ctx.basic_blocks: - for in_bb in bb.cfg_in: - in_bb.add_cfg_out(bb) - - -def _reset_liveness(ctx: IRFunction) -> None: - for bb in ctx.basic_blocks: - for inst in bb.instructions: - inst.liveness = OrderedSet() - - -def _calculate_liveness(bb: IRBasicBlock, liveness_visited: OrderedSet) -> None: - assert isinstance(liveness_visited, OrderedSet) - for out_bb in bb.cfg_out: - if liveness_visited.get(bb) == out_bb: - continue - liveness_visited[bb] = out_bb - _calculate_liveness(out_bb, liveness_visited) - in_vars = out_bb.in_vars_from(bb) - bb.out_vars = bb.out_vars.union(in_vars) - - bb.calculate_liveness() - - -def calculate_liveness(ctx: IRFunction) -> None: - _reset_liveness(ctx) - _calculate_liveness(ctx.basic_blocks[0], OrderedSet()) - - @ir_pass def ir_pass_optimize_empty_blocks(ctx: IRFunction) -> int: changes = _optimize_empty_basicblocks(ctx) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 923564ffea..26994bd27f 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -1,53 +1,10 @@ from vyper.utils import OrderedSet -from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable +from vyper.venom.analysis import DFG +from vyper.venom.basicblock import IRBasicBlock, IRInstruction from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass -# DataFlow Graph -# this could be refactored into its own file, but it's only used here -# for now -class DFG: - _dfg_inputs: dict[IRVariable, list[IRInstruction]] - _dfg_outputs: dict[IRVariable, IRInstruction] - - def __init__(self): - self._dfg_inputs = dict() - self._dfg_outputs = dict() - - # return uses of a given variable - def get_uses(self, op: IRVariable) -> list[IRInstruction]: - return self._dfg_inputs.get(op, []) - - # the instruction which produces this variable. - def get_producing_instruction(self, op: IRVariable) -> IRInstruction: - return self._dfg_outputs[op] - - @classmethod - def build_dfg(cls, ctx: IRFunction): - dfg = cls() - - # Build DFG - - # %15 = add %13 %14 - # %16 = iszero %15 - # dfg_outputs of %15 is (%15 = add %13 %14) - # dfg_inputs of %15 is all the instructions which *use* %15, ex. [(%16 = iszero %15), ...] - for bb in ctx.basic_blocks: - for inst in bb.instructions: - operands = inst.get_inputs() - res = inst.get_outputs() - - for op in operands: - inputs = dfg._dfg_inputs.setdefault(op, []) - inputs.append(inst) - - for op in res: - dfg._dfg_outputs[op] = inst - - return dfg - - # DataFlow Transformation class DFTPass(IRPass): def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 7c4bd58cc3..798658c569 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,5 +1,6 @@ from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet +from vyper.venom.analysis import calculate_cfg, calculate_liveness from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, @@ -8,7 +9,6 @@ IRVariable, MemType, ) -from vyper.venom.bb_optimizer import calculate_cfg, calculate_liveness from vyper.venom.function import IRFunction from vyper.venom.stack_model import StackModel From ac386ad09ab58052d4316ccb5d0fa0c9457f2586 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 11:24:46 -0500 Subject: [PATCH 395/471] perf nit --- vyper/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/utils.py b/vyper/utils.py index c06a8d345b..be43544c4f 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -39,7 +39,7 @@ def remove(self, item): def difference(self, other): ret = self.copy() for k in other.keys(): - if k in ret.keys(): + if k in ret: ret.remove(k) return ret From 4ac187a253fefed79be298a006e1437a42f1615c Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 11:24:57 -0500 Subject: [PATCH 396/471] rename a variable --- vyper/venom/basicblock.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 17d0284ece..750c528cff 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -280,10 +280,13 @@ def remove_cfg_out(self, bb: "IRBasicBlock") -> None: # calculate the input variables into self from source def in_vars_from(self, source: "IRBasicBlock") -> OrderedSet[IRVariable]: - liveness = self.instructions[0].liveness.copy() + target = self + + liveness = target.instructions[0].liveness.copy() assert isinstance(liveness, OrderedSet) - for inst in self.instructions: + + for inst in target.instructions: if inst.opcode == "phi": # we arbitrarily choose one of the arguments to be in the # live variables set (dependent on how we traversed into this From 260cecf6017586262a47c92c4d924ed056d83b9c Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 11:36:11 -0500 Subject: [PATCH 397/471] move input_vars_from and liveness analysis into analysis.py --- vyper/venom/analysis.py | 60 +++++++++++++++++++++++++++++--- vyper/venom/basicblock.py | 52 --------------------------- vyper/venom/venom_to_assembly.py | 15 +++++--- 3 files changed, 65 insertions(+), 62 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index a8dd8e9615..7c81278dad 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -57,23 +57,73 @@ def _reset_liveness(ctx: IRFunction) -> None: for inst in bb.instructions: inst.liveness = OrderedSet() +def _calculate_liveness_bb(bb: IRBasicBlock): + """ + Compute liveness of each instruction in the basic block. + """ + liveness = bb.out_vars.copy() + for instruction in reversed(bb.instructions): + ops = instruction.get_inputs() + + for op in ops: + if op in liveness: + instruction.dup_requirements.add(op) + + liveness = liveness.union(OrderedSet.fromkeys(ops)) + out = instruction.get_outputs()[0] if len(instruction.get_outputs()) > 0 else None + if out in liveness: + liveness.remove(out) + instruction.liveness = liveness + -def _calculate_liveness(bb: IRBasicBlock, liveness_visited: OrderedSet) -> None: +def _calculate_liveness_helper(bb: IRBasicBlock, liveness_visited: OrderedSet) -> None: assert isinstance(liveness_visited, OrderedSet) for out_bb in bb.cfg_out: if liveness_visited.get(bb) == out_bb: continue liveness_visited[bb] = out_bb - _calculate_liveness(out_bb, liveness_visited) - target_vars = out_bb.in_vars_from(bb) + _calculate_liveness_helper(out_bb, liveness_visited) + target_vars = input_vars_from(bb, out_bb) bb.out_vars = bb.out_vars.union(target_vars) - bb.calculate_liveness() + _calculate_liveness_bb(bb) def calculate_liveness(ctx: IRFunction) -> None: _reset_liveness(ctx) - _calculate_liveness(ctx.basic_blocks[0], OrderedSet()) + _calculate_liveness_helper(ctx.basic_blocks[0], OrderedSet()) + + +# calculate the input variables into self from source +def input_vars_from(source: IRBasicBlock, target: IRBasicBlock) -> OrderedSet[IRVariable]: + liveness = target.instructions[0].liveness.copy() + assert isinstance(liveness, OrderedSet) + + for inst in target.instructions: + if inst.opcode == "phi": + # we arbitrarily choose one of the arguments to be in the + # live variables set (dependent on how we traversed into this + # basic block). the argument will be replaced by the destination + # operand during instruction selection. + # for instance, `%56 = phi %label1 %12 %label2 %14` + # will arbitrarily choose either %12 or %14 to be in the liveness + # set, and then during instruction selection, after this instruction, + # %12 will be replaced by %56 in the liveness set + source1, source2 = inst.operands[0], inst.operands[2] + phi1, phi2 = inst.operands[1], inst.operands[3] + if source.label == source1: + liveness.add(phi1) + if phi2 in liveness: + liveness.remove(phi2) + elif source.label == source2: + liveness.add(phi2) + if phi1 in liveness: + liveness.remove(phi1) + else: + # bad path into this phi node + raise CompilerPanic(f"unreachable: {inst}") + + return liveness # DataFlow Graph diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 750c528cff..ff9ba78f69 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -278,40 +278,6 @@ def union_cfg_out(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: def remove_cfg_out(self, bb: "IRBasicBlock") -> None: self.cfg_out.remove(bb) - # calculate the input variables into self from source - def in_vars_from(self, source: "IRBasicBlock") -> OrderedSet[IRVariable]: - target = self - - liveness = target.instructions[0].liveness.copy() - assert isinstance(liveness, OrderedSet) - - - for inst in target.instructions: - if inst.opcode == "phi": - # we arbitrarily choose one of the arguments to be in the - # live variables set (dependent on how we traversed into this - # basic block). the argument will be replaced by the destination - # operand during instruction selection. - # for instance, `%56 = phi %label1 %12 %label2 %14` - # will arbitrarily choose either %12 or %14 to be in the liveness - # set, and then during instruction selection, after this instruction, - # %12 will be replaced by %56 in the liveness set - source1, source2 = inst.operands[0], inst.operands[2] - phi1, phi2 = inst.operands[1], inst.operands[3] - if source.label == source1: - liveness.add(phi1) - if phi2 in liveness: - liveness.remove(phi2) - elif source.label == source2: - liveness.add(phi2) - if phi1 in liveness: - liveness.remove(phi1) - else: - # bad path into this phi node - raise CompilerPanic(f"unreachable: {inst}") - - return liveness - @property def is_reachable(self) -> bool: return len(self.cfg_in) > 0 @@ -347,24 +313,6 @@ def is_terminated(self) -> bool: return False return self.instructions[-1].opcode in BB_TERMINATORS - def calculate_liveness(self) -> None: - """ - Compute liveness of each instruction in the basic block. - """ - liveness = self.out_vars.copy() - for instruction in reversed(self.instructions): - ops = instruction.get_inputs() - - for op in ops: - if op in liveness: - instruction.dup_requirements.add(op) - - liveness = liveness.union(OrderedSet.fromkeys(ops)) - out = instruction.get_outputs()[0] if len(instruction.get_outputs()) > 0 else None - if out in liveness: - liveness.remove(out) - instruction.liveness = liveness - def copy(self): bb = IRBasicBlock(self.label, self.parent) bb.instructions = self.instructions.copy() diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 798658c569..4a3c707c78 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,6 +1,6 @@ from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet -from vyper.venom.analysis import calculate_cfg, calculate_liveness +from vyper.venom.analysis import calculate_cfg, calculate_liveness, input_vars_from from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, @@ -232,11 +232,16 @@ def _generate_evm_for_basicblock_r( # pop values from stack at entry to bb def clean_stack_from_cfg_in(self, asm: list, basicblock: IRBasicBlock, stack: StackModel): - in_vars = OrderedSet() + if not basicblock.cfg_in: + return + + to_pop = OrderedSet() for in_bb in basicblock.cfg_in: - in_vars |= in_bb.out_vars.difference(basicblock.in_vars_from(in_bb)) + inputs = input_vars_from(in_bb, basicblock) + layout = in_bb.out_vars + to_pop |= in_bb.out_vars.difference(inputs) - for var in in_vars: + for var in to_pop: depth = stack.get_depth(IRValueBase(var.value)) # don't pop phantom phi inputs if depth is StackModel.NOT_IN_STACK: @@ -291,7 +296,7 @@ def _generate_evm_for_instruction( if opcode in ["jnz", "jmp"]: assert isinstance(inst.parent.cfg_out, OrderedSet) b = next(iter(inst.parent.cfg_out)) - target_stack = b.in_vars_from(inst.parent) + target_stack = input_vars_from(inst.parent, b) # REVIEW: this seems like it generates bad code, because # the next _stack_reorder will undo the changes to the stack. # i think we can just remove it entirely. From 548b216cea086c906d11c2dce16d11ffa8dc48ec Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 12:03:45 -0500 Subject: [PATCH 398/471] add some comments, small style adjustments and remove is_commutative from _stack_reorder --- vyper/utils.py | 3 +++ vyper/venom/analysis.py | 18 ++++++++----- vyper/venom/venom_to_assembly.py | 43 ++++++++++++++------------------ 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/vyper/utils.py b/vyper/utils.py index be43544c4f..7a49a4a8de 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -30,6 +30,9 @@ def __repr__(self): keys = ", ".join(repr(k) for k in self.keys()) return f"{{{keys}}}" + def get(self, *args, **kwargs): + raise RuntimeError("can't call get() on OrderedSet!") + def add(self, item): self[item] = None diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 7c81278dad..3873692e1c 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -76,14 +76,20 @@ def _calculate_liveness_bb(bb: IRBasicBlock): instruction.liveness = liveness -def _calculate_liveness_helper(bb: IRBasicBlock, liveness_visited: OrderedSet) -> None: - assert isinstance(liveness_visited, OrderedSet) +def _calculate_liveness_r(bb: IRBasicBlock, visited: dict) -> None: + assert isinstance(visited, dict) for out_bb in bb.cfg_out: - if liveness_visited.get(bb) == out_bb: + if visited.get(bb) == out_bb: continue - liveness_visited[bb] = out_bb - _calculate_liveness_helper(out_bb, liveness_visited) + visited[bb] = out_bb + + # recurse + _calculate_liveness_r(out_bb, visited) + target_vars = input_vars_from(bb, out_bb) + + # the output stack layout for bb. it produces a stack layout + # which works for all possible cfg_outs from the bb. bb.out_vars = bb.out_vars.union(target_vars) _calculate_liveness_bb(bb) @@ -91,7 +97,7 @@ def _calculate_liveness_helper(bb: IRBasicBlock, liveness_visited: OrderedSet) - def calculate_liveness(ctx: IRFunction) -> None: _reset_liveness(ctx) - _calculate_liveness_helper(ctx.basic_blocks[0], OrderedSet()) + _calculate_liveness_r(ctx.basic_blocks[0], dict()) # calculate the input variables into self from source diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 4a3c707c78..01d129a5ba 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -12,9 +12,6 @@ from vyper.venom.function import IRFunction from vyper.venom.stack_model import StackModel -# binary instructions which are commutative -_COMMUTATIVE_INSTRUCTIONS = frozenset(["add", "mul", "and", "or", "xor", "eq"]) - # instructions which map one-to-one from venom to EVM _ONE_TO_ONE_INSTRUCTIONS = frozenset( [ @@ -136,18 +133,11 @@ def _stack_reorder( assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVariable], - commutative: bool = False, ) -> None: # make a list so we can index it stack_ops = [x for x in stack_ops] stack_ops_count = len(stack_ops) - if commutative: - depth = stack.get_depth(stack_ops[0]) - # TODO: Apply commutative knowledge to optimize stack - # if depth == 0: - # stack_ops = list(reversed(stack_ops)) - for i in range(stack_ops_count): op = stack_ops[i] final_stack_depth = -(stack_ops_count - i - 1) @@ -231,15 +221,30 @@ def _generate_evm_for_basicblock_r( self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) # pop values from stack at entry to bb + # note this produces the same result(!) no matter which basic block + # we enter from in the CFG. def clean_stack_from_cfg_in(self, asm: list, basicblock: IRBasicBlock, stack: StackModel): - if not basicblock.cfg_in: + if len(basicblock.cfg_in) == 0: return + for in_bb in basicblock.cfg_in: + if in_bb.out_vars == OrderedSet(stack._stack): + break + else: + # the input stack is not produced by a cfg_in + raise CompilerPanic("unreachable!") + to_pop = OrderedSet() for in_bb in basicblock.cfg_in: + # inputs is the input variables we need from in_bb inputs = input_vars_from(in_bb, basicblock) + + # layout is the output stack layout for in_bb (which works + # for all possible cfg_outs from the in_bb). layout = in_bb.out_vars - to_pop |= in_bb.out_vars.difference(inputs) + + # pop all the stack items which in_bb produced which we don't need. + to_pop |= layout.difference(inputs) for var in to_pop: depth = stack.get_depth(IRValueBase(var.value)) @@ -297,20 +302,10 @@ def _generate_evm_for_instruction( assert isinstance(inst.parent.cfg_out, OrderedSet) b = next(iter(inst.parent.cfg_out)) target_stack = input_vars_from(inst.parent, b) - # REVIEW: this seems like it generates bad code, because - # the next _stack_reorder will undo the changes to the stack. - # i think we can just remove it entirely. - # HK: Can't be removed entirely because we need to ensure that - # the stack is in the correct state for the next basic block - # regardless from where we came from. The next _stack_reorder - # will undo the changes to the stack for the operand only. - # The optimization is to have the _stack_reorder emidiatly below - # stop at the operand lenght. Or make a combined version. - # I am adding a TODO for this. + # TODO optimize stack reordering at entry and exit from basic blocks self._stack_reorder(assembly, stack, target_stack) - is_commutative = opcode in _COMMUTATIVE_INSTRUCTIONS - self._stack_reorder(assembly, stack, operands, is_commutative) + self._stack_reorder(assembly, stack, operands) # some instructions (i.e. invoke) need to do stack manipulations # with the stack model containing the return value(s), so we fiddle From 31aba53ab985b0b6556b80b553b38e357d24c84a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 12:05:05 -0500 Subject: [PATCH 399/471] fix lint --- vyper/venom/analysis.py | 2 ++ vyper/venom/basicblock.py | 1 - vyper/venom/venom_to_assembly.py | 6 ++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 3873692e1c..83641ddbbc 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -1,3 +1,4 @@ +from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRVariable from vyper.venom.function import IRFunction @@ -57,6 +58,7 @@ def _reset_liveness(ctx: IRFunction) -> None: for inst in bb.instructions: inst.liveness = OrderedSet() + def _calculate_liveness_bb(bb: IRBasicBlock): """ Compute liveness of each instruction in the basic block. diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index ff9ba78f69..e816fd55f8 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -1,7 +1,6 @@ from enum import Enum, auto from typing import TYPE_CHECKING, Optional -from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet # instructions which can terminate a basic block diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 01d129a5ba..84b2e53e1d 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,3 +1,4 @@ +from vyper.exceptions import CompilerPanic from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet from vyper.venom.analysis import calculate_cfg, calculate_liveness, input_vars_from @@ -129,10 +130,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: return asm def _stack_reorder( - self, - assembly: list, - stack: StackModel, - stack_ops: OrderedSet[IRVariable], + self, assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVariable] ) -> None: # make a list so we can index it stack_ops = [x for x in stack_ops] From e7540fd8d1691ef6ef39b6799dd76a38e8b3d141 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 12:09:43 -0500 Subject: [PATCH 400/471] add a couple comments --- vyper/venom/venom_to_assembly.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 84b2e53e1d..4c63978edc 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -297,12 +297,15 @@ def _generate_evm_for_instruction( # Step 3: Reorder stack if opcode in ["jnz", "jmp"]: + # prepare stack for jump into another basic block assert isinstance(inst.parent.cfg_out, OrderedSet) b = next(iter(inst.parent.cfg_out)) target_stack = input_vars_from(inst.parent, b) # TODO optimize stack reordering at entry and exit from basic blocks self._stack_reorder(assembly, stack, target_stack) + # final step to get the inputs to this instruction ordered + # correctly on the stack self._stack_reorder(assembly, stack, operands) # some instructions (i.e. invoke) need to do stack manipulations From 7cd903006ebff98f6270a57edd22b442ce4362bb Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 8 Nov 2023 12:23:32 -0500 Subject: [PATCH 401/471] add review comment --- vyper/venom/venom_to_assembly.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 4c63978edc..a0218820c5 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -244,6 +244,7 @@ def clean_stack_from_cfg_in(self, asm: list, basicblock: IRBasicBlock, stack: St # pop all the stack items which in_bb produced which we don't need. to_pop |= layout.difference(inputs) + # REVIEW: does this work when len(cfg_in) > 1? for var in to_pop: depth = stack.get_depth(IRValueBase(var.value)) # don't pop phantom phi inputs From 8b808eb903a7fb88145054b9bce3b3bcf99b2c1e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 9 Nov 2023 15:45:59 +0200 Subject: [PATCH 402/471] Add test for multi-entry block in Vyper compiler --- .../compiler/venom/test_multi_entry_block.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/compiler/venom/test_multi_entry_block.py diff --git a/tests/compiler/venom/test_multi_entry_block.py b/tests/compiler/venom/test_multi_entry_block.py new file mode 100644 index 0000000000..ea60dc8453 --- /dev/null +++ b/tests/compiler/venom/test_multi_entry_block.py @@ -0,0 +1,41 @@ +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, IRLabel, IRBasicBlock + + +def test_multi_entry_block(): + ctx = IRFunction() + + finish_label = IRLabel("finish") + target_label = IRLabel("target") + block_1_label = IRLabel("block_1", ctx) + + op = ctx.append_instruction("store", [IRLiteral(10)]) + sum = ctx.append_instruction("add", [op, op]) + ctx.append_instruction("jnz", [sum, finish_label, block_1_label], False) + + target_bb = IRBasicBlock(block_1_label, ctx) + ctx.append_basic_block(target_bb) + sum = ctx.append_instruction("add", [sum, op]) + op = ctx.append_instruction("store", [IRLiteral(10)]) + ctx.append_instruction("mstore", [sum, op], False) + ctx.append_instruction("jnz", [sum, target_label, finish_label]) + + target_bb = IRBasicBlock(target_label, ctx) + ctx.append_basic_block(target_bb) + mul = ctx.append_instruction("mul", [sum, sum]) + ctx.append_instruction("jmp", [finish_label], False) + + finish_label = IRBasicBlock(finish_label, ctx) + ctx.append_basic_block(finish_label) + ctx.append_instruction("stop", [], False) + + asm = generate_assembly_experimental(ctx, OptimizationLevel.CODESIZE) + + print(ctx) + print(asm) + + +if __name__ == "__main__": + test_multi_entry_block() From 7be6321e64aa6a71c12333972d0cbabe946b5d70 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 9 Nov 2023 15:46:36 +0200 Subject: [PATCH 403/471] Remove not wanted test and assertion assertion in VenomCompiler --- vyper/venom/venom_to_assembly.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index a0218820c5..e6a494d675 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -225,13 +225,6 @@ def clean_stack_from_cfg_in(self, asm: list, basicblock: IRBasicBlock, stack: St if len(basicblock.cfg_in) == 0: return - for in_bb in basicblock.cfg_in: - if in_bb.out_vars == OrderedSet(stack._stack): - break - else: - # the input stack is not produced by a cfg_in - raise CompilerPanic("unreachable!") - to_pop = OrderedSet() for in_bb in basicblock.cfg_in: # inputs is the input variables we need from in_bb @@ -339,7 +332,7 @@ def _generate_evm_for_instruction( assembly.append("JUMPI") # make sure the if_zero_label will be optimized out - assert if_zero_label == next(iter(inst.parent.cfg_out)).label + # assert if_zero_label == next(iter(inst.parent.cfg_out)).label assembly.append(f"_sym_{if_zero_label.value}") assembly.append("JUMP") From 264142a8f644dede6aa605c7165b354b70412ba0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 9 Nov 2023 16:17:06 +0200 Subject: [PATCH 404/471] Add normalization pass to split basic blocks with multiple conditional predecessors --- vyper/venom/passes/normalization.py | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 vyper/venom/passes/normalization.py diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py new file mode 100644 index 0000000000..9a55575f40 --- /dev/null +++ b/vyper/venom/passes/normalization.py @@ -0,0 +1,42 @@ +from vyper.utils import OrderedSet +from vyper.venom.analysis import DFG, calculate_cfg, calculate_liveness +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel +from vyper.venom.function import IRFunction +from vyper.venom.passes.base_pass import IRPass + + +class Normalization(IRPass): + """ + This pass splits basic blocks when there are multiple conditional predecessors. + The code generator expect a normalized CFG, that has the property that + each basic block has at most one conditional predecessor. + """ + + def _split_basic_block(self, bb: IRBasicBlock) -> None: + ctx = self.ctx + label_base = bb.label.value + + # Iterate over the predecessors of the basic block + for in_bb in bb.cfg_in: + jump_inst = in_bb.instructions[-1] + # We are only splitting on contitional jumps + if jump_inst.opcode != "jnz": + continue + + # Create an intermediary basic block and append it + split_bb = IRBasicBlock(IRLabel(label_base + "_split_" + in_bb.label.value), ctx) + ctx.append_basic_block(split_bb) + ctx.append_instruction("jmp", [bb.label], False) + + # Redirect the original conditional jump to the intermediary basic block + jump_inst.operands[1] = split_bb.label + + def _run_pass(self, ctx: IRFunction) -> None: + self.ctx = ctx + self.dfg = DFG.build_dfg(ctx) + + calculate_cfg(ctx) + + for bb in ctx.basic_blocks: + if len(bb.cfg_in) > 1: + self._split_basic_block(bb) From c7b50fa64a1fa4723befa9965dc28683d463e01a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 9 Nov 2023 16:17:47 +0200 Subject: [PATCH 405/471] Use normalize pass in multi-entry block test case --- tests/compiler/venom/test_multi_entry_block.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/compiler/venom/test_multi_entry_block.py b/tests/compiler/venom/test_multi_entry_block.py index ea60dc8453..5a457fed94 100644 --- a/tests/compiler/venom/test_multi_entry_block.py +++ b/tests/compiler/venom/test_multi_entry_block.py @@ -2,6 +2,7 @@ from vyper.venom import generate_assembly_experimental from vyper.venom.basicblock import IRLiteral from vyper.venom.function import IRFunction, IRLabel, IRBasicBlock +from vyper.venom.passes.normalization import Normalization def test_multi_entry_block(): @@ -20,7 +21,7 @@ def test_multi_entry_block(): sum = ctx.append_instruction("add", [sum, op]) op = ctx.append_instruction("store", [IRLiteral(10)]) ctx.append_instruction("mstore", [sum, op], False) - ctx.append_instruction("jnz", [sum, target_label, finish_label]) + ctx.append_instruction("jnz", [sum, finish_label, target_label], False) target_bb = IRBasicBlock(target_label, ctx) ctx.append_basic_block(target_bb) @@ -31,6 +32,8 @@ def test_multi_entry_block(): ctx.append_basic_block(finish_label) ctx.append_instruction("stop", [], False) + Normalization.run_pass(ctx) + asm = generate_assembly_experimental(ctx, OptimizationLevel.CODESIZE) print(ctx) From ca42dc61ae51f0a4132fdb467ebd5bd148aaaa72 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 09:04:47 +0200 Subject: [PATCH 406/471] return changes from normalization pass --- vyper/venom/passes/normalization.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 9a55575f40..a11dd2d3ac 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -31,12 +31,16 @@ def _split_basic_block(self, bb: IRBasicBlock) -> None: # Redirect the original conditional jump to the intermediary basic block jump_inst.operands[1] = split_bb.label - def _run_pass(self, ctx: IRFunction) -> None: + def _run_pass(self, ctx: IRFunction) -> int: self.ctx = ctx self.dfg = DFG.build_dfg(ctx) calculate_cfg(ctx) + changes = 0 for bb in ctx.basic_blocks: if len(bb.cfg_in) > 1: self._split_basic_block(bb) + changes += 1 + + return changes From 40fa04b985fa385631741fda886b9d69a9d62c77 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 09:05:01 +0200 Subject: [PATCH 407/471] remove review comment --- vyper/venom/venom_to_assembly.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index e6a494d675..e765928514 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -237,7 +237,6 @@ def clean_stack_from_cfg_in(self, asm: list, basicblock: IRBasicBlock, stack: St # pop all the stack items which in_bb produced which we don't need. to_pop |= layout.difference(inputs) - # REVIEW: does this work when len(cfg_in) > 1? for var in to_pop: depth = stack.get_depth(IRValueBase(var.value)) # don't pop phantom phi inputs From 4591b00946ad3686bf94c637587a883d12fb881f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 10:02:17 +0200 Subject: [PATCH 408/471] Add normalized property to IRFunction --- vyper/venom/function.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 9cd94cc455..da2cdae9cc 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -113,6 +113,29 @@ def append_data(self, opcode: str, args: list[IRValueBase]): """ self.data_segment.append(IRInstruction(opcode, args)) + @property + def normalized(self) -> bool: + """ + Check if function is normalized. + """ + for bb in self.basic_blocks: + # Ignore if there are no multiple predecessors + if len(bb.cfg_in) <= 1: + continue + + # Check if there is a conditional jump at the end + # of one of the predecessors + for in_bb in bb.cfg_in: + jump_inst = in_bb.instructions[-1] + if jump_inst.opcode != "jnz": + continue + + # The function is not normalized + return False + + # The function is normalized + return True + def copy(self): new = IRFunction(self.name) new.basic_blocks = self.basic_blocks.copy() From 821bedd0d08f9a3d3a2c3d5064c09eb824e12bae Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 10:02:57 +0200 Subject: [PATCH 409/471] normalization pass return changes --- vyper/venom/passes/normalization.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index a11dd2d3ac..5055aaaf62 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -12,6 +12,8 @@ class Normalization(IRPass): each basic block has at most one conditional predecessor. """ + changes = 0 + def _split_basic_block(self, bb: IRBasicBlock) -> None: ctx = self.ctx label_base = bb.label.value @@ -31,16 +33,22 @@ def _split_basic_block(self, bb: IRBasicBlock) -> None: # Redirect the original conditional jump to the intermediary basic block jump_inst.operands[1] = split_bb.label + self.changes += 1 + def _run_pass(self, ctx: IRFunction) -> int: self.ctx = ctx self.dfg = DFG.build_dfg(ctx) + self.changes = 0 calculate_cfg(ctx) - - changes = 0 for bb in ctx.basic_blocks: if len(bb.cfg_in) > 1: self._split_basic_block(bb) - changes += 1 - return changes + # Recalculate control flow graph + calculate_cfg(ctx) + + # Sanity check + assert ctx.normalized == True, "Normalization pass failed" + + return self.changes From 2f40fc5ce0965a48e4cb535e1fc87538277083ea Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 10:21:08 +0200 Subject: [PATCH 410/471] dirty cfg tracking --- vyper/venom/basicblock.py | 17 +++++++++++++++++ vyper/venom/function.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index e816fd55f8..6d14f64d83 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -29,6 +29,8 @@ "jnz", ] +CFG_ALTERING_OPS = ["jmp", "jnz", "call", "staticcall", "invoke", "deploy"] + if TYPE_CHECKING: from vyper.venom.function import IRFunction @@ -247,6 +249,8 @@ class IRBasicBlock: cfg_in: OrderedSet["IRBasicBlock"] # basic blocks which this basic block can jump to cfg_out: OrderedSet["IRBasicBlock"] + # Does this basic block have a dirty cfg + cfg_dirty: bool # stack items which this basic block produces out_vars: OrderedSet[IRVariable] @@ -255,6 +259,7 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.label = label self.parent = parent self.instructions = [] + self.cfg_dirty = False self.cfg_in = OrderedSet() self.cfg_out = OrderedSet() self.out_vars = OrderedSet() @@ -284,14 +289,19 @@ def is_reachable(self) -> bool: def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" instruction.parent = self + if instruction.opcode in CFG_ALTERING_OPS: + self.cfg_dirty = True self.instructions.append(instruction) def insert_instruction(self, instruction: IRInstruction, index: int) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" instruction.parent = self + if instruction.opcode in CFG_ALTERING_OPS: + self.cfg_dirty = True self.instructions.insert(index, instruction) def clear_instructions(self) -> None: + self.cfg_dirty = True self.instructions = [] def replace_operands(self, replacements: dict) -> None: @@ -301,6 +311,12 @@ def replace_operands(self, replacements: dict) -> None: for instruction in self.instructions: instruction.replace_operands(replacements) + def cfg_dirty_clear(self) -> None: + """ + Clear CFG dirty flag + """ + self.cfg_dirty = False + @property def is_terminated(self) -> bool: """ @@ -315,6 +331,7 @@ def is_terminated(self) -> bool: def copy(self): bb = IRBasicBlock(self.label, self.parent) bb.instructions = self.instructions.copy() + bb.cfg_dirty = self.cfg_dirty bb.cfg_in = self.cfg_in.copy() bb.cfg_out = self.cfg_out.copy() bb.out_vars = self.out_vars.copy() diff --git a/vyper/venom/function.py b/vyper/venom/function.py index da2cdae9cc..a0334af749 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -113,6 +113,23 @@ def append_data(self, opcode: str, args: list[IRValueBase]): """ self.data_segment.append(IRInstruction(opcode, args)) + @property + def cfg_dirty(self) -> bool: + """ + Check if CFG needs to be recalculated + """ + for bb in self.basic_blocks: + if bb.cfg_dirty: + return True + return False + + def cfg_dirty_clear(self) -> None: + """ + Clear CFG dirty flag + """ + for bb in self.basic_blocks: + bb.cfg_dirty_clear() + @property def normalized(self) -> bool: """ From 96bf316f5217a27880681f56c4d249b1ca2acb2f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 10:21:30 +0200 Subject: [PATCH 411/471] Add CFG_ALTERING_OPS to basicblock imports --- vyper/venom/analysis.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 83641ddbbc..1f72df023c 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -1,6 +1,12 @@ from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet -from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRVariable +from vyper.venom.basicblock import ( + CFG_ALTERING_OPS, + BB_TERMINATORS, + IRBasicBlock, + IRInstruction, + IRVariable, +) from vyper.venom.function import IRFunction @@ -42,7 +48,7 @@ def calculate_cfg(ctx: IRFunction) -> None: ) for inst in bb.instructions: - if inst.opcode in ["jmp", "jnz", "call", "staticcall", "invoke", "deploy"]: + if inst.opcode in CFG_ALTERING_OPS: ops = inst.get_label_operands() for op in ops: ctx.get_basic_block(op.value).add_cfg_in(bb) From 3a36dc7ed60df36d500a85e03fcd5498f1fb0fbf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 10:22:21 +0200 Subject: [PATCH 412/471] Refactor basic block module: use frozenset for constants --- vyper/venom/basicblock.py | 54 ++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 6d14f64d83..ed9edfac28 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -4,32 +4,34 @@ from vyper.utils import OrderedSet # instructions which can terminate a basic block -BB_TERMINATORS = ["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"] - -VOLATILE_INSTRUCTIONS = [ - "param", - "alloca", - "call", - "staticcall", - "invoke", - "sload", - "sstore", - "iload", - "istore", - "assert", - "mstore", - "mload", - "calldatacopy", - "codecopy", - "dloadbytes", - "dload", - "return", - "ret", - "jmp", - "jnz", -] - -CFG_ALTERING_OPS = ["jmp", "jnz", "call", "staticcall", "invoke", "deploy"] +BB_TERMINATORS = frozenset(["jmp", "jnz", "ret", "return", "revert", "deploy", "stop"]) + +VOLATILE_INSTRUCTIONS = frozenset( + [ + "param", + "alloca", + "call", + "staticcall", + "invoke", + "sload", + "sstore", + "iload", + "istore", + "assert", + "mstore", + "mload", + "calldatacopy", + "codecopy", + "dloadbytes", + "dload", + "return", + "ret", + "jmp", + "jnz", + ] +) + +CFG_ALTERING_OPS = frozenset(["jmp", "jnz", "call", "staticcall", "invoke", "deploy"]) if TYPE_CHECKING: From a7c076d37234a113e90724ce73c085d4c4acaa17 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 10:23:15 +0200 Subject: [PATCH 413/471] Clear CFG dirty flag in calculate_cfg function --- vyper/venom/analysis.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 1f72df023c..b870bcae3c 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -58,6 +58,8 @@ def calculate_cfg(ctx: IRFunction) -> None: for in_bb in bb.cfg_in: in_bb.add_cfg_out(bb) + ctx.cfg_dirty_clear() + def _reset_liveness(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: From b0cbca058e1700d285cc8404e7dfac6d9dc792c8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 10:27:01 +0200 Subject: [PATCH 414/471] Add normalization check and control flow graph calculation --- vyper/venom/passes/normalization.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 5055aaaf62..2575180f86 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -40,7 +40,14 @@ def _run_pass(self, ctx: IRFunction) -> int: self.dfg = DFG.build_dfg(ctx) self.changes = 0 - calculate_cfg(ctx) + # Already normalized -> bail out + if ctx.normalized: + return 0 + + # Calculate control flow graph if needed + if ctx.cfg_dirty: + calculate_cfg(ctx) + for bb in ctx.basic_blocks: if len(bb.cfg_in) > 1: self._split_basic_block(bb) From f6777e611bbbaf9315464a37c3e94f78ea454140 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 10:43:50 +0200 Subject: [PATCH 415/471] Remove redundant code for already normalized context. --- vyper/venom/passes/normalization.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 2575180f86..c37a90a440 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -40,10 +40,6 @@ def _run_pass(self, ctx: IRFunction) -> int: self.dfg = DFG.build_dfg(ctx) self.changes = 0 - # Already normalized -> bail out - if ctx.normalized: - return 0 - # Calculate control flow graph if needed if ctx.cfg_dirty: calculate_cfg(ctx) From 2134b48423ef56fc571f00c777267c8e7a4b2cec Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 10:44:05 +0200 Subject: [PATCH 416/471] Refactor multi-entry block test case --- .../compiler/venom/test_multi_entry_block.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/compiler/venom/test_multi_entry_block.py b/tests/compiler/venom/test_multi_entry_block.py index 5a457fed94..63aad26e22 100644 --- a/tests/compiler/venom/test_multi_entry_block.py +++ b/tests/compiler/venom/test_multi_entry_block.py @@ -28,17 +28,19 @@ def test_multi_entry_block(): mul = ctx.append_instruction("mul", [sum, sum]) ctx.append_instruction("jmp", [finish_label], False) - finish_label = IRBasicBlock(finish_label, ctx) - ctx.append_basic_block(finish_label) + finish_bb = IRBasicBlock(finish_label, ctx) + ctx.append_basic_block(finish_bb) ctx.append_instruction("stop", [], False) - Normalization.run_pass(ctx) - - asm = generate_assembly_experimental(ctx, OptimizationLevel.CODESIZE) + assert ctx.cfg_dirty == True, "CFG should be dirty" - print(ctx) - print(asm) + Normalization.run_pass(ctx) + assert ctx.cfg_dirty == False, "CFG should be clean" + assert ctx.normalized == True, "CFG should be normalized" -if __name__ == "__main__": - test_multi_entry_block() + 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 7c573c5345e68645680e286db2f4650482774b0c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 11:00:35 +0200 Subject: [PATCH 417/471] lint --- tests/compiler/venom/test_multi_entry_block.py | 12 +++++------- vyper/venom/analysis.py | 2 +- vyper/venom/passes/normalization.py | 7 +++---- vyper/venom/venom_to_assembly.py | 1 - 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/tests/compiler/venom/test_multi_entry_block.py b/tests/compiler/venom/test_multi_entry_block.py index 63aad26e22..5dbaab20e2 100644 --- a/tests/compiler/venom/test_multi_entry_block.py +++ b/tests/compiler/venom/test_multi_entry_block.py @@ -1,7 +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, IRLabel, IRBasicBlock +from vyper.venom.function import IRBasicBlock, IRFunction, IRLabel from vyper.venom.passes.normalization import Normalization @@ -25,19 +23,19 @@ def test_multi_entry_block(): target_bb = IRBasicBlock(target_label, ctx) ctx.append_basic_block(target_bb) - mul = ctx.append_instruction("mul", [sum, sum]) + _ = ctx.append_instruction("mul", [sum, sum]) ctx.append_instruction("jmp", [finish_label], False) finish_bb = IRBasicBlock(finish_label, ctx) ctx.append_basic_block(finish_bb) ctx.append_instruction("stop", [], False) - assert ctx.cfg_dirty == True, "CFG should be dirty" + assert ctx.cfg_dirty is True, "CFG should be dirty" Normalization.run_pass(ctx) - assert ctx.cfg_dirty == False, "CFG should be clean" - assert ctx.normalized == True, "CFG should be normalized" + assert ctx.cfg_dirty is False, "CFG should be clean" + assert ctx.normalized is True, "CFG should be normalized" finish_bb = ctx.get_basic_block(finish_label.value) cfg_in = list(finish_bb.cfg_in.keys()) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index b870bcae3c..b2c080153e 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -1,8 +1,8 @@ from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet from vyper.venom.basicblock import ( - CFG_ALTERING_OPS, BB_TERMINATORS, + CFG_ALTERING_OPS, IRBasicBlock, IRInstruction, IRVariable, diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index c37a90a440..66189272cd 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -1,6 +1,5 @@ -from vyper.utils import OrderedSet -from vyper.venom.analysis import DFG, calculate_cfg, calculate_liveness -from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel +from vyper.venom.analysis import DFG, calculate_cfg +from vyper.venom.basicblock import IRBasicBlock, IRLabel from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass @@ -52,6 +51,6 @@ def _run_pass(self, ctx: IRFunction) -> int: calculate_cfg(ctx) # Sanity check - assert ctx.normalized == True, "Normalization pass failed" + assert ctx.normalized is True, "Normalization pass failed" return self.changes diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index e765928514..db70c2e767 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,4 +1,3 @@ -from vyper.exceptions import CompilerPanic from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet from vyper.venom.analysis import calculate_cfg, calculate_liveness, input_vars_from From bed0fe371aa643e85a804e5ec74adac1c3ff870f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 11:33:48 +0200 Subject: [PATCH 418/471] mypy lint --- vyper/venom/basicblock.py | 4 ++-- vyper/venom/bb_optimizer.py | 2 +- vyper/venom/function.py | 4 +++- vyper/venom/venom_to_assembly.py | 10 +++++----- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index ed9edfac28..74f3a8f9a6 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -63,7 +63,7 @@ class IRValueBase: value: IRValueBaseValue def __init__(self, value: IRValueBaseValue) -> None: - assert isinstance(value, IRValueBaseValue), "value must be an IRValueBaseValue" + assert isinstance(value, str) or isinstance(value, int), "value must be an IRValueBaseValue" self.value = value @property @@ -175,7 +175,7 @@ def get_non_label_operands(self) -> list[IRValueBase]: """ return [op for op in self.operands if not isinstance(op, IRLabel)] - def get_inputs(self) -> list[IRValueBase]: + def get_inputs(self) -> list[IRVariable]: """ Get all input operands for instruction. """ diff --git a/vyper/venom/bb_optimizer.py b/vyper/venom/bb_optimizer.py index 6d620b902f..620ee66d15 100644 --- a/vyper/venom/bb_optimizer.py +++ b/vyper/venom/bb_optimizer.py @@ -4,7 +4,7 @@ from vyper.venom.function import IRFunction -def _optimize_unused_variables(ctx: IRFunction) -> list[IRInstruction]: +def _optimize_unused_variables(ctx: IRFunction) -> set[IRInstruction]: """ Remove unused variables. """ diff --git a/vyper/venom/function.py b/vyper/venom/function.py index a0334af749..97fb1105f6 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -98,7 +98,9 @@ def remove_unreachable_blocks(self) -> int: self.basic_blocks = new_basic_blocks return removed - def append_instruction(self, opcode: str, args: list[IRValueBase], do_ret: bool = True): + def append_instruction( + self, opcode: str, args: list[IRValueBase], do_ret: bool = True + ) -> IRVariable: """ Append instruction to last basic block. """ diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index db70c2e767..58aa0bdb0b 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -78,8 +78,8 @@ class VenomCompiler: ctx: IRFunction label_counter = 0 - visited_instructions = None # {IRInstruction} - visited_basicblocks = None # {IRBasicBlock} + visited_instructions: OrderedSet # {IRInstruction} + visited_basicblocks: OrderedSet # {IRBasicBlock} def __init__(self, ctx: IRFunction): self.ctx = ctx @@ -93,7 +93,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: self.label_counter = 0 stack = StackModel() - asm = [] + asm: list[str] = [] calculate_cfg(self.ctx) calculate_liveness(self.ctx) @@ -148,7 +148,7 @@ def _stack_reorder( def _emit_input_operands( self, assembly: list, inst: IRInstruction, ops: list[IRValueBase], stack: StackModel - ): + ) -> None: # PRE: we already have all the items on the stack that have # been scheduled to be killed. now it's just a matter of emitting # SWAPs, DUPs and PUSHes until we match the `ops` argument @@ -200,7 +200,7 @@ def _emit_input_operands( # basic block regardless from where we came from. Let's discuss offline. def _generate_evm_for_basicblock_r( self, asm: list, basicblock: IRBasicBlock, stack: StackModel - ): + ) -> None: if basicblock in self.visited_basicblocks: return self.visited_basicblocks.add(basicblock) From b3e6b41e6179603367b7d8b5666e180cfc6d6913 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 14:01:05 +0200 Subject: [PATCH 419/471] Add VariableRecord import and type annotations to ir_node_to_venom.py --- vyper/venom/ir_node_to_venom.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a07a4e174a..8e13e6ee6f 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,4 +1,5 @@ from typing import Optional +from vyper.codegen.context import VariableRecord from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes @@ -104,7 +105,7 @@ def _convert_binary_op( ctx: IRFunction, ir: IRnode, symbols: SymbolTable, - variables, + variables: OrderedSet, allocated_variables: dict[str, IRVariable], swap: bool = False, ) -> IRVariable: @@ -226,7 +227,7 @@ def _convert_ir_simple_node( _continue_target: IRBasicBlock = None -def _get_variable_from_address(variables: OrderedSet, addr: int) -> IRVariable: +def _get_variable_from_address(variables: OrderedSet[VariableRecord], addr: int) -> VariableRecord: assert isinstance(addr, int), "non-int address" for var in variables: if var.location.name != "memory": From da70812dc6798a3ffa3f82ae0426480c142cbe01 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 14:09:26 +0200 Subject: [PATCH 420/471] Refactor OrderedSet class to use generics --- vyper/utils.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/vyper/utils.py b/vyper/utils.py index 7a49a4a8de..a61162a575 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -11,7 +11,12 @@ from vyper.exceptions import DecimalOverrideException, InvalidLiteral -class OrderedSet(dict): +from typing import Generic, TypeVar + +_T = TypeVar("_T") + + +class OrderedSet(Generic[_T], dict[_T, None]): """ a minimal "ordered set" class. this is needed in some places because, while dict guarantees you can recover insertion order @@ -33,10 +38,10 @@ def __repr__(self): def get(self, *args, **kwargs): raise RuntimeError("can't call get() on OrderedSet!") - def add(self, item): + def add(self, item: _T) -> None: self[item] = None - def remove(self, item): + def remove(self, item: _T) -> None: del self[item] def difference(self, other): From f60dc5dc38e7520543f084acfd7466427fd3c528 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 14:16:20 +0200 Subject: [PATCH 421/471] Set called_functions type to ContractFunctionT --- vyper/semantics/types/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index 77b9efb13d..140f73f095 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -93,7 +93,7 @@ def __init__( self.nonreentrant = nonreentrant # a list of internal functions this function calls - self.called_functions = OrderedSet() + self.called_functions = OrderedSet[ContractFunctionT]() # to be populated during codegen self._ir_info: Any = None From 104ee795ea1c8468898ebe82067e63ed395b636b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 14:17:08 +0200 Subject: [PATCH 422/471] append_instruction does not return --- 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 8e13e6ee6f..9b0e73adb0 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -647,7 +647,8 @@ def _convert_ir_basicblock( src = ctx.append_instruction("add", [src_offset, IRLabel("code_end")]) inst = IRInstruction("dloadbytes", [len_, src, dst]) - return ctx.get_basic_block().append_instruction(inst) + ctx.get_basic_block().append_instruction(inst) + return None elif ir.value == "mload": sym_ir = ir.args[0] var = _get_variable_from_address(variables, sym_ir.value) if sym_ir.is_literal else None From f24cbf1062418d06ac02b913f19eaa11ac04960e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 14:17:31 +0200 Subject: [PATCH 423/471] Add return type None to append_data method. --- vyper/venom/function.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 97fb1105f6..7cdb8ebdb6 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -100,7 +100,7 @@ def remove_unreachable_blocks(self) -> int: def append_instruction( self, opcode: str, args: list[IRValueBase], do_ret: bool = True - ) -> IRVariable: + ) -> IRVariable | None: """ Append instruction to last basic block. """ @@ -109,7 +109,7 @@ def append_instruction( self.get_basic_block().append_instruction(inst) return ret - def append_data(self, opcode: str, args: list[IRValueBase]): + def append_data(self, opcode: str, args: list[IRValueBase]) -> None: """ Append data """ From 4389a812fd4f5102b4201ebd872a60bee4847699 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 14:18:14 +0200 Subject: [PATCH 424/471] Assert calculate_cfg function and add value_str property to IRValueBase class --- vyper/venom/analysis.py | 9 +++++---- vyper/venom/basicblock.py | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index b2c080153e..9984beded0 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -28,6 +28,7 @@ def calculate_cfg(ctx: IRFunction) -> None: break if deploy_bb: + assert after_deploy_bb is not None, "No block after deploy block" entry_block = after_deploy_bb has_constructor = True if ctx.basic_blocks[0].instructions[0].opcode != "deploy" else False if has_constructor: @@ -37,7 +38,7 @@ def calculate_cfg(ctx: IRFunction) -> None: entry_block = ctx.basic_blocks[0] for bb in ctx.basic_blocks: - if "selector_bucket_" in bb.label.value or bb.label.value == "fallback": + if "selector_bucket_" in bb.label.value_str or bb.label.value == "fallback": bb.add_cfg_in(entry_block) for bb in ctx.basic_blocks: @@ -51,7 +52,7 @@ def calculate_cfg(ctx: IRFunction) -> None: if inst.opcode in CFG_ALTERING_OPS: ops = inst.get_label_operands() for op in ops: - ctx.get_basic_block(op.value).add_cfg_in(bb) + ctx.get_basic_block(op.value_str).add_cfg_in(bb) # Fill in the "out" set for each basic block for bb in ctx.basic_blocks: @@ -67,7 +68,7 @@ def _reset_liveness(ctx: IRFunction) -> None: inst.liveness = OrderedSet() -def _calculate_liveness_bb(bb: IRBasicBlock): +def _calculate_liveness_bb(bb: IRBasicBlock) -> None: """ Compute liveness of each instruction in the basic block. """ @@ -162,7 +163,7 @@ def get_producing_instruction(self, op: IRVariable) -> IRInstruction: return self._dfg_outputs[op] @classmethod - def build_dfg(cls, ctx: IRFunction): + def build_dfg(cls, ctx: IRFunction) -> "DFG": dfg = cls() # Build DFG diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 74f3a8f9a6..628385768d 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -70,6 +70,10 @@ def __init__(self, value: IRValueBaseValue) -> None: def is_literal(self) -> bool: return False + @property + def value_str(self) -> str: + return str(self.value) + def __repr__(self) -> str: return str(self.value) From d8ec267e40ec0c93ca0eaacd37d8e629952e44e5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 14:42:52 +0200 Subject: [PATCH 425/471] Add visited_instructions and visited_basicblocks to VenomCompiler constructor --- vyper/venom/venom_to_assembly.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 58aa0bdb0b..70179cc156 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -6,6 +6,7 @@ IRInstruction, IRLabel, IRValueBase, + IRValueBaseValue, IRVariable, MemType, ) @@ -84,8 +85,8 @@ class VenomCompiler: def __init__(self, ctx: IRFunction): self.ctx = ctx self.label_counter = 0 - self.visited_instructions = None - self.visited_basicblocks = None + self.visited_instructions = OrderedSet() + self.visited_basicblocks = OrderedSet() def generate_evm(self, no_optimize: bool = False) -> list[str]: self.visited_instructions = OrderedSet() @@ -112,7 +113,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: asm.append(runtime) # Append data segment - data_segments = {} + data_segments = dict[IRValueBase | IRValueBaseValue, list[str | DataHeader]]() for inst in self.ctx.data_segment: if inst.opcode == "dbname": label = inst.operands[0].value From 317ea91347d5515710d42b0010e477afacfdb10a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 14:44:16 +0200 Subject: [PATCH 426/471] Make NOT_IN_STACK int -> or we need to change all invocations that return it --- vyper/venom/stack_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index e85eecf2b3..21270d0e84 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -2,7 +2,7 @@ class StackModel: - NOT_IN_STACK = object() + NOT_IN_STACK = 1 _stack: list[IRValueBase] def __init__(self): From 6a0da6171711ce37e28c518954a56eb3d2191938 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 14:44:58 +0200 Subject: [PATCH 427/471] Fix variable naming in DFG class method (mypy). --- vyper/venom/analysis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 9984beded0..91b623255b 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -181,7 +181,7 @@ def build_dfg(cls, ctx: IRFunction) -> "DFG": inputs = dfg._dfg_inputs.setdefault(op, []) inputs.append(inst) - for op in res: - dfg._dfg_outputs[op] = inst + for op2 in res: + dfg._dfg_outputs[op2] = inst return dfg From c3dd68159f18a9527c3c1ece9c610312a2b193ea Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 14:45:12 +0200 Subject: [PATCH 428/471] Refactor IRInstruction constructor to allow for IRValueBaseValue operands --- vyper/venom/basicblock.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 628385768d..89b374e646 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -154,13 +154,16 @@ class IRInstruction: fence_id: int annotation: Optional[str] - def __init__(self, opcode: str, operands: list[IRValueBase], output: IRValueBase = None): + def __init__( + self, + opcode: str, + operands: list[IRValueBase | IRValueBaseValue], + output: Optional[IRValueBase] = None, + ): self.opcode = opcode self.volatile = opcode in VOLATILE_INSTRUCTIONS self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] - self.output = ( - output if isinstance(output, IRValueBase) else IRValueBase(output) if output else None - ) + self.output = output self.liveness = OrderedSet() self.dup_requirements = OrderedSet() self.parent = None @@ -185,7 +188,7 @@ def get_inputs(self) -> list[IRVariable]: """ return [op for op in self.operands if isinstance(op, IRVariable)] - def get_outputs(self) -> list[IRVariable]: + def get_outputs(self) -> list[IRValueBase]: """ Get the output item for an instruction. (Currently all instructions output at most one item, but write From 966156465befc3ed0bc2fa9dfe799c2aa37b0d7f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 15:32:31 +0200 Subject: [PATCH 429/471] lint: add passthrough fields to FuncIR --- vyper/codegen/function_definitions/common.py | 3 ++- vyper/venom/analysis.py | 2 +- vyper/venom/function.py | 4 +-- vyper/venom/ir_node_to_venom.py | 26 +++++++++++--------- vyper/venom/venom_to_assembly.py | 24 +++++++++--------- 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/vyper/codegen/function_definitions/common.py b/vyper/codegen/function_definitions/common.py index 4cf33fbaeb..cf1d7272c2 100644 --- a/vyper/codegen/function_definitions/common.py +++ b/vyper/codegen/function_definitions/common.py @@ -64,7 +64,8 @@ def internal_function_label(self, is_ctor_context: bool = False) -> str: class FuncIR: - pass + common_ir: IRnode # the "common" code for the function + func_ir: IRnode # the code for the function @dataclass diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 91b623255b..3a0e72858f 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -182,6 +182,6 @@ def build_dfg(cls, ctx: IRFunction) -> "DFG": inputs.append(inst) for op2 in res: - dfg._dfg_outputs[op2] = inst + dfg._dfg_outputs[op2] = inst # type: ignore return dfg diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 7cdb8ebdb6..7c4a0f137a 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -105,7 +105,7 @@ def append_instruction( Append instruction to last basic block. """ ret = self.get_next_variable() if do_ret else None - inst = IRInstruction(opcode, args, ret) + inst = IRInstruction(opcode, args, ret) # type: ignore self.get_basic_block().append_instruction(inst) return ret @@ -113,7 +113,7 @@ def append_data(self, opcode: str, args: list[IRValueBase]) -> None: """ Append data """ - self.data_segment.append(IRInstruction(opcode, args)) + self.data_segment.append(IRInstruction(opcode, args)) # type: ignore @property def cfg_dirty(self) -> bool: diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 9b0e73adb0..1c77bf2d02 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -116,7 +116,7 @@ def _convert_binary_op( ret = ctx.get_next_variable() - inst = IRInstruction(str(ir.value), args, ret) + inst = IRInstruction(str(ir.value), args, ret) # type: ignore ctx.get_basic_block().append_instruction(inst) return ret @@ -142,13 +142,13 @@ def _handle_self_call( symbols: SymbolTable, variables: OrderedSet, allocated_variables: dict[str, IRVariable], -) -> None: +) -> IRVariable: func_t = ir.passthrough_metadata.get("func_t", None) args_ir = ir.passthrough_metadata["args_ir"] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto return_buf = goto_ir.args[1] # return buffer - ret_args = [IRLabel(target_label)] + ret_args = list[Optional[IRValueBase]]([IRLabel(str(target_label))]) for arg in args_ir: if arg.is_literal: @@ -163,21 +163,21 @@ 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]) + ret = ctx.append_instruction(arg.location.load_op, [ret]) # type: ignore ret_args.append(ret) if return_buf.is_literal: ret_args.append(IRLiteral(return_buf.value)) - invoke_ret = ctx.append_instruction("invoke", ret_args, func_t.return_type is not None) - allocated_variables["return_buffer"] = invoke_ret + invoke_ret = ctx.append_instruction("invoke", ret_args, func_t.return_type is not None) # type: ignore + allocated_variables["return_buffer"] = invoke_ret # type: ignore return invoke_ret def _handle_internal_func( ctx: IRFunction, ir: IRnode, func_t: ContractFunctionT, symbols: SymbolTable ) -> IRnode: - bb = IRBasicBlock(IRLabel(ir.args[0].args[0].value, True), ctx) + bb = IRBasicBlock(IRLabel(str(ir.args[0].args[0].value), True), ctx) bb = ctx.append_basic_block(bb) old_ir_mempos = 0 @@ -216,18 +216,20 @@ def _convert_ir_simple_node( symbols: SymbolTable, variables: OrderedSet, allocated_variables: dict[str, IRVariable], -) -> IRVariable: +) -> Optional[IRVariable]: args = [ _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) for arg in ir.args ] - return ctx.append_instruction(ir.value, args) + return ctx.append_instruction(ir.value, args) # type: ignore -_break_target: IRBasicBlock = None -_continue_target: IRBasicBlock = None +_break_target: Optional[IRBasicBlock] = None +_continue_target: Optional[IRBasicBlock] = None -def _get_variable_from_address(variables: OrderedSet[VariableRecord], addr: int) -> VariableRecord: +def _get_variable_from_address( + variables: OrderedSet[VariableRecord], addr: int +) -> Optional[VariableRecord]: assert isinstance(addr, int), "non-int address" for var in variables: if var.location.name != "memory": diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 70179cc156..c6b652d228 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -122,7 +122,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: 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]) + extent_point.extend([data_segments[label] for label in data_segments]) # type: ignore if no_optimize is False: optimize_assembly(asm) @@ -130,16 +130,16 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: return asm def _stack_reorder( - self, assembly: list, stack: StackModel, stack_ops: OrderedSet[IRVariable] + self, assembly: list, stack: StackModel, _stack_ops: OrderedSet[IRVariable] ) -> None: # make a list so we can index it - stack_ops = [x for x in stack_ops] - stack_ops_count = len(stack_ops) + stack_ops = [x for x in _stack_ops.keys()] + stack_ops_count = len(_stack_ops) for i in range(stack_ops_count): op = stack_ops[i] final_stack_depth = -(stack_ops_count - i - 1) - depth = stack.get_depth(op) + depth = stack.get_depth(op) # type: ignore if depth == final_stack_depth: continue @@ -162,7 +162,7 @@ def _emit_input_operands( self.swap_op(assembly, stack, op) break - emitted_ops = OrderedSet() + emitted_ops = OrderedSet[IRValueBase]() for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -221,11 +221,13 @@ def _generate_evm_for_basicblock_r( # pop values from stack at entry to bb # note this produces the same result(!) no matter which basic block # we enter from in the CFG. - def clean_stack_from_cfg_in(self, asm: list, basicblock: IRBasicBlock, stack: StackModel): + def clean_stack_from_cfg_in( + self, asm: list, basicblock: IRBasicBlock, stack: StackModel + ) -> None: if len(basicblock.cfg_in) == 0: return - to_pop = OrderedSet() + to_pop = OrderedSet[IRVariable]() for in_bb in basicblock.cfg_in: # inputs is the input variables we need from in_bb inputs = input_vars_from(in_bb, basicblock) @@ -291,7 +293,7 @@ def _generate_evm_for_instruction( # Step 3: Reorder stack if opcode in ["jnz", "jmp"]: # prepare stack for jump into another basic block - assert isinstance(inst.parent.cfg_out, OrderedSet) + assert inst.parent and isinstance(inst.parent.cfg_out, OrderedSet) b = next(iter(inst.parent.cfg_out)) target_stack = input_vars_from(inst.parent, b) # TODO optimize stack reordering at entry and exit from basic blocks @@ -299,7 +301,7 @@ def _generate_evm_for_instruction( # final step to get the inputs to this instruction ordered # correctly on the stack - self._stack_reorder(assembly, stack, operands) + self._stack_reorder(assembly, stack, OrderedSet(operands)) # some instructions (i.e. invoke) need to do stack manipulations # with the stack model containing the return value(s), so we fiddle @@ -401,7 +403,7 @@ def _generate_evm_for_instruction( 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)]) + assembly.append([RuntimeHeader("_sym_runtime_begin", memsize, padding)]) # type: ignore assembly = assembly[-1] elif opcode == "iload": loc = inst.operands[0].value From 5a10513711899d33515f5361ab5269328d21b672 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 16:04:47 +0200 Subject: [PATCH 430/471] lint --- vyper/compiler/phases.py | 4 +- vyper/venom/ir_node_to_venom.py | 233 +++++++++++++++++--------------- 2 files changed, 125 insertions(+), 112 deletions(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index f51145ce57..6e0ea29a43 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -193,14 +193,14 @@ def function_signatures(self) -> dict[str, ContractFunctionT]: @cached_property def assembly(self) -> list: if self.experimental_codegen: - return generate_assembly_experimental(self.ir_nodes, self.settings.optimize) + return generate_assembly_experimental(self.ir_nodes, self.settings.optimize) # type: ignore else: return generate_assembly(self.ir_nodes, self.settings.optimize) @cached_property def assembly_runtime(self) -> list: if self.experimental_codegen: - return generate_assembly_experimental(self.ir_runtime, self.settings.optimize) + return generate_assembly_experimental(self.ir_runtime, self.settings.optimize) # type: ignore else: return generate_assembly(self.ir_runtime, self.settings.optimize) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 1c77bf2d02..6a857f64b1 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -142,7 +142,7 @@ def _handle_self_call( symbols: SymbolTable, variables: OrderedSet, allocated_variables: dict[str, IRVariable], -) -> IRVariable: +) -> Optional[IRVariable]: func_t = ir.passthrough_metadata.get("func_t", None) args_ir = ir.passthrough_metadata["args_ir"] goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] @@ -231,30 +231,30 @@ def _get_variable_from_address( variables: OrderedSet[VariableRecord], addr: int ) -> Optional[VariableRecord]: assert isinstance(addr, int), "non-int address" - for var in variables: + for var in variables.keys(): if var.location.name != "memory": continue - if addr >= var.pos and addr < var.pos + var.size: + if addr >= var.pos and addr < var.pos + var.size: # type: ignore return var return None def _get_return_for_stack_operand( ctx: IRFunction, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable -): +) -> IRInstruction: if ret_ir.is_literal: 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) + ctx.append_instruction("mstore", [sym, new_var], False) # type: ignore else: - sym = symbols.get(ret_ir.value, None) + sym = symbols.get(ret_ir.value_str, 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) + ctx.append_instruction("mstore", [ret_ir, new_var], False) # type: ignore else: new_var = ret_ir - return IRInstruction("return", [last_ir, new_var]) + return IRInstruction("return", [last_ir, new_var]) # type: ignore def _convert_ir_basicblock( @@ -263,13 +263,13 @@ def _convert_ir_basicblock( symbols: SymbolTable, variables: OrderedSet, allocated_variables: dict[str, IRVariable], -) -> Optional[IRVariable]: +) -> Optional[IRValueBase]: assert isinstance(variables, OrderedSet) global _break_target, _continue_target frame_info = ir.passthrough_metadata.get("frame_info", None) if frame_info is not None: - local_vars = OrderedSet(frame_info.frame_vars.values()) + local_vars = OrderedSet[VariableRecord](frame_info.frame_vars.values()) variables |= local_vars assert isinstance(variables, OrderedSet) @@ -281,7 +281,7 @@ def _convert_ir_basicblock( elif ir.value in INVERSE_MAPPED_IR_INSTRUCTIONS: org_value = ir.value - ir.value = INVERSE_MAPPED_IR_INSTRUCTIONS[ir.value] + ir.value = INVERSE_MAPPED_IR_INSTRUCTIONS[str(ir.value)] new_var = _convert_binary_op(ctx, ir, symbols, variables, allocated_variables) ir.value = org_value return ctx.append_instruction("iszero", [new_var]) @@ -354,29 +354,33 @@ def _convert_ir_basicblock( ctx, ir.args[idx + 6], symbols, variables, allocated_variables ) - if argsOffset.is_literal: - addr = argsOffset.value - 32 + 4 if argsOffset.value > 0 else 0 + if argsOffset and argsOffset.is_literal: + offset = int(argsOffset.value) + addr = offset - 32 + 4 if offset > 0 else 0 argsOffsetVar = symbols.get(f"&{addr}", None) if argsOffsetVar is None: argsOffsetVar = argsOffset - else: + elif isinstance(argsOffsetVar, IRVariable): argsOffsetVar.mem_type = MemType.MEMORY argsOffsetVar.mem_addr = addr - argsOffsetVar.offset = 32 - 4 if argsOffset.value > 0 else 0 + argsOffsetVar.offset = 32 - 4 if offset > 0 else 0 + else: + assert False, "unreachable" else: argsOffsetVar = argsOffset - retVar = ctx.get_next_variable(MemType.MEMORY, retOffset.value) - symbols[f"&{retOffset.value}"] = retVar + retOffsetValue = int(retOffset.value) if retOffset else 0 + retVar = ctx.get_next_variable(MemType.MEMORY, retOffsetValue) + symbols[f"&{retOffsetValue}"] = retVar if ir.value == "call": return ctx.append_instruction( - ir.value, - reversed([gas, address, value, argsOffsetVar, argsSize, retOffset, retSize]), + str(ir.value), + reversed([gas, address, value, argsOffsetVar, argsSize, retOffset, retSize]), # type: ignore ) else: return ctx.append_instruction( - reversed(ir.value, [gas, address, argsOffsetVar, argsSize, retOffset, retSize]) + str(ir.value), reversed([gas, address, argsOffsetVar, argsSize, retOffset, retSize]) # type: ignore ) elif ir.value == "if": cond = ir.args[0] @@ -407,9 +411,9 @@ def _convert_ir_basicblock( ctx, ir.args[1], symbols, variables, allocated_variables ) if then_ret_val is not None and then_ret_val.is_literal: - then_ret_val = ctx.append_instruction("store", [IRLiteral(then_ret_val.value)]) + then_ret_val = ctx.append_instruction("store", [IRLiteral(then_ret_val.value)]) # type: ignore - inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) + inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) # type: ignore current_bb.append_instruction(inst) after_then_syms = symbols.copy() @@ -434,9 +438,9 @@ def _convert_ir_basicblock( old_var = symbols.get(sym, None) symbols[sym] = ret if old_var is not None: - for idx, var in allocated_variables.items(): - if var.value == old_var.value: - allocated_variables[idx] = ret + 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) ) @@ -460,26 +464,26 @@ def _convert_ir_basicblock( with_symbols = symbols.copy() sym = ir.args[0] - if ret.is_literal: - new_var = ctx.append_instruction("store", [ret]) + if ret and ret.is_literal: + new_var = ctx.append_instruction("store", [ret]) # type: ignore with_symbols[sym.value] = new_var else: - with_symbols[sym.value] = ret + with_symbols[sym.value] = ret # type: ignore return _convert_ir_basicblock( ctx, ir.args[2], with_symbols, variables, allocated_variables ) # body elif ir.value == "goto": - _append_jmp(ctx, IRLabel(ir.args[0].value)) + _append_jmp(ctx, IRLabel(str(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]) + inst = IRInstruction("jmp", [arg_1]) # type: ignore ctx.get_basic_block().append_instruction(inst) _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.append_instruction("store", [arg_1]) + new_var = ctx.append_instruction("store", [arg_1]) # type: ignore symbols[sym.value] = new_var elif ir.value == "calldatacopy": @@ -487,47 +491,51 @@ def _convert_ir_basicblock( 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 = arg_0 - var = _get_variable_from_address(variables, arg_0.value) if arg_0.is_literal else None + new_v = arg_0 + var = ( + _get_variable_from_address(variables, int(arg_0.value)) + if arg_0 and arg_0.is_literal + else None + ) if var is not None: if allocated_variables.get(var.name, None) is None: - new_var = ctx.append_instruction( - "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] + new_v = ctx.append_instruction( + "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] # type: ignore ) - allocated_variables[var.name] = new_var - ctx.append_instruction("calldatacopy", [size, arg_1, new_var], False) - symbols[f"&{var.pos}"] = new_var + allocated_variables[var.name] = new_v # type: ignore + ctx.append_instruction("calldatacopy", [size, arg_1, new_v], False) # type: ignore + symbols[f"&{var.pos}"] = new_v # type: ignore else: - ctx.append_instruction("calldatacopy", [size, arg_1, new_var], False) + ctx.append_instruction("calldatacopy", [size, arg_1, new_v], False) # type: ignore - return new_var + return new_v elif ir.value == "codecopy": arg_0 = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) size = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) - ctx.append_instruction("codecopy", [size, arg_1, arg_0], False) + ctx.append_instruction("codecopy", [size, arg_1, arg_0], False) # type: ignore elif ir.value == "symbol": - return IRLabel(ir.args[0].value, True) + return IRLabel(str(ir.args[0].value), True) elif ir.value == "data": - label = IRLabel(ir.args[0].value) + label = IRLabel(str(ir.args[0].value)) ctx.append_data("dbname", [label]) for c in ir.args[1:]: if isinstance(c, int): assert 0 <= c <= 255, "data with invalid size" - ctx.append_data("db", [c]) + ctx.append_data("db", [c]) # type: ignore elif isinstance(c, bytes): - ctx.append_data("db", [c]) + ctx.append_data("db", [c]) # type: ignore elif isinstance(c, IRnode): data = _convert_ir_basicblock(ctx, c, symbols, variables, allocated_variables) - ctx.append_data("db", [data]) + ctx.append_data("db", [data]) # type: ignore 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]) + inst = IRInstruction("assert", [arg_0]) # type: ignore current_bb.append_instruction(inst) elif ir.value == "label": - label = IRLabel(ir.args[0].value, True) + label = IRLabel(str(ir.args[0].value), True) if ctx.get_basic_block().is_terminated is False: inst = IRInstruction("jmp", [label]) ctx.get_basic_block().append_instruction(inst) @@ -541,14 +549,14 @@ def _convert_ir_basicblock( if func_t.is_external: # Hardcoded contructor special case if func_t.name == "__init__": - label = IRLabel(ir.args[0].value, True) + label = IRLabel(str(ir.args[0].value), True) inst = IRInstruction("jmp", [label]) ctx.get_basic_block().append_instruction(inst) - return + return None if func_t.return_type is None: inst = IRInstruction("stop", []) ctx.get_basic_block().append_instruction(inst) - return + return None else: last_ir = None ret_var = ir.args[1] @@ -568,46 +576,46 @@ def _convert_ir_basicblock( ) var = ( - _get_variable_from_address(variables, ret_ir.value) - if ret_ir.is_literal + _get_variable_from_address(variables, int(ret_ir.value)) + if ret_ir and ret_ir.is_literal else None ) if var is not None: allocated_var = allocated_variables.get(var.name, None) assert allocated_var is not None, "unallocated variable" - new_var = symbols.get(f"&{ret_ir.value}", allocated_var) + new_var = symbols.get(f"&{ret_ir.value}", allocated_var) # type: ignore - if var.size > 32: - offset = ret_ir.value - var.pos + 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)] ) else: ptr_var = allocated_var - inst = IRInstruction("return", [last_ir, ptr_var]) + inst = IRInstruction("return", [last_ir, ptr_var]) # type: ignore else: - inst = _get_return_for_stack_operand(ctx, symbols, new_var, last_ir) + inst = _get_return_for_stack_operand(ctx, symbols, new_var, last_ir) # type: ignore else: - if ret_ir.is_literal: + if ret_ir and ret_ir.is_literal: sym = symbols.get(f"&{ret_ir.value}", None) if sym is None: - inst = IRInstruction("return", [last_ir, ret_ir]) + inst = IRInstruction("return", [last_ir, ret_ir]) # type: ignore else: if func_t.return_type.memory_bytes_required > 32: - new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) + new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_ir]) # type: ignore ctx.append_instruction("mstore", [sym, new_var], False) - inst = IRInstruction("return", [last_ir, new_var]) + inst = IRInstruction("return", [last_ir, new_var]) # type: ignore else: - inst = IRInstruction("return", [last_ir, ret_ir]) + inst = IRInstruction("return", [last_ir, ret_ir]) # type: ignore else: - if last_ir.value > 32: - inst = IRInstruction("return", [last_ir, ret_ir]) + if last_ir and int(last_ir.value) > 32: + inst = IRInstruction("return", [last_ir, ret_ir]) # type: ignore 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 = ctx.append_instruction("alloca", [IRLiteral(32), ret_buf]) # type: ignore + ctx.append_instruction("mstore", [ret_ir, new_var], False) # type: ignore + inst = IRInstruction("return", [last_ir, new_var]) # type: ignore ctx.get_basic_block().append_instruction(inst) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) @@ -621,22 +629,22 @@ def _convert_ir_basicblock( inst = IRInstruction("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"]]) + inst = IRInstruction("ret", [ret_by_value, symbols["return_pc"]]) # type: ignore ctx.get_basic_block().append_instruction(inst) 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]) + inst = IRInstruction("revert", [arg_1, arg_0]) # type: ignore ctx.get_basic_block().append_instruction(inst) 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")]) + src = ctx.append_instruction("add", [arg_0, IRLabel("code_end")]) # type: ignore ctx.append_instruction( - "dloadbytes", [IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE)], False + "dloadbytes", [IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE)], False # type: ignore ) return ctx.append_instruction("mload", [IRLiteral(MemoryPositions.FREE_VAR_SPACE)]) elif ir.value == "dloadbytes": @@ -646,33 +654,35 @@ def _convert_ir_basicblock( ) len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) - src = ctx.append_instruction("add", [src_offset, IRLabel("code_end")]) + src = ctx.append_instruction("add", [src_offset, IRLabel("code_end")]) # type: ignore - inst = IRInstruction("dloadbytes", [len_, src, dst]) + inst = IRInstruction("dloadbytes", [len_, src, dst]) # type: ignore ctx.get_basic_block().append_instruction(inst) return None elif ir.value == "mload": sym_ir = ir.args[0] - var = _get_variable_from_address(variables, sym_ir.value) if sym_ir.is_literal else None + var = ( + _get_variable_from_address(variables, int(sym_ir.value)) if sym_ir.is_literal else None + ) if var is not None: - if var.size > 32: + 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)] - ) + "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] # type: ignore + ) # type: ignore - offset = sym_ir.value - var.pos + offset = int(sym_ir.value) - var.pos if offset > 0: ptr_var = ctx.append_instruction("add", [IRLiteral(var.pos), IRLiteral(offset)]) else: ptr_var = allocated_variables[var.name] - return ctx.append_instruction("mload", [ptr_var]) + return ctx.append_instruction("mload", [ptr_var]) # type: ignore else: if sym_ir.is_literal: sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: - new_var = ctx.append_instruction("store", [IRValueBase(sym_ir.value)]) + new_var = ctx.append_instruction("store", [IRValueBase(sym_ir.value)]) # type: ignore symbols[f"&{sym_ir.value}"] = new_var if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = new_var @@ -685,7 +695,7 @@ def _convert_ir_basicblock( return sym else: if sym_ir.is_literal: - new_var = symbols.get(f"&{sym_ir.value}", None) + new_var = symbols.get(f"&{sym_ir.value}", None) # type: ignore if new_var is not None: return ctx.append_instruction("mload", [new_var]) else: @@ -693,7 +703,7 @@ def _convert_ir_basicblock( else: new_var = _convert_ir_basicblock( ctx, sym_ir, symbols, variables, allocated_variables - ) + ) # type: ignore # # Old IR gets it's return value as a reference in the stack # New IR gets it's return value in stack in case of 32 bytes or less @@ -705,60 +715,63 @@ def _convert_ir_basicblock( return ctx.append_instruction("mload", [new_var]) elif ir.value == "mstore": - sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) + sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) # type: ignore arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) - var = _get_variable_from_address(variables, sym_ir.value) if sym_ir.is_literal else None - if var is not None: - if var.size > 32: + var = ( + _get_variable_from_address(variables, int(sym_ir.value)) if sym_ir.is_literal else None + ) + + if var is not None and var.size != 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)] - ) + "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] # type: ignore + ) # type: ignore - offset = sym_ir.value - 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 = ctx.append_instruction("add", [IRLiteral(var.pos), IRLiteral(offset)]) # type: ignore else: ptr_var = allocated_variables[var.name] - return ctx.append_instruction("mstore", [arg_1, ptr_var], False) + return ctx.append_instruction("mstore", [arg_1, ptr_var], False) # type: ignore else: if sym_ir.is_literal: - new_var = ctx.append_instruction("store", [arg_1]) + new_var = ctx.append_instruction("store", [arg_1]) # type: ignore symbols[f"&{sym_ir.value}"] = new_var # if allocated_variables.get(var.name, None) is None: - allocated_variables[var.name] = new_var + allocated_variables[var.name] = new_var # type: ignore return new_var else: if sym_ir.is_literal is False: - inst = IRInstruction("mstore", [arg_1, sym_ir]) + inst = IRInstruction("mstore", [arg_1, sym_ir]) # type: ignore ctx.get_basic_block().append_instruction(inst) - return sym_ir + return None sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: - inst = IRInstruction("mstore", [arg_1, sym_ir]) + inst = IRInstruction("mstore", [arg_1, sym_ir]) # type: ignore ctx.get_basic_block().append_instruction(inst) - if not arg_1.is_literal: + if arg_1 and not arg_1.is_literal: symbols[f"&{sym_ir.value}"] = arg_1 return None if sym_ir.is_literal: - inst = IRInstruction("mstore", [arg_1, sym]) + inst = IRInstruction("mstore", [arg_1, sym]) # type: ignore ctx.get_basic_block().append_instruction(inst) return None else: - symbols[sym_ir.value] = arg_1 + symbols[sym_ir.value] = arg_1 # type: ignore return arg_1 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.append_instruction(ir.value, [arg_0]) # type: ignore 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]) + inst = IRInstruction(ir.value, [arg_1, arg_0]) # type: ignore ctx.get_basic_block().append_instruction(inst) elif ir.value == "unique_symbol": sym = ir.args[0] @@ -800,7 +813,7 @@ def emit_body_block(): counter_inc_var = ctx.get_next_variable() ret = ctx.get_next_variable() - inst = IRInstruction("store", [start], counter_var) + inst = IRInstruction("store", [start], counter_var) # type: ignore ctx.get_basic_block().append_instruction(inst) symbols[sym.value] = counter_var inst = IRInstruction("jmp", [cond_block.label]) @@ -815,7 +828,7 @@ def emit_body_block(): xor_ret = ctx.get_next_variable() cont_ret = ctx.get_next_variable() - inst = IRInstruction("xor", [ret, end], xor_ret) + inst = IRInstruction("xor", [ret, end], xor_ret) # type: ignore cond_block.append_instruction(inst) cond_block.append_instruction(IRInstruction("iszero", [xor_ret], cont_ret)) ctx.append_basic_block(cond_block) @@ -880,26 +893,26 @@ def emit_body_block(): 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.append_instruction("returndatacopy", [arg_1, size]) # type: ignore - symbols[f"&{arg_0.value}"] = new_var + symbols[f"&{arg_0.value}"] = new_var # type: ignore 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.append_instruction("selfdestruct", [arg_0], False) # type: ignore 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)) + inst = IRInstruction(ir.value, reversed(args)) # type: ignore ctx.get_basic_block().append_instruction(inst) 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: return symbols[ir.value] elif ir.is_literal: - return IRLiteral(ir.value) + return IRLiteral(ir.value) # type: ignore else: raise Exception(f"Unknown IR node: {ir}") @@ -920,7 +933,7 @@ def _convert_ir_opcode( inst_args.append( _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) ) - instruction = IRInstruction(opcode, inst_args) + instruction = IRInstruction(opcode, inst_args) # type: ignore ctx.get_basic_block().append_instruction(instruction) From 5c5ecbb030849ff92be82517d1b19dcc87c4ca76 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 13 Nov 2023 20:46:21 +0200 Subject: [PATCH 431/471] Refactor NOT_IN_STACK constant in StackModel * Turn it into object() * Add type ignore pragmas --- vyper/venom/stack_model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 21270d0e84..97e8c8b9ef 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -2,7 +2,7 @@ class StackModel: - NOT_IN_STACK = 1 + NOT_IN_STACK = object() _stack: list[IRValueBase] def __init__(self): @@ -41,7 +41,7 @@ def get_depth(self, op: IRValueBase) -> int: if stack_op.value == op.value: return -i - return StackModel.NOT_IN_STACK + return StackModel.NOT_IN_STACK # type: ignore def get_phi_depth(self, phi1: IRVariable, phi2: IRVariable) -> int: """ @@ -60,7 +60,7 @@ def get_phi_depth(self, phi1: IRVariable, phi2: IRVariable) -> int: ), f"phi argument is not unique! {phi1}, {phi2}, {self._stack}" ret = -i - return ret + return ret # type: ignore def peek(self, depth: int) -> IRValueBase: """ From d9a67e8a39ba69201aec7c135381634f31aa8652 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 16 Nov 2023 10:45:09 +0200 Subject: [PATCH 432/471] Rename Normalization to NormalizationPass --- tests/compiler/venom/test_multi_entry_block.py | 4 ++-- vyper/venom/__init__.py | 2 +- vyper/venom/passes/normalization.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/compiler/venom/test_multi_entry_block.py b/tests/compiler/venom/test_multi_entry_block.py index 5dbaab20e2..3e19fbdc24 100644 --- a/tests/compiler/venom/test_multi_entry_block.py +++ b/tests/compiler/venom/test_multi_entry_block.py @@ -1,6 +1,6 @@ from vyper.venom.basicblock import IRLiteral from vyper.venom.function import IRBasicBlock, IRFunction, IRLabel -from vyper.venom.passes.normalization import Normalization +from vyper.venom.passes.normalization import NormalizationPass def test_multi_entry_block(): @@ -32,7 +32,7 @@ def test_multi_entry_block(): assert ctx.cfg_dirty is True, "CFG should be dirty" - Normalization.run_pass(ctx) + NormalizationPass.run_pass(ctx) assert ctx.cfg_dirty is False, "CFG should be clean" assert ctx.normalized is True, "CFG should be normalized" diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 5a09f8378e..8236e9abd3 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -13,7 +13,7 @@ ) from vyper.venom.function import IRFunction from vyper.venom.ir_node_to_venom import convert_ir_basicblock -from vyper.venom.passes.constant_propagation import ir_pass_constant_propagation +from vyper.venom.passes.constant_propagation import ConstantPropagationPass from vyper.venom.passes.dft import DFTPass from vyper.venom.venom_to_assembly import VenomCompiler diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 66189272cd..17b0df919e 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -4,7 +4,7 @@ from vyper.venom.passes.base_pass import IRPass -class Normalization(IRPass): +class NormalizationPass(IRPass): """ This pass splits basic blocks when there are multiple conditional predecessors. The code generator expect a normalized CFG, that has the property that From 95c9721e78317cfb659e2f5825de2c5e9050f407 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 20 Nov 2023 15:24:11 +0200 Subject: [PATCH 433/471] small revert from feature/const_propagation changes --- 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 8236e9abd3..5a09f8378e 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -13,7 +13,7 @@ ) from vyper.venom.function import IRFunction from vyper.venom.ir_node_to_venom import convert_ir_basicblock -from vyper.venom.passes.constant_propagation import ConstantPropagationPass +from vyper.venom.passes.constant_propagation import ir_pass_constant_propagation from vyper.venom.passes.dft import DFTPass from vyper.venom.venom_to_assembly import VenomCompiler From 0eabd92929b6c8f6127ae052f6a679b9adbeb037 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 22 Nov 2023 13:17:17 -0500 Subject: [PATCH 434/471] rename IRValueBase to IRValue remove IRValueBaseValue fix a bunch of mypy --- vyper/codegen/function_definitions/common.py | 9 +- vyper/compiler/phases.py | 9 +- vyper/utils.py | 5 +- vyper/venom/analysis.py | 4 +- vyper/venom/basicblock.py | 44 +++--- vyper/venom/function.py | 8 +- vyper/venom/ir_node_to_venom.py | 139 ++++++++++--------- vyper/venom/stack_model.py | 20 +-- vyper/venom/venom_to_assembly.py | 13 +- 9 files changed, 123 insertions(+), 128 deletions(-) diff --git a/vyper/codegen/function_definitions/common.py b/vyper/codegen/function_definitions/common.py index cf1d7272c2..c48f1256c3 100644 --- a/vyper/codegen/function_definitions/common.py +++ b/vyper/codegen/function_definitions/common.py @@ -64,8 +64,7 @@ def internal_function_label(self, is_ctor_context: bool = False) -> str: class FuncIR: - common_ir: IRnode # the "common" code for the function - func_ir: IRnode # the code for the function + pass @dataclass @@ -163,9 +162,9 @@ def generate_ir_for_function( # (note: internal functions do not need to adjust gas estimate since mem_expansion_cost = calc_mem_gas(func_t._ir_info.frame_info.mem_used) # type: ignore ret.common_ir.add_gas_estimate += mem_expansion_cost # type: ignore - ret.common_ir.passthrough_metadata["func_t"] = func_t - ret.common_ir.passthrough_metadata["frame_info"] = frame_info + ret.common_ir.passthrough_metadata["func_t"] = func_t # type: ignore + ret.common_ir.passthrough_metadata["frame_info"] = frame_info # type: ignore else: - ret.func_ir.passthrough_metadata["frame_info"] = frame_info + ret.func_ir.passthrough_metadata["frame_info"] = frame_info # type: ignore return ret diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 3be1e2d25c..f5180c23c9 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -17,7 +17,6 @@ from vyper.semantics.types.function import ContractFunctionT from vyper.typing import StorageLayout from vyper.venom import generate_assembly_experimental, generate_ir -from vyper.typing import StorageLayout DEFAULT_CONTRACT_NAME = PurePath("VyperContract.vy") @@ -195,14 +194,18 @@ def function_signatures(self) -> dict[str, ContractFunctionT]: @cached_property def assembly(self) -> list: if self.experimental_codegen: - return generate_assembly_experimental(self.ir_nodes, self.settings.optimize) # type: ignore + return generate_assembly_experimental( + self.ir_nodes, self.settings.optimize # type: ignore + ) else: return generate_assembly(self.ir_nodes, self.settings.optimize) @cached_property def assembly_runtime(self) -> list: if self.experimental_codegen: - return generate_assembly_experimental(self.ir_runtime, self.settings.optimize) # type: ignore + return generate_assembly_experimental( + self.ir_runtime, self.settings.optimize # type: ignore + ) else: return generate_assembly(self.ir_runtime, self.settings.optimize) diff --git a/vyper/utils.py b/vyper/utils.py index a61162a575..0a2e1f831f 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -6,13 +6,10 @@ import time import traceback import warnings -from typing import List, Union +from typing import Generic, List, TypeVar, Union from vyper.exceptions import DecimalOverrideException, InvalidLiteral - -from typing import Generic, TypeVar - _T = TypeVar("_T") diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 3a0e72858f..b423b18a7f 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -181,7 +181,7 @@ def build_dfg(cls, ctx: IRFunction) -> "DFG": inputs = dfg._dfg_inputs.setdefault(op, []) inputs.append(inst) - for op2 in res: - dfg._dfg_outputs[op2] = inst # type: ignore + for op in res: # type: ignore + dfg._dfg_outputs[op] = inst return dfg diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 89b374e646..43eeef47cb 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -56,14 +56,11 @@ def __repr__(self) -> str: return f"\t# line {self.line_no}: {src}".expandtabs(20) -IRValueBaseValue = str | int +class IRValue: + value: int | str # maybe just Any - -class IRValueBase: - value: IRValueBaseValue - - def __init__(self, value: IRValueBaseValue) -> None: - assert isinstance(value, str) or isinstance(value, int), "value must be an IRValueBaseValue" + def __init__(self, value: int | str) -> None: + assert isinstance(value, str) or isinstance(value, int), "value must be an int | str" self.value = value @property @@ -77,13 +74,15 @@ def value_str(self) -> str: def __repr__(self) -> str: return str(self.value) +# REVIEW: consider putting IROperand into the inheritance tree: +# `IRLiteral | IRVariable`, i.e. something which can live on the operand stack -class IRLiteral(IRValueBase): +class IRLiteral(IRValue): """ IRLiteral represents a literal in IR """ - def __init__(self, value: IRValueBaseValue) -> None: + def __init__(self, value: int) -> None: super().__init__(value) @property @@ -96,7 +95,7 @@ class MemType(Enum): MEMORY = auto() -class IRVariable(IRValueBase): +class IRVariable(IRValue): """ IRVariable represents a variable in IR. A variable is a string that starts with a %. """ @@ -108,20 +107,16 @@ class IRVariable(IRValueBase): mem_addr: Optional[int] = None def __init__( - self, - value: IRValueBaseValue, - mem_type: MemType = MemType.OPERAND_STACK, - mem_addr: int = None, + self, value: str, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: int = None ) -> None: - if isinstance(value, IRLiteral): - value = value.value + assert isinstance(value, str) super().__init__(value) self.offset = 0 self.mem_type = mem_type self.mem_addr = mem_addr -class IRLabel(IRValueBase): +class IRLabel(IRValue): """ IRLabel represents a label in IR. A label is a string that starts with a %. """ @@ -145,8 +140,8 @@ class IRInstruction: opcode: str volatile: bool - operands: list[IRValueBase] - output: Optional[IRValueBase] + operands: list[IRValue] + output: Optional[IRValue] # set of live variables at this instruction liveness: OrderedSet[IRVariable] dup_requirements: OrderedSet[IRVariable] @@ -155,14 +150,11 @@ class IRInstruction: annotation: Optional[str] def __init__( - self, - opcode: str, - operands: list[IRValueBase | IRValueBaseValue], - output: Optional[IRValueBase] = None, + self, opcode: str, operands: list[IRValue | str | int], output: Optional[IRValue] = None ): self.opcode = opcode self.volatile = opcode in VOLATILE_INSTRUCTIONS - self.operands = [op if isinstance(op, IRValueBase) else IRValueBase(op) for op in operands] + self.operands = [op if isinstance(op, IRValue) else IRValue(op) for op in operands] self.output = output self.liveness = OrderedSet() self.dup_requirements = OrderedSet() @@ -176,7 +168,7 @@ def get_label_operands(self) -> list[IRLabel]: """ return [op for op in self.operands if isinstance(op, IRLabel)] - def get_non_label_operands(self) -> list[IRValueBase]: + def get_non_label_operands(self) -> list[IRValue]: """ Get input operands for instruction which are not labels """ @@ -188,7 +180,7 @@ def get_inputs(self) -> list[IRVariable]: """ return [op for op in self.operands if isinstance(op, IRVariable)] - def get_outputs(self) -> list[IRValueBase]: + def get_outputs(self) -> list[IRValue]: """ Get the output item for an instruction. (Currently all instructions output at most one item, but write diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 7c4a0f137a..4b709cad6a 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -4,7 +4,7 @@ IRBasicBlock, IRInstruction, IRLabel, - IRValueBase, + IRValue, IRVariable, MemType, ) @@ -99,8 +99,8 @@ def remove_unreachable_blocks(self) -> int: return removed def append_instruction( - self, opcode: str, args: list[IRValueBase], do_ret: bool = True - ) -> IRVariable | None: + self, opcode: str, args: list[IRValue], do_ret: bool = True + ) -> Optional[IRVariable]: """ Append instruction to last basic block. """ @@ -109,7 +109,7 @@ def append_instruction( self.get_basic_block().append_instruction(inst) return ret - def append_data(self, opcode: str, args: list[IRValueBase]) -> None: + def append_data(self, opcode: str, args: list[IRValue]) -> None: """ Append data """ diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 6a857f64b1..d5493b06c5 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,8 +1,9 @@ from typing import Optional -from vyper.codegen.context import VariableRecord +from vyper.codegen.context import VariableRecord from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes +from vyper.exceptions import CompilerPanic from vyper.ir.compile_ir import is_mem_sym, is_symbol from vyper.semantics.types.function import ContractFunctionT from vyper.utils import MemoryPositions, OrderedSet @@ -11,7 +12,7 @@ IRInstruction, IRLabel, IRLiteral, - IRValueBase, + IRValue, IRVariable, MemType, ) @@ -71,7 +72,7 @@ "balance", ] -SymbolTable = dict[str, IRValueBase] +SymbolTable = dict[str, IRValue] def _get_symbols_common(a: dict, b: dict) -> dict: @@ -148,7 +149,7 @@ def _handle_self_call( goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto return_buf = goto_ir.args[1] # return buffer - ret_args = list[Optional[IRValueBase]]([IRLabel(str(target_label))]) + ret_args = list[Optional[IRValue]]([IRLabel(str(target_label))]) for arg in args_ir: if arg.is_literal: @@ -163,13 +164,14 @@ 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]) # type: ignore + ret = ctx.append_instruction(arg.location.load_op, [ret]) ret_args.append(ret) if return_buf.is_literal: - ret_args.append(IRLiteral(return_buf.value)) + ret_args.append(IRLiteral(return_buf.value)) # type: ignore - invoke_ret = ctx.append_instruction("invoke", ret_args, func_t.return_type is not None) # type: ignore + 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 @@ -257,13 +259,14 @@ def _get_return_for_stack_operand( return IRInstruction("return", [last_ir, new_var]) # type: ignore -def _convert_ir_basicblock( - ctx: IRFunction, - ir: IRnode, - symbols: SymbolTable, - variables: OrderedSet, - allocated_variables: dict[str, IRVariable], -) -> Optional[IRValueBase]: +# def _convert_ir_basicblock( +# ctx: IRFunction, +# ir: IRnode, +# symbols: SymbolTable, +# variables: OrderedSet, +# allocated_variables: dict[str, IRVariable], +# ) -> Optional[IRValue]: +def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): assert isinstance(variables, OrderedSet) global _break_target, _continue_target @@ -364,8 +367,8 @@ def _convert_ir_basicblock( argsOffsetVar.mem_type = MemType.MEMORY argsOffsetVar.mem_addr = addr argsOffsetVar.offset = 32 - 4 if offset > 0 else 0 - else: - assert False, "unreachable" + else: # pragma: nocover + raise CompilerPanic("unreachable") else: argsOffsetVar = argsOffset @@ -374,14 +377,11 @@ def _convert_ir_basicblock( symbols[f"&{retOffsetValue}"] = retVar if ir.value == "call": - return ctx.append_instruction( - str(ir.value), - reversed([gas, address, value, argsOffsetVar, argsSize, retOffset, retSize]), # type: ignore - ) + args = reversed([gas, address, value, argsOffsetVar, argsSize, retOffset, retSize]) + return ctx.append_instruction(str(ir.value), args) else: - return ctx.append_instruction( - str(ir.value), reversed([gas, address, argsOffsetVar, argsSize, retOffset, retSize]) # type: ignore - ) + args = reversed([gas, address, argsOffsetVar, argsSize, retOffset, retSize]) + return ctx.append_instruction(str(ir.value), args) elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() @@ -400,6 +400,7 @@ def _convert_ir_basicblock( ctx, ir.args[2], else_syms, variables, allocated_variables.copy() ) if else_ret_val is not None and else_ret_val.is_literal: + assert isinstance(else_ret_val.value, int) # help mypy else_ret_val = ctx.append_instruction("store", [IRLiteral(else_ret_val.value)]) after_else_syms = else_syms.copy() @@ -411,9 +412,9 @@ def _convert_ir_basicblock( ctx, ir.args[1], symbols, variables, allocated_variables ) if then_ret_val is not None and then_ret_val.is_literal: - then_ret_val = ctx.append_instruction("store", [IRLiteral(then_ret_val.value)]) # type: ignore + then_ret_val = ctx.append_instruction("store", [IRLiteral(then_ret_val.value)]) - inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) # type: ignore + inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) current_bb.append_instruction(inst) after_then_syms = symbols.copy() @@ -477,7 +478,7 @@ def _convert_ir_basicblock( _append_jmp(ctx, IRLabel(str(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]) # type: ignore + inst = IRInstruction("jmp", [arg_1]) ctx.get_basic_block().append_instruction(inst) _new_block(ctx) elif ir.value == "set": @@ -593,29 +594,29 @@ def _convert_ir_basicblock( ) else: ptr_var = allocated_var - inst = IRInstruction("return", [last_ir, ptr_var]) # type: ignore + inst = IRInstruction("return", [last_ir, ptr_var]) else: - inst = _get_return_for_stack_operand(ctx, symbols, new_var, last_ir) # type: ignore + inst = _get_return_for_stack_operand(ctx, symbols, new_var, last_ir) else: if ret_ir and ret_ir.is_literal: sym = symbols.get(f"&{ret_ir.value}", None) if sym is None: - inst = IRInstruction("return", [last_ir, ret_ir]) # type: ignore + inst = IRInstruction("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]) # type: ignore + 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]) # type: ignore + inst = IRInstruction("return", [last_ir, new_var]) else: - inst = IRInstruction("return", [last_ir, ret_ir]) # type: ignore + inst = IRInstruction("return", [last_ir, ret_ir]) else: if last_ir and int(last_ir.value) > 32: - inst = IRInstruction("return", [last_ir, ret_ir]) # type: ignore + inst = IRInstruction("return", [last_ir, ret_ir]) else: ret_buf = IRLiteral(128) # TODO: need allocator - new_var = ctx.append_instruction("alloca", [IRLiteral(32), ret_buf]) # type: ignore - ctx.append_instruction("mstore", [ret_ir, new_var], False) # type: ignore - inst = IRInstruction("return", [last_ir, new_var]) # type: ignore + 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]) ctx.get_basic_block().append_instruction(inst) ctx.append_basic_block(IRBasicBlock(ctx.get_next_label(), ctx)) @@ -629,22 +630,22 @@ def _convert_ir_basicblock( inst = IRInstruction("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"]]) # type: ignore + inst = IRInstruction("ret", [ret_by_value, symbols["return_pc"]]) ctx.get_basic_block().append_instruction(inst) 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]) # type: ignore + inst = IRInstruction("revert", [arg_1, arg_0]) ctx.get_basic_block().append_instruction(inst) 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")]) # type: ignore + src = ctx.append_instruction("add", [arg_0, IRLabel("code_end")]) ctx.append_instruction( - "dloadbytes", [IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE)], False # type: ignore + "dloadbytes", [IRLiteral(32), src, IRLiteral(MemoryPositions.FREE_VAR_SPACE)], False ) return ctx.append_instruction("mload", [IRLiteral(MemoryPositions.FREE_VAR_SPACE)]) elif ir.value == "dloadbytes": @@ -654,9 +655,9 @@ def _convert_ir_basicblock( ) len_ = _convert_ir_basicblock(ctx, ir.args[2], symbols, variables, allocated_variables) - src = ctx.append_instruction("add", [src_offset, IRLabel("code_end")]) # type: ignore + src = ctx.append_instruction("add", [src_offset, IRLabel("code_end")]) - inst = IRInstruction("dloadbytes", [len_, src, dst]) # type: ignore + inst = IRInstruction("dloadbytes", [len_, src, dst]) ctx.get_basic_block().append_instruction(inst) return None elif ir.value == "mload": @@ -668,8 +669,8 @@ def _convert_ir_basicblock( 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)] # type: ignore - ) # type: ignore + "alloca", [IRLiteral(var.size), IRLiteral(var.pos)] + ) offset = int(sym_ir.value) - var.pos if offset > 0: @@ -677,12 +678,12 @@ def _convert_ir_basicblock( else: ptr_var = allocated_variables[var.name] - return ctx.append_instruction("mload", [ptr_var]) # type: ignore + return ctx.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 = ctx.append_instruction("store", [IRValueBase(sym_ir.value)]) # type: ignore + new_var = ctx.append_instruction("store", [IRValue(sym_ir.value)]) symbols[f"&{sym_ir.value}"] = new_var if allocated_variables.get(var.name, None) is None: allocated_variables[var.name] = new_var @@ -695,7 +696,7 @@ def _convert_ir_basicblock( return sym else: if sym_ir.is_literal: - new_var = symbols.get(f"&{sym_ir.value}", None) # type: ignore + new_var = symbols.get(f"&{sym_ir.value}", None) if new_var is not None: return ctx.append_instruction("mload", [new_var]) else: @@ -703,7 +704,7 @@ def _convert_ir_basicblock( else: new_var = _convert_ir_basicblock( ctx, sym_ir, symbols, variables, allocated_variables - ) # type: ignore + ) # # Old IR gets it's return value as a reference in the stack # New IR gets it's return value in stack in case of 32 bytes or less @@ -715,63 +716,63 @@ def _convert_ir_basicblock( return ctx.append_instruction("mload", [new_var]) elif ir.value == "mstore": - sym_ir = _convert_ir_basicblock(ctx, ir.args[0], symbols, variables, allocated_variables) # type: ignore + 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) var = ( _get_variable_from_address(variables, int(sym_ir.value)) if sym_ir.is_literal else None ) - if var is not None and var.size != None: + 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)] # type: ignore - ) # type: ignore + "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)]) # type: ignore + ptr_var = ctx.append_instruction("add", [IRLiteral(var.pos), IRLiteral(offset)]) else: ptr_var = allocated_variables[var.name] - return ctx.append_instruction("mstore", [arg_1, ptr_var], False) # type: ignore + return ctx.append_instruction("mstore", [arg_1, ptr_var], False) else: if sym_ir.is_literal: - new_var = ctx.append_instruction("store", [arg_1]) # type: ignore + new_var = ctx.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 # type: ignore + allocated_variables[var.name] = new_var return new_var else: if sym_ir.is_literal is False: - inst = IRInstruction("mstore", [arg_1, sym_ir]) # type: ignore + inst = IRInstruction("mstore", [arg_1, sym_ir]) ctx.get_basic_block().append_instruction(inst) return None sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: - inst = IRInstruction("mstore", [arg_1, sym_ir]) # type: ignore + inst = IRInstruction("mstore", [arg_1, sym_ir]) ctx.get_basic_block().append_instruction(inst) if arg_1 and not arg_1.is_literal: symbols[f"&{sym_ir.value}"] = arg_1 return None if sym_ir.is_literal: - inst = IRInstruction("mstore", [arg_1, sym]) # type: ignore + inst = IRInstruction("mstore", [arg_1, sym]) ctx.get_basic_block().append_instruction(inst) return None else: - symbols[sym_ir.value] = arg_1 # type: ignore + symbols[sym_ir.value] = arg_1 return arg_1 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]) # type: ignore + return ctx.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) - inst = IRInstruction(ir.value, [arg_1, arg_0]) # type: ignore + inst = IRInstruction(ir.value, [arg_1, arg_0]) ctx.get_basic_block().append_instruction(inst) elif ir.value == "unique_symbol": sym = ir.args[0] @@ -813,7 +814,7 @@ def emit_body_block(): counter_inc_var = ctx.get_next_variable() ret = ctx.get_next_variable() - inst = IRInstruction("store", [start], counter_var) # type: ignore + inst = IRInstruction("store", [start], counter_var) ctx.get_basic_block().append_instruction(inst) symbols[sym.value] = counter_var inst = IRInstruction("jmp", [cond_block.label]) @@ -828,7 +829,7 @@ def emit_body_block(): xor_ret = ctx.get_next_variable() cont_ret = ctx.get_next_variable() - inst = IRInstruction("xor", [ret, end], xor_ret) # type: ignore + inst = IRInstruction("xor", [ret, end], xor_ret) cond_block.append_instruction(inst) cond_block.append_instruction(IRInstruction("iszero", [xor_ret], cont_ret)) ctx.append_basic_block(cond_block) @@ -893,26 +894,26 @@ def emit_body_block(): 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]) # type: ignore + new_var = ctx.append_instruction("returndatacopy", [arg_1, size]) - symbols[f"&{arg_0.value}"] = new_var # type: ignore + 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) # type: ignore + ctx.append_instruction("selfdestruct", [arg_0], False) 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)) # type: ignore + inst = IRInstruction(ir.value, reversed(args)) ctx.get_basic_block().append_instruction(inst) 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: return symbols[ir.value] elif ir.is_literal: - return IRLiteral(ir.value) # type: ignore + return IRLiteral(ir.value) else: raise Exception(f"Unknown IR node: {ir}") diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 97e8c8b9ef..b15c6ac694 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -1,9 +1,11 @@ -from vyper.venom.basicblock import IRValueBase, IRVariable +from vyper.venom.basicblock import IRValue, IRVariable class StackModel: NOT_IN_STACK = object() - _stack: list[IRValueBase] + # REVIEW: maybe this type signature could be clearer -- we + # only have IRVariable | IRLiteral on the stack + _stack: list[IRValue] def __init__(self): self._stack = [] @@ -20,22 +22,22 @@ def height(self) -> int: """ return len(self._stack) - def push(self, op: IRValueBase) -> None: + def push(self, op: IRValue) -> None: """ Pushes an operand onto the stack map. """ - assert isinstance(op, IRValueBase), f"push takes IRValueBase, got '{op}'" + assert isinstance(op, IRValue), f"{type(op)}: {op}" self._stack.append(op) def pop(self, num: int = 1) -> None: del self._stack[len(self._stack) - num :] - def get_depth(self, op: IRValueBase) -> int: + def get_depth(self, op: IRValue) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ - assert isinstance(op, IRValueBase), f"get_depth takes IRValueBase or list, got '{op}'" + assert isinstance(op, IRValue), f"{type(op)}: {op}" for i, stack_op in enumerate(reversed(self._stack)): if stack_op.value == op.value: @@ -62,20 +64,20 @@ def get_phi_depth(self, phi1: IRVariable, phi2: IRVariable) -> int: return ret # type: ignore - def peek(self, depth: int) -> IRValueBase: + def peek(self, depth: int) -> IRValue: """ Returns the top of the stack map. """ assert depth is not StackModel.NOT_IN_STACK, "Cannot peek non-in-stack depth" return self._stack[depth - 1] - def poke(self, depth: int, op: IRValueBase) -> None: + def poke(self, depth: int, op: IRValue) -> None: """ Pokes an operand at the given depth in the stack map. """ assert depth is not StackModel.NOT_IN_STACK, "Cannot poke non-in-stack depth" assert depth <= 0, "Bad depth" - assert isinstance(op, IRValueBase), f"poke takes IRValueBase, got '{op}'" + assert isinstance(op, IRValue), f"{type(op)}: {op}" self._stack[depth - 1] = op def dup(self, depth: int) -> None: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index c6b652d228..5dd0332ff9 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,3 +1,5 @@ +from typing import Any + from vyper.ir.compile_ir import PUSH, DataHeader, RuntimeHeader, optimize_assembly from vyper.utils import MemoryPositions, OrderedSet from vyper.venom.analysis import calculate_cfg, calculate_liveness, input_vars_from @@ -5,8 +7,7 @@ IRBasicBlock, IRInstruction, IRLabel, - IRValueBase, - IRValueBaseValue, + IRValue, IRVariable, MemType, ) @@ -113,7 +114,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: asm.append(runtime) # Append data segment - data_segments = dict[IRValueBase | IRValueBaseValue, list[str | DataHeader]]() + data_segments: dict[Any, list[Any]] = dict() for inst in self.ctx.data_segment: if inst.opcode == "dbname": label = inst.operands[0].value @@ -148,7 +149,7 @@ def _stack_reorder( self.swap(assembly, stack, final_stack_depth) def _emit_input_operands( - self, assembly: list, inst: IRInstruction, ops: list[IRValueBase], stack: StackModel + self, assembly: list, inst: IRInstruction, ops: list[IRValue], stack: StackModel ) -> None: # PRE: we already have all the items on the stack that have # been scheduled to be killed. now it's just a matter of emitting @@ -162,7 +163,7 @@ def _emit_input_operands( self.swap_op(assembly, stack, op) break - emitted_ops = OrderedSet[IRValueBase]() + emitted_ops = OrderedSet[IRValue]() for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -240,7 +241,7 @@ def clean_stack_from_cfg_in( to_pop |= layout.difference(inputs) for var in to_pop: - depth = stack.get_depth(IRValueBase(var.value)) + depth = stack.get_depth(IRValue(var.value)) # don't pop phantom phi inputs if depth is StackModel.NOT_IN_STACK: continue From 29ab92b3ad198ebaea1b5fc2424be1319fc0f78d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 22 Nov 2023 21:37:35 -0800 Subject: [PATCH 435/471] add review --- vyper/venom/passes/normalization.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 17b0df919e..a36a8fe8c7 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -39,6 +39,9 @@ def _run_pass(self, ctx: IRFunction) -> int: self.dfg = DFG.build_dfg(ctx) self.changes = 0 + # REVIEW: maybe we can avoid `.cfg_dirty` machinery if we + # just recalculate the cfg every time here. is there a performance + # consideration? # Calculate control flow graph if needed if ctx.cfg_dirty: calculate_cfg(ctx) From b83fd286f21f194f959780dd20c116d5e0b91c0b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 26 Nov 2023 21:21:30 +0200 Subject: [PATCH 436/471] don't reverse on printout reverse list instead of using reversed() stack use type IROperand Refactor VenomCompiler class to use IROperand Update IRValue to IROperand in IRFunction Refactor IRValue, IRVariable classes Fix variable type in ir_node_to_venom.py remove usage of is_literal property Remove is_literal property from IRLiteral, IRVariable, and IRLabel classes Remove value_str property Fix type hinting, etc --- vyper/venom/analysis.py | 4 +- vyper/venom/basicblock.py | 77 +++++++++++++++----------------- vyper/venom/function.py | 6 +-- vyper/venom/ir_node_to_venom.py | 49 +++++++++----------- vyper/venom/stack_model.py | 18 ++++---- vyper/venom/venom_to_assembly.py | 11 ++--- 6 files changed, 78 insertions(+), 87 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index b423b18a7f..03a2d3bb43 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -38,7 +38,7 @@ def calculate_cfg(ctx: IRFunction) -> None: entry_block = ctx.basic_blocks[0] for bb in ctx.basic_blocks: - if "selector_bucket_" in bb.label.value_str or bb.label.value == "fallback": + if "selector_bucket_" in bb.label.value or bb.label.value == "fallback": bb.add_cfg_in(entry_block) for bb in ctx.basic_blocks: @@ -52,7 +52,7 @@ def calculate_cfg(ctx: IRFunction) -> None: if inst.opcode in CFG_ALTERING_OPS: ops = inst.get_label_operands() for op in ops: - ctx.get_basic_block(op.value_str).add_cfg_in(bb) + ctx.get_basic_block(op.value).add_cfg_in(bb) # Fill in the "out" set for each basic block for bb in ctx.basic_blocks: diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 43eeef47cb..2f1afcfb76 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, Optional +from typing import TYPE_CHECKING, Iterator, Optional, TypeAlias from vyper.utils import OrderedSet @@ -56,38 +56,19 @@ def __repr__(self) -> str: return f"\t# line {self.line_no}: {src}".expandtabs(20) -class IRValue: - value: int | str # maybe just Any - - def __init__(self, value: int | str) -> None: - assert isinstance(value, str) or isinstance(value, int), "value must be an int | str" - self.value = value - - @property - def is_literal(self) -> bool: - return False - - @property - def value_str(self) -> str: - return str(self.value) - - def __repr__(self) -> str: - return str(self.value) - -# REVIEW: consider putting IROperand into the inheritance tree: -# `IRLiteral | IRVariable`, i.e. something which can live on the operand stack - -class IRLiteral(IRValue): +class IRLiteral: """ IRLiteral represents a literal in IR """ + value: int + def __init__(self, value: int) -> None: - super().__init__(value) + assert isinstance(value, str) or isinstance(value, int), "value must be an int" + self.value = value - @property - def is_literal(self) -> bool: - return True + def __repr__(self) -> str: + return str(self.value) class MemType(Enum): @@ -95,11 +76,12 @@ class MemType(Enum): MEMORY = auto() -class IRVariable(IRValue): +class IRVariable: """ IRVariable represents a variable in IR. A variable is a string that starts with a %. """ + value: str offset: int = 0 # some variables can be in memory for conversion from legacy IR to venom @@ -110,13 +92,16 @@ def __init__( self, value: str, mem_type: MemType = MemType.OPERAND_STACK, mem_addr: int = None ) -> None: assert isinstance(value, str) - super().__init__(value) + self.value = value self.offset = 0 self.mem_type = mem_type self.mem_addr = mem_addr + def __repr__(self) -> str: + return str(self.value) + -class IRLabel(IRValue): +class IRLabel: """ IRLabel represents a label in IR. A label is a string that starts with a %. """ @@ -124,11 +109,19 @@ class IRLabel(IRValue): # is_symbol is used to indicate if the label came from upstream # (like a function name, try to preserve it in optimization passes) is_symbol: bool = False + value: str def __init__(self, value: str, is_symbol: bool = False) -> None: - super().__init__(value) + assert isinstance(value, str), "value must be an str" + self.value = value self.is_symbol = is_symbol + def __repr__(self) -> str: + return str(self.value) + + +IROperand: TypeAlias = IRLiteral | IRVariable | IRLabel + class IRInstruction: """ @@ -140,8 +133,8 @@ class IRInstruction: opcode: str volatile: bool - operands: list[IRValue] - output: Optional[IRValue] + operands: list[IROperand] + output: Optional[IROperand] # set of live variables at this instruction liveness: OrderedSet[IRVariable] dup_requirements: OrderedSet[IRVariable] @@ -150,11 +143,16 @@ class IRInstruction: annotation: Optional[str] def __init__( - self, opcode: str, operands: list[IRValue | str | int], output: Optional[IRValue] = None + self, + opcode: str, + operands: list[IROperand] | Iterator[IROperand], + output: Optional[IROperand] = None, ): + assert isinstance(opcode, str), "opcode must be an str" + assert isinstance(operands, list | Iterator), "operands must be a list" self.opcode = opcode self.volatile = opcode in VOLATILE_INSTRUCTIONS - self.operands = [op if isinstance(op, IRValue) else IRValue(op) for op in operands] + self.operands = [op for op in operands] # in case we get an iterator self.output = output self.liveness = OrderedSet() self.dup_requirements = OrderedSet() @@ -168,7 +166,7 @@ def get_label_operands(self) -> list[IRLabel]: """ return [op for op in self.operands if isinstance(op, IRLabel)] - def get_non_label_operands(self) -> list[IRValue]: + def get_non_label_operands(self) -> list[IROperand]: """ Get input operands for instruction which are not labels """ @@ -180,7 +178,7 @@ def get_inputs(self) -> list[IRVariable]: """ return [op for op in self.operands if isinstance(op, IRVariable)] - def get_outputs(self) -> list[IRValue]: + def get_outputs(self) -> list[IROperand]: """ Get the output item for an instruction. (Currently all instructions output at most one item, but write @@ -204,10 +202,7 @@ def __repr__(self) -> str: opcode = f"{self.opcode} " if self.opcode != "store" else "" s += opcode operands = ", ".join( - [ - (f"label %{op}" if isinstance(op, IRLabel) else str(op)) - for op in reversed(self.operands) - ] + [(f"label %{op}" if isinstance(op, IRLabel) else str(op)) for op in self.operands] ) s += operands diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 4b709cad6a..8f53fdc290 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -4,7 +4,7 @@ IRBasicBlock, IRInstruction, IRLabel, - IRValue, + IROperand, IRVariable, MemType, ) @@ -99,7 +99,7 @@ def remove_unreachable_blocks(self) -> int: return removed def append_instruction( - self, opcode: str, args: list[IRValue], do_ret: bool = True + self, opcode: str, args: list[IROperand], do_ret: bool = True ) -> Optional[IRVariable]: """ Append instruction to last basic block. @@ -109,7 +109,7 @@ def append_instruction( self.get_basic_block().append_instruction(inst) return ret - def append_data(self, opcode: str, args: list[IRValue]) -> None: + def append_data(self, opcode: str, args: list[IROperand]) -> None: """ Append data """ diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index d5493b06c5..9293434995 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -12,7 +12,7 @@ IRInstruction, IRLabel, IRLiteral, - IRValue, + IROperand, IRVariable, MemType, ) @@ -72,7 +72,7 @@ "balance", ] -SymbolTable = dict[str, IRValue] +SymbolTable = dict[str, IROperand] def _get_symbols_common(a: dict, b: dict) -> dict: @@ -149,7 +149,7 @@ def _handle_self_call( goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto return_buf = goto_ir.args[1] # return buffer - ret_args = list[Optional[IRValue]]([IRLabel(str(target_label))]) + ret_args = list[Optional[IROperand]]([IRLabel(str(target_label))]) for arg in args_ir: if arg.is_literal: @@ -244,12 +244,12 @@ def _get_variable_from_address( def _get_return_for_stack_operand( ctx: IRFunction, symbols: SymbolTable, ret_ir: IRVariable, last_ir: IRVariable ) -> IRInstruction: - if ret_ir.is_literal: + 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 else: - sym = symbols.get(ret_ir.value_str, None) + 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)]) @@ -259,13 +259,6 @@ def _get_return_for_stack_operand( return IRInstruction("return", [last_ir, new_var]) # type: ignore -# def _convert_ir_basicblock( -# ctx: IRFunction, -# ir: IRnode, -# symbols: SymbolTable, -# variables: OrderedSet, -# allocated_variables: dict[str, IRVariable], -# ) -> Optional[IRValue]: def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): assert isinstance(variables, OrderedSet) global _break_target, _continue_target @@ -357,7 +350,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ctx, ir.args[idx + 6], symbols, variables, allocated_variables ) - if argsOffset and argsOffset.is_literal: + if isinstance(argsOffset, IRLiteral): offset = int(argsOffset.value) addr = offset - 32 + 4 if offset > 0 else 0 argsOffsetVar = symbols.get(f"&{addr}", None) @@ -377,10 +370,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): symbols[f"&{retOffsetValue}"] = retVar if ir.value == "call": - args = reversed([gas, address, value, argsOffsetVar, argsSize, retOffset, retSize]) + args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] return ctx.append_instruction(str(ir.value), args) else: - args = reversed([gas, address, argsOffsetVar, argsSize, retOffset, retSize]) + args = [retSize, retOffset, argsSize, argsOffsetVar, address, gas] return ctx.append_instruction(str(ir.value), args) elif ir.value == "if": cond = ir.args[0] @@ -399,7 +392,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): else_ret_val = _convert_ir_basicblock( ctx, ir.args[2], else_syms, variables, allocated_variables.copy() ) - if else_ret_val is not None and else_ret_val.is_literal: + 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)]) after_else_syms = else_syms.copy() @@ -411,7 +404,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): then_ret_val = _convert_ir_basicblock( ctx, ir.args[1], symbols, variables, allocated_variables ) - if then_ret_val is not None and then_ret_val.is_literal: + if isinstance(then_ret_val, IRLiteral): then_ret_val = ctx.append_instruction("store", [IRLiteral(then_ret_val.value)]) inst = IRInstruction("jnz", [cont_ret, then_block.label, else_block.label]) @@ -465,7 +458,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): with_symbols = symbols.copy() sym = ir.args[0] - if ret and ret.is_literal: + if isinstance(ret, IRLiteral): new_var = ctx.append_instruction("store", [ret]) # type: ignore with_symbols[sym.value] = new_var else: @@ -495,7 +488,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): new_v = arg_0 var = ( _get_variable_from_address(variables, int(arg_0.value)) - if arg_0 and arg_0.is_literal + if isinstance(arg_0, IRLiteral) else None ) if var is not None: @@ -578,7 +571,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): var = ( _get_variable_from_address(variables, int(ret_ir.value)) - if ret_ir and ret_ir.is_literal + if isinstance(ret_ir, IRLiteral) else None ) if var is not None: @@ -598,7 +591,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): else: inst = _get_return_for_stack_operand(ctx, symbols, new_var, last_ir) else: - if ret_ir and ret_ir.is_literal: + if isinstance(ret_ir, IRLiteral): sym = symbols.get(f"&{ret_ir.value}", None) if sym is None: inst = IRInstruction("return", [last_ir, ret_ir]) @@ -683,7 +676,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if sym_ir.is_literal: sym = symbols.get(f"&{sym_ir.value}", None) if sym is None: - new_var = ctx.append_instruction("store", [IRValue(sym_ir.value)]) + new_var = ctx.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 @@ -720,7 +713,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): arg_1 = _convert_ir_basicblock(ctx, ir.args[1], symbols, variables, allocated_variables) var = ( - _get_variable_from_address(variables, int(sym_ir.value)) if sym_ir.is_literal else None + _get_variable_from_address(variables, int(sym_ir.value)) + if isinstance(sym_ir, IRLiteral) + else None ) if var is not None and var.size is not None: @@ -738,14 +733,14 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): return ctx.append_instruction("mstore", [arg_1, ptr_var], False) else: - if sym_ir.is_literal: + if isinstance(sym_ir, IRLiteral): new_var = ctx.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 sym_ir.is_literal is False: + if isinstance(sym_ir, IRLiteral) is False: inst = IRInstruction("mstore", [arg_1, sym_ir]) ctx.get_basic_block().append_instruction(inst) return None @@ -754,11 +749,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if sym is None: inst = IRInstruction("mstore", [arg_1, sym_ir]) ctx.get_basic_block().append_instruction(inst) - if arg_1 and not arg_1.is_literal: + if arg_1 and not isinstance(sym_ir, IRLiteral): symbols[f"&{sym_ir.value}"] = arg_1 return None - if sym_ir.is_literal: + if isinstance(sym_ir, IRLiteral): inst = IRInstruction("mstore", [arg_1, sym]) ctx.get_basic_block().append_instruction(inst) return None diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index b15c6ac694..6a5f21ff8c 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -1,11 +1,11 @@ -from vyper.venom.basicblock import IRValue, IRVariable +from vyper.venom.basicblock import IRLabel, IRLiteral, IROperand, IRVariable class StackModel: NOT_IN_STACK = object() # REVIEW: maybe this type signature could be clearer -- we # only have IRVariable | IRLiteral on the stack - _stack: list[IRValue] + _stack: list[IROperand] def __init__(self): self._stack = [] @@ -22,22 +22,22 @@ def height(self) -> int: """ return len(self._stack) - def push(self, op: IRValue) -> None: + def push(self, op: IROperand) -> None: """ Pushes an operand onto the stack map. """ - assert isinstance(op, IRValue), f"{type(op)}: {op}" + assert isinstance(op, IRLiteral | IRVariable | IRLabel), f"{type(op)}: {op}" self._stack.append(op) def pop(self, num: int = 1) -> None: del self._stack[len(self._stack) - num :] - def get_depth(self, op: IRValue) -> int: + def get_depth(self, op: IROperand) -> int: """ Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ - assert isinstance(op, IRValue), f"{type(op)}: {op}" + assert isinstance(op, IRLiteral | IRVariable | IRLabel), f"{type(op)}: {op}" for i, stack_op in enumerate(reversed(self._stack)): if stack_op.value == op.value: @@ -64,20 +64,20 @@ def get_phi_depth(self, phi1: IRVariable, phi2: IRVariable) -> int: return ret # type: ignore - def peek(self, depth: int) -> IRValue: + def peek(self, depth: int) -> IROperand: """ Returns the top of the stack map. """ assert depth is not StackModel.NOT_IN_STACK, "Cannot peek non-in-stack depth" return self._stack[depth - 1] - def poke(self, depth: int, op: IRValue) -> None: + def poke(self, depth: int, op: IROperand) -> None: """ Pokes an operand at the given depth in the stack map. """ assert depth is not StackModel.NOT_IN_STACK, "Cannot poke non-in-stack depth" assert depth <= 0, "Bad depth" - assert isinstance(op, IRValue), f"{type(op)}: {op}" + assert isinstance(op, IRLiteral | IRVariable | IRLabel), f"{type(op)}: {op}" self._stack[depth - 1] = op def dup(self, depth: int) -> None: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 5dd0332ff9..d4cd92cf38 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -7,7 +7,8 @@ IRBasicBlock, IRInstruction, IRLabel, - IRValue, + IRLiteral, + IROperand, IRVariable, MemType, ) @@ -149,7 +150,7 @@ def _stack_reorder( self.swap(assembly, stack, final_stack_depth) def _emit_input_operands( - self, assembly: list, inst: IRInstruction, ops: list[IRValue], stack: StackModel + self, assembly: list, inst: IRInstruction, ops: list[IROperand], stack: StackModel ) -> None: # PRE: we already have all the items on the stack that have # been scheduled to be killed. now it's just a matter of emitting @@ -163,7 +164,7 @@ def _emit_input_operands( self.swap_op(assembly, stack, op) break - emitted_ops = OrderedSet[IRValue]() + emitted_ops = OrderedSet[IROperand]() for op in ops: if isinstance(op, IRLabel): # invoke emits the actual instruction itself so we don't need to emit it here @@ -173,7 +174,7 @@ def _emit_input_operands( stack.push(op) continue - if op.is_literal: + if isinstance(op, IRLiteral): assembly.extend([*PUSH(op.value)]) stack.push(op) continue @@ -241,7 +242,7 @@ def clean_stack_from_cfg_in( to_pop |= layout.difference(inputs) for var in to_pop: - depth = stack.get_depth(IRValue(var.value)) + depth = stack.get_depth(var) # don't pop phantom phi inputs if depth is StackModel.NOT_IN_STACK: continue From 1a29685e9be1e59532309e4fe9817867855207eb Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Nov 2023 11:04:08 +0200 Subject: [PATCH 437/471] remove review comment (resolved) --- vyper/venom/stack_model.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index 6a5f21ff8c..cb71d255aa 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -3,8 +3,6 @@ class StackModel: NOT_IN_STACK = object() - # REVIEW: maybe this type signature could be clearer -- we - # only have IRVariable | IRLiteral on the stack _stack: list[IROperand] def __init__(self): From f19410382225ced284bcd02fd4ec8a3d91eb160e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Nov 2023 11:04:42 +0200 Subject: [PATCH 438/471] remove outdated review discussion --- vyper/venom/venom_to_assembly.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index d4cd92cf38..74a0e0a2f1 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -194,13 +194,6 @@ def _emit_input_operands( emitted_ops.add(op) - # REVIEW: we don't need to thread the stack through this recursion, - # because the input stack will depend on CFG traversal order. would - # be better to construct a new stack model on entry into this - # function, which uses the stack layout calculated by the CFG inputs. - # HK: I think we need to thread the stack through the recursion because - # we need to ensure that the stack is in the correct state for the next - # basic block regardless from where we came from. Let's discuss offline. def _generate_evm_for_basicblock_r( self, asm: list, basicblock: IRBasicBlock, stack: StackModel ) -> None: From 6ffb33ae354eeaaffc9cdfd5319f95b2539239c8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Nov 2023 11:11:43 +0200 Subject: [PATCH 439/471] Remove CFG tracking machinery --- vyper/venom/analysis.py | 2 -- vyper/venom/basicblock.py | 15 --------------- vyper/venom/function.py | 17 ----------------- vyper/venom/passes/normalization.py | 8 ++------ 4 files changed, 2 insertions(+), 40 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 03a2d3bb43..afccf2aafc 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -59,8 +59,6 @@ def calculate_cfg(ctx: IRFunction) -> None: for in_bb in bb.cfg_in: in_bb.add_cfg_out(bb) - ctx.cfg_dirty_clear() - def _reset_liveness(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 2f1afcfb76..8004349c04 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -245,8 +245,6 @@ class IRBasicBlock: cfg_in: OrderedSet["IRBasicBlock"] # basic blocks which this basic block can jump to cfg_out: OrderedSet["IRBasicBlock"] - # Does this basic block have a dirty cfg - cfg_dirty: bool # stack items which this basic block produces out_vars: OrderedSet[IRVariable] @@ -255,7 +253,6 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.label = label self.parent = parent self.instructions = [] - self.cfg_dirty = False self.cfg_in = OrderedSet() self.cfg_out = OrderedSet() self.out_vars = OrderedSet() @@ -285,19 +282,14 @@ def is_reachable(self) -> bool: def append_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" instruction.parent = self - if instruction.opcode in CFG_ALTERING_OPS: - self.cfg_dirty = True self.instructions.append(instruction) def insert_instruction(self, instruction: IRInstruction, index: int) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" instruction.parent = self - if instruction.opcode in CFG_ALTERING_OPS: - self.cfg_dirty = True self.instructions.insert(index, instruction) def clear_instructions(self) -> None: - self.cfg_dirty = True self.instructions = [] def replace_operands(self, replacements: dict) -> None: @@ -307,12 +299,6 @@ def replace_operands(self, replacements: dict) -> None: for instruction in self.instructions: instruction.replace_operands(replacements) - def cfg_dirty_clear(self) -> None: - """ - Clear CFG dirty flag - """ - self.cfg_dirty = False - @property def is_terminated(self) -> bool: """ @@ -327,7 +313,6 @@ def is_terminated(self) -> bool: def copy(self): bb = IRBasicBlock(self.label, self.parent) bb.instructions = self.instructions.copy() - bb.cfg_dirty = self.cfg_dirty bb.cfg_in = self.cfg_in.copy() bb.cfg_out = self.cfg_out.copy() bb.out_vars = self.out_vars.copy() diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 8f53fdc290..3f20c534ab 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -115,23 +115,6 @@ def append_data(self, opcode: str, args: list[IROperand]) -> None: """ self.data_segment.append(IRInstruction(opcode, args)) # type: ignore - @property - def cfg_dirty(self) -> bool: - """ - Check if CFG needs to be recalculated - """ - for bb in self.basic_blocks: - if bb.cfg_dirty: - return True - return False - - def cfg_dirty_clear(self) -> None: - """ - Clear CFG dirty flag - """ - for bb in self.basic_blocks: - bb.cfg_dirty_clear() - @property def normalized(self) -> bool: """ diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index a36a8fe8c7..45987d6bdd 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -39,12 +39,8 @@ def _run_pass(self, ctx: IRFunction) -> int: self.dfg = DFG.build_dfg(ctx) self.changes = 0 - # REVIEW: maybe we can avoid `.cfg_dirty` machinery if we - # just recalculate the cfg every time here. is there a performance - # consideration? - # Calculate control flow graph if needed - if ctx.cfg_dirty: - calculate_cfg(ctx) + # Ensure that the CFG is up to date + calculate_cfg(ctx) for bb in ctx.basic_blocks: if len(bb.cfg_in) > 1: From a46bf711ba3287b91dd885d63324aabd90d1dbca Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 28 Nov 2023 08:28:37 -0800 Subject: [PATCH 440/471] fix mypy --- vyper/venom/ir_node_to_venom.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 9293434995..9d3aef719e 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -117,7 +117,7 @@ def _convert_binary_op( ret = ctx.get_next_variable() - inst = IRInstruction(str(ir.value), args, ret) # type: ignore + inst = IRInstruction(ir.value, args, ret) # type: ignore ctx.get_basic_block().append_instruction(inst) return ret @@ -149,7 +149,7 @@ def _handle_self_call( goto_ir = [ir for ir in ir.args if ir.value == "goto"][0] target_label = goto_ir.args[0].value # goto return_buf = goto_ir.args[1] # return buffer - ret_args = list[Optional[IROperand]]([IRLabel(str(target_label))]) + ret_args = [IRLabel(target_label)] # type: ignore for arg in args_ir: if arg.is_literal: @@ -158,7 +158,7 @@ def _handle_self_call( ret = _convert_ir_basicblock(ctx, arg, symbols, variables, allocated_variables) ret_args.append(ret) else: - ret_args.append(sym) + ret_args.append(sym) # type: ignore else: ret = _convert_ir_basicblock( ctx, arg._optimized, symbols, variables, allocated_variables @@ -179,7 +179,7 @@ def _handle_self_call( def _handle_internal_func( ctx: IRFunction, ir: IRnode, func_t: ContractFunctionT, symbols: SymbolTable ) -> IRnode: - bb = IRBasicBlock(IRLabel(str(ir.args[0].args[0].value), True), ctx) + bb = IRBasicBlock(IRLabel(ir.args[0].args[0].value, True), ctx) # type: ignore bb = ctx.append_basic_block(bb) old_ir_mempos = 0 @@ -277,7 +277,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): elif ir.value in INVERSE_MAPPED_IR_INSTRUCTIONS: org_value = ir.value - ir.value = INVERSE_MAPPED_IR_INSTRUCTIONS[str(ir.value)] + 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]) @@ -371,10 +371,10 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if ir.value == "call": args = [retSize, retOffset, argsSize, argsOffsetVar, value, address, gas] - return ctx.append_instruction(str(ir.value), args) + return ctx.append_instruction(ir.value, args) else: args = [retSize, retOffset, argsSize, argsOffsetVar, address, gas] - return ctx.append_instruction(str(ir.value), args) + return ctx.append_instruction(ir.value, args) elif ir.value == "if": cond = ir.args[0] current_bb = ctx.get_basic_block() @@ -468,7 +468,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ctx, ir.args[2], with_symbols, variables, allocated_variables ) # body elif ir.value == "goto": - _append_jmp(ctx, IRLabel(str(ir.args[0].value))) + _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]) @@ -510,9 +510,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): ctx.append_instruction("codecopy", [size, arg_1, arg_0], False) # type: ignore elif ir.value == "symbol": - return IRLabel(str(ir.args[0].value), True) + return IRLabel(ir.args[0].value, True) elif ir.value == "data": - label = IRLabel(str(ir.args[0].value)) + label = IRLabel(ir.args[0].value) ctx.append_data("dbname", [label]) for c in ir.args[1:]: if isinstance(c, int): @@ -529,7 +529,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): inst = IRInstruction("assert", [arg_0]) # type: ignore current_bb.append_instruction(inst) elif ir.value == "label": - label = IRLabel(str(ir.args[0].value), True) + label = IRLabel(ir.args[0].value, True) if ctx.get_basic_block().is_terminated is False: inst = IRInstruction("jmp", [label]) ctx.get_basic_block().append_instruction(inst) @@ -543,7 +543,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): if func_t.is_external: # Hardcoded contructor special case if func_t.name == "__init__": - label = IRLabel(str(ir.args[0].value), True) + label = IRLabel(ir.args[0].value, True) inst = IRInstruction("jmp", [label]) ctx.get_basic_block().append_instruction(inst) return None @@ -922,7 +922,7 @@ def _convert_ir_opcode( variables: OrderedSet, allocated_variables: dict[str, IRVariable], ) -> None: - opcode = str(ir.value).upper() + opcode = ir.value.upper() # type: ignore inst_args = [] for arg in ir.args: if isinstance(arg, IRnode): From 36cce8b6eea9462d6dd10b508625c34af258c97a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 28 Nov 2023 08:33:29 -0800 Subject: [PATCH 441/471] remove some `is False` --- vyper/venom/ir_node_to_venom.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 9d3aef719e..ddf3874714 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -92,7 +92,7 @@ def convert_ir_basicblock(ir: IRnode) -> IRFunction: _convert_ir_basicblock(global_function, ir, {}, OrderedSet(), {}) for i, bb in enumerate(global_function.basic_blocks): - if bb.is_terminated is False and i < len(global_function.basic_blocks) - 1: + 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])) revert_bb = IRBasicBlock(IRLabel("__revert"), global_function) @@ -439,11 +439,11 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): IRInstruction("phi", [then_block.label, val[0], else_block.label, val[1]], ret) ) - if else_block.is_terminated is False: + if not else_block.is_terminated: exit_inst = IRInstruction("jmp", [bb.label]) else_block.append_instruction(exit_inst) - if then_block.is_terminated is False: + if not then_block.is_terminated: exit_inst = IRInstruction("jmp", [bb.label]) then_block.append_instruction(exit_inst) @@ -530,7 +530,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): current_bb.append_instruction(inst) elif ir.value == "label": label = IRLabel(ir.args[0].value, True) - if ctx.get_basic_block().is_terminated is False: + if not ctx.get_basic_block().is_terminated: inst = IRInstruction("jmp", [label]) ctx.get_basic_block().append_instruction(inst) bb = IRBasicBlock(label, ctx) @@ -740,7 +740,7 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): allocated_variables[var.name] = new_var return new_var else: - if isinstance(sym_ir, IRLiteral) is False: + if not isinstance(sym_ir, IRLiteral): inst = IRInstruction("mstore", [arg_1, sym_ir]) ctx.get_basic_block().append_instruction(inst) return None @@ -852,7 +852,7 @@ def emit_body_block(): body_block.replace_operands(replacements) body_end = ctx.get_basic_block() - if body_end.is_terminated is False: + if not body_end.is_terminated: body_end.append_instruction(IRInstruction("jmp", [jump_up_block.label])) jump_cond = IRInstruction("jmp", [increment_block.label]) From 54a2bea1618ac05996d7e24a4f9f9106631a02fa Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 28 Nov 2023 08:45:54 -0800 Subject: [PATCH 442/471] refine the inheritance tree for IRValues --- vyper/venom/basicblock.py | 34 ++++++++++++++++++++++++--------- vyper/venom/ir_node_to_venom.py | 8 +++----- vyper/venom/stack_model.py | 8 ++++---- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 8004349c04..15ca105dcc 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, Iterator, Optional, TypeAlias +from typing import TYPE_CHECKING, Any, Iterator, Optional from vyper.utils import OrderedSet @@ -56,7 +56,26 @@ def __repr__(self) -> str: return f"\t# line {self.line_no}: {src}".expandtabs(20) -class IRLiteral: +class IROperand: + """ + IROperand represents an operand in IR. An operand is anything that can + be an argument to an IRInstruction + """ + + value: Any + + +class IRValue(IROperand): + """ + IRValue represents a value in IR. A value is anything that can be + operated by non-control flow instructions. That is, IRValues can be + IRVariables or IRLiterals. + """ + + pass + + +class IRLiteral(IRValue): """ IRLiteral represents a literal in IR """ @@ -76,7 +95,7 @@ class MemType(Enum): MEMORY = auto() -class IRVariable: +class IRVariable(IRValue): """ IRVariable represents a variable in IR. A variable is a string that starts with a %. """ @@ -98,10 +117,10 @@ def __init__( self.mem_addr = mem_addr def __repr__(self) -> str: - return str(self.value) + return self.value -class IRLabel: +class IRLabel(IROperand): """ IRLabel represents a label in IR. A label is a string that starts with a %. """ @@ -117,10 +136,7 @@ def __init__(self, value: str, is_symbol: bool = False) -> None: self.is_symbol = is_symbol def __repr__(self) -> str: - return str(self.value) - - -IROperand: TypeAlias = IRLiteral | IRVariable | IRLabel + return self.value class IRInstruction: diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index ddf3874714..19bd5c8b73 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -712,11 +712,9 @@ def _convert_ir_basicblock(ctx, ir, symbols, variables, allocated_variables): 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) - var = ( - _get_variable_from_address(variables, int(sym_ir.value)) - if isinstance(sym_ir, IRLiteral) - else None - ) + var = None + if isinstance(sym_ir, IRLiteral): + var = _get_variable_from_address(variables, int(sym_ir.value)) if var is not None and var.size is not None: if var.size and var.size > 32: diff --git a/vyper/venom/stack_model.py b/vyper/venom/stack_model.py index cb71d255aa..66c62b74d2 100644 --- a/vyper/venom/stack_model.py +++ b/vyper/venom/stack_model.py @@ -1,4 +1,4 @@ -from vyper.venom.basicblock import IRLabel, IRLiteral, IROperand, IRVariable +from vyper.venom.basicblock import IROperand, IRVariable class StackModel: @@ -24,7 +24,7 @@ def push(self, op: IROperand) -> None: """ Pushes an operand onto the stack map. """ - assert isinstance(op, IRLiteral | IRVariable | IRLabel), f"{type(op)}: {op}" + assert isinstance(op, IROperand), f"{type(op)}: {op}" self._stack.append(op) def pop(self, num: int = 1) -> None: @@ -35,7 +35,7 @@ def get_depth(self, op: IROperand) -> int: Returns the depth of the first matching operand in the stack map. If the operand is not in the stack map, returns NOT_IN_STACK. """ - assert isinstance(op, IRLiteral | IRVariable | IRLabel), f"{type(op)}: {op}" + assert isinstance(op, IROperand), f"{type(op)}: {op}" for i, stack_op in enumerate(reversed(self._stack)): if stack_op.value == op.value: @@ -75,7 +75,7 @@ def poke(self, depth: int, op: IROperand) -> None: """ assert depth is not StackModel.NOT_IN_STACK, "Cannot poke non-in-stack depth" assert depth <= 0, "Bad depth" - assert isinstance(op, IRLiteral | IRVariable | IRLabel), f"{type(op)}: {op}" + assert isinstance(op, IROperand), f"{type(op)}: {op}" self._stack[depth - 1] = op def dup(self, depth: int) -> None: From b664277f8b6023d083060c0183f6698c526e4b89 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 28 Nov 2023 09:50:01 -0800 Subject: [PATCH 443/471] add sanity checks in add_cfg_in, add_cfg_out also remove union_cfg_out, union_cfg_in, remove_cfg_out, remove_cfg_in --- vyper/venom/basicblock.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 15ca105dcc..5e17b53da3 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -274,23 +274,16 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.out_vars = OrderedSet() def add_cfg_in(self, bb: "IRBasicBlock") -> None: + assert bb not in self.cfg_in # seems malformed self.cfg_in.add(bb) - def union_cfg_in(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: - self.cfg_in = self.cfg_in.union(bb_set) - - def remove_cfg_in(self, bb: "IRBasicBlock") -> None: - self.cfg_in.remove(bb) - def add_cfg_out(self, bb: "IRBasicBlock") -> None: + # malformed: jnz condition label1 label1 + # (we could handle but it makes a lot of code easier + # if we have this assumption) + assert bb not in in_bb.cfg_out self.cfg_out.add(bb) - def union_cfg_out(self, bb_set: OrderedSet["IRBasicBlock"]) -> None: - self.cfg_out = self.cfg_out.union(bb_set) - - def remove_cfg_out(self, bb: "IRBasicBlock") -> None: - self.cfg_out.remove(bb) - @property def is_reachable(self) -> bool: return len(self.cfg_in) > 0 From 38b537fb25f1402c2a411dee030444216d34b5ad Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 28 Nov 2023 09:55:42 -0800 Subject: [PATCH 444/471] review: polish and fix cfg normalization --- .../compiler/venom/test_multi_entry_block.py | 76 ++++++++++++++++--- vyper/venom/basicblock.py | 2 +- vyper/venom/function.py | 23 +++--- vyper/venom/passes/normalization.py | 43 ++++++++--- vyper/venom/venom_to_assembly.py | 2 + 5 files changed, 110 insertions(+), 36 deletions(-) diff --git a/tests/compiler/venom/test_multi_entry_block.py b/tests/compiler/venom/test_multi_entry_block.py index 3e19fbdc24..2fdd23e172 100644 --- a/tests/compiler/venom/test_multi_entry_block.py +++ b/tests/compiler/venom/test_multi_entry_block.py @@ -1,9 +1,10 @@ +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 -def test_multi_entry_block(): +def test_multi_entry_block_1(): ctx = IRFunction() finish_label = IRLabel("finish") @@ -11,31 +12,84 @@ def test_multi_entry_block(): block_1_label = IRLabel("block_1", ctx) op = ctx.append_instruction("store", [IRLiteral(10)]) - sum = ctx.append_instruction("add", [op, op]) - ctx.append_instruction("jnz", [sum, finish_label, block_1_label], False) + acc = ctx.append_instruction("add", [op, op]) + ctx.append_instruction("jnz", [acc, finish_label, block_1_label], False) - target_bb = IRBasicBlock(block_1_label, ctx) + 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) + + target_bb = IRBasicBlock(target_label, ctx) ctx.append_basic_block(target_bb) - sum = ctx.append_instruction("add", [sum, op]) + ctx.append_instruction("mul", [acc, acc]) + ctx.append_instruction("jmp", [finish_label], False) + + finish_bb = IRBasicBlock(finish_label, ctx) + ctx.append_basic_block(finish_bb) + ctx.append_instruction("stop", [], False) + + calculate_cfg(ctx) + assert not ctx.normalized, "CFG should not be normalized" + + NormalizationPass.run_pass(ctx) + + calculate_cfg(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" + + +# more complicated one +def test_multi_entry_block_2(): + ctx = IRFunction() + + finish_label = IRLabel("finish") + target_label = IRLabel("target") + 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) + + 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) + + 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", [sum, op], False) - ctx.append_instruction("jnz", [sum, finish_label, target_label], False) + 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) target_bb = IRBasicBlock(target_label, ctx) ctx.append_basic_block(target_bb) - _ = ctx.append_instruction("mul", [sum, sum]) + ctx.append_instruction("mul", [acc, acc]) ctx.append_instruction("jmp", [finish_label], False) finish_bb = IRBasicBlock(finish_label, ctx) ctx.append_basic_block(finish_bb) ctx.append_instruction("stop", [], False) - assert ctx.cfg_dirty is True, "CFG should be dirty" + calculate_cfg(ctx) + assert not ctx.normalized, "CFG should not be normalized" NormalizationPass.run_pass(ctx) - assert ctx.cfg_dirty is False, "CFG should be clean" - assert ctx.normalized is True, "CFG should be normalized" + calculate_cfg(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()) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 5e17b53da3..5cede07a49 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -281,7 +281,7 @@ def add_cfg_out(self, bb: "IRBasicBlock") -> None: # malformed: jnz condition label1 label1 # (we could handle but it makes a lot of code easier # if we have this assumption) - assert bb not in in_bb.cfg_out + assert bb not in self.cfg_out self.cfg_out.add(bb) @property diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 3f20c534ab..6d23126728 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -43,6 +43,8 @@ def append_basic_block(self, bb: IRBasicBlock) -> IRBasicBlock: assert isinstance(bb, IRBasicBlock), f"append_basic_block takes IRBasicBlock, got '{bb}'" self.basic_blocks.append(bb) + # TODO add sanity check somewhere that basic blocks have unique labels + return self.basic_blocks[-1] def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: @@ -90,8 +92,6 @@ def remove_unreachable_blocks(self) -> int: new_basic_blocks = [] for bb in self.basic_blocks: if not bb.is_reachable and bb.label.value != "global": - for bb2 in bb.cfg_out: - bb2.remove_cfg_in(bb) removed += 1 else: new_basic_blocks.append(bb) @@ -118,24 +118,21 @@ def append_data(self, opcode: str, args: list[IROperand]) -> None: @property def normalized(self) -> bool: """ - Check if function is normalized. + Check if function is normalized. A function is normalized if in the + CFG, no basic block simultaneously has multiple inputs and outputs. + That is, a basic block can be jumped to *from* multiple blocks, or it + can jump *to* multiple blocks, but it cannot simultaneously do both. + Having a normalized CFG makes calculation of stack layout easier when + emitting assembly. """ for bb in self.basic_blocks: - # Ignore if there are no multiple predecessors if len(bb.cfg_in) <= 1: continue - # Check if there is a conditional jump at the end - # of one of the predecessors for in_bb in bb.cfg_in: - jump_inst = in_bb.instructions[-1] - if jump_inst.opcode != "jnz": - continue - - # The function is not normalized - return False + if len(in_bb.cfg_out) > 1: + return False - # The function is normalized return True def copy(self): diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 45987d6bdd..db1f2270b3 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -1,5 +1,6 @@ -from vyper.venom.analysis import DFG, calculate_cfg -from vyper.venom.basicblock import IRBasicBlock, IRLabel +from vyper.exceptions import CompilerPanic +from vyper.venom.analysis import calculate_cfg +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass @@ -15,28 +16,47 @@ class NormalizationPass(IRPass): def _split_basic_block(self, bb: IRBasicBlock) -> None: ctx = self.ctx - label_base = bb.label.value # Iterate over the predecessors of the basic block for in_bb in bb.cfg_in: - jump_inst = in_bb.instructions[-1] - # We are only splitting on contitional jumps - if jump_inst.opcode != "jnz": + # We are only splitting on conditional jumps + if len(in_bb.cfg_out) < 2: continue + # sanity checks. only jnz can product len(cfg_out) > 1 + jump_inst = in_bb.instructions[-1] + assert jump_inst.opcode == "jnz" + # jnz produces cfg_out with length 2 + assert len(in_bb.cfg_out) == 2 + assert bb in in_bb.cfg_out + + # find which edge of the jnz targets this block + # jnz condition label1 label2 + jump_inst.operands[1] == jump_inst.operands[2] + 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 + # Create an intermediary basic block and append it - split_bb = IRBasicBlock(IRLabel(label_base + "_split_" + in_bb.label.value), ctx) + source = in_bb.label.value + target = bb.label.value + split_bb = IRBasicBlock(IRLabel(f"{target}_split_{source}"), ctx) + split_bb.append_instruction(IRInstruction("jmp", [bb.label])) + ctx.append_basic_block(split_bb) - ctx.append_instruction("jmp", [bb.label], False) # Redirect the original conditional jump to the intermediary basic block - jump_inst.operands[1] = split_bb.label + jump_inst.operands[edge] = split_bb.label self.changes += 1 def _run_pass(self, ctx: IRFunction) -> int: self.ctx = ctx - self.dfg = DFG.build_dfg(ctx) self.changes = 0 # Ensure that the CFG is up to date @@ -47,9 +67,10 @@ def _run_pass(self, ctx: IRFunction) -> int: self._split_basic_block(bb) # Recalculate control flow graph + # (perf: could do this only when self.changes > 0, but be paranoid) calculate_cfg(ctx) # Sanity check - assert ctx.normalized is True, "Normalization pass failed" + assert ctx.normalized, "Normalization pass failed" return self.changes diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 74a0e0a2f1..64d3143e81 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -101,6 +101,8 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: calculate_cfg(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) # Append postambles From bcda7a4154ba10a6c3046763251dcd89fdc93082 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 28 Nov 2023 09:56:56 -0800 Subject: [PATCH 445/471] disable experimental codegen by default --- vyper/compiler/phases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index f5180c23c9..4e32812fee 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -84,7 +84,7 @@ def __init__( Use experimental codegen. Defaults to False """ # to force experimental codegen, uncomment: - experimental_codegen = True + # experimental_codegen = True self.contract_path = contract_path self.source_code = source_code self.source_id = source_id From 72b4b45f983ec0a4703791ea9edce8b2045e8c31 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 28 Nov 2023 09:59:39 -0800 Subject: [PATCH 446/471] add a note --- vyper/venom/basicblock.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 5cede07a49..f81de04b9d 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -145,6 +145,8 @@ class IRInstruction: operands, and return value. For example, the following IR instruction: %1 = add %0, 1 has opcode "add", operands ["%0", "1"], and return value "%1". + + Convention: the rightmost value is the top of the stack. """ opcode: str From ec0a0ed848a454d0265b9cebf78f1f0a7ed44617 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 Nov 2023 14:22:56 -0800 Subject: [PATCH 447/471] add readme (mostly written by harkal) --- vyper/venom/README.md | 162 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 vyper/venom/README.md diff --git a/vyper/venom/README.md b/vyper/venom/README.md new file mode 100644 index 0000000000..a76d326b1b --- /dev/null +++ b/vyper/venom/README.md @@ -0,0 +1,162 @@ +## Venom - An Intermediate representation language for Vyper + +### Introduction + +Venom serves as the next-gen intermediate representation language specifically tailored for use with the Vyper smart contract compiler. Drawing inspiration from LLVM IR, Venom has been adapted to be simpler, and to be architected towards emitting code for stack-based virtual machines. Designed with a Single Static Assignment (SSA) form, Venom allows for sophisticated analysis and optimizations, while accommodating the idiosyncrasies of the EVM architecture. + +### Venom Form + +In Venom, values are denoted as strings commencing with the `'%'` character, referred to as variables. Variables can only be assigned to at declaration (they remain immutable post-assignment). Constants are represented as decimal or hex numbers. + +Reserved words include all the instruction opcodes and 'IRFunction', 'param', 'dbname' and 'db'. + +Any content following the ';' character until the line end is treated as a comment. + +For instance, an example of incrementing a variable by one is as follows: + +```llvm +%sum = add %x, 1 ; Add one to x +``` + +Each instruction is identified by its opcode and a list of input operands. In cases where an instruction produces a result, it is stored in a new variable, as indicated on the left side of the assignment character. + +Code is organized into non-branching instruction blocks, known as _"Basic Blocks"_. Each basic block is defined by a label and contains its set of instructions. The final instruction of a basic block should either be a terminating instruction or a jump (conditional or unconditional) to other block(s). + +Basic blocks are grouped into _functions_ that are named and dictate the first block to execute. + +Venom employs two scopes: global and function level. + +### Example code + +```llvm +IRFunction: global + +global: + %1 = calldataload 0 + %2 = shr 224, %1 + jmp label %selector_bucket_0 + +selector_bucket_0: + %3 = xor %2, 1579456981 + %4 = iszero %3 + jnz label %1, label %2, %4 + +1: IN=[selector_bucket_0] OUT=[9] + jmp label %fallback + +2: + %5 = callvalue + %6 = calldatasize + %7 = lt %6, 164 + %8 = or %5, %7 + %9 = iszero %8 + assert %9 + stop + +fallback: + revert 0, 0 +``` + +### Grammar + +Below is a (not-so-complete) grammar to describe the text format of Venom IR: + +```llvm +program ::= function_declaration* + +function_declaration ::= "IRFunction:" identifier input_list? output_list? "=>" block + +input_list ::= "IN=" "[" (identifier ("," identifier)*)? "]" +output_list ::= "OUT=" "[" (identifier ("," identifier)*)? "]" + +block ::= label ":" input_list? output_list? "=>{" operation* "}" + +operation ::= "%" identifier "=" opcode operand ("," operand)* + | opcode operand ("," operand)* + +opcode ::= "calldataload" | "shr" | "shl" | "and" | "add" | "codecopy" | "mload" | "jmp" | "xor" | "iszero" | "jnz" | "label" | "lt" | "or" | "assert" | "callvalue" | "calldatasize" | "alloca" | "calldatacopy" | "invoke" | "gt" | ... + +operand ::= "%" identifier | label | integer | "label" "%" identifier +label ::= "%" identifier + +identifier ::= [a-zA-Z_][a-zA-Z0-9_]* +integer ::= [0-9]+ +``` + +## Implementation + +In the current implementation the compiler was extended to incorporate a new pass responsible for translating the original s-expr based IR into Venom. Subsequently, the generated Venom code undegoes processing by the actual Venom compiler, ultimately converting it to assembly code. That final assembly code is the passed to the original assembler of Vyper to produce the executable bytecode. + +Currently there is no implementation of the text format (that is, there is no front-end), although this is planned. At this time, Venom IR can only be constructed programmatically. + +## Architecture + +The Venom implementation is composed of several distinct passes that iteratively transform and optimize the Venom IR code until it reaches the assembly emitter, which produces the stack-based EVM assembly. + +These passes encompass generic transformations that streamline the code (such as dead code elimination and normalization), as well as those generating supplementary information about the code, like liveness analysis and control-flow graph (CFG) construction. Some passes may rely on the output of others, requiring a specific execution order. For instance, the code emitter expects the execution of a normalization pass preceding it, and this normalization pass, in turn, requires the augmentation of the Venom IR with code flow information. + +The primary categorization of pass types are: + +- Transformation passes +- Analysis/augmentation passes +- Optimization passes + +## Currently implemented passes + +The Venom compiler currently implements the following passes. + +### Control Flow Graph calculation + +The compiler generates a fundamental data structure known as the Control Flow Graph (CFG). This graph illustrates the interconnections between basic blocks, serving as a foundational data structure upon which many subsequent passes depend. + +### Data Flow Graph calculation + +To enable the compiler to analyze the movement of data through the code during execution, a specialized graph, the Dataflow Graph (DFG), is generated. The compiler inspects the code, determining where each variable is defined (in one location) and all the places where it is utilized. + +### Dataflow Transformation + +This pass depends on the DFG construction, and reorders variable declarations to try to reduce stack traffic during instruction selection. + +### Liveness analysis + +This pass conducts a dataflow analysis, utilizing information from previous passes to identify variables that are live at each instruction in the Venom IR code. A variable is deemed live at a particular instruction if it holds a value necessary for future operations. Variables only alive for their assignment instructions are identified here and then eliminated by the dead code elimination pass. + +### Dead code elimination + +This pass eliminates all basic blocks that are not reachable from any other basic block, leveraging the CFG. + +### Normalization + +A Venom program may feature basic blocks with multiple CFG inputs and outputs. This currently can occur when multiple blocks direct control to the same target basic block, and the target basic block concludes with a conditional jump. We define a Venom IR as "normalized" when it contains no basic blocks that have multiple inputs and outputs. The normalization transform pass is responsible for converting any Venom IR program to its normalized form. EVM assembly emission operates solely on normalized Venom programs, because the stack layout is not well defined for non-normalized basic blocks. + +### Code emission + +This final pass of the compiler aims to emit EVM assembly recognized by Vyper's assembler. It calcluates the desired stack layout for every basic block, schedules items on the stack and selects instructions. It ensures that deploy code, runtime code, and data segments are arranged according to the assembler's expectations. + +## Future planned passes + +A number of passes that are planned to be implemented, or are implemented for immediately after the initial PR merge are below. + +### Constant folding + +### Instruction combination + +### Dead store elimination + +### Scalar evolution + +### Loop invariant code motion + +### Loop unrolling + +### Code sinking + +### Expression reassociation + +### Stack to mem + +### Mem to stack + +### Function inlining + +### Load-store elimination From 267cb2ac5e4fbe0b4b53633d20e2b90ceca3f4da Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 Nov 2023 14:28:31 -0800 Subject: [PATCH 448/471] readme formatting --- vyper/venom/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vyper/venom/README.md b/vyper/venom/README.md index a76d326b1b..b1edf178df 100644 --- a/vyper/venom/README.md +++ b/vyper/venom/README.md @@ -6,11 +6,11 @@ Venom serves as the next-gen intermediate representation language specifically t ### Venom Form -In Venom, values are denoted as strings commencing with the `'%'` character, referred to as variables. Variables can only be assigned to at declaration (they remain immutable post-assignment). Constants are represented as decimal or hex numbers. +In Venom, values are denoted as strings commencing with the `'%'` character, referred to as variables. Variables can only be assigned to at declaration (they remain immutable post-assignment). Constants are represented as decimal numbers (hexadecimal may be added in the future). -Reserved words include all the instruction opcodes and 'IRFunction', 'param', 'dbname' and 'db'. +Reserved words include all the instruction opcodes and `'IRFunction'`, `'param'`, `'dbname'` and `'db'`. -Any content following the ';' character until the line end is treated as a comment. +Any content following the `';'` character until the line end is treated as a comment. For instance, an example of incrementing a variable by one is as follows: From 632e554f8f22f75d6fa9f8c7d62100e0b560e3e1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 00:41:07 +0200 Subject: [PATCH 449/471] Add experimental codegen flag to vyper_compile.py and __init__.py --- vyper/cli/vyper_compile.py | 1 + vyper/compiler/__init__.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/vyper/cli/vyper_compile.py b/vyper/cli/vyper_compile.py index ba909216da..ca1792384e 100755 --- a/vyper/cli/vyper_compile.py +++ b/vyper/cli/vyper_compile.py @@ -282,6 +282,7 @@ 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 62ea05b243..61d7a7c229 100644 --- a/vyper/compiler/__init__.py +++ b/vyper/compiler/__init__.py @@ -55,6 +55,7 @@ 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. @@ -104,6 +105,7 @@ def compile_code( storage_layout_override, show_gas_estimates, no_bytecode_metadata, + experimental_codegen, ) ret = {} From 04d425b1dd22e47465ff8f17c1742381f0a19ad9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 00:41:31 +0200 Subject: [PATCH 450/471] Commented out liveness output in IRInstruction class --- vyper/venom/basicblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index f81de04b9d..33292505bd 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -227,8 +227,8 @@ def __repr__(self) -> str: if self.annotation: s += f" <{self.annotation}>" - if self.liveness: - return f"{s: <30} # {self.liveness}" + # if self.liveness: + # return f"{s: <30} # {self.liveness}" return s From 1d8059a4c58b3aad0a7b30083220b988c457a10c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 01:10:27 +0200 Subject: [PATCH 451/471] Use normalization pass --- vyper/venom/venom_to_assembly.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 64d3143e81..75b2f963a8 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -13,6 +13,7 @@ MemType, ) from vyper.venom.function import IRFunction +from vyper.venom.passes.normalization import NormalizationPass from vyper.venom.stack_model import StackModel # instructions which map one-to-one from venom to EVM @@ -98,6 +99,8 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: stack = StackModel() asm: list[str] = [] + NormalizationPass.run_pass(self.ctx) + calculate_cfg(self.ctx) calculate_liveness(self.ctx) From db768dd9604b3c722773e50c1e4018536b4fd65e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 Nov 2023 15:45:57 -0800 Subject: [PATCH 452/471] add note on pluggability --- vyper/venom/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/README.md b/vyper/venom/README.md index b1edf178df..6728d603dd 100644 --- a/vyper/venom/README.md +++ b/vyper/venom/README.md @@ -85,13 +85,13 @@ integer ::= [0-9]+ ## Implementation -In the current implementation the compiler was extended to incorporate a new pass responsible for translating the original s-expr based IR into Venom. Subsequently, the generated Venom code undegoes processing by the actual Venom compiler, ultimately converting it to assembly code. That final assembly code is the passed to the original assembler of Vyper to produce the executable bytecode. +In the current implementation the compiler was extended to incorporate a new pass responsible for translating the original s-expr based IR into Venom. Subsequently, the generated Venom code undergoes processing by the actual Venom compiler, ultimately converting it to assembly code. That final assembly code is then passed to the original assembler of Vyper to produce the executable bytecode. Currently there is no implementation of the text format (that is, there is no front-end), although this is planned. At this time, Venom IR can only be constructed programmatically. ## Architecture -The Venom implementation is composed of several distinct passes that iteratively transform and optimize the Venom IR code until it reaches the assembly emitter, which produces the stack-based EVM assembly. +The Venom implementation is composed of several distinct passes that iteratively transform and optimize the Venom IR code until it reaches the assembly emitter, which produces the stack-based EVM assembly. The compiler is designed to be more-or-less pluggable, so passes can be written without too much knowledge of or dependency on other passes. These passes encompass generic transformations that streamline the code (such as dead code elimination and normalization), as well as those generating supplementary information about the code, like liveness analysis and control-flow graph (CFG) construction. Some passes may rely on the output of others, requiring a specific execution order. For instance, the code emitter expects the execution of a normalization pass preceding it, and this normalization pass, in turn, requires the augmentation of the Venom IR with code flow information. From 73399fce80b4cc9ce4ef696c7e9a0f8f1ed18de5 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 Nov 2023 17:26:14 -0800 Subject: [PATCH 453/471] reword normalization pass explanation --- vyper/venom/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/README.md b/vyper/venom/README.md index 6728d603dd..a81f6c0582 100644 --- a/vyper/venom/README.md +++ b/vyper/venom/README.md @@ -127,7 +127,7 @@ This pass eliminates all basic blocks that are not reachable from any other basi ### Normalization -A Venom program may feature basic blocks with multiple CFG inputs and outputs. This currently can occur when multiple blocks direct control to the same target basic block, and the target basic block concludes with a conditional jump. We define a Venom IR as "normalized" when it contains no basic blocks that have multiple inputs and outputs. The normalization transform pass is responsible for converting any Venom IR program to its normalized form. EVM assembly emission operates solely on normalized Venom programs, because the stack layout is not well defined for non-normalized basic blocks. +A Venom program may feature basic blocks with multiple CFG inputs and outputs. This currently can occur when multiple blocks conditionally direct control to the same target basic block. We define a Venom IR as "normalized" when it contains no basic blocks that have multiple inputs and outputs. The normalization pass is responsible for converting any Venom IR program to its normalized form. EVM assembly emission operates solely on normalized Venom programs, because the stack layout is not well defined for non-normalized basic blocks. ### Code emission From 234c89c62bb25c5db0960fb1fa2c628f69ab5a0f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 12:09:56 +0200 Subject: [PATCH 454/471] Add comment for special case of jump to selector buckets and fallback --- vyper/venom/analysis.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index afccf2aafc..0a5fdfe985 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -37,6 +37,9 @@ def calculate_cfg(ctx: IRFunction) -> None: else: entry_block = ctx.basic_blocks[0] + # Special case for the jump table of selector buckets + # and fallback. It will be generalized when the + # dispacher code is directly generated in Venom for bb in ctx.basic_blocks: if "selector_bucket_" in bb.label.value or bb.label.value == "fallback": bb.add_cfg_in(entry_block) From 2109bb2e13f3ad8e016826625c04fecb972b5eba Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 13:11:00 +0200 Subject: [PATCH 455/471] refactor starting point --- vyper/venom/passes/normalization.py | 56 ++++++++++++++++------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index db1f2270b3..3b7c10061f 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -1,6 +1,6 @@ from vyper.exceptions import CompilerPanic from vyper.venom.analysis import calculate_cfg -from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass @@ -20,40 +20,48 @@ def _split_basic_block(self, bb: IRBasicBlock) -> None: # Iterate over the predecessors of the basic block for in_bb in bb.cfg_in: # We are only splitting on conditional jumps - if len(in_bb.cfg_out) < 2: + if len(in_bb.cfg_out) <= 1: continue - # sanity checks. only jnz can product len(cfg_out) > 1 jump_inst = in_bb.instructions[-1] - assert jump_inst.opcode == "jnz" - # jnz produces cfg_out with length 2 - assert len(in_bb.cfg_out) == 2 assert bb in in_bb.cfg_out # find which edge of the jnz targets this block # jnz condition label1 label2 - jump_inst.operands[1] == jump_inst.operands[2] - for i, op in enumerate(jump_inst.operands): - if op == bb.label: - edge = i - break + 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) else: - # none of the edges points to this bb - raise CompilerPanic("bad CFG") - assert edge in (1, 2) # the arguments which can be labels + raise CompilerPanic("Unexpected termination instruction during normalization") - # 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}"), ctx) - split_bb.append_instruction(IRInstruction("jmp", [bb.label])) + self.changes += 1 - ctx.append_basic_block(split_bb) + 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") - # Redirect the original conditional jump to the intermediary basic block - jump_inst.operands[edge] = split_bb.label + assert edge in (1, 2) # the arguments which can be labels - self.changes += 1 + # 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_bb.append_instruction(IRInstruction("jmp", [bb.label])) + + self.ctx.append_basic_block(split_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: + pass def _run_pass(self, ctx: IRFunction) -> int: self.ctx = ctx @@ -63,7 +71,7 @@ def _run_pass(self, ctx: IRFunction) -> int: calculate_cfg(ctx) for bb in ctx.basic_blocks: - if len(bb.cfg_in) > 1: + if len(bb.cfg_in) > 1: # and len(bb.cfg_out) > 1: self._split_basic_block(bb) # Recalculate control flow graph From e1a16268fbe2aefa243d4f8aa656ba0d030f266c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 14:09:27 +0200 Subject: [PATCH 456/471] Dont throw in get_basic_block when bb not found --- vyper/venom/function.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 6d23126728..72ef72f702 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -47,7 +47,7 @@ def append_basic_block(self, bb: IRBasicBlock) -> IRBasicBlock: return self.basic_blocks[-1] - def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: + def get_basic_block(self, label: Optional[str] = None) -> Optional[IRBasicBlock]: """ Get basic block by label. If label is None, return the last basic block. @@ -57,7 +57,7 @@ def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: for bb in self.basic_blocks: if bb.label.value == label: return bb - raise AssertionError(f"Basic block '{label}' not found") + return None def get_basic_block_after(self, label: IRLabel) -> IRBasicBlock: """ From 9af2c84fb4fa1e898bf809529a3d6d9b2499ee59 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 14:15:51 +0200 Subject: [PATCH 457/471] Normalize basic blocks and split multiple incoming edges --- vyper/venom/passes/normalization.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 3b7c10061f..f24b02f96b 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -14,6 +14,22 @@ class NormalizationPass(IRPass): changes = 0 + def _normalize_basic_block(self, bb: IRBasicBlock) -> None: + for out_bb in bb.cfg_out: + if len(out_bb.cfg_in) < 2: + continue + + split_label = IRLabel(f"{bb.label.value}_split_{out_bb.label.value}") + new_out_bb = self.ctx.get_basic_block(split_label.value) + if new_out_bb is None: + new_out_bb = IRBasicBlock( + IRLabel(f"{bb.label.value}_split_{out_bb.label.value}"), self.ctx + ) + new_out_bb.instructions = out_bb.instructions + self.ctx.append_basic_block(new_out_bb) + + out_bb.instructions = [IRInstruction("jmp", [new_out_bb.label])] + def _split_basic_block(self, bb: IRBasicBlock) -> None: ctx = self.ctx @@ -69,15 +85,20 @@ def _run_pass(self, ctx: IRFunction) -> int: # Ensure that the CFG is up to date calculate_cfg(ctx) + print(ctx) + # for bb in ctx.basic_blocks: + # if len(bb.cfg_out) > 1: + # self._normalize_basic_block(bb) for bb in ctx.basic_blocks: - if len(bb.cfg_in) > 1: # and len(bb.cfg_out) > 1: + if len(bb.cfg_in) > 1: self._split_basic_block(bb) # Recalculate control flow graph # (perf: could do this only when self.changes > 0, but be paranoid) calculate_cfg(ctx) - + print("--------------------------") + print(ctx) # Sanity check assert ctx.normalized, "Normalization pass failed" From a50dc7ec31a1d06e34c137a307190772eb587e6b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 15:03:07 +0200 Subject: [PATCH 458/471] Add adding and removing cfg_in and cfg_out in IRBasicBlock --- vyper/venom/basicblock.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 33292505bd..c4bac9ff22 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -279,6 +279,10 @@ def add_cfg_in(self, bb: "IRBasicBlock") -> None: assert bb not in self.cfg_in # seems malformed self.cfg_in.add(bb) + def remove_cfg_in(self, bb: "IRBasicBlock") -> None: + assert bb in self.cfg_in + self.cfg_in.remove(bb) + def add_cfg_out(self, bb: "IRBasicBlock") -> None: # malformed: jnz condition label1 label1 # (we could handle but it makes a lot of code easier @@ -286,6 +290,10 @@ def add_cfg_out(self, bb: "IRBasicBlock") -> None: assert bb not in self.cfg_out self.cfg_out.add(bb) + def remove_cfg_out(self, bb: "IRBasicBlock") -> None: + assert bb in self.cfg_out + self.cfg_out.remove(bb) + @property def is_reachable(self) -> bool: return len(self.cfg_in) > 0 From 765044d69918e08000225f9a2617f92510372e6e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 15:03:53 +0200 Subject: [PATCH 459/471] Fix dynamic jump normalization --- vyper/venom/passes/normalization.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index f24b02f96b..8456ae524e 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -34,7 +34,7 @@ def _split_basic_block(self, bb: IRBasicBlock) -> None: ctx = self.ctx # Iterate over the predecessors of the basic block - for in_bb in bb.cfg_in: + for in_bb in list(bb.cfg_in): # We are only splitting on conditional jumps if len(in_bb.cfg_out) <= 1: continue @@ -70,21 +70,37 @@ def _split_for_static_branch(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> Non target = bb.label.value split_bb = IRBasicBlock(IRLabel(f"{target}_split_{source}"), self.ctx) split_bb.append_instruction(IRInstruction("jmp", [bb.label])) - self.ctx.append_basic_block(split_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: - pass + in_bb.remove_cfg_out(bb) + + # 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_bb.append_instruction(IRInstruction("jmp", [bb.label])) + self.ctx.append_basic_block(split_bb) + + split_bb.add_cfg_in(in_bb) + split_bb.add_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 def _run_pass(self, ctx: IRFunction) -> int: self.ctx = ctx self.changes = 0 # Ensure that the CFG is up to date - calculate_cfg(ctx) + # calculate_cfg(ctx) print(ctx) # for bb in ctx.basic_blocks: # if len(bb.cfg_out) > 1: @@ -96,7 +112,7 @@ def _run_pass(self, ctx: IRFunction) -> int: # Recalculate control flow graph # (perf: could do this only when self.changes > 0, but be paranoid) - calculate_cfg(ctx) + # calculate_cfg(ctx) print("--------------------------") print(ctx) # Sanity check From ac5b283407804ee4e1a5c70a758d49c1e105fa21 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 15:09:44 +0200 Subject: [PATCH 460/471] Fix cfg calculation, normalization and liveness calculation order --- vyper/venom/venom_to_assembly.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 75b2f963a8..d0fe85f58d 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -99,9 +99,15 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: stack = StackModel() asm: list[str] = [] - NormalizationPass.run_pass(self.ctx) - + # Before emitting the assembly, we need to make sure that the + # CFG is normalized. Calling calculate_cfg() will denormalize IR (reset) + # so it should not be called after calling NormalizationPass.run_pass(). + # Liveness is then computed for the normalized IR, and we can proceed to + # 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!" From fc06f0242de89605e9e8d64e46b5e6ed0346ba0d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 15:13:23 +0200 Subject: [PATCH 461/471] Remove unused code --- vyper/venom/passes/normalization.py | 33 +++-------------------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 8456ae524e..c04e61a9af 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -14,22 +14,6 @@ class NormalizationPass(IRPass): changes = 0 - def _normalize_basic_block(self, bb: IRBasicBlock) -> None: - for out_bb in bb.cfg_out: - if len(out_bb.cfg_in) < 2: - continue - - split_label = IRLabel(f"{bb.label.value}_split_{out_bb.label.value}") - new_out_bb = self.ctx.get_basic_block(split_label.value) - if new_out_bb is None: - new_out_bb = IRBasicBlock( - IRLabel(f"{bb.label.value}_split_{out_bb.label.value}"), self.ctx - ) - new_out_bb.instructions = out_bb.instructions - self.ctx.append_basic_block(new_out_bb) - - out_bb.instructions = [IRInstruction("jmp", [new_out_bb.label])] - def _split_basic_block(self, bb: IRBasicBlock) -> None: ctx = self.ctx @@ -42,8 +26,7 @@ def _split_basic_block(self, bb: IRBasicBlock) -> None: jump_inst = in_bb.instructions[-1] assert bb in in_bb.cfg_out - # find which edge of the jnz targets this block - # jnz condition label1 label2 + # 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): @@ -85,12 +68,14 @@ def _split_for_dynamic_branch(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> No split_bb.append_instruction(IRInstruction("jmp", [bb.label])) self.ctx.append_basic_block(split_bb) + # Rewire the CFG split_bb.add_cfg_in(in_bb) split_bb.add_cfg_out(bb) in_bb.add_cfg_out(split_bb) bb.remove_cfg_in(in_bb) bb.add_cfg_in(split_bb) + # Update any affected 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 @@ -99,22 +84,10 @@ def _run_pass(self, ctx: IRFunction) -> int: self.ctx = ctx self.changes = 0 - # Ensure that the CFG is up to date - # calculate_cfg(ctx) - print(ctx) - # for bb in ctx.basic_blocks: - # if len(bb.cfg_out) > 1: - # self._normalize_basic_block(bb) - for bb in ctx.basic_blocks: if len(bb.cfg_in) > 1: self._split_basic_block(bb) - # Recalculate control flow graph - # (perf: could do this only when self.changes > 0, but be paranoid) - # calculate_cfg(ctx) - print("--------------------------") - print(ctx) # Sanity check assert ctx.normalized, "Normalization pass failed" From b3f620b7f603a5779dcc0d84acb4f4af76260668 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 15:16:58 +0200 Subject: [PATCH 462/471] Revert get_basic_block method changes --- vyper/venom/function.py | 4 ++-- vyper/venom/passes/normalization.py | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 72ef72f702..6d23126728 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -47,7 +47,7 @@ def append_basic_block(self, bb: IRBasicBlock) -> IRBasicBlock: return self.basic_blocks[-1] - def get_basic_block(self, label: Optional[str] = None) -> Optional[IRBasicBlock]: + def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: """ Get basic block by label. If label is None, return the last basic block. @@ -57,7 +57,7 @@ def get_basic_block(self, label: Optional[str] = None) -> Optional[IRBasicBlock] for bb in self.basic_blocks: if bb.label.value == label: return bb - return None + raise AssertionError(f"Basic block '{label}' not found") def get_basic_block_after(self, label: IRLabel) -> IRBasicBlock: """ diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index c04e61a9af..860f6da309 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -1,5 +1,4 @@ from vyper.exceptions import CompilerPanic -from vyper.venom.analysis import calculate_cfg from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass @@ -15,8 +14,6 @@ class NormalizationPass(IRPass): changes = 0 def _split_basic_block(self, bb: IRBasicBlock) -> None: - ctx = self.ctx - # Iterate over the predecessors of the basic block for in_bb in list(bb.cfg_in): # We are only splitting on conditional jumps From ac592011ed8f286ff29233cb50745745b0e3057d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 15:33:50 +0200 Subject: [PATCH 463/471] fix multi entry test --- tests/compiler/venom/test_multi_entry_block.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/compiler/venom/test_multi_entry_block.py b/tests/compiler/venom/test_multi_entry_block.py index 2fdd23e172..bb57fa1065 100644 --- a/tests/compiler/venom/test_multi_entry_block.py +++ b/tests/compiler/venom/test_multi_entry_block.py @@ -36,7 +36,6 @@ def test_multi_entry_block_1(): NormalizationPass.run_pass(ctx) - calculate_cfg(ctx) assert ctx.normalized, "CFG should be normalized" finish_bb = ctx.get_basic_block(finish_label.value) @@ -88,7 +87,6 @@ def test_multi_entry_block_2(): NormalizationPass.run_pass(ctx) - calculate_cfg(ctx) assert ctx.normalized, "CFG should be normalized" finish_bb = ctx.get_basic_block(finish_label.value) From 600505e9e24ce36c90f8cc0b27a0ee199d292566 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 15:35:06 +0200 Subject: [PATCH 464/471] Refactor NormalizationPass --- vyper/venom/passes/normalization.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 860f6da309..ee96f26702 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -45,19 +45,20 @@ def _split_for_static_branch(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> Non assert edge in (1, 2) # the arguments which can be labels - # 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_bb.append_instruction(IRInstruction("jmp", [bb.label])) - self.ctx.append_basic_block(split_bb) + 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: - in_bb.remove_cfg_out(bb) + split_bb = self._insert_split_basicblock(bb, in_bb) + + # Update any affected 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 + 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 @@ -68,14 +69,11 @@ def _split_for_dynamic_branch(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> No # Rewire the 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) - - # Update any affected 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 + return split_bb def _run_pass(self, ctx: IRFunction) -> int: self.ctx = ctx From 8a7665818949e9cac1460eaf33f85089e3a2ad5e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 16:00:23 +0200 Subject: [PATCH 465/471] Remove assert guarding against adding existing --- vyper/venom/basicblock.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index c4bac9ff22..b95d7416ca 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -276,7 +276,6 @@ def __init__(self, label: IRLabel, parent: "IRFunction") -> None: self.out_vars = OrderedSet() def add_cfg_in(self, bb: "IRBasicBlock") -> None: - assert bb not in self.cfg_in # seems malformed self.cfg_in.add(bb) def remove_cfg_in(self, bb: "IRBasicBlock") -> None: @@ -287,7 +286,6 @@ def add_cfg_out(self, bb: "IRBasicBlock") -> None: # malformed: jnz condition label1 label1 # (we could handle but it makes a lot of code easier # if we have this assumption) - assert bb not in self.cfg_out self.cfg_out.add(bb) def remove_cfg_out(self, bb: "IRBasicBlock") -> None: From b00a41384401a79d6f4a83bc4d9c43b8f8d0fff2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 16:03:10 +0200 Subject: [PATCH 466/471] Revert normalized property I reverted to my original code for normalized that works on instructions This is important as because of how deploy works (in order to be compatible with the old system) some CFG connections are created that are not visible in IR --- vyper/venom/function.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 6d23126728..ef4c98ca5f 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -118,21 +118,26 @@ def append_data(self, opcode: str, args: list[IROperand]) -> None: @property def normalized(self) -> bool: """ - Check if function is normalized. A function is normalized if in the - CFG, no basic block simultaneously has multiple inputs and outputs. - That is, a basic block can be jumped to *from* multiple blocks, or it - can jump *to* multiple blocks, but it cannot simultaneously do both. - Having a normalized CFG makes calculation of stack layout easier when - emitting assembly. + Check if function is normalized. """ for bb in self.basic_blocks: + # Ignore if there are no multiple predecessors if len(bb.cfg_in) <= 1: continue + # Check if there is a conditional jump at the end + # of one of the predecessors for in_bb in bb.cfg_in: - if len(in_bb.cfg_out) > 1: - return False + 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 + + # The function is normalized return True def copy(self): From 37ed3bf0c2c6cf0b3b5dca924d243b43980d3d22 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 16:03:41 +0200 Subject: [PATCH 467/471] Normalize based on instructions not cfg outputs --- vyper/venom/passes/normalization.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index ee96f26702..5eec8c94b2 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -16,10 +16,6 @@ class NormalizationPass(IRPass): def _split_basic_block(self, bb: IRBasicBlock) -> None: # Iterate over the predecessors of the basic block for in_bb in list(bb.cfg_in): - # We are only splitting on conditional jumps - if len(in_bb.cfg_out) <= 1: - continue - jump_inst = in_bb.instructions[-1] assert bb in in_bb.cfg_out @@ -29,7 +25,7 @@ def _split_basic_block(self, bb: IRBasicBlock) -> None: elif jump_inst.opcode == "jmp" and isinstance(jump_inst.operands[0], IRVariable): self._split_for_dynamic_branch(bb, in_bb) else: - raise CompilerPanic("Unexpected termination instruction during normalization") + continue self.changes += 1 From f596312f499f58d608912f2bc38f6adf4825a3be Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 1 Dec 2023 18:29:57 +0200 Subject: [PATCH 468/471] Added some TODOS TODOS regarding the CFG<->deploy<->dynamic jump interactions --- vyper/venom/analysis.py | 14 +++++++++++--- vyper/venom/venom_to_assembly.py | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 0a5fdfe985..a369d31f28 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -19,6 +19,10 @@ 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. Going directly to the new IR we should have just + # one entry for the contructor and one for the runtime core. These will be propertly + # linked in the CFG, and the deploy instruction will be removed completely. deploy_bb = None after_deploy_bb = None for i, bb in enumerate(ctx.basic_blocks): @@ -37,9 +41,13 @@ def calculate_cfg(ctx: IRFunction) -> None: else: entry_block = ctx.basic_blocks[0] - # Special case for the jump table of selector buckets - # and fallback. It will be generalized when the - # dispacher code is directly generated in Venom + # TODO: Special case for the jump table of selector buckets and fallback. + # It will be generalized when the dispacher code is directly generated in Venom. + # The when directly generating the dispatcher code from the AST we should be emitting + # a dynamic jmp instruction with all the posible targets (this in EOF this will be also + # a jump via jump table). This will remove the need for this special case, and will result + # in cleaner code for normalization just according to CFG, without the need to examine + # the block termination instructions. for bb in ctx.basic_blocks: if "selector_bucket_" in bb.label.value or bb.label.value == "fallback": bb.add_cfg_in(entry_block) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index d0fe85f58d..f6ec45440a 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -71,7 +71,7 @@ ) -# REVIEW: "assembly" gets into the recursion due to how the original +# TODO: "assembly" gets into the recursion due to how the original # IR was structured recursively in regards with the deploy instruction. # There, recursing into the deploy instruction was by design, and # made it easier to make the assembly generated "recursive" (i.e. From febd073b4087020de48faf8d8cb316621648f87b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Dec 2023 09:18:24 -0800 Subject: [PATCH 469/471] add some comments --- vyper/venom/function.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index ef4c98ca5f..c14ad77345 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -118,7 +118,12 @@ def append_data(self, opcode: str, args: list[IROperand]) -> None: @property def normalized(self) -> bool: """ - Check if function is normalized. + Check if function is normalized. A function is normalized if in the + CFG, no basic block simultaneously has multiple inputs and outputs. + That is, a basic block can be jumped to *from* multiple blocks, or it + can jump *to* multiple blocks, but it cannot simultaneously do both. + Having a normalized CFG makes calculation of stack layout easier when + emitting assembly. """ for bb in self.basic_blocks: # Ignore if there are no multiple predecessors @@ -127,6 +132,12 @@ def normalized(self) -> bool: # Check if there is a conditional 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 + # certain special instructions (deploy instruction and + # selector table indirect jumps). for in_bb in bb.cfg_in: jump_inst = in_bb.instructions[-1] if jump_inst.opcode != "jnz": From 1b75ce0aa5ea97d683af78e91261518351ddadc8 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Dec 2023 09:54:55 -0800 Subject: [PATCH 470/471] add some comments, minor nits --- vyper/venom/analysis.py | 26 ++++++++++++-------------- vyper/venom/passes/normalization.py | 5 +++++ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index a369d31f28..9c3a7614eb 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -20,9 +20,8 @@ def calculate_cfg(ctx: IRFunction) -> None: bb.out_vars = OrderedSet() # TODO: This is a hack to support the old IR format - # where deploy is an instruction. Going directly to the new IR we should have just - # one entry for the contructor and one for the runtime core. These will be propertly - # linked in the CFG, and the deploy instruction will be removed completely. + # where deploy is an instruction. Going directly to the new IR 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): @@ -31,10 +30,10 @@ def calculate_cfg(ctx: IRFunction) -> None: after_deploy_bb = ctx.basic_blocks[i + 1] break - if deploy_bb: + 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 = True if ctx.basic_blocks[0].instructions[0].opcode != "deploy" else False + 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) @@ -42,12 +41,13 @@ def calculate_cfg(ctx: IRFunction) -> None: entry_block = ctx.basic_blocks[0] # TODO: Special case for the jump table of selector buckets and fallback. - # It will be generalized when the dispacher code is directly generated in Venom. - # The when directly generating the dispatcher code from the AST we should be emitting - # a dynamic jmp instruction with all the posible targets (this in EOF this will be also - # a jump via jump table). This will remove the need for this special case, and will result - # in cleaner code for normalization just according to CFG, without the need to examine - # the block termination instructions. + # It will be generalized when the dispacher code is directly generated in + # Venom. The when directly generating the dispatcher code from the AST we + # should be emitting a dynamic jmp instruction with all the posible targets + # (this in EOF this will be also a jump via jump table). This will remove + # the need for this special case, and will result in cleaner code for + # normalization just according to CFG, without the need to examine the + # block termination instructions. for bb in ctx.basic_blocks: if "selector_bucket_" in bb.label.value or bb.label.value == "fallback": bb.add_cfg_in(entry_block) @@ -55,9 +55,7 @@ def calculate_cfg(ctx: IRFunction) -> None: for bb in ctx.basic_blocks: assert len(bb.instructions) > 0, "Basic block should not be empty" last_inst = bb.instructions[-1] - assert last_inst.opcode in BB_TERMINATORS, "Last instruction should be a terminator" + str( - bb - ) + 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: diff --git a/vyper/venom/passes/normalization.py b/vyper/venom/passes/normalization.py index 5eec8c94b2..9ee1012f91 100644 --- a/vyper/venom/passes/normalization.py +++ b/vyper/venom/passes/normalization.py @@ -50,6 +50,8 @@ def _split_for_dynamic_branch(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> No 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 @@ -63,6 +65,9 @@ def _insert_split_basicblock(self, bb: IRBasicBlock, in_bb: IRBasicBlock) -> IRB 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) From 8aa5b4f2968360e414f569f7f2112f2ef328b673 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 1 Dec 2023 09:57:10 -0800 Subject: [PATCH 471/471] comments --- vyper/venom/analysis.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/vyper/venom/analysis.py b/vyper/venom/analysis.py index 9c3a7614eb..5980e21028 100644 --- a/vyper/venom/analysis.py +++ b/vyper/venom/analysis.py @@ -19,9 +19,9 @@ 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. Going directly to the new IR we should - # have two entry points, one for the initcode and one for the runtime code. + # 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): @@ -41,13 +41,10 @@ def calculate_cfg(ctx: IRFunction) -> None: entry_block = ctx.basic_blocks[0] # TODO: Special case for the jump table of selector buckets and fallback. - # It will be generalized when the dispacher code is directly generated in - # Venom. The when directly generating the dispatcher code from the AST we - # should be emitting a dynamic jmp instruction with all the posible targets - # (this in EOF this will be also a jump via jump table). This will remove - # the need for this special case, and will result in cleaner code for - # normalization just according to CFG, without the need to examine the - # block termination instructions. + # 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)