def optimize(node: LLLnode) -> LLLnode: argz = [optimize(arg) for arg in node.args] 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) calcer, symb = arith[node.value] new_value = calcer(left, right) if argz[0].annotation and argz[1].annotation: annotation = argz[0].annotation + symb + argz[1].annotation elif argz[0].annotation or argz[1].annotation: annotation = (argz[0].annotation or str(left)) + symb + ( argz[1].annotation or str(right)) else: annotation = '' return LLLnode( new_value, [], node.typ, None, node.pos, annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif _is_constant_add(node, argz): calcer, symb = arith[node.value] if argz[0].annotation and argz[1].args[0].annotation: annotation = argz[0].annotation + symb + argz[1].args[0].annotation elif argz[0].annotation or argz[1].args[0].annotation: annotation = (argz[0].annotation or str(argz[0].value)) + symb + ( argz[1].args[0].annotation or str(argz[1].args[0].value)) else: annotation = '' return LLLnode( "add", [ LLLnode(argz[0].value + argz[1].args[0].value, annotation=annotation), argz[1].args[1], ], node.typ, None, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "add" and get_int_at(argz, 0) == 0: return LLLnode( argz[1].value, argz[1].args, node.typ, node.location, node.pos, argz[1].annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "add" and get_int_at(argz, 1) == 0: return LLLnode( argz[0].value, argz[0].args, node.typ, node.location, node.pos, argz[0].annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "clamp" and int_at(argz, 0) and int_at( argz, 1) and int_at(argz, 2): if get_int_at(argz, 0, True) > get_int_at(argz, 1, True): raise Exception("Clamp always fails") elif get_int_at(argz, 1, True) > get_int_at(argz, 2, True): raise Exception("Clamp always fails") else: return argz[1] elif node.value == "clamp" and int_at(argz, 0) and int_at(argz, 1): if get_int_at(argz, 0, True) > get_int_at(argz, 1, True): raise Exception("Clamp always fails") else: return LLLnode( "clample", [argz[1], argz[2]], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "clamp_nonzero" and int_at(argz, 0): if get_int_at(argz, 0) != 0: return LLLnode( argz[0].value, [], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) else: raise Exception("Clamp always fails") # [eq, x, 0] is the same as [iszero, x]. elif node.value == 'eq' and int_at(argz, 1) and argz[1].value == 0: return LLLnode( 'iszero', [argz[0]], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) # [ne, x, y] has the same truthyness as [xor, x, y] # rewrite 'ne' as 'xor' in places where truthy is accepted. elif has_cond_arg(node) and argz[0].value == 'ne': argz[0] = LLLnode.from_list(['xor'] + argz[0].args) return LLLnode.from_list( [node.value] + argz, typ=node.typ, location=node.location, pos=node.pos, annotation=node.annotation, # let from_list handle valency and gas_estimate ) elif _is_with_without_set(node, argz): # TODO: This block is currently unreachable due to # `_is_with_without_set` unconditionally returning `False` this appears # to be because this "is actually not such a good optimization after # all" accordiing to previous comment. o = replace_with_value(argz[2], argz[0].value, argz[1].value) return o elif node.value == "seq": o = [] for arg in argz: if arg.value == "seq": o.extend(arg.args) elif arg.value != "pass": o.append(arg) return LLLnode( node.value, o, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.total_gas is not None: o = LLLnode( node.value, argz, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) o.total_gas = node.total_gas - node.gas + o.gas o.func_name = node.func_name return o else: return LLLnode( node.value, argz, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, )
def optimize(node: LLLnode) -> LLLnode: argz = [optimize(arg) for arg in node.args] 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) # `node.value in arith` implies that `node.value` is a `str` calcer, symb = arith[str(node.value)] new_value = calcer(left, right) if argz[0].annotation and argz[1].annotation: annotation = argz[0].annotation + symb + argz[1].annotation elif argz[0].annotation or argz[1].annotation: annotation = ((argz[0].annotation or str(left)) + symb + (argz[1].annotation or str(right))) else: annotation = "" return LLLnode( new_value, [], node.typ, None, node.pos, annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif _is_constant_add(node, argz): # `node.value in arith` implies that `node.value` is a `str` calcer, symb = arith[str(node.value)] if argz[0].annotation and argz[1].args[0].annotation: annotation = argz[0].annotation + symb + argz[1].args[0].annotation elif argz[0].annotation or argz[1].args[0].annotation: annotation = ( (argz[0].annotation or str(argz[0].value)) + symb + (argz[1].args[0].annotation or str(argz[1].args[0].value))) else: annotation = "" return LLLnode( "add", [ LLLnode(int(argz[0].value) + int(argz[1].args[0].value), annotation=annotation), argz[1].args[1], ], node.typ, None, annotation=node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "add" and get_int_at(argz, 0) == 0: return LLLnode( argz[1].value, argz[1].args, node.typ, node.location, node.pos, annotation=argz[1].annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "add" and get_int_at(argz, 1) == 0: return LLLnode( argz[0].value, argz[0].args, node.typ, node.location, node.pos, argz[0].annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "clamp" and int_at(argz, 0) and int_at( argz, 1) and int_at(argz, 2): if get_int_at(argz, 0, True) > get_int_at(argz, 1, True): # type: ignore raise Exception("Clamp always fails") elif get_int_at(argz, 1, True) > get_int_at(argz, 2, True): # type: ignore raise Exception("Clamp always fails") else: return argz[1] elif node.value == "clamp" and int_at(argz, 0) and int_at(argz, 1): if get_int_at(argz, 0, True) > get_int_at(argz, 1, True): # type: ignore raise Exception("Clamp always fails") else: return LLLnode( "clample", [argz[1], argz[2]], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "clamp_nonzero" and int_at(argz, 0): if get_int_at(argz, 0) != 0: return LLLnode( argz[0].value, [], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) else: raise Exception("Clamp always fails") # [eq, x, 0] is the same as [iszero, x]. elif node.value == "eq" and int_at(argz, 1) and argz[1].value == 0: return LLLnode( "iszero", [argz[0]], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) # [ne, x, y] has the same truthyness as [xor, x, y] # rewrite 'ne' as 'xor' in places where truthy is accepted. elif has_cond_arg(node) and argz[0].value == "ne": argz[0] = LLLnode.from_list(["xor"] + argz[0].args) # type: ignore return LLLnode.from_list( [node.value] + argz, # type: ignore typ=node.typ, location=node.location, pos=node.pos, annotation=node.annotation, # let from_list handle valency and gas_estimate ) elif node.value == "seq": xs: List[Any] = [] for arg in argz: if arg.value == "seq": xs.extend(arg.args) else: xs.append(arg) return LLLnode( node.value, xs, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.total_gas is not None: o = LLLnode( node.value, argz, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) o.total_gas = node.total_gas - node.gas + o.gas o.func_name = node.func_name return o else: return LLLnode( node.value, argz, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, )
def optimize(node): argz = [optimize(arg) for arg in node.args] 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) calcer, symb = arith[node.value] new_value = calcer(left, right) if argz[0].annotation and argz[1].annotation: annotation = argz[0].annotation + symb + argz[1].annotation elif argz[0].annotation or argz[1].annotation: annotation = (argz[0].annotation or str(left)) + symb + ( argz[1].annotation or str(right)) else: annotation = '' return LLLnode(new_value, [], node.typ, None, node.pos, annotation, add_gas_estimate=node.add_gas_estimate) elif node.value == "add" and int_at( argz, 0) and argz[1].value == "add" and int_at(argz[1].args, 0): calcer, symb = arith[node.value] if argz[0].annotation and argz[1].args[0].annotation: annotation = argz[0].annotation + symb + argz[1].args[0].annotation elif argz[0].annotation or argz[1].args[0].annotation: annotation = (argz[0].annotation or str(argz[0].value)) + symb + ( argz[1].args[0].annotation or str(argz[1].args[0].value)) else: annotation = '' return LLLnode("add", [ LLLnode(argz[0].value + argz[1].args[0].value, annotation=annotation), argz[1].args[1] ], node.typ, None, node.annotation, add_gas_estimate=node.add_gas_estimate) elif node.value == "add" and get_int_at(argz, 0) == 0: return LLLnode(argz[1].value, argz[1].args, node.typ, node.location, node.pos, argz[1].annotation, add_gas_estimate=node.add_gas_estimate) elif node.value == "add" and get_int_at(argz, 1) == 0: return LLLnode(argz[0].value, argz[0].args, node.typ, node.location, node.pos, argz[0].annotation, add_gas_estimate=node.add_gas_estimate) elif node.value == "clamp" and int_at(argz, 0) and int_at( argz, 1) and int_at(argz, 2): if get_int_at(argz, 0, True) > get_int_at(argz, 1, True): raise Exception("Clamp always fails") elif get_int_at(argz, 1, True) > get_int_at(argz, 2, True): raise Exception("Clamp always fails") else: return argz[1] elif node.value == "clamp" and int_at(argz, 0) and int_at(argz, 1): if get_int_at(argz, 0, True) > get_int_at(argz, 1, True): raise Exception("Clamp always fails") else: return LLLnode("clample", [argz[1], argz[2]], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate) elif node.value == "clamp_nonzero" and int_at(argz, 0): if get_int_at(argz, 0) != 0: return LLLnode(argz[0].value, [], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate) else: raise Exception("Clamp always fails") # Turns out this is actually not such a good optimization after all elif node.value == "with" and int_at( argz, 1) and not search_for_set(argz[2], argz[0].value) and False: o = replace_with_value(argz[2], argz[0].value, argz[1].value) return o elif node.value == "seq": o = [] for arg in argz: if arg.value == "seq": o.extend(arg.args) elif arg.value != "pass": o.append(arg) return LLLnode(node.value, o, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate) elif hasattr(node, 'total_gas'): o = LLLnode(node.value, argz, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate) o.total_gas = node.total_gas - node.gas + o.gas o.func_name = node.func_name return o else: return LLLnode(node.value, argz, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate)
def apply_general_optimizations(node: LLLnode) -> LLLnode: # TODO refactor this into several functions 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() 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) # `node.value in arith` implies that `node.value` is a `str` calcer, symb = arith[str(node.value)] new_value = calcer(left, right) if argz[0].annotation and argz[1].annotation: annotation = argz[0].annotation + symb + argz[1].annotation elif argz[0].annotation or argz[1].annotation: annotation = ((argz[0].annotation or str(left)) + symb + (argz[1].annotation or str(right))) else: annotation = "" return LLLnode( new_value, [], node.typ, None, node.pos, annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif _is_constant_add(node, argz): # `node.value in arith` implies that `node.value` is a `str` calcer, symb = arith[str(node.value)] if argz[0].annotation and argz[1].args[0].annotation: annotation = argz[0].annotation + symb + argz[1].args[0].annotation elif argz[0].annotation or argz[1].args[0].annotation: annotation = ( (argz[0].annotation or str(argz[0].value)) + symb + (argz[1].args[0].annotation or str(argz[1].args[0].value))) else: annotation = "" return LLLnode( "add", [ LLLnode(int(argz[0].value) + int(argz[1].args[0].value), annotation=annotation), argz[1].args[1], ], node.typ, None, annotation=node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "add" and get_int_at(argz, 0) == 0: return LLLnode( argz[1].value, argz[1].args, node.typ, node.location, node.pos, annotation=argz[1].annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "add" and get_int_at(argz, 1) == 0: return LLLnode( argz[0].value, argz[0].args, node.typ, node.location, node.pos, argz[0].annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "clamp" and int_at(argz, 0) and int_at( argz, 1) and int_at(argz, 2): if get_int_at(argz, 0, True) > get_int_at(argz, 1, True): # type: ignore raise Exception("Clamp always fails") elif get_int_at(argz, 1, True) > get_int_at(argz, 2, True): # type: ignore raise Exception("Clamp always fails") else: return argz[1] elif node.value == "clamp" and int_at(argz, 0) and int_at(argz, 1): if get_int_at(argz, 0, True) > get_int_at(argz, 1, True): # type: ignore raise Exception("Clamp always fails") else: return LLLnode( "clample", [argz[1], argz[2]], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.value == "clamp_nonzero" and int_at(argz, 0): if get_int_at(argz, 0) != 0: return LLLnode( argz[0].value, [], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) else: raise Exception("Clamp always fails") # [eq, x, 0] is the same as [iszero, x]. elif node.value == "eq" and int_at(argz, 1) and argz[1].value == 0: return LLLnode( "iszero", [argz[0]], node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) # [ne, x, y] has the same truthyness as [xor, x, y] # rewrite 'ne' as 'xor' in places where truthy is accepted. elif node.value in ("if", "if_unchecked", "assert") and argz[0].value == "ne": argz[0] = LLLnode.from_list(["xor"] + argz[0].args) # type: ignore return LLLnode.from_list( [node.value] + argz, # type: ignore typ=node.typ, location=node.location, pos=node.pos, annotation=node.annotation, # let from_list handle valency and gas_estimate ) elif node.value == "seq": xs: List[Any] = [] for arg in argz: if arg.value == "seq": xs.extend(arg.args) else: xs.append(arg) return LLLnode( node.value, xs, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) elif node.total_gas is not None: o = LLLnode( node.value, argz, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, ) o.total_gas = node.total_gas - node.gas + o.gas o.func_name = node.func_name return o else: return LLLnode( node.value, argz, node.typ, node.location, node.pos, node.annotation, add_gas_estimate=node.add_gas_estimate, valency=node.valency, )