From 7c231b65e9e39fb3f49543d6259b62f83bf516dd Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sun, 21 Sep 2025 19:23:56 +0000 Subject: [PATCH 1/5] [mypyc] feat: make Final values immortal on py3.12+ --- mypyc/irbuild/builder.py | 4 ++++ mypyc/irbuild/classdef.py | 2 +- mypyc/irbuild/ll_builder.py | 5 +++++ mypyc/lib-rt/misc_ops.c | 12 +++++++++++- mypyc/primitives/misc_ops.py | 4 ++-- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 4f2f539118d7..f4294e98fae4 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -428,6 +428,9 @@ def debug_print(self, toprint: str | Value) -> None: 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 @@ -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..76bbab4db40d 100644 --- a/mypyc/irbuild/classdef.py +++ b/mypyc/irbuild/classdef.py @@ -263,7 +263,7 @@ def finalize(self, ir: ClassIR) -> None: 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.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..4b84a217b6c5 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2491,6 +2491,11 @@ def set_immortal_if_free_threaded(self, v: Value, line: int) -> None: """Make an object immortal on free-threaded builds (to avoid contention).""" 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 diff --git a/mypyc/lib-rt/misc_ops.c b/mypyc/lib-rt/misc_ops.c index ca09c347b4ff..357026fe0250 100644 --- a/mypyc/lib-rt/misc_ops.c +++ b/mypyc/lib-rt/misc_ops.c @@ -1129,12 +1129,22 @@ void CPy_SetTypeAliasTypeComputeFunction(PyObject *alias, PyObject *compute_valu #endif -#if CPY_3_14_FEATURES +#if CPY_3_12_FEATURES #include "internal/pycore_object.h" +#if !CPY_3_14_FEATURES + // 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, From 2f693b98ffe60cec17914c931c3f63d39952b6cf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 21 Sep 2025 19:36:51 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/builder.py | 2 +- mypyc/irbuild/ll_builder.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index f4294e98fae4..604822ec58c5 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -428,7 +428,7 @@ def debug_print(self, toprint: str | Value) -> None: 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) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 4b84a217b6c5..bd1c18324018 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -2491,7 +2491,7 @@ def set_immortal_if_free_threaded(self, v: Value, line: int) -> None: """Make an object immortal on free-threaded builds (to avoid contention).""" 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): From 05e5de01cbb018edca2d4cf0fc43d466d15cd19a Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 21 Sep 2025 15:39:52 -0400 Subject: [PATCH 3/5] Update classdef.py --- mypyc/irbuild/classdef.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/classdef.py b/mypyc/irbuild/classdef.py index 76bbab4db40d..5dfbedd373f5 100644 --- a/mypyc/irbuild/classdef.py +++ b/mypyc/irbuild/classdef.py @@ -263,7 +263,7 @@ def finalize(self, ir: ClassIR) -> None: non_ext_class = load_decorated_class(self.builder, self.cdef, non_ext_class) # Try to avoid contention when using free threading. - self.builder.builder.set_immortal_if_py312plus(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( From 5b733f388659cee3c356bf8e0a92c48fdbbc8851 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 21 Sep 2025 15:40:28 -0400 Subject: [PATCH 4/5] Update classdef.py --- mypyc/irbuild/classdef.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/irbuild/classdef.py b/mypyc/irbuild/classdef.py index 5dfbedd373f5..7a60245d4906 100644 --- a/mypyc/irbuild/classdef.py +++ b/mypyc/irbuild/classdef.py @@ -262,7 +262,6 @@ 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_py312plus(non_ext_class, self.cdef.line) # Save the decorated class From 5cd81763ab5a4b7e946f88e22961bf7a35af5ecf Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Sun, 21 Sep 2025 20:17:32 +0000 Subject: [PATCH 5/5] fix --- mypyc/lib-rt/misc_ops.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/mypyc/lib-rt/misc_ops.c b/mypyc/lib-rt/misc_ops.c index 357026fe0250..c85c6b9de295 100644 --- a/mypyc/lib-rt/misc_ops.c +++ b/mypyc/lib-rt/misc_ops.c @@ -1130,14 +1130,13 @@ void CPy_SetTypeAliasTypeComputeFunction(PyObject *alias, PyObject *compute_valu #endif #if CPY_3_12_FEATURES - -#include "internal/pycore_object.h" - -#if !CPY_3_14_FEATURES - // 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 + #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