Skip to content

Commit

Permalink
Cache irnodes in the constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
electriclilies committed Apr 28, 2024
1 parent fbac628 commit d9a0265
Showing 1 changed file with 62 additions and 1 deletion.
63 changes: 62 additions & 1 deletion vyper/codegen/ir_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ class IRnode:

_id: int
_next_id: int = -1
_cached_nodes = dict[int, "IRnode"] = {}

def __init__(
self,
Expand All @@ -157,6 +158,7 @@ def __init__(
is_self_call: bool = False,
passthrough_metadata: dict[str, Any] = None,
_id: int = None,
attempt_to_cache = True
):
if args is None:
args = []
Expand Down Expand Up @@ -359,7 +361,50 @@ def __init__(
else: # pragma: nocover
raise CompilerPanic(f"Invalid value for IR AST node: {self.value}")
assert isinstance(self.args, list)


if attempt_to_cache:
# Replace any cached arguments with the correct variable.
new_args = [] # Arg list containing references to the cached variables.
replaced_arg = False
newVars = [] # List of new variables that we need to define.

for arg in self.args:
should_inline = not arg.is_complex_ir
if should_inline:
# We're inlining arg, so don't try to replace the argument with a variable.
new_args.append(arg)
else:
# If valency of the arg is not 1 or it's pass, we can't define it in a with statement, so don't attempt to cache it.
if (arg.valency != 1 or arg.value == "pass"):
new_args.append(arg)
continue
# Check if we've already created a variable to represent this arg. If so, replace the argument IRnode with the variable.
cached_arg = self.get_cached_node(arg._id)
if cached_arg:
new_args.append(cached_arg)
replaced_arg = True
else:
# Create a variable to represent the argument, and add the variable to the cache.
ir_var_name = "ir_var_" + str(arg._id)
ir_var = IRnode.from_list(ir_var_name, typ=self.typ, location=self.location, encoding=self.encoding, attempt_to_cache=False)
self.cache_node(arg._id, ir_var)
# Add the new variable to the list of variables we need to define in a "with" scope.
new_args.append(ir_var)
newVars.append((ir_var_name, arg))
replaced_arg = True

if (replaced_arg):
self.args = new_args.copy()

# Wrap self in "with" statements that define the new variables we just created
body = self
for (ir_var_name, ir_node) in newVars:
body = IRnode.from_list(["with", ir_var_name, ir_node, body], self.typ, attempt_to_cache=False)

# Update args and value to include the new scoped "with" statement we just created.
self.value = body.value
self.args = body.args

# deepcopy is a perf hotspot; it pays to optimize it a little
def __deepcopy__(self, memo):
cls = self.__class__
Expand All @@ -377,6 +422,19 @@ def ensure_id(self):
if self._id is None:
self._id = self.generate_id()

@classmethod
def get_cached_node(cls, _id):
if _id in cls._cached_nodes:
return cls._cached_nodes[_id]
return None

@classmethod
def cache_node(cls, _id, ir_var):
if _id in cls._cached_nodes:
raise CompilerPanic("Can't cache a node twice!")
else:
cls._cached_nodes[_id] = ir_var

# TODO would be nice to rename to `gas_estimate` or `gas_bound`
@property
def gas(self):
Expand Down Expand Up @@ -581,6 +639,7 @@ def from_list(
passthrough_metadata: dict[str, Any] = None,
encoding: Encoding = Encoding.VYPER,
_id=None,
attempt_to_cache=True,
) -> "IRnode":
if isinstance(typ, str): # pragma: nocover
raise CompilerPanic(f"Expected type, not string: {typ}")
Expand Down Expand Up @@ -616,6 +675,7 @@ def from_list(
is_self_call=is_self_call,
passthrough_metadata=passthrough_metadata,
_id=_id,
attempt_to_cache=attempt_to_cache,
)
else:
return cls(
Expand All @@ -632,4 +692,5 @@ def from_list(
is_self_call=is_self_call,
passthrough_metadata=passthrough_metadata,
_id=_id,
attempt_to_cache=attempt_to_cache,
)

0 comments on commit d9a0265

Please sign in to comment.