diff --git a/mypyc/irbuild/callable_class.py b/mypyc/irbuild/callable_class.py index 9ca1318a843f..1b850f57d231 100644 --- a/mypyc/irbuild/callable_class.py +++ b/mypyc/irbuild/callable_class.py @@ -78,6 +78,9 @@ class for the nested function. # this is a toplevel lambda), don't set up an environment. if builder.fn_infos[-2].contains_nested: callable_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_infos[-2].env_class) + # The link is initialized before the callable is published and is never rebound. + # Treating it as Final permits plain loads on free-threaded builds. + callable_class_ir.final_attributes.add(ENV_ATTR_NAME) callable_class_ir.mro = [callable_class_ir] builder.fn_info.callable_class = ImplicitClass(callable_class_ir) builder.classes.append(callable_class_ir) @@ -235,7 +238,11 @@ def instantiate_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> Value: elif builder.fn_info.contains_nested: curr_env_reg = builder.fn_info.curr_env_reg if curr_env_reg: - builder.add(SetAttr(func_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line)) + set_env = SetAttr(func_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line) + # A new or freelist-reused callable has had all of its fields cleared, and this store + # happens before the callable can escape. + set_env.mark_as_initializer() + builder.add(set_env) # Initialize function wrapper for callable classes. As opposed to regular functions, # each instance of a callable class needs its own wrapper because they might be instantiated # inside other functions. diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 55bf15014916..113b7b826c27 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -2475,13 +2475,12 @@ def a(f): r0 :: __main__.a_env r1 :: bool r2 :: __main__.g_a_obj - 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 + r2.__mypyc_env__ = r0 g = r2 return g def g_b_obj.__get__(__mypyc_self__, instance, owner): @@ -2540,13 +2539,12 @@ def b(f): r0 :: __main__.b_env r1 :: bool r2 :: __main__.g_b_obj - 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 + r2.__mypyc_env__ = r0 g = r2 return g def d_c_obj.__get__(__mypyc_self__, instance, owner): @@ -2586,62 +2584,61 @@ L0: def c(): r0 :: __main__.c_env r1 :: __main__.d_c_obj - r2 :: bool - r3 :: dict - r4 :: str - r5 :: object - r6 :: object[1] - r7 :: object_ptr - r8 :: object - r9 :: dict - r10 :: str - r11 :: object - r12 :: object[1] - r13 :: object_ptr - r14, d :: object - r15 :: dict - r16 :: str - r17 :: i32 - r18 :: bit - r19 :: str - r20 :: object - r21 :: str - r22 :: object - r23 :: object[1] - r24 :: object_ptr - r25, r26 :: object + r2 :: dict + r3 :: str + r4 :: object + r5 :: object[1] + r6 :: object_ptr + r7 :: object + r8 :: dict + r9 :: str + r10 :: object + r11 :: object[1] + r12 :: object_ptr + r13, d :: object + r14 :: dict + r15 :: str + r16 :: i32 + r17 :: bit + r18 :: str + r19 :: object + r20 :: str + r21 :: object + r22 :: object[1] + r23 :: object_ptr + r24, r25 :: object L0: r0 = c_env() r1 = d_c_obj() - r1.__mypyc_env__ = r0; r2 = is_error - r3 = __main__.globals :: static - r4 = 'b' - r5 = CPyDict_GetItem(r3, r4) - r6 = [r1] - r7 = load_address r6 - r8 = PyObject_Vectorcall(r5, r7, 1, 0) + r1.__mypyc_env__ = r0 + r2 = __main__.globals :: static + r3 = 'b' + r4 = CPyDict_GetItem(r2, r3) + r5 = [r1] + r6 = load_address r5 + r7 = PyObject_Vectorcall(r4, r6, 1, 0) keep_alive r1 - r9 = __main__.globals :: static - r10 = 'a' - r11 = CPyDict_GetItem(r9, r10) - r12 = [r8] - r13 = load_address r12 - r14 = PyObject_Vectorcall(r11, r13, 1, 0) - keep_alive r8 - d = r14 - r15 = __main__.globals :: static - r16 = 'd' - r17 = PyDict_SetItem(r15, r16, r14) - r18 = r17 >= 0 :: signed - r19 = 'c' - r20 = builtins :: module - r21 = 'print' - r22 = CPyObject_GetAttr(r20, r21) - r23 = [r19] - r24 = load_address r23 - r25 = PyObject_Vectorcall(r22, r24, 1, 0) - keep_alive r19 - r26 = PyObject_Vectorcall(d, 0, 0, 0) + r8 = __main__.globals :: static + r9 = 'a' + r10 = CPyDict_GetItem(r8, r9) + r11 = [r7] + r12 = load_address r11 + r13 = PyObject_Vectorcall(r10, r12, 1, 0) + keep_alive r7 + d = r13 + r14 = __main__.globals :: static + r15 = 'd' + r16 = PyDict_SetItem(r14, r15, r13) + r17 = r16 >= 0 :: signed + r18 = 'c' + r19 = builtins :: module + r20 = 'print' + r21 = CPyObject_GetAttr(r19, r20) + r22 = [r18] + r23 = load_address r22 + r24 = PyObject_Vectorcall(r21, r23, 1, 0) + keep_alive r18 + r25 = PyObject_Vectorcall(d, 0, 0, 0) return 1 def __top_level__(): r0, r1 :: object @@ -2775,13 +2772,12 @@ def a(f): r0 :: __main__.a_env r1 :: bool r2 :: __main__.g_a_obj - 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 + r2.__mypyc_env__ = r0 g = r2 return g def __top_level__(): @@ -3588,13 +3584,12 @@ def deco(fn): r0 :: __main__.deco_env r1 :: bool r2 :: __main__.wrapper_deco_obj - r3 :: bool wrapper :: object L0: r0 = deco_env() r0.fn = fn; r1 = is_error r2 = wrapper_deco_obj() - r2.__mypyc_env__ = r0; r3 = is_error + r2.__mypyc_env__ = r0 wrapper = r2 return wrapper @@ -3637,13 +3632,12 @@ def deco(fn): r0 :: __main__.deco_env r1 :: bool r2 :: __main__.wrapper_deco_obj - r3 :: bool wrapper :: object L0: r0 = deco_env() r0.fn = fn; r1 = is_error r2 = wrapper_deco_obj() - r2.__mypyc_env__ = r0; r3 = is_error + r2.__mypyc_env__ = r0 wrapper = r2 return wrapper @@ -3689,13 +3683,12 @@ def deco(fn): r0 :: __main__.deco_env r1 :: bool r2 :: __main__.wrapper_deco_obj - r3 :: bool wrapper :: object L0: r0 = deco_env() r0.fn = fn; r1 = is_error r2 = wrapper_deco_obj() - r2.__mypyc_env__ = r0; r3 = is_error + r2.__mypyc_env__ = r0 wrapper = r2 return wrapper @@ -3738,13 +3731,12 @@ def deco(fn): r0 :: __main__.deco_env r1 :: bool r2 :: __main__.wrapper_deco_obj - r3 :: bool wrapper :: object L0: r0 = deco_env() r0.fn = fn; r1 = is_error r2 = wrapper_deco_obj() - r2.__mypyc_env__ = r0; r3 = is_error + r2.__mypyc_env__ = r0 wrapper = r2 return wrapper @@ -3790,13 +3782,12 @@ def deco(fn): r0 :: __main__.deco_env r1 :: bool r2 :: __main__.wrapper_deco_obj - r3 :: bool wrapper :: object L0: r0 = deco_env() r0.fn = fn; r1 = is_error r2 = wrapper_deco_obj() - r2.__mypyc_env__ = r0; r3 = is_error + r2.__mypyc_env__ = r0 wrapper = r2 return wrapper diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index 0d4dad4f97e2..66f4a2914ee6 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -178,7 +178,6 @@ def f(x): x :: int L0: return x - [case testTypeVarMappingBound] # Dicts are special-cased for efficient iteration. from typing import Dict, TypedDict, TypeVar, Union @@ -760,13 +759,12 @@ def deco(func): r0 :: __main__.deco_env r1 :: bool r2 :: __main__.inner_deco_obj - r3 :: bool inner :: object L0: r0 = deco_env() r0.func = func; r1 = is_error r2 = inner_deco_obj() - r2.__mypyc_env__ = r0; r3 = is_error + r2.__mypyc_env__ = r0 inner = r2 return inner def f(x): diff --git a/mypyc/test-data/irbuild-nested.test b/mypyc/test-data/irbuild-nested.test index 1b390e9c3504..be5129aa2b8c 100644 --- a/mypyc/test-data/irbuild-nested.test +++ b/mypyc/test-data/irbuild-nested.test @@ -58,12 +58,11 @@ L0: def a(): r0 :: __main__.a_env r1 :: __main__.inner_a_obj - r2 :: bool inner :: object L0: r0 = a_env() r1 = inner_a_obj() - r1.__mypyc_env__ = r0; r2 = is_error + r1.__mypyc_env__ = r0 inner = r1 return inner def second_b_first_obj.__get__(__mypyc_self__, instance, owner): @@ -108,25 +107,23 @@ def first_b_obj.__call__(__mypyc_self__): r1 :: __main__.first_b_env r2 :: bool r3 :: __main__.second_b_first_obj - r4 :: bool second :: object L0: r0 = __mypyc_self__.__mypyc_env__ r1 = first_b_env() r1.__mypyc_env__ = r0; r2 = is_error r3 = second_b_first_obj() - r3.__mypyc_env__ = r1; r4 = is_error + r3.__mypyc_env__ = r1 second = r3 return second def b(): r0 :: __main__.b_env r1 :: __main__.first_b_obj - r2 :: bool first :: object L0: r0 = b_env() r1 = first_b_obj() - r1.__mypyc_env__ = r0; r2 = is_error + r1.__mypyc_env__ = r0 first = r1 return first def inner_c_obj.__get__(__mypyc_self__, instance, owner): @@ -156,12 +153,11 @@ def c(num): num :: float r0 :: __main__.c_env r1 :: __main__.inner_c_obj - r2 :: bool inner :: object L0: r0 = c_env() r1 = inner_c_obj() - r1.__mypyc_env__ = r0; r2 = is_error + r1.__mypyc_env__ = r0 inner = r1 return inner def inner_d_obj.__get__(__mypyc_self__, instance, owner): @@ -191,36 +187,35 @@ def d(num): num :: float r0 :: __main__.d_env r1 :: __main__.inner_d_obj - r2 :: bool inner :: object - r3 :: str - r4 :: object[1] - r5 :: object_ptr - r6 :: object - r7, a, r8 :: str - r9 :: object[1] - r10 :: object_ptr - r11 :: object - r12, b :: str + r2 :: str + r3 :: object[1] + r4 :: object_ptr + r5 :: object + r6, a, r7 :: str + r8 :: object[1] + r9 :: object_ptr + r10 :: object + r11, b :: str L0: r0 = d_env() r1 = inner_d_obj() - r1.__mypyc_env__ = r0; r2 = is_error + r1.__mypyc_env__ = r0 inner = r1 - r3 = 'one' - r4 = [r3] - r5 = load_address r4 - r6 = PyObject_Vectorcall(inner, r5, 1, 0) - keep_alive r3 - r7 = cast(str, r6) - a = r7 - r8 = 'two' - r9 = [r8] - r10 = load_address r9 - r11 = PyObject_Vectorcall(inner, r10, 1, 0) - keep_alive r8 - r12 = cast(str, r11) - b = r12 + r2 = 'one' + r3 = [r2] + r4 = load_address r3 + r5 = PyObject_Vectorcall(inner, r4, 1, 0) + keep_alive r2 + r6 = cast(str, r5) + a = r6 + r7 = 'two' + r8 = [r7] + r9 = load_address r8 + r10 = PyObject_Vectorcall(inner, r9, 1, 0) + keep_alive r7 + r11 = cast(str, r10) + b = r11 return a def inner(): r0 :: str @@ -291,18 +286,17 @@ def a(num): r0 :: __main__.a_env r1 :: bool r2 :: __main__.inner_a_obj - r3 :: bool - inner, r4 :: object - r5 :: int + inner, r3 :: object + r4 :: int L0: r0 = a_env() r0.num = num; r1 = is_error r2 = inner_a_obj() - r2.__mypyc_env__ = r0; r3 = is_error + r2.__mypyc_env__ = r0 inner = r2 - r4 = PyObject_Vectorcall(inner, 0, 0, 0) - r5 = unbox(int, r4) - return r5 + r3 = PyObject_Vectorcall(inner, 0, 0, 0) + r4 = unbox(int, r3) + return r4 def inner_b_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -331,20 +325,19 @@ def b(): r0 :: __main__.b_env r1 :: bool r2 :: __main__.inner_b_obj - r3 :: bool - inner, r4 :: object - r5, r6, r7 :: int + inner, r3 :: object + r4, r5, r6 :: int L0: r0 = b_env() r0.num = 6; r1 = is_error r2 = inner_b_obj() - r2.__mypyc_env__ = r0; r3 = is_error + r2.__mypyc_env__ = r0 inner = r2 - r4 = PyObject_Vectorcall(inner, 0, 0, 0) - r5 = unbox(int, r4) - r6 = r0.num - r7 = CPyTagged_Add(r5, r6) - return r7 + r3 = PyObject_Vectorcall(inner, 0, 0, 0) + r4 = unbox(int, r3) + r5 = r0.num + r6 = CPyTagged_Add(r4, r5) + return r6 def inner_c_obj.__get__(__mypyc_self__, instance, owner): __mypyc_self__, instance, owner, r0 :: object r1 :: bit @@ -391,28 +384,26 @@ def c(flag): flag :: bool r0 :: __main__.c_env r1 :: __main__.inner_c_obj - r2 :: bool inner :: object - r3 :: __main__.inner_c_obj_0 - r4 :: bool - r5 :: object - r6 :: str + r2 :: __main__.inner_c_obj_0 + r3 :: object + r4 :: 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 + r1.__mypyc_env__ = r0 inner = r1 goto L3 L2: - r3 = inner_c_obj_0() - r3.__mypyc_env__ = r0; r4 = is_error - inner = r3 + r2 = inner_c_obj_0() + r2.__mypyc_env__ = r0 + inner = r2 L3: - r5 = PyObject_Vectorcall(inner, 0, 0, 0) - r6 = cast(str, r5) - return r6 + r3 = PyObject_Vectorcall(inner, 0, 0, 0) + r4 = cast(str, r3) + return r4 [case testSpecialNested] def a() -> int: @@ -469,9 +460,8 @@ def b_a_obj.__call__(__mypyc_self__): r3, r4 :: int r5 :: bool r6 :: __main__.c_a_b_obj - r7 :: bool - c, r8 :: object - r9 :: int + c, r7 :: object + r8 :: int L0: r0 = __mypyc_self__.__mypyc_env__ r1 = b_a_env() @@ -480,27 +470,26 @@ L0: r4 = CPyTagged_Add(r3, 2) r0.x = r4; r5 = is_error r6 = c_a_b_obj() - r6.__mypyc_env__ = r1; r7 = is_error + r6.__mypyc_env__ = r1 c = r6 - r8 = PyObject_Vectorcall(c, 0, 0, 0) - r9 = unbox(int, r8) - return r9 + r7 = PyObject_Vectorcall(c, 0, 0, 0) + r8 = unbox(int, r7) + return r8 def a(): r0 :: __main__.a_env r1 :: bool r2 :: __main__.b_a_obj - r3 :: bool - b, r4 :: object - r5 :: int + b, r3 :: object + r4 :: int L0: r0 = a_env() r0.x = 2; r1 = is_error r2 = b_a_obj() - r2.__mypyc_env__ = r0; r3 = is_error + r2.__mypyc_env__ = r0 b = r2 - r4 = PyObject_Vectorcall(b, 0, 0, 0) - r5 = unbox(int, r4) - return r5 + r3 = PyObject_Vectorcall(b, 0, 0, 0) + r4 = unbox(int, r3) + return r4 [case testNestedFunctionInsideStatements] def f(flag: bool) -> str: @@ -558,28 +547,26 @@ def f(flag): flag :: bool r0 :: __main__.f_env r1 :: __main__.inner_f_obj - r2 :: bool inner :: object - r3 :: __main__.inner_f_obj_0 - r4 :: bool - r5 :: object - r6 :: str + r2 :: __main__.inner_f_obj_0 + r3 :: object + r4 :: 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 + r1.__mypyc_env__ = r0 inner = r1 goto L3 L2: - r3 = inner_f_obj_0() - r3.__mypyc_env__ = r0; r4 = is_error - inner = r3 + r2 = inner_f_obj_0() + r2.__mypyc_env__ = r0 + inner = r2 L3: - r5 = PyObject_Vectorcall(inner, 0, 0, 0) - r6 = cast(str, r5) - return r6 + r3 = PyObject_Vectorcall(inner, 0, 0, 0) + r4 = cast(str, r3) + return r4 [case testNestedFunctionsCallEachOther] from typing import Callable, List @@ -691,43 +678,43 @@ def f(a): r0 :: __main__.f_env r1 :: bool r2 :: __main__.foo_f_obj - r3, r4 :: bool - r5 :: __main__.bar_f_obj - r6, r7 :: bool - r8 :: __main__.baz_f_obj - r9, r10 :: bool - r11, r12 :: object - r13, r14 :: int - r15, r16 :: object - r17 :: object[1] - r18 :: object_ptr - r19 :: object - r20, r21 :: int + r3 :: bool + r4 :: __main__.bar_f_obj + r5 :: bool + r6 :: __main__.baz_f_obj + r7 :: bool + r8, r9 :: object + r10, r11 :: int + r12, r13 :: object + r14 :: object[1] + r15 :: object_ptr + r16 :: object + r17, r18 :: int L0: r0 = f_env() r0.a = a; r1 = is_error r2 = foo_f_obj() - r2.__mypyc_env__ = r0; r3 = is_error - r0.foo = r2; r4 = is_error - r5 = bar_f_obj() - r5.__mypyc_env__ = r0; r6 = is_error - r0.bar = r5; r7 = is_error - r8 = baz_f_obj() - r8.__mypyc_env__ = r0; r9 = is_error - r0.baz = r8; r10 = is_error - r11 = r0.bar - r12 = PyObject_Vectorcall(r11, 0, 0, 0) - r13 = unbox(int, r12) - r14 = r0.a - r15 = r0.baz - r16 = box(int, r14) - r17 = [r16] - r18 = load_address r17 - r19 = PyObject_Vectorcall(r15, r18, 1, 0) - keep_alive r16 - r20 = unbox(int, r19) - r21 = CPyTagged_Add(r13, r20) - return r21 + r2.__mypyc_env__ = r0 + r0.foo = r2; r3 = is_error + r4 = bar_f_obj() + r4.__mypyc_env__ = r0 + r0.bar = r4; r5 = is_error + r6 = baz_f_obj() + r6.__mypyc_env__ = r0 + r0.baz = r6; r7 = is_error + r8 = r0.bar + r9 = PyObject_Vectorcall(r8, 0, 0, 0) + r10 = unbox(int, r9) + r11 = r0.a + r12 = r0.baz + r13 = box(int, r11) + r14 = [r13] + r15 = load_address r14 + r16 = PyObject_Vectorcall(r12, r15, 1, 0) + keep_alive r13 + r17 = unbox(int, r16) + r18 = CPyTagged_Add(r10, r17) + return r18 [case testLambdas] def f(x: int, y: int) -> None: @@ -791,30 +778,29 @@ def f(x, y): x, y :: int r0 :: __main__.f_env r1 :: __main__.__mypyc_lambda__0_f_obj - r2, r3 :: bool - r4 :: __main__.__mypyc_lambda__1_f_obj - r5 :: bool - t, r6, r7 :: object - r8 :: object[2] - r9 :: object_ptr - r10 :: object - r11 :: None + r2 :: bool + r3 :: __main__.__mypyc_lambda__1_f_obj + t, r4, r5 :: object + r6 :: object[2] + r7 :: object_ptr + r8 :: object + r9 :: None L0: r0 = f_env() r1 = __mypyc_lambda__0_f_obj() - r1.__mypyc_env__ = r0; r2 = is_error - r0.s = r1; r3 = is_error - r4 = __mypyc_lambda__1_f_obj() - r4.__mypyc_env__ = r0; r5 = is_error - t = r4 - r6 = box(int, x) - r7 = box(int, y) - r8 = [r6, r7] - r9 = load_address r8 - r10 = PyObject_Vectorcall(t, r9, 2, 0) - keep_alive r6, r7 - r11 = unbox(None, r10) - return r11 + r1.__mypyc_env__ = r0 + r0.s = r1; r2 = is_error + r3 = __mypyc_lambda__1_f_obj() + r3.__mypyc_env__ = r0 + t = r3 + r4 = box(int, x) + r5 = box(int, y) + r6 = [r4, r5] + r7 = load_address r6 + r8 = PyObject_Vectorcall(t, r7, 2, 0) + keep_alive r4, r5 + r9 = unbox(None, r8) + return r9 [case testRecursiveFunction] from typing import Callable diff --git a/mypyc/test-data/run-functions.test b/mypyc/test-data/run-functions.test index 8932d0f11006..3e1ee1379ca4 100644 --- a/mypyc/test-data/run-functions.test +++ b/mypyc/test-data/run-functions.test @@ -1346,6 +1346,26 @@ def test_nested() -> None: {'x': 1} 1 +[case testNestedFunctionEnvironmentIsReadOnly] +from typing import Any, Callable + +def outer(value: str) -> Callable[[], str]: + def inner() -> str: + return value + return inner + +def test_environment_link_is_read_only() -> None: + fn: Any = outer("value") + environment = fn.__mypyc_env__ + try: + fn.__mypyc_env__ = None + except AttributeError: + pass + else: + assert False + assert fn.__mypyc_env__ is environment + assert fn() == "value" + [case testFunctoolsUpdateWrapper] import functools