From 8238831eb2bd9399ba83be3643a39982f97dc8e5 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 26 Dec 2024 22:56:44 +0200 Subject: [PATCH 01/84] init --- vyper/codegen/self_call.py | 2 ++ vyper/compiler/phases.py | 2 +- vyper/venom/analysis/cfg.py | 12 +++++++---- vyper/venom/basicblock.py | 37 +++++++++++++++++++++++++++------ vyper/venom/context.py | 17 +++++++++++++-- vyper/venom/function.py | 12 ++++++----- vyper/venom/ir_node_to_venom.py | 9 ++++++++ 7 files changed, 73 insertions(+), 18 deletions(-) diff --git a/vyper/codegen/self_call.py b/vyper/codegen/self_call.py index fef6070d14..a7182f6eba 100644 --- a/vyper/codegen/self_call.py +++ b/vyper/codegen/self_call.py @@ -109,4 +109,6 @@ def ir_for_self_call(stmt_expr, context): ) o.is_self_call = True o.invoked_function_ir = func_t._ir_info.func_ir + o.passthrough_metadata["func_t"] = func_t + o.passthrough_metadata["args_ir"] = args_ir return o diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index 503281a867..e825ebccb2 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -310,7 +310,7 @@ def generate_ir_nodes(global_ctx: ModuleT, settings: Settings) -> tuple[IRnode, with anchor_settings(settings): ir_nodes, ir_runtime = module.generate_ir_for_module(global_ctx) - if settings.optimize != OptimizationLevel.NONE: + if settings.optimize != OptimizationLevel.NONE and not settings.experimental_codegen: ir_nodes = optimizer.optimize(ir_nodes) ir_runtime = optimizer.optimize(ir_runtime) return ir_nodes, ir_runtime diff --git a/vyper/venom/analysis/cfg.py b/vyper/venom/analysis/cfg.py index 2f90410cd5..714e47dd3d 100644 --- a/vyper/venom/analysis/cfg.py +++ b/vyper/venom/analysis/cfg.py @@ -53,10 +53,14 @@ def dfs_walk(self) -> Iterator[IRBasicBlock]: def invalidate(self): from vyper.venom.analysis import DFGAnalysis, DominatorTreeAnalysis, LivenessAnalysis + fn = self.function + for bb in fn.get_basic_blocks(): + bb.cfg_in = OrderedSet() + bb.cfg_out = OrderedSet() + bb.out_vars = OrderedSet() + self.analyses_cache.invalidate_analysis(DominatorTreeAnalysis) self.analyses_cache.invalidate_analysis(LivenessAnalysis) - - self._dfs = None - - # be conservative - assume cfg invalidation invalidates dfg self.analyses_cache.invalidate_analysis(DFGAnalysis) + self._dfs = None + diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 4c75c67700..41737ec090 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -375,6 +375,28 @@ def get_ast_source(self) -> Optional[IRnode]: if inst.ast_source: return inst.ast_source return self.parent.parent.ast_source + + def copy(self, prefix: str = "") -> "IRInstruction": + ops: list[IROperand] = [] + for op in self.operands: + if isinstance(op, IRLabel): + ops.append(IRLabel(op.value)) + elif isinstance(op, IRVariable): + ops.append(IRVariable(f"{prefix}{op.name}")) + else: + ops.append(IRLiteral(op.value)) + + output = None + if self.output: + output = IRVariable(f"{prefix}{self.output.name}") + + inst = IRInstruction(self.opcode, ops, output) + inst.parent = self.parent + inst.liveness = self.liveness.copy() + inst.annotation = self.annotation + inst.ast_source = inst.ast_source + inst.error_msg = inst.error_msg + return inst def __repr__(self) -> str: s = "" @@ -647,12 +669,15 @@ def liveness_in_vars(self) -> OrderedSet[IRVariable]: return inst.liveness return OrderedSet() - def copy(self): - bb = IRBasicBlock(self.label, self.parent) - bb.instructions = self.instructions.copy() - bb.cfg_in = self.cfg_in.copy() - bb.cfg_out = self.cfg_out.copy() - bb.out_vars = self.out_vars.copy() + def copy(self, prefix: str = "") -> "IRBasicBlock": + new_label = IRLabel(f"{prefix}{self.label.value}") + bb = IRBasicBlock(new_label, self.parent) + bb.instructions = [inst.copy(prefix) for inst in self.instructions] + for inst in bb.instructions: + inst.parent = bb + bb.cfg_in = OrderedSet() + bb.cfg_out = OrderedSet() + bb.out_vars = OrderedSet() return bb def __repr__(self) -> str: diff --git a/vyper/venom/context.py b/vyper/venom/context.py index 0c5cbc379c..96a1496893 100644 --- a/vyper/venom/context.py +++ b/vyper/venom/context.py @@ -1,8 +1,8 @@ import textwrap from dataclasses import dataclass, field -from typing import Optional +from typing import Iterator, Optional -from vyper.venom.basicblock import IRLabel +from vyper.venom.basicblock import IRBasicBlock, IRLabel from vyper.venom.function import IRFunction @@ -36,6 +36,7 @@ class IRContext: immutables_len: Optional[int] data_segment: list[DataSection] last_label: int + last_variable: int def __init__(self) -> None: self.functions = {} @@ -43,14 +44,26 @@ def __init__(self) -> None: self.immutables_len = None self.data_segment = [] self.last_label = 0 + self.last_variable = 0 + + def get_basic_blocks(self) -> Iterator[IRBasicBlock]: + for fn in self.functions.values(): + for bb in fn.get_basic_blocks(): + yield bb def add_function(self, fn: IRFunction) -> None: fn.ctx = self self.functions[fn.name] = fn + def remove_function(self, fn: IRFunction) -> None: + del self.functions[fn.name] + def create_function(self, name: str) -> IRFunction: label = IRLabel(name, True) + if label in self.functions: + return self.functions[label] fn = IRFunction(label, self) + fn.append_basic_block(IRBasicBlock(label, fn)) self.add_function(fn) return fn diff --git a/vyper/venom/function.py b/vyper/venom/function.py index f02da77fe3..2659a97b55 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -42,7 +42,7 @@ def append_basic_block(self, bb: IRBasicBlock): Append basic block to function. """ assert isinstance(bb, IRBasicBlock), bb - assert bb.label.name not in self._basic_block_dict, bb.label + # assert bb.label.name not in self._basic_block_dict, bb.label self._basic_block_dict[bb.label.name] = bb def remove_basic_block(self, bb: IRBasicBlock): @@ -179,10 +179,12 @@ def chain_basic_blocks(self) -> None: else: bb.append_instruction("stop") - def copy(self): - new = IRFunction(self.name) - new._basic_block_dict = self._basic_block_dict.copy() - new.last_variable = self.last_variable + def copy(self, prefix: str = ""): + new_label = IRLabel(f"{prefix}{self.name.value}") + new = IRFunction(new_label) + for bb in self.get_basic_blocks(): + new_bb = bb.copy(prefix) + new.append_basic_block(new_bb) return new def as_graph(self, only_subgraph=False) -> str: diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index f46457b77f..032cd4b538 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -160,6 +160,12 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio target_label = goto_ir.args[0].value # goto return_buf_ir = goto_ir.args[1] # return buffer ret_args: list[IROperand] = [IRLabel(target_label)] # type: ignore + func_t = ir.passthrough_metadata["func_t"] + assert func_t is not None, "func_t not found in passthrough metadata" + + fn.ctx.create_function(target_label) + + stack_args: list[IROperand] = [] if setup_ir != goto_ir: _convert_ir_bb(fn, setup_ir, symbols) @@ -170,6 +176,9 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio if len(goto_ir.args) > 2: ret_args.append(return_buf) # type: ignore + for stack_arg in stack_args: + ret_args.append(stack_arg) + bb.append_invoke_instruction(ret_args, returns=False) # type: ignore return return_buf From c54273cdc9e87cc35bd50037bb8326ea8a6c7a8a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 26 Dec 2024 23:00:29 +0200 Subject: [PATCH 02/84] add fcg --- vyper/venom/analysis/fcg.py | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 vyper/venom/analysis/fcg.py diff --git a/vyper/venom/analysis/fcg.py b/vyper/venom/analysis/fcg.py new file mode 100644 index 0000000000..fd90cb843e --- /dev/null +++ b/vyper/venom/analysis/fcg.py @@ -0,0 +1,50 @@ +from vyper.utils import OrderedSet +from vyper.venom.analysis.analysis import IRAnalysesCache, IRAnalysis +from vyper.venom.basicblock import IRInstruction, IRLabel +from vyper.venom.context import IRContext +from vyper.venom.function import IRFunction + + +class FCGAnalysis(IRAnalysis): + """ + Compute the function call graph for the context. + """ + + ctx: IRContext + calls: dict[IRFunction, OrderedSet[IRInstruction]] + callees: dict[IRFunction, OrderedSet[IRFunction]] + + def __init__(self, analyses_cache: IRAnalysesCache, function: IRFunction): + super().__init__(analyses_cache, function) + self.ctx = function.ctx + self.calls = dict() + self.callees = dict() + + def analyze(self) -> None: + ctx = self.ctx + fn = self.function + for func in ctx.get_functions(): + self.calls[func] = OrderedSet() + self.callees[func] = OrderedSet() + + for fn in ctx.get_functions(): + self._analyze_function(fn) + + def get_calls(self, fn: IRFunction) -> OrderedSet[IRInstruction]: + return self.calls[fn] + + def get_callees(self, fn: IRFunction) -> OrderedSet[IRFunction]: + return self.callees[fn] + + def _analyze_function(self, fn: IRFunction) -> None: + for bb in fn.get_basic_blocks(): + for inst in bb.instructions: + if inst.opcode == "invoke": + label = inst.operands[0] + assert isinstance(label, IRLabel) # mypy help + callee = self.ctx.get_function(label) + self.callees[fn].add(callee) + self.calls[callee].add(inst) + + def invalidate(self): + pass \ No newline at end of file From 5565d4c0fdb517c89601e0cfe9fc45a89a2b63e0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 26 Dec 2024 23:01:56 +0200 Subject: [PATCH 03/84] func inliner --- vyper/venom/passes/func_inliner.py | 116 +++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 vyper/venom/passes/func_inliner.py diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py new file mode 100644 index 0000000000..5ffd75978d --- /dev/null +++ b/vyper/venom/passes/func_inliner.py @@ -0,0 +1,116 @@ +from vyper.venom.analysis.cfg import CFGAnalysis +from vyper.venom.analysis.dfg import DFGAnalysis +from vyper.venom.analysis.fcg import FCGAnalysis +from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable +from vyper.venom.context import IRContext +from vyper.venom.passes.base_pass import IRPass + + +class FuncInlinerPass(IRPass): + """ + This pass inlines functions into the call sites. + """ + + ctx: IRContext + inline_count: int + fcg: FCGAnalysis + + def run_pass(self): + self.inline_count = 0 + self.ctx = self.function.ctx + self.fcg = self.analyses_cache.request_analysis(FCGAnalysis) + + walk = self._build_call_walk() + for func in walk: + calls = self.fcg.get_calls(func) + if len(calls) == 1 and False: + # sys.stderr.write("**** Inlining function " + str(func.name) + "\n") + self._inline_function(func, calls) + self.ctx.remove_function(func) + # break + + self.analyses_cache.invalidate_analysis(DFGAnalysis) + self.analyses_cache.invalidate_analysis(CFGAnalysis) + + def _build_call_walk(self): + """ + DFS walk over the call graph. + """ + visited = set() + call_walk = [] + + def dfs(fn): + if fn in visited: + return + visited.add(fn) + + callees = self.fcg.get_callees(fn) + for callee in callees: + dfs(callee) + + call_walk.append(fn) + + dfs(self.function) + + return call_walk + + def _filter_candidates(self, func_call_counts): + """ + Filter candidates for inlining. This will become more sophisticated in the future. + """ + return [fn for fn, call_sites in func_call_counts.items() if len(call_sites) == 1] + + def _inline_function(self, func, call_sites): + """ + Inline function into call sites. + """ + for call_site in call_sites: + self._inline_call_site(func, call_site) + + def _inline_call_site(self, func, call_site): + """ + Inline function into call site. + """ + prefix = f"inline_{self.inline_count}_" + self.inline_count += 1 + call_site_bb = call_site.parent + call_site_func = call_site_bb.parent + + call_site_return = IRBasicBlock( + self.ctx.get_next_label(f"{prefix}inline_return"), call_site_bb.parent + ) + call_idx = call_site_bb.instructions.index(call_site) + + for inst in call_site_bb.instructions[call_idx + 1 :]: + call_site_return.insert_instruction(inst) + call_site_func.append_basic_block(call_site_return) + + func_copy = func.copy(prefix) + + for bb in func_copy.get_basic_blocks(): + bb.parent = call_site_func + call_site_func.append_basic_block(bb) + for inst in bb.instructions: + if inst.opcode == "param": + if inst.annotation == "return_buffer": + inst.opcode = "store" + inst.operands = [call_site.operands[1]] + inst.output = IRVariable(inst.output.name, inst.output.version + 1) + elif inst.annotation == "return_pc": + inst.make_nop() + elif inst.opcode == "palloca": + inst.opcode = "store" + inst.operands = [inst.operands[0]] + elif inst.opcode == "store": + if "ret_ofst" in inst.output.name or "ret_size" in inst.output.name: + inst.make_nop() + elif inst.opcode == "ret": + inst.opcode = "jmp" + inst.operands = [call_site_return.label] + elif inst.opcode in ["jmp", "jnz", "djmp", "phi"]: + for i, op in enumerate(inst.operands): + if isinstance(op, IRLabel): + inst.operands[i] = IRLabel(f"{prefix}{op.name}") + + call_site_bb.instructions = call_site_bb.instructions[:call_idx] + call_site_bb.append_instruction("jmp", func_copy.entry.label) \ No newline at end of file From 5f01394c7fe10ce07036a025eba58a087cc2ed85 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 26 Dec 2024 23:06:34 +0200 Subject: [PATCH 04/84] params --- vyper/venom/function.py | 14 ++++++++++++++ vyper/venom/ir_node_to_venom.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 2659a97b55..1ad39c5560 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -5,6 +5,14 @@ from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable +class IRParameter: + offset: int + size: int + call_site_var: Optional[IRVariable] + func_var: Optional[IRVariable] + addr_var: Optional[IRVariable] + + class IRFunction: """ Function that contains basic blocks. @@ -149,6 +157,12 @@ def pop_source(self): assert len(self._error_msg_stack) > 0, "Empty error stack" self._error_msg_stack.pop() + def get_param_at_offset(self, offset: int) -> Optional[IRParameter]: + for param in self.args: + if param.offset == offset: + return param + return None + @property def ast_source(self) -> Optional[IRnode]: return self._ast_source_stack[-1] if len(self._ast_source_stack) > 0 else None diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 032cd4b538..ffc30c64dc 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -195,12 +195,27 @@ def _handle_internal_func( symbols["return_buffer"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_buffer" + for arg in fn.args: + ret = bb.append_instruction("param") + arg.func_var = ret + # return address symbols["return_pc"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_pc" + for arg in fn.args: + ret = bb.append_instruction("alloca", arg.offset, 32) + bb.append_instruction("mstore", arg.func_var, ret) # type: ignore + arg.addr_var = ret + _convert_ir_bb(fn, ir.args[0].args[2], symbols) + for inst in bb.instructions: + if inst.opcode == "store": + param = fn.get_param_at_offset(inst.operands[0].value) + if param is not None: + inst.operands[0] = param.addr_var # type: ignore + return fn From 6b88eed9f20b4e25de8d479221bb607cdf361cde Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 26 Dec 2024 23:08:04 +0200 Subject: [PATCH 05/84] helpers --- vyper/venom/basicblock.py | 6 ++++++ vyper/venom/context.py | 12 +++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 41737ec090..cfc3bb695a 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -308,6 +308,12 @@ def get_outputs(self) -> list[IROperand]: """ return [self.output] if self.output else [] + def make_nop(self): + self.opcode = "nop" + self.output = None + self.operands = [] + self.annotation = None + def flip(self): """ Flip operands for commutative or comparator opcodes diff --git a/vyper/venom/context.py b/vyper/venom/context.py index 96a1496893..502eaf00a6 100644 --- a/vyper/venom/context.py +++ b/vyper/venom/context.py @@ -2,7 +2,7 @@ from dataclasses import dataclass, field from typing import Iterator, Optional -from vyper.venom.basicblock import IRBasicBlock, IRLabel +from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable from vyper.venom.function import IRFunction @@ -71,12 +71,22 @@ def get_function(self, name: IRLabel) -> IRFunction: if name in self.functions: return self.functions[name] raise Exception(f"Function {name} not found in context") + + def get_functions(self) -> Iterator[IRFunction]: + return iter(self.functions.values()) def get_next_label(self, suffix: str = "") -> IRLabel: if suffix != "": suffix = f"_{suffix}" self.last_label += 1 return IRLabel(f"{self.last_label}{suffix}") + + def get_next_variable(self) -> IRVariable: + self.last_variable += 1 + return IRVariable(f"%{self.last_variable}") + + def get_last_variable(self) -> str: + return f"%{self.last_variable}" def chain_basic_blocks(self) -> None: """ From 2b19059ae167f037d9ab4c0097d415d8191dcbbb Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 26 Dec 2024 23:13:49 +0200 Subject: [PATCH 06/84] global opti --- vyper/venom/__init__.py | 23 ++++++++++++++--------- vyper/venom/passes/__init__.py | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index ddd9065194..0388f649d4 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -25,6 +25,7 @@ SimplifyCFGPass, StoreElimination, StoreExpansionPass, + FuncInlinerPass, ) from vyper.venom.venom_to_assembly import VenomCompiler @@ -46,12 +47,10 @@ def generate_assembly_experimental( return compiler.generate_evm(optimize == OptimizationLevel.NONE) -def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None: +def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache) -> None: # Run passes on Venom IR # TODO: Add support for optimization levels - ac = IRAnalysesCache(fn) - FloatAllocas(ac, fn).run_pass() SimplifyCFGPass(ac, fn).run_pass() @@ -85,15 +84,21 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None: DFTPass(ac, fn).run_pass() - -def run_passes_on(ctx: IRContext, optimize: OptimizationLevel): - for fn in ctx.functions.values(): - _run_passes(fn, optimize) - +def _run_global_passes(ctx: IRContext, optimize: OptimizationLevel, ir_analyses: dict) -> None: + fn = next(ctx.get_functions()) + FuncInlinerPass(ir_analyses[fn], fn).run_pass() def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> IRContext: # Convert "old" IR to "new" IR ctx = ir_node_to_venom(ir) - run_passes_on(ctx, optimize) + + ir_analyses = {} + for fn in ctx.functions.values(): + ir_analyses[fn] = IRAnalysesCache(fn) + + _run_global_passes(ctx, optimize, ir_analyses) + + for fn in ctx.functions.values(): + _run_passes(fn, optimize, ir_analyses[fn]) return ctx diff --git a/vyper/venom/passes/__init__.py b/vyper/venom/passes/__init__.py index a3227dcf4b..bde765520c 100644 --- a/vyper/venom/passes/__init__.py +++ b/vyper/venom/passes/__init__.py @@ -14,3 +14,4 @@ from .simplify_cfg import SimplifyCFGPass from .store_elimination import StoreElimination from .store_expansion import StoreExpansionPass +from .func_inliner import FuncInlinerPass From 7d144de7f304b3cd3864d24580edc995be464c7d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 27 Dec 2024 00:16:24 +0200 Subject: [PATCH 07/84] work --- vyper/venom/__init__.py | 7 +++-- vyper/venom/analysis/fcg.py | 1 - vyper/venom/context.py | 2 ++ vyper/venom/ir_node_to_venom.py | 1 + vyper/venom/passes/base_pass.py | 17 +++++++++++ vyper/venom/passes/func_inliner.py | 48 +++++++++++++++++------------- 6 files changed, 53 insertions(+), 23 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 0388f649d4..d38168ee00 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -85,8 +85,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache DFTPass(ac, fn).run_pass() def _run_global_passes(ctx: IRContext, optimize: OptimizationLevel, ir_analyses: dict) -> None: - fn = next(ctx.get_functions()) - FuncInlinerPass(ir_analyses[fn], fn).run_pass() + FuncInlinerPass(ir_analyses, ctx).run_pass() def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> IRContext: # Convert "old" IR to "new" IR @@ -97,6 +96,10 @@ def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> IRContext: ir_analyses[fn] = IRAnalysesCache(fn) _run_global_passes(ctx, optimize, ir_analyses) + + ir_analyses = {} + for fn in ctx.functions.values(): + ir_analyses[fn] = IRAnalysesCache(fn) for fn in ctx.functions.values(): _run_passes(fn, optimize, ir_analyses[fn]) diff --git a/vyper/venom/analysis/fcg.py b/vyper/venom/analysis/fcg.py index fd90cb843e..9eaef858b7 100644 --- a/vyper/venom/analysis/fcg.py +++ b/vyper/venom/analysis/fcg.py @@ -22,7 +22,6 @@ def __init__(self, analyses_cache: IRAnalysesCache, function: IRFunction): def analyze(self) -> None: ctx = self.ctx - fn = self.function for func in ctx.get_functions(): self.calls[func] = OrderedSet() self.callees[func] = OrderedSet() diff --git a/vyper/venom/context.py b/vyper/venom/context.py index 502eaf00a6..58cd14f626 100644 --- a/vyper/venom/context.py +++ b/vyper/venom/context.py @@ -32,6 +32,7 @@ def __str__(self): class IRContext: functions: dict[IRLabel, IRFunction] + entry_function: IRFunction ctor_mem_size: Optional[int] immutables_len: Optional[int] data_segment: list[DataSection] @@ -40,6 +41,7 @@ class IRContext: def __init__(self) -> None: self.functions = {} + self.entry_function = None self.ctor_mem_size = None self.immutables_len = None self.data_segment = [] diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index ffc30c64dc..7c0f95dd25 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -121,6 +121,7 @@ def ir_node_to_venom(ir: IRnode) -> IRContext: ctx = IRContext() fn = ctx.create_function(MAIN_ENTRY_LABEL_NAME) + ctx.entry_function = fn _convert_ir_bb(fn, ir, {}) diff --git a/vyper/venom/passes/base_pass.py b/vyper/venom/passes/base_pass.py index 3951ac4455..16116df964 100644 --- a/vyper/venom/passes/base_pass.py +++ b/vyper/venom/passes/base_pass.py @@ -1,3 +1,4 @@ +from vyper.venom.context import IRContext from vyper.venom.analysis import IRAnalysesCache from vyper.venom.function import IRFunction @@ -16,3 +17,19 @@ def __init__(self, analyses_cache: IRAnalysesCache, function: IRFunction): def run_pass(self, *args, **kwargs): raise NotImplementedError(f"Not implemented! {self.__class__}.run_pass()") + + +class IRGlobalPass: + """ + Base class for all Venom IR passes. + """ + + ctx: IRContext + analyses_caches: dict[IRFunction, IRAnalysesCache] + + def __init__(self, analyses_caches: dict[IRFunction, IRAnalysesCache], ctx: IRContext): + self.analyses_caches = analyses_caches + self.ctx = ctx + + def run_pass(self, *args, **kwargs): + raise NotImplementedError(f"Not implemented! {self.__class__}.run_pass()") \ No newline at end of file diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 5ffd75978d..7044ec3a08 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -1,38 +1,43 @@ +import sys +from vyper.venom.function import IRFunction from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.fcg import FCGAnalysis from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable from vyper.venom.context import IRContext -from vyper.venom.passes.base_pass import IRPass +from vyper.venom.passes.base_pass import IRGlobalPass -class FuncInlinerPass(IRPass): +class FuncInlinerPass(IRGlobalPass): """ This pass inlines functions into the call sites. """ - ctx: IRContext inline_count: int fcg: FCGAnalysis def run_pass(self): + entry = self.ctx.entry_function self.inline_count = 0 - self.ctx = self.function.ctx - self.fcg = self.analyses_cache.request_analysis(FCGAnalysis) - - walk = self._build_call_walk() - for func in walk: - calls = self.fcg.get_calls(func) - if len(calls) == 1 and False: - # sys.stderr.write("**** Inlining function " + str(func.name) + "\n") - self._inline_function(func, calls) - self.ctx.remove_function(func) - # break - - self.analyses_cache.invalidate_analysis(DFGAnalysis) - self.analyses_cache.invalidate_analysis(CFGAnalysis) - - def _build_call_walk(self): + + self.fcg = self.analyses_caches[entry].request_analysis(FCGAnalysis) + self.walk = self._build_call_walk(entry) + + for function in list(self.ctx.functions.values()): + self.run_pass_on(function) + + def run_pass_on(self, func: IRFunction): + calls = self.fcg.get_calls(func) + if len(calls) == 1: + sys.stderr.write("**** Inlining function " + str(func.name) + "\n") + self._inline_function(func, calls) + + self.analyses_caches[func].invalidate_analysis(DFGAnalysis) + self.analyses_caches[func].invalidate_analysis(CFGAnalysis) + + self.ctx.remove_function(func) + + def _build_call_walk(self, function: IRFunction): """ DFS walk over the call graph. """ @@ -50,7 +55,7 @@ def dfs(fn): call_walk.append(fn) - dfs(self.function) + dfs(function) return call_walk @@ -66,6 +71,9 @@ def _inline_function(self, func, call_sites): """ for call_site in call_sites: self._inline_call_site(func, call_site) + fn = call_site.parent.parent + self.analyses_caches[fn].invalidate_analysis(DFGAnalysis) + self.analyses_caches[fn].invalidate_analysis(CFGAnalysis) def _inline_call_site(self, func, call_site): """ From fe9f39a7ab766e11f2c602430f23693550253799 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 27 Dec 2024 17:18:18 +0200 Subject: [PATCH 08/84] refactor --- vyper/venom/passes/func_inliner.py | 51 ++++++++++++++---------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 7044ec3a08..c4e6aedd1a 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -31,33 +31,7 @@ def run_pass_on(self, func: IRFunction): if len(calls) == 1: sys.stderr.write("**** Inlining function " + str(func.name) + "\n") self._inline_function(func, calls) - - self.analyses_caches[func].invalidate_analysis(DFGAnalysis) - self.analyses_caches[func].invalidate_analysis(CFGAnalysis) - self.ctx.remove_function(func) - - def _build_call_walk(self, function: IRFunction): - """ - DFS walk over the call graph. - """ - visited = set() - call_walk = [] - - def dfs(fn): - if fn in visited: - return - visited.add(fn) - - callees = self.fcg.get_callees(fn) - for callee in callees: - dfs(callee) - - call_walk.append(fn) - - dfs(function) - - return call_walk def _filter_candidates(self, func_call_counts): """ @@ -121,4 +95,27 @@ def _inline_call_site(self, func, call_site): inst.operands[i] = IRLabel(f"{prefix}{op.name}") call_site_bb.instructions = call_site_bb.instructions[:call_idx] - call_site_bb.append_instruction("jmp", func_copy.entry.label) \ No newline at end of file + call_site_bb.append_instruction("jmp", func_copy.entry.label) + + + def _build_call_walk(self, function: IRFunction): + """ + DFS walk over the call graph. + """ + visited = set() + call_walk = [] + + def dfs(fn): + if fn in visited: + return + visited.add(fn) + + callees = self.fcg.get_callees(fn) + for callee in callees: + dfs(callee) + + call_walk.append(fn) + + dfs(function) + + return call_walk \ No newline at end of file From 051225a2267d8edba4a5f9e4a67d6f6ec1b1bcae Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Fri, 27 Dec 2024 17:55:52 +0200 Subject: [PATCH 09/84] float --- vyper/venom/passes/func_inliner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index c4e6aedd1a..76e9986eec 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -1,4 +1,5 @@ import sys +from vyper.venom.passes import FloatAllocas from vyper.venom.function import IRFunction from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis @@ -29,7 +30,7 @@ def run_pass(self): def run_pass_on(self, func: IRFunction): calls = self.fcg.get_calls(func) if len(calls) == 1: - sys.stderr.write("**** Inlining function " + str(func.name) + "\n") + #sys.stderr.write("****\n**** Inlining function " + str(func.name) + "\n****\n") self._inline_function(func, calls) self.ctx.remove_function(func) @@ -44,6 +45,7 @@ def _inline_function(self, func, call_sites): Inline function into call sites. """ for call_site in call_sites: + FloatAllocas(self.analyses_caches[func], func).run_pass() self._inline_call_site(func, call_site) fn = call_site.parent.parent self.analyses_caches[fn].invalidate_analysis(DFGAnalysis) From 1997b25cbfbfcb90f32e4bed553524c93b6882e8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 00:28:20 +0200 Subject: [PATCH 10/84] codename `asylum` --- vyper/venom/analysis/fcg.py | 12 ++++++------ vyper/venom/passes/func_inliner.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vyper/venom/analysis/fcg.py b/vyper/venom/analysis/fcg.py index 9eaef858b7..8898231157 100644 --- a/vyper/venom/analysis/fcg.py +++ b/vyper/venom/analysis/fcg.py @@ -11,26 +11,26 @@ class FCGAnalysis(IRAnalysis): """ ctx: IRContext - calls: dict[IRFunction, OrderedSet[IRInstruction]] + call_sites: dict[IRFunction, OrderedSet[IRInstruction]] callees: dict[IRFunction, OrderedSet[IRFunction]] def __init__(self, analyses_cache: IRAnalysesCache, function: IRFunction): super().__init__(analyses_cache, function) self.ctx = function.ctx - self.calls = dict() + self.call_sites = dict() self.callees = dict() def analyze(self) -> None: ctx = self.ctx for func in ctx.get_functions(): - self.calls[func] = OrderedSet() + self.call_sites[func] = OrderedSet() self.callees[func] = OrderedSet() for fn in ctx.get_functions(): self._analyze_function(fn) - def get_calls(self, fn: IRFunction) -> OrderedSet[IRInstruction]: - return self.calls[fn] + def get_call_sites(self, fn: IRFunction) -> OrderedSet[IRInstruction]: + return self.call_sites[fn] def get_callees(self, fn: IRFunction) -> OrderedSet[IRFunction]: return self.callees[fn] @@ -43,7 +43,7 @@ def _analyze_function(self, fn: IRFunction) -> None: assert isinstance(label, IRLabel) # mypy help callee = self.ctx.get_function(label) self.callees[fn].add(callee) - self.calls[callee].add(inst) + self.call_sites[callee].add(inst) def invalidate(self): pass \ No newline at end of file diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 76e9986eec..cc0bea4e98 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -24,13 +24,13 @@ def run_pass(self): self.fcg = self.analyses_caches[entry].request_analysis(FCGAnalysis) self.walk = self._build_call_walk(entry) - for function in list(self.ctx.functions.values()): + for function in self.walk: self.run_pass_on(function) def run_pass_on(self, func: IRFunction): - calls = self.fcg.get_calls(func) + calls = self.fcg.get_call_sites(func) if len(calls) == 1: - #sys.stderr.write("****\n**** Inlining function " + str(func.name) + "\n****\n") + # sys.stderr.write("****\n**** Inlining function " + str(func.name) + "\n****\n") self._inline_function(func, calls) self.ctx.remove_function(func) @@ -112,9 +112,9 @@ def dfs(fn): return visited.add(fn) - callees = self.fcg.get_callees(fn) - for callee in callees: - dfs(callee) + called_functions = self.fcg.get_callees(fn) + for func in called_functions: + dfs(func) call_walk.append(fn) From 027a2ebe0c6f7c8d4a155c2d1ec294a6e4112db8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 13:02:34 +0200 Subject: [PATCH 11/84] more conservative --- vyper/venom/passes/func_inliner.py | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index cc0bea4e98..cdce914adb 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -21,24 +21,24 @@ def run_pass(self): entry = self.ctx.entry_function self.inline_count = 0 - self.fcg = self.analyses_caches[entry].request_analysis(FCGAnalysis) - self.walk = self._build_call_walk(entry) - - for function in self.walk: - self.run_pass_on(function) - - def run_pass_on(self, func: IRFunction): - calls = self.fcg.get_call_sites(func) - if len(calls) == 1: - # sys.stderr.write("****\n**** Inlining function " + str(func.name) + "\n****\n") - self._inline_function(func, calls) - self.ctx.remove_function(func) + while True: + self.fcg = self.analyses_caches[entry].force_analysis(FCGAnalysis) + self.walk = self._build_call_walk(entry) - def _filter_candidates(self, func_call_counts): - """ - Filter candidates for inlining. This will become more sophisticated in the future. - """ - return [fn for fn, call_sites in func_call_counts.items() if len(call_sites) == 1] + candidates = list(self._get_inline_candidates()) + if len(candidates) == 0: + return + + candidate = candidates[0] + calls = self.fcg.get_call_sites(candidate) + self._inline_function(candidate, calls) + self.ctx.remove_function(candidate) + + def _get_inline_candidates(self): + for func in self.walk: + calls = self.fcg.get_call_sites(func) + if len(calls) == 1: + yield func def _inline_function(self, func, call_sites): """ From d79c6d94a0091ab6e2835f7ab1d63b112be976da Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 13:13:22 +0200 Subject: [PATCH 12/84] disable load elimination --- 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 d38168ee00..d44a861605 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -60,7 +60,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache MakeSSA(ac, fn).run_pass() SCCP(ac, fn).run_pass() - LoadElimination(ac, fn).run_pass() + # LoadElimination(ac, fn).run_pass() StoreElimination(ac, fn).run_pass() MemMergePass(ac, fn).run_pass() SimplifyCFGPass(ac, fn).run_pass() From ca1ba5fa35a036f42154dc4494c992582bdea7ff Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 13:27:35 +0200 Subject: [PATCH 13/84] export func for repl --- vyper/venom/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index d44a861605..31f30d3212 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -60,7 +60,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache MakeSSA(ac, fn).run_pass() SCCP(ac, fn).run_pass() - # LoadElimination(ac, fn).run_pass() + LoadElimination(ac, fn).run_pass() StoreElimination(ac, fn).run_pass() MemMergePass(ac, fn).run_pass() SimplifyCFGPass(ac, fn).run_pass() @@ -87,10 +87,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache def _run_global_passes(ctx: IRContext, optimize: OptimizationLevel, ir_analyses: dict) -> None: FuncInlinerPass(ir_analyses, ctx).run_pass() -def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> IRContext: - # Convert "old" IR to "new" IR - ctx = ir_node_to_venom(ir) - +def run_passes_on(ctx: IRContext, optimize: OptimizationLevel) -> None: ir_analyses = {} for fn in ctx.functions.values(): ir_analyses[fn] = IRAnalysesCache(fn) @@ -104,4 +101,10 @@ def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> IRContext: for fn in ctx.functions.values(): _run_passes(fn, optimize, ir_analyses[fn]) +def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> IRContext: + # Convert "old" IR to "new" IR + ctx = ir_node_to_venom(ir) + + run_passes_on(ctx, optimize) + return ctx From 7508436f14686eecb2a822d08a4377337ccac755 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 13:52:49 +0200 Subject: [PATCH 14/84] first function is entry --- vyper/venom/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vyper/venom/parser.py b/vyper/venom/parser.py index 5ccc29b7a4..91c030826c 100644 --- a/vyper/venom/parser.py +++ b/vyper/venom/parser.py @@ -124,6 +124,8 @@ def start(self, children) -> IRContext: funcs = children for fn_name, blocks in funcs: fn = ctx.create_function(fn_name) + if ctx.entry_function is None: + ctx.entry_function = fn fn._basic_block_dict.clear() for block_name, instructions in blocks: From 1257fd4a6ff44f484be5c04fde23f908ddf8ec99 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 13:53:11 +0200 Subject: [PATCH 15/84] debuging aids --- vyper/venom/analysis/equivalent_vars.py | 2 +- vyper/venom/basicblock.py | 10 ++++------ vyper/venom/passes/sccp/sccp.py | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/vyper/venom/analysis/equivalent_vars.py b/vyper/venom/analysis/equivalent_vars.py index 895895651a..02d76a5c6f 100644 --- a/vyper/venom/analysis/equivalent_vars.py +++ b/vyper/venom/analysis/equivalent_vars.py @@ -22,7 +22,7 @@ def analyze(self): source = inst.operands[0] - assert var not in equivalence_set # invariant + assert var not in equivalence_set, f"var `{var}` in equivalence_set" # invariant if source in equivalence_set: equivalence_set[var] = equivalence_set[source] continue diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index cfc3bb695a..72e7ebb552 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -309,10 +309,10 @@ def get_outputs(self) -> list[IROperand]: return [self.output] if self.output else [] def make_nop(self): - self.opcode = "nop" + self.annotation = str(self) # Keep original instruction as annotation for debugging + self.opcode = "nop" self.output = None - self.operands = [] - self.annotation = None + self.operands = [] def flip(self): """ @@ -621,9 +621,7 @@ def fix_phi_instructions(self): inst.opcode = "store" inst.operands = [inst.operands[1]] elif op_len == 0: - inst.opcode = "nop" - inst.output = None - inst.operands = [] + inst.make_nop() if needs_sort: self.instructions.sort(key=lambda inst: inst.opcode != "phi") diff --git a/vyper/venom/passes/sccp/sccp.py b/vyper/venom/passes/sccp/sccp.py index 9004a357f0..8093d50882 100644 --- a/vyper/venom/passes/sccp/sccp.py +++ b/vyper/venom/passes/sccp/sccp.py @@ -297,7 +297,7 @@ def _replace_constants(self, inst: IRInstruction): if isinstance(lat, IRLiteral): if lat.value > 0: - inst.opcode = "nop" + inst.make_nop() else: raise StaticAssertionException( f"assertion found to fail at compile time ({inst.error_msg}).", From d4893bd459c33956de213a984dbbf9eff26661fa Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 14:04:07 +0200 Subject: [PATCH 16/84] work --- vyper/venom/__init__.py | 1 + vyper/venom/passes/func_inliner.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 31f30d3212..1e86bc5058 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -60,6 +60,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache MakeSSA(ac, fn).run_pass() SCCP(ac, fn).run_pass() + StoreElimination(ac, fn).run_pass() LoadElimination(ac, fn).run_pass() StoreElimination(ac, fn).run_pass() MemMergePass(ac, fn).run_pass() diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index cdce914adb..82bb8f9dd6 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -4,6 +4,7 @@ from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.fcg import FCGAnalysis +from vyper.venom.analysis.equivalent_vars import VarEquivalenceAnalysis from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable from vyper.venom.context import IRContext from vyper.venom.passes.base_pass import IRGlobalPass @@ -32,7 +33,7 @@ def run_pass(self): candidate = candidates[0] calls = self.fcg.get_call_sites(candidate) self._inline_function(candidate, calls) - self.ctx.remove_function(candidate) + self.ctx.remove_function(candidate) def _get_inline_candidates(self): for func in self.walk: @@ -50,6 +51,7 @@ def _inline_function(self, func, call_sites): fn = call_site.parent.parent self.analyses_caches[fn].invalidate_analysis(DFGAnalysis) self.analyses_caches[fn].invalidate_analysis(CFGAnalysis) + self.analyses_caches[fn].invalidate_analysis(VarEquivalenceAnalysis) def _inline_call_site(self, func, call_site): """ From a1f76b65e583c502080e21f9cb4aafe69390bf1d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 14:06:40 +0200 Subject: [PATCH 17/84] fix grammar --- vyper/venom/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/parser.py b/vyper/venom/parser.py index 91c030826c..ae6ce39310 100644 --- a/vyper/venom/parser.py +++ b/vyper/venom/parser.py @@ -49,7 +49,7 @@ CONST: SIGNED_INT OPCODE: CNAME - VAR_IDENT: "%" (DIGIT|LETTER|"_"|":")+ + VAR_IDENT: "%" (DIGIT|LETTER|"_"|":"|"%")+ # handy for identifier to be an escaped string sometimes # (especially for machine-generated labels) From b4b4ae6f1f3a34be2afca444edab4840e14ac596 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 14:21:14 +0200 Subject: [PATCH 18/84] test --- tests/unit/compiler/asm/test_asm_optimizer.py | 4 +++- vyper/venom/parser.py | 2 +- vyper/venom/passes/mem2var.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/unit/compiler/asm/test_asm_optimizer.py b/tests/unit/compiler/asm/test_asm_optimizer.py index ee6b1653b0..5d15c3663c 100644 --- a/tests/unit/compiler/asm/test_asm_optimizer.py +++ b/tests/unit/compiler/asm/test_asm_optimizer.py @@ -120,7 +120,9 @@ def foo(): input_bundle = make_input_bundle({"library.vy": library}) res = compile_code(code, input_bundle=input_bundle, output_formats=["asm"]) asm = res["asm"] - assert "some_function()" in asm + + if not experimental_codegen: + assert "some_function()" in asm # Venom function inliner will remove this assert "unused1()" not in asm assert "unused2()" not in asm diff --git a/vyper/venom/parser.py b/vyper/venom/parser.py index ae6ce39310..2b47b2e335 100644 --- a/vyper/venom/parser.py +++ b/vyper/venom/parser.py @@ -49,7 +49,7 @@ CONST: SIGNED_INT OPCODE: CNAME - VAR_IDENT: "%" (DIGIT|LETTER|"_"|":"|"%")+ + VAR_IDENT: "%" (DIGIT|LETTER|"_"|":"|"%"|"@")+ # handy for identifier to be an escaped string sometimes # (especially for machine-generated labels) diff --git a/vyper/venom/passes/mem2var.py b/vyper/venom/passes/mem2var.py index 9f985e2b0b..8fe18a354a 100644 --- a/vyper/venom/passes/mem2var.py +++ b/vyper/venom/passes/mem2var.py @@ -28,7 +28,7 @@ def run_pass(self): def _mk_varname(self, varname: str): varname = varname.removeprefix("%") - varname = f"var{varname}_{self.var_name_count}" + varname = f"@var{varname}_{self.var_name_count}" self.var_name_count += 1 return varname From a4d4ea97b520c95d0d9087ba5cbba558b1270847 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 14:23:17 +0200 Subject: [PATCH 19/84] lint --- tests/unit/compiler/asm/test_asm_optimizer.py | 4 ++-- vyper/venom/__init__.py | 7 +++++-- vyper/venom/analysis/cfg.py | 1 - vyper/venom/analysis/fcg.py | 2 +- vyper/venom/basicblock.py | 8 ++++---- vyper/venom/context.py | 6 +++--- vyper/venom/passes/__init__.py | 2 +- vyper/venom/passes/base_pass.py | 4 ++-- vyper/venom/passes/func_inliner.py | 15 ++++++--------- 9 files changed, 24 insertions(+), 25 deletions(-) diff --git a/tests/unit/compiler/asm/test_asm_optimizer.py b/tests/unit/compiler/asm/test_asm_optimizer.py index 5d15c3663c..dfbb53ad5a 100644 --- a/tests/unit/compiler/asm/test_asm_optimizer.py +++ b/tests/unit/compiler/asm/test_asm_optimizer.py @@ -121,8 +121,8 @@ def foo(): res = compile_code(code, input_bundle=input_bundle, output_formats=["asm"]) asm = res["asm"] - if not experimental_codegen: - assert "some_function()" in asm # Venom function inliner will remove this + if not experimental_codegen: + assert "some_function()" in asm # Venom function inliner will remove this assert "unused1()" not in asm assert "unused2()" not in asm diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 1e86bc5058..4eed984aed 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -15,6 +15,7 @@ BranchOptimizationPass, DFTPass, FloatAllocas, + FuncInlinerPass, LoadElimination, LowerDloadPass, MakeSSA, @@ -25,7 +26,6 @@ SimplifyCFGPass, StoreElimination, StoreExpansionPass, - FuncInlinerPass, ) from vyper.venom.venom_to_assembly import VenomCompiler @@ -85,9 +85,11 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache DFTPass(ac, fn).run_pass() + def _run_global_passes(ctx: IRContext, optimize: OptimizationLevel, ir_analyses: dict) -> None: FuncInlinerPass(ir_analyses, ctx).run_pass() + def run_passes_on(ctx: IRContext, optimize: OptimizationLevel) -> None: ir_analyses = {} for fn in ctx.functions.values(): @@ -98,10 +100,11 @@ def run_passes_on(ctx: IRContext, optimize: OptimizationLevel) -> None: ir_analyses = {} for fn in ctx.functions.values(): ir_analyses[fn] = IRAnalysesCache(fn) - + for fn in ctx.functions.values(): _run_passes(fn, optimize, ir_analyses[fn]) + def generate_ir(ir: IRnode, optimize: OptimizationLevel) -> IRContext: # Convert "old" IR to "new" IR ctx = ir_node_to_venom(ir) diff --git a/vyper/venom/analysis/cfg.py b/vyper/venom/analysis/cfg.py index 714e47dd3d..aaa6614e10 100644 --- a/vyper/venom/analysis/cfg.py +++ b/vyper/venom/analysis/cfg.py @@ -63,4 +63,3 @@ def invalidate(self): self.analyses_cache.invalidate_analysis(LivenessAnalysis) self.analyses_cache.invalidate_analysis(DFGAnalysis) self._dfs = None - diff --git a/vyper/venom/analysis/fcg.py b/vyper/venom/analysis/fcg.py index 8898231157..7b4c833482 100644 --- a/vyper/venom/analysis/fcg.py +++ b/vyper/venom/analysis/fcg.py @@ -46,4 +46,4 @@ def _analyze_function(self, fn: IRFunction) -> None: self.call_sites[callee].add(inst) def invalidate(self): - pass \ No newline at end of file + pass diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 72e7ebb552..a44b4dfa4c 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -309,10 +309,10 @@ def get_outputs(self) -> list[IROperand]: return [self.output] if self.output else [] def make_nop(self): - self.annotation = str(self) # Keep original instruction as annotation for debugging - self.opcode = "nop" + self.annotation = str(self) # Keep original instruction as annotation for debugging + self.opcode = "nop" self.output = None - self.operands = [] + self.operands = [] def flip(self): """ @@ -381,7 +381,7 @@ def get_ast_source(self) -> Optional[IRnode]: if inst.ast_source: return inst.ast_source return self.parent.parent.ast_source - + def copy(self, prefix: str = "") -> "IRInstruction": ops: list[IROperand] = [] for op in self.operands: diff --git a/vyper/venom/context.py b/vyper/venom/context.py index 58cd14f626..ce08125b74 100644 --- a/vyper/venom/context.py +++ b/vyper/venom/context.py @@ -32,7 +32,7 @@ def __str__(self): class IRContext: functions: dict[IRLabel, IRFunction] - entry_function: IRFunction + entry_function: Optional[IRFunction] ctor_mem_size: Optional[int] immutables_len: Optional[int] data_segment: list[DataSection] @@ -73,7 +73,7 @@ def get_function(self, name: IRLabel) -> IRFunction: if name in self.functions: return self.functions[name] raise Exception(f"Function {name} not found in context") - + def get_functions(self) -> Iterator[IRFunction]: return iter(self.functions.values()) @@ -82,7 +82,7 @@ def get_next_label(self, suffix: str = "") -> IRLabel: suffix = f"_{suffix}" self.last_label += 1 return IRLabel(f"{self.last_label}{suffix}") - + def get_next_variable(self) -> IRVariable: self.last_variable += 1 return IRVariable(f"%{self.last_variable}") diff --git a/vyper/venom/passes/__init__.py b/vyper/venom/passes/__init__.py index bde765520c..489519b89b 100644 --- a/vyper/venom/passes/__init__.py +++ b/vyper/venom/passes/__init__.py @@ -2,6 +2,7 @@ from .branch_optimization import BranchOptimizationPass from .dft import DFTPass from .float_allocas import FloatAllocas +from .func_inliner import FuncInlinerPass from .literals_codesize import ReduceLiteralsCodesize from .load_elimination import LoadElimination from .lower_dload import LowerDloadPass @@ -14,4 +15,3 @@ from .simplify_cfg import SimplifyCFGPass from .store_elimination import StoreElimination from .store_expansion import StoreExpansionPass -from .func_inliner import FuncInlinerPass diff --git a/vyper/venom/passes/base_pass.py b/vyper/venom/passes/base_pass.py index 16116df964..eb8287e93b 100644 --- a/vyper/venom/passes/base_pass.py +++ b/vyper/venom/passes/base_pass.py @@ -1,5 +1,5 @@ -from vyper.venom.context import IRContext from vyper.venom.analysis import IRAnalysesCache +from vyper.venom.context import IRContext from vyper.venom.function import IRFunction @@ -32,4 +32,4 @@ def __init__(self, analyses_caches: dict[IRFunction, IRAnalysesCache], ctx: IRCo self.ctx = ctx def run_pass(self, *args, **kwargs): - raise NotImplementedError(f"Not implemented! {self.__class__}.run_pass()") \ No newline at end of file + raise NotImplementedError(f"Not implemented! {self.__class__}.run_pass()") diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 82bb8f9dd6..778d48cb08 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -1,12 +1,10 @@ -import sys -from vyper.venom.passes import FloatAllocas -from vyper.venom.function import IRFunction from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis -from vyper.venom.analysis.fcg import FCGAnalysis from vyper.venom.analysis.equivalent_vars import VarEquivalenceAnalysis +from vyper.venom.analysis.fcg import FCGAnalysis from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable -from vyper.venom.context import IRContext +from vyper.venom.function import IRFunction +from vyper.venom.passes import FloatAllocas from vyper.venom.passes.base_pass import IRGlobalPass @@ -21,7 +19,7 @@ class FuncInlinerPass(IRGlobalPass): def run_pass(self): entry = self.ctx.entry_function self.inline_count = 0 - + while True: self.fcg = self.analyses_caches[entry].force_analysis(FCGAnalysis) self.walk = self._build_call_walk(entry) @@ -29,7 +27,7 @@ def run_pass(self): candidates = list(self._get_inline_candidates()) if len(candidates) == 0: return - + candidate = candidates[0] calls = self.fcg.get_call_sites(candidate) self._inline_function(candidate, calls) @@ -101,7 +99,6 @@ def _inline_call_site(self, func, call_site): call_site_bb.instructions = call_site_bb.instructions[:call_idx] call_site_bb.append_instruction("jmp", func_copy.entry.label) - def _build_call_walk(self, function: IRFunction): """ DFS walk over the call graph. @@ -122,4 +119,4 @@ def dfs(fn): dfs(function) - return call_walk \ No newline at end of file + return call_walk From 7c79be7a27a94a660b6dc23b56e150bafcd5d478 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Dec 2024 08:25:20 -0500 Subject: [PATCH 20/84] mem2var debug --- vyper/venom/passes/mem2var.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/vyper/venom/passes/mem2var.py b/vyper/venom/passes/mem2var.py index 8fe18a354a..a131a40266 100644 --- a/vyper/venom/passes/mem2var.py +++ b/vyper/venom/passes/mem2var.py @@ -19,20 +19,20 @@ def run_pass(self): self.var_name_count = 0 for var, inst in dfg.outputs.items(): if inst.opcode == "alloca": - self._process_alloca_var(dfg, var) + self._process_alloca_var(dfg, inst, var) elif inst.opcode == "palloca": self._process_palloca_var(dfg, inst, var) self.analyses_cache.invalidate_analysis(DFGAnalysis) self.analyses_cache.invalidate_analysis(LivenessAnalysis) - def _mk_varname(self, varname: str): + def _mk_varname(self, varname: str, alloca_id: int): varname = varname.removeprefix("%") - varname = f"@var{varname}_{self.var_name_count}" + varname = f"@alloca_{alloca_id}_{varname}_{self.var_name_count}" self.var_name_count += 1 return varname - def _process_alloca_var(self, dfg: DFGAnalysis, var: IRVariable): + def _process_alloca_var(self, dfg: DFGAnalysis, alloca_inst, var: IRVariable): """ Process alloca allocated variable. If it is only used by mstore/mload/return instructions, it is promoted to a stack variable. @@ -42,7 +42,8 @@ def _process_alloca_var(self, dfg: DFGAnalysis, var: IRVariable): if not all([inst.opcode in ["mstore", "mload", "return"] for inst in uses]): return - var_name = self._mk_varname(var.name) + alloca_id = alloca_inst.operands[2] + var_name = self._mk_varname(var.value, alloca_id.value) var = IRVariable(var_name) for inst in uses: if inst.opcode == "mstore": @@ -68,12 +69,13 @@ def _process_palloca_var(self, dfg: DFGAnalysis, palloca_inst: IRInstruction, va if not all(inst.opcode in ["mstore", "mload"] for inst in uses): return - var_name = self._mk_varname(var.name) + ofst, _size, alloca_id = palloca_inst.operands + var_name = self._mk_varname(var.value, alloca_id.value) var = IRVariable(var_name) # some value given to us by the calling convention palloca_inst.opcode = "mload" - palloca_inst.operands = [palloca_inst.operands[0]] + palloca_inst.operands = [ofst] palloca_inst.output = var for inst in uses: From 29ece7cac52a8d4288ff5c2559c76b665847f4be Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Dec 2024 10:54:53 -0500 Subject: [PATCH 21/84] update varname in mem2var --- vyper/venom/passes/mem2var.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/mem2var.py b/vyper/venom/passes/mem2var.py index a131a40266..83990a51d4 100644 --- a/vyper/venom/passes/mem2var.py +++ b/vyper/venom/passes/mem2var.py @@ -28,7 +28,7 @@ def run_pass(self): def _mk_varname(self, varname: str, alloca_id: int): varname = varname.removeprefix("%") - varname = f"@alloca_{alloca_id}_{varname}_{self.var_name_count}" + varname = f"alloca_{alloca_id}_@{varname}_{self.var_name_count}" self.var_name_count += 1 return varname From 542c115dff7eada64018b93b2c713e8e14ab9f91 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 18:43:41 +0200 Subject: [PATCH 22/84] invalidate dfg --- vyper/venom/passes/store_elimination.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/vyper/venom/passes/store_elimination.py b/vyper/venom/passes/store_elimination.py index 22d4723013..ced536138e 100644 --- a/vyper/venom/passes/store_elimination.py +++ b/vyper/venom/passes/store_elimination.py @@ -14,25 +14,26 @@ class StoreElimination(IRPass): def run_pass(self): self.analyses_cache.request_analysis(CFGAnalysis) - dfg = self.analyses_cache.request_analysis(DFGAnalysis) + self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) - for var, inst in dfg.outputs.items(): + for var, inst in self.dfg.outputs.items(): if inst.opcode != "store": continue - self._process_store(dfg, inst, var, inst.operands[0]) + self._process_store(inst, var, inst.operands[0]) self.analyses_cache.invalidate_analysis(LivenessAnalysis) self.analyses_cache.invalidate_analysis(DFGAnalysis) + - def _process_store(self, dfg, inst, var: IRVariable, new_var: IRVariable): + def _process_store(self, inst, var: IRVariable, new_var: IRVariable): """ Process store instruction. If the variable is only used by a load instruction, forward the variable to the load instruction. """ - if any([inst.opcode == "phi" for inst in dfg.get_uses(new_var)]): + if any([inst.opcode == "phi" for inst in self.dfg.get_uses(new_var)]): return - uses = dfg.get_uses(var) + uses = self.dfg.get_uses(var) if any([inst.opcode == "phi" for inst in uses]): return for use_inst in uses: @@ -41,3 +42,4 @@ def _process_store(self, dfg, inst, var: IRVariable, new_var: IRVariable): use_inst.operands[i] = new_var inst.parent.remove_instruction(inst) + self.dfg = self.analyses_cache.force_analysis(DFGAnalysis) From a376e8874350bbc65e4f8ad363d790d6bd38801f Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 28 Dec 2024 12:02:15 -0500 Subject: [PATCH 23/84] polish store elimination - remove CFG dependency --- vyper/venom/passes/store_elimination.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vyper/venom/passes/store_elimination.py b/vyper/venom/passes/store_elimination.py index ced536138e..a4f217505b 100644 --- a/vyper/venom/passes/store_elimination.py +++ b/vyper/venom/passes/store_elimination.py @@ -1,4 +1,4 @@ -from vyper.venom.analysis import CFGAnalysis, DFGAnalysis, LivenessAnalysis +from vyper.venom.analysis import DFGAnalysis, LivenessAnalysis from vyper.venom.basicblock import IRVariable from vyper.venom.passes.base_pass import IRPass @@ -13,7 +13,6 @@ class StoreElimination(IRPass): # with LoadElimination def run_pass(self): - self.analyses_cache.request_analysis(CFGAnalysis) self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) for var, inst in self.dfg.outputs.items(): @@ -23,7 +22,6 @@ def run_pass(self): self.analyses_cache.invalidate_analysis(LivenessAnalysis) self.analyses_cache.invalidate_analysis(DFGAnalysis) - def _process_store(self, inst, var: IRVariable, new_var: IRVariable): """ @@ -36,10 +34,12 @@ def _process_store(self, inst, var: IRVariable, new_var: IRVariable): uses = self.dfg.get_uses(var) if any([inst.opcode == "phi" for inst in uses]): return - for use_inst in uses: + for use_inst in uses.copy(): for i, operand in enumerate(use_inst.operands): if operand == var: use_inst.operands[i] = new_var + self.dfg.add_use(new_var, use_inst) + self.dfg.remove_use(var, use_inst) + inst.parent.remove_instruction(inst) - self.dfg = self.analyses_cache.force_analysis(DFGAnalysis) From 33fd262b7daf32b8462a95b03450af41f7ea6d1a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 22:33:20 +0200 Subject: [PATCH 24/84] test update --- tests/unit/compiler/test_source_map.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/unit/compiler/test_source_map.py b/tests/unit/compiler/test_source_map.py index ae1999a26e..974e4f2ece 100644 --- a/tests/unit/compiler/test_source_map.py +++ b/tests/unit/compiler/test_source_map.py @@ -38,6 +38,7 @@ def test_jump_map(optimize, experimental_codegen): jump_map = source_map["pc_jump_map"] expected_jumps = 1 + expected_internals = 2 if optimize == OptimizationLevel.NONE: # some jumps which don't get optimized out when optimizer is off # (slightly different behavior depending if venom pipeline is enabled): @@ -45,9 +46,15 @@ def test_jump_map(optimize, experimental_codegen): expected_jumps = 3 else: expected_jumps = 2 + else: + if not experimental_codegen: + expected_internals = 2 + else: + expected_jumps = 0 + expected_internals = 0 assert len([v for v in jump_map.values() if v == "o"]) == expected_jumps - assert len([v for v in jump_map.values() if v == "i"]) == 2 + assert len([v for v in jump_map.values() if v == "i"]) == expected_internals code_lines = [i + "\n" for i in TEST_CODE.split("\n")] for pc in [k for k, v in jump_map.items() if v == "o"]: From 30e0c79ee066c341f69e3eb99272563a5fa00483 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 22:45:21 +0200 Subject: [PATCH 25/84] remove superflus passes --- vyper/venom/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 4eed984aed..f61cd4cf92 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -60,8 +60,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache MakeSSA(ac, fn).run_pass() SCCP(ac, fn).run_pass() - StoreElimination(ac, fn).run_pass() - LoadElimination(ac, fn).run_pass() + # LoadElimination(ac, fn).run_pass() StoreElimination(ac, fn).run_pass() MemMergePass(ac, fn).run_pass() SimplifyCFGPass(ac, fn).run_pass() From c6c3c8ad39d1012ccf90d6be29180c7e6e831564 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 28 Dec 2024 23:05:39 +0200 Subject: [PATCH 26/84] testing --- vyper/venom/passes/func_inliner.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 778d48cb08..fb1efa0951 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -29,6 +29,7 @@ def run_pass(self): return candidate = candidates[0] + calls = self.fcg.get_call_sites(candidate) self._inline_function(candidate, calls) self.ctx.remove_function(candidate) @@ -36,6 +37,12 @@ def run_pass(self): def _get_inline_candidates(self): for func in self.walk: calls = self.fcg.get_call_sites(func) + if func.name.name in [ + # "internal 14 __exchange(uint256,DynArray[uint256, 8],DynArray[uint256, 8],uint128,uint128)_runtime", + # "internal 11 exp(int256)_runtime", + "internal 6 get_y(uint128,uint128,uint256,DynArray[uint256, 8],uint256,uint256)_runtime" + ]: + continue if len(calls) == 1: yield func From 2ef4df6fcafefd0ddc7f910513573b26e4d10ace Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 29 Dec 2024 01:28:58 +0200 Subject: [PATCH 27/84] helper --- vyper/venom/basicblock.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index a44b4dfa4c..6d14a57d55 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -567,6 +567,11 @@ def remove_instruction(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" self.instructions.remove(instruction) + def remove_instructions_after(self, instruction: IRInstruction) -> None: + assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" + assert instruction in self.instructions, "instruction must be in basic block" + self.instructions = self.instructions[:self.instructions.index(instruction)-1] + @property def phi_instructions(self) -> Iterator[IRInstruction]: for inst in self.instructions: From 7341c04af401f6c821076d9e1542e0c71b466e8e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 29 Dec 2024 01:29:14 +0200 Subject: [PATCH 28/84] handle reverts --- vyper/venom/passes/func_inliner.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index fb1efa0951..99d593f2b1 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -2,7 +2,7 @@ from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.equivalent_vars import VarEquivalenceAnalysis from vyper.venom.analysis.fcg import FCGAnalysis -from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes import FloatAllocas from vyper.venom.passes.base_pass import IRGlobalPass @@ -38,9 +38,11 @@ def _get_inline_candidates(self): for func in self.walk: calls = self.fcg.get_call_sites(func) if func.name.name in [ + "internal 1 middle()_runtime", + #"internal 0 sum(uint256)_runtime", # "internal 14 __exchange(uint256,DynArray[uint256, 8],DynArray[uint256, 8],uint128,uint128)_runtime", # "internal 11 exp(int256)_runtime", - "internal 6 get_y(uint128,uint128,uint256,DynArray[uint256, 8],uint256,uint256)_runtime" + # "internal 6 get_y(uint128,uint128,uint256,DynArray[uint256, 8],uint256,uint256)_runtime" ]: continue if len(calls) == 1: @@ -99,9 +101,13 @@ def _inline_call_site(self, func, call_site): inst.opcode = "jmp" inst.operands = [call_site_return.label] elif inst.opcode in ["jmp", "jnz", "djmp", "phi"]: - for i, op in enumerate(inst.operands): - if isinstance(op, IRLabel): - inst.operands[i] = IRLabel(f"{prefix}{op.name}") + for i, label in enumerate(inst.operands): + if isinstance(label, IRLabel): + inst.operands[i] = IRLabel(f"{prefix}{label.name}") + elif inst.opcode == "revert": + bb.remove_instructions_after(inst) + bb.append_instruction("stop") + break call_site_bb.instructions = call_site_bb.instructions[:call_idx] call_site_bb.append_instruction("jmp", func_copy.entry.label) From 757563dca5bdc80b8a776337219db54805e333a9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 29 Dec 2024 10:47:20 +0200 Subject: [PATCH 29/84] add tests --- tests/unit/compiler/venom/test_inliner.py | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/unit/compiler/venom/test_inliner.py diff --git a/tests/unit/compiler/venom/test_inliner.py b/tests/unit/compiler/venom/test_inliner.py new file mode 100644 index 0000000000..eb0bcb8eb4 --- /dev/null +++ b/tests/unit/compiler/venom/test_inliner.py @@ -0,0 +1,46 @@ +import pytest + +from tests.evm_backends.base_env import ExecutionReverted + + +def test_call_in_call(get_contract): + code = """ +@internal +def _foo(a: uint256,) -> uint256: + return 1 + a + +@internal +def _foo2() -> uint256: + return 4 + +@external +def foo() -> uint256: + return self._foo(self._foo2()) + """ + + c = get_contract(code) + assert c.foo() == 5 + +def test_call_in_call_with_raise(get_contract): + code = """ +@internal +def sum(a: uint256) -> uint256: + if a > 1: + return a + 1 + raise + +@internal +def middle(a: uint256) -> uint256: + return self.sum(a) + +@external +def test(a: uint256) -> uint256: + return self.middle(a) + """ + + c = get_contract(code) + + assert c.test(2) == 3 + + with pytest.raises(ExecutionReverted): + c.test(0) From bf0ebb33ac0f45b0beffabaed1b11d06cb3d6513 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 29 Dec 2024 17:22:54 +0200 Subject: [PATCH 30/84] cleanup --- tests/unit/compiler/venom/test_inliner.py | 1 + vyper/venom/basicblock.py | 2 +- vyper/venom/passes/func_inliner.py | 10 +--------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/unit/compiler/venom/test_inliner.py b/tests/unit/compiler/venom/test_inliner.py index eb0bcb8eb4..c33104274c 100644 --- a/tests/unit/compiler/venom/test_inliner.py +++ b/tests/unit/compiler/venom/test_inliner.py @@ -21,6 +21,7 @@ def foo() -> uint256: c = get_contract(code) assert c.foo() == 5 + def test_call_in_call_with_raise(get_contract): code = """ @internal diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 6d14a57d55..c16f7b962d 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -570,7 +570,7 @@ def remove_instruction(self, instruction: IRInstruction) -> None: def remove_instructions_after(self, instruction: IRInstruction) -> None: assert isinstance(instruction, IRInstruction), "instruction must be an IRInstruction" assert instruction in self.instructions, "instruction must be in basic block" - self.instructions = self.instructions[:self.instructions.index(instruction)-1] + self.instructions = self.instructions[: self.instructions.index(instruction) - 1] @property def phi_instructions(self) -> Iterator[IRInstruction]: diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 99d593f2b1..c2726f56cc 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -2,7 +2,7 @@ from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.equivalent_vars import VarEquivalenceAnalysis from vyper.venom.analysis.fcg import FCGAnalysis -from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRVariable +from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes import FloatAllocas from vyper.venom.passes.base_pass import IRGlobalPass @@ -37,14 +37,6 @@ def run_pass(self): def _get_inline_candidates(self): for func in self.walk: calls = self.fcg.get_call_sites(func) - if func.name.name in [ - "internal 1 middle()_runtime", - #"internal 0 sum(uint256)_runtime", - # "internal 14 __exchange(uint256,DynArray[uint256, 8],DynArray[uint256, 8],uint128,uint128)_runtime", - # "internal 11 exp(int256)_runtime", - # "internal 6 get_y(uint128,uint128,uint256,DynArray[uint256, 8],uint256,uint256)_runtime" - ]: - continue if len(calls) == 1: yield func From 289d683958a4f6b5afa61d516ff3ca45345704ea Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 29 Dec 2024 23:03:00 +0200 Subject: [PATCH 31/84] bounds --- vyper/venom/analysis/fcg.py | 2 +- vyper/venom/passes/func_inliner.py | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/vyper/venom/analysis/fcg.py b/vyper/venom/analysis/fcg.py index 7b4c833482..98e2d51914 100644 --- a/vyper/venom/analysis/fcg.py +++ b/vyper/venom/analysis/fcg.py @@ -30,7 +30,7 @@ def analyze(self) -> None: self._analyze_function(fn) def get_call_sites(self, fn: IRFunction) -> OrderedSet[IRInstruction]: - return self.call_sites[fn] + return self.call_sites.get(fn, OrderedSet()) def get_callees(self, fn: IRFunction) -> OrderedSet[IRFunction]: return self.callees[fn] diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index c2726f56cc..dbea110e78 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -20,25 +20,28 @@ def run_pass(self): entry = self.ctx.entry_function self.inline_count = 0 - while True: + function_count = len(self.ctx.functions) + + for _ in range(function_count): self.fcg = self.analyses_caches[entry].force_analysis(FCGAnalysis) self.walk = self._build_call_walk(entry) - candidates = list(self._get_inline_candidates()) - if len(candidates) == 0: + candidate = self._select_inline_candidate() + if candidate is None: return - candidate = candidates[0] - calls = self.fcg.get_call_sites(candidate) self._inline_function(candidate, calls) self.ctx.remove_function(candidate) - def _get_inline_candidates(self): + def _select_inline_candidate(self): for func in self.walk: calls = self.fcg.get_call_sites(func) - if len(calls) == 1: - yield func + if len(calls) == 0: + continue + if len(calls) <= 1: + return func + return None def _inline_function(self, func, call_sites): """ From 674fb50a1acd58769826a59ff15f82a62c0c815e Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 29 Dec 2024 23:49:13 +0200 Subject: [PATCH 32/84] refactors --- vyper/venom/passes/func_inliner.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index dbea110e78..6733762219 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -21,11 +21,10 @@ def run_pass(self): self.inline_count = 0 function_count = len(self.ctx.functions) + self.fcg = self.analyses_caches[entry].request_analysis(FCGAnalysis) + self.walk = self._build_call_walk(entry) for _ in range(function_count): - self.fcg = self.analyses_caches[entry].force_analysis(FCGAnalysis) - self.walk = self._build_call_walk(entry) - candidate = self._select_inline_candidate() if candidate is None: return @@ -33,6 +32,9 @@ def run_pass(self): calls = self.fcg.get_call_sites(candidate) self._inline_function(candidate, calls) self.ctx.remove_function(candidate) + self.walk.remove(candidate) + + self.fcg = self.analyses_caches[entry].force_analysis(FCGAnalysis) def _select_inline_candidate(self): for func in self.walk: From 05adb1a6abf448bbd63901c4b82023000a290fdc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 00:21:32 +0200 Subject: [PATCH 33/84] refactor, lint, cleanup --- vyper/venom/passes/func_inliner.py | 32 +++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 6733762219..2e7ce61f65 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -1,8 +1,11 @@ +from typing import List, Optional + +from vyper.exceptions import CompilerPanic from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.equivalent_vars import VarEquivalenceAnalysis from vyper.venom.analysis.fcg import FCGAnalysis -from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel from vyper.venom.function import IRFunction from vyper.venom.passes import FloatAllocas from vyper.venom.passes.base_pass import IRGlobalPass @@ -13,6 +16,11 @@ class FuncInlinerPass(IRGlobalPass): This pass inlines functions into the call sites. """ + RETURN_BUFFER_ANNOTATION = "return_buffer" + RETURN_PC_ANNOTATION = "return_pc" + RETURN_OFFSET_MARKER = "ret_ofst" + RETURN_SIZE_MARKER = "ret_size" + inline_count: int fcg: FCGAnalysis @@ -36,7 +44,7 @@ def run_pass(self): self.fcg = self.analyses_caches[entry].force_analysis(FCGAnalysis) - def _select_inline_candidate(self): + def _select_inline_candidate(self) -> Optional[IRFunction]: for func in self.walk: calls = self.fcg.get_call_sites(func) if len(calls) == 0: @@ -45,7 +53,7 @@ def _select_inline_candidate(self): return func return None - def _inline_function(self, func, call_sites): + def _inline_function(self, func: IRFunction, call_sites: List[IRInstruction]) -> None: """ Inline function into call sites. """ @@ -57,10 +65,13 @@ def _inline_function(self, func, call_sites): self.analyses_caches[fn].invalidate_analysis(CFGAnalysis) self.analyses_caches[fn].invalidate_analysis(VarEquivalenceAnalysis) - def _inline_call_site(self, func, call_site): + def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: """ Inline function into call site. """ + if call_site.opcode != "invoke": + raise CompilerPanic(f"Expected invoke instruction, got {call_site.opcode}") + prefix = f"inline_{self.inline_count}_" self.inline_count += 1 call_site_bb = call_site.parent @@ -82,17 +93,20 @@ def _inline_call_site(self, func, call_site): call_site_func.append_basic_block(bb) for inst in bb.instructions: if inst.opcode == "param": - if inst.annotation == "return_buffer": + if inst.annotation == self.RETURN_BUFFER_ANNOTATION: inst.opcode = "store" inst.operands = [call_site.operands[1]] - inst.output = IRVariable(inst.output.name, inst.output.version + 1) - elif inst.annotation == "return_pc": + elif inst.annotation == self.RETURN_PC_ANNOTATION: inst.make_nop() elif inst.opcode == "palloca": inst.opcode = "store" inst.operands = [inst.operands[0]] elif inst.opcode == "store": - if "ret_ofst" in inst.output.name or "ret_size" in inst.output.name: + assert inst.output is not None # mypy is not smart enough + if ( + self.RETURN_OFFSET_MARKER in inst.output.name + or self.RETURN_SIZE_MARKER in inst.output.name + ): inst.make_nop() elif inst.opcode == "ret": inst.opcode = "jmp" @@ -109,7 +123,7 @@ def _inline_call_site(self, func, call_site): call_site_bb.instructions = call_site_bb.instructions[:call_idx] call_site_bb.append_instruction("jmp", func_copy.entry.label) - def _build_call_walk(self, function: IRFunction): + def _build_call_walk(self, function: IRFunction) -> List[IRFunction]: """ DFS walk over the call graph. """ From 4a2041f05e77f65978a97afe56faea3d9aabb8c6 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 01:18:22 +0200 Subject: [PATCH 34/84] debug --- vyper/venom/basicblock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index c16f7b962d..52977a0797 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -309,7 +309,8 @@ def get_outputs(self) -> list[IROperand]: return [self.output] if self.output else [] def make_nop(self): - self.annotation = str(self) # Keep original instruction as annotation for debugging + # self.annotation = str(self) # Keep original instruction as annotation for debugging + self.annotation = None self.opcode = "nop" self.output = None self.operands = [] From 4878398639239b75d15a30ff496f43e435291720 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 01:27:52 +0200 Subject: [PATCH 35/84] work --- tests/unit/compiler/venom/test_inliner.py | 20 ++++++++++++++++++++ vyper/venom/passes/func_inliner.py | 17 ++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/tests/unit/compiler/venom/test_inliner.py b/tests/unit/compiler/venom/test_inliner.py index c33104274c..5b03dbcc58 100644 --- a/tests/unit/compiler/venom/test_inliner.py +++ b/tests/unit/compiler/venom/test_inliner.py @@ -45,3 +45,23 @@ def test(a: uint256) -> uint256: with pytest.raises(ExecutionReverted): c.test(0) + + +# TODO: not allowed at all in Vyper at the moment +# def test_call_recursive(get_contract): +# code = """ +# @internal +# def foo(a: uint256) -> uint256: +# if a > 0: +# return self.foo(a - 1) +# else: +# return 1 + +# @external +# def test() -> uint256: +# return self.foo(10) +# """ + +# c = get_contract(code) + +# assert c.test() == 1 diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 2e7ce61f65..ce82a04cc6 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -1,6 +1,7 @@ from typing import List, Optional from vyper.exceptions import CompilerPanic +from vyper.utils import OrderedSet from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.equivalent_vars import VarEquivalenceAnalysis @@ -13,7 +14,14 @@ class FuncInlinerPass(IRGlobalPass): """ - This pass inlines functions into the call sites. + This pass inlines functions into their call sites to reduce function call overhead. + + Limitations: + - Does not handle recursive functions + + Side effects: + - Modifies the control flow graph + - Invalidates DFG, CFG and VarEquivalence analyses """ RETURN_BUFFER_ANNOTATION = "return_buffer" @@ -69,6 +77,9 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: """ Inline function into call site. """ + if func == call_site.parent.parent: + raise CompilerPanic("Recursive function inlining is not supported") + if call_site.opcode != "invoke": raise CompilerPanic(f"Expected invoke instruction, got {call_site.opcode}") @@ -123,7 +134,7 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: call_site_bb.instructions = call_site_bb.instructions[:call_idx] call_site_bb.append_instruction("jmp", func_copy.entry.label) - def _build_call_walk(self, function: IRFunction) -> List[IRFunction]: + def _build_call_walk(self, function: IRFunction) -> OrderedSet[IRFunction]: """ DFS walk over the call graph. """ @@ -143,4 +154,4 @@ def dfs(fn): dfs(function) - return call_walk + return OrderedSet(call_walk) From 385e59f2317b57df5ec604d8dabe1c8dd5dbc41d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 10:45:27 +0200 Subject: [PATCH 36/84] recursive inline out --- vyper/venom/passes/func_inliner.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index ce82a04cc6..1da17ad657 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -77,8 +77,11 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: """ Inline function into call site. """ - if func == call_site.parent.parent: - raise CompilerPanic("Recursive function inlining is not supported") + # TODO: not allowed at all in Vyper at the moment + # but we could support it if we want to with Venom. + # (I think we should support tail call optimizable cases at least) + # if func == call_site.parent.parent: + # raise CompilerPanic("Recursive function inlining is not supported") if call_site.opcode != "invoke": raise CompilerPanic(f"Expected invoke instruction, got {call_site.opcode}") From af699c64457218647a2a5fcd9c6b42cc03598b62 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 10:55:56 +0200 Subject: [PATCH 37/84] add has block method --- vyper/venom/function.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 1ad39c5560..14bf72d744 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -57,6 +57,9 @@ def remove_basic_block(self, bb: IRBasicBlock): assert isinstance(bb, IRBasicBlock), bb del self._basic_block_dict[bb.label.name] + def has_basic_block(self, label: str) -> bool: + return label in self._basic_block_dict + def get_basic_block(self, label: Optional[str] = None) -> IRBasicBlock: """ Get basic block by label. From 33d8ffdb2788747eab99a263ee2cfa34779c77ba Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 10:57:18 +0200 Subject: [PATCH 38/84] make "consts".. "private" --- vyper/venom/passes/func_inliner.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 1da17ad657..361486cb93 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -24,10 +24,10 @@ class FuncInlinerPass(IRGlobalPass): - Invalidates DFG, CFG and VarEquivalence analyses """ - RETURN_BUFFER_ANNOTATION = "return_buffer" - RETURN_PC_ANNOTATION = "return_pc" - RETURN_OFFSET_MARKER = "ret_ofst" - RETURN_SIZE_MARKER = "ret_size" + _RETURN_BUFFER_ANNOTATION = "return_buffer" + _RETURN_PC_ANNOTATION = "return_pc" + _RETURN_OFFSET_MARKER = "ret_ofst" + _RETURN_SIZE_MARKER = "ret_size" inline_count: int fcg: FCGAnalysis @@ -107,10 +107,10 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: call_site_func.append_basic_block(bb) for inst in bb.instructions: if inst.opcode == "param": - if inst.annotation == self.RETURN_BUFFER_ANNOTATION: + if inst.annotation == self._RETURN_BUFFER_ANNOTATION: inst.opcode = "store" inst.operands = [call_site.operands[1]] - elif inst.annotation == self.RETURN_PC_ANNOTATION: + elif inst.annotation == self._RETURN_PC_ANNOTATION: inst.make_nop() elif inst.opcode == "palloca": inst.opcode = "store" @@ -118,8 +118,8 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: elif inst.opcode == "store": assert inst.output is not None # mypy is not smart enough if ( - self.RETURN_OFFSET_MARKER in inst.output.name - or self.RETURN_SIZE_MARKER in inst.output.name + self._RETURN_OFFSET_MARKER in inst.output.name + or self._RETURN_SIZE_MARKER in inst.output.name ): inst.make_nop() elif inst.opcode == "ret": @@ -127,7 +127,7 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: inst.operands = [call_site_return.label] elif inst.opcode in ["jmp", "jnz", "djmp", "phi"]: for i, label in enumerate(inst.operands): - if isinstance(label, IRLabel): + if isinstance(label, IRLabel) and func.has_basic_block(label.name): inst.operands[i] = IRLabel(f"{prefix}{label.name}") elif inst.opcode == "revert": bb.remove_instructions_after(inst) From dab4060377cab1424ee36b02c3775e87abfac739 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 11:39:14 +0200 Subject: [PATCH 39/84] temp --- vyper/venom/basicblock.py | 6 ++++++ vyper/venom/function.py | 4 ++++ vyper/venom/passes/base_pass.py | 6 +++++- vyper/venom/passes/func_inliner.py | 27 ++++++++++++++++++++++++--- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 52977a0797..fedf66a74f 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -223,6 +223,7 @@ class IRInstruction: annotation: Optional[str] ast_source: Optional[IRnode] error_msg: Optional[str] + code_size_cost: int def __init__( self, @@ -239,6 +240,7 @@ def __init__( self.annotation = None self.ast_source = None self.error_msg = None + self.code_size_cost = 1 @property def is_volatile(self) -> bool: @@ -601,6 +603,10 @@ def pseudo_instructions(self) -> Iterator[IRInstruction]: def body_instructions(self) -> Iterator[IRInstruction]: return (inst for inst in self.instructions[:-1] if not inst.is_pseudo) + @property + def code_size_cost(self) -> int: + return sum(inst.code_size_cost for inst in self.instructions) + def replace_operands(self, replacements: dict) -> None: """ Update operands with replacements. diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 14bf72d744..ef97ce139f 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -83,6 +83,10 @@ def get_basic_blocks(self) -> Iterator[IRBasicBlock]: def num_basic_blocks(self) -> int: return len(self._basic_block_dict) + @property + def code_size_cost(self) -> int: + return sum(bb.code_size_cost for bb in self.get_basic_blocks()) + def get_terminal_basicblocks(self) -> Iterator[IRBasicBlock]: """ Get basic blocks that are terminal. diff --git a/vyper/venom/passes/base_pass.py b/vyper/venom/passes/base_pass.py index eb8287e93b..3ce0f4890b 100644 --- a/vyper/venom/passes/base_pass.py +++ b/vyper/venom/passes/base_pass.py @@ -1,3 +1,5 @@ +from typing import Optional +from vyper.compiler.settings import Settings, get_global_settings from vyper.venom.analysis import IRAnalysesCache from vyper.venom.context import IRContext from vyper.venom.function import IRFunction @@ -26,10 +28,12 @@ class IRGlobalPass: ctx: IRContext analyses_caches: dict[IRFunction, IRAnalysesCache] + settings: Settings - def __init__(self, analyses_caches: dict[IRFunction, IRAnalysesCache], ctx: IRContext): + def __init__(self, analyses_caches: dict[IRFunction, IRAnalysesCache], ctx: IRContext, settings: Optional[Settings] = None): self.analyses_caches = analyses_caches self.ctx = ctx + self.settings = settings or get_global_settings() def run_pass(self, *args, **kwargs): raise NotImplementedError(f"Not implemented! {self.__class__}.run_pass()") diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 361486cb93..2adc972a31 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -1,5 +1,6 @@ from typing import List, Optional +from vyper.compiler.settings import OptimizationLevel from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet from vyper.venom.analysis.cfg import CFGAnalysis @@ -45,6 +46,8 @@ def run_pass(self): if candidate is None: return + print(f"Inlining function {candidate.name} with cost {candidate.code_size_cost}") + calls = self.fcg.get_call_sites(candidate) self._inline_function(candidate, calls) self.ctx.remove_function(candidate) @@ -54,11 +57,29 @@ def run_pass(self): def _select_inline_candidate(self) -> Optional[IRFunction]: for func in self.walk: - calls = self.fcg.get_call_sites(func) - if len(calls) == 0: + call_count = len(self.fcg.get_call_sites(func)) + if call_count == 0: continue - if len(calls) <= 1: + + # Always inline if there is only one call site. + if call_count == 1: return func + + # Decide whether to inline based on the optimization level. + if self.settings.optimize == OptimizationLevel.CODESIZE: + if func.code_size_cost <= 100: + return func + elif self.settings.optimize == OptimizationLevel.GAS: + # Inline if the function is not too big. + if func.code_size_cost <= 1000: + return func + elif self.settings.optimize == OptimizationLevel.NONE: + continue + else: + raise CompilerPanic(f"Unsupported inlining optimization level: {self.settings.optimize}") + + # Inline if the function is not too big. + return None def _inline_function(self, func: IRFunction, call_sites: List[IRInstruction]) -> None: From 476b9debb280d8e59011c0ad9a785f87ed5f68a4 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 18:35:37 +0200 Subject: [PATCH 40/84] inliner --- vyper/venom/passes/base_pass.py | 11 +++++++++-- vyper/venom/passes/func_inliner.py | 26 ++++++++++++++++++++------ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/vyper/venom/passes/base_pass.py b/vyper/venom/passes/base_pass.py index 3ce0f4890b..2999d3a4b1 100644 --- a/vyper/venom/passes/base_pass.py +++ b/vyper/venom/passes/base_pass.py @@ -1,4 +1,5 @@ from typing import Optional + from vyper.compiler.settings import Settings, get_global_settings from vyper.venom.analysis import IRAnalysesCache from vyper.venom.context import IRContext @@ -30,10 +31,16 @@ class IRGlobalPass: analyses_caches: dict[IRFunction, IRAnalysesCache] settings: Settings - def __init__(self, analyses_caches: dict[IRFunction, IRAnalysesCache], ctx: IRContext, settings: Optional[Settings] = None): + def __init__( + self, + analyses_caches: dict[IRFunction, IRAnalysesCache], + ctx: IRContext, + settings: Optional[Settings] = None, + ): self.analyses_caches = analyses_caches self.ctx = ctx - self.settings = settings or get_global_settings() + settings = settings or get_global_settings() + self.settings = settings or Settings() def run_pass(self, *args, **kwargs): raise NotImplementedError(f"Not implemented! {self.__class__}.run_pass()") diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 2adc972a31..c277578652 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -16,10 +16,10 @@ class FuncInlinerPass(IRGlobalPass): """ This pass inlines functions into their call sites to reduce function call overhead. - + Limitations: - Does not handle recursive functions - + Side effects: - Modifies the control flow graph - Invalidates DFG, CFG and VarEquivalence analyses @@ -34,6 +34,7 @@ class FuncInlinerPass(IRGlobalPass): fcg: FCGAnalysis def run_pass(self): + return entry = self.ctx.entry_function self.inline_count = 0 @@ -46,7 +47,7 @@ def run_pass(self): if candidate is None: return - print(f"Inlining function {candidate.name} with cost {candidate.code_size_cost}") + # print(f"Inlining function {candidate.name} with cost {candidate.code_size_cost}") calls = self.fcg.get_call_sites(candidate) self._inline_function(candidate, calls) @@ -61,22 +62,35 @@ def _select_inline_candidate(self) -> Optional[IRFunction]: if call_count == 0: continue + # if func.name.name not in [ + # #"internal 15 _transfer_out(uint128,uint256,address)_runtime", + # # "internal 38 _domain_separator()_runtime", + # #"internal 34 _transfer(address,address,uint256)_runtime", + # #"internal 26 _withdraw_admin_fees()_runtime", + # #"internal 2 _xp_mem(DynArray[uint256, 8],DynArray[uint256, 8])_runtime", + # "internal 19 get_D_mem(DynArray[uint256, 8],DynArray[uint256, 8],uint256)_runtime", + # "internal 5 get_D(DynArray[uint256, 8],uint256)_runtime" + # ]: + # continue + # Always inline if there is only one call site. if call_count == 1: return func # Decide whether to inline based on the optimization level. if self.settings.optimize == OptimizationLevel.CODESIZE: - if func.code_size_cost <= 100: + if func.code_size_cost <= 10: return func elif self.settings.optimize == OptimizationLevel.GAS: # Inline if the function is not too big. - if func.code_size_cost <= 1000: + if func.code_size_cost <= 25: return func elif self.settings.optimize == OptimizationLevel.NONE: continue else: - raise CompilerPanic(f"Unsupported inlining optimization level: {self.settings.optimize}") + raise CompilerPanic( + f"Unsupported inlining optimization level: {self.settings.optimize}" + ) # Inline if the function is not too big. From 6fd42efb986d9be4d8b031ecfaebd221c3656966 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 18:53:15 +0200 Subject: [PATCH 41/84] fix var equivalance --- vyper/venom/analysis/equivalent_vars.py | 37 +++++++++---------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/vyper/venom/analysis/equivalent_vars.py b/vyper/venom/analysis/equivalent_vars.py index 02d76a5c6f..d327406ece 100644 --- a/vyper/venom/analysis/equivalent_vars.py +++ b/vyper/venom/analysis/equivalent_vars.py @@ -1,40 +1,29 @@ from vyper.venom.analysis import DFGAnalysis, IRAnalysis -from vyper.venom.basicblock import IRVariable class VarEquivalenceAnalysis(IRAnalysis): """ - Generate equivalence sets of variables. This is used to avoid swapping - variables which are the same during venom_to_assembly. Theoretically, - the DFTPass should order variable declarations optimally, but, it is - not aware of the "pickaxe" heuristic in venom_to_assembly, so they can - interfere. + Generate equivalence sets of variables. Essentially, variables chained + by store instructions are equivalent. These are used to avoid swapping + variables which are the same during venom_to_assembly, and are produced + by the StoreExpansionPass. """ def analyze(self): dfg = self.analyses_cache.request_analysis(DFGAnalysis) - equivalence_set: dict[IRVariable, int] = {} + self._equivalence_set = {} - for bag, (var, inst) in enumerate(dfg._dfg_outputs.items()): + for output, inst in dfg.outputs.items(): if inst.opcode != "store": continue - source = inst.operands[0] - - assert var not in equivalence_set, f"var `{var}` in equivalence_set" # invariant - if source in equivalence_set: - equivalence_set[var] = equivalence_set[source] - continue - else: - equivalence_set[var] = bag - equivalence_set[source] = bag - - self._equivalence_set = equivalence_set + self._equivalence_set[output] = self._get_equivalent(inst.operands[0]) + + def _get_equivalent(self, var): + if var in self._equivalence_set: + return self._equivalence_set[var] + return var def equivalent(self, var1, var2): - if var1 not in self._equivalence_set: - return False - if var2 not in self._equivalence_set: - return False - return self._equivalence_set[var1] == self._equivalence_set[var2] + return self._get_equivalent(var1) == self._get_equivalent(var2) From 4e602b97aae02142b6fc60ac49e5e6ab67695f8a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 19:07:54 +0200 Subject: [PATCH 42/84] lint --- vyper/venom/analysis/equivalent_vars.py | 2 +- vyper/venom/passes/func_inliner.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/venom/analysis/equivalent_vars.py b/vyper/venom/analysis/equivalent_vars.py index d327406ece..8a333fa401 100644 --- a/vyper/venom/analysis/equivalent_vars.py +++ b/vyper/venom/analysis/equivalent_vars.py @@ -19,7 +19,7 @@ def analyze(self): continue self._equivalence_set[output] = self._get_equivalent(inst.operands[0]) - + def _get_equivalent(self, var): if var in self._equivalence_set: return self._equivalence_set[var] diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index c277578652..0d25a78507 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -34,7 +34,6 @@ class FuncInlinerPass(IRGlobalPass): fcg: FCGAnalysis def run_pass(self): - return entry = self.ctx.entry_function self.inline_count = 0 From 5d0ac96634f84a32665fb6f8df46a9d09c96f260 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 19:26:12 +0200 Subject: [PATCH 43/84] comment wip stuff --- vyper/venom/passes/func_inliner.py | 42 ++++++++++-------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 0d25a78507..e6daa1e175 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -1,6 +1,5 @@ from typing import List, Optional -from vyper.compiler.settings import OptimizationLevel from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet from vyper.venom.analysis.cfg import CFGAnalysis @@ -61,37 +60,24 @@ def _select_inline_candidate(self) -> Optional[IRFunction]: if call_count == 0: continue - # if func.name.name not in [ - # #"internal 15 _transfer_out(uint128,uint256,address)_runtime", - # # "internal 38 _domain_separator()_runtime", - # #"internal 34 _transfer(address,address,uint256)_runtime", - # #"internal 26 _withdraw_admin_fees()_runtime", - # #"internal 2 _xp_mem(DynArray[uint256, 8],DynArray[uint256, 8])_runtime", - # "internal 19 get_D_mem(DynArray[uint256, 8],DynArray[uint256, 8],uint256)_runtime", - # "internal 5 get_D(DynArray[uint256, 8],uint256)_runtime" - # ]: - # continue - # Always inline if there is only one call site. if call_count == 1: return func - # Decide whether to inline based on the optimization level. - if self.settings.optimize == OptimizationLevel.CODESIZE: - if func.code_size_cost <= 10: - return func - elif self.settings.optimize == OptimizationLevel.GAS: - # Inline if the function is not too big. - if func.code_size_cost <= 25: - return func - elif self.settings.optimize == OptimizationLevel.NONE: - continue - else: - raise CompilerPanic( - f"Unsupported inlining optimization level: {self.settings.optimize}" - ) - - # Inline if the function is not too big. + # # Decide whether to inline based on the optimization level. + # if self.settings.optimize == OptimizationLevel.CODESIZE: + # if func.code_size_cost <= 10: + # return func + # elif self.settings.optimize == OptimizationLevel.GAS: + # # Inline if the function is not too big. + # if func.code_size_cost <= 25: + # return func + # elif self.settings.optimize == OptimizationLevel.NONE: + # continue + # else: + # raise CompilerPanic( + # f"Unsupported inlining optimization level: {self.settings.optimize}" + # ) return None From 828e6238109a6693f2d501915dd6425bde78c39b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 30 Dec 2024 20:04:24 +0200 Subject: [PATCH 44/84] make cost a function --- vyper/venom/basicblock.py | 8 ++++++-- vyper/venom/passes/func_inliner.py | 27 +++++++++++++-------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index fedf66a74f..ffec6aa238 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -223,7 +223,6 @@ class IRInstruction: annotation: Optional[str] ast_source: Optional[IRnode] error_msg: Optional[str] - code_size_cost: int def __init__( self, @@ -240,7 +239,6 @@ def __init__( self.annotation = None self.ast_source = None self.error_msg = None - self.code_size_cost = 1 @property def is_volatile(self) -> bool: @@ -376,6 +374,12 @@ def remove_phi_operand(self, label: IRLabel) -> None: del self.operands[i : i + 2] return + @property + def code_size_cost(self) -> int: + if self.opcode == "store": + return 1 + return 2 + def get_ast_source(self) -> Optional[IRnode]: if self.ast_source: return self.ast_source diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index e6daa1e175..2bb3d52808 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -1,5 +1,6 @@ from typing import List, Optional +from vyper.compiler.settings import OptimizationLevel from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet from vyper.venom.analysis.cfg import CFGAnalysis @@ -64,20 +65,18 @@ def _select_inline_candidate(self) -> Optional[IRFunction]: if call_count == 1: return func - # # Decide whether to inline based on the optimization level. - # if self.settings.optimize == OptimizationLevel.CODESIZE: - # if func.code_size_cost <= 10: - # return func - # elif self.settings.optimize == OptimizationLevel.GAS: - # # Inline if the function is not too big. - # if func.code_size_cost <= 25: - # return func - # elif self.settings.optimize == OptimizationLevel.NONE: - # continue - # else: - # raise CompilerPanic( - # f"Unsupported inlining optimization level: {self.settings.optimize}" - # ) + # Decide whether to inline based on the optimization level. + if self.settings.optimize == OptimizationLevel.CODESIZE: + continue + elif self.settings.optimize == OptimizationLevel.GAS: + if func.code_size_cost <= 15: + return func + elif self.settings.optimize == OptimizationLevel.NONE: + continue + else: + raise CompilerPanic( + f"Unsupported inlining optimization level: {self.settings.optimize}" + ) return None From ad2e8b76b4e8f73d3a5f32450bdfa0c0d8993483 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 4 Jan 2025 18:15:59 +0200 Subject: [PATCH 45/84] dfg equiv --- vyper/venom/analysis/dfg.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/vyper/venom/analysis/dfg.py b/vyper/venom/analysis/dfg.py index a2e050094d..8fe3ff5f6f 100644 --- a/vyper/venom/analysis/dfg.py +++ b/vyper/venom/analysis/dfg.py @@ -38,6 +38,22 @@ def remove_use(self, op: IRVariable, inst: IRInstruction): uses: OrderedSet = self._dfg_inputs.get(op, OrderedSet()) uses.remove(inst) + def are_equivalent(self, var1: IRVariable, var2: IRVariable) -> bool: + if var1 == var2: + return True + + var1 = self._traverse_store_chain(var1) + var2 = self._traverse_store_chain(var2) + + return var1 == var2 + + def _traverse_store_chain(self, var: IRVariable) -> IRVariable: + while True: + inst = self.get_producing_instruction(var) + if inst is None or inst.opcode != "store": + return var + var = inst.operands[0] + @property def outputs(self) -> dict[IRVariable, IRInstruction]: return self._dfg_outputs From 6bf03ec0f4f89b31de40b009ca5bd9594a96c9dc Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 4 Jan 2025 18:28:28 +0200 Subject: [PATCH 46/84] drop var equivalence --- .../venom/test_variable_equivalence.py | 38 +++++++++++++++++++ vyper/venom/analysis/__init__.py | 1 - vyper/venom/passes/func_inliner.py | 2 - vyper/venom/passes/load_elimination.py | 6 +-- vyper/venom/venom_to_assembly.py | 8 ++-- 5 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 tests/unit/compiler/venom/test_variable_equivalence.py diff --git a/tests/unit/compiler/venom/test_variable_equivalence.py b/tests/unit/compiler/venom/test_variable_equivalence.py new file mode 100644 index 0000000000..f51d00cbc5 --- /dev/null +++ b/tests/unit/compiler/venom/test_variable_equivalence.py @@ -0,0 +1,38 @@ +import itertools + +from tests.venom_utils import parse_from_basic_block +from vyper.venom.analysis import IRAnalysesCache, DFGAnalysis +from vyper.venom.basicblock import IRVariable +from vyper.venom.context import IRContext + + +def _entry_fn(ctx: "IRContext"): + # TODO: make this part of IRContext + return next(iter(ctx.functions.values())) + + +def test_variable_equivalence_dfg_order(): + a_code = """ + main: + %1 = 1 + %2 = %1 + %3 = %2 + """ + # technically invalid code, but variable equivalence should handle + # it either way + b_code = """ + main: + %3 = %2 + %2 = %1 + %1 = 1 + """ + fn1 = _entry_fn(parse_from_basic_block(a_code)) + fn2 = _entry_fn(parse_from_basic_block(b_code)) + + dfg1 = IRAnalysesCache(fn1).request_analysis(DFGAnalysis) + dfg2 = IRAnalysesCache(fn2).request_analysis(DFGAnalysis) + + vars_ = map(IRVariable, ("%1", "%2", "%3")) + for var1, var2 in itertools.combinations(vars_, 2): + assert dfg1.are_equivalent(var1, var2) + assert dfg2.are_equivalent(var1, var2) diff --git a/vyper/venom/analysis/__init__.py b/vyper/venom/analysis/__init__.py index 4870de3fb7..fd6437b431 100644 --- a/vyper/venom/analysis/__init__.py +++ b/vyper/venom/analysis/__init__.py @@ -2,5 +2,4 @@ from .cfg import CFGAnalysis from .dfg import DFGAnalysis from .dominators import DominatorTreeAnalysis -from .equivalent_vars import VarEquivalenceAnalysis from .liveness import LivenessAnalysis diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 2bb3d52808..20721dc21d 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -5,7 +5,6 @@ from vyper.utils import OrderedSet from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis -from vyper.venom.analysis.equivalent_vars import VarEquivalenceAnalysis from vyper.venom.analysis.fcg import FCGAnalysis from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel from vyper.venom.function import IRFunction @@ -90,7 +89,6 @@ def _inline_function(self, func: IRFunction, call_sites: List[IRInstruction]) -> fn = call_site.parent.parent self.analyses_caches[fn].invalidate_analysis(DFGAnalysis) self.analyses_caches[fn].invalidate_analysis(CFGAnalysis) - self.analyses_caches[fn].invalidate_analysis(VarEquivalenceAnalysis) def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: """ diff --git a/vyper/venom/passes/load_elimination.py b/vyper/venom/passes/load_elimination.py index 6701b588fe..76eaa2c453 100644 --- a/vyper/venom/passes/load_elimination.py +++ b/vyper/venom/passes/load_elimination.py @@ -1,4 +1,4 @@ -from vyper.venom.analysis import DFGAnalysis, LivenessAnalysis, VarEquivalenceAnalysis +from vyper.venom.analysis import DFGAnalysis, LivenessAnalysis from vyper.venom.effects import Effects from vyper.venom.passes.base_pass import IRPass @@ -11,7 +11,7 @@ class LoadElimination(IRPass): # should this be renamed to EffectsElimination? def run_pass(self): - self.equivalence = self.analyses_cache.request_analysis(VarEquivalenceAnalysis) + self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) for bb in self.function.get_basic_blocks(): self._process_bb(bb, Effects.MEMORY, "mload", "mstore") @@ -22,7 +22,7 @@ def run_pass(self): self.analyses_cache.invalidate_analysis(DFGAnalysis) def equivalent(self, op1, op2): - return op1 == op2 or self.equivalence.equivalent(op1, op2) + return op1 == op2 or self.dfg.are_equivalent(op1, op2) def _process_bb(self, bb, eff, load_opcode, store_opcode): # not really a lattice even though it is not really inter-basic block; diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 048555a221..538524c1df 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -14,7 +14,7 @@ CFGAnalysis, IRAnalysesCache, LivenessAnalysis, - VarEquivalenceAnalysis, + DFGAnalysis, ) from vyper.venom.basicblock import ( IRBasicBlock, @@ -162,7 +162,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: NormalizationPass(ac, fn).run_pass() self.liveness_analysis = ac.request_analysis(LivenessAnalysis) - self.equivalence = ac.request_analysis(VarEquivalenceAnalysis) + self.dfg = ac.request_analysis(DFGAnalysis) ac.request_analysis(CFGAnalysis) assert fn.normalized, "Non-normalized CFG!" @@ -231,7 +231,7 @@ def _stack_reorder( continue to_swap = stack.peek(final_stack_depth) - if self.equivalence.equivalent(op, to_swap): + if self.dfg.are_equivalent(op, to_swap): # perform a "virtual" swap stack.poke(final_stack_depth, op) stack.poke(depth, to_swap) @@ -579,7 +579,7 @@ def _generate_evm_for_instruction( next_scheduled = next_liveness.last() cost = 0 - if not self.equivalence.equivalent(inst.output, next_scheduled): + if not self.dfg.are_equivalent(inst.output, next_scheduled): cost = self.swap_op(assembly, stack, next_scheduled) if DEBUG_SHOW_COST and cost != 0: From 6ca95a639c9196c014e947cdeb9c5e6d3fbea446 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sat, 4 Jan 2025 22:06:11 +0200 Subject: [PATCH 47/84] lint --- tests/unit/compiler/venom/test_variable_equivalence.py | 2 +- vyper/venom/analysis/dfg.py | 4 ++-- vyper/venom/venom_to_assembly.py | 7 +------ 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/unit/compiler/venom/test_variable_equivalence.py b/tests/unit/compiler/venom/test_variable_equivalence.py index f51d00cbc5..717c026dff 100644 --- a/tests/unit/compiler/venom/test_variable_equivalence.py +++ b/tests/unit/compiler/venom/test_variable_equivalence.py @@ -1,7 +1,7 @@ import itertools from tests.venom_utils import parse_from_basic_block -from vyper.venom.analysis import IRAnalysesCache, DFGAnalysis +from vyper.venom.analysis import DFGAnalysis, IRAnalysesCache from vyper.venom.basicblock import IRVariable from vyper.venom.context import IRContext diff --git a/vyper/venom/analysis/dfg.py b/vyper/venom/analysis/dfg.py index 8fe3ff5f6f..0d4b0ccf23 100644 --- a/vyper/venom/analysis/dfg.py +++ b/vyper/venom/analysis/dfg.py @@ -38,7 +38,7 @@ def remove_use(self, op: IRVariable, inst: IRInstruction): uses: OrderedSet = self._dfg_inputs.get(op, OrderedSet()) uses.remove(inst) - def are_equivalent(self, var1: IRVariable, var2: IRVariable) -> bool: + def are_equivalent(self, var1: IRVariable, var2: IRVariable) -> bool: if var1 == var2: return True @@ -52,7 +52,7 @@ def _traverse_store_chain(self, var: IRVariable) -> IRVariable: inst = self.get_producing_instruction(var) if inst is None or inst.opcode != "store": return var - var = inst.operands[0] + var = inst.operands[0] # type: ignore @property def outputs(self) -> dict[IRVariable, IRInstruction]: diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 538524c1df..d4523d1002 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -10,12 +10,7 @@ optimize_assembly, ) from vyper.utils import MemoryPositions, OrderedSet -from vyper.venom.analysis import ( - CFGAnalysis, - IRAnalysesCache, - LivenessAnalysis, - DFGAnalysis, -) +from vyper.venom.analysis import CFGAnalysis, DFGAnalysis, IRAnalysesCache, LivenessAnalysis from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, From bb31c403d7efe507022ea9976720a179b3a24331 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 21 Jan 2025 21:48:28 +0200 Subject: [PATCH 48/84] passthroug --- vyper/codegen/function_definitions/internal_function.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/codegen/function_definitions/internal_function.py b/vyper/codegen/function_definitions/internal_function.py index e98c8a5632..ef4cf1c35a 100644 --- a/vyper/codegen/function_definitions/internal_function.py +++ b/vyper/codegen/function_definitions/internal_function.py @@ -78,6 +78,9 @@ def generate_ir_for_internal_function( ir_node = IRnode.from_list(["seq", body, cleanup_routine]) + ir_node.passthrough_metadata["func_t"] = func_t + ir_node.passthrough_metadata["context"] = context + # tag gas estimate and frame info func_t._ir_info.gas_estimate = ir_node.gas tag_frame_info(func_t, context) From 9d17a79b5025ebd473d9d07e1badf93e1a3c655a Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 21 Jan 2025 21:51:00 +0200 Subject: [PATCH 49/84] assert --- 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 d4523d1002..7e0906f3a2 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -366,6 +366,7 @@ def _generate_evm_for_instruction( if opcode in ["jmp", "djmp", "jnz", "invoke"]: operands = list(inst.get_non_label_operands()) elif opcode in ("alloca", "palloca"): + assert len(inst.operands) == 2, f"alloca/palloca must have 2 operands, got {inst.operands}" offset, _size = inst.operands operands = [offset] From aba2afca76479a9e2cc99cdc3819c796382adc80 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 21 Jan 2025 21:51:39 +0200 Subject: [PATCH 50/84] params --- vyper/venom/function.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index ef97ce139f..9f888ce16e 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass import textwrap from typing import Iterator, Optional @@ -5,7 +6,9 @@ from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRVariable +@dataclass class IRParameter: + name: str offset: int size: int call_site_var: Optional[IRVariable] From 2e6c79b03c48ba5df9a8378c239aad9b77239cbd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 22 Jan 2025 10:48:59 +0200 Subject: [PATCH 51/84] temp --- tests/unit/compiler/venom/test_call_conv.py | 38 ++++++++++ vyper/venom/__init__.py | 44 ++++++------ vyper/venom/ir_node_to_venom.py | 80 ++++++++++++++++----- vyper/venom/passes/mem2var.py | 7 +- 4 files changed, 127 insertions(+), 42 deletions(-) create mode 100644 tests/unit/compiler/venom/test_call_conv.py diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py new file mode 100644 index 0000000000..243d9256dc --- /dev/null +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -0,0 +1,38 @@ + +def test_simple_call(get_contract): + code = """ +@internal +def name(_name: uint256) -> uint256: + return _name + 1 + + +@external +def foo(x: uint256) -> uint256: + u: uint256 = 1 + x + ret: uint256 = self.name(u + 10) + return ret + """ + + c = get_contract(code) + assert c.foo(1) == 13 + +# def test_call_in_call(get_contract): +# code = """ +# @internal +# def _foo(a: uint256, b: uint256, c: uint256) -> (uint256, uint256, uint256, uint256, uint256): +# return 1, a, b, c, 5 + +# @internal +# def _foo2() -> uint256: +# a: uint256[10] = [6,7,8,9,10,11,12,13,15,16] +# return 4 + +# @external +# def foo() -> (uint256, uint256, uint256, uint256, uint256): +# return self._foo(2, 3, self._foo2()) +# """ + +# c = get_contract(code) + +# assert c.foo() == (1, 2, 3, 4, 5) + diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index f61cd4cf92..b366e1db21 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -57,27 +57,27 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache MakeSSA(ac, fn).run_pass() StoreElimination(ac, fn).run_pass() Mem2Var(ac, fn).run_pass() - MakeSSA(ac, fn).run_pass() - SCCP(ac, fn).run_pass() - - # LoadElimination(ac, fn).run_pass() - StoreElimination(ac, fn).run_pass() - MemMergePass(ac, fn).run_pass() - SimplifyCFGPass(ac, fn).run_pass() - - LowerDloadPass(ac, fn).run_pass() - AlgebraicOptimizationPass(ac, fn).run_pass() - # NOTE: MakeSSA is after algebraic optimization it currently produces - # smaller code by adding some redundant phi nodes. This is not a - # problem for us, but we need to be aware of it, and should be - # removed when the dft pass is fixed to produce the smallest code - # without making the code generation more expensive by running - # MakeSSA again. - MakeSSA(ac, fn).run_pass() - BranchOptimizationPass(ac, fn).run_pass() - RemoveUnusedVariablesPass(ac, fn).run_pass() - - StoreExpansionPass(ac, fn).run_pass() + # MakeSSA(ac, fn).run_pass() + # SCCP(ac, fn).run_pass() + + # # LoadElimination(ac, fn).run_pass() + # StoreElimination(ac, fn).run_pass() + # MemMergePass(ac, fn).run_pass() + # SimplifyCFGPass(ac, fn).run_pass() + + # LowerDloadPass(ac, fn).run_pass() + # AlgebraicOptimizationPass(ac, fn).run_pass() + # # NOTE: MakeSSA is after algebraic optimization it currently produces + # # smaller code by adding some redundant phi nodes. This is not a + # # problem for us, but we need to be aware of it, and should be + # # removed when the dft pass is fixed to produce the smallest code + # # without making the code generation more expensive by running + # # MakeSSA again. + # MakeSSA(ac, fn).run_pass() + # BranchOptimizationPass(ac, fn).run_pass() + # RemoveUnusedVariablesPass(ac, fn).run_pass() + + # StoreExpansionPass(ac, fn).run_pass() if optimize == OptimizationLevel.CODESIZE: ReduceLiteralsCodesize(ac, fn).run_pass() @@ -94,7 +94,7 @@ def run_passes_on(ctx: IRContext, optimize: OptimizationLevel) -> None: for fn in ctx.functions.values(): ir_analyses[fn] = IRAnalysesCache(fn) - _run_global_passes(ctx, optimize, ir_analyses) + # _run_global_passes(ctx, optimize, ir_analyses) ir_analyses = {} for fn in ctx.functions.values(): diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 7c0f95dd25..24df2826b2 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -2,8 +2,10 @@ import re from typing import Optional +from vyper.codegen.core import LOAD from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes +from vyper.semantics.types.shortcuts import UINT256_T from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, @@ -13,7 +15,7 @@ IRVariable, ) from vyper.venom.context import IRContext -from vyper.venom.function import IRFunction +from vyper.venom.function import IRFunction, IRParameter # Instructions that are mapped to their inverse INVERSE_MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} @@ -63,7 +65,7 @@ "gasprice", "gaslimit", "returndatasize", - "mload", + # "mload", "iload", "istore", "dload", @@ -162,6 +164,7 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio return_buf_ir = goto_ir.args[1] # return buffer ret_args: list[IROperand] = [IRLabel(target_label)] # type: ignore func_t = ir.passthrough_metadata["func_t"] + args_ir = ir.passthrough_metadata["args_ir"] assert func_t is not None, "func_t not found in passthrough metadata" fn.ctx.create_function(target_label) @@ -169,7 +172,20 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio stack_args: list[IROperand] = [] if setup_ir != goto_ir: - _convert_ir_bb(fn, setup_ir, symbols) + bb = fn.get_basic_block() + for arg in args_ir: + if arg.is_pointer or arg.typ != UINT256_T: + _convert_ir_bb(fn, setup_ir, symbols) + continue + #a = _convert_ir_bb(fn, LOAD(arg), symbols) + else: + a = _convert_ir_bb(fn, arg, symbols) + sarg = fn.get_next_variable() + assert a is not None, f"a is None: {a}" + bb.append_instruction("store", a, ret=sarg) + stack_args.append(sarg) + + #_convert_ir_bb(fn, setup_ir, symbols) return_buf = _convert_ir_bb(fn, return_buf_ir, symbols) @@ -188,34 +204,46 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio def _handle_internal_func( fn: IRFunction, ir: IRnode, does_return_data: bool, symbols: SymbolTable ) -> IRFunction: + func_t = ir.passthrough_metadata["func_t"] + context = ir.passthrough_metadata["context"] fn = fn.ctx.create_function(ir.args[0].args[0].value) bb = fn.get_basic_block() - # return buffer - if does_return_data: - symbols["return_buffer"] = bb.append_instruction("param") - bb.instructions[-1].annotation = "return_buffer" + arg_params = [] + + for arg in func_t.arguments: + var = context.lookup_var(arg.name) + venom_arg = IRParameter(var.name, var.alloca.offset, var.alloca.size, None, None, None) + fn.args.append(venom_arg) for arg in fn.args: ret = bb.append_instruction("param") + bb.instructions[-1].annotation = arg.name + symbols[arg.name] = ret arg.func_var = ret + # return buffer + if does_return_data: + symbols["return_buffer"] = bb.append_instruction("param") + bb.instructions[-1].annotation = "return_buffer" + # return address symbols["return_pc"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_pc" - for arg in fn.args: - ret = bb.append_instruction("alloca", arg.offset, 32) - bb.append_instruction("mstore", arg.func_var, ret) # type: ignore - arg.addr_var = ret + # for arg in fn.args: + # var = IRVariable(arg.name) + # bb.append_instruction("store", IRLiteral(arg.offset), ret=var) # type: ignore + # bb.append_instruction("mstore", arg.func_var, var) # type: ignore + # arg.addr_var = var _convert_ir_bb(fn, ir.args[0].args[2], symbols) - for inst in bb.instructions: - if inst.opcode == "store": - param = fn.get_param_at_offset(inst.operands[0].value) - if param is not None: - inst.operands[0] = param.addr_var # type: ignore + # for inst in bb.instructions: + # if inst.opcode == "store": + # param = fn.get_param_at_offset(inst.operands[0].value) + # if param is not None: + # inst.operands[0] = param.addr_var # type: ignore return fn @@ -427,13 +455,27 @@ def _convert_ir_bb(fn, ir, symbols): else: bb.append_instruction("jmp", label) - elif ir.value == "mstore": + elif ir.value == "mstore": # some upstream code depends on reversed order of evaluation -- # to fix upstream. val, ptr = _convert_ir_bb_list(fn, reversed(ir.args), symbols) - return fn.get_basic_block().append_instruction("mstore", val, ptr) + # if ptr.value.startswith("$palloca"): + # symbol = symbols.get(arg.annotation, None) + # if symbol is not None: + # return fn.get_basic_block().append_instruction("store", symbol) + return fn.get_basic_block().append_instruction("mstore", val, ptr) + elif ir.value == "mload": + arg = ir.args[0] + ptr = _convert_ir_bb(fn, arg, symbols) + + if arg.value.startswith("$palloca"): + symbol = symbols.get(arg.annotation, None) + if symbol is not None: + return fn.get_basic_block().append_instruction("store", symbol) + + return fn.get_basic_block().append_instruction("mload", ptr) elif ir.value == "ceil32": x = ir.args[0] expanded = IRnode.from_list(["and", ["add", x, 31], ["not", 31]]) @@ -543,6 +585,8 @@ def emit_body_blocks(): elif ir.value.startswith("$palloca"): alloca = ir.passthrough_metadata["alloca"] + # if fn.get_param_at_offset(alloca.offset) is not None: + # return fn.get_param_at_offset(alloca.offset).addr_var if alloca._id not in _alloca_table: ptr = fn.get_basic_block().append_instruction( "palloca", alloca.offset, alloca.size, alloca._id diff --git a/vyper/venom/passes/mem2var.py b/vyper/venom/passes/mem2var.py index 83990a51d4..5bdb0f8175 100644 --- a/vyper/venom/passes/mem2var.py +++ b/vyper/venom/passes/mem2var.py @@ -65,8 +65,11 @@ def _process_palloca_var(self, dfg: DFGAnalysis, palloca_inst: IRInstruction, va Process alloca allocated variable. If it is only used by mstore/mload instructions, it is promoted to a stack variable. Otherwise, it is left as is. """ - uses = dfg.get_uses(var) - if not all(inst.opcode in ["mstore", "mload"] for inst in uses): + uses = [inst.opcode in ["mstore", "mload"] for inst in dfg.get_uses(var)] + if not all(uses): + return + + if len(uses) == 0: return ofst, _size, alloca_id = palloca_inst.operands From 29c1302a700da7b6220038905d686c2f749d6df3 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 22 Jan 2025 11:16:51 +0200 Subject: [PATCH 52/84] fix edge case in mem2var --- vyper/utils.py | 11 +++++++++++ vyper/venom/passes/mem2var.py | 7 ++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/vyper/utils.py b/vyper/utils.py index 39d3093478..f1d84d5fd4 100644 --- a/vyper/utils.py +++ b/vyper/utils.py @@ -693,3 +693,14 @@ def safe_relpath(path): # on Windows, if path and curdir are on different drives, an exception # can be thrown return path + + +def all_nonempty(iter): + """ + This function checks if all elements in the given `iterable` are truthy, + similar to Python's built-in `all()` function. However, `all_nonempty` + diverges by returning `False` if the iterable is empty, whereas `all()` + would return `True` for an empty iterable. + """ + items = list(iter) + return len(items) > 0 and all(items) diff --git a/vyper/venom/passes/mem2var.py b/vyper/venom/passes/mem2var.py index 5bdb0f8175..56d4306e4d 100644 --- a/vyper/venom/passes/mem2var.py +++ b/vyper/venom/passes/mem2var.py @@ -1,3 +1,4 @@ +from vyper.utils import all_nonempty from vyper.venom.analysis import CFGAnalysis, DFGAnalysis, LivenessAnalysis from vyper.venom.basicblock import IRInstruction, IRVariable from vyper.venom.function import IRFunction @@ -39,7 +40,7 @@ def _process_alloca_var(self, dfg: DFGAnalysis, alloca_inst, var: IRVariable): Otherwise, it is left as is. """ uses = dfg.get_uses(var) - if not all([inst.opcode in ["mstore", "mload", "return"] for inst in uses]): + if not all_nonempty(inst.opcode in ["mstore", "mload", "return"] for inst in uses): return alloca_id = alloca_inst.operands[2] @@ -65,8 +66,8 @@ def _process_palloca_var(self, dfg: DFGAnalysis, palloca_inst: IRInstruction, va Process alloca allocated variable. If it is only used by mstore/mload instructions, it is promoted to a stack variable. Otherwise, it is left as is. """ - uses = [inst.opcode in ["mstore", "mload"] for inst in dfg.get_uses(var)] - if not all(uses): + uses = dfg.get_uses(var) + if not all_nonempty(inst.opcode in ["mstore", "mload"] for inst in uses): return if len(uses) == 0: From cac9d4c1b1f850593a6909356225909a1b28b86c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 22 Jan 2025 11:33:34 +0200 Subject: [PATCH 53/84] post merge --- vyper/venom/__init__.py | 4 ++-- vyper/venom/venom_to_assembly.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index fd70f6679b..2eb489840c 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -59,8 +59,8 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache AlgebraicOptimizationPass(ac, fn).run_pass() StoreElimination(ac, fn).run_pass() Mem2Var(ac, fn).run_pass() - # MakeSSA(ac, fn).run_pass() - # SCCP(ac, fn).run_pass() + MakeSSA(ac, fn).run_pass() + SCCP(ac, fn).run_pass() SimplifyCFGPass(ac, fn).run_pass() StoreElimination(ac, fn).run_pass() diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 1eae032c12..83d9f23758 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -14,7 +14,6 @@ CFGAnalysis, IRAnalysesCache, LivenessAnalysis, - VarEquivalenceAnalysis, ) from vyper.venom.basicblock import ( IRBasicBlock, From f04400cb752a12be1586489becc2537700e11b13 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 22 Jan 2025 15:53:17 +0200 Subject: [PATCH 54/84] temp --- tests/unit/compiler/venom/test_call_conv.py | 48 ++++++++++++++++----- vyper/venom/__init__.py | 2 +- vyper/venom/ir_node_to_venom.py | 19 ++++---- vyper/venom/passes/mem2var.py | 3 -- vyper/venom/venom_to_assembly.py | 26 ++++++++--- 5 files changed, 70 insertions(+), 28 deletions(-) diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py index 243d9256dc..27abd39589 100644 --- a/tests/unit/compiler/venom/test_call_conv.py +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -16,23 +16,49 @@ def foo(x: uint256) -> uint256: c = get_contract(code) assert c.foo(1) == 13 -# def test_call_in_call(get_contract): -# code = """ -# @internal -# def _foo(a: uint256, b: uint256, c: uint256) -> (uint256, uint256, uint256, uint256, uint256): -# return 1, a, b, c, 5 +def test_simple_call_multiple_args_in_call(get_contract): + code = """ +@internal +def bar(_name: uint256, _name2: uint256) -> uint256: + return _name + 10 + +@external +def foo(x: uint256) -> uint256: + ret: uint256 = self.bar(20, 10) + return ret + """ + c = get_contract(code) + assert c.foo(1) == 30 + +# def test_simple_call_multiple_args(get_contract): +# code = """ # @internal -# def _foo2() -> uint256: -# a: uint256[10] = [6,7,8,9,10,11,12,13,15,16] -# return 4 +# def bar(_name: uint256, _name2: uint256) -> (uint256, uint256): +# return _name + 1, _name + 2 # @external -# def foo() -> (uint256, uint256, uint256, uint256, uint256): -# return self._foo(2, 3, self._foo2()) +# def foo(x: uint256) -> (uint256, uint256): +# ret: (uint256, uint256) = self.bar(20, 10) +# return ret # """ # c = get_contract(code) +# assert c.foo(1) == (21, 22) + +def test_call_in_call(get_contract): + code = """ +@internal +def _foo(a: uint256) -> uint256: + return a + +@external +def foo() -> uint256: + a: uint256 = 1 + return self._foo(a) +""" + + c = get_contract(code) -# assert c.foo() == (1, 2, 3, 4, 5) + assert c.foo() == 1 diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 2eb489840c..379a50e105 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -85,7 +85,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel, ac: IRAnalysesCache AlgebraicOptimizationPass(ac, fn).run_pass() RemoveUnusedVariablesPass(ac, fn).run_pass() - # StoreExpansionPass(ac, fn).run_pass() + StoreExpansionPass(ac, fn).run_pass() if optimize == OptimizationLevel.CODESIZE: ReduceLiteralsCodesize(ac, fn).run_pass() diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 24df2826b2..aa53012f40 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -174,12 +174,15 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio if setup_ir != goto_ir: bb = fn.get_basic_block() for arg in args_ir: - if arg.is_pointer or arg.typ != UINT256_T: + if arg.typ != UINT256_T: _convert_ir_bb(fn, setup_ir, symbols) continue - #a = _convert_ir_bb(fn, LOAD(arg), symbols) + + if arg.is_pointer: + a = _convert_ir_bb(fn, LOAD(arg), symbols) else: a = _convert_ir_bb(fn, arg, symbols) + sarg = fn.get_next_variable() assert a is not None, f"a is None: {a}" bb.append_instruction("store", a, ret=sarg) @@ -216,17 +219,17 @@ def _handle_internal_func( venom_arg = IRParameter(var.name, var.alloca.offset, var.alloca.size, None, None, None) fn.args.append(venom_arg) + # return buffer + if does_return_data: + symbols["return_buffer"] = bb.append_instruction("param") + bb.instructions[-1].annotation = "return_buffer" + for arg in fn.args: ret = bb.append_instruction("param") bb.instructions[-1].annotation = arg.name symbols[arg.name] = ret arg.func_var = ret - # return buffer - if does_return_data: - symbols["return_buffer"] = bb.append_instruction("param") - bb.instructions[-1].annotation = "return_buffer" - # return address symbols["return_pc"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_pc" @@ -470,7 +473,7 @@ def _convert_ir_bb(fn, ir, symbols): arg = ir.args[0] ptr = _convert_ir_bb(fn, arg, symbols) - if arg.value.startswith("$palloca"): + if isinstance(arg.value, str) and arg.value.startswith("$palloca"): symbol = symbols.get(arg.annotation, None) if symbol is not None: return fn.get_basic_block().append_instruction("store", symbol) diff --git a/vyper/venom/passes/mem2var.py b/vyper/venom/passes/mem2var.py index 56d4306e4d..8b1fb1f0f8 100644 --- a/vyper/venom/passes/mem2var.py +++ b/vyper/venom/passes/mem2var.py @@ -70,9 +70,6 @@ def _process_palloca_var(self, dfg: DFGAnalysis, palloca_inst: IRInstruction, va if not all_nonempty(inst.opcode in ["mstore", "mload"] for inst in uses): return - if len(uses) == 0: - return - ofst, _size, alloca_id = palloca_inst.operands var_name = self._mk_varname(var.value, alloca_id.value) var = IRVariable(var_name) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 83d9f23758..8fd9cdafe2 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -14,6 +14,7 @@ CFGAnalysis, IRAnalysesCache, LivenessAnalysis, + DFGAnalysis, ) from vyper.venom.basicblock import ( IRBasicBlock, @@ -140,6 +141,7 @@ class VenomCompiler: visited_instructions: OrderedSet # {IRInstruction} visited_basicblocks: OrderedSet # {IRBasicBlock} liveness_analysis: LivenessAnalysis + dfg: DFGAnalysis def __init__(self, ctxs: list[IRContext]): self.ctxs = ctxs @@ -304,11 +306,25 @@ def _generate_evm_for_basicblock_r( if len(basicblock.cfg_in) == 1: self.clean_stack_from_cfg_in(asm, basicblock, stack) - all_insts = sorted(basicblock.instructions, key=lambda x: x.opcode != "param") + param_insts = [inst for inst in basicblock.instructions if inst.opcode == "param"] + body_insts = sorted(basicblock.instructions, key=lambda x: x.opcode != "param") - for i, inst in enumerate(all_insts): + params_to_pop = [] + for i, inst in enumerate(param_insts): + stack.push(inst.output) + if len(self.dfg.get_uses(inst.output)) == 0: + params_to_pop.append(inst.output) + # asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) + + for param in params_to_pop: + depth = stack.get_depth(param) + if depth != 0: + self.swap(asm, stack, depth) + self.pop(asm, stack) + + for i, inst in enumerate(body_insts): next_liveness = ( - all_insts[i + 1].liveness if i + 1 < len(all_insts) else basicblock.out_vars + body_insts[i + 1].liveness if i + 1 < len(body_insts) else basicblock.out_vars ) asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) @@ -370,8 +386,8 @@ def _generate_evm_for_instruction( if opcode in ["jmp", "djmp", "jnz", "invoke"]: operands = list(inst.get_non_label_operands()) elif opcode in ("alloca", "palloca"): - assert len(inst.operands) == 2, f"alloca/palloca must have 2 operands, got {inst.operands}" - offset, _size = inst.operands + assert len(inst.operands) == 3, f"alloca/palloca must have 3 operands, got {inst}" + offset, _size, _id = inst.operands operands = [offset] # iload and istore are special cases because they can take a literal From 7fe033d3fa95156c682bb0f99d51e45d95697b4b Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 22 Jan 2025 15:57:24 +0200 Subject: [PATCH 55/84] tmep --- 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 8fd9cdafe2..781e115c81 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -318,9 +318,9 @@ def _generate_evm_for_basicblock_r( for param in params_to_pop: depth = stack.get_depth(param) - if depth != 0: + if depth != StackModel.NOT_IN_STACK: self.swap(asm, stack, depth) - self.pop(asm, stack) + self.pop(asm, stack) for i, inst in enumerate(body_insts): next_liveness = ( From 733e3b225170011dbb8ec556ce7e2e990e406581 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 22 Jan 2025 16:10:38 +0200 Subject: [PATCH 56/84] fix --- tests/unit/compiler/venom/test_call_conv.py | 24 ++++++++++----------- vyper/venom/venom_to_assembly.py | 3 +-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py index 27abd39589..5c98463e12 100644 --- a/tests/unit/compiler/venom/test_call_conv.py +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -31,20 +31,20 @@ def foo(x: uint256) -> uint256: c = get_contract(code) assert c.foo(1) == 30 -# def test_simple_call_multiple_args(get_contract): -# code = """ -# @internal -# def bar(_name: uint256, _name2: uint256) -> (uint256, uint256): -# return _name + 1, _name + 2 +def test_simple_call_multiple_args(get_contract): + code = """ +@internal +def bar(_name: uint256, _name2: uint256) -> (uint256, uint256): + return _name + 1, _name + 2 -# @external -# def foo(x: uint256) -> (uint256, uint256): -# ret: (uint256, uint256) = self.bar(20, 10) -# return ret -# """ +@external +def foo(x: uint256) -> (uint256, uint256): + ret: (uint256, uint256) = self.bar(20, 10) + return ret + """ -# c = get_contract(code) -# assert c.foo(1) == (21, 22) + c = get_contract(code) + assert c.foo(1) == (21, 22) def test_call_in_call(get_contract): code = """ diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 781e115c81..6fb04ad4f8 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -307,14 +307,13 @@ def _generate_evm_for_basicblock_r( self.clean_stack_from_cfg_in(asm, basicblock, stack) param_insts = [inst for inst in basicblock.instructions if inst.opcode == "param"] - body_insts = sorted(basicblock.instructions, key=lambda x: x.opcode != "param") + body_insts = [inst for inst in basicblock.instructions if inst.opcode != "param"] params_to_pop = [] for i, inst in enumerate(param_insts): stack.push(inst.output) if len(self.dfg.get_uses(inst.output)) == 0: params_to_pop.append(inst.output) - # asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) for param in params_to_pop: depth = stack.get_depth(param) From e54bd0fdb51a292634870e97742876571829b2ff Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 22 Jan 2025 22:37:31 +0200 Subject: [PATCH 57/84] comment out --- tests/unit/compiler/venom/test_call_conv.py | 14 ++++ vyper/venom/basicblock.py | 2 +- vyper/venom/ir_node_to_venom.py | 78 ++++++++++----------- vyper/venom/venom_to_assembly.py | 41 ++++++----- 4 files changed, 79 insertions(+), 56 deletions(-) diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py index 5c98463e12..dbc9da627a 100644 --- a/tests/unit/compiler/venom/test_call_conv.py +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -62,3 +62,17 @@ def foo() -> uint256: assert c.foo() == 1 + +def test_2d_array_input_1(get_contract): + code = """ +@internal +def test_input(arr: DynArray[DynArray[int128, 2], 1]) -> DynArray[DynArray[int128, 2], 1]: + return arr + +@external +def test_values(arr: DynArray[DynArray[int128, 2], 1]) -> DynArray[DynArray[int128, 2], 1]: + return self.test_input(arr) + """ + + c = get_contract(code) + assert c.test_values([[1, 2]]) == ([[1, 2]]) \ No newline at end of file diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 06c96fa47b..9dab0e7e84 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -436,7 +436,7 @@ def __repr__(self) -> str: if self.annotation: s += f" ; {self.annotation}" - return f"{s: <30}" + return f"{s: <30} ; {self.liveness}" def _ir_operand_from_value(val: Any) -> IROperand: diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index aa53012f40..a2ce79cd0e 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -65,7 +65,7 @@ "gasprice", "gaslimit", "returndatasize", - # "mload", + "mload", "iload", "istore", "dload", @@ -167,28 +167,28 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio args_ir = ir.passthrough_metadata["args_ir"] assert func_t is not None, "func_t not found in passthrough metadata" - fn.ctx.create_function(target_label) + # fn.ctx.create_function(target_label) stack_args: list[IROperand] = [] if setup_ir != goto_ir: - bb = fn.get_basic_block() - for arg in args_ir: - if arg.typ != UINT256_T: - _convert_ir_bb(fn, setup_ir, symbols) - continue + # bb = fn.get_basic_block() + # for arg in args_ir: + # if arg.typ != UINT256_T: + # _convert_ir_bb(fn, setup_ir, symbols) + # continue - if arg.is_pointer: - a = _convert_ir_bb(fn, LOAD(arg), symbols) - else: - a = _convert_ir_bb(fn, arg, symbols) + # if arg.is_pointer: + # a = _convert_ir_bb(fn, LOAD(arg), symbols) + # else: + # a = _convert_ir_bb(fn, arg, symbols) - sarg = fn.get_next_variable() - assert a is not None, f"a is None: {a}" - bb.append_instruction("store", a, ret=sarg) - stack_args.append(sarg) + # sarg = fn.get_next_variable() + # assert a is not None, f"a is None: {a}" + # bb.append_instruction("store", a, ret=sarg) + # stack_args.append(sarg) - #_convert_ir_bb(fn, setup_ir, symbols) + _convert_ir_bb(fn, setup_ir, symbols) return_buf = _convert_ir_bb(fn, return_buf_ir, symbols) @@ -196,8 +196,8 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio if len(goto_ir.args) > 2: ret_args.append(return_buf) # type: ignore - for stack_arg in stack_args: - ret_args.append(stack_arg) + # for stack_arg in stack_args: + # ret_args.append(stack_arg) bb.append_invoke_instruction(ret_args, returns=False) # type: ignore @@ -212,23 +212,23 @@ def _handle_internal_func( fn = fn.ctx.create_function(ir.args[0].args[0].value) bb = fn.get_basic_block() - arg_params = [] - - for arg in func_t.arguments: - var = context.lookup_var(arg.name) - venom_arg = IRParameter(var.name, var.alloca.offset, var.alloca.size, None, None, None) - fn.args.append(venom_arg) + # for arg in func_t.arguments: + # var = context.lookup_var(arg.name) + # if var.typ != UINT256_T: + # continue + # venom_arg = IRParameter(var.name, var.alloca.offset, var.alloca.size, None, None, None) + # fn.args.append(venom_arg) # return buffer if does_return_data: symbols["return_buffer"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_buffer" - for arg in fn.args: - ret = bb.append_instruction("param") - bb.instructions[-1].annotation = arg.name - symbols[arg.name] = ret - arg.func_var = ret + # for arg in fn.args: + # ret = bb.append_instruction("param") + # bb.instructions[-1].annotation = arg.name + # symbols[arg.name] = ret + # arg.func_var = ret # return address symbols["return_pc"] = bb.append_instruction("param") @@ -458,7 +458,7 @@ def _convert_ir_bb(fn, ir, symbols): else: bb.append_instruction("jmp", label) - elif ir.value == "mstore": + elif ir.value == "mstore": # some upstream code depends on reversed order of evaluation -- # to fix upstream. val, ptr = _convert_ir_bb_list(fn, reversed(ir.args), symbols) @@ -469,16 +469,16 @@ def _convert_ir_bb(fn, ir, symbols): # return fn.get_basic_block().append_instruction("store", symbol) return fn.get_basic_block().append_instruction("mstore", val, ptr) - elif ir.value == "mload": - arg = ir.args[0] - ptr = _convert_ir_bb(fn, arg, symbols) - - if isinstance(arg.value, str) and arg.value.startswith("$palloca"): - symbol = symbols.get(arg.annotation, None) - if symbol is not None: - return fn.get_basic_block().append_instruction("store", symbol) + # elif ir.value == "mload": + # arg = ir.args[0] + # ptr = _convert_ir_bb(fn, arg, symbols) + + # if isinstance(arg.value, str) and arg.value.startswith("$palloca"): + # symbol = symbols.get(arg.annotation, None) + # if symbol is not None: + # return fn.get_basic_block().append_instruction("store", symbol) - return fn.get_basic_block().append_instruction("mload", ptr) + # return fn.get_basic_block().append_instruction("mload", ptr) elif ir.value == "ceil32": x = ir.args[0] expanded = IRnode.from_list(["and", ["add", x, 31], ["not", 31]]) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 6fb04ad4f8..c17e1f7e86 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -306,28 +306,37 @@ def _generate_evm_for_basicblock_r( if len(basicblock.cfg_in) == 1: self.clean_stack_from_cfg_in(asm, basicblock, stack) - param_insts = [inst for inst in basicblock.instructions if inst.opcode == "param"] - body_insts = [inst for inst in basicblock.instructions if inst.opcode != "param"] + all_insts = sorted(basicblock.instructions, key=lambda x: x.opcode != "param") - params_to_pop = [] - for i, inst in enumerate(param_insts): - stack.push(inst.output) - if len(self.dfg.get_uses(inst.output)) == 0: - params_to_pop.append(inst.output) - - for param in params_to_pop: - depth = stack.get_depth(param) - if depth != StackModel.NOT_IN_STACK: - self.swap(asm, stack, depth) - self.pop(asm, stack) - - for i, inst in enumerate(body_insts): + for i, inst in enumerate(all_insts): next_liveness = ( - body_insts[i + 1].liveness if i + 1 < len(body_insts) else basicblock.out_vars + all_insts[i + 1].liveness if i + 1 < len(all_insts) else basicblock.out_vars ) asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) + # param_insts = [inst for inst in basicblock.instructions if inst.opcode == "param"] + # body_insts = [inst for inst in basicblock.instructions if inst.opcode != "param"] + + # params_to_pop = [] + # for i, inst in enumerate(param_insts): + # stack.push(inst.output) + # if len(self.dfg.get_uses(inst.output)) == 0: + # params_to_pop.append(inst.output) + + # for param in params_to_pop: + # depth = stack.get_depth(param) + # if depth != StackModel.NOT_IN_STACK: + # self.swap(asm, stack, depth) + # self.pop(asm, stack) + + # for i, inst in enumerate(body_insts): + # next_liveness = ( + # body_insts[i + 1].liveness if i + 1 < len(body_insts) else basicblock.out_vars + # ) + + # asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) + if DEBUG_SHOW_COST: print(" ".join(map(str, asm)), file=sys.stderr) print("\n", file=sys.stderr) From 3cdbb4862b2e1fc77293e1a2acb34379fdf365bf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 23 Jan 2025 12:05:11 +0200 Subject: [PATCH 58/84] dfg linting --- vyper/venom/analysis/dfg.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/vyper/venom/analysis/dfg.py b/vyper/venom/analysis/dfg.py index a80c774ebe..fec8f65e2a 100644 --- a/vyper/venom/analysis/dfg.py +++ b/vyper/venom/analysis/dfg.py @@ -3,7 +3,7 @@ from vyper.utils import OrderedSet from vyper.venom.analysis.analysis import IRAnalysesCache, IRAnalysis from vyper.venom.analysis.liveness import LivenessAnalysis -from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable +from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IROperand, IRVariable from vyper.venom.function import IRFunction @@ -41,12 +41,13 @@ def remove_use(self, op: IRVariable, inst: IRInstruction): uses: OrderedSet = self._dfg_inputs.get(op, OrderedSet()) uses.remove(inst) - def are_equivalent(self, var1: IRVariable, var2: IRVariable) -> bool: + def are_equivalent(self, var1: IROperand, var2: IROperand) -> bool: if var1 == var2: return True - var1 = self._traverse_store_chain(var1) - var2 = self._traverse_store_chain(var2) + if isinstance(var1, IRVariable) and isinstance(var2, IRVariable): + var1 = self._traverse_store_chain(var1) + var2 = self._traverse_store_chain(var2) return var1 == var2 From 70ff390d986a7476c3370c17930c2e5ce1571c6c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 23 Jan 2025 12:05:28 +0200 Subject: [PATCH 59/84] lint --- tests/unit/compiler/venom/test_call_conv.py | 6 ++++-- vyper/venom/ir_node_to_venom.py | 4 ++-- vyper/venom/venom_to_assembly.py | 7 +------ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py index dbc9da627a..38ff5e27f2 100644 --- a/tests/unit/compiler/venom/test_call_conv.py +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -1,4 +1,3 @@ - def test_simple_call(get_contract): code = """ @internal @@ -16,6 +15,7 @@ def foo(x: uint256) -> uint256: c = get_contract(code) assert c.foo(1) == 13 + def test_simple_call_multiple_args_in_call(get_contract): code = """ @internal @@ -31,6 +31,7 @@ def foo(x: uint256) -> uint256: c = get_contract(code) assert c.foo(1) == 30 + def test_simple_call_multiple_args(get_contract): code = """ @internal @@ -46,6 +47,7 @@ def foo(x: uint256) -> (uint256, uint256): c = get_contract(code) assert c.foo(1) == (21, 22) + def test_call_in_call(get_contract): code = """ @internal @@ -75,4 +77,4 @@ def test_values(arr: DynArray[DynArray[int128, 2], 1]) -> DynArray[DynArray[int1 """ c = get_contract(code) - assert c.test_values([[1, 2]]) == ([[1, 2]]) \ No newline at end of file + assert c.test_values([[1, 2]]) == ([[1, 2]]) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a2ce79cd0e..17d050931c 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -177,7 +177,7 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio # if arg.typ != UINT256_T: # _convert_ir_bb(fn, setup_ir, symbols) # continue - + # if arg.is_pointer: # a = _convert_ir_bb(fn, LOAD(arg), symbols) # else: @@ -477,7 +477,7 @@ def _convert_ir_bb(fn, ir, symbols): # symbol = symbols.get(arg.annotation, None) # if symbol is not None: # return fn.get_basic_block().append_instruction("store", symbol) - + # return fn.get_basic_block().append_instruction("mload", ptr) elif ir.value == "ceil32": x = ir.args[0] diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index c17e1f7e86..070e689341 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -10,12 +10,7 @@ optimize_assembly, ) from vyper.utils import MemoryPositions, OrderedSet, wrap256 -from vyper.venom.analysis import ( - CFGAnalysis, - IRAnalysesCache, - LivenessAnalysis, - DFGAnalysis, -) +from vyper.venom.analysis import CFGAnalysis, IRAnalysesCache, LivenessAnalysis, DFGAnalysis from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, From eaef2a40b8f2688d955633ea62878390c68eef98 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Thu, 23 Jan 2025 14:46:42 +0200 Subject: [PATCH 60/84] sccp fixes --- vyper/venom/analysis/analysis.py | 4 ++++ vyper/venom/basicblock.py | 6 +++--- vyper/venom/passes/sccp/sccp.py | 3 ++- vyper/venom/passes/simplify_cfg.py | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/vyper/venom/analysis/analysis.py b/vyper/venom/analysis/analysis.py index 7bff6ba555..4cd573af8d 100644 --- a/vyper/venom/analysis/analysis.py +++ b/vyper/venom/analysis/analysis.py @@ -72,4 +72,8 @@ def force_analysis(self, analysis_cls: Type[IRAnalysis], *args, **kwargs): assert issubclass(analysis_cls, IRAnalysis), f"{analysis_cls} is not an IRAnalysis" if analysis_cls in self.analyses_cache: self.invalidate_analysis(analysis_cls) + + for analysis in self.analyses_cache.values(): + self.request_analysis(analysis.__class__) + return self.request_analysis(analysis_cls, *args, **kwargs) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 9dab0e7e84..32aae36ec8 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -436,7 +436,7 @@ def __repr__(self) -> str: if self.annotation: s += f" ; {self.annotation}" - return f"{s: <30} ; {self.liveness}" + return f"{s: <30}" def _ir_operand_from_value(val: Any) -> IROperand: @@ -709,8 +709,8 @@ def copy(self, prefix: str = "") -> "IRBasicBlock": return bb def __repr__(self) -> str: - s = f"{self.label}: ; IN={[bb.label for bb in self.cfg_in]}" - s += f" OUT={[bb.label for bb in self.cfg_out]} => {self.out_vars}\n" + s = f"{self.label}:" + # s += f" OUT={[bb.label for bb in self.cfg_out]} => {self.out_vars}\n" for instruction in self.instructions: s += f" {str(instruction).strip()}\n" if len(self.instructions) > 30: diff --git a/vyper/venom/passes/sccp/sccp.py b/vyper/venom/passes/sccp/sccp.py index dbe5e607da..f3573cfc76 100644 --- a/vyper/venom/passes/sccp/sccp.py +++ b/vyper/venom/passes/sccp/sccp.py @@ -61,11 +61,12 @@ def __init__(self, analyses_cache: IRAnalysesCache, function: IRFunction): super().__init__(analyses_cache, function) self.lattice = {} self.work_list: list[WorkListItem] = [] - self.cfg_dirty = False def run_pass(self): self.fn = self.function self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) # type: ignore + self.analyses_cache.request_analysis(CFGAnalysis) + self.cfg_dirty = False self._calculate_sccp(self.fn.entry) self._propagate_constants() diff --git a/vyper/venom/passes/simplify_cfg.py b/vyper/venom/passes/simplify_cfg.py index 10535c2144..4cf43479be 100644 --- a/vyper/venom/passes/simplify_cfg.py +++ b/vyper/venom/passes/simplify_cfg.py @@ -1,6 +1,6 @@ from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet -from vyper.venom.analysis import CFGAnalysis +from vyper.venom.analysis import CFGAnalysis, DominatorTreeAnalysis, LivenessAnalysis, DFGAnalysis from vyper.venom.basicblock import IRBasicBlock, IRLabel from vyper.venom.passes.base_pass import IRPass From abd12cd4ee536a77bc4129961161c50e898462dd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Jan 2025 10:53:36 +0200 Subject: [PATCH 61/84] add back --- vyper/venom/__init__.py | 1 - vyper/venom/basicblock.py | 2 +- vyper/venom/ir_node_to_venom.py | 91 ++++++++++++++++++--------------- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 379a50e105..c49b84577c 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -31,7 +31,6 @@ DEFAULT_OPT_LEVEL = OptimizationLevel.default() - def generate_assembly_experimental( runtime_code: IRContext, deploy_code: Optional[IRContext] = None, diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 32aae36ec8..6ce614f1d8 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -709,7 +709,7 @@ def copy(self, prefix: str = "") -> "IRBasicBlock": return bb def __repr__(self) -> str: - s = f"{self.label}:" + s = f"{self.label}:\n" # s += f" OUT={[bb.label for bb in self.cfg_out]} => {self.out_vars}\n" for instruction in self.instructions: s += f" {str(instruction).strip()}\n" diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 17d050931c..f588eaf843 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -17,6 +17,8 @@ from vyper.venom.context import IRContext from vyper.venom.function import IRFunction, IRParameter +ENABLE_NEW_CALL_CONV = True + # Instructions that are mapped to their inverse INVERSE_MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} @@ -171,24 +173,28 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio stack_args: list[IROperand] = [] - if setup_ir != goto_ir: - # bb = fn.get_basic_block() - # for arg in args_ir: - # if arg.typ != UINT256_T: - # _convert_ir_bb(fn, setup_ir, symbols) - # continue - - # if arg.is_pointer: - # a = _convert_ir_bb(fn, LOAD(arg), symbols) - # else: - # a = _convert_ir_bb(fn, arg, symbols) - - # sarg = fn.get_next_variable() - # assert a is not None, f"a is None: {a}" - # bb.append_instruction("store", a, ret=sarg) - # stack_args.append(sarg) + if ENABLE_NEW_CALL_CONV: + if setup_ir != goto_ir: + bb = fn.get_basic_block() + for arg in args_ir: + if arg.typ != UINT256_T: + _convert_ir_bb(fn, setup_ir, symbols) + continue + + if arg.is_pointer: + a = _convert_ir_bb(fn, LOAD(arg), symbols) + else: + a = _convert_ir_bb(fn, arg, symbols) + + sarg = fn.get_next_variable() + assert a is not None, f"a is None: {a}" + bb.append_instruction("store", a, ret=sarg) + stack_args.append(sarg) + else: + if setup_ir != goto_ir: + _convert_ir_bb(fn, setup_ir, symbols) - _convert_ir_bb(fn, setup_ir, symbols) + return_buf = _convert_ir_bb(fn, return_buf_ir, symbols) @@ -196,8 +202,9 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio if len(goto_ir.args) > 2: ret_args.append(return_buf) # type: ignore - # for stack_arg in stack_args: - # ret_args.append(stack_arg) + if ENABLE_NEW_CALL_CONV: + for stack_arg in stack_args: + ret_args.append(stack_arg) bb.append_invoke_instruction(ret_args, returns=False) # type: ignore @@ -212,41 +219,45 @@ def _handle_internal_func( fn = fn.ctx.create_function(ir.args[0].args[0].value) bb = fn.get_basic_block() - # for arg in func_t.arguments: - # var = context.lookup_var(arg.name) - # if var.typ != UINT256_T: - # continue - # venom_arg = IRParameter(var.name, var.alloca.offset, var.alloca.size, None, None, None) - # fn.args.append(venom_arg) + if ENABLE_NEW_CALL_CONV: + for arg in func_t.arguments: + var = context.lookup_var(arg.name) + if var.typ != UINT256_T: + continue + venom_arg = IRParameter(var.name, var.alloca.offset, var.alloca.size, None, None, None) + fn.args.append(venom_arg) # return buffer if does_return_data: symbols["return_buffer"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_buffer" - # for arg in fn.args: - # ret = bb.append_instruction("param") - # bb.instructions[-1].annotation = arg.name - # symbols[arg.name] = ret - # arg.func_var = ret + if ENABLE_NEW_CALL_CONV: + for arg in fn.args: + ret = bb.append_instruction("param") + bb.instructions[-1].annotation = arg.name + symbols[arg.name] = ret + arg.func_var = ret # return address symbols["return_pc"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_pc" - # for arg in fn.args: - # var = IRVariable(arg.name) - # bb.append_instruction("store", IRLiteral(arg.offset), ret=var) # type: ignore - # bb.append_instruction("mstore", arg.func_var, var) # type: ignore - # arg.addr_var = var + if ENABLE_NEW_CALL_CONV: + for arg in fn.args: + var = IRVariable(arg.name) + bb.append_instruction("store", IRLiteral(arg.offset), ret=var) # type: ignore + bb.append_instruction("mstore", arg.func_var, var) # type: ignore + arg.addr_var = var _convert_ir_bb(fn, ir.args[0].args[2], symbols) - # for inst in bb.instructions: - # if inst.opcode == "store": - # param = fn.get_param_at_offset(inst.operands[0].value) - # if param is not None: - # inst.operands[0] = param.addr_var # type: ignore + # if ENABLE_NEW_CALL_CONV: + # for inst in bb.instructions: + # if inst.opcode == "store": + # param = fn.get_param_at_offset(inst.operands[0].value) + # if param is not None: + # inst.operands[0] = param.addr_var # type: ignore return fn From d98dbcda2f77788fad79e84d3767b5cc08e7d056 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Jan 2025 11:12:17 +0200 Subject: [PATCH 62/84] enable more --- vyper/venom/ir_node_to_venom.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index f588eaf843..ad2380d3cd 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -67,7 +67,7 @@ "gasprice", "gaslimit", "returndatasize", - "mload", + # "mload", "iload", "istore", "dload", @@ -474,22 +474,24 @@ def _convert_ir_bb(fn, ir, symbols): # to fix upstream. val, ptr = _convert_ir_bb_list(fn, reversed(ir.args), symbols) - # if ptr.value.startswith("$palloca"): - # symbol = symbols.get(arg.annotation, None) - # if symbol is not None: - # return fn.get_basic_block().append_instruction("store", symbol) + if ENABLE_NEW_CALL_CONV: + if ptr.value.startswith("$palloca"): + symbol = symbols.get(arg.annotation, None) + if symbol is not None: + return fn.get_basic_block().append_instruction("store", symbol) return fn.get_basic_block().append_instruction("mstore", val, ptr) - # elif ir.value == "mload": - # arg = ir.args[0] - # ptr = _convert_ir_bb(fn, arg, symbols) + elif ir.value == "mload": + arg = ir.args[0] + ptr = _convert_ir_bb(fn, arg, symbols) - # if isinstance(arg.value, str) and arg.value.startswith("$palloca"): - # symbol = symbols.get(arg.annotation, None) - # if symbol is not None: - # return fn.get_basic_block().append_instruction("store", symbol) + if ENABLE_NEW_CALL_CONV: + if isinstance(arg.value, str) and arg.value.startswith("$palloca"): + symbol = symbols.get(arg.annotation, None) + if symbol is not None: + return fn.get_basic_block().append_instruction("store", symbol) - # return fn.get_basic_block().append_instruction("mload", ptr) + return fn.get_basic_block().append_instruction("mload", ptr) elif ir.value == "ceil32": x = ir.args[0] expanded = IRnode.from_list(["and", ["add", x, 31], ["not", 31]]) @@ -599,8 +601,8 @@ def emit_body_blocks(): elif ir.value.startswith("$palloca"): alloca = ir.passthrough_metadata["alloca"] - # if fn.get_param_at_offset(alloca.offset) is not None: - # return fn.get_param_at_offset(alloca.offset).addr_var + if ENABLE_NEW_CALL_CONV and fn.get_param_at_offset(alloca.offset) is not None: + return fn.get_param_at_offset(alloca.offset).addr_var if alloca._id not in _alloca_table: ptr = fn.get_basic_block().append_instruction( "palloca", alloca.offset, alloca.size, alloca._id From 317cfb5b5cc29a618e3e2e24a8e5a79959eb46cf Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Jan 2025 11:26:23 +0200 Subject: [PATCH 63/84] fix --- vyper/venom/ir_node_to_venom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index ad2380d3cd..c3a1fa8312 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -475,7 +475,7 @@ def _convert_ir_bb(fn, ir, symbols): val, ptr = _convert_ir_bb_list(fn, reversed(ir.args), symbols) if ENABLE_NEW_CALL_CONV: - if ptr.value.startswith("$palloca"): + if isinstance(ptr, IRLabel) and ptr.value.startswith("$palloca"): symbol = symbols.get(arg.annotation, None) if symbol is not None: return fn.get_basic_block().append_instruction("store", symbol) From 0234851214e40fa70354fb07942dc62e2634216d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Jan 2025 11:26:32 +0200 Subject: [PATCH 64/84] new failing test --- tests/unit/compiler/venom/test_call_conv.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py index 38ff5e27f2..e725cbf0c9 100644 --- a/tests/unit/compiler/venom/test_call_conv.py +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -78,3 +78,18 @@ def test_values(arr: DynArray[DynArray[int128, 2], 1]) -> DynArray[DynArray[int1 c = get_contract(code) assert c.test_values([[1, 2]]) == ([[1, 2]]) + +def test_call_with_unused_params(get_contract): + code = """ +@internal +def _foo3(a: uint256, b: uint256) -> uint256: + return 42 + +@external +def foo() -> uint256: + return self._foo3(9, 11) + """ + + c = get_contract(code) + + assert c.foo() == 42 From bd82d5c243934ee1de838caf73f57e16c57fdccd Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Jan 2025 14:02:24 +0200 Subject: [PATCH 65/84] venom -> evm changed --- vyper/venom/venom_to_assembly.py | 56 +++++++++++++++++--------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 070e689341..6c7d711564 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,5 +1,6 @@ from typing import Any +from vyper.venom.ir_node_to_venom import ENABLE_NEW_CALL_CONV from vyper.exceptions import CompilerPanic, StackTooDeep from vyper.ir.compile_ir import ( PUSH, @@ -301,36 +302,37 @@ def _generate_evm_for_basicblock_r( if len(basicblock.cfg_in) == 1: self.clean_stack_from_cfg_in(asm, basicblock, stack) - all_insts = sorted(basicblock.instructions, key=lambda x: x.opcode != "param") - - for i, inst in enumerate(all_insts): - next_liveness = ( - all_insts[i + 1].liveness if i + 1 < len(all_insts) else basicblock.out_vars - ) - - asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) - - # param_insts = [inst for inst in basicblock.instructions if inst.opcode == "param"] - # body_insts = [inst for inst in basicblock.instructions if inst.opcode != "param"] - - # params_to_pop = [] - # for i, inst in enumerate(param_insts): - # stack.push(inst.output) - # if len(self.dfg.get_uses(inst.output)) == 0: - # params_to_pop.append(inst.output) + if ENABLE_NEW_CALL_CONV: + param_insts = [inst for inst in basicblock.instructions if inst.opcode == "param"] + body_insts = [inst for inst in basicblock.instructions if inst.opcode != "param"] + + params_to_pop = [] + for i, inst in enumerate(param_insts): + stack.push(inst.output) + if len(self.dfg.get_uses(inst.output)) == 0: + params_to_pop.append(inst.output) + + for param in params_to_pop: + depth = stack.get_depth(param) + if depth != StackModel.NOT_IN_STACK: + self.swap(asm, stack, depth) + self.pop(asm, stack) + + for i, inst in enumerate(body_insts): + next_liveness = ( + body_insts[i + 1].liveness if i + 1 < len(body_insts) else basicblock.out_vars + ) - # for param in params_to_pop: - # depth = stack.get_depth(param) - # if depth != StackModel.NOT_IN_STACK: - # self.swap(asm, stack, depth) - # self.pop(asm, stack) + asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) + else: + all_insts = sorted(basicblock.instructions, key=lambda x: x.opcode != "param") - # for i, inst in enumerate(body_insts): - # next_liveness = ( - # body_insts[i + 1].liveness if i + 1 < len(body_insts) else basicblock.out_vars - # ) + for i, inst in enumerate(all_insts): + next_liveness = ( + all_insts[i + 1].liveness if i + 1 < len(all_insts) else basicblock.out_vars + ) - # asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) + asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) if DEBUG_SHOW_COST: print(" ".join(map(str, asm)), file=sys.stderr) From abba8caf0d13d062e69317d0f1196962e6842089 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Jan 2025 14:55:46 +0200 Subject: [PATCH 66/84] internal update test case --- tests/unit/compiler/venom/test_call_conv.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py index e725cbf0c9..3623d5ddb6 100644 --- a/tests/unit/compiler/venom/test_call_conv.py +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -93,3 +93,18 @@ def foo() -> uint256: c = get_contract(code) assert c.foo() == 42 + +def test_internal_assign(get_contract): + code = f""" +@internal +def foo(x: uint256) -> uint256: + x = 10 + return x + +@external +def bar(x: uint256) -> uint256: + return self.foo(x) + """ + c = get_contract(code) + + assert c.bar(70) == 10 \ No newline at end of file From 3552bae0450ace88a9e18eb37ab4154fee0d484f Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Jan 2025 14:55:56 +0200 Subject: [PATCH 67/84] get param by name --- vyper/venom/function.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 9f888ce16e..1a5e65b7cb 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -172,6 +172,12 @@ def get_param_at_offset(self, offset: int) -> Optional[IRParameter]: if param.offset == offset: return param return None + + def get_param_by_name(self, var: IRVariable) -> Optional[IRParameter]: + for param in self.args: + if f"%{param.name}" == var.name: + return param + return None @property def ast_source(self) -> Optional[IRnode]: From b61009bcf9d4ac85f96c8e483cf6f280b4bf45b1 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Jan 2025 14:56:08 +0200 Subject: [PATCH 68/84] store --- vyper/venom/ir_node_to_venom.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index c3a1fa8312..af78b6c2ab 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -475,6 +475,11 @@ def _convert_ir_bb(fn, ir, symbols): val, ptr = _convert_ir_bb_list(fn, reversed(ir.args), symbols) if ENABLE_NEW_CALL_CONV: + if isinstance(ptr, IRVariable): + param = fn.get_param_by_name(ptr) + if param is not None: + return fn.get_basic_block().append_instruction("store", param.func_var) + if isinstance(ptr, IRLabel) and ptr.value.startswith("$palloca"): symbol = symbols.get(arg.annotation, None) if symbol is not None: From 7d44552b260cd82dc4771d3cda9f8f8264f1f128 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Mon, 27 Jan 2025 15:31:30 +0200 Subject: [PATCH 69/84] fix --- vyper/venom/ir_node_to_venom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index af78b6c2ab..adf6854363 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -478,7 +478,7 @@ def _convert_ir_bb(fn, ir, symbols): if isinstance(ptr, IRVariable): param = fn.get_param_by_name(ptr) if param is not None: - return fn.get_basic_block().append_instruction("store", param.func_var) + return fn.get_basic_block().append_instruction("store", val, ret=param.func_var) if isinstance(ptr, IRLabel) and ptr.value.startswith("$palloca"): symbol = symbols.get(arg.annotation, None) From 75d1a44b1e0f23ccbb1334bfdeea67713fe9e8f2 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 28 Jan 2025 22:26:17 +0200 Subject: [PATCH 70/84] start --- vyper/venom/ir_node_to_venom.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index adf6854363..4b09397191 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,3 +1,4 @@ +from ast import FunctionType import functools import re from typing import Optional @@ -211,6 +212,17 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio return return_buf +def _use_new_call_conv(context: IRContext, func_t: FunctionType) -> bool: + if not ENABLE_NEW_CALL_CONV: + return False + + for arg in func_t.arguments: + var = context.lookup_var(arg.name) + if var.typ == UINT256_T: + return True + + return True + def _handle_internal_func( fn: IRFunction, ir: IRnode, does_return_data: bool, symbols: SymbolTable ) -> IRFunction: @@ -219,7 +231,9 @@ def _handle_internal_func( fn = fn.ctx.create_function(ir.args[0].args[0].value) bb = fn.get_basic_block() - if ENABLE_NEW_CALL_CONV: + new_call_conv = _use_new_call_conv(context, func_t) + + if new_call_conv: for arg in func_t.arguments: var = context.lookup_var(arg.name) if var.typ != UINT256_T: @@ -232,7 +246,7 @@ def _handle_internal_func( symbols["return_buffer"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_buffer" - if ENABLE_NEW_CALL_CONV: + if new_call_conv: for arg in fn.args: ret = bb.append_instruction("param") bb.instructions[-1].annotation = arg.name From 9f3eac07dcee13bec2a1105d135bad923339d9d7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 28 Jan 2025 22:27:34 +0200 Subject: [PATCH 71/84] Revert "start" This reverts commit 75d1a44b1e0f23ccbb1334bfdeea67713fe9e8f2. --- vyper/venom/ir_node_to_venom.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 4b09397191..adf6854363 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -1,4 +1,3 @@ -from ast import FunctionType import functools import re from typing import Optional @@ -212,17 +211,6 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio return return_buf -def _use_new_call_conv(context: IRContext, func_t: FunctionType) -> bool: - if not ENABLE_NEW_CALL_CONV: - return False - - for arg in func_t.arguments: - var = context.lookup_var(arg.name) - if var.typ == UINT256_T: - return True - - return True - def _handle_internal_func( fn: IRFunction, ir: IRnode, does_return_data: bool, symbols: SymbolTable ) -> IRFunction: @@ -231,9 +219,7 @@ def _handle_internal_func( fn = fn.ctx.create_function(ir.args[0].args[0].value) bb = fn.get_basic_block() - new_call_conv = _use_new_call_conv(context, func_t) - - if new_call_conv: + if ENABLE_NEW_CALL_CONV: for arg in func_t.arguments: var = context.lookup_var(arg.name) if var.typ != UINT256_T: @@ -246,7 +232,7 @@ def _handle_internal_func( symbols["return_buffer"] = bb.append_instruction("param") bb.instructions[-1].annotation = "return_buffer" - if new_call_conv: + if ENABLE_NEW_CALL_CONV: for arg in fn.args: ret = bb.append_instruction("param") bb.instructions[-1].annotation = arg.name From 0b50b4bc114a51041636b8808581f976007bf43d Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 28 Jan 2025 22:41:38 +0200 Subject: [PATCH 72/84] new cc on all prims --- vyper/venom/ir_node_to_venom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index adf6854363..b43f44e2bd 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -177,7 +177,7 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio if setup_ir != goto_ir: bb = fn.get_basic_block() for arg in args_ir: - if arg.typ != UINT256_T: + if not arg.typ._is_prim_word: _convert_ir_bb(fn, setup_ir, symbols) continue @@ -222,7 +222,7 @@ def _handle_internal_func( if ENABLE_NEW_CALL_CONV: for arg in func_t.arguments: var = context.lookup_var(arg.name) - if var.typ != UINT256_T: + if not var.typ._is_prim_word: continue venom_arg = IRParameter(var.name, var.alloca.offset, var.alloca.size, None, None, None) fn.args.append(venom_arg) From 309398753c8deed2bad1da02fd09a2aa872a29b9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Tue, 28 Jan 2025 22:48:34 +0200 Subject: [PATCH 73/84] bugfix --- 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 b43f44e2bd..d57ca51deb 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -175,7 +175,6 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio if ENABLE_NEW_CALL_CONV: if setup_ir != goto_ir: - bb = fn.get_basic_block() for arg in args_ir: if not arg.typ._is_prim_word: _convert_ir_bb(fn, setup_ir, symbols) @@ -186,6 +185,7 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio else: a = _convert_ir_bb(fn, arg, symbols) + bb = fn.get_basic_block() sarg = fn.get_next_variable() assert a is not None, f"a is None: {a}" bb.append_instruction("store", a, ret=sarg) @@ -217,7 +217,6 @@ def _handle_internal_func( func_t = ir.passthrough_metadata["func_t"] context = ir.passthrough_metadata["context"] fn = fn.ctx.create_function(ir.args[0].args[0].value) - bb = fn.get_basic_block() if ENABLE_NEW_CALL_CONV: for arg in func_t.arguments: @@ -227,6 +226,8 @@ def _handle_internal_func( venom_arg = IRParameter(var.name, var.alloca.offset, var.alloca.size, None, None, None) fn.args.append(venom_arg) + bb = fn.get_basic_block() + # return buffer if does_return_data: symbols["return_buffer"] = bb.append_instruction("param") From 4e19321b4dedc1ca701c8677c1dfc9d5ca136524 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 29 Jan 2025 11:46:08 +0200 Subject: [PATCH 74/84] fixes and cleanup --- tests/unit/compiler/venom/test_call_conv.py | 6 ++++-- vyper/venom/__init__.py | 1 + vyper/venom/function.py | 4 ++-- vyper/venom/ir_node_to_venom.py | 9 +++------ vyper/venom/passes/simplify_cfg.py | 2 +- vyper/venom/venom_to_assembly.py | 7 ++++--- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py index 3623d5ddb6..1556674c80 100644 --- a/tests/unit/compiler/venom/test_call_conv.py +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -79,6 +79,7 @@ def test_values(arr: DynArray[DynArray[int128, 2], 1]) -> DynArray[DynArray[int1 c = get_contract(code) assert c.test_values([[1, 2]]) == ([[1, 2]]) + def test_call_with_unused_params(get_contract): code = """ @internal @@ -94,8 +95,9 @@ def foo() -> uint256: assert c.foo() == 42 + def test_internal_assign(get_contract): - code = f""" + code = """ @internal def foo(x: uint256) -> uint256: x = 10 @@ -107,4 +109,4 @@ def bar(x: uint256) -> uint256: """ c = get_contract(code) - assert c.bar(70) == 10 \ No newline at end of file + assert c.bar(70) == 10 diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index c49b84577c..379a50e105 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -31,6 +31,7 @@ DEFAULT_OPT_LEVEL = OptimizationLevel.default() + def generate_assembly_experimental( runtime_code: IRContext, deploy_code: Optional[IRContext] = None, diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 1a5e65b7cb..4a0287b2f1 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -1,5 +1,5 @@ -from dataclasses import dataclass import textwrap +from dataclasses import dataclass from typing import Iterator, Optional from vyper.codegen.ir_node import IRnode @@ -172,7 +172,7 @@ def get_param_at_offset(self, offset: int) -> Optional[IRParameter]: if param.offset == offset: return param return None - + def get_param_by_name(self, var: IRVariable) -> Optional[IRParameter]: for param in self.args: if f"%{param.name}" == var.name: diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index d57ca51deb..95514a84d3 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -5,7 +5,6 @@ from vyper.codegen.core import LOAD from vyper.codegen.ir_node import IRnode from vyper.evm.opcodes import get_opcodes -from vyper.semantics.types.shortcuts import UINT256_T from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, @@ -194,8 +193,6 @@ def _handle_self_call(fn: IRFunction, ir: IRnode, symbols: SymbolTable) -> Optio if setup_ir != goto_ir: _convert_ir_bb(fn, setup_ir, symbols) - - return_buf = _convert_ir_bb(fn, return_buf_ir, symbols) bb = fn.get_basic_block() @@ -480,14 +477,14 @@ def _convert_ir_bb(fn, ir, symbols): param = fn.get_param_by_name(ptr) if param is not None: return fn.get_basic_block().append_instruction("store", val, ret=param.func_var) - + if isinstance(ptr, IRLabel) and ptr.value.startswith("$palloca"): - symbol = symbols.get(arg.annotation, None) + symbol = symbols.get(ptr.annotation, None) if symbol is not None: return fn.get_basic_block().append_instruction("store", symbol) return fn.get_basic_block().append_instruction("mstore", val, ptr) - elif ir.value == "mload": + elif ir.value == "mload": arg = ir.args[0] ptr = _convert_ir_bb(fn, arg, symbols) diff --git a/vyper/venom/passes/simplify_cfg.py b/vyper/venom/passes/simplify_cfg.py index 4cf43479be..10535c2144 100644 --- a/vyper/venom/passes/simplify_cfg.py +++ b/vyper/venom/passes/simplify_cfg.py @@ -1,6 +1,6 @@ from vyper.exceptions import CompilerPanic from vyper.utils import OrderedSet -from vyper.venom.analysis import CFGAnalysis, DominatorTreeAnalysis, LivenessAnalysis, DFGAnalysis +from vyper.venom.analysis import CFGAnalysis from vyper.venom.basicblock import IRBasicBlock, IRLabel from vyper.venom.passes.base_pass import IRPass diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 6c7d711564..485834d5ec 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -1,6 +1,5 @@ from typing import Any -from vyper.venom.ir_node_to_venom import ENABLE_NEW_CALL_CONV from vyper.exceptions import CompilerPanic, StackTooDeep from vyper.ir.compile_ir import ( PUSH, @@ -11,7 +10,7 @@ optimize_assembly, ) from vyper.utils import MemoryPositions, OrderedSet, wrap256 -from vyper.venom.analysis import CFGAnalysis, IRAnalysesCache, LivenessAnalysis, DFGAnalysis +from vyper.venom.analysis import CFGAnalysis, DFGAnalysis, IRAnalysesCache, LivenessAnalysis from vyper.venom.basicblock import ( IRBasicBlock, IRInstruction, @@ -21,6 +20,7 @@ IRVariable, ) from vyper.venom.context import IRContext +from vyper.venom.ir_node_to_venom import ENABLE_NEW_CALL_CONV from vyper.venom.passes import NormalizationPass from vyper.venom.stack_model import StackModel @@ -307,7 +307,8 @@ def _generate_evm_for_basicblock_r( body_insts = [inst for inst in basicblock.instructions if inst.opcode != "param"] params_to_pop = [] - for i, inst in enumerate(param_insts): + for inst in param_insts: + assert isinstance(inst.output, IRVariable) stack.push(inst.output) if len(self.dfg.get_uses(inst.output)) == 0: params_to_pop.append(inst.output) From fb21434517e4b100503e6da68303608529fd5691 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 29 Jan 2025 12:50:36 +0200 Subject: [PATCH 75/84] param support in inliner --- tests/unit/compiler/venom/test_call_conv.py | 15 +++++++++++++++ vyper/venom/__init__.py | 2 +- vyper/venom/function.py | 5 ++++- vyper/venom/ir_node_to_venom.py | 4 +++- vyper/venom/passes/func_inliner.py | 5 +++++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py index 1556674c80..7b0b1f5dfc 100644 --- a/tests/unit/compiler/venom/test_call_conv.py +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -110,3 +110,18 @@ def bar(x: uint256) -> uint256: c = get_contract(code) assert c.bar(70) == 10 + +def test_internal_assign(get_contract): + code = """ +@internal +def foo(x: uint256, y: int128) -> uint256: + x = convert(y, uint256) + return x + +@external +def bar(x: uint256) -> uint256: + return self.foo(x, 11) + """ + c = get_contract(code) + + assert c.bar(70) == 11 diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 379a50e105..ec0b2360fb 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -102,7 +102,7 @@ def run_passes_on(ctx: IRContext, optimize: OptimizationLevel) -> None: for fn in ctx.functions.values(): ir_analyses[fn] = IRAnalysesCache(fn) - # _run_global_passes(ctx, optimize, ir_analyses) + _run_global_passes(ctx, optimize, ir_analyses) ir_analyses = {} for fn in ctx.functions.values(): diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 4a0287b2f1..6069b9ec98 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -9,6 +9,7 @@ @dataclass class IRParameter: name: str + index: int offset: int size: int call_site_var: Optional[IRVariable] @@ -173,7 +174,9 @@ def get_param_at_offset(self, offset: int) -> Optional[IRParameter]: return param return None - def get_param_by_name(self, var: IRVariable) -> Optional[IRParameter]: + def get_param_by_name(self, var: IRVariable | str) -> Optional[IRParameter]: + if isinstance(var, str): + var = IRVariable(var) for param in self.args: if f"%{param.name}" == var.name: return param diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 95514a84d3..d5b8327c68 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -216,12 +216,14 @@ def _handle_internal_func( fn = fn.ctx.create_function(ir.args[0].args[0].value) if ENABLE_NEW_CALL_CONV: + index = 0 for arg in func_t.arguments: var = context.lookup_var(arg.name) if not var.typ._is_prim_word: continue - venom_arg = IRParameter(var.name, var.alloca.offset, var.alloca.size, None, None, None) + venom_arg = IRParameter(var.name, index, var.alloca.offset, var.alloca.size, None, None, None) fn.args.append(venom_arg) + index += 1 bb = fn.get_basic_block() diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index 20721dc21d..a4a182f48e 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -129,6 +129,11 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: inst.operands = [call_site.operands[1]] elif inst.annotation == self._RETURN_PC_ANNOTATION: inst.make_nop() + else: + arg = func.get_param_by_name(inst.annotation) + inst.opcode = "store" + inst.operands = [call_site.operands[arg.index + 2]] + inst.annotation = None elif inst.opcode == "palloca": inst.opcode = "store" inst.operands = [inst.operands[0]] From b1df259d5adefc858eaa4cd58aa09683bad1d8e9 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 29 Jan 2025 13:00:06 +0200 Subject: [PATCH 76/84] failing test --- tests/unit/compiler/venom/test_inliner.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/unit/compiler/venom/test_inliner.py b/tests/unit/compiler/venom/test_inliner.py index 5b03dbcc58..346ba16287 100644 --- a/tests/unit/compiler/venom/test_inliner.py +++ b/tests/unit/compiler/venom/test_inliner.py @@ -47,6 +47,22 @@ def test(a: uint256) -> uint256: c.test(0) +def test_inliner_with_unused_param(get_contract): + code = """ +data: public(uint256) + +@internal +def _foo(start: uint256, length: uint256): + self.data = start + +@external +def foo(x: uint256, y: uint256): + self._foo(x, y) +""" + + c = get_contract(code) + c.foo(1, 2) + # TODO: not allowed at all in Vyper at the moment # def test_call_recursive(get_contract): # code = """ From 642dcb2d4fd901a9ba7980c75070b4bf15b750d7 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 29 Jan 2025 13:43:55 +0200 Subject: [PATCH 77/84] no return handling --- vyper/venom/ir_node_to_venom.py | 2 ++ vyper/venom/passes/func_inliner.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index d5b8327c68..f58c7a26b0 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -217,6 +217,8 @@ def _handle_internal_func( if ENABLE_NEW_CALL_CONV: index = 0 + if func_t.return_type is not None: + index += 1 for arg in func_t.arguments: var = context.lookup_var(arg.name) if not var.typ._is_prim_word: diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index a4a182f48e..e5aeacbd42 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -132,7 +132,7 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: else: arg = func.get_param_by_name(inst.annotation) inst.opcode = "store" - inst.operands = [call_site.operands[arg.index + 2]] + inst.operands = [call_site.operands[arg.index + 1]] inst.annotation = None elif inst.opcode == "palloca": inst.opcode = "store" From 83134faa91df5837613c023842b669709846b33c Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 29 Jan 2025 13:48:54 +0200 Subject: [PATCH 78/84] lint --- tests/unit/compiler/venom/test_call_conv.py | 1 + tests/unit/compiler/venom/test_inliner.py | 19 ------------------- vyper/venom/ir_node_to_venom.py | 4 +++- vyper/venom/passes/func_inliner.py | 2 ++ 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py index 7b0b1f5dfc..8857bde708 100644 --- a/tests/unit/compiler/venom/test_call_conv.py +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -111,6 +111,7 @@ def bar(x: uint256) -> uint256: assert c.bar(70) == 10 + def test_internal_assign(get_contract): code = """ @internal diff --git a/tests/unit/compiler/venom/test_inliner.py b/tests/unit/compiler/venom/test_inliner.py index 346ba16287..9b6690a15e 100644 --- a/tests/unit/compiler/venom/test_inliner.py +++ b/tests/unit/compiler/venom/test_inliner.py @@ -62,22 +62,3 @@ def foo(x: uint256, y: uint256): c = get_contract(code) c.foo(1, 2) - -# TODO: not allowed at all in Vyper at the moment -# def test_call_recursive(get_contract): -# code = """ -# @internal -# def foo(a: uint256) -> uint256: -# if a > 0: -# return self.foo(a - 1) -# else: -# return 1 - -# @external -# def test() -> uint256: -# return self.foo(10) -# """ - -# c = get_contract(code) - -# assert c.test() == 1 diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index f58c7a26b0..3d19365361 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -223,7 +223,9 @@ def _handle_internal_func( var = context.lookup_var(arg.name) if not var.typ._is_prim_word: continue - venom_arg = IRParameter(var.name, index, var.alloca.offset, var.alloca.size, None, None, None) + venom_arg = IRParameter( + var.name, index, var.alloca.offset, var.alloca.size, None, None, None + ) fn.args.append(venom_arg) index += 1 diff --git a/vyper/venom/passes/func_inliner.py b/vyper/venom/passes/func_inliner.py index e5aeacbd42..639ee9ff01 100644 --- a/vyper/venom/passes/func_inliner.py +++ b/vyper/venom/passes/func_inliner.py @@ -130,7 +130,9 @@ def _inline_call_site(self, func: IRFunction, call_site: IRInstruction) -> None: elif inst.annotation == self._RETURN_PC_ANNOTATION: inst.make_nop() else: + assert inst.annotation is not None arg = func.get_param_by_name(inst.annotation) + assert arg is not None inst.opcode = "store" inst.operands = [call_site.operands[arg.index + 1]] inst.annotation = None From 678c80c7a3b914f584d977fac87aeff886c329ab Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Wed, 29 Jan 2025 13:51:21 +0200 Subject: [PATCH 79/84] wrong paste --- tests/unit/compiler/venom/test_call_conv.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/unit/compiler/venom/test_call_conv.py b/tests/unit/compiler/venom/test_call_conv.py index 8857bde708..1556674c80 100644 --- a/tests/unit/compiler/venom/test_call_conv.py +++ b/tests/unit/compiler/venom/test_call_conv.py @@ -110,19 +110,3 @@ def bar(x: uint256) -> uint256: c = get_contract(code) assert c.bar(70) == 10 - - -def test_internal_assign(get_contract): - code = """ -@internal -def foo(x: uint256, y: int128) -> uint256: - x = convert(y, uint256) - return x - -@external -def bar(x: uint256) -> uint256: - return self.foo(x, 11) - """ - c = get_contract(code) - - assert c.bar(70) == 11 From 6d43af929cc63df3b25ac181831983b3ec2ea7da Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 2 Feb 2025 21:30:59 +0200 Subject: [PATCH 80/84] wip --- vyper/venom/__init__.py | 2 +- vyper/venom/ir_node_to_venom.py | 2 +- vyper/venom/venom_to_assembly.py | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index ec0b2360fb..379a50e105 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -102,7 +102,7 @@ def run_passes_on(ctx: IRContext, optimize: OptimizationLevel) -> None: for fn in ctx.functions.values(): ir_analyses[fn] = IRAnalysesCache(fn) - _run_global_passes(ctx, optimize, ir_analyses) + # _run_global_passes(ctx, optimize, ir_analyses) ir_analyses = {} for fn in ctx.functions.values(): diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 3d19365361..a7a06aa193 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -16,7 +16,7 @@ from vyper.venom.context import IRContext from vyper.venom.function import IRFunction, IRParameter -ENABLE_NEW_CALL_CONV = True +ENABLE_NEW_CALL_CONV = False # Instructions that are mapped to their inverse INVERSE_MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 485834d5ec..560a69e2e8 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -164,7 +164,15 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: assert fn.normalized, "Non-normalized CFG!" - self._generate_evm_for_basicblock_r(asm, fn.entry, StackModel()) + stack = StackModel() + # stack.push(IRVariable("return_pc")) + + param_insts = [inst for inst in fn.entry.instructions if inst.opcode == "param"] + for inst in param_insts: + stack.push(inst.output) + fn.entry.remove_instruction(inst) + + self._generate_evm_for_basicblock_r(asm, fn.entry, stack) # TODO make this property on IRFunction asm.extend(["_sym__ctor_exit", "JUMPDEST"]) From 6adc1c1727d3015015e746080fb8500532409ac0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 2 Feb 2025 21:44:10 +0200 Subject: [PATCH 81/84] wip --- vyper/venom/function.py | 1 + vyper/venom/ir_node_to_venom.py | 8 ++++---- vyper/venom/venom_to_assembly.py | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 6069b9ec98..01c673fa8f 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -25,6 +25,7 @@ class IRFunction: name: IRLabel # symbol name ctx: "IRContext" # type: ignore # noqa: F821 args: list + return_pc: IRVariable last_variable: int _basic_block_dict: dict[str, IRBasicBlock] diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a7a06aa193..665efd4e42 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -16,7 +16,7 @@ from vyper.venom.context import IRContext from vyper.venom.function import IRFunction, IRParameter -ENABLE_NEW_CALL_CONV = False +ENABLE_NEW_CALL_CONV = True # Instructions that are mapped to their inverse INVERSE_MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} @@ -244,8 +244,8 @@ def _handle_internal_func( arg.func_var = ret # return address - symbols["return_pc"] = bb.append_instruction("param") - bb.instructions[-1].annotation = "return_pc" + fn.return_pc = IRVariable("__return_pc") + symbols["return_pc"] = fn.return_pc if ENABLE_NEW_CALL_CONV: for arg in fn.args: @@ -469,7 +469,7 @@ def _convert_ir_bb(fn, ir, symbols): label = IRLabel(ir.args[0].value) if label.value == "return_pc": label = symbols.get("return_pc") - bb.append_instruction("ret", label) + bb.append_instruction("ret") else: bb.append_instruction("jmp", label) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 560a69e2e8..a0e232a4fe 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -167,10 +167,10 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: stack = StackModel() # stack.push(IRVariable("return_pc")) - param_insts = [inst for inst in fn.entry.instructions if inst.opcode == "param"] - for inst in param_insts: - stack.push(inst.output) - fn.entry.remove_instruction(inst) + # param_insts = [inst for inst in fn.entry.instructions if inst.opcode == "param"] + # for inst in param_insts: + # stack.push(inst.output) + # fn.entry.remove_instruction(inst) self._generate_evm_for_basicblock_r(asm, fn.entry, stack) From 7d6a20493d214765081f618f8f73f65c05c59fe8 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 2 Feb 2025 21:44:25 +0200 Subject: [PATCH 82/84] Revert "wip" This reverts commit 6adc1c1727d3015015e746080fb8500532409ac0. --- vyper/venom/function.py | 1 - vyper/venom/ir_node_to_venom.py | 8 ++++---- vyper/venom/venom_to_assembly.py | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/vyper/venom/function.py b/vyper/venom/function.py index 01c673fa8f..6069b9ec98 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -25,7 +25,6 @@ class IRFunction: name: IRLabel # symbol name ctx: "IRContext" # type: ignore # noqa: F821 args: list - return_pc: IRVariable last_variable: int _basic_block_dict: dict[str, IRBasicBlock] diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 665efd4e42..a7a06aa193 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -16,7 +16,7 @@ from vyper.venom.context import IRContext from vyper.venom.function import IRFunction, IRParameter -ENABLE_NEW_CALL_CONV = True +ENABLE_NEW_CALL_CONV = False # Instructions that are mapped to their inverse INVERSE_MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} @@ -244,8 +244,8 @@ def _handle_internal_func( arg.func_var = ret # return address - fn.return_pc = IRVariable("__return_pc") - symbols["return_pc"] = fn.return_pc + symbols["return_pc"] = bb.append_instruction("param") + bb.instructions[-1].annotation = "return_pc" if ENABLE_NEW_CALL_CONV: for arg in fn.args: @@ -469,7 +469,7 @@ def _convert_ir_bb(fn, ir, symbols): label = IRLabel(ir.args[0].value) if label.value == "return_pc": label = symbols.get("return_pc") - bb.append_instruction("ret") + bb.append_instruction("ret", label) else: bb.append_instruction("jmp", label) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index a0e232a4fe..560a69e2e8 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -167,10 +167,10 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: stack = StackModel() # stack.push(IRVariable("return_pc")) - # param_insts = [inst for inst in fn.entry.instructions if inst.opcode == "param"] - # for inst in param_insts: - # stack.push(inst.output) - # fn.entry.remove_instruction(inst) + param_insts = [inst for inst in fn.entry.instructions if inst.opcode == "param"] + for inst in param_insts: + stack.push(inst.output) + fn.entry.remove_instruction(inst) self._generate_evm_for_basicblock_r(asm, fn.entry, stack) From a5e4ed8110b0e82361041f8ecde406babdf13376 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 2 Feb 2025 21:44:33 +0200 Subject: [PATCH 83/84] Revert "wip" This reverts commit 6d43af929cc63df3b25ac181831983b3ec2ea7da. --- vyper/venom/__init__.py | 2 +- vyper/venom/ir_node_to_venom.py | 2 +- vyper/venom/venom_to_assembly.py | 10 +--------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 379a50e105..ec0b2360fb 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -102,7 +102,7 @@ def run_passes_on(ctx: IRContext, optimize: OptimizationLevel) -> None: for fn in ctx.functions.values(): ir_analyses[fn] = IRAnalysesCache(fn) - # _run_global_passes(ctx, optimize, ir_analyses) + _run_global_passes(ctx, optimize, ir_analyses) ir_analyses = {} for fn in ctx.functions.values(): diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index a7a06aa193..3d19365361 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -16,7 +16,7 @@ from vyper.venom.context import IRContext from vyper.venom.function import IRFunction, IRParameter -ENABLE_NEW_CALL_CONV = False +ENABLE_NEW_CALL_CONV = True # Instructions that are mapped to their inverse INVERSE_MAPPED_IR_INSTRUCTIONS = {"ne": "eq", "le": "gt", "sle": "sgt", "ge": "lt", "sge": "slt"} diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 560a69e2e8..485834d5ec 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -164,15 +164,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: assert fn.normalized, "Non-normalized CFG!" - stack = StackModel() - # stack.push(IRVariable("return_pc")) - - param_insts = [inst for inst in fn.entry.instructions if inst.opcode == "param"] - for inst in param_insts: - stack.push(inst.output) - fn.entry.remove_instruction(inst) - - self._generate_evm_for_basicblock_r(asm, fn.entry, stack) + self._generate_evm_for_basicblock_r(asm, fn.entry, StackModel()) # TODO make this property on IRFunction asm.extend(["_sym__ctor_exit", "JUMPDEST"]) From 77cf70a38fd9bd23d30f897be9a708add4e4f9d0 Mon Sep 17 00:00:00 2001 From: Harry Kalogirou Date: Sun, 2 Feb 2025 22:07:17 +0200 Subject: [PATCH 84/84] fix --- 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 6ce614f1d8..b15d0e7523 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -415,8 +415,8 @@ def copy(self, prefix: str = "") -> "IRInstruction": inst.parent = self.parent inst.liveness = self.liveness.copy() inst.annotation = self.annotation - inst.ast_source = inst.ast_source - inst.error_msg = inst.error_msg + inst.ast_source = self.ast_source + inst.error_msg = self.error_msg return inst def __repr__(self) -> str: