diff --git a/vyper/codegen/core.py b/vyper/codegen/core.py index 2bd4f81f50..d696b4690d 100644 --- a/vyper/codegen/core.py +++ b/vyper/codegen/core.py @@ -1049,10 +1049,10 @@ def _complex_make_setter(left, right, hi=None): # sstore(add (dst ofst), (sload (add (src ofst)))) is 16 bytes, # whereas loop overhead is 16-17 bytes. base_cost = 3 - if left._optimized.is_literal: + if left.optimized.is_literal: # code size is smaller since add is performed at compile-time base_cost += 1 - if right._optimized.is_literal: + if right.optimized.is_literal: base_cost += 1 # the formula is a heuristic, but it works. # (CMC 2023-07-14 could get more detailed for PUSH1 vs @@ -1076,10 +1076,10 @@ def _complex_make_setter(left, right, hi=None): # cost for 0th word - (mstore dst (mload src)) base_unroll_cost = 12 nth_word_cost = base_unroll_cost - if not left._optimized.is_literal: + if not left.optimized.is_literal: # (mstore (add N dst) (mload src)) nth_word_cost += 6 - if not right._optimized.is_literal: + if not right.optimized.is_literal: # (mstore dst (mload (add N src))) nth_word_cost += 6 diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index 6f9eb0359b..9d971e6c80 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -84,7 +84,7 @@ def resolve(self, body): # this creates a magical block which maps to IR `with` class _WithBuilder: def __init__(self, ir_node, name, should_inline=False): - if should_inline and ir_node._optimized.is_complex_ir: # pragma: nocover + if should_inline and ir_node.optimized.is_complex_ir: # pragma: nocover # this can only mean trouble raise CompilerPanic("trying to inline a complex IR node") @@ -175,6 +175,8 @@ def __init__( self.func_ir = None self.common_ir = None + self._optimized = None + assert self.value is not None, "None is not allowed as IRnode value" # Determine this node's valency (1 if it pushes a value on the stack, @@ -359,12 +361,12 @@ def __deepcopy__(self, memo): return ret # TODO would be nice to rename to `gas_estimate` or `gas_bound` - @property + @cached_property def gas(self): return self._gas + self.add_gas_estimate # the IR should be cached and/or evaluated exactly once - @property + @cached_property def is_complex_ir(self): # list of items not to cache. note can add other env variables # which do not change, e.g. calldatasize, coinbase, etc. @@ -424,12 +426,15 @@ def is_pointer(self) -> bool: # eventually return self.location is not None - @property # probably could be cached_property but be paranoid - def _optimized(self): - # TODO figure out how to fix this circular import - from vyper.ir.optimizer import optimize + @property + def optimized(self): + if self._optimized is None: + # TODO figure out how to fix this circular import + from vyper.ir.optimizer import optimize + + self._optimized = optimize(self) - return optimize(self) + return self._optimized # This function is slightly confusing but abstracts a common pattern: # when an IR value needs to be computed once and then cached as an @@ -451,7 +456,7 @@ def cache_when_complex(self, name): # because a non-literal expr could turn into a literal, # (e.g. `(add 1 2)`) # TODO this could really be moved into optimizer.py - should_inline = not self._optimized.is_complex_ir + should_inline = not self.optimized.is_complex_ir return _WithBuilder(self, name, should_inline) diff --git a/vyper/ir/optimizer.py b/vyper/ir/optimizer.py index 7ff5390e4b..5fdff82dff 100644 --- a/vyper/ir/optimizer.py +++ b/vyper/ir/optimizer.py @@ -422,6 +422,9 @@ def optimize(node: IRnode) -> IRnode: def _optimize(node: IRnode, parent: Optional[IRnode]) -> Tuple[bool, IRnode]: + if node._optimized is not None: + return node._optimized is not node, node._optimized + starting_symbols = node.unique_symbols res = [_optimize(arg, node) for arg in node.args]