Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat[venom]: multidimensional fencing #4066

Draft
wants to merge 60 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d552898
feat: multidimensional fencing
charles-cooper May 30, 2024
d4dad09
invalidate liveness
charles-cooper May 30, 2024
398a344
Merge branch 'master' into feat/fencing
charles-cooper May 30, 2024
a50473a
add log, revert to effects
charles-cooper May 30, 2024
c7da081
sha3 fence
charles-cooper May 30, 2024
80c5154
fix fence checker
charles-cooper May 30, 2024
6cf7b6b
feat[venom]: extract literals pass
charles-cooper May 30, 2024
04b53d0
don't reorder param instructions
charles-cooper May 30, 2024
8adf783
remove a comment
charles-cooper May 30, 2024
8506bbb
small perf
charles-cooper May 30, 2024
ef7c369
feat: store expansion pass
charles-cooper May 31, 2024
ff700b4
lint
charles-cooper May 31, 2024
ea9b1c5
remove inter-bb restriction
charles-cooper May 31, 2024
adbf01c
don't replace first use
charles-cooper May 31, 2024
f3acde1
fix terminator instruction
charles-cooper May 30, 2024
988a1a9
wip - fix fence
charles-cooper May 31, 2024
058c4db
fix can_reorder
charles-cooper May 31, 2024
edaf756
Merge branch 'master' into feat/fencing
charles-cooper May 31, 2024
3555fcb
force phi instructions first
charles-cooper May 31, 2024
3e096de
fix can_reorder(?)
charles-cooper May 31, 2024
7dc473d
clean up phi
charles-cooper May 31, 2024
e087c4f
update fence calculation
charles-cooper May 31, 2024
50e13cf
Revert "update fence calculation"
charles-cooper May 31, 2024
490c377
sort again
charles-cooper May 31, 2024
608bfaa
traverse out_vars
charles-cooper May 31, 2024
afb49a6
update can_reorder
charles-cooper May 31, 2024
3d0c4bb
fix a table
charles-cooper May 31, 2024
a44c0bd
wip - effects graph
charles-cooper May 31, 2024
f1bb354
update table
charles-cooper Jun 1, 2024
6e87f1f
minor cleanup
charles-cooper Jun 1, 2024
8b415ff
downstream_of data structure
charles-cooper Jun 1, 2024
eb55ab2
remove old fence member
charles-cooper Jun 1, 2024
733b1fb
lint
charles-cooper Jun 1, 2024
7e184e2
fix bad dependency
charles-cooper Jun 1, 2024
83ba491
traverse down effects graph
charles-cooper Jun 1, 2024
9ec2b4e
fix phi+param instructions
charles-cooper Jun 1, 2024
939d671
don't traverse downstream
charles-cooper Jun 1, 2024
6f370ca
cleanup
charles-cooper Jun 1, 2024
d50130e
reverse out_vars
charles-cooper Jun 1, 2024
881daf6
fiddle with stack layout, traversal order
charles-cooper Jun 1, 2024
aa2234c
fix bugs
charles-cooper Jun 1, 2024
b6b7aed
allow inter-bb
charles-cooper Jun 1, 2024
a71cad8
lint
charles-cooper Jun 1, 2024
ef8a56c
reverse use traversal
charles-cooper Jun 4, 2024
7065892
for debugging
charles-cooper Jun 4, 2024
2dad58b
add balance fence
charles-cooper Jun 4, 2024
20bd8b3
add balance fence
charles-cooper Jun 4, 2024
f89711e
Merge branch 'feat/store-expansion' into feat/fencing
charles-cooper Jun 4, 2024
7fa84b4
lift out prev=var
charles-cooper Jun 4, 2024
18192ce
fiddle with store expansion
charles-cooper Jun 5, 2024
372d007
tune order of passes
charles-cooper Jun 5, 2024
d83c886
add a degree of freedom
charles-cooper Jun 5, 2024
33f15e5
add a peephole optimization
charles-cooper Jun 5, 2024
6174be0
run dftpass twice!
charles-cooper Jun 5, 2024
c7d57f4
remove dead variables
charles-cooper Jun 5, 2024
eaa1c67
add returndata fencing
charles-cooper Jun 12, 2024
e385ecb
fix lint
charles-cooper Jun 12, 2024
993e875
fix returndata
charles-cooper Jun 12, 2024
1f8001f
Merge branch 'master' into feat/fencing
charles-cooper Jun 13, 2024
b84595a
improved sanity check
charles-cooper Jun 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion vyper/venom/analysis/liveness.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions vyper/venom/basicblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,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]
Expand All @@ -229,7 +228,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
Expand Down
176 changes: 133 additions & 43 deletions vyper/venom/passes/dft.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,171 @@
from dataclasses import asdict, dataclass

from vyper.utils import OrderedSet
from vyper.venom.analysis.dfg import DFGAnalysis
from vyper.venom.analysis.liveness import LivenessAnalysis
from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRVariable
from vyper.venom.function import IRFunction
from vyper.venom.passes.base_pass import IRPass

_ALL = ("storage", "transient", "memory", "immutables")

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,
"log": "memory",
"revert": "memory",
"return": "memory",
"sha3": "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 _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

for eff in get_reads(inst1.opcode):
#if eff in get_writes(inst2.opcode):
# return False
Fixed Show fixed Hide fixed
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
Fixed Show fixed Hide fixed
if getattr(inst1.fence, eff) != getattr(inst2.fence, eff):
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):
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)

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
# 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 == "phi":
# phi instructions stay at the beginning of the basic block
# and no input processing is needed
# bb.instructions.append(inst)
self.inst_order[inst] = 0
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}"
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
self._process_instruction_r(bb, target)

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.is_volatile:
self.fence_id += 1

# 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
# 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")
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
print(inst)
print(inst.fence)
print()

term_inst = bb.instructions[-1]
Fixed Show fixed Hide fixed

instructions = bb.instructions.copy()

for inst in instructions:
self._process_instruction_r(bb, inst)

bb.instructions.sort(key=lambda x: self.inst_order[x])
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)

self.fence_id = 0
self.fence = Fence()
self.visited_instructions: OrderedSet[IRInstruction] = OrderedSet()

basic_blocks = list(self.function.get_basic_blocks())

self.function.clear_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)
Loading