diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 4f2f539118d7..604822ec58c5 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -429,6 +429,9 @@ def set_immortal_if_free_threaded(self, v: Value, line: int) -> None: """Make an object immortal on free-threaded builds (to avoid contention).""" self.builder.set_immortal_if_free_threaded(v, line) + def set_immortal_if_py312plus(self, v: Value, line: int) -> None: + self.builder.set_immortal_if_py312plus(v, line) + # Helpers for IR building def add_to_non_ext_dict( @@ -572,6 +575,7 @@ def init_final_static( coerced = self.coerce(rvalue_reg, type_override or self.node_type(lvalue), lvalue.line) self.final_names.append((name, coerced.type)) self.add(InitStatic(coerced, name, self.module_name)) + self.set_immortal_if_py312plus(coerced, lvalue.line) def load_final_static( self, fullname: str, typ: RType, line: int, error_name: str | None = None diff --git a/mypyc/irbuild/classdef.py b/mypyc/irbuild/classdef.py index 324b44b95dc4..7a60245d4906 100644 --- a/mypyc/irbuild/classdef.py +++ b/mypyc/irbuild/classdef.py @@ -262,8 +262,7 @@ def finalize(self, ir: ClassIR) -> None: non_ext_class = load_non_ext_class(self.builder, ir, self.non_ext, self.cdef.line) non_ext_class = load_decorated_class(self.builder, self.cdef, non_ext_class) - # Try to avoid contention when using free threading. - self.builder.set_immortal_if_free_threaded(non_ext_class, self.cdef.line) + self.builder.set_immortal_if_py312plus(non_ext_class, self.cdef.line) # Save the decorated class self.builder.add( diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 4b85c13892c1..bd1c18324018 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2492,6 +2492,11 @@ def set_immortal_if_free_threaded(self, v: Value, line: int) -> None: if IS_FREE_THREADED and sys.version_info >= (3, 14): self.primitive_op(set_immortal_op, [v], line) + def set_immortal_if_py312plus(self, v: Value, line: int) -> None: + """Emit IR to mark an object as immortal in Python 3.12+ (calls CPy_SetImmortal).""" + if sys.version_info >= (3, 12): + self.primitive_op(set_immortal_op, [v], line) + # Internal helpers def decompose_union_helper( diff --git a/mypyc/lib-rt/misc_ops.c b/mypyc/lib-rt/misc_ops.c index ca09c347b4ff..c85c6b9de295 100644 --- a/mypyc/lib-rt/misc_ops.c +++ b/mypyc/lib-rt/misc_ops.c @@ -1129,12 +1129,21 @@ void CPy_SetTypeAliasTypeComputeFunction(PyObject *alias, PyObject *compute_valu #endif -#if CPY_3_14_FEATURES - -#include "internal/pycore_object.h" +#if CPY_3_12_FEATURES + #if CPY_3_14_FEATURES + #include "internal/pycore_object.h" + #else + // The immortal refcount value is -1 (see PEP 683 and CPython source) + // This is safe for statically allocated objects and matches CPython's logic. + #define IMMORTAL_REFCNT ((Py_ssize_t)-1) + #endif void CPy_SetImmortal(PyObject *obj) { +#if CPY_3_14_FEATURES _Py_SetImmortal(obj); +#elif CPY_3_12_FEATURES + Py_SET_REFCNT(obj, IMMORTAL_REFCNT); +#endif } #endif diff --git a/mypyc/primitives/misc_ops.py b/mypyc/primitives/misc_ops.py index 8e6e450c64dc..7c59ba68f4ba 100644 --- a/mypyc/primitives/misc_ops.py +++ b/mypyc/primitives/misc_ops.py @@ -324,9 +324,9 @@ # threading, since this eliminates contention from concurrent reference count # updates. # -# Needs at least Python 3.14. +# Available for Python 3.12+ (uses public API in 3.14+, internal logic in 3.12/3.13). set_immortal_op = custom_primitive_op( - name="set_immmortal", + name="set_immortal", c_function_name="CPy_SetImmortal", arg_types=[object_rprimitive], return_type=void_rtype,