Skip to content

Commit

Permalink
Merge pull request #2352 from iamdefinitelyahuman/optimize-calldataload
Browse files Browse the repository at this point in the history
Optimize calldataload
  • Loading branch information
fubuloubu authored Apr 15, 2021
2 parents b455c18 + b0033f7 commit 3af89b5
Showing 1 changed file with 103 additions and 51 deletions.
154 changes: 103 additions & 51 deletions vyper/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,57 +59,8 @@ 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)
_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)
Expand Down Expand Up @@ -284,6 +235,107 @@ 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 _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
Expand Down

0 comments on commit 3af89b5

Please sign in to comment.