Skip to content

Commit

Permalink
feat[venom]: propagate dload instruction to venom (#4410)
Browse files Browse the repository at this point in the history
this commit propagates `dload` instructions to venom from the frontend.
this improves our ability to merge `dload/mstore` instructions. then,
after the LowerDload pass, we have stripped both `dload` and
`dloadbytes` instructions from venom.
  • Loading branch information
charles-cooper authored Dec 20, 2024
1 parent eee31e7 commit 724559a
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 36 deletions.
38 changes: 22 additions & 16 deletions tests/unit/compiler/venom/test_memmerging.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ def _check_no_change(pre):
_check_pre_post(pre, pre)


# for parametrizing tests
LOAD_COPY = [("dload", "dloadbytes"), ("calldataload", "calldatacopy")]


def test_memmerging():
"""
Basic memory merge test
Expand Down Expand Up @@ -762,43 +766,45 @@ def test_memmerging_double_use():
_check_pre_post(pre, post)


def test_memmerging_calldataload():
pre = """
@pytest.mark.parametrize("load_opcode,copy_opcode", LOAD_COPY)
def test_memmerging_load(load_opcode, copy_opcode):
pre = f"""
_global:
%1 = calldataload 0
%1 = {load_opcode} 0
mstore 32, %1
%2 = calldataload 32
%2 = {load_opcode} 32
mstore 64, %2
stop
"""

post = """
post = f"""
_global:
calldatacopy 32, 0, 64
{copy_opcode} 32, 0, 64
stop
"""
_check_pre_post(pre, post)


def test_memmerging_calldataload_two_intervals_diff_offset():
@pytest.mark.parametrize("load_opcode,copy_opcode", LOAD_COPY)
def test_memmerging_two_intervals_diff_offset(load_opcode, copy_opcode):
"""
Test different calldatacopy sequences are separately merged
Test different dloadbytes/calldatacopy sequences are separately merged
"""
pre = """
pre = f"""
_global:
%1 = calldataload 0
%1 = {load_opcode} 0
mstore 0, %1
calldatacopy 32, 32, 64
%2 = calldataload 0
{copy_opcode} 32, 32, 64
%2 = {load_opcode} 0
mstore 8, %2
calldatacopy 40, 32, 64
{copy_opcode} 40, 32, 64
stop
"""

post = """
post = f"""
_global:
calldatacopy 0, 0, 96
calldatacopy 8, 0, 96
{copy_opcode} 0, 0, 96
{copy_opcode} 8, 0, 96
stop
"""
_check_pre_post(pre, post)
Expand Down
2 changes: 2 additions & 0 deletions vyper/venom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
BranchOptimizationPass,
DFTPass,
FloatAllocas,
LowerDloadPass,
MakeSSA,
Mem2Var,
MemMergePass,
Expand Down Expand Up @@ -59,6 +60,7 @@ def _run_passes(fn: IRFunction, optimize: OptimizationLevel) -> None:
StoreElimination(ac, fn).run_pass()
MemMergePass(ac, fn).run_pass()
SimplifyCFGPass(ac, fn).run_pass()
LowerDloadPass(ac, fn).run_pass()
AlgebraicOptimizationPass(ac, fn).run_pass()
# NOTE: MakeSSA is after algebraic optimization it currently produces
# smaller code by adding some redundant phi nodes. This is not a
Expand Down
1 change: 1 addition & 0 deletions vyper/venom/effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def __iter__(self):
"invoke": ALL, # could be smarter, look up the effects of the invoked function
"log": LOG,
"dloadbytes": MEMORY,
"dload": MEMORY,
"returndatacopy": MEMORY,
"calldatacopy": MEMORY,
"codecopy": MEMORY,
Expand Down
19 changes: 2 additions & 17 deletions vyper/venom/ir_node_to_venom.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from vyper.codegen.ir_node import IRnode
from vyper.evm.opcodes import get_opcodes
from vyper.utils import MemoryPositions
from vyper.venom.basicblock import (
IRBasicBlock,
IRInstruction,
Expand Down Expand Up @@ -67,6 +66,8 @@
"mload",
"iload",
"istore",
"dload",
"dloadbytes",
"sload",
"sstore",
"tload",
Expand Down Expand Up @@ -400,22 +401,6 @@ def _convert_ir_bb(fn, ir, symbols):
else:
bb.append_instruction("jmp", label)

elif ir.value == "dload":
arg_0 = _convert_ir_bb(fn, ir.args[0], symbols)
bb = fn.get_basic_block()
src = bb.append_instruction("add", arg_0, IRLabel("code_end"))

bb.append_instruction("dloadbytes", 32, src, MemoryPositions.FREE_VAR_SPACE)
return bb.append_instruction("mload", MemoryPositions.FREE_VAR_SPACE)

elif ir.value == "dloadbytes":
dst, src_offset, len_ = _convert_ir_bb_list(fn, ir.args, symbols)

bb = fn.get_basic_block()
src = bb.append_instruction("add", src_offset, IRLabel("code_end"))
bb.append_instruction("dloadbytes", len_, src, dst)
return None

elif ir.value == "mstore":
# some upstream code depends on reversed order of evaluation --
# to fix upstream.
Expand Down
1 change: 1 addition & 0 deletions vyper/venom/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .branch_optimization import BranchOptimizationPass
from .dft import DFTPass
from .float_allocas import FloatAllocas
from .lower_dload import LowerDloadPass
from .make_ssa import MakeSSA
from .mem2var import Mem2Var
from .memmerging import MemMergePass
Expand Down
42 changes: 42 additions & 0 deletions vyper/venom/passes/lower_dload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from vyper.utils import MemoryPositions
from vyper.venom.analysis import DFGAnalysis, LivenessAnalysis
from vyper.venom.basicblock import IRBasicBlock, IRInstruction, IRLabel, IRLiteral
from vyper.venom.passes.base_pass import IRPass


class LowerDloadPass(IRPass):
"""
Lower dload and dloadbytes instructions
"""

def run_pass(self):
for bb in self.function.get_basic_blocks():
self._handle_bb(bb)
self.analyses_cache.invalidate_analysis(LivenessAnalysis)
self.analyses_cache.invalidate_analysis(DFGAnalysis)

def _handle_bb(self, bb: IRBasicBlock):
fn = bb.parent
for idx, inst in enumerate(bb.instructions):
if inst.opcode == "dload":
(ptr,) = inst.operands
var = fn.get_next_variable()
bb.insert_instruction(
IRInstruction("add", [ptr, IRLabel("code_end")], output=var), index=idx
)
idx += 1
dst = IRLiteral(MemoryPositions.FREE_VAR_SPACE)
bb.insert_instruction(
IRInstruction("codecopy", [IRLiteral(32), var, dst]), index=idx
)

inst.opcode = "mload"
inst.operands = [dst]
elif inst.opcode == "dloadbytes":
_, src, _ = inst.operands
code_ptr = fn.get_next_variable()
bb.insert_instruction(
IRInstruction("add", [src, IRLabel("code_end")], output=code_ptr), index=idx
)
inst.opcode = "codecopy"
inst.operands[1] = code_ptr
3 changes: 2 additions & 1 deletion vyper/venom/passes/memmerging.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def run_pass(self):
for bb in self.function.get_basic_blocks():
self._handle_bb_memzero(bb)
self._handle_bb(bb, "calldataload", "calldatacopy", allow_dst_overlaps_src=True)
self._handle_bb(bb, "dload", "dloadbytes", allow_dst_overlaps_src=True)

if version_check(begin="cancun"):
# mcopy is available
Expand All @@ -117,7 +118,7 @@ def _optimize_copy(self, bb: IRBasicBlock, copy_opcode: str, load_opcode: str):

pin_inst = None
inst = copy.insts[-1]
if copy.length != 32:
if copy.length != 32 or load_opcode == "dload":
inst.output = None
inst.opcode = copy_opcode
inst.operands = [IRLiteral(copy.length), IRLiteral(copy.src), IRLiteral(copy.dst)]
Expand Down
3 changes: 1 addition & 2 deletions vyper/venom/venom_to_assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"calldatacopy",
"mcopy",
"calldataload",
"codecopy",
"gas",
"gasprice",
"gaslimit",
Expand Down Expand Up @@ -472,8 +473,6 @@ def _generate_evm_for_instruction(
pass
elif opcode == "dbname":
pass
elif opcode in ["codecopy", "dloadbytes"]:
assembly.append("CODECOPY")
elif opcode == "jnz":
# jump if not zero
if_nonzero_label = inst.operands[1]
Expand Down

0 comments on commit 724559a

Please sign in to comment.