Skip to content

Commit

Permalink
add equivalence analysis
Browse files Browse the repository at this point in the history
use equivalence analysis to reduce swaps
  • Loading branch information
charles-cooper committed Sep 28, 2024
1 parent 1bf0173 commit 54d7e97
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 6 deletions.
37 changes: 37 additions & 0 deletions vyper/venom/analysis/equivalent_vars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from vyper.utils import OrderedSet

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'OrderedSet' is not used.
from vyper.venom.analysis.analysis import IRAnalysis
from vyper.venom.basicblock import IRVariable
from vyper.venom.analysis.dfg import DFGAnalysis


class VarEquivalenceAnalysis(IRAnalysis):
"""
Generate equivalence sets of variables
"""
def analyze(self):
dfg = self.analyses_cache.request_analysis(DFGAnalysis)

Check warning on line 12 in vyper/venom/analysis/equivalent_vars.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/equivalent_vars.py#L12

Added line #L12 was not covered by tests

equivalence_set: dict[IRVariable, int] = {}

Check warning on line 14 in vyper/venom/analysis/equivalent_vars.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/equivalent_vars.py#L14

Added line #L14 was not covered by tests

for bag, (var, inst) in enumerate(dfg._dfg_outputs.items()):
if inst.opcode != "store":
continue

Check warning on line 18 in vyper/venom/analysis/equivalent_vars.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/equivalent_vars.py#L18

Added line #L18 was not covered by tests

source = inst.operands[0]

Check warning on line 20 in vyper/venom/analysis/equivalent_vars.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/equivalent_vars.py#L20

Added line #L20 was not covered by tests

if source in equivalence_set:
equivalence_set[var] = equivalence_set[source]
continue

Check warning on line 24 in vyper/venom/analysis/equivalent_vars.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/equivalent_vars.py#L23-L24

Added lines #L23 - L24 were not covered by tests
else:
assert var not in equivalence_set
equivalence_set[var] = bag
equivalence_set[source] = bag

Check warning on line 28 in vyper/venom/analysis/equivalent_vars.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/equivalent_vars.py#L26-L28

Added lines #L26 - L28 were not covered by tests

self._equivalence_set = equivalence_set

Check warning on line 30 in vyper/venom/analysis/equivalent_vars.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/equivalent_vars.py#L30

Added line #L30 was not covered by tests

def equivalent(self, var1, var2):
if var1 not in self._equivalence_set:
return False

Check warning on line 34 in vyper/venom/analysis/equivalent_vars.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/analysis/equivalent_vars.py#L34

Added line #L34 was not covered by tests
if var2 not in self._equivalence_set:
return False
return self._equivalence_set[var1] == self._equivalence_set[var2]
51 changes: 45 additions & 6 deletions vyper/venom/venom_to_assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from vyper.utils import MemoryPositions, OrderedSet
from vyper.venom.analysis.analysis import IRAnalysesCache
from vyper.venom.analysis.liveness import LivenessAnalysis
from vyper.venom.analysis.equivalent_vars import VarEquivalenceAnalysis
from vyper.venom.basicblock import (
IRBasicBlock,
IRInstruction,
Expand All @@ -25,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(
[
Expand Down Expand Up @@ -152,6 +157,7 @@ def generate_evm(self, no_optimize: bool = False) -> list[str]:

NormalizationPass(ac, fn).run_pass()
self.liveness_analysis = ac.request_analysis(LivenessAnalysis)
self.equivalence = ac.request_analysis(VarEquivalenceAnalysis)

Check warning on line 160 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L160

Added line #L160 was not covered by tests

assert fn.normalized, "Non-normalized CFG!"

Expand Down Expand Up @@ -220,7 +226,10 @@ def _stack_reorder(
if depth == final_stack_depth:
continue

if op == stack.peek(final_stack_depth):
to_swap = stack.peek(final_stack_depth)

Check warning on line 229 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L229

Added line #L229 was not covered by tests
if self.equivalence.equivalent(op, to_swap):
stack.poke(final_stack_depth, op)
stack.poke(depth, to_swap)

Check warning on line 232 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L231-L232

Added lines #L231 - L232 were not covered by tests
continue

cost += self.swap(assembly, stack, depth)
Expand Down Expand Up @@ -276,6 +285,12 @@ def _generate_evm_for_basicblock_r(
return
self.visited_basicblocks.add(basicblock)

if DEBUG_SHOW_COST:
print(basicblock, file=sys.stderr)

Check warning on line 289 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L289

Added line #L289 was not covered by tests

ref = asm
asm = []

Check warning on line 292 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L291-L292

Added lines #L291 - L292 were not covered by tests

# assembly entry point into the block
asm.append(f"_sym_{basicblock.label}")
asm.append("JUMPDEST")
Expand All @@ -291,8 +306,14 @@ def _generate_evm_for_basicblock_r(

asm.extend(self._generate_evm_for_instruction(inst, stack, next_liveness))

if DEBUG_SHOW_COST:
print(" ".join(map(str, asm)), file=sys.stderr)
print("\n", file=sys.stderr)

Check warning on line 311 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L310-L311

Added lines #L310 - L311 were not covered by tests

ref.extend(asm)

Check warning on line 313 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L313

Added line #L313 was not covered by tests

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())

Check warning on line 316 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L316

Added line #L316 was not covered by tests

# pop values from stack at entry to bb
# note this produces the same result(!) no matter which basic block
Expand Down Expand Up @@ -415,6 +436,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)

Check warning on line 439 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L439

Added line #L439 was not covered by tests
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)

Check warning on line 444 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L441-L444

Added lines #L441 - L444 were not covered by tests

# final step to get the inputs to this instruction ordered
# correctly on the stack
self._stack_reorder(assembly, stack, operands)
Expand Down Expand Up @@ -531,10 +559,21 @@ def _generate_evm_for_instruction(
if inst.output not in next_liveness:
self.pop(assembly, stack)
else:
# peek at next_liveness to find the next scheduled item,
# and optimistically swap with it
# heuristic: peek at next_liveness to find the next scheduled
# item, and optimistically swap with it
if DEBUG_SHOW_COST:
stack0 = stack.copy()

Check warning on line 565 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L565

Added line #L565 was not covered by tests

next_scheduled = next_liveness.last()
self.swap_op(assembly, stack, next_scheduled)
cost = 0

Check warning on line 568 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L568

Added line #L568 was not covered by tests
if not self.equivalence.equivalent(inst.output, next_scheduled):
cost = self.swap_op(assembly, stack, next_scheduled)

Check warning on line 570 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L570

Added line #L570 was not covered by tests

if DEBUG_SHOW_COST and cost != 0:
print("ENTER", inst, file=sys.stderr)
print(" HAVE", stack0, file=sys.stderr)
print(" NEXT LIVENESS", next_liveness, file=sys.stderr)
print(" NEW_STACK", stack, file=sys.stderr)

Check warning on line 576 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L573-L576

Added lines #L573 - L576 were not covered by tests

return apply_line_numbers(inst, assembly)

Expand All @@ -556,7 +595,7 @@ def dup(self, assembly, stack, depth):
assembly.append(_evm_dup_for(depth))

def swap_op(self, assembly, stack, op):
self.swap(assembly, stack, stack.get_depth(op))
return self.swap(assembly, stack, stack.get_depth(op))

Check warning on line 598 in vyper/venom/venom_to_assembly.py

View check run for this annotation

Codecov / codecov/patch

vyper/venom/venom_to_assembly.py#L598

Added line #L598 was not covered by tests

def dup_op(self, assembly, stack, op):
self.dup(assembly, stack, stack.get_depth(op))
Expand Down

0 comments on commit 54d7e97

Please sign in to comment.