diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index 14e396ff74..1ce27ee701 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -139,6 +139,9 @@ class IRnode: func_ir: Any common_ir: Any + _id: int + _next_id: int = -1 + def __init__( self, value: Union[str, int], @@ -153,6 +156,7 @@ def __init__( encoding: Encoding = Encoding.VYPER, is_self_call: bool = False, passthrough_metadata: dict[str, Any] = None, + _id: int = None, ): if args is None: args = [] @@ -175,6 +179,12 @@ def __init__( self.func_ir = None self.common_ir = None + self._id = _id # type: ignore[assignment] + self.ensure_id() + + # helpful for debugging: + # self._origin = traceback.extract_stack() + 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, @@ -358,6 +368,15 @@ def __deepcopy__(self, memo): ret.args = [copy.deepcopy(arg) for arg in ret.args] return ret + @classmethod + def generate_id(cls): + cls._next_id += 1 + return cls._next_id + + def ensure_id(self): + if self._id is None: + self._id = self.generate_id() + # TODO would be nice to rename to `gas_estimate` or `gas_bound` @property def gas(self): @@ -489,6 +508,8 @@ def repr_value(self): return hex(self.value) if not isinstance(self.value, str): return str(self.value) + # useful for debugging + # return f"{self._id}:{self.value}" return self.value @staticmethod @@ -559,11 +580,13 @@ def from_list( is_self_call: bool = False, passthrough_metadata: dict[str, Any] = None, encoding: Encoding = Encoding.VYPER, + _id=None, ) -> "IRnode": if isinstance(typ, str): # pragma: nocover raise CompilerPanic(f"Expected type, not string: {typ}") if isinstance(obj, IRnode): + assert _id is None # note: this modify-and-returnclause is a little weird since # the input gets modified. CC 20191121. if typ is not None: @@ -592,6 +615,7 @@ def from_list( error_msg=error_msg, is_self_call=is_self_call, passthrough_metadata=passthrough_metadata, + _id=_id, ) else: return cls( @@ -607,4 +631,5 @@ def from_list( error_msg=error_msg, is_self_call=is_self_call, passthrough_metadata=passthrough_metadata, + _id=_id, ) diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index 472d28f4fb..e841cf8531 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -211,11 +211,24 @@ def apply_line_no_wrapper(*args, **kwargs): return apply_line_no_wrapper +def check_duplicated_nodes(ir_node, seen=None): + seen = seen or set() + + if ir_node.is_complex_ir and ir_node._id in seen: + raise CompilerPanic(f"bad code {ir_node}", ir_node.ast_source) + seen.add(ir_node._id) + + for arg in ir_node.args: + check_duplicated_nodes(arg, seen) + + @apply_line_numbers def compile_to_assembly(code, optimize=OptimizationLevel.GAS): global _revert_label _revert_label = mksymbol("revert") + check_duplicated_nodes(code) + # don't overwrite ir since the original might need to be output, e.g. `-f ir,asm` code = copy.deepcopy(code) _rewrite_return_sequences(code) diff --git a/vyper/ir/optimizer.py b/vyper/ir/optimizer.py index 7ff5390e4b..edcaec9fef 100644 --- a/vyper/ir/optimizer.py +++ b/vyper/ir/optimizer.py @@ -442,6 +442,7 @@ def _optimize(node: IRnode, parent: Optional[IRnode]) -> Tuple[bool, IRnode]: add_gas_estimate = node.add_gas_estimate is_self_call = node.is_self_call passthrough_metadata = node.passthrough_metadata + _id = node._id changed = False @@ -466,6 +467,7 @@ def finalize(val, args): add_gas_estimate=add_gas_estimate, is_self_call=is_self_call, passthrough_metadata=passthrough_metadata, + _id=_id, ) if should_check_symbols: