Skip to content

Commit

Permalink
refactor[venom]: refactor sccp pass to use dfg (vyperlang#4329)
Browse files Browse the repository at this point in the history
use `DFGAnalysis` in `SCCP` instead of duplicating the logic to compute
uses in the `SCCP` itself. also use `OrderedSet` for var uses, this
ensures we don't add the same instruction multiple times (as in `add
%2 %2`) to a var's use set, and also enables a cheaper `remove_use()`
implementation.
  • Loading branch information
HodanPlodky authored Oct 25, 2024
1 parent b3ea663 commit 658f0c4
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 29 deletions.
18 changes: 10 additions & 8 deletions vyper/venom/analysis/dfg.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from typing import Optional

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 IRInstruction, IRVariable
from vyper.venom.function import IRFunction


class DFGAnalysis(IRAnalysis):
_dfg_inputs: dict[IRVariable, list[IRInstruction]]
_dfg_inputs: dict[IRVariable, OrderedSet[IRInstruction]]
_dfg_outputs: dict[IRVariable, IRInstruction]

def __init__(self, analyses_cache: IRAnalysesCache, function: IRFunction):
Expand All @@ -16,19 +17,19 @@ def __init__(self, analyses_cache: IRAnalysesCache, function: IRFunction):
self._dfg_outputs = dict()

# return uses of a given variable
def get_uses(self, op: IRVariable) -> list[IRInstruction]:
return self._dfg_inputs.get(op, [])
def get_uses(self, op: IRVariable) -> OrderedSet[IRInstruction]:
return self._dfg_inputs.get(op, OrderedSet())

# the instruction which produces this variable.
def get_producing_instruction(self, op: IRVariable) -> Optional[IRInstruction]:
return self._dfg_outputs.get(op)

def add_use(self, op: IRVariable, inst: IRInstruction):
uses = self._dfg_inputs.setdefault(op, [])
uses.append(inst)
uses = self._dfg_inputs.setdefault(op, OrderedSet())
uses.add(inst)

def remove_use(self, op: IRVariable, inst: IRInstruction):
uses = self._dfg_inputs.get(op, [])
uses: OrderedSet = self._dfg_inputs.get(op, OrderedSet())
uses.remove(inst)

@property
Expand All @@ -48,10 +49,11 @@ def analyze(self):
res = inst.get_outputs()

for op in operands:
inputs = self._dfg_inputs.setdefault(op, [])
inputs.append(inst)
inputs = self._dfg_inputs.setdefault(op, OrderedSet())
inputs.add(inst)

for op in res: # type: ignore
assert isinstance(op, IRVariable)
self._dfg_outputs[op] = inst

def as_graph(self) -> str:
Expand Down
28 changes: 7 additions & 21 deletions vyper/venom/passes/sccp/sccp.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from vyper.exceptions import CompilerPanic, StaticAssertionException
from vyper.utils import OrderedSet
from vyper.venom.analysis import CFGAnalysis, DominatorTreeAnalysis, IRAnalysesCache
from vyper.venom.analysis import CFGAnalysis, DFGAnalysis, DominatorTreeAnalysis, IRAnalysesCache
from vyper.venom.basicblock import (
IRBasicBlock,
IRInstruction,
Expand Down Expand Up @@ -51,7 +51,7 @@ class SCCP(IRPass):

fn: IRFunction
dom: DominatorTreeAnalysis
uses: dict[IRVariable, OrderedSet[IRInstruction]]
dfg: DFGAnalysis
lattice: Lattice
work_list: list[WorkListItem]
cfg_in_exec: dict[IRBasicBlock, OrderedSet[IRBasicBlock]]
Expand All @@ -67,14 +67,16 @@ def __init__(self, analyses_cache: IRAnalysesCache, function: IRFunction):
def run_pass(self):
self.fn = self.function
self.dom = self.analyses_cache.request_analysis(DominatorTreeAnalysis)
self._compute_uses()
self.dfg = self.analyses_cache.request_analysis(DFGAnalysis)
self._calculate_sccp(self.fn.entry)
self._propagate_constants()

if self.cfg_dirty:
self.analyses_cache.force_analysis(CFGAnalysis)
self._fix_phi_nodes()

self.analyses_cache.invalidate_analysis(DFGAnalysis)

def _calculate_sccp(self, entry: IRBasicBlock):
"""
This method is the main entry point for the SCCP algorithm. It
Expand All @@ -92,7 +94,7 @@ def _calculate_sccp(self, entry: IRBasicBlock):
self.work_list.append(FlowWorkItem(dummy, entry))

# Initialize the lattice with TOP values for all variables
for v in self.uses.keys():
for v in self.dfg._dfg_outputs:
self.lattice[v] = LatticeEnum.TOP

# Iterate over the work list until it is empty
Expand Down Expand Up @@ -258,25 +260,9 @@ def _eval(self, inst) -> LatticeItem:
return ret # type: ignore

def _add_ssa_work_items(self, inst: IRInstruction):
for target_inst in self._get_uses(inst.output): # type: ignore
for target_inst in self.dfg.get_uses(inst.output): # type: ignore
self.work_list.append(SSAWorkListItem(target_inst))

def _compute_uses(self):
"""
This method computes the uses for each variable in the IR.
It iterates over the dominator tree and collects all the
instructions that use each variable.
"""
self.uses = {}
for bb in self.dom.dfs_walk:
for var, insts in bb.get_uses().items():
self._get_uses(var).update(insts)

def _get_uses(self, var: IRVariable):
if var not in self.uses:
self.uses[var] = OrderedSet()
return self.uses[var]

def _propagate_constants(self):
"""
This method iterates over the IR and replaces constant values
Expand Down

0 comments on commit 658f0c4

Please sign in to comment.