From d552898a23fb0ce0ab3955c667113c076c37b72b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 May 2024 10:27:11 -0400 Subject: [PATCH 01/64] feat: multidimensional fencing --- vyper/venom/basicblock.py | 2 - vyper/venom/passes/dft.py | 102 +++++++++++++++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 10 deletions(-) diff --git a/vyper/venom/basicblock.py b/vyper/venom/basicblock.py index 19e8801663..d56d535334 100644 --- a/vyper/venom/basicblock.py +++ b/vyper/venom/basicblock.py @@ -197,7 +197,6 @@ class IRInstruction: liveness: OrderedSet[IRVariable] dup_requirements: OrderedSet[IRVariable] parent: "IRBasicBlock" - fence_id: int annotation: Optional[str] ast_source: Optional[IRnode] error_msg: Optional[str] @@ -215,7 +214,6 @@ def __init__( self.output = output self.liveness = OrderedSet() self.dup_requirements = OrderedSet() - self.fence_id = -1 self.annotation = None self.ast_source = None self.error_msg = None diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 8429c19711..134d197dbc 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -1,14 +1,102 @@ +from dataclasses import asdict, dataclass + from vyper.utils import OrderedSet from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass +_ALL = ("storage", "transient", "memory") + +writes = { + "sstore": "storage", + "tstore": "transient", + "mstore": "memory", + "istore": "immutables", + "delegatecall": _ALL, + "call": _ALL, + "create": _ALL, + "create2": _ALL, + "invoke": _ALL, # could be smarter, look up the effects of the invoked function + "staticcall": "memory", + "dloadbytes": "memory", + "returndatacopy": "memory", + "calldatacopy": "memory", + "codecopy": "memory", + "extcodecopy": "memory", + "mcopy": "memory", +} +reads = { + "sload": "storage", + "tload": "transient", + "iload": "immutables", + "mstore": "memory", + "mcopy": "memory", + "call": _ALL, + "delegatecall": _ALL, + "staticcall": _ALL, + "return": "memory", +} + + +@dataclass +class Fence: + storage: int = 0 + memory: int = 0 + transient: int = 0 + immutables: int = 0 + + +def _compute_fence(opcode: str, fence: Fence) -> Fence: + if opcode not in writes: + return fence + + effects = get_writes(opcode) + + tmp = asdict(fence) + for eff in effects: + tmp[eff] += 1 + + return Fence(**tmp) + + +def get_reads(opcode): + ret = reads.get(opcode, ()) + if not isinstance(ret, tuple): + ret = (ret,) + return ret + + +def get_writes(opcode): + ret = writes.get(opcode, ()) + if not isinstance(ret, tuple): + ret = (ret,) + return ret + + +def _can_reorder(inst1, inst2): + if inst1.parent != inst2.parent: + return False + + effects = ( + get_reads(inst1.opcode) + + get_reads(inst2.opcode) + + get_writes(inst1.opcode) + + get_writes(inst2.opcode) + ) + + for eff in effects: + if getattr(inst1.fence, eff) != getattr(inst2.fence, eff): # type: ignore + return False + + return True + class DFTPass(IRPass): function: IRFunction inst_order: dict[IRInstruction, int] inst_order_num: int + fence: Fence def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: int = 0): for op in inst.get_outputs(): @@ -16,8 +104,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: uses = self.dfg.get_uses(op) for uses_this in uses: - if uses_this.parent != inst.parent or uses_this.fence_id != inst.fence_id: - # don't reorder across basic block or fence boundaries + if not _can_reorder(inst, uses_this): continue # if the instruction is a terminator, we need to place @@ -43,9 +130,9 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: for op in inst.get_input_variables(): target = self.dfg.get_producing_instruction(op) assert target is not None, f"no producing instruction for {op}" - if target.parent != inst.parent or target.fence_id != inst.fence_id: - # don't reorder across basic block or fence boundaries + if not _can_reorder(target, inst): continue + self._process_instruction_r(bb, target, offset) self.inst_order[inst] = self.inst_order_num + offset @@ -54,9 +141,8 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: self.function.append_basic_block(bb) for inst in bb.instructions: - inst.fence_id = self.fence_id - if inst.volatile: - self.fence_id += 1 + inst.fence = self.fence # type: ignore + self.fence = _compute_fence(inst.opcode, self.fence) # We go throught the instructions and calculate the order in which they should be executed # based on the data flow graph. This order is stored in the inst_order dictionary. @@ -71,7 +157,7 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: def run_pass(self) -> None: self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) - self.fence_id = 0 + self.fence = Fence() self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet() basic_blocks = list(self.function.get_basic_blocks()) From d4dad09ef31177a14d7236eb454acedd617eda97 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 May 2024 12:19:15 -0400 Subject: [PATCH 02/64] invalidate liveness --- vyper/venom/passes/dft.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 134d197dbc..fe7c762464 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -2,6 +2,8 @@ from vyper.utils import OrderedSet from vyper.venom.analysis.dfg import DFGAnalysis +from vyper.venom.analysis.cfg import CFGAnalysis +from vyper.venom.analysis.liveness import LivenessAnalysis from vyper.venom.basicblock import BB_TERMINATORS, IRBasicBlock, IRInstruction, IRVariable from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass @@ -138,8 +140,6 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: self.inst_order[inst] = self.inst_order_num + offset def _process_basic_block(self, bb: IRBasicBlock) -> None: - self.function.append_basic_block(bb) - for inst in bb.instructions: inst.fence = self.fence # type: ignore self.fence = _compute_fence(inst.opcode, self.fence) @@ -162,6 +162,7 @@ def run_pass(self) -> None: basic_blocks = list(self.function.get_basic_blocks()) - self.function.clear_basic_blocks() for bb in basic_blocks: self._process_basic_block(bb) + + self.analyses_cache.invalidate_analysis(LivenessAnalysis) From a50473a2b6989856cf158dab28ca5d1008d2f6c1 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 May 2024 16:21:16 -0400 Subject: [PATCH 03/64] add log, revert to effects --- vyper/venom/passes/dft.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 0c50235d3d..11d550f287 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -8,7 +8,7 @@ from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass -_ALL = ("storage", "transient", "memory") +_ALL = ("storage", "transient", "memory", "immutables") writes = { "sstore": "storage", @@ -37,6 +37,8 @@ "call": _ALL, "delegatecall": _ALL, "staticcall": _ALL, + "log": "memory", + "revert": "memory", "return": "memory", } From c7da0812bdc76c609596c045ff6e11a818eed556 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 May 2024 16:38:34 -0400 Subject: [PATCH 04/64] sha3 fence --- vyper/venom/passes/dft.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 11d550f287..eb753e7f04 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -40,6 +40,7 @@ "log": "memory", "revert": "memory", "return": "memory", + "sha3": "memory", } From 80c51545a156abdccaf1ee6abb73041605bfb015 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 May 2024 17:07:12 -0400 Subject: [PATCH 05/64] fix fence checker --- vyper/venom/passes/dft.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index eb753e7f04..06f95958d1 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -1,7 +1,6 @@ from dataclasses import asdict, dataclass from vyper.utils import OrderedSet -from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.liveness import LivenessAnalysis from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable @@ -79,16 +78,21 @@ def get_writes(opcode): return ret +def _intersect(tuple1, tuple2): + ret = [] + for s in tuple1: + if s in tuple2: + ret.append(s) + return tuple(ret) + + def _can_reorder(inst1, inst2): if inst1.parent != inst2.parent: return False - effects = ( - get_reads(inst1.opcode) - + get_reads(inst2.opcode) - + get_writes(inst1.opcode) - + get_writes(inst2.opcode) - ) + effects = _intersect(get_writes(inst1.opcode), get_writes(inst2.opcode)) + # is this required? + effects += _intersect(get_reads(inst2.opcode), get_writes(inst1.opcode)) for eff in effects: if getattr(inst1.fence, eff) != getattr(inst2.fence, eff): # type: ignore @@ -163,9 +167,7 @@ def run_pass(self) -> None: self.fence = Fence() self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet() - basic_blocks = list(self.function.get_basic_blocks()) - - for bb in basic_blocks: + for bb in self.function.get_basic_blocks(): self._process_basic_block(bb) self.analyses_cache.invalidate_analysis(LivenessAnalysis) From 6cf7b6bf15116ca8be1b3d1a73eca7625240a287 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 May 2024 17:32:35 -0400 Subject: [PATCH 06/64] feat[venom]: extract literals pass extract IRLiterals which are instruction arguments; this reduces pressure on the stack scheduler --- vyper/venom/__init__.py | 2 ++ vyper/venom/passes/extract_literals.py | 37 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 vyper/venom/passes/extract_literals.py diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index cd981cd462..d7a2c27ddf 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -13,6 +13,7 @@ from vyper.venom.passes.branch_optimization import BranchOptimizationPass from vyper.venom.passes.dft import DFTPass from vyper.venom.passes.make_ssa import MakeSSA +from vyper.venom.passes.extract_literals import ExtractLiteralsPass from vyper.venom.passes.mem2var import Mem2Var from vyper.venom.passes.remove_unused_variables import RemoveUnusedVariablesPass from vyper.venom.passes.sccp import SCCP @@ -53,6 +54,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None: SimplifyCFGPass(ac, fn).run_pass() AlgebraicOptimizationPass(ac, fn).run_pass() BranchOptimizationPass(ac, fn).run_pass() + ExtractLiteralsPass(ac, fn).run_pass() RemoveUnusedVariablesPass(ac, fn).run_pass() DFTPass(ac, fn).run_pass() diff --git a/vyper/venom/passes/extract_literals.py b/vyper/venom/passes/extract_literals.py new file mode 100644 index 0000000000..3ee54af3c9 --- /dev/null +++ b/vyper/venom/passes/extract_literals.py @@ -0,0 +1,37 @@ +from vyper.utils import OrderedSet +from vyper.venom.analysis.dfg import DFGAnalysis +from vyper.venom.analysis.liveness import LivenessAnalysis +from vyper.venom.basicblock import IRInstruction, IRLiteral +from vyper.venom.passes.base_pass import IRPass + + +class ExtractLiteralsPass(IRPass): + """ + This pass extracts literals so that they can be reordered by the DFT pass + """ + def run_pass(self): + for bb in self.function.get_basic_blocks(): + self._process_bb(bb) + + self.analyses_cache.invalidate_analysis(DFGAnalysis) + self.analyses_cache.invalidate_analysis(LivenessAnalysis) + + def _process_bb(self, bb): + i = 0 + while i < len(bb.instructions): + inst = bb.instructions[i] + if inst.opcode == "store": + i += 1 + continue + + for j, op in enumerate(inst.operands): + # first operand to log is magic + if inst.opcode == "log" and j == 0: + continue + + if isinstance(op, IRLiteral): + var = self.function.get_next_variable() + to_insert = IRInstruction("store", [op], var) + bb.insert_instruction(to_insert, index=i) + inst.operands[j] = var + i += 1 From 04b53d055ce2e5aa363bbc64989ca44b012bf2ab Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 May 2024 19:36:02 -0400 Subject: [PATCH 07/64] don't reorder param instructions --- vyper/venom/passes/dft.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 06f95958d1..008c6a6e15 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -129,10 +129,9 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: if inst.is_bb_terminator: offset = len(bb.instructions) - if inst.opcode == "phi": - # phi instructions stay at the beginning of the basic block - # and no input processing is needed - # bb.instructions.append(inst) + if inst.opcode in ("phi", "param"): + # phi and param instructions stay at the beginning of the basic + # block and no input processing is needed self.inst_order[inst] = 0 return From 8adf7838a6b7600948e8d5eb3c851bd024647f16 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 May 2024 19:38:21 -0400 Subject: [PATCH 08/64] remove a comment --- vyper/venom/passes/dft.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 008c6a6e15..8408b7f789 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -91,7 +91,6 @@ def _can_reorder(inst1, inst2): return False effects = _intersect(get_writes(inst1.opcode), get_writes(inst2.opcode)) - # is this required? effects += _intersect(get_reads(inst2.opcode), get_writes(inst1.opcode)) for eff in effects: From 8506bbbbac008559951d8dc12d1c85404b2a724c Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 May 2024 19:54:09 -0400 Subject: [PATCH 09/64] small perf --- vyper/venom/analysis/liveness.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/analysis/liveness.py b/vyper/venom/analysis/liveness.py index ac06ff4dae..5ee22e4be8 100644 --- a/vyper/venom/analysis/liveness.py +++ b/vyper/venom/analysis/liveness.py @@ -57,7 +57,8 @@ def _calculate_out_vars(self, bb: IRBasicBlock) -> bool: out_vars = bb.out_vars.copy() for out_bb in bb.cfg_out: target_vars = self.input_vars_from(bb, out_bb) - bb.out_vars = bb.out_vars.union(target_vars) + bb.out_vars |= target_vars + return out_vars != bb.out_vars # calculate the input variables into self from source From ef7c3691c5597c56104fec591b0db4ffbf076602 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 07:38:45 -0400 Subject: [PATCH 10/64] feat: store expansion pass --- vyper/venom/__init__.py | 2 ++ vyper/venom/passes/store_expansion.py | 50 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 vyper/venom/passes/store_expansion.py diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index d7a2c27ddf..2da94bf98d 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -14,6 +14,7 @@ from vyper.venom.passes.dft import DFTPass from vyper.venom.passes.make_ssa import MakeSSA from vyper.venom.passes.extract_literals import ExtractLiteralsPass +from vyper.venom.passes.store_expansion import StoreExpansionPass from vyper.venom.passes.mem2var import Mem2Var from vyper.venom.passes.remove_unused_variables import RemoveUnusedVariablesPass from vyper.venom.passes.sccp import SCCP @@ -55,6 +56,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None: AlgebraicOptimizationPass(ac, fn).run_pass() BranchOptimizationPass(ac, fn).run_pass() ExtractLiteralsPass(ac, fn).run_pass() + StoreExpansionPass(ac, fn).run_pass() RemoveUnusedVariablesPass(ac, fn).run_pass() DFTPass(ac, fn).run_pass() diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py new file mode 100644 index 0000000000..91911e3854 --- /dev/null +++ b/vyper/venom/passes/store_expansion.py @@ -0,0 +1,50 @@ +from vyper.venom.analysis.cfg import CFGAnalysis +from vyper.venom.analysis.dfg import DFGAnalysis +from vyper.venom.analysis.dominators import DominatorTreeAnalysis +from vyper.venom.analysis.liveness import LivenessAnalysis +from vyper.venom.basicblock import IRVariable, IRInstruction +from vyper.venom.passes.base_pass import IRPass + + +class StoreExpansionPass(IRPass): + """ + This pass expands variables to their uses though `store` instructions, + reducing pressure on the stack scheduler + """ + + def run_pass(self): + self.analyses_cache.request_analysis(CFGAnalysis) + dfg = self.analyses_cache.request_analysis(DFGAnalysis) + + for bb in self.function.get_basic_blocks(): + for idx, inst in enumerate(bb.instructions): + if inst.opcode != "store": + continue + + self._process_inst(dfg, inst, idx) + + self.analyses_cache.invalidate_analysis(LivenessAnalysis) + self.analyses_cache.invalidate_analysis(DFGAnalysis) + + def _process_inst(self, dfg, inst, idx): + """ + Process store instruction. If the variable is only used by a load instruction, + forward the variable to the load instruction. + """ + var = inst.output + uses = dfg.get_uses(var) + + insertion_idx = idx + 1 + + for use_inst in uses: + if use_inst.parent != inst.parent: + continue + + for i, operand in enumerate(use_inst.operands): + if operand == var: + new_var = self.function.get_next_variable() + new_inst = IRInstruction("store", [var], new_var) + inst.parent.insert_instruction(new_inst, insertion_idx) + insertion_idx += 1 + use_inst.operands[i] = new_var + From ff700b49ccb121e3a51bd8c2c3f86c09d85fa4cd Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 07:53:20 -0400 Subject: [PATCH 11/64] lint --- vyper/venom/__init__.py | 4 ++-- vyper/venom/passes/extract_literals.py | 1 + vyper/venom/passes/store_expansion.py | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 2da94bf98d..9b1f67b195 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -12,14 +12,14 @@ from vyper.venom.passes.algebraic_optimization import AlgebraicOptimizationPass from vyper.venom.passes.branch_optimization import BranchOptimizationPass from vyper.venom.passes.dft import DFTPass -from vyper.venom.passes.make_ssa import MakeSSA from vyper.venom.passes.extract_literals import ExtractLiteralsPass -from vyper.venom.passes.store_expansion import StoreExpansionPass +from vyper.venom.passes.make_ssa import MakeSSA from vyper.venom.passes.mem2var import Mem2Var from vyper.venom.passes.remove_unused_variables import RemoveUnusedVariablesPass from vyper.venom.passes.sccp import SCCP from vyper.venom.passes.simplify_cfg import SimplifyCFGPass from vyper.venom.passes.store_elimination import StoreElimination +from vyper.venom.passes.store_expansion import StoreExpansionPass from vyper.venom.venom_to_assembly import VenomCompiler DEFAULT_OPT_LEVEL = OptimizationLevel.default() diff --git a/vyper/venom/passes/extract_literals.py b/vyper/venom/passes/extract_literals.py index 3ee54af3c9..f504481616 100644 --- a/vyper/venom/passes/extract_literals.py +++ b/vyper/venom/passes/extract_literals.py @@ -9,6 +9,7 @@ class ExtractLiteralsPass(IRPass): """ This pass extracts literals so that they can be reordered by the DFT pass """ + def run_pass(self): for bb in self.function.get_basic_blocks(): self._process_bb(bb) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index 91911e3854..ff60e7c5ca 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -2,7 +2,7 @@ from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.dominators import DominatorTreeAnalysis from vyper.venom.analysis.liveness import LivenessAnalysis -from vyper.venom.basicblock import IRVariable, IRInstruction +from vyper.venom.basicblock import IRInstruction, IRVariable from vyper.venom.passes.base_pass import IRPass @@ -47,4 +47,3 @@ def _process_inst(self, dfg, inst, idx): inst.parent.insert_instruction(new_inst, insertion_idx) insertion_idx += 1 use_inst.operands[i] = new_var - From ea9b1c519abe89ddb03accf0eaef7c5648e47f33 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 09:57:37 -0400 Subject: [PATCH 12/64] remove inter-bb restriction --- vyper/venom/passes/store_expansion.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index ff60e7c5ca..0f260fb224 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -38,7 +38,8 @@ def _process_inst(self, dfg, inst, idx): for use_inst in uses: if use_inst.parent != inst.parent: - continue + pass + #continue for i, operand in enumerate(use_inst.operands): if operand == var: From adbf01cf7549c08905d3120c2a5ebe5401192d18 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 10:01:14 -0400 Subject: [PATCH 13/64] don't replace first use --- vyper/venom/passes/store_expansion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index 0f260fb224..5c86a5518d 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -36,7 +36,7 @@ def _process_inst(self, dfg, inst, idx): insertion_idx = idx + 1 - for use_inst in uses: + for use_inst in uses[1:]: if use_inst.parent != inst.parent: pass #continue From f3acde1ba5a5350ccd6fb2ba7a85766b1bf9e172 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 30 May 2024 18:07:03 -0400 Subject: [PATCH 14/64] fix terminator instruction --- vyper/venom/passes/dft.py | 42 +++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 8408b7f789..6b07103f4d 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -102,11 +102,9 @@ def _can_reorder(inst1, inst2): class DFTPass(IRPass): function: IRFunction - inst_order: dict[IRInstruction, int] - inst_order_num: int fence: Fence - def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: int = 0): + def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): for op in inst.get_outputs(): assert isinstance(op, IRVariable), f"expected variable, got {op}" uses = self.dfg.get_uses(op) @@ -115,23 +113,16 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: if not _can_reorder(inst, uses_this): continue - # if the instruction is a terminator, we need to place - # it at the end of the basic block - # along with all the instructions that "lead" to it - self._process_instruction_r(bb, uses_this, offset) + self._process_instruction_r(bb, uses_this) if inst in self.visited_instructions: return self.visited_instructions.add(inst) - self.inst_order_num += 1 - if inst.is_bb_terminator: - offset = len(bb.instructions) - - if inst.opcode in ("phi", "param"): - # phi and param instructions stay at the beginning of the basic + if inst.opcode == "phi": + # phi instructions stay at the beginning of the basic # block and no input processing is needed - self.inst_order[inst] = 0 + bb.instructions.append(inst) return for op in inst.get_input_variables(): @@ -139,25 +130,28 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction, offset: assert target is not None, f"no producing instruction for {op}" if not _can_reorder(target, inst): continue + self._process_instruction_r(bb, target) - self._process_instruction_r(bb, target, offset) - - self.inst_order[inst] = self.inst_order_num + offset + if not inst.is_bb_terminator: + bb.instructions.append(inst) def _process_basic_block(self, bb: IRBasicBlock) -> None: + # preprocess, compute fence for every instruction for inst in bb.instructions: inst.fence = self.fence # type: ignore self.fence = _compute_fence(inst.opcode, self.fence) - # We go throught the instructions and calculate the order in which they should be executed - # based on the data flow graph. This order is stored in the inst_order dictionary. - # We then sort the instructions based on this order. - self.inst_order = {} - self.inst_order_num = 0 - for inst in bb.instructions: + term_inst = bb.instructions[-1] + + instructions = bb.instructions.copy() + + bb.instructions.clear() + + for inst in instructions: self._process_instruction_r(bb, inst) - bb.instructions.sort(key=lambda x: self.inst_order[x]) + # force terminating instruction to come after everything else in the block + bb.instructions.append(term_inst) def run_pass(self) -> None: self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) From 988a1a9f9207cd9a0f610f6bf12275e949a6db65 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 10:30:11 -0400 Subject: [PATCH 15/64] wip - fix fence --- vyper/venom/passes/dft.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 6b07103f4d..b58c9eba8a 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -138,8 +138,14 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): def _process_basic_block(self, bb: IRBasicBlock) -> None: # preprocess, compute fence for every instruction for inst in bb.instructions: - inst.fence = self.fence # type: ignore self.fence = _compute_fence(inst.opcode, self.fence) + inst.fence = self.fence # type: ignore + + if False: + print("ENTER") + print(inst) + print(inst.fence) + print() term_inst = bb.instructions[-1] From 058c4dbc065ba9085a51157b8faa4764322d252a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 10:36:55 -0400 Subject: [PATCH 16/64] fix can_reorder --- vyper/venom/passes/dft.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index b58c9eba8a..0d02b3b716 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -90,12 +90,10 @@ def _can_reorder(inst1, inst2): if inst1.parent != inst2.parent: return False - effects = _intersect(get_writes(inst1.opcode), get_writes(inst2.opcode)) - effects += _intersect(get_reads(inst2.opcode), get_writes(inst1.opcode)) - - for eff in effects: - if getattr(inst1.fence, eff) != getattr(inst2.fence, eff): # type: ignore - return False + if len(_intersect(get_writes(inst1.opcode), get_writes(inst2.opcode))) > 0: + return False + if len(_intersect(get_reads(inst2.opcode), get_writes(inst1.opcode))) > 0: + return False return True @@ -138,8 +136,8 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): def _process_basic_block(self, bb: IRBasicBlock) -> None: # preprocess, compute fence for every instruction for inst in bb.instructions: - self.fence = _compute_fence(inst.opcode, self.fence) inst.fence = self.fence # type: ignore + self.fence = _compute_fence(inst.opcode, self.fence) if False: print("ENTER") From 3555fcbc190894b5bf5b5ea8ac078dd21ce63a79 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 11:44:59 -0400 Subject: [PATCH 17/64] force phi instructions first --- vyper/venom/passes/dft.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 0d02b3b716..610ceeec86 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -120,7 +120,6 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): if inst.opcode == "phi": # phi instructions stay at the beginning of the basic # block and no input processing is needed - bb.instructions.append(inst) return for op in inst.get_input_variables(): @@ -130,7 +129,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): continue self._process_instruction_r(bb, target) - if not inst.is_bb_terminator: + if not inst.is_bb_terminator and inst.opcode != "phi": bb.instructions.append(inst) def _process_basic_block(self, bb: IRBasicBlock) -> None: @@ -150,6 +149,7 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: instructions = bb.instructions.copy() bb.instructions.clear() + bb.instructions = [inst for inst in instructions if inst.opcode == "phi"] for inst in instructions: self._process_instruction_r(bb, inst) From 3e096de84a1f485dce1b095ecaa1c0856d7dc122 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 11:58:00 -0400 Subject: [PATCH 18/64] fix can_reorder(?) --- vyper/venom/passes/dft.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 610ceeec86..b7fdf3c682 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -90,10 +90,17 @@ def _can_reorder(inst1, inst2): if inst1.parent != inst2.parent: return False - if len(_intersect(get_writes(inst1.opcode), get_writes(inst2.opcode))) > 0: - return False - if len(_intersect(get_reads(inst2.opcode), get_writes(inst1.opcode))) > 0: - return False + for eff in get_reads(inst1.opcode): + #if eff in get_writes(inst2.opcode): + # return False + if getattr(inst1.fence, eff) != getattr(inst2.fence, eff): + return False + + for eff in get_reads(inst2.opcode): + #if eff in get_writes(inst1.opcode): + # return False + if getattr(inst1.fence, eff) != getattr(inst2.fence, eff): + return False return True From 7dc473d87f75c614c335983aa6004b466d4029bc Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 12:01:27 -0400 Subject: [PATCH 19/64] clean up phi --- vyper/venom/passes/dft.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index b7fdf3c682..4a6ff0de7f 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -124,11 +124,6 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): return self.visited_instructions.add(inst) - if inst.opcode == "phi": - # phi instructions stay at the beginning of the basic - # block and no input processing is needed - return - for op in inst.get_input_variables(): target = self.dfg.get_producing_instruction(op) assert target is not None, f"no producing instruction for {op}" @@ -155,7 +150,7 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: instructions = bb.instructions.copy() - bb.instructions.clear() + # put phi instructions first bb.instructions = [inst for inst in instructions if inst.opcode == "phi"] for inst in instructions: From e087c4f3b73908d5b37050c296fa3f4650f14984 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 11:59:46 -0400 Subject: [PATCH 20/64] update fence calculation --- vyper/venom/passes/dft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 4a6ff0de7f..a7c91fa5b0 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -137,8 +137,8 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): def _process_basic_block(self, bb: IRBasicBlock) -> None: # preprocess, compute fence for every instruction for inst in bb.instructions: - inst.fence = self.fence # type: ignore self.fence = _compute_fence(inst.opcode, self.fence) + inst.fence = self.fence # type: ignore if False: print("ENTER") From 50e13cfadf34622e3c965bd66b5e3757ea14644b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 12:08:26 -0400 Subject: [PATCH 21/64] Revert "update fence calculation" This reverts commit e087c4f3b73908d5b37050c296fa3f4650f14984. --- vyper/venom/passes/dft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index a7c91fa5b0..4a6ff0de7f 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -137,8 +137,8 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): def _process_basic_block(self, bb: IRBasicBlock) -> None: # preprocess, compute fence for every instruction for inst in bb.instructions: - self.fence = _compute_fence(inst.opcode, self.fence) inst.fence = self.fence # type: ignore + self.fence = _compute_fence(inst.opcode, self.fence) if False: print("ENTER") From 490c3771b3be2238ba49e98662d167bb0e7519d3 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 12:18:36 -0400 Subject: [PATCH 22/64] sort again --- vyper/venom/passes/dft.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 4a6ff0de7f..01e105c94d 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -131,9 +131,6 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): continue self._process_instruction_r(bb, target) - if not inst.is_bb_terminator and inst.opcode != "phi": - bb.instructions.append(inst) - def _process_basic_block(self, bb: IRBasicBlock) -> None: # preprocess, compute fence for every instruction for inst in bb.instructions: @@ -150,14 +147,17 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: instructions = bb.instructions.copy() - # put phi instructions first - bb.instructions = [inst for inst in instructions if inst.opcode == "phi"] - for inst in instructions: self._process_instruction_r(bb, inst) - # force terminating instruction to come after everything else in the block - bb.instructions.append(term_inst) + def key(inst): + if inst.opcode == "phi": + return 0 + if inst.is_bb_terminator: + return 2 + return 1 + + bb.instructions.sort(key=key) def run_pass(self) -> None: self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) From 608bfaaaee011b566fdc3f3a6012c473983ed1f1 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 13:14:33 -0400 Subject: [PATCH 23/64] traverse out_vars --- vyper/venom/passes/dft.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 01e105c94d..6cef131b84 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -131,6 +131,8 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): continue self._process_instruction_r(bb, target) + bb.instructions.append(inst) + def _process_basic_block(self, bb: IRBasicBlock) -> None: # preprocess, compute fence for every instruction for inst in bb.instructions: @@ -143,10 +145,17 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: print(inst.fence) print() - term_inst = bb.instructions[-1] - instructions = bb.instructions.copy() + bb.instructions.clear() + + # start with out liveness + for var in bb.out_vars: + inst = self.dfg.get_producing_instruction(var) + if inst.parent != bb: + continue + self._process_instruction_r(bb, inst) + for inst in instructions: self._process_instruction_r(bb, inst) @@ -161,6 +170,7 @@ def key(inst): def run_pass(self) -> None: self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) + self.analyses_cache.request_analysis(LivenessAnalysis) # use out_vars self.fence = Fence() self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet() From afb49a644e1bcbed76f8806ada8b517545cda0b8 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 13:30:36 -0400 Subject: [PATCH 24/64] update can_reorder --- vyper/venom/passes/dft.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 6cef131b84..3aad9f44ab 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -90,15 +90,11 @@ def _can_reorder(inst1, inst2): if inst1.parent != inst2.parent: return False - for eff in get_reads(inst1.opcode): - #if eff in get_writes(inst2.opcode): - # return False + for eff in get_reads(inst1.opcode) + get_writes(inst1.opcode): if getattr(inst1.fence, eff) != getattr(inst2.fence, eff): return False - for eff in get_reads(inst2.opcode): - #if eff in get_writes(inst1.opcode): - # return False + for eff in get_reads(inst2.opcode) + get_writes(inst2.opcode): if getattr(inst1.fence, eff) != getattr(inst2.fence, eff): return False From 3d0c4bb14fef7ecc0e0f5c9524c5541664934d5e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 18:57:05 -0400 Subject: [PATCH 25/64] fix a table --- vyper/venom/passes/dft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 3aad9f44ab..41685f3784 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -31,7 +31,7 @@ "sload": "storage", "tload": "transient", "iload": "immutables", - "mstore": "memory", + "mload": "memory", "mcopy": "memory", "call": _ALL, "delegatecall": _ALL, From a44c0bd68b71cd761944973375eef9e8280c8797 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 19:06:27 -0400 Subject: [PATCH 26/64] wip - effects graph --- vyper/venom/passes/dft.py | 99 +++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 41685f3784..36fbbbae38 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -1,3 +1,4 @@ +from collections import defaultdict from dataclasses import asdict, dataclass from vyper.utils import OrderedSet @@ -51,54 +52,74 @@ class Fence: immutables: int = 0 -def _compute_fence(opcode: str, fence: Fence) -> Fence: - if opcode not in writes: - return fence +# effects graph +class EffectsG: + def __init__(self): + self._graph = defaultdict(list) - effects = get_writes(opcode) + def analyze(self, bb): + fence = Fence() - tmp = asdict(fence) - for eff in effects: - tmp[eff] += 1 + groups = {} + terms = {} - return Fence(**tmp) + for inst in bb.instructions: + reads = _get_reads(inst.opcode) + writes = _get_writes(inst.opcode) + for eff in reads: + fence_id = getattr(fence, eff) + group = groups.setdefault((eff, fence_id), []) + group.append(inst) + # collect writes in a separate dict + for eff in writes: + fence_id = getattr(fence, eff) + assert (eff, fence_id) not in terms + terms[(eff, fence_id)] = inst -def get_reads(opcode): + fence = _compute_fence(inst.opcode, fence) + + for (effect, fence_id), write_inst in terms.items(): + reads = groups.get((effect, fence_id), []) + self._graph[write_inst].extend(reads) + + prev_id = fence_id - 1 + if (prev_write := terms.get((effect, prev_id))) is not None: + self._graph[write_inst].append(prev_write) + + next_reads = groups.get((effect, fence_id + 1), []) + for inst in next_reads: + self._graph[inst].append(write_inst) + + def required_by(self, inst): + return self._graph.get(inst, []) + + +def _get_reads(opcode): ret = reads.get(opcode, ()) if not isinstance(ret, tuple): ret = (ret,) return ret -def get_writes(opcode): +def _get_writes(opcode): ret = writes.get(opcode, ()) if not isinstance(ret, tuple): ret = (ret,) return ret -def _intersect(tuple1, tuple2): - ret = [] - for s in tuple1: - if s in tuple2: - ret.append(s) - return tuple(ret) - - -def _can_reorder(inst1, inst2): - if inst1.parent != inst2.parent: - return False +def _compute_fence(opcode: str, fence: Fence) -> Fence: + if opcode not in writes: + return fence - for eff in get_reads(inst1.opcode) + get_writes(inst1.opcode): - if getattr(inst1.fence, eff) != getattr(inst2.fence, eff): - return False + effects = _get_writes(opcode) - for eff in get_reads(inst2.opcode) + get_writes(inst2.opcode): - if getattr(inst1.fence, eff) != getattr(inst2.fence, eff): - return False + tmp = asdict(fence) + for eff in effects: + tmp[eff] += 1 - return True + return Fence(**tmp) class DFTPass(IRPass): @@ -111,7 +132,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): uses = self.dfg.get_uses(op) for uses_this in uses: - if not _can_reorder(inst, uses_this): + if uses_this.parent != inst.parent: continue self._process_instruction_r(bb, uses_this) @@ -120,29 +141,23 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): return self.visited_instructions.add(inst) + for target in self._effects_g.required_by(inst): + self._process_instruction_r(bb, target) + for op in inst.get_input_variables(): target = self.dfg.get_producing_instruction(op) assert target is not None, f"no producing instruction for {op}" - if not _can_reorder(target, inst): + if target.parent != inst.parent: continue self._process_instruction_r(bb, target) bb.instructions.append(inst) def _process_basic_block(self, bb: IRBasicBlock) -> None: - # preprocess, compute fence for every instruction - for inst in bb.instructions: - inst.fence = self.fence # type: ignore - self.fence = _compute_fence(inst.opcode, self.fence) - - if False: - print("ENTER") - print(inst) - print(inst.fence) - print() + self._effects_g = EffectsG() + self._effects_g.analyze(bb) instructions = bb.instructions.copy() - bb.instructions.clear() # start with out liveness @@ -155,6 +170,8 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: for inst in instructions: self._process_instruction_r(bb, inst) + assert len(bb.instructions) == len(instructions), (instructions, bb) + def key(inst): if inst.opcode == "phi": return 0 From f1bb354d7c273cfc88a44e0a11e4aa1bff66d6d3 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 20:04:22 -0400 Subject: [PATCH 27/64] update table --- vyper/venom/passes/dft.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 36fbbbae38..74de3c245c 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -15,12 +15,12 @@ "tstore": "transient", "mstore": "memory", "istore": "immutables", - "delegatecall": _ALL, "call": _ALL, + "delegatecall": _ALL, + "staticcall": "memory", "create": _ALL, "create2": _ALL, "invoke": _ALL, # could be smarter, look up the effects of the invoked function - "staticcall": "memory", "dloadbytes": "memory", "returndatacopy": "memory", "calldatacopy": "memory", From 6e87f1f7e3876aa6eb44758d10624a5db53f95c9 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 20:12:13 -0400 Subject: [PATCH 28/64] minor cleanup --- vyper/venom/passes/dft.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 74de3c245c..d833a32673 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -60,7 +60,7 @@ def __init__(self): def analyze(self, bb): fence = Fence() - groups = {} + read_groups = {} terms = {} for inst in bb.instructions: @@ -68,7 +68,7 @@ def analyze(self, bb): writes = _get_writes(inst.opcode) for eff in reads: fence_id = getattr(fence, eff) - group = groups.setdefault((eff, fence_id), []) + group = read_groups.setdefault((eff, fence_id), []) group.append(inst) # collect writes in a separate dict @@ -80,17 +80,23 @@ def analyze(self, bb): fence = _compute_fence(inst.opcode, fence) for (effect, fence_id), write_inst in terms.items(): - reads = groups.get((effect, fence_id), []) + reads = read_groups.get((effect, fence_id), []) self._graph[write_inst].extend(reads) - prev_id = fence_id - 1 - if (prev_write := terms.get((effect, prev_id))) is not None: - self._graph[write_inst].append(prev_write) + next_id = fence_id + 1 - next_reads = groups.get((effect, fence_id + 1), []) + next_write = terms.get((effect, next_id)) + if next_write is not None: + self._graph[next_write].append(write_inst) + + next_reads = read_groups.get((effect, next_id), []) for inst in next_reads: self._graph[inst].append(write_inst) + #print(read_groups) + #print(terms) + #print(self._graph) + def required_by(self, inst): return self._graph.get(inst, []) @@ -127,6 +133,8 @@ class DFTPass(IRPass): fence: Fence def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): + if inst in self.done: + return for op in inst.get_outputs(): assert isinstance(op, IRVariable), f"expected variable, got {op}" uses = self.dfg.get_uses(op) @@ -137,9 +145,9 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): self._process_instruction_r(bb, uses_this) - if inst in self.visited_instructions: + if inst in self.started: return - self.visited_instructions.add(inst) + self.started.add(inst) for target in self._effects_g.required_by(inst): self._process_instruction_r(bb, target) @@ -152,6 +160,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): self._process_instruction_r(bb, target) bb.instructions.append(inst) + self.done.add(inst) def _process_basic_block(self, bb: IRBasicBlock) -> None: self._effects_g = EffectsG() @@ -186,7 +195,8 @@ def run_pass(self) -> None: self.analyses_cache.request_analysis(LivenessAnalysis) # use out_vars self.fence = Fence() - self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet() + self.started: OrderedSet[IRInstruction] = OrderedSet() + self.done: OrderedSet[IRInstruction] = OrderedSet() for bb in self.function.get_basic_blocks(): self._process_basic_block(bb) From 8b415ff5686ef882f443518605d9c3bc95e050ed Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 20:30:37 -0400 Subject: [PATCH 29/64] downstream_of data structure --- vyper/venom/passes/dft.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index d833a32673..c3efb6f9a0 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -57,6 +57,9 @@ class EffectsG: def __init__(self): self._graph = defaultdict(list) + # not sure if this will be useful + self._outputs = defaultdict(list) + def analyze(self, bb): fence = Fence() @@ -93,6 +96,11 @@ def analyze(self, bb): for inst in next_reads: self._graph[inst].append(write_inst) + # invert the graph, go the other way + for inst, dependencies in self._graph.items(): + for target in dependencies: + self._outputs[target].append(inst) + #print(read_groups) #print(terms) #print(self._graph) @@ -100,6 +108,9 @@ def analyze(self, bb): def required_by(self, inst): return self._graph.get(inst, []) + def downstream_of(self, inst): + return self._outputs.get(inst, []) + def _get_reads(opcode): ret = reads.get(opcode, ()) From eb55ab2833838e384b004b9a72d6723364d01dd2 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 20:30:54 -0400 Subject: [PATCH 30/64] remove old fence member --- vyper/venom/passes/dft.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index c3efb6f9a0..8d9611db07 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -141,7 +141,6 @@ def _compute_fence(opcode: str, fence: Fence) -> Fence: class DFTPass(IRPass): function: IRFunction - fence: Fence def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): if inst in self.done: @@ -205,7 +204,6 @@ def run_pass(self) -> None: self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) self.analyses_cache.request_analysis(LivenessAnalysis) # use out_vars - self.fence = Fence() self.started: OrderedSet[IRInstruction] = OrderedSet() self.done: OrderedSet[IRInstruction] = OrderedSet() From 733b1fbb01e15cb4fe7a901c1ae21bf36659940d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 31 May 2024 20:31:26 -0400 Subject: [PATCH 31/64] lint --- vyper/venom/passes/dft.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 8d9611db07..c237e77e14 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -101,10 +101,6 @@ def analyze(self, bb): for target in dependencies: self._outputs[target].append(inst) - #print(read_groups) - #print(terms) - #print(self._graph) - def required_by(self, inst): return self._graph.get(inst, []) From 7e184e2984807960e15bdcb2ffea2c4474a309a4 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 10:05:29 -0400 Subject: [PATCH 32/64] fix bad dependency --- vyper/venom/passes/dft.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index c237e77e14..4e5c4d14aa 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -84,7 +84,10 @@ def analyze(self, bb): for (effect, fence_id), write_inst in terms.items(): reads = read_groups.get((effect, fence_id), []) - self._graph[write_inst].extend(reads) + for read in reads: + if read == write_inst: + continue + self._graph[write_inst].append(read) next_id = fence_id + 1 @@ -99,6 +102,7 @@ def analyze(self, bb): # invert the graph, go the other way for inst, dependencies in self._graph.items(): for target in dependencies: + assert target != inst, inst self._outputs[target].append(inst) def required_by(self, inst): From 83ba491294a8270d3f184fc4c7046ba981168df1 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 10:06:49 -0400 Subject: [PATCH 33/64] traverse down effects graph --- vyper/venom/passes/dft.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 4e5c4d14aa..74a92b8343 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -101,8 +101,9 @@ def analyze(self, bb): # invert the graph, go the other way for inst, dependencies in self._graph.items(): + # sanity check the graph + assert inst not in dependencies, inst for target in dependencies: - assert target != inst, inst self._outputs[target].append(inst) def required_by(self, inst): @@ -145,15 +146,19 @@ class DFTPass(IRPass): def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): if inst in self.done: return + for op in inst.get_outputs(): assert isinstance(op, IRVariable), f"expected variable, got {op}" uses = self.dfg.get_uses(op) - for uses_this in uses: - if uses_this.parent != inst.parent: + for use in uses: + if use.parent != inst.parent: continue - self._process_instruction_r(bb, uses_this) + self._process_instruction_r(bb, use) + + for target in self._effects_g.downstream_of(inst): + self._process_instruction_r(bb, target) if inst in self.started: return From 9ec2b4ef1adc4dc1676a6669d9a95f11edf7c841 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 10:26:18 -0400 Subject: [PATCH 34/64] fix phi+param instructions --- vyper/venom/passes/dft.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 74a92b8343..a69c7cf559 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -164,6 +164,9 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): return self.started.add(inst) + if inst.opcode in ("phi", "param"): + return + for target in self._effects_g.required_by(inst): self._process_instruction_r(bb, target) @@ -182,7 +185,7 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: self._effects_g.analyze(bb) instructions = bb.instructions.copy() - bb.instructions.clear() + bb.instructions = [inst for inst in bb.instructions if inst.opcode in ("phi","param")] # start with out liveness for var in bb.out_vars: @@ -197,8 +200,6 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: assert len(bb.instructions) == len(instructions), (instructions, bb) def key(inst): - if inst.opcode == "phi": - return 0 if inst.is_bb_terminator: return 2 return 1 From 939d6716106fccded34c96cf03bb6b4dbb68b25b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 10:26:59 -0400 Subject: [PATCH 35/64] don't traverse downstream --- vyper/venom/passes/dft.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index a69c7cf559..f3044e548f 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -158,7 +158,8 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): self._process_instruction_r(bb, use) for target in self._effects_g.downstream_of(inst): - self._process_instruction_r(bb, target) + pass + #self._process_instruction_r(bb, target) if inst in self.started: return From 6f370caf02bd2527a6166a26ae39d337dca63493 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 10:33:10 -0400 Subject: [PATCH 36/64] cleanup --- vyper/venom/passes/dft.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index f3044e548f..0e3e0fa8d5 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -144,6 +144,8 @@ class DFTPass(IRPass): function: IRFunction def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): + if inst.parent != bb: + return if inst in self.done: return @@ -152,9 +154,6 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): uses = self.dfg.get_uses(op) for use in uses: - if use.parent != inst.parent: - continue - self._process_instruction_r(bb, use) for target in self._effects_g.downstream_of(inst): @@ -174,8 +173,6 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): for op in inst.get_input_variables(): target = self.dfg.get_producing_instruction(op) assert target is not None, f"no producing instruction for {op}" - if target.parent != inst.parent: - continue self._process_instruction_r(bb, target) bb.instructions.append(inst) @@ -191,8 +188,6 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: # start with out liveness for var in bb.out_vars: inst = self.dfg.get_producing_instruction(var) - if inst.parent != bb: - continue self._process_instruction_r(bb, inst) for inst in instructions: From d50130eb863bf41607d6791eabb8803cf97aee2e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 10:39:08 -0400 Subject: [PATCH 37/64] reverse out_vars produces slightly better code --- vyper/venom/passes/dft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 0e3e0fa8d5..5939d56822 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -186,7 +186,7 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: bb.instructions = [inst for inst in bb.instructions if inst.opcode in ("phi","param")] # start with out liveness - for var in bb.out_vars: + for var in reversed(list(bb.out_vars)): inst = self.dfg.get_producing_instruction(var) self._process_instruction_r(bb, inst) From 881daf6be1353022e0af4ad4e65f22b1a109cd47 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 11:39:16 -0400 Subject: [PATCH 38/64] fiddle with stack layout, traversal order --- vyper/venom/passes/dft.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 5939d56822..7562348377 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -156,10 +156,6 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): for use in uses: self._process_instruction_r(bb, use) - for target in self._effects_g.downstream_of(inst): - pass - #self._process_instruction_r(bb, target) - if inst in self.started: return self.started.add(inst) @@ -167,14 +163,14 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): if inst.opcode in ("phi", "param"): return - for target in self._effects_g.required_by(inst): - self._process_instruction_r(bb, target) - for op in inst.get_input_variables(): target = self.dfg.get_producing_instruction(op) assert target is not None, f"no producing instruction for {op}" self._process_instruction_r(bb, target) + for target in self._effects_g.required_by(inst): + self._process_instruction_r(bb, target) + bb.instructions.append(inst) self.done.add(inst) @@ -183,12 +179,15 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: self._effects_g.analyze(bb) instructions = bb.instructions.copy() - bb.instructions = [inst for inst in bb.instructions if inst.opcode in ("phi","param")] + bb.instructions = [inst for inst in bb.instructions if inst.opcode in ("phi", "param")] # start with out liveness - for var in reversed(list(bb.out_vars)): - inst = self.dfg.get_producing_instruction(var) - self._process_instruction_r(bb, inst) + if len(bb.cfg_out) > 0: + next_bb = next(iter(bb.cfg_out)) + target_stack = self.liveness.input_vars_from(bb, next_bb) + for var in reversed(list(target_stack)): + inst = self.dfg.get_producing_instruction(var) + self._process_instruction_r(bb, inst) for inst in instructions: self._process_instruction_r(bb, inst) @@ -204,7 +203,7 @@ def key(inst): def run_pass(self) -> None: self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) - self.analyses_cache.request_analysis(LivenessAnalysis) # use out_vars + self.liveness = self.analyses_cache.request_analysis(LivenessAnalysis) # use out_vars self.started: OrderedSet[IRInstruction] = OrderedSet() self.done: OrderedSet[IRInstruction] = OrderedSet() From aa2234c8e7981fbd05edeb508a091eeca68da49d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 19:31:44 -0400 Subject: [PATCH 39/64] fix bugs --- vyper/venom/passes/extract_literals.py | 1 - vyper/venom/passes/store_expansion.py | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/vyper/venom/passes/extract_literals.py b/vyper/venom/passes/extract_literals.py index f504481616..b8e042b357 100644 --- a/vyper/venom/passes/extract_literals.py +++ b/vyper/venom/passes/extract_literals.py @@ -1,4 +1,3 @@ -from vyper.utils import OrderedSet from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.liveness import LivenessAnalysis from vyper.venom.basicblock import IRInstruction, IRLiteral diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index 5c86a5518d..b857a66b9b 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -1,8 +1,7 @@ from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis -from vyper.venom.analysis.dominators import DominatorTreeAnalysis from vyper.venom.analysis.liveness import LivenessAnalysis -from vyper.venom.basicblock import IRInstruction, IRVariable +from vyper.venom.basicblock import IRInstruction from vyper.venom.passes.base_pass import IRPass @@ -13,7 +12,6 @@ class StoreExpansionPass(IRPass): """ def run_pass(self): - self.analyses_cache.request_analysis(CFGAnalysis) dfg = self.analyses_cache.request_analysis(DFGAnalysis) for bb in self.function.get_basic_blocks(): @@ -38,13 +36,14 @@ def _process_inst(self, dfg, inst, idx): for use_inst in uses[1:]: if use_inst.parent != inst.parent: - pass - #continue + continue + prev = var for i, operand in enumerate(use_inst.operands): if operand == var: new_var = self.function.get_next_variable() - new_inst = IRInstruction("store", [var], new_var) + new_inst = IRInstruction("store", [prev], new_var) inst.parent.insert_instruction(new_inst, insertion_idx) insertion_idx += 1 use_inst.operands[i] = new_var + prev = new_var From b6b7aeda2b5d00f2a09c2337df8dfa4d6b4018e3 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 19:33:14 -0400 Subject: [PATCH 40/64] allow inter-bb --- vyper/venom/passes/store_expansion.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index b857a66b9b..99d011edb3 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -36,7 +36,8 @@ def _process_inst(self, dfg, inst, idx): for use_inst in uses[1:]: if use_inst.parent != inst.parent: - continue + #continue # improves codesize + pass prev = var for i, operand in enumerate(use_inst.operands): From a71cad8977b1b94960d0123a43708b6c069f4b00 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 1 Jun 2024 19:38:54 -0400 Subject: [PATCH 41/64] lint --- vyper/venom/passes/store_expansion.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index 99d011edb3..37145464ec 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -1,4 +1,3 @@ -from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.liveness import LivenessAnalysis from vyper.venom.basicblock import IRInstruction @@ -36,7 +35,7 @@ def _process_inst(self, dfg, inst, idx): for use_inst in uses[1:]: if use_inst.parent != inst.parent: - #continue # improves codesize + # continue # improves codesize pass prev = var From ef8a56c8401e770780b76c9743b678789d73da2a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 14:19:27 -0400 Subject: [PATCH 42/64] reverse use traversal --- vyper/venom/passes/dft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 7562348377..bbea312299 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -153,7 +153,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): assert isinstance(op, IRVariable), f"expected variable, got {op}" uses = self.dfg.get_uses(op) - for use in uses: + for use in reversed(uses): self._process_instruction_r(bb, use) if inst in self.started: From 70658920063c166ed6724aee7eca262809b575fe Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 14:24:53 -0400 Subject: [PATCH 43/64] for debugging --- vyper/venom/passes/dft.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index bbea312299..cc30d28a02 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -211,4 +211,5 @@ def run_pass(self) -> None: for bb in self.function.get_basic_blocks(): self._process_basic_block(bb) - self.analyses_cache.invalidate_analysis(LivenessAnalysis) + # for repr + self.analyses_cache.force_analysis(LivenessAnalysis) From 2dad58b075e340c5c27a6cadc53aaa00d7b7bfb4 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 15:24:04 -0400 Subject: [PATCH 44/64] add balance fence --- vyper/venom/passes/dft.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index cc30d28a02..33ad011711 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -8,7 +8,7 @@ from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass -_ALL = ("storage", "transient", "memory", "immutables") +_ALL = ("storage", "transient", "memory", "immutables", "balance") writes = { "sstore": "storage", @@ -37,6 +37,8 @@ "call": _ALL, "delegatecall": _ALL, "staticcall": _ALL, + "balance": "balance", + "selfbalance": "balance", "log": "memory", "revert": "memory", "return": "memory", From 20bd8b31cfaa2c80632962469d25838112634fdb Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 15:24:36 -0400 Subject: [PATCH 45/64] add balance fence --- vyper/venom/passes/dft.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 33ad011711..f6bdee0bae 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -52,6 +52,7 @@ class Fence: memory: int = 0 transient: int = 0 immutables: int = 0 + balance: int = 0 # effects graph From 7fa84b425ab8242bff63da5064eefadc66397eaf Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 17:04:22 -0400 Subject: [PATCH 46/64] lift out prev=var --- vyper/venom/passes/store_expansion.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index 018b277e94..84093e576b 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -34,19 +34,16 @@ def _process_inst(self, dfg, inst, idx): insertion_idx = idx + 1 - for use_inst in uses[1:]: + prev = var + + for use_inst in reversed(uses): if use_inst.parent != inst.parent: continue # improves codesize - #pass - #print("ENTER", var) - prev = var for i, operand in enumerate(use_inst.operands): if operand == var: new_var = self.function.get_next_variable() - #print("ENTER2", prev, new_var) new_inst = IRInstruction("store", [prev], new_var) - #print("ENTER3", new_inst) inst.parent.insert_instruction(new_inst, insertion_idx) insertion_idx += 1 use_inst.operands[i] = new_var From 18192ce1565171807b3e14f0b642e55574b7d334 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 21:12:05 -0400 Subject: [PATCH 47/64] fiddle with store expansion --- vyper/venom/passes/store_expansion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index 84093e576b..9413ee4510 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -36,14 +36,14 @@ def _process_inst(self, dfg, inst, idx): prev = var - for use_inst in reversed(uses): + for use_inst in uses[:-1]: if use_inst.parent != inst.parent: continue # improves codesize for i, operand in enumerate(use_inst.operands): if operand == var: new_var = self.function.get_next_variable() - new_inst = IRInstruction("store", [prev], new_var) + new_inst = IRInstruction("store", [var], new_var) inst.parent.insert_instruction(new_inst, insertion_idx) insertion_idx += 1 use_inst.operands[i] = new_var From 372d007e262f24bfeea614f6b1e5da4ace829f15 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 22:02:46 -0400 Subject: [PATCH 48/64] tune order of passes --- 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 9b1f67b195..801710d8ab 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -55,8 +55,8 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None: SimplifyCFGPass(ac, fn).run_pass() AlgebraicOptimizationPass(ac, fn).run_pass() BranchOptimizationPass(ac, fn).run_pass() - ExtractLiteralsPass(ac, fn).run_pass() StoreExpansionPass(ac, fn).run_pass() + ExtractLiteralsPass(ac, fn).run_pass() RemoveUnusedVariablesPass(ac, fn).run_pass() DFTPass(ac, fn).run_pass() From d83c886b71eeae1191bd4e60730f2ff7a274c2f2 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 22:11:48 -0400 Subject: [PATCH 49/64] add a degree of freedom --- vyper/venom/passes/store_expansion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index 9413ee4510..d74c8a394d 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -36,7 +36,7 @@ def _process_inst(self, dfg, inst, idx): prev = var - for use_inst in uses[:-1]: + for use_inst in uses: if use_inst.parent != inst.parent: continue # improves codesize From 33f15e58d8a00ea3aacca630491e5209d3fc1ae1 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 22:18:38 -0400 Subject: [PATCH 50/64] add a peephole optimization --- vyper/ir/compile_ir.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 4c68aa2c8f..751a0261e9 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -1033,6 +1033,9 @@ def _stack_peephole_opts(assembly): if assembly[i] == "SWAP1" and assembly[i + 1].lower() in COMMUTATIVE_OPS: changed = True del assembly[i] + if assembly[i] == "DUP1" and assembly[i + 1] == "SWAP1": + changed = True + del assembly[i+1] i += 1 return changed From 6174be0c696afceb2b0055829c6bed16a5ffa4dd Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 22:39:59 -0400 Subject: [PATCH 51/64] run dftpass twice! --- vyper/venom/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 801710d8ab..3cd7a848e5 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -55,9 +55,12 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None: SimplifyCFGPass(ac, fn).run_pass() AlgebraicOptimizationPass(ac, fn).run_pass() BranchOptimizationPass(ac, fn).run_pass() + RemoveUnusedVariablesPass(ac, fn).run_pass() + + # reorder and prepare for stack scheduling + DFTPass(ac, fn).run_pass() StoreExpansionPass(ac, fn).run_pass() ExtractLiteralsPass(ac, fn).run_pass() - RemoveUnusedVariablesPass(ac, fn).run_pass() DFTPass(ac, fn).run_pass() From c7d57f40d8636b8b48d5fd395880e4b8888aeb1c Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 4 Jun 2024 22:50:22 -0400 Subject: [PATCH 52/64] remove dead variables --- vyper/venom/passes/store_expansion.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index d74c8a394d..bfb2fc82a0 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -34,8 +34,6 @@ def _process_inst(self, dfg, inst, idx): insertion_idx = idx + 1 - prev = var - for use_inst in uses: if use_inst.parent != inst.parent: continue # improves codesize @@ -45,6 +43,4 @@ def _process_inst(self, dfg, inst, idx): new_var = self.function.get_next_variable() new_inst = IRInstruction("store", [var], new_var) inst.parent.insert_instruction(new_inst, insertion_idx) - insertion_idx += 1 use_inst.operands[i] = new_var - prev = new_var From eaa1c67c9304c4bb54ebfe7dc4e0c66d74f8b84a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 12 Jun 2024 08:38:30 -0400 Subject: [PATCH 53/64] add returndata fencing --- vyper/venom/passes/dft.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index f6bdee0bae..70cde9630d 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -8,7 +8,7 @@ from vyper.venom.function import IRFunction from vyper.venom.passes.base_pass import IRPass -_ALL = ("storage", "transient", "memory", "immutables", "balance") +_ALL = ("storage", "transient", "memory", "immutables", "balance", "returndata") writes = { "sstore": "storage", @@ -37,6 +37,8 @@ "call": _ALL, "delegatecall": _ALL, "staticcall": _ALL, + "returndatasize": "returndata", + "returndatacopy": "returndata", "balance": "balance", "selfbalance": "balance", "log": "memory", From e385ecb5c2678278d578e44d843c4739f57e8fe3 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 12 Jun 2024 08:38:39 -0400 Subject: [PATCH 54/64] fix lint --- vyper/ir/compile_ir.py | 2 +- vyper/venom/passes/store_expansion.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 751a0261e9..ddd1940365 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -1035,7 +1035,7 @@ def _stack_peephole_opts(assembly): del assembly[i] if assembly[i] == "DUP1" and assembly[i + 1] == "SWAP1": changed = True - del assembly[i+1] + del assembly[i + 1] i += 1 return changed diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index bfb2fc82a0..d7ae25fd0f 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -18,7 +18,7 @@ def run_pass(self): if inst.output is None: continue - #print("ENTER", inst) + # print("ENTER", inst) self._process_inst(dfg, inst, idx) self.analyses_cache.invalidate_analysis(LivenessAnalysis) From 993e875fc6b9d879cefc0ee7f4db9fd7f0f4e08b Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 12 Jun 2024 13:56:48 -0400 Subject: [PATCH 55/64] fix returndata --- vyper/venom/passes/dft.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 70cde9630d..39400e6a0c 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -55,6 +55,7 @@ class Fence: transient: int = 0 immutables: int = 0 balance: int = 0 + returndata: int = 0 # effects graph From b84595a3a2fdc193a4e01824302f1b2b77c76941 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 13 Jun 2024 10:14:16 -0400 Subject: [PATCH 56/64] improved sanity check --- vyper/venom/passes/dft.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 39400e6a0c..b4912030d4 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -198,8 +198,6 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: for inst in instructions: self._process_instruction_r(bb, inst) - assert len(bb.instructions) == len(instructions), (instructions, bb) - def key(inst): if inst.is_bb_terminator: return 2 @@ -207,6 +205,10 @@ def key(inst): bb.instructions.sort(key=key) + # sanity check: the instructions we started with are the same + # as we have now + assert set(bb.instructions) == set(instructions), (instructions, bb) + def run_pass(self) -> None: self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) self.liveness = self.analyses_cache.request_analysis(LivenessAnalysis) # use out_vars From 2a7fde7c8dc9343cff977be73bffd90e7e901447 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 18 Sep 2024 16:39:51 -0400 Subject: [PATCH 57/64] use .first() --- vyper/venom/passes/dft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index b4912030d4..9a916111e3 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -189,7 +189,7 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: # start with out liveness if len(bb.cfg_out) > 0: - next_bb = next(iter(bb.cfg_out)) + next_bb = bb.cfg_out.first() target_stack = self.liveness.input_vars_from(bb, next_bb) for var in reversed(list(target_stack)): inst = self.dfg.get_producing_instruction(var) From 0d19fd542a78c379660ea9a0d7a4aca95b54baaf Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 18 Sep 2024 16:45:29 -0400 Subject: [PATCH 58/64] another usage of first() --- 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 eb8c4e69ec..0fc0c0427b 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -396,7 +396,7 @@ def _generate_evm_for_instruction( if opcode in ["jnz", "djmp", "jmp"]: # prepare stack for jump into another basic block assert inst.parent and isinstance(inst.parent.cfg_out, OrderedSet) - b = next(iter(inst.parent.cfg_out)) + b = inst.parent.cfg_out.first() target_stack = self.liveness_analysis.input_vars_from(inst.parent, b) # TODO optimize stack reordering at entry and exit from basic blocks # NOTE: stack in general can contain multiple copies of the same variable, @@ -446,7 +446,7 @@ def _generate_evm_for_instruction( assembly.append("JUMPI") # make sure the if_zero_label will be optimized out - # assert if_zero_label == next(iter(inst.parent.cfg_out)).label + # assert if_zero_label == inst.parent.cfg_out.first().label assembly.append(f"_sym_{if_zero_label.value}") assembly.append("JUMP") From 0582db5a581c5915786d05e9beb17121fe97dc1e Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 18 Sep 2024 20:27:37 -0400 Subject: [PATCH 59/64] wip - store-expand bb in vars --- vyper/venom/passes/store_expansion.py | 31 ++++++++++++++++++--------- vyper/venom/venom_to_assembly.py | 14 +++++++++++- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index d7ae25fd0f..a8285fa09d 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -1,3 +1,4 @@ +from vyper.venom.analysis.cfg import CFGAnalysis from vyper.venom.analysis.dfg import DFGAnalysis from vyper.venom.analysis.liveness import LivenessAnalysis from vyper.venom.basicblock import IRInstruction @@ -12,35 +13,45 @@ class StoreExpansionPass(IRPass): def run_pass(self): dfg = self.analyses_cache.request_analysis(DFGAnalysis) + self.analyses_cache.request_analysis(CFGAnalysis) + liveness = self.analyses_cache.force_analysis(LivenessAnalysis) for bb in self.function.get_basic_blocks(): + if len(bb.instructions) == 0: + continue + + for var in bb.instructions[0].liveness: + self._process_var(dfg, bb, var, 0) + for idx, inst in enumerate(bb.instructions): if inst.output is None: continue - # print("ENTER", inst) - self._process_inst(dfg, inst, idx) + self._process_var(dfg, bb, inst.output, idx + 1) + + bb.instructions.sort(key=lambda inst: inst.opcode not in ("phi", "param")) self.analyses_cache.invalidate_analysis(LivenessAnalysis) self.analyses_cache.invalidate_analysis(DFGAnalysis) - def _process_inst(self, dfg, inst, idx): + def _process_var(self, dfg, bb, var, idx): """ - Process store instruction. If the variable is only used by a load instruction, - forward the variable to the load instruction. + Process a variable, allocating a new variable for each use + and copying it to the new instruction """ - var = inst.output uses = dfg.get_uses(var) - insertion_idx = idx + 1 + _cache = {} for use_inst in uses: - if use_inst.parent != inst.parent: - continue # improves codesize + if use_inst.opcode == "phi": + continue + if use_inst.parent != bb: + continue for i, operand in enumerate(use_inst.operands): if operand == var: new_var = self.function.get_next_variable() new_inst = IRInstruction("store", [var], new_var) - inst.parent.insert_instruction(new_inst, insertion_idx) + bb.insert_instruction(new_inst, idx) use_inst.operands[i] = new_var diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 0fc0c0427b..ae73b112c5 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -279,6 +279,12 @@ def _generate_evm_for_basicblock_r( return self.visited_basicblocks.add(basicblock) + import sys + #print(basicblock, file=sys.stderr) + + ref = asm + asm = [] + # assembly entry point into the block asm.append(f"_sym_{basicblock.label}") asm.append("JUMPDEST") @@ -292,8 +298,13 @@ def _generate_evm_for_basicblock_r( asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) + #print(" ".join(map(str, asm)), file=sys.stderr) + #print("\n", file=sys.stderr) + + ref.extend(asm) + for bb in basicblock.reachable: - self._generate_evm_for_basicblock_r(asm, bb, stack.copy()) + self._generate_evm_for_basicblock_r(ref, bb, stack.copy()) # pop values from stack at entry to bb # note this produces the same result(!) no matter which basic block @@ -553,6 +564,7 @@ def dup(self, assembly, stack, depth): assembly.append(_evm_dup_for(depth)) def swap_op(self, assembly, stack, op): + assert stack.get_depth(op) is not StackModel.NOT_IN_STACK, op self.swap(assembly, stack, stack.get_depth(op)) def dup_op(self, assembly, stack, op): From 04314e05d828842fb687b9c31b1a7dac2fcab573 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 19 Sep 2024 08:21:19 -0400 Subject: [PATCH 60/64] move normalization pass --- vyper/venom/__init__.py | 3 +++ vyper/venom/venom_to_assembly.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index 3cd7a848e5..3c4ffb26a7 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -10,6 +10,7 @@ from vyper.venom.function import IRFunction from vyper.venom.ir_node_to_venom import ir_node_to_venom from vyper.venom.passes.algebraic_optimization import AlgebraicOptimizationPass +from vyper.venom.passes.normalization import NormalizationPass from vyper.venom.passes.branch_optimization import BranchOptimizationPass from vyper.venom.passes.dft import DFTPass from vyper.venom.passes.extract_literals import ExtractLiteralsPass @@ -54,6 +55,8 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None: StoreElimination(ac, fn).run_pass() SimplifyCFGPass(ac, fn).run_pass() AlgebraicOptimizationPass(ac, fn).run_pass() + + NormalizationPass(ac, fn).run_pass() BranchOptimizationPass(ac, fn).run_pass() RemoveUnusedVariablesPass(ac, fn).run_pass() diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 9c49f74aeb..506192ef6c 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -151,7 +151,6 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]: for fn in ctx.functions.values(): ac = IRAnalysesCache(fn) - NormalizationPass(ac, fn).run_pass() self.liveness_analysis = ac.request_analysis(LivenessAnalysis) ac.request_analysis(DupRequirementsAnalysis) From 32a5da5db6ae1271c9cf247b1bedd9f3a6b7a656 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 19 Sep 2024 12:39:41 -0400 Subject: [PATCH 61/64] debug show cost --- vyper/venom/venom_to_assembly.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 506192ef6c..cc3701b933 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -26,6 +26,10 @@ from vyper.venom.passes.normalization import NormalizationPass from vyper.venom.stack_model import StackModel +DEBUG_SHOW_COST = True +if DEBUG_SHOW_COST: + import sys + # instructions which map one-to-one from venom to EVM _ONE_TO_ONE_INSTRUCTIONS = frozenset( [ @@ -278,8 +282,8 @@ def _generate_evm_for_basicblock_r( return self.visited_basicblocks.add(basicblock) - #import sys - # print(basicblock, file=sys.stderr) + if DEBUG_SHOW_COST: + print(basicblock, file=sys.stderr) ref = asm asm = [] @@ -297,8 +301,9 @@ def _generate_evm_for_basicblock_r( asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness)) - # print(" ".join(map(str, asm)), file=sys.stderr) - # print("\n", file=sys.stderr) + if DEBUG_SHOW_COST: + print(" ".join(map(str, asm)), file=sys.stderr) + print("\n", file=sys.stderr) ref.extend(asm) @@ -427,6 +432,13 @@ def _generate_evm_for_instruction( if cost_with_swap > cost_no_swap: operands[-1], operands[-2] = operands[-2], operands[-1] + cost = self._stack_reorder([], stack, operands, dry_run=True) + if DEBUG_SHOW_COST and cost: + print("ENTER", inst, file=sys.stderr) + print(" HAVE", stack, file=sys.stderr) + print(" WANT", operands, file=sys.stderr) + print(" COST", cost, file=sys.stderr) + # final step to get the inputs to this instruction ordered # correctly on the stack self._stack_reorder(assembly, stack, operands) From 35fe8ce7cf4dc7cdbe44b0fd7125cea1884f6b7a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 20 Sep 2024 10:32:06 -0400 Subject: [PATCH 62/64] wip dft traverse bbs in topsort order (doesn't seem to make a difference) disable store expansion --- vyper/venom/passes/dft.py | 18 +++++++++++++----- vyper/venom/passes/store_expansion.py | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index 9a916111e3..ac6c9bb032 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -180,7 +180,14 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): bb.instructions.append(inst) self.done.add(inst) - def _process_basic_block(self, bb: IRBasicBlock) -> None: + def _process_basic_block_r(self, bb: IRBasicBlock) -> None: + if bb in self.visited_bbs: + return + for next_bb in bb.cfg_out: + self._process_basic_block_r(next_bb) + self.liveness._calculate_out_vars(next_bb) + self.liveness._calculate_liveness(next_bb) + self._effects_g = EffectsG() self._effects_g.analyze(bb) @@ -188,8 +195,8 @@ def _process_basic_block(self, bb: IRBasicBlock) -> None: bb.instructions = [inst for inst in bb.instructions if inst.opcode in ("phi", "param")] # start with out liveness - if len(bb.cfg_out) > 0: - next_bb = bb.cfg_out.first() + for next_bb in bb.cfg_out: + break target_stack = self.liveness.input_vars_from(bb, next_bb) for var in reversed(list(target_stack)): inst = self.dfg.get_producing_instruction(var) @@ -208,6 +215,7 @@ def key(inst): # sanity check: the instructions we started with are the same # as we have now assert set(bb.instructions) == set(instructions), (instructions, bb) + self.visited_bbs.add(bb) def run_pass(self) -> None: self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) @@ -216,8 +224,8 @@ def run_pass(self) -> None: self.started: OrderedSet[IRInstruction] = OrderedSet() self.done: OrderedSet[IRInstruction] = OrderedSet() - for bb in self.function.get_basic_blocks(): - self._process_basic_block(bb) + self.visited_bbs = OrderedSet() + self._process_basic_block_r(self.function.entry) # for repr self.analyses_cache.force_analysis(LivenessAnalysis) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index a8285fa09d..708b080f24 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -12,6 +12,7 @@ class StoreExpansionPass(IRPass): """ def run_pass(self): + return dfg = self.analyses_cache.request_analysis(DFGAnalysis) self.analyses_cache.request_analysis(CFGAnalysis) liveness = self.analyses_cache.force_analysis(LivenessAnalysis) From f5b260902c2cd5c4917ca828ef6abfbe5f8c4c8c Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 20 Sep 2024 10:39:08 -0400 Subject: [PATCH 63/64] revert cfg traversal order change --- vyper/venom/passes/dft.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/vyper/venom/passes/dft.py b/vyper/venom/passes/dft.py index ac6c9bb032..42bc7df07e 100644 --- a/vyper/venom/passes/dft.py +++ b/vyper/venom/passes/dft.py @@ -180,14 +180,7 @@ def _process_instruction_r(self, bb: IRBasicBlock, inst: IRInstruction): bb.instructions.append(inst) self.done.add(inst) - def _process_basic_block_r(self, bb: IRBasicBlock) -> None: - if bb in self.visited_bbs: - return - for next_bb in bb.cfg_out: - self._process_basic_block_r(next_bb) - self.liveness._calculate_out_vars(next_bb) - self.liveness._calculate_liveness(next_bb) - + def _process_basic_block(self, bb: IRBasicBlock) -> None: self._effects_g = EffectsG() self._effects_g.analyze(bb) @@ -195,8 +188,9 @@ def _process_basic_block_r(self, bb: IRBasicBlock) -> None: bb.instructions = [inst for inst in bb.instructions if inst.opcode in ("phi", "param")] # start with out liveness - for next_bb in bb.cfg_out: - break + #if len(bb.cfg_out) > 0: + if False: + next_bb = bb.cfg_out.first() target_stack = self.liveness.input_vars_from(bb, next_bb) for var in reversed(list(target_stack)): inst = self.dfg.get_producing_instruction(var) @@ -215,7 +209,6 @@ def key(inst): # sanity check: the instructions we started with are the same # as we have now assert set(bb.instructions) == set(instructions), (instructions, bb) - self.visited_bbs.add(bb) def run_pass(self) -> None: self.dfg = self.analyses_cache.request_analysis(DFGAnalysis) @@ -224,8 +217,8 @@ def run_pass(self) -> None: self.started: OrderedSet[IRInstruction] = OrderedSet() self.done: OrderedSet[IRInstruction] = OrderedSet() - self.visited_bbs = OrderedSet() - self._process_basic_block_r(self.function.entry) + for bb in self.function.get_basic_blocks(): + self._process_basic_block(bb) # for repr self.analyses_cache.force_analysis(LivenessAnalysis) From 6129813d64f4248f4b16d6d7eb97bbd26d984b0d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sat, 21 Sep 2024 20:44:01 -0400 Subject: [PATCH 64/64] bring back store expansion --- vyper/venom/passes/store_expansion.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vyper/venom/passes/store_expansion.py b/vyper/venom/passes/store_expansion.py index 708b080f24..a8285fa09d 100644 --- a/vyper/venom/passes/store_expansion.py +++ b/vyper/venom/passes/store_expansion.py @@ -12,7 +12,6 @@ class StoreExpansionPass(IRPass): """ def run_pass(self): - return dfg = self.analyses_cache.request_analysis(DFGAnalysis) self.analyses_cache.request_analysis(CFGAnalysis) liveness = self.analyses_cache.force_analysis(LivenessAnalysis)