diff --git a/tests/unit/compiler/venom/test_memmerging.py b/tests/unit/compiler/venom/test_memmerging.py index b7a4d33805..d309752621 100644 --- a/tests/unit/compiler/venom/test_memmerging.py +++ b/tests/unit/compiler/venom/test_memmerging.py @@ -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 @@ -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) diff --git a/vyper/venom/__init__.py b/vyper/venom/__init__.py index a531ffda48..1aac42e4fb 100644 --- a/vyper/venom/__init__.py +++ b/vyper/venom/__init__.py @@ -15,6 +15,7 @@ BranchOptimizationPass, DFTPass, FloatAllocas, + LowerDloadPass, MakeSSA, Mem2Var, MemMergePass, @@ -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 diff --git a/vyper/venom/effects.py b/vyper/venom/effects.py index 97cffe2cb2..bbda481e14 100644 --- a/vyper/venom/effects.py +++ b/vyper/venom/effects.py @@ -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, diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index 5454b994b3..4dcc5ee4e6 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -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, @@ -67,6 +66,8 @@ "mload", "iload", "istore", + "dload", + "dloadbytes", "sload", "sstore", "tload", @@ -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. diff --git a/vyper/venom/passes/__init__.py b/vyper/venom/passes/__init__.py index 2c113027a5..fe1e387c56 100644 --- a/vyper/venom/passes/__init__.py +++ b/vyper/venom/passes/__init__.py @@ -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 diff --git a/vyper/venom/passes/lower_dload.py b/vyper/venom/passes/lower_dload.py new file mode 100644 index 0000000000..c863a1b7c7 --- /dev/null +++ b/vyper/venom/passes/lower_dload.py @@ -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 diff --git a/vyper/venom/passes/memmerging.py b/vyper/venom/passes/memmerging.py index 41216e3041..2e5ee46b84 100644 --- a/vyper/venom/passes/memmerging.py +++ b/vyper/venom/passes/memmerging.py @@ -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 @@ -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)] diff --git a/vyper/venom/venom_to_assembly.py b/vyper/venom/venom_to_assembly.py index 5cf37006a4..e136932f51 100644 --- a/vyper/venom/venom_to_assembly.py +++ b/vyper/venom/venom_to_assembly.py @@ -41,6 +41,7 @@ "calldatacopy", "mcopy", "calldataload", + "codecopy", "gas", "gasprice", "gaslimit", @@ -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]