From b3497db1194a76b87732e58b42529a830db8c36a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 13 Feb 2024 19:33:35 -0500 Subject: [PATCH 1/3] optimize optimizer --- vyper/codegen/ir_node.py | 17 +++++++++++------ vyper/ir/optimizer.py | 3 +++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index b1a71021c8..d8c2bbfeb5 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -213,6 +213,8 @@ def __init__( self.func_ir = None self.common_ir = None + self._has_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, @@ -402,12 +404,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. @@ -466,12 +468,15 @@ def is_pointer(self) -> bool: # eventually return self.location is not None - @property # probably could be cached_property but be paranoid + @property def _optimized(self): - # TODO figure out how to fix this circular import - from vyper.ir.optimizer import optimize + if self._has_optimized is None: + # TODO figure out how to fix this circular import + from vyper.ir.optimizer import optimize + + self._has_optimized = optimize(self) - return optimize(self) + return self._has_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 diff --git a/vyper/ir/optimizer.py b/vyper/ir/optimizer.py index 79e02f041d..42ed0d0783 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._has_optimized is not None: + return node._has_optimized is not node, node._has_optimized + starting_symbols = node.unique_symbols res = [_optimize(arg, node) for arg in node.args] From c2bad14d27ead83637fbcaabfc5d6f69b226a274 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 14 Feb 2024 01:12:44 -0500 Subject: [PATCH 2/3] change a name --- vyper/codegen/ir_node.py | 8 ++++---- vyper/ir/optimizer.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index d8c2bbfeb5..d23def455f 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -213,7 +213,7 @@ def __init__( self.func_ir = None self.common_ir = None - self._has_optimized = None + self.__optimized = None assert self.value is not None, "None is not allowed as IRnode value" @@ -470,13 +470,13 @@ def is_pointer(self) -> bool: @property def _optimized(self): - if self._has_optimized is None: + if self.__optimized is None: # TODO figure out how to fix this circular import from vyper.ir.optimizer import optimize - self._has_optimized = optimize(self) + self.__optimized = optimize(self) - return self._has_optimized + 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 diff --git a/vyper/ir/optimizer.py b/vyper/ir/optimizer.py index 42ed0d0783..c37c3473b0 100644 --- a/vyper/ir/optimizer.py +++ b/vyper/ir/optimizer.py @@ -422,8 +422,8 @@ def optimize(node: IRnode) -> IRnode: def _optimize(node: IRnode, parent: Optional[IRnode]) -> Tuple[bool, IRnode]: - if node._has_optimized is not None: - return node._has_optimized is not node, node._has_optimized + if node.__optimized is not None: + return node.__optimized is not node, node.__optimized starting_symbols = node.unique_symbols From c59712944887f2991be8169e230477438c9af739 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Wed, 14 Feb 2024 01:14:35 -0500 Subject: [PATCH 3/3] fix a name i guess dunder things don't get into the __dict__ object? --- vyper/codegen/core.py | 8 ++++---- vyper/codegen/ir_node.py | 16 ++++++++-------- vyper/ir/optimizer.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/vyper/codegen/core.py b/vyper/codegen/core.py index 1a090ac316..58d0fec6db 100644 --- a/vyper/codegen/core.py +++ b/vyper/codegen/core.py @@ -981,10 +981,10 @@ def _complex_make_setter(left, right): # 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 @@ -1008,10 +1008,10 @@ def _complex_make_setter(left, right): # 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 d23def455f..14bd54e80a 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -86,7 +86,7 @@ def resolve(self, body): def scope_together(ir_nodes, names): assert len(ir_nodes) == len(names) - should_scope = any(s._optimized.is_complex_ir for s in ir_nodes) + should_scope = any(s.optimized.is_complex_ir for s in ir_nodes) class _Builder: def resolve(self, body): @@ -122,7 +122,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: + if should_inline and ir_node.optimized.is_complex_ir: # this can only mean trouble raise CompilerPanic("trying to inline a complex IR node") @@ -213,7 +213,7 @@ def __init__( self.func_ir = None self.common_ir = None - self.__optimized = None + self._optimized = None assert self.value is not None, "None is not allowed as IRnode value" @@ -469,14 +469,14 @@ def is_pointer(self) -> bool: return self.location is not None @property - def _optimized(self): - if self.__optimized is None: + 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) + self._optimized = optimize(self) - return self.__optimized + 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 @@ -498,7 +498,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 c37c3473b0..9ad07e8e30 100644 --- a/vyper/ir/optimizer.py +++ b/vyper/ir/optimizer.py @@ -422,8 +422,8 @@ 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 + if node._optimized is not None: + return node._optimized is not node, node._optimized starting_symbols = node.unique_symbols