From f1b3c94826f58607b8a75f4d88955185d9e50fc7 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Tue, 7 Oct 2025 20:01:21 +0300 Subject: [PATCH 01/22] First variant of stackref tests --- Lib/test/test_stackrefs.py | 83 +++++++++++++++++++++++++++++++ Modules/_testinternalcapi.c | 98 +++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 Lib/test/test_stackrefs.py diff --git a/Lib/test/test_stackrefs.py b/Lib/test/test_stackrefs.py new file mode 100644 index 00000000000000..2aabc0d82c0d7c --- /dev/null +++ b/Lib/test/test_stackrefs.py @@ -0,0 +1,83 @@ +import unittest +import sys +from test.support import cpython_only +try: + import _testinternalcapi +except ImportError: + _testinternalcapi = None + + +class StackRef: + def __init__(self, obj, flags): + self.obj = obj + self.flags = flags + self.refcount = sys.getrefcount(obj) + + def __str__(self): + return f"StackRef(obj={self.obj}, flags={self.flags}, refcount={self.refcount})" + + def __eq__(self, other): + return ( + self.obj is other.obj + and self.flags == other.flags + and self.refcount == other.refcount + ) + + def __hash__(self): + return tuple.__hash__((self.obj, self.flags, self.refcount)) + + +@cpython_only +class TestStackRef(unittest.TestCase): + + def test_equivalent(self): + def run_with_refcount_check(self, func, obj): + refcount = sys.getrefcount(obj) + res = func(obj) + self.assertEqual(sys.getrefcount(obj), refcount) + return res + + def stackref_from_object_borrow(obj): + return _testinternalcapi.stackref_from_object_borrow(obj) + + funcs = [ + _testinternalcapi.stackref_from_object_new, + _testinternalcapi.stackref_from_object_new2, + _testinternalcapi.stackref_from_object_steal_with_incref, + _testinternalcapi.stackref_make_heap_safe, + _testinternalcapi.stackref_make_heap_safe_with_borrow, + _testinternalcapi.stackref_strong_reference, + ] + + funcs2 = [ + _testinternalcapi.stackref_from_object_borrow, + _testinternalcapi.stackref_dup_borrowed_with_close, + ] + + for obj in (None, True, False, 42, '1'): + results = set() + for func in funcs + funcs2: + res = run_with_refcount_check(self, func, obj) + #print(func.__name__, obj, res) + results.add(res) + self.assertEqual(len(results), 1) + + for obj in (5000, range(10)): + results = set() + for func in funcs: + res = run_with_refcount_check(self, func, obj) + #print(func.__name__, obj, res) + results.add(res) + self.assertEqual(len(results), 1) + + results = set() + for func in funcs2: + res = run_with_refcount_check(self, func, obj) + #print(func.__name__, obj, res) + results.add(res) + self.assertEqual(len(results), 1) + + + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index c2647d405e25bc..64c52d44c50e44 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -35,6 +35,7 @@ #include "pycore_pylifecycle.h" // _PyInterpreterConfig_InitFromDict() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_runtime_structs.h" // _PY_NSMALLPOSINTS +#include "pycore_stackref.h" // PyStackRef_FunctionCheck() #include "pycore_unicodeobject.h" // _PyUnicode_TransformDecimalAndSpaceToASCII() #include "clinic/_testinternalcapi.c.h" @@ -2418,6 +2419,95 @@ set_vectorcall_nop(PyObject *self, PyObject *func) Py_RETURN_NONE; } +static PyObject * +stackref_to_tuple(_PyStackRef ref, PyObject *op) +{ + int flags = ref.bits & Py_TAG_BITS; + return Py_BuildValue("(Ii)", Py_REFCNT(op), flags); +} + +static PyObject * +stackref_from_object_new(PyObject *self, PyObject *op) +{ + _PyStackRef ref = PyStackRef_FromPyObjectNew(op); + PyObject *obj = stackref_to_tuple(ref, op); + PyStackRef_CLOSE(ref); + return obj; +} + +static PyObject * +stackref_from_object_new2(PyObject *self, PyObject *op) +{ + _PyStackRef ref = PyStackRef_FromPyObjectNew(op); + PyObject *obj = stackref_to_tuple(ref, op); + PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); + Py_DECREF(op2); + return obj; +} + +static PyObject * +stackref_from_object_steal_with_incref(PyObject *self, PyObject *op) +{ + Py_INCREF(op); + _PyStackRef ref = PyStackRef_FromPyObjectSteal(op); + PyObject *obj = stackref_to_tuple(ref, op); + PyStackRef_CLOSE(ref); + return obj; +} + +static PyObject * +stackref_make_heap_safe(PyObject *self, PyObject *op) +{ + _PyStackRef ref = PyStackRef_FromPyObjectNew(op); + _PyStackRef ref2 = PyStackRef_MakeHeapSafe(ref); + PyObject *obj = stackref_to_tuple(ref2, op); + PyStackRef_CLOSE(ref2); + return obj; +} + +static PyObject * +stackref_make_heap_safe_with_borrow(PyObject *self, PyObject *op) +{ + _PyStackRef ref = PyStackRef_FromPyObjectNew(op); + _PyStackRef ref2 = PyStackRef_Borrow(ref); + _PyStackRef ref3 = PyStackRef_MakeHeapSafe(ref2); + PyStackRef_CLOSE(ref); + PyObject *obj = stackref_to_tuple(ref3, op); + PyStackRef_CLOSE(ref3); + return obj; +} + +static PyObject * +stackref_strong_reference(PyObject *self, PyObject *op) +{ + _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); + PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); + _PyStackRef ref2 = PyStackRef_FromPyObjectSteal(op2); + PyObject *obj = stackref_to_tuple(ref2, op); + PyStackRef_CLOSE(ref2); + return obj; +} + +static PyObject * +stackref_from_object_borrow(PyObject *self, PyObject *op) +{ + _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); + PyObject *obj = stackref_to_tuple(ref, op); + PyStackRef_CLOSE(ref); + return obj; +} + +static PyObject * +stackref_dup_borrowed_with_close(PyObject *self, PyObject *op) +{ + _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); + _PyStackRef ref2 = PyStackRef_DUP(ref); + PyStackRef_XCLOSE(ref); + PyObject *obj = stackref_to_tuple(ref2, op); + PyStackRef_XCLOSE(ref2); + return obj; +} + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -2527,6 +2617,14 @@ static PyMethodDef module_functions[] = { #endif {"simple_pending_call", simple_pending_call, METH_O}, {"set_vectorcall_nop", set_vectorcall_nop, METH_O}, + {"stackref_from_object_new", stackref_from_object_new, METH_O}, + {"stackref_from_object_new2", stackref_from_object_new2, METH_O}, + {"stackref_from_object_steal_with_incref", stackref_from_object_steal_with_incref, METH_O}, + {"stackref_make_heap_safe", stackref_make_heap_safe, METH_O}, + {"stackref_make_heap_safe_with_borrow", stackref_make_heap_safe_with_borrow, METH_O}, + {"stackref_strong_reference", stackref_strong_reference, METH_O}, + {"stackref_from_object_borrow", stackref_from_object_borrow, METH_O}, + {"stackref_dup_borrowed_with_close", stackref_dup_borrowed_with_close, METH_O}, {NULL, NULL} /* sentinel */ }; From 871c013d76a542ec1c2cb2b1523b655a999a4d26 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Tue, 7 Oct 2025 20:50:16 +0300 Subject: [PATCH 02/22] Fixes for Py_STACKREF_DEBUG build --- Modules/_testinternalcapi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 64c52d44c50e44..873970884830b8 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2422,7 +2422,11 @@ set_vectorcall_nop(PyObject *self, PyObject *func) static PyObject * stackref_to_tuple(_PyStackRef ref, PyObject *op) { +#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) + int flags = ref.index & Py_TAG_BITS; +#else int flags = ref.bits & Py_TAG_BITS; +#endif return Py_BuildValue("(Ii)", Py_REFCNT(op), flags); } From 0a82e96a668fdc3f715c6f53851b91a46b7c1e56 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Tue, 7 Oct 2025 21:16:38 +0300 Subject: [PATCH 03/22] Improvements --- Lib/test/test_stackrefs.py | 50 ++++++++++--------------------------- Modules/_testinternalcapi.c | 14 ++--------- 2 files changed, 15 insertions(+), 49 deletions(-) diff --git a/Lib/test/test_stackrefs.py b/Lib/test/test_stackrefs.py index 2aabc0d82c0d7c..227deff8cb1114 100644 --- a/Lib/test/test_stackrefs.py +++ b/Lib/test/test_stackrefs.py @@ -6,74 +6,50 @@ except ImportError: _testinternalcapi = None - -class StackRef: - def __init__(self, obj, flags): - self.obj = obj - self.flags = flags - self.refcount = sys.getrefcount(obj) - - def __str__(self): - return f"StackRef(obj={self.obj}, flags={self.flags}, refcount={self.refcount})" - - def __eq__(self, other): - return ( - self.obj is other.obj - and self.flags == other.flags - and self.refcount == other.refcount - ) - - def __hash__(self): - return tuple.__hash__((self.obj, self.flags, self.refcount)) - - @cpython_only -class TestStackRef(unittest.TestCase): +class TestDefinition(unittest.TestCase): - def test_equivalent(self): + def test_equivalence(self): def run_with_refcount_check(self, func, obj): refcount = sys.getrefcount(obj) res = func(obj) self.assertEqual(sys.getrefcount(obj), refcount) return res - def stackref_from_object_borrow(obj): - return _testinternalcapi.stackref_from_object_borrow(obj) - - funcs = [ + funcs_with_incref = [ _testinternalcapi.stackref_from_object_new, - _testinternalcapi.stackref_from_object_new2, _testinternalcapi.stackref_from_object_steal_with_incref, _testinternalcapi.stackref_make_heap_safe, _testinternalcapi.stackref_make_heap_safe_with_borrow, _testinternalcapi.stackref_strong_reference, ] - funcs2 = [ + funcs_with_borrow = [ _testinternalcapi.stackref_from_object_borrow, _testinternalcapi.stackref_dup_borrowed_with_close, ] - for obj in (None, True, False, 42, '1'): + immortal_objs = (None, True, False, 42, '1') + + for obj in immortal_objs: results = set() - for func in funcs + funcs2: + for func in funcs_with_incref + funcs_with_borrow: res = run_with_refcount_check(self, func, obj) - #print(func.__name__, obj, res) results.add(res) self.assertEqual(len(results), 1) - for obj in (5000, range(10)): + mortal_objs = (5000, 3+2j, range(10)) + + for obj in mortal_objs: results = set() - for func in funcs: + for func in funcs_with_incref: res = run_with_refcount_check(self, func, obj) - #print(func.__name__, obj, res) results.add(res) self.assertEqual(len(results), 1) results = set() - for func in funcs2: + for func in funcs_with_borrow: res = run_with_refcount_check(self, func, obj) - #print(func.__name__, obj, res) results.add(res) self.assertEqual(len(results), 1) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 873970884830b8..6dea8e23a61624 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2439,23 +2439,14 @@ stackref_from_object_new(PyObject *self, PyObject *op) return obj; } -static PyObject * -stackref_from_object_new2(PyObject *self, PyObject *op) -{ - _PyStackRef ref = PyStackRef_FromPyObjectNew(op); - PyObject *obj = stackref_to_tuple(ref, op); - PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); - Py_DECREF(op2); - return obj; -} - static PyObject * stackref_from_object_steal_with_incref(PyObject *self, PyObject *op) { Py_INCREF(op); _PyStackRef ref = PyStackRef_FromPyObjectSteal(op); PyObject *obj = stackref_to_tuple(ref, op); - PyStackRef_CLOSE(ref); + PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); + Py_DECREF(op2); return obj; } @@ -2622,7 +2613,6 @@ static PyMethodDef module_functions[] = { {"simple_pending_call", simple_pending_call, METH_O}, {"set_vectorcall_nop", set_vectorcall_nop, METH_O}, {"stackref_from_object_new", stackref_from_object_new, METH_O}, - {"stackref_from_object_new2", stackref_from_object_new2, METH_O}, {"stackref_from_object_steal_with_incref", stackref_from_object_steal_with_incref, METH_O}, {"stackref_make_heap_safe", stackref_make_heap_safe, METH_O}, {"stackref_make_heap_safe_with_borrow", stackref_make_heap_safe_with_borrow, METH_O}, From a5e39e8911ad406d008689a91372b98eb62f26eb Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Tue, 7 Oct 2025 22:09:52 +0300 Subject: [PATCH 04/22] Do not check flags on FT build --- Modules/_testinternalcapi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 6dea8e23a61624..525935f70c148f 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2424,6 +2424,8 @@ stackref_to_tuple(_PyStackRef ref, PyObject *op) { #if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) int flags = ref.index & Py_TAG_BITS; +#elif defined(Py_GIL_DISABLED) + int flags = 0; #else int flags = ref.bits & Py_TAG_BITS; #endif From 6feef7a7be2646c337be30ab1ca0f1232531ab06 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 27 Oct 2025 19:28:06 +0300 Subject: [PATCH 05/22] Review addressed --- Include/internal/pycore_stackref.h | 20 ++++++++++++++++++++ Lib/test/test_stackrefs.py | 2 +- Modules/_testinternalcapi.c | 27 +++++++-------------------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 94fcb1d8aee52b..7d7254d16f6b03 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -74,6 +74,13 @@ static const _PyStackRef PyStackRef_ERROR = { .index = (1 << Py_TAGGED_SHIFT) }; #define INITIAL_STACKREF_INDEX (5 << Py_TAGGED_SHIFT) +static inline PyObject * +_PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) +{ + int flags = ref.index & Py_TAG_BITS; + return Py_BuildValue("(Ii)", Py_REFCNT(op), flags); +} + static inline _PyStackRef PyStackRef_Wrap(void *ptr) { @@ -447,6 +454,12 @@ static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED}; #define PyStackRef_IsNullOrInt(stackref) (PyStackRef_IsNull(stackref) || PyStackRef_IsTaggedInt(stackref)) +static inline PyObject * +_PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) +{ + return Py_BuildValue("(Ii)", Py_REFCNT(op), 0); +} + static inline PyObject * PyStackRef_AsPyObjectBorrow(_PyStackRef stackref) { @@ -633,6 +646,13 @@ static const _PyStackRef PyStackRef_NULL = { .bits = PyStackRef_NULL_BITS }; #ifdef Py_DEBUG +static inline PyObject * +_PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) +{ + int flags = ref.bits & Py_TAG_BITS; + return Py_BuildValue("(Ii)", Py_REFCNT(op), flags); +} + static inline void PyStackRef_CheckValid(_PyStackRef ref) { assert(ref.bits != 0); int tag = ref.bits & Py_TAG_BITS; diff --git a/Lib/test/test_stackrefs.py b/Lib/test/test_stackrefs.py index 227deff8cb1114..955a5e7b5b0a45 100644 --- a/Lib/test/test_stackrefs.py +++ b/Lib/test/test_stackrefs.py @@ -38,7 +38,7 @@ def run_with_refcount_check(self, func, obj): results.add(res) self.assertEqual(len(results), 1) - mortal_objs = (5000, 3+2j, range(10)) + mortal_objs = (5000, 3+2j, range(10), object()) for obj in mortal_objs: results = set() diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 525935f70c148f..6594506b5a349f 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2419,24 +2419,11 @@ set_vectorcall_nop(PyObject *self, PyObject *func) Py_RETURN_NONE; } -static PyObject * -stackref_to_tuple(_PyStackRef ref, PyObject *op) -{ -#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) - int flags = ref.index & Py_TAG_BITS; -#elif defined(Py_GIL_DISABLED) - int flags = 0; -#else - int flags = ref.bits & Py_TAG_BITS; -#endif - return Py_BuildValue("(Ii)", Py_REFCNT(op), flags); -} - static PyObject * stackref_from_object_new(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectNew(op); - PyObject *obj = stackref_to_tuple(ref, op); + PyObject *obj = _PyStackRef_AsTuple(ref, op); PyStackRef_CLOSE(ref); return obj; } @@ -2446,7 +2433,7 @@ stackref_from_object_steal_with_incref(PyObject *self, PyObject *op) { Py_INCREF(op); _PyStackRef ref = PyStackRef_FromPyObjectSteal(op); - PyObject *obj = stackref_to_tuple(ref, op); + PyObject *obj = _PyStackRef_AsTuple(ref, op); PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); Py_DECREF(op2); return obj; @@ -2457,7 +2444,7 @@ stackref_make_heap_safe(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectNew(op); _PyStackRef ref2 = PyStackRef_MakeHeapSafe(ref); - PyObject *obj = stackref_to_tuple(ref2, op); + PyObject *obj = _PyStackRef_AsTuple(ref2, op); PyStackRef_CLOSE(ref2); return obj; } @@ -2469,7 +2456,7 @@ stackref_make_heap_safe_with_borrow(PyObject *self, PyObject *op) _PyStackRef ref2 = PyStackRef_Borrow(ref); _PyStackRef ref3 = PyStackRef_MakeHeapSafe(ref2); PyStackRef_CLOSE(ref); - PyObject *obj = stackref_to_tuple(ref3, op); + PyObject *obj = _PyStackRef_AsTuple(ref3, op); PyStackRef_CLOSE(ref3); return obj; } @@ -2480,7 +2467,7 @@ stackref_strong_reference(PyObject *self, PyObject *op) _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); _PyStackRef ref2 = PyStackRef_FromPyObjectSteal(op2); - PyObject *obj = stackref_to_tuple(ref2, op); + PyObject *obj = _PyStackRef_AsTuple(ref2, op); PyStackRef_CLOSE(ref2); return obj; } @@ -2489,7 +2476,7 @@ static PyObject * stackref_from_object_borrow(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); - PyObject *obj = stackref_to_tuple(ref, op); + PyObject *obj = _PyStackRef_AsTuple(ref, op); PyStackRef_CLOSE(ref); return obj; } @@ -2500,7 +2487,7 @@ stackref_dup_borrowed_with_close(PyObject *self, PyObject *op) _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); _PyStackRef ref2 = PyStackRef_DUP(ref); PyStackRef_XCLOSE(ref); - PyObject *obj = stackref_to_tuple(ref2, op); + PyObject *obj = _PyStackRef_AsTuple(ref2, op); PyStackRef_XCLOSE(ref2); return obj; } From 6390e57bc8b6f71e66b03ae370f1b2ebb6b6fb84 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 27 Oct 2025 22:13:45 +0300 Subject: [PATCH 06/22] Build fix --- Include/internal/pycore_stackref.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 7d7254d16f6b03..fdbe1bd1e32f0e 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -644,8 +644,6 @@ static const _PyStackRef PyStackRef_NULL = { .bits = PyStackRef_NULL_BITS }; #define PyStackRef_IsFalse(REF) ((REF).bits == (((uintptr_t)&_Py_FalseStruct) | Py_TAG_REFCNT)) #define PyStackRef_IsNone(REF) ((REF).bits == (((uintptr_t)&_Py_NoneStruct) | Py_TAG_REFCNT)) -#ifdef Py_DEBUG - static inline PyObject * _PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) { @@ -653,6 +651,8 @@ _PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) return Py_BuildValue("(Ii)", Py_REFCNT(op), flags); } +#ifdef Py_DEBUG + static inline void PyStackRef_CheckValid(_PyStackRef ref) { assert(ref.bits != 0); int tag = ref.bits & Py_TAG_BITS; From f0a09c34505722b2e6bc9fc5c3ac70a511ac4d4f Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Tue, 28 Oct 2025 10:40:49 +0300 Subject: [PATCH 07/22] Review addressed --- Include/internal/pycore_stackref.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index fdbe1bd1e32f0e..dd04a3f78caaf1 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -457,7 +457,8 @@ static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED}; static inline PyObject * _PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) { - return Py_BuildValue("(Ii)", Py_REFCNT(op), 0); + // Do not check StackRef flags in the free threading build. + return Py_BuildValue("(Ii)", Py_REFCNT(op), -1); } static inline PyObject * From bf076549c79e65df4cd73cfd779be2bb31db680c Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Tue, 28 Oct 2025 11:15:04 +0300 Subject: [PATCH 08/22] Some comments are added --- Modules/_testinternalcapi.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 6594506b5a349f..1b7e97f336686e 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2431,9 +2431,11 @@ stackref_from_object_new(PyObject *self, PyObject *op) static PyObject * stackref_from_object_steal_with_incref(PyObject *self, PyObject *op) { + // The next two lines are equivalent to PyStackRef_FromPyObjectNew. Py_INCREF(op); _PyStackRef ref = PyStackRef_FromPyObjectSteal(op); PyObject *obj = _PyStackRef_AsTuple(ref, op); + // The next two lines are equivalent to PyStackRef_CLOSE. PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); Py_DECREF(op2); return obj; @@ -2443,6 +2445,7 @@ static PyObject * stackref_make_heap_safe(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectNew(op); + // In no-GIL production build ref2 is equal to ref. _PyStackRef ref2 = PyStackRef_MakeHeapSafe(ref); PyObject *obj = _PyStackRef_AsTuple(ref2, op); PyStackRef_CLOSE(ref2); @@ -2455,6 +2458,7 @@ stackref_make_heap_safe_with_borrow(PyObject *self, PyObject *op) _PyStackRef ref = PyStackRef_FromPyObjectNew(op); _PyStackRef ref2 = PyStackRef_Borrow(ref); _PyStackRef ref3 = PyStackRef_MakeHeapSafe(ref2); + // We can close ref, since ref3 is heap safe. PyStackRef_CLOSE(ref); PyObject *obj = _PyStackRef_AsTuple(ref3, op); PyStackRef_CLOSE(ref3); @@ -2464,6 +2468,7 @@ stackref_make_heap_safe_with_borrow(PyObject *self, PyObject *op) static PyObject * stackref_strong_reference(PyObject *self, PyObject *op) { + // The next two lines are equivalent to op2 = Py_NewRef(op). _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); _PyStackRef ref2 = PyStackRef_FromPyObjectSteal(op2); @@ -2485,7 +2490,9 @@ static PyObject * stackref_dup_borrowed_with_close(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); + // In no-GIL production build ref2 is equal to ref. _PyStackRef ref2 = PyStackRef_DUP(ref); + // In production build closing borrowed ref is no-op. PyStackRef_XCLOSE(ref); PyObject *obj = _PyStackRef_AsTuple(ref2, op); PyStackRef_XCLOSE(ref2); From f53afa682aa9a099b39d842e3f40ce703b28553c Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Tue, 28 Oct 2025 11:20:06 +0300 Subject: [PATCH 09/22] Wording --- Modules/_testinternalcapi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 1b7e97f336686e..d3f4158a40aac2 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2445,7 +2445,7 @@ static PyObject * stackref_make_heap_safe(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectNew(op); - // In no-GIL production build ref2 is equal to ref. + // For no-GIL release build ref2 is equal to ref. _PyStackRef ref2 = PyStackRef_MakeHeapSafe(ref); PyObject *obj = _PyStackRef_AsTuple(ref2, op); PyStackRef_CLOSE(ref2); @@ -2490,9 +2490,9 @@ static PyObject * stackref_dup_borrowed_with_close(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); - // In no-GIL production build ref2 is equal to ref. + // For no-GIL release build ref2 is equal to ref. _PyStackRef ref2 = PyStackRef_DUP(ref); - // In production build closing borrowed ref is no-op. + // For release build closing borrowed ref is no-op. PyStackRef_XCLOSE(ref); PyObject *obj = _PyStackRef_AsTuple(ref2, op); PyStackRef_XCLOSE(ref2); From b1b3b2bb0cd4c188239da3a8f75fe3f9cb3d1284 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 17:06:15 +0300 Subject: [PATCH 10/22] Changes in Py_BuildValue calls --- Include/internal/pycore_stackref.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 224398d5c6145a..b31d2a78f8fdc2 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -80,7 +80,7 @@ static inline PyObject * _PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) { int flags = ref.index & Py_TAG_BITS; - return Py_BuildValue("(Ii)", Py_REFCNT(op), flags); + return Py_BuildValue("(ni)", Py_REFCNT(op), flags); } static inline _PyStackRef @@ -480,7 +480,7 @@ static inline PyObject * _PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) { // Do not check StackRef flags in the free threading build. - return Py_BuildValue("(Ii)", Py_REFCNT(op), -1); + return Py_BuildValue("(ni)", Py_REFCNT(op), flags); } static inline PyObject * @@ -671,7 +671,7 @@ static inline PyObject * _PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) { int flags = ref.bits & Py_TAG_BITS; - return Py_BuildValue("(Ii)", Py_REFCNT(op), flags); + return Py_BuildValue("(ni)", Py_REFCNT(op), -1); } #ifdef Py_DEBUG From d965529d0775668cf665bceed384fe744e50242a Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 17:09:17 +0300 Subject: [PATCH 11/22] Changes in Py_BuildValue calls 2 --- Include/internal/pycore_stackref.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index b31d2a78f8fdc2..fddeb99ee204fe 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -480,7 +480,7 @@ static inline PyObject * _PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) { // Do not check StackRef flags in the free threading build. - return Py_BuildValue("(ni)", Py_REFCNT(op), flags); + return Py_BuildValue("(ni)", Py_REFCNT(op), -1); } static inline PyObject * @@ -671,7 +671,7 @@ static inline PyObject * _PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) { int flags = ref.bits & Py_TAG_BITS; - return Py_BuildValue("(ni)", Py_REFCNT(op), -1); + return Py_BuildValue("(ni)", Py_REFCNT(op), flags); } #ifdef Py_DEBUG From 77c88bbb4557e0a56b1229ff9b8d2558fd2b3b0a Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 18:48:40 +0300 Subject: [PATCH 12/22] Improvements in test --- Lib/test/test_stackrefs.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_stackrefs.py b/Lib/test/test_stackrefs.py index 955a5e7b5b0a45..0e5afc94331b33 100644 --- a/Lib/test/test_stackrefs.py +++ b/Lib/test/test_stackrefs.py @@ -9,6 +9,8 @@ @cpython_only class TestDefinition(unittest.TestCase): + BIG_REFCOUNT = 1000 + def test_equivalence(self): def run_with_refcount_check(self, func, obj): refcount = sys.getrefcount(obj) @@ -34,23 +36,30 @@ def run_with_refcount_check(self, func, obj): for obj in immortal_objs: results = set() for func in funcs_with_incref + funcs_with_borrow: - res = run_with_refcount_check(self, func, obj) - results.add(res) + refcount, flags = run_with_refcount_check(self, func, obj) + self.assertGreater(refcount, self.BIG_REFCOUNT) + self.assertIn(flags, (1, -1)) + results.add((refcount, flags)) + self.assertEqual(len(results), 1) - mortal_objs = (5000, 3+2j, range(10), object()) + mortal_objs = (object(), range(10), iter([1, 2, 3])) for obj in mortal_objs: results = set() for func in funcs_with_incref: - res = run_with_refcount_check(self, func, obj) - results.add(res) + refcount, flags = run_with_refcount_check(self, func, obj) + self.assertLess(refcount, self.BIG_REFCOUNT) + self.assertEqual(flags, 0) + results.add((refcount, flags)) self.assertEqual(len(results), 1) results = set() for func in funcs_with_borrow: - res = run_with_refcount_check(self, func, obj) - results.add(res) + refcount, flags = run_with_refcount_check(self, func, obj) + self.assertLess(refcount, self.BIG_REFCOUNT) + self.assertEqual(flags, 1) + results.add((refcount, flags)) self.assertEqual(len(results), 1) From 06fe751a081c06541a0f28c629f00c19692147e1 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 18:50:05 +0300 Subject: [PATCH 13/22] Improvements in test 2 --- Lib/test/test_stackrefs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_stackrefs.py b/Lib/test/test_stackrefs.py index 0e5afc94331b33..9d038b666f1bc6 100644 --- a/Lib/test/test_stackrefs.py +++ b/Lib/test/test_stackrefs.py @@ -50,7 +50,7 @@ def run_with_refcount_check(self, func, obj): for func in funcs_with_incref: refcount, flags = run_with_refcount_check(self, func, obj) self.assertLess(refcount, self.BIG_REFCOUNT) - self.assertEqual(flags, 0) + self.assertIn(flags, (0, -1)) results.add((refcount, flags)) self.assertEqual(len(results), 1) @@ -58,7 +58,7 @@ def run_with_refcount_check(self, func, obj): for func in funcs_with_borrow: refcount, flags = run_with_refcount_check(self, func, obj) self.assertLess(refcount, self.BIG_REFCOUNT) - self.assertEqual(flags, 1) + self.assertIn(flags, (1, -1)) results.add((refcount, flags)) self.assertEqual(len(results), 1) From c9ae051d8f2ae771ab2b80067d1aa774652cf958 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 19:09:23 +0300 Subject: [PATCH 14/22] Remove blank line --- Lib/test/test_stackrefs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_stackrefs.py b/Lib/test/test_stackrefs.py index 9d038b666f1bc6..8143a710f1a57b 100644 --- a/Lib/test/test_stackrefs.py +++ b/Lib/test/test_stackrefs.py @@ -40,7 +40,6 @@ def run_with_refcount_check(self, func, obj): self.assertGreater(refcount, self.BIG_REFCOUNT) self.assertIn(flags, (1, -1)) results.add((refcount, flags)) - self.assertEqual(len(results), 1) mortal_objs = (object(), range(10), iter([1, 2, 3])) From 474f650ced253ab97bd0666382ad2614a1d10ab0 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 21:07:44 +0300 Subject: [PATCH 15/22] Use -1 in flags only for immortal objects on ft build --- Include/internal/pycore_stackref.h | 27 +++++++++++++-------------- Lib/test/test_stackrefs.py | 4 ++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index fddeb99ee204fe..4655b2e6024aa7 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -376,6 +376,19 @@ PyStackRef_IsNullOrInt(_PyStackRef ref); static const _PyStackRef PyStackRef_ERROR = { .bits = Py_TAG_INVALID }; +static inline PyObject * +_PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) +{ + int flags = ref.bits & Py_TAG_BITS; +#ifdef Py_GIL_DISABLED + if (_Py_IsImmortal(op)) { + // Do not check flags on immortal objects in the free threading build. + flags = -1; + } +#endif + return Py_BuildValue("(ni)", Py_REFCNT(op), flags); +} + /* Wrap a pointer in a stack ref. * The resulting stack reference is not safe and should only be used * in the interpreter to pass values from one uop to another. @@ -476,13 +489,6 @@ static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED}; #define PyStackRef_IsNullOrInt(stackref) (PyStackRef_IsNull(stackref) || PyStackRef_IsTaggedInt(stackref)) -static inline PyObject * -_PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) -{ - // Do not check StackRef flags in the free threading build. - return Py_BuildValue("(ni)", Py_REFCNT(op), -1); -} - static inline PyObject * PyStackRef_AsPyObjectBorrow(_PyStackRef stackref) { @@ -667,13 +673,6 @@ static const _PyStackRef PyStackRef_NULL = { .bits = PyStackRef_NULL_BITS }; #define PyStackRef_IsFalse(REF) ((REF).bits == (((uintptr_t)&_Py_FalseStruct) | Py_TAG_REFCNT)) #define PyStackRef_IsNone(REF) ((REF).bits == (((uintptr_t)&_Py_NoneStruct) | Py_TAG_REFCNT)) -static inline PyObject * -_PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) -{ - int flags = ref.bits & Py_TAG_BITS; - return Py_BuildValue("(ni)", Py_REFCNT(op), flags); -} - #ifdef Py_DEBUG static inline void PyStackRef_CheckValid(_PyStackRef ref) { diff --git a/Lib/test/test_stackrefs.py b/Lib/test/test_stackrefs.py index 8143a710f1a57b..4214f12d49c20e 100644 --- a/Lib/test/test_stackrefs.py +++ b/Lib/test/test_stackrefs.py @@ -49,7 +49,7 @@ def run_with_refcount_check(self, func, obj): for func in funcs_with_incref: refcount, flags = run_with_refcount_check(self, func, obj) self.assertLess(refcount, self.BIG_REFCOUNT) - self.assertIn(flags, (0, -1)) + self.assertEqual(flags, 0) results.add((refcount, flags)) self.assertEqual(len(results), 1) @@ -57,7 +57,7 @@ def run_with_refcount_check(self, func, obj): for func in funcs_with_borrow: refcount, flags = run_with_refcount_check(self, func, obj) self.assertLess(refcount, self.BIG_REFCOUNT) - self.assertIn(flags, (1, -1)) + self.assertEqual(flags, 1) results.add((refcount, flags)) self.assertEqual(len(results), 1) From 527412215e7fdaacdd9f804697170c8039a057d3 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 21:48:19 +0300 Subject: [PATCH 16/22] Rename obj -> res in aux functions --- Modules/_testinternalcapi.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 3a002bb317359c..4668243c760f8e 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2501,9 +2501,9 @@ static PyObject * stackref_from_object_new(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectNew(op); - PyObject *obj = _PyStackRef_AsTuple(ref, op); + PyObject *res = _PyStackRef_AsTuple(ref, op); PyStackRef_CLOSE(ref); - return obj; + return res; } static PyObject * @@ -2512,11 +2512,11 @@ stackref_from_object_steal_with_incref(PyObject *self, PyObject *op) // The next two lines are equivalent to PyStackRef_FromPyObjectNew. Py_INCREF(op); _PyStackRef ref = PyStackRef_FromPyObjectSteal(op); - PyObject *obj = _PyStackRef_AsTuple(ref, op); + PyObject *res = _PyStackRef_AsTuple(ref, op); // The next two lines are equivalent to PyStackRef_CLOSE. PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); Py_DECREF(op2); - return obj; + return res; } static PyObject * @@ -2525,9 +2525,9 @@ stackref_make_heap_safe(PyObject *self, PyObject *op) _PyStackRef ref = PyStackRef_FromPyObjectNew(op); // For no-GIL release build ref2 is equal to ref. _PyStackRef ref2 = PyStackRef_MakeHeapSafe(ref); - PyObject *obj = _PyStackRef_AsTuple(ref2, op); + PyObject *res = _PyStackRef_AsTuple(ref2, op); PyStackRef_CLOSE(ref2); - return obj; + return res; } static PyObject * @@ -2538,9 +2538,9 @@ stackref_make_heap_safe_with_borrow(PyObject *self, PyObject *op) _PyStackRef ref3 = PyStackRef_MakeHeapSafe(ref2); // We can close ref, since ref3 is heap safe. PyStackRef_CLOSE(ref); - PyObject *obj = _PyStackRef_AsTuple(ref3, op); + PyObject *res = _PyStackRef_AsTuple(ref3, op); PyStackRef_CLOSE(ref3); - return obj; + return res; } static PyObject * @@ -2550,18 +2550,18 @@ stackref_strong_reference(PyObject *self, PyObject *op) _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); _PyStackRef ref2 = PyStackRef_FromPyObjectSteal(op2); - PyObject *obj = _PyStackRef_AsTuple(ref2, op); + PyObject *res = _PyStackRef_AsTuple(ref2, op); PyStackRef_CLOSE(ref2); - return obj; + return res; } static PyObject * stackref_from_object_borrow(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); - PyObject *obj = _PyStackRef_AsTuple(ref, op); + PyObject *res = _PyStackRef_AsTuple(ref, op); PyStackRef_CLOSE(ref); - return obj; + return res; } static PyObject * @@ -2572,9 +2572,9 @@ stackref_dup_borrowed_with_close(PyObject *self, PyObject *op) _PyStackRef ref2 = PyStackRef_DUP(ref); // For release build closing borrowed ref is no-op. PyStackRef_XCLOSE(ref); - PyObject *obj = _PyStackRef_AsTuple(ref2, op); + PyObject *res = _PyStackRef_AsTuple(ref2, op); PyStackRef_XCLOSE(ref2); - return obj; + return res; } From 24716092f4b10f7573859fc7e6c2d5dbafb7b8e6 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 21:49:01 +0300 Subject: [PATCH 17/22] Use constants in test --- Lib/test/test_stackrefs.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_stackrefs.py b/Lib/test/test_stackrefs.py index 4214f12d49c20e..0ffc3e04ff4855 100644 --- a/Lib/test/test_stackrefs.py +++ b/Lib/test/test_stackrefs.py @@ -10,6 +10,9 @@ class TestDefinition(unittest.TestCase): BIG_REFCOUNT = 1000 + FLAGS_REFCOUNT = 1 + FLAGS_NO_REFCOUNT = 0 + FLAGS_INVALID = -1 def test_equivalence(self): def run_with_refcount_check(self, func, obj): @@ -38,7 +41,7 @@ def run_with_refcount_check(self, func, obj): for func in funcs_with_incref + funcs_with_borrow: refcount, flags = run_with_refcount_check(self, func, obj) self.assertGreater(refcount, self.BIG_REFCOUNT) - self.assertIn(flags, (1, -1)) + self.assertIn(flags, (self.FLAGS_REFCOUNT, self.FLAGS_INVALID)) results.add((refcount, flags)) self.assertEqual(len(results), 1) @@ -49,7 +52,7 @@ def run_with_refcount_check(self, func, obj): for func in funcs_with_incref: refcount, flags = run_with_refcount_check(self, func, obj) self.assertLess(refcount, self.BIG_REFCOUNT) - self.assertEqual(flags, 0) + self.assertEqual(flags, self.FLAGS_NO_REFCOUNT) results.add((refcount, flags)) self.assertEqual(len(results), 1) @@ -57,7 +60,7 @@ def run_with_refcount_check(self, func, obj): for func in funcs_with_borrow: refcount, flags = run_with_refcount_check(self, func, obj) self.assertLess(refcount, self.BIG_REFCOUNT) - self.assertEqual(flags, 1) + self.assertEqual(flags, self.FLAGS_REFCOUNT) results.add((refcount, flags)) self.assertEqual(len(results), 1) From 36a2fb4780a385affcaf223a69fcbf302aee2c89 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 21:49:42 +0300 Subject: [PATCH 18/22] Remove blank line --- Lib/test/test_stackrefs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_stackrefs.py b/Lib/test/test_stackrefs.py index 0ffc3e04ff4855..e96d650bec7274 100644 --- a/Lib/test/test_stackrefs.py +++ b/Lib/test/test_stackrefs.py @@ -65,6 +65,5 @@ def run_with_refcount_check(self, func, obj): self.assertEqual(len(results), 1) - if __name__ == "__main__": unittest.main() From 14521a112df484d0182ecb77ef18aa87e2a5749e Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 21:52:27 +0300 Subject: [PATCH 19/22] Asserts on mortality --- Lib/test/test_stackrefs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_stackrefs.py b/Lib/test/test_stackrefs.py index e96d650bec7274..e8855e7635d14c 100644 --- a/Lib/test/test_stackrefs.py +++ b/Lib/test/test_stackrefs.py @@ -37,6 +37,7 @@ def run_with_refcount_check(self, func, obj): immortal_objs = (None, True, False, 42, '1') for obj in immortal_objs: + self.assertTrue(sys._is_immortal(obj)) results = set() for func in funcs_with_incref + funcs_with_borrow: refcount, flags = run_with_refcount_check(self, func, obj) @@ -48,6 +49,7 @@ def run_with_refcount_check(self, func, obj): mortal_objs = (object(), range(10), iter([1, 2, 3])) for obj in mortal_objs: + self.assertFalse(sys._is_immortal(obj)) results = set() for func in funcs_with_incref: refcount, flags = run_with_refcount_check(self, func, obj) From db34b878f20d8a7359b711e59a826ab338fef327 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Mon, 17 Nov 2025 22:05:32 +0300 Subject: [PATCH 20/22] Comment change --- Modules/_testinternalcapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 4668243c760f8e..e5438930e87b95 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2570,7 +2570,7 @@ stackref_dup_borrowed_with_close(PyObject *self, PyObject *op) _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); // For no-GIL release build ref2 is equal to ref. _PyStackRef ref2 = PyStackRef_DUP(ref); - // For release build closing borrowed ref is no-op. + // Closing borrowed ref is no-op. PyStackRef_XCLOSE(ref); PyObject *res = _PyStackRef_AsTuple(ref2, op); PyStackRef_XCLOSE(ref2); From 63ea804e54d527a47794696d2c1d221d0929bb25 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Tue, 18 Nov 2025 16:27:06 +0300 Subject: [PATCH 21/22] Comments improvement --- Modules/_testinternalcapi.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index e5438930e87b95..4926429e1c5576 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2500,8 +2500,10 @@ test_threadstate_set_stack_protection(PyObject *self, PyObject *Py_UNUSED(args)) static PyObject * stackref_from_object_new(PyObject *self, PyObject *op) { + // Make a new strong reference and give ownership of it to ref. _PyStackRef ref = PyStackRef_FromPyObjectNew(op); PyObject *res = _PyStackRef_AsTuple(ref, op); + // Remove a strong reference by closing ref. PyStackRef_CLOSE(ref); return res; } @@ -2509,13 +2511,22 @@ stackref_from_object_new(PyObject *self, PyObject *op) static PyObject * stackref_from_object_steal_with_incref(PyObject *self, PyObject *op) { - // The next two lines are equivalent to PyStackRef_FromPyObjectNew. + // Combination of Py_INCREF and PyStackRef_FromPyObjectSteal + // is equivalent to PyStackRef_FromPyObjectNew. + Py_INCREF(op); + // Transfer ownership of a strong reference from op to ref. _PyStackRef ref = PyStackRef_FromPyObjectSteal(op); + PyObject *res = _PyStackRef_AsTuple(ref, op); - // The next two lines are equivalent to PyStackRef_CLOSE. + + // Combination of PyStackRef_AsPyObjectSteal and Py_DECREF + // is equivalent to PyStackRef_CLOSE. + + // Transfer ownership of a strong reference from ref to op2. PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); Py_DECREF(op2); + return res; } @@ -2523,7 +2534,8 @@ static PyObject * stackref_make_heap_safe(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectNew(op); - // For no-GIL release build ref2 is equal to ref. + // Transfer ownership of a strong reference to op from ref to ref2, + // ref shouldn't be used anymore. _PyStackRef ref2 = PyStackRef_MakeHeapSafe(ref); PyObject *res = _PyStackRef_AsTuple(ref2, op); PyStackRef_CLOSE(ref2); @@ -2534,7 +2546,10 @@ static PyObject * stackref_make_heap_safe_with_borrow(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectNew(op); + // Create a borrowed reference to op, ref keeps a strong reference. _PyStackRef ref2 = PyStackRef_Borrow(ref); + // Make a new strong reference to op from ref2, + // ref2 shouldn't be used anymore. _PyStackRef ref3 = PyStackRef_MakeHeapSafe(ref2); // We can close ref, since ref3 is heap safe. PyStackRef_CLOSE(ref); @@ -2546,7 +2561,8 @@ stackref_make_heap_safe_with_borrow(PyObject *self, PyObject *op) static PyObject * stackref_strong_reference(PyObject *self, PyObject *op) { - // The next two lines are equivalent to op2 = Py_NewRef(op). + // Combination of PyStackRef_FromPyObjectBorrow and + // PyStackRef_AsPyObjectSteal is equivalent to Py_NewRef. _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); PyObject *op2 = PyStackRef_AsPyObjectSteal(ref); _PyStackRef ref2 = PyStackRef_FromPyObjectSteal(op2); @@ -2560,6 +2576,7 @@ stackref_from_object_borrow(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); PyObject *res = _PyStackRef_AsTuple(ref, op); + // Closing borrowed ref is no-op. PyStackRef_CLOSE(ref); return res; } @@ -2568,9 +2585,9 @@ static PyObject * stackref_dup_borrowed_with_close(PyObject *self, PyObject *op) { _PyStackRef ref = PyStackRef_FromPyObjectBorrow(op); - // For no-GIL release build ref2 is equal to ref. + // Duplicate reference, ref and ref2 both will be + // correct borrowed references from op. _PyStackRef ref2 = PyStackRef_DUP(ref); - // Closing borrowed ref is no-op. PyStackRef_XCLOSE(ref); PyObject *res = _PyStackRef_AsTuple(ref2, op); PyStackRef_XCLOSE(ref2); From 2998fed1bd8839e89977d68bfab112eee4ad7e63 Mon Sep 17 00:00:00 2001 From: Mikhail Efimov Date: Sat, 20 Dec 2025 15:27:05 +0300 Subject: [PATCH 22/22] Lint fix --- Include/internal/pycore_stackref.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 7cc8ecc10f40d6..a042cfe38989a6 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -381,7 +381,7 @@ static const _PyStackRef PyStackRef_ERROR = { .bits = Py_TAG_INVALID }; /* For use in the JIT to clear an unused value. * PyStackRef_ZERO_BITS has no meaning and should not be used other than by the JIT. */ static const _PyStackRef PyStackRef_ZERO_BITS = { .bits = 0 }; - + static inline PyObject * _PyStackRef_AsTuple(_PyStackRef ref, PyObject *op) {