From 9d8c3ba8d43e6075027099684b11967167dddec4 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Wed, 14 Apr 2021 17:16:56 +0400 Subject: [PATCH 1/2] refactor: move mzero merge logic into a private function --- vyper/optimizer.py | 106 +++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/vyper/optimizer.py b/vyper/optimizer.py index 4cd987169e..ef997632cf 100644 --- a/vyper/optimizer.py +++ b/vyper/optimizer.py @@ -59,57 +59,7 @@ def apply_general_optimizations(node: LLLnode) -> LLLnode: argz = [apply_general_optimizations(arg) for arg in node.args] if node.value == "seq": - # look for sequential mzero / calldatacopy operations that are zero'ing memory - # and merge them into a single calldatacopy - mzero_nodes: List = [] - initial_offset = 0 - total_length = 0 - for lll_node in [i for i in argz if i.value != "pass"]: - if ( - lll_node.value == "mstore" - and isinstance(lll_node.args[0].value, int) - and lll_node.args[1].value == 0 - ): - # mstore of a zero value - offset = lll_node.args[0].value - if not mzero_nodes: - initial_offset = offset - if initial_offset + total_length == offset: - mzero_nodes.append(lll_node) - total_length += 32 - continue - - if ( - lll_node.value == "calldatacopy" - and isinstance(lll_node.args[0].value, int) - and lll_node.args[1].value == "calldatasize" - and isinstance(lll_node.args[2].value, int) - ): - # calldatacopy from the end of calldata - efficient zero'ing via `empty()` - offset, length = lll_node.args[0].value, lll_node.args[2].value - if not mzero_nodes: - initial_offset = offset - if initial_offset + total_length == offset: - mzero_nodes.append(lll_node) - total_length += length - continue - - # if we get this far, the current node is not a zero'ing operation - # it's time to apply the optimization if possible - if len(mzero_nodes) > 1: - new_lll = LLLnode.from_list( - ["calldatacopy", initial_offset, "calldatasize", total_length], - pos=mzero_nodes[0].pos, - ) - # replace first zero'ing operation with optimized node and remove the rest - idx = argz.index(mzero_nodes[0]) - argz[idx] = new_lll - for i in mzero_nodes[1:]: - argz.remove(i) - - initial_offset = 0 - total_length = 0 - mzero_nodes.clear() + _merge_memzero(argz) if node.value in arith and int_at(argz, 0) and int_at(argz, 1): left, right = get_int_at(argz, 0), get_int_at(argz, 1) @@ -284,6 +234,60 @@ def apply_general_optimizations(node: LLLnode) -> LLLnode: ) +def _merge_memzero(argz): + # look for sequential mzero / calldatacopy operations that are zero'ing memory + # and merge them into a single calldatacopy + mstore_nodes: List = [] + initial_offset = 0 + total_length = 0 + for lll_node in [i for i in argz if i.value != "pass"]: + if ( + lll_node.value == "mstore" + and isinstance(lll_node.args[0].value, int) + and lll_node.args[1].value == 0 + ): + # mstore of a zero value + offset = lll_node.args[0].value + if not mstore_nodes: + initial_offset = offset + if initial_offset + total_length == offset: + mstore_nodes.append(lll_node) + total_length += 32 + continue + + if ( + lll_node.value == "calldatacopy" + and isinstance(lll_node.args[0].value, int) + and lll_node.args[1].value == "calldatasize" + and isinstance(lll_node.args[2].value, int) + ): + # calldatacopy from the end of calldata - efficient zero'ing via `empty()` + offset, length = lll_node.args[0].value, lll_node.args[2].value + if not mstore_nodes: + initial_offset = offset + if initial_offset + total_length == offset: + mstore_nodes.append(lll_node) + total_length += length + continue + + # if we get this far, the current node is not a zero'ing operation + # it's time to apply the optimization if possible + if len(mstore_nodes) > 1: + new_lll = LLLnode.from_list( + ["calldatacopy", initial_offset, "calldatasize", total_length], + pos=mstore_nodes[0].pos, + ) + # replace first zero'ing operation with optimized node and remove the rest + idx = argz.index(mstore_nodes[0]) + argz[idx] = new_lll + for i in mstore_nodes[1:]: + argz.remove(i) + + initial_offset = 0 + total_length = 0 + mstore_nodes.clear() + + def filter_unused_sizelimits(lll_node: LLLnode) -> LLLnode: # recursively search the LLL for mloads of the size limits, and then remove # the initial mstore operations for size limits that are never referenced From b0033f7dadc207e199e5cf36a10ef25ce215d5f6 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Wed, 14 Apr 2021 17:26:07 +0400 Subject: [PATCH 2/2] feat: optimize sequential calldataload operations --- vyper/optimizer.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/vyper/optimizer.py b/vyper/optimizer.py index ef997632cf..740ba3b050 100644 --- a/vyper/optimizer.py +++ b/vyper/optimizer.py @@ -60,6 +60,7 @@ def apply_general_optimizations(node: LLLnode) -> LLLnode: if node.value == "seq": _merge_memzero(argz) + _merge_calldataload(argz) if node.value in arith and int_at(argz, 0) and int_at(argz, 1): left, right = get_int_at(argz, 0), get_int_at(argz, 1) @@ -288,6 +289,53 @@ def _merge_memzero(argz): mstore_nodes.clear() +def _merge_calldataload(argz): + # look for sequential operations copying from calldata to memory + # and merge them into a single calldatacopy operation + mstore_nodes: List = [] + initial_mem_offset = 0 + initial_calldata_offset = 0 + total_length = 0 + for lll_node in [i for i in argz if i.value != "pass"]: + if ( + lll_node.value == "mstore" + and isinstance(lll_node.args[0].value, int) + and lll_node.args[1].value == "calldataload" + and isinstance(lll_node.args[1].args[0].value, int) + ): + # mstore of a zero value + mem_offset = lll_node.args[0].value + calldata_offset = lll_node.args[1].args[0].value + if not mstore_nodes: + initial_mem_offset = mem_offset + initial_calldata_offset = calldata_offset + if ( + initial_mem_offset + total_length == mem_offset + and initial_calldata_offset + total_length == calldata_offset + ): + mstore_nodes.append(lll_node) + total_length += 32 + continue + + # if we get this far, the current node is a different operation + # it's time to apply the optimization if possible + if len(mstore_nodes) > 1: + new_lll = LLLnode.from_list( + ["calldatacopy", initial_mem_offset, initial_calldata_offset, total_length], + pos=mstore_nodes[0].pos, + ) + # replace first copy operation with optimized node and remove the rest + idx = argz.index(mstore_nodes[0]) + argz[idx] = new_lll + for i in mstore_nodes[1:]: + argz.remove(i) + + initial_mem_offset = 0 + initial_calldata_offset = 0 + total_length = 0 + mstore_nodes.clear() + + def filter_unused_sizelimits(lll_node: LLLnode) -> LLLnode: # recursively search the LLL for mloads of the size limits, and then remove # the initial mstore operations for size limits that are never referenced