From 5d442a3156d9d2e6fa94fd0e2c897e5454a71c0d Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 9 Sep 2023 13:23:03 +0100 Subject: [PATCH 1/6] WIP very incomplete -- make nested functions faster --- mypyc/ir/ops.py | 3 +++ mypyc/irbuild/builder.py | 1 + mypyc/irbuild/env_class.py | 4 +++- mypyc/irbuild/expression.py | 1 + mypyc/irbuild/function.py | 10 ++++++++-- mypyc/test-data/irbuild-nested.test | 9 +++++++++ 6 files changed, 25 insertions(+), 3 deletions(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 04c50d1e2841..e3db0108f457 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -642,6 +642,9 @@ class GetAttr(RegisterOp): def __init__(self, obj: Value, attr: str, line: int, *, borrow: bool = False) -> None: super().__init__(line) + if attr == 'g': + print([6], obj) + assert False self.obj = obj self.attr = attr assert isinstance(obj.type, RInstance), "Attribute access not supported: %s" % obj.type diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 8c68f91bf633..b3658cb8ba99 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -1236,6 +1236,7 @@ def node_type(self, node: Expression) -> RType: def add_var_to_env_class( self, var: SymbolNode, rtype: RType, base: FuncInfo | ImplicitClass, reassign: bool = False ) -> AssignmentTarget: + print([1], 'add_var_to_env', var.fullname) # First, define the variable name as an attribute of the environment class, and then # construct a target for that attribute. self.fn_info.env_class.attributes[var.name] = rtype diff --git a/mypyc/irbuild/env_class.py b/mypyc/irbuild/env_class.py index ded8072deb63..174f2a0443f4 100644 --- a/mypyc/irbuild/env_class.py +++ b/mypyc/irbuild/env_class.py @@ -108,7 +108,9 @@ def load_env_registers(builder: IRBuilder) -> None: # If this is a FuncDef, then make sure to load the FuncDef into its own environment # class so that the function can be called recursively. if isinstance(fitem, FuncDef): - setup_func_for_recursive_call(builder, fitem, fn_info.callable_class) + # XXX DON'T PLEASE + if 0: + setup_func_for_recursive_call(builder, fitem, fn_info.callable_class) def load_outer_env( diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 8d205b432d2d..c349a6807a57 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -179,6 +179,7 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: ) return obj else: + print([4]) return builder.read(builder.get_assignment_target(expr, for_read=True), expr.line) return builder.load_global(expr) diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index 822350ea829b..26d8379e0540 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -292,6 +292,7 @@ def c() -> None: elif builder.fn_info.is_nested or builder.fn_info.in_non_ext: env_for_func = builder.fn_info.callable_class + print(builder.free_variables) if builder.fn_info.fitem in builder.free_variables: # Sort the variables to keep things deterministic for var in sorted(builder.free_variables[builder.fn_info.fitem], key=lambda x: x.name): @@ -307,10 +308,12 @@ def c() -> None: # the same name and signature across conditional blocks # will generate different callable classes, so the callable # class that gets instantiated must be generic. + print('here', repr(nested_fn)) builder.add_var_to_env_class( nested_fn, object_rprimitive, env_for_func, reassign=False ) + print([111], fitem.body) builder.accept(fitem.body) builder.maybe_add_implicit_return() @@ -765,11 +768,14 @@ def get_func_target(builder: IRBuilder, fdef: FuncDef) -> AssignmentTarget: and add it to the current environment. """ if fdef.original_def: + print([2]) # Get the target associated with the previously defined FuncDef. return builder.lookup(fdef.original_def) - if builder.fn_info.is_generator or builder.fn_info.contains_nested: - return builder.lookup(fdef) + if 0: + if builder.fn_info.is_generator or builder.fn_info.contains_nested: + print([3]) + return builder.lookup(fdef) return builder.add_local_reg(fdef, object_rprimitive) diff --git a/mypyc/test-data/irbuild-nested.test b/mypyc/test-data/irbuild-nested.test index adef80263533..eb7c7b52514c 100644 --- a/mypyc/test-data/irbuild-nested.test +++ b/mypyc/test-data/irbuild-nested.test @@ -859,3 +859,12 @@ L2: r2 = baz(r1) r3 = CPyTagged_Add(n, r2) return r3 + +[case testXXX] +def f() -> None: + x = 0 + y = 1 + def g() -> None: + y + g() +[out] From df56d2327f7787c23a14546d13850a63071d6393 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 16 Sep 2023 12:20:45 +0100 Subject: [PATCH 2/6] Implement properly --- mypyc/ir/ops.py | 3 --- mypyc/irbuild/builder.py | 6 ++++- mypyc/irbuild/context.py | 2 ++ mypyc/irbuild/env_class.py | 6 ++--- mypyc/irbuild/expression.py | 1 - mypyc/irbuild/function.py | 45 +++++++++++++++++++++++-------------- 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index e3db0108f457..04c50d1e2841 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -642,9 +642,6 @@ class GetAttr(RegisterOp): def __init__(self, obj: Value, attr: str, line: int, *, borrow: bool = False) -> None: super().__init__(line) - if attr == 'g': - print([6], obj) - assert False self.obj = obj self.attr = attr assert isinstance(obj.type, RInstance), "Attribute access not supported: %s" % obj.type diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index b3658cb8ba99..0757415f6753 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -502,6 +502,11 @@ def non_function_scope(self) -> bool: # Currently the stack always has at least two items: dummy and top-level. return len(self.fn_infos) <= 2 + def top_level_fn_info(self) -> FuncInfo | None: + if self.non_function_scope(): + return None + return self.fn_infos[2] + def init_final_static( self, lvalue: Lvalue, @@ -1236,7 +1241,6 @@ def node_type(self, node: Expression) -> RType: def add_var_to_env_class( self, var: SymbolNode, rtype: RType, base: FuncInfo | ImplicitClass, reassign: bool = False ) -> AssignmentTarget: - print([1], 'add_var_to_env', var.fullname) # First, define the variable name as an attribute of the environment class, and then # construct a target for that attribute. self.fn_info.env_class.attributes[var.name] = rtype diff --git a/mypyc/irbuild/context.py b/mypyc/irbuild/context.py index 676afb507504..5d702673ef67 100644 --- a/mypyc/irbuild/context.py +++ b/mypyc/irbuild/context.py @@ -22,6 +22,7 @@ def __init__( contains_nested: bool = False, is_decorated: bool = False, in_non_ext: bool = False, + any_free_nested_func: bool = False, ) -> None: self.fitem = fitem self.name = name @@ -47,6 +48,7 @@ def __init__( self.contains_nested = contains_nested self.is_decorated = is_decorated self.in_non_ext = in_non_ext + self.any_free_nested_func = any_free_nested_func # TODO: add field for ret_type: RType = none_rprimitive diff --git a/mypyc/irbuild/env_class.py b/mypyc/irbuild/env_class.py index 174f2a0443f4..0f608e5c7a3d 100644 --- a/mypyc/irbuild/env_class.py +++ b/mypyc/irbuild/env_class.py @@ -107,10 +107,8 @@ def load_env_registers(builder: IRBuilder) -> None: load_outer_envs(builder, fn_info.callable_class) # If this is a FuncDef, then make sure to load the FuncDef into its own environment # class so that the function can be called recursively. - if isinstance(fitem, FuncDef): - # XXX DON'T PLEASE - if 0: - setup_func_for_recursive_call(builder, fitem, fn_info.callable_class) + if isinstance(fitem, FuncDef) and fn_info.any_free_nested_func: + setup_func_for_recursive_call(builder, fitem, fn_info.callable_class) def load_outer_env( diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index c349a6807a57..8d205b432d2d 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -179,7 +179,6 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: ) return obj else: - print([4]) return builder.read(builder.get_assignment_target(expr, for_read=True), expr.line) return builder.load_global(expr) diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index 26d8379e0540..1849a3aff256 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -222,6 +222,7 @@ def c() -> None: is_decorated = fitem in builder.fdefs_to_decorators is_singledispatch = fitem in builder.singledispatch_impls in_non_ext = False + any_free_nested_func = has_free_nested_func(builder, fitem) class_name = None if cdef: ir = builder.mapper.type_to_ir[cdef.info] @@ -234,14 +235,15 @@ def c() -> None: func_name = name builder.enter( FuncInfo( - fitem, - func_name, - class_name, - gen_func_ns(builder), - is_nested, - contains_nested, - is_decorated, - in_non_ext, + fitem=fitem, + name=func_name, + class_name=class_name, + namespace=gen_func_ns(builder), + is_nested=is_nested, + contains_nested=contains_nested, + is_decorated=is_decorated, + in_non_ext=in_non_ext, + any_free_nested_func=any_free_nested_func, ) ) @@ -267,7 +269,9 @@ def c() -> None: builder.enter(fn_info) setup_env_for_generator_class(builder) load_outer_envs(builder, builder.fn_info.generator_class) - if builder.fn_info.is_nested and isinstance(fitem, FuncDef): + top_level = builder.top_level_fn_info() + if (builder.fn_info.is_nested and isinstance(fitem, FuncDef) and top_level + and top_level.any_free_nested_func): setup_func_for_recursive_call(builder, fitem, builder.fn_info.generator_class) create_switch_for_generator_class(builder) add_raise_exception_blocks_to_generator_class(builder, fitem.line) @@ -292,7 +296,6 @@ def c() -> None: elif builder.fn_info.is_nested or builder.fn_info.in_non_ext: env_for_func = builder.fn_info.callable_class - print(builder.free_variables) if builder.fn_info.fitem in builder.free_variables: # Sort the variables to keep things deterministic for var in sorted(builder.free_variables[builder.fn_info.fitem], key=lambda x: x.name): @@ -308,12 +311,10 @@ def c() -> None: # the same name and signature across conditional blocks # will generate different callable classes, so the callable # class that gets instantiated must be generic. - print('here', repr(nested_fn)) builder.add_var_to_env_class( nested_fn, object_rprimitive, env_for_func, reassign=False ) - print([111], fitem.body) builder.accept(fitem.body) builder.maybe_add_implicit_return() @@ -347,6 +348,19 @@ def c() -> None: return func_ir, func_reg +def has_free_nested_func(builder: IRBuilder, fitem: FuncItem) -> bool: + """Does function have a free nested function? + + If a nested function is called recursively, it's free. Typically + it's not, and we don't need to store it in the environment. + """ + if any(isinstance(sym, FuncItem) + for sym in builder.free_variables.get(fitem, set())): + return True + return any(has_free_nested_func(builder, nested) + for nested in builder.encapsulating_funcs.get(fitem, [])) + + def gen_func_ir( builder: IRBuilder, args: list[Register], @@ -768,14 +782,11 @@ def get_func_target(builder: IRBuilder, fdef: FuncDef) -> AssignmentTarget: and add it to the current environment. """ if fdef.original_def: - print([2]) # Get the target associated with the previously defined FuncDef. return builder.lookup(fdef.original_def) - if 0: - if builder.fn_info.is_generator or builder.fn_info.contains_nested: - print([3]) - return builder.lookup(fdef) + if builder.fn_info.is_generator or builder.fn_info.any_free_nested_func: + return builder.lookup(fdef) return builder.add_local_reg(fdef, object_rprimitive) From 6d02c09e685212bed78694bfec5cae43cb950248 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 16 Sep 2023 12:29:50 +0100 Subject: [PATCH 3/6] Update tests --- mypyc/test-data/irbuild-basic.test | 219 ++++++++-------- mypyc/test-data/irbuild-nested.test | 389 ++++++++++++---------------- 2 files changed, 264 insertions(+), 344 deletions(-) diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 33fc8cfaa83b..bf608abb87ad 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -2694,47 +2694,43 @@ L2: def g_a_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.g_a_obj r0 :: __main__.a_env - r1, g :: object - r2 :: str - r3 :: object - r4 :: str - r5, r6, r7, r8 :: object - r9 :: str - r10 :: object - r11 :: str - r12, r13 :: object + r1 :: str + r2 :: object + r3 :: str + r4, r5, r6, r7 :: object + r8 :: str + r9 :: object + r10 :: str + r11, r12 :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.g - g = r1 - r2 = 'Entering' - r3 = builtins :: module - r4 = 'print' - r5 = CPyObject_GetAttr(r3, r4) - r6 = PyObject_CallFunctionObjArgs(r5, r2, 0) - r7 = r0.f - r8 = PyObject_CallFunctionObjArgs(r7, 0) - r9 = 'Exited' - r10 = builtins :: module - r11 = 'print' - r12 = CPyObject_GetAttr(r10, r11) - r13 = PyObject_CallFunctionObjArgs(r12, r9, 0) + r1 = 'Entering' + r2 = builtins :: module + r3 = 'print' + r4 = CPyObject_GetAttr(r2, r3) + r5 = PyObject_CallFunctionObjArgs(r4, r1, 0) + r6 = r0.f + r7 = PyObject_CallFunctionObjArgs(r6, 0) + r8 = 'Exited' + r9 = builtins :: module + r10 = 'print' + r11 = CPyObject_GetAttr(r9, r10) + r12 = PyObject_CallFunctionObjArgs(r11, r8, 0) return 1 def a(f): f :: object r0 :: __main__.a_env r1 :: bool r2 :: __main__.g_a_obj - r3, r4 :: bool - r5 :: object + r3 :: bool + g :: object L0: r0 = a_env() r0.f = f; r1 = is_error r2 = g_a_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.g = r2; r4 = is_error - r5 = r0.g - return r5 + g = r2 + return g def g_b_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -2751,47 +2747,43 @@ L2: def g_b_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.g_b_obj r0 :: __main__.b_env - r1, g :: object - r2 :: str - r3 :: object - r4 :: str - r5, r6, r7, r8 :: object - r9 :: str - r10 :: object - r11 :: str - r12, r13 :: object + r1 :: str + r2 :: object + r3 :: str + r4, r5, r6, r7 :: object + r8 :: str + r9 :: object + r10 :: str + r11, r12 :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.g - g = r1 - r2 = '---' - r3 = builtins :: module - r4 = 'print' - r5 = CPyObject_GetAttr(r3, r4) - r6 = PyObject_CallFunctionObjArgs(r5, r2, 0) - r7 = r0.f - r8 = PyObject_CallFunctionObjArgs(r7, 0) - r9 = '---' - r10 = builtins :: module - r11 = 'print' - r12 = CPyObject_GetAttr(r10, r11) - r13 = PyObject_CallFunctionObjArgs(r12, r9, 0) + r1 = '---' + r2 = builtins :: module + r3 = 'print' + r4 = CPyObject_GetAttr(r2, r3) + r5 = PyObject_CallFunctionObjArgs(r4, r1, 0) + r6 = r0.f + r7 = PyObject_CallFunctionObjArgs(r6, 0) + r8 = '---' + r9 = builtins :: module + r10 = 'print' + r11 = CPyObject_GetAttr(r9, r10) + r12 = PyObject_CallFunctionObjArgs(r11, r8, 0) return 1 def b(f): f :: object r0 :: __main__.b_env r1 :: bool r2 :: __main__.g_b_obj - r3, r4 :: bool - r5 :: object + r3 :: bool + g :: object L0: r0 = b_env() r0.f = f; r1 = is_error r2 = g_b_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.g = r2; r4 = is_error - r5 = r0.g - return r5 + g = r2 + return g def d_c_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -2808,20 +2800,17 @@ L2: def d_c_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.d_c_obj r0 :: __main__.c_env - r1, d :: object - r2 :: str - r3 :: object - r4 :: str - r5, r6 :: object + r1 :: str + r2 :: object + r3 :: str + r4, r5 :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.d - d = r1 - r2 = 'd' - r3 = builtins :: module - r4 = 'print' - r5 = CPyObject_GetAttr(r3, r4) - r6 = PyObject_CallFunctionObjArgs(r5, r2, 0) + r1 = 'd' + r2 = builtins :: module + r3 = 'print' + r4 = CPyObject_GetAttr(r2, r3) + r5 = PyObject_CallFunctionObjArgs(r4, r1, 0) return 1 def c(): r0 :: __main__.c_env @@ -2832,16 +2821,15 @@ def c(): r5, r6 :: object r7 :: dict r8 :: str - r9, r10 :: object - r11 :: bool - r12 :: dict - r13 :: str - r14 :: i32 - r15 :: bit - r16 :: str - r17 :: object - r18 :: str - r19, r20, r21, r22 :: object + r9, r10, d :: object + r11 :: dict + r12 :: str + r13 :: i32 + r14 :: bit + r15 :: str + r16 :: object + r17 :: str + r18, r19, r20 :: object L0: r0 = c_env() r1 = d_c_obj() @@ -2854,18 +2842,17 @@ L0: r8 = 'a' r9 = CPyDict_GetItem(r7, r8) r10 = PyObject_CallFunctionObjArgs(r9, r6, 0) - r0.d = r10; r11 = is_error - r12 = __main__.globals :: static - r13 = 'd' - r14 = CPyDict_SetItem(r12, r13, r10) - r15 = r14 >= 0 :: signed - r16 = 'c' - r17 = builtins :: module - r18 = 'print' - r19 = CPyObject_GetAttr(r17, r18) - r20 = PyObject_CallFunctionObjArgs(r19, r16, 0) - r21 = r0.d - r22 = PyObject_CallFunctionObjArgs(r21, 0) + d = r10 + r11 = __main__.globals :: static + r12 = 'd' + r13 = CPyDict_SetItem(r11, r12, r10) + r14 = r13 >= 0 :: signed + r15 = 'c' + r16 = builtins :: module + r17 = 'print' + r18 = CPyObject_GetAttr(r16, r17) + r19 = PyObject_CallFunctionObjArgs(r18, r15, 0) + r20 = PyObject_CallFunctionObjArgs(d, 0) return 1 def __top_level__(): r0, r1 :: object @@ -2947,47 +2934,43 @@ L2: def g_a_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.g_a_obj r0 :: __main__.a_env - r1, g :: object - r2 :: str - r3 :: object - r4 :: str - r5, r6, r7, r8 :: object - r9 :: str - r10 :: object - r11 :: str - r12, r13 :: object + r1 :: str + r2 :: object + r3 :: str + r4, r5, r6, r7 :: object + r8 :: str + r9 :: object + r10 :: str + r11, r12 :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.g - g = r1 - r2 = 'Entering' - r3 = builtins :: module - r4 = 'print' - r5 = CPyObject_GetAttr(r3, r4) - r6 = PyObject_CallFunctionObjArgs(r5, r2, 0) - r7 = r0.f - r8 = PyObject_CallFunctionObjArgs(r7, 0) - r9 = 'Exited' - r10 = builtins :: module - r11 = 'print' - r12 = CPyObject_GetAttr(r10, r11) - r13 = PyObject_CallFunctionObjArgs(r12, r9, 0) + r1 = 'Entering' + r2 = builtins :: module + r3 = 'print' + r4 = CPyObject_GetAttr(r2, r3) + r5 = PyObject_CallFunctionObjArgs(r4, r1, 0) + r6 = r0.f + r7 = PyObject_CallFunctionObjArgs(r6, 0) + r8 = 'Exited' + r9 = builtins :: module + r10 = 'print' + r11 = CPyObject_GetAttr(r9, r10) + r12 = PyObject_CallFunctionObjArgs(r11, r8, 0) return 1 def a(f): f :: object r0 :: __main__.a_env r1 :: bool r2 :: __main__.g_a_obj - r3, r4 :: bool - r5 :: object + r3 :: bool + g :: object L0: r0 = a_env() r0.f = f; r1 = is_error r2 = g_a_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.g = r2; r4 = is_error - r5 = r0.g - return r5 + g = r2 + return g def __top_level__(): r0, r1 :: object r2 :: bit diff --git a/mypyc/test-data/irbuild-nested.test b/mypyc/test-data/irbuild-nested.test index eb7c7b52514c..b2b884705366 100644 --- a/mypyc/test-data/irbuild-nested.test +++ b/mypyc/test-data/irbuild-nested.test @@ -50,25 +50,22 @@ L2: def inner_a_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_a_obj r0 :: __main__.a_env - r1, inner, r2 :: object + r1 :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = box(None, 1) - return r2 + r1 = box(None, 1) + return r1 def a(): r0 :: __main__.a_env r1 :: __main__.inner_a_obj - r2, r3 :: bool - r4 :: object + r2 :: bool + inner :: object L0: r0 = a_env() r1 = inner_a_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.inner = r1; r3 = is_error - r4 = r0.inner - return r4 + inner = r1 + return inner def second_b_first_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -86,15 +83,12 @@ def second_b_first_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.second_b_first_obj r0 :: __main__.first_b_env r1 :: __main__.b_env - r2, second :: object - r3 :: str + r2 :: str L0: r0 = __mypyc_self__.__mypyc_env__ r1 = r0.__mypyc_env__ - r2 = r0.second - second = r2 - r3 = 'b.first.second: nested function' - return r3 + r2 = 'b.first.second: nested function' + return r2 def first_b_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -111,35 +105,30 @@ L2: def first_b_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.first_b_obj r0 :: __main__.b_env - r1, first :: object - r2 :: __main__.first_b_env - r3 :: bool - r4 :: __main__.second_b_first_obj - r5, r6 :: bool - r7 :: object + r1 :: __main__.first_b_env + r2 :: bool + r3 :: __main__.second_b_first_obj + r4 :: bool + second :: object L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.first - first = r1 - r2 = first_b_env() - r2.__mypyc_env__ = r0; r3 = is_error - r4 = second_b_first_obj() - r4.__mypyc_env__ = r2; r5 = is_error - r2.second = r4; r6 = is_error - r7 = r2.second - return r7 + r1 = first_b_env() + r1.__mypyc_env__ = r0; r2 = is_error + r3 = second_b_first_obj() + r3.__mypyc_env__ = r1; r4 = is_error + second = r3 + return second def b(): r0 :: __main__.b_env r1 :: __main__.first_b_obj - r2, r3 :: bool - r4 :: object + r2 :: bool + first :: object L0: r0 = b_env() r1 = first_b_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.first = r1; r3 = is_error - r4 = r0.first - return r4 + first = r1 + return first def inner_c_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -157,28 +146,24 @@ def inner_c_obj.__call__(__mypyc_self__, s): __mypyc_self__ :: __main__.inner_c_obj s :: str r0 :: __main__.c_env - r1, inner :: object - r2, r3 :: str + r1, r2 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = '!' - r3 = PyUnicode_Concat(s, r2) - return r3 + r1 = '!' + r2 = PyUnicode_Concat(s, r1) + return r2 def c(num): num :: float r0 :: __main__.c_env r1 :: __main__.inner_c_obj - r2, r3 :: bool - r4 :: object + r2 :: bool + inner :: object L0: r0 = c_env() r1 = inner_c_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.inner = r1; r3 = is_error - r4 = r0.inner - return r4 + inner = r1 + return inner def inner_d_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -196,40 +181,36 @@ def inner_d_obj.__call__(__mypyc_self__, s): __mypyc_self__ :: __main__.inner_d_obj s :: str r0 :: __main__.d_env - r1, inner :: object - r2, r3 :: str + r1, r2 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = '?' - r3 = PyUnicode_Concat(s, r2) - return r3 + r1 = '?' + r2 = PyUnicode_Concat(s, r1) + return r2 def d(num): num :: float r0 :: __main__.d_env r1 :: __main__.inner_d_obj - r2, r3 :: bool - r4 :: str - r5, r6 :: object - r7, a, r8 :: str - r9, r10 :: object - r11, b :: str + r2 :: bool + inner :: object + r3 :: str + r4 :: object + r5, a, r6 :: str + r7 :: object + r8, b :: str L0: r0 = d_env() r1 = inner_d_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.inner = r1; r3 = is_error - r4 = 'one' - r5 = r0.inner - r6 = PyObject_CallFunctionObjArgs(r5, r4, 0) - r7 = cast(str, r6) - a = r7 - r8 = 'two' - r9 = r0.inner - r10 = PyObject_CallFunctionObjArgs(r9, r8, 0) - r11 = cast(str, r10) - b = r11 + inner = r1 + r3 = 'one' + r4 = PyObject_CallFunctionObjArgs(inner, r3, 0) + r5 = cast(str, r4) + a = r5 + r6 = 'two' + r7 = PyObject_CallFunctionObjArgs(inner, r6, 0) + r8 = cast(str, r7) + b = r8 return a def inner(): r0 :: str @@ -290,32 +271,28 @@ L2: def inner_a_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_a_obj r0 :: __main__.a_env - r1, inner :: object - r2 :: int + r1 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = r0.num - return r2 + r1 = r0.num + return r1 def a(num): num :: int r0 :: __main__.a_env r1 :: bool r2 :: __main__.inner_a_obj - r3, r4 :: bool - r5, r6 :: object - r7 :: int + r3 :: bool + inner, r4 :: object + r5 :: int L0: r0 = a_env() r0.num = num; r1 = is_error r2 = inner_a_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.inner = r2; r4 = is_error - r5 = r0.inner - r6 = PyObject_CallFunctionObjArgs(r5, 0) - r7 = unbox(int, r6) - return r7 + inner = r2 + r4 = PyObject_CallFunctionObjArgs(inner, 0) + r5 = unbox(int, r4) + return r5 def inner_b_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -332,36 +309,32 @@ L2: def inner_b_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_b_obj r0 :: __main__.b_env - r1, inner :: object - r2 :: bool - foo, r3 :: int + r1 :: bool + foo, r2 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r0.num = 8; r2 = is_error + r0.num = 8; r1 = is_error foo = 12 - r3 = r0.num - return r3 + r2 = r0.num + return r2 def b(): r0 :: __main__.b_env r1 :: bool r2 :: __main__.inner_b_obj - r3, r4 :: bool - r5, r6 :: object - r7, r8, r9 :: int + r3 :: bool + inner, r4 :: object + r5, r6, r7 :: int L0: r0 = b_env() r0.num = 6; r1 = is_error r2 = inner_b_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.inner = r2; r4 = is_error - r5 = r0.inner - r6 = PyObject_CallFunctionObjArgs(r5, 0) - r7 = unbox(int, r6) - r8 = r0.num - r9 = CPyTagged_Add(r7, r8) - return r9 + inner = r2 + r4 = PyObject_CallFunctionObjArgs(inner, 0) + r5 = unbox(int, r4) + r6 = r0.num + r7 = CPyTagged_Add(r5, r6) + return r7 def inner_c_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -378,14 +351,11 @@ L2: def inner_c_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_c_obj r0 :: __main__.c_env - r1, inner :: object - r2 :: str + r1 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = 'f.inner: first definition' - return r2 + r1 = 'f.inner: first definition' + return r1 def inner_c_obj_0.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -402,40 +372,37 @@ L2: def inner_c_obj_0.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_c_obj_0 r0 :: __main__.c_env - r1, inner :: object - r2 :: str + r1 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = 'f.inner: second definition' - return r2 + r1 = 'f.inner: second definition' + return r1 def c(flag): flag :: bool r0 :: __main__.c_env r1 :: __main__.inner_c_obj - r2, r3 :: bool - r4 :: __main__.inner_c_obj_0 - r5, r6 :: bool - r7, r8 :: object - r9 :: str + r2 :: bool + inner :: object + r3 :: __main__.inner_c_obj_0 + r4 :: bool + r5 :: object + r6 :: str L0: r0 = c_env() if flag goto L1 else goto L2 :: bool L1: r1 = inner_c_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.inner = r1; r3 = is_error + inner = r1 goto L3 L2: - r4 = inner_c_obj_0() - r4.__mypyc_env__ = r0; r5 = is_error - r0.inner = r4; r6 = is_error + r3 = inner_c_obj_0() + r3.__mypyc_env__ = r0; r4 = is_error + inner = r3 L3: - r7 = r0.inner - r8 = PyObject_CallFunctionObjArgs(r7, 0) - r9 = cast(str, r8) - return r9 + r5 = PyObject_CallFunctionObjArgs(inner, 0) + r6 = cast(str, r5) + return r6 [case testSpecialNested] def a() -> int: @@ -465,15 +432,12 @@ def c_a_b_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.c_a_b_obj r0 :: __main__.b_a_env r1 :: __main__.a_env - r2, c :: object - r3 :: int + r2 :: int L0: r0 = __mypyc_self__.__mypyc_env__ r1 = r0.__mypyc_env__ - r2 = r0.c - c = r2 - r3 = r1.x - return r3 + r2 = r1.x + return r2 def b_a_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -490,48 +454,43 @@ L2: def b_a_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.b_a_obj r0 :: __main__.a_env - r1, b :: object - r2 :: __main__.b_a_env - r3 :: bool - r4, r5 :: int - r6 :: bool - r7 :: __main__.c_a_b_obj - r8, r9 :: bool - r10, r11 :: object - r12 :: int + r1 :: __main__.b_a_env + r2 :: bool + r3, r4 :: int + r5 :: bool + r6 :: __main__.c_a_b_obj + r7 :: bool + c, r8 :: object + r9 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.b - b = r1 - r2 = b_a_env() - r2.__mypyc_env__ = r0; r3 = is_error - r4 = r0.x - r5 = CPyTagged_Add(r4, 2) - r0.x = r5; r6 = is_error - r7 = c_a_b_obj() - r7.__mypyc_env__ = r2; r8 = is_error - r2.c = r7; r9 = is_error - r10 = r2.c - r11 = PyObject_CallFunctionObjArgs(r10, 0) - r12 = unbox(int, r11) - return r12 + r1 = b_a_env() + r1.__mypyc_env__ = r0; r2 = is_error + r3 = r0.x + r4 = CPyTagged_Add(r3, 2) + r0.x = r4; r5 = is_error + r6 = c_a_b_obj() + r6.__mypyc_env__ = r1; r7 = is_error + c = r6 + r8 = PyObject_CallFunctionObjArgs(c, 0) + r9 = unbox(int, r8) + return r9 def a(): r0 :: __main__.a_env r1 :: bool r2 :: __main__.b_a_obj - r3, r4 :: bool - r5, r6 :: object - r7 :: int + r3 :: bool + b, r4 :: object + r5 :: int L0: r0 = a_env() r0.x = 2; r1 = is_error r2 = b_a_obj() r2.__mypyc_env__ = r0; r3 = is_error - r0.b = r2; r4 = is_error - r5 = r0.b - r6 = PyObject_CallFunctionObjArgs(r5, 0) - r7 = unbox(int, r6) - return r7 + b = r2 + r4 = PyObject_CallFunctionObjArgs(b, 0) + r5 = unbox(int, r4) + return r5 [case testNestedFunctionInsideStatements] def f(flag: bool) -> str: @@ -559,14 +518,11 @@ L2: def inner_f_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_f_obj r0 :: __main__.f_env - r1, inner :: object - r2 :: str + r1 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = 'f.inner: first definition' - return r2 + r1 = 'f.inner: first definition' + return r1 def inner_f_obj_0.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -583,40 +539,37 @@ L2: def inner_f_obj_0.__call__(__mypyc_self__): __mypyc_self__ :: __main__.inner_f_obj_0 r0 :: __main__.f_env - r1, inner :: object - r2 :: str + r1 :: str L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.inner - inner = r1 - r2 = 'f.inner: second definition' - return r2 + r1 = 'f.inner: second definition' + return r1 def f(flag): flag :: bool r0 :: __main__.f_env r1 :: __main__.inner_f_obj - r2, r3 :: bool - r4 :: __main__.inner_f_obj_0 - r5, r6 :: bool - r7, r8 :: object - r9 :: str + r2 :: bool + inner :: object + r3 :: __main__.inner_f_obj_0 + r4 :: bool + r5 :: object + r6 :: str L0: r0 = f_env() if flag goto L1 else goto L2 :: bool L1: r1 = inner_f_obj() r1.__mypyc_env__ = r0; r2 = is_error - r0.inner = r1; r3 = is_error + inner = r1 goto L3 L2: - r4 = inner_f_obj_0() - r4.__mypyc_env__ = r0; r5 = is_error - r0.inner = r4; r6 = is_error + r3 = inner_f_obj_0() + r3.__mypyc_env__ = r0; r4 = is_error + inner = r3 L3: - r7 = r0.inner - r8 = PyObject_CallFunctionObjArgs(r7, 0) - r9 = cast(str, r8) - return r9 + r5 = PyObject_CallFunctionObjArgs(inner, 0) + r6 = cast(str, r5) + return r6 [case testNestedFunctionsCallEachOther] from typing import Callable, List @@ -652,15 +605,12 @@ L2: def foo_f_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.foo_f_obj r0 :: __main__.f_env - r1, foo :: object - r2, r3 :: int + r1, r2 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.foo - foo = r1 - r2 = r0.a - r3 = CPyTagged_Add(r2, 2) - return r3 + r1 = r0.a + r2 = CPyTagged_Add(r1, 2) + return r2 def bar_f_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -677,16 +627,14 @@ L2: def bar_f_obj.__call__(__mypyc_self__): __mypyc_self__ :: __main__.bar_f_obj r0 :: __main__.f_env - r1, bar, r2, r3 :: object - r4 :: int + r1, r2 :: object + r3 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.bar - bar = r1 - r2 = r0.foo - r3 = PyObject_CallFunctionObjArgs(r2, 0) - r4 = unbox(int, r3) - return r4 + r1 = r0.foo + r2 = PyObject_CallFunctionObjArgs(r1, 0) + r3 = unbox(int, r2) + return r3 def baz_f_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -704,23 +652,21 @@ def baz_f_obj.__call__(__mypyc_self__, n): __mypyc_self__ :: __main__.baz_f_obj n :: int r0 :: __main__.f_env - r1, baz :: object - r2 :: bit - r3 :: int - r4, r5 :: object + r1 :: bit + r2 :: int + r3, r4, r5 :: object r6, r7 :: int L0: r0 = __mypyc_self__.__mypyc_env__ - r1 = r0.baz - baz = r1 - r2 = n == 0 - if r2 goto L1 else goto L2 :: bool + r1 = n == 0 + if r1 goto L1 else goto L2 :: bool L1: return 0 L2: - r3 = CPyTagged_Subtract(n, 2) - r4 = box(int, r3) - r5 = PyObject_CallFunctionObjArgs(baz, r4, 0) + r2 = CPyTagged_Subtract(n, 2) + r3 = r0.baz + r4 = box(int, r2) + r5 = PyObject_CallFunctionObjArgs(r3, r4, 0) r6 = unbox(int, r5) r7 = CPyTagged_Add(n, r6) return r7 @@ -859,12 +805,3 @@ L2: r2 = baz(r1) r3 = CPyTagged_Add(n, r2) return r3 - -[case testXXX] -def f() -> None: - x = 0 - y = 1 - def g() -> None: - y - g() -[out] From 9e9670b19aff33597510bfd077eeef7026f070cd Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 16 Sep 2023 12:34:52 +0100 Subject: [PATCH 4/6] Black --- mypyc/irbuild/function.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index 1849a3aff256..3cfa1bafd8a3 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -270,8 +270,12 @@ def c() -> None: setup_env_for_generator_class(builder) load_outer_envs(builder, builder.fn_info.generator_class) top_level = builder.top_level_fn_info() - if (builder.fn_info.is_nested and isinstance(fitem, FuncDef) and top_level - and top_level.any_free_nested_func): + if ( + builder.fn_info.is_nested + and isinstance(fitem, FuncDef) + and top_level + and top_level.any_free_nested_func + ): setup_func_for_recursive_call(builder, fitem, builder.fn_info.generator_class) create_switch_for_generator_class(builder) add_raise_exception_blocks_to_generator_class(builder, fitem.line) @@ -354,11 +358,12 @@ def has_free_nested_func(builder: IRBuilder, fitem: FuncItem) -> bool: If a nested function is called recursively, it's free. Typically it's not, and we don't need to store it in the environment. """ - if any(isinstance(sym, FuncItem) - for sym in builder.free_variables.get(fitem, set())): + if any(isinstance(sym, FuncItem) for sym in builder.free_variables.get(fitem, set())): return True - return any(has_free_nested_func(builder, nested) - for nested in builder.encapsulating_funcs.get(fitem, [])) + return any( + has_free_nested_func(builder, nested) + for nested in builder.encapsulating_funcs.get(fitem, []) + ) def gen_func_ir( From d06c7f10ebf6c807cd4864196723df7db1be0a54 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 15 Oct 2023 10:44:55 +0100 Subject: [PATCH 5/6] Refactor --- mypyc/irbuild/context.py | 4 ++-- mypyc/irbuild/env_class.py | 2 +- mypyc/irbuild/function.py | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/mypyc/irbuild/context.py b/mypyc/irbuild/context.py index 5d702673ef67..a740f0b821d9 100644 --- a/mypyc/irbuild/context.py +++ b/mypyc/irbuild/context.py @@ -22,7 +22,7 @@ def __init__( contains_nested: bool = False, is_decorated: bool = False, in_non_ext: bool = False, - any_free_nested_func: bool = False, + add_nested_funcs_to_env: bool = False, ) -> None: self.fitem = fitem self.name = name @@ -48,7 +48,7 @@ def __init__( self.contains_nested = contains_nested self.is_decorated = is_decorated self.in_non_ext = in_non_ext - self.any_free_nested_func = any_free_nested_func + self.add_nested_funcs_to_env = add_nested_funcs_to_env # TODO: add field for ret_type: RType = none_rprimitive diff --git a/mypyc/irbuild/env_class.py b/mypyc/irbuild/env_class.py index 0f608e5c7a3d..aa223fe20176 100644 --- a/mypyc/irbuild/env_class.py +++ b/mypyc/irbuild/env_class.py @@ -107,7 +107,7 @@ def load_env_registers(builder: IRBuilder) -> None: load_outer_envs(builder, fn_info.callable_class) # If this is a FuncDef, then make sure to load the FuncDef into its own environment # class so that the function can be called recursively. - if isinstance(fitem, FuncDef) and fn_info.any_free_nested_func: + if isinstance(fitem, FuncDef) and fn_info.add_nested_funcs_to_env: setup_func_for_recursive_call(builder, fitem, fn_info.callable_class) diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index 3cfa1bafd8a3..1bc448fc4780 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -222,7 +222,7 @@ def c() -> None: is_decorated = fitem in builder.fdefs_to_decorators is_singledispatch = fitem in builder.singledispatch_impls in_non_ext = False - any_free_nested_func = has_free_nested_func(builder, fitem) + add_nested_funcs_to_env = has_nested_func_self_reference(builder, fitem) class_name = None if cdef: ir = builder.mapper.type_to_ir[cdef.info] @@ -243,7 +243,7 @@ def c() -> None: contains_nested=contains_nested, is_decorated=is_decorated, in_non_ext=in_non_ext, - any_free_nested_func=any_free_nested_func, + add_nested_funcs_to_env=add_nested_funcs_to_env, ) ) @@ -274,7 +274,7 @@ def c() -> None: builder.fn_info.is_nested and isinstance(fitem, FuncDef) and top_level - and top_level.any_free_nested_func + and top_level.add_nested_funcs_to_env ): setup_func_for_recursive_call(builder, fitem, builder.fn_info.generator_class) create_switch_for_generator_class(builder) @@ -352,16 +352,16 @@ def c() -> None: return func_ir, func_reg -def has_free_nested_func(builder: IRBuilder, fitem: FuncItem) -> bool: - """Does function have a free nested function? +def has_nested_func_self_reference(builder: IRBuilder, fitem: FuncItem) -> bool: + """Does a nested function contain a self-reference in its body? - If a nested function is called recursively, it's free. Typically - it's not, and we don't need to store it in the environment. + If a nested function only has references in the surrounding function, + we don't need to add it to the environment. """ if any(isinstance(sym, FuncItem) for sym in builder.free_variables.get(fitem, set())): return True return any( - has_free_nested_func(builder, nested) + has_nested_func_self_reference(builder, nested) for nested in builder.encapsulating_funcs.get(fitem, []) ) @@ -790,7 +790,7 @@ def get_func_target(builder: IRBuilder, fdef: FuncDef) -> AssignmentTarget: # Get the target associated with the previously defined FuncDef. return builder.lookup(fdef.original_def) - if builder.fn_info.is_generator or builder.fn_info.any_free_nested_func: + if builder.fn_info.is_generator or builder.fn_info.add_nested_funcs_to_env: return builder.lookup(fdef) return builder.add_local_reg(fdef, object_rprimitive) From ea57cf20784ab6bbaf0601efe89df6348b0da523 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 15 Oct 2023 10:54:51 +0100 Subject: [PATCH 6/6] Minor fix --- mypyc/irbuild/function.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index 1bc448fc4780..ebf7fa9a54de 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -19,6 +19,7 @@ ArgKind, ClassDef, Decorator, + FuncBase, FuncDef, FuncItem, LambdaExpr, @@ -358,7 +359,7 @@ def has_nested_func_self_reference(builder: IRBuilder, fitem: FuncItem) -> bool: If a nested function only has references in the surrounding function, we don't need to add it to the environment. """ - if any(isinstance(sym, FuncItem) for sym in builder.free_variables.get(fitem, set())): + if any(isinstance(sym, FuncBase) for sym in builder.free_variables.get(fitem, set())): return True return any( has_nested_func_self_reference(builder, nested)