https://github.com/python/cpython/commit/2bef8ea8ea045d20394f0daec7a5c5b1046a4e22
commit: 2bef8ea8ea045d20394f0daec7a5c5b1046a4e22
branch: main
author: Mark Shannon <m...@hotpy.org>
committer: markshannon <m...@hotpy.org>
date: 2025-03-10T14:06:56Z
summary:

GH-127705: Use `_PyStackRef`s in the default build. (GH-127875)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-02-17-12-48-00.gh-issue-127705.Qad2hx.rst
M Include/internal/pycore_ceval.h
M Include/internal/pycore_frame.h
M Include/internal/pycore_object.h
M Include/internal/pycore_opcode_metadata.h
M Include/internal/pycore_stackref.h
M Include/internal/pycore_uop_metadata.h
M Include/object.h
M Include/refcount.h
M Lib/test/test_builtin.py
M Objects/floatobject.c
M Objects/object.c
M Python/bytecodes.c
M Python/ceval.c
M Python/ceval_macros.h
M Python/executor_cases.c.h
M Python/frame.c
M Python/gc.c
M Python/generated_cases.c.h
M Python/stackrefs.c
M Tools/cases_generator/analyzer.py

diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index 13ab7d68663810..5618539f2df73e 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -342,7 +342,7 @@ _Py_eval_breaker_bit_is_set(PyThreadState *tstate, 
uintptr_t bit)
 void _Py_set_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit);
 void _Py_unset_eval_breaker_bit_all(PyInterpreterState *interp, uintptr_t bit);
 
-PyAPI_FUNC(PyObject *) _PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, 
_PyStackRef right, double value);
+PyAPI_FUNC(_PyStackRef) _PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, 
_PyStackRef right, double value);
 
 #ifdef __cplusplus
 }
diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h
index 8cc3504723b64c..a3a5d034056dfd 100644
--- a/Include/internal/pycore_frame.h
+++ b/Include/internal/pycore_frame.h
@@ -148,17 +148,26 @@ _PyFrame_NumSlotsForCodeObject(PyCodeObject *code)
 
 static inline void _PyFrame_Copy(_PyInterpreterFrame *src, _PyInterpreterFrame 
*dest)
 {
-    *dest = *src;
+    dest->f_executable = PyStackRef_MakeHeapSafe(src->f_executable);
+    // Don't leave a dangling pointer to the old frame when creating generators
+    // and coroutines:
+    dest->previous = NULL;
+    dest->f_funcobj = PyStackRef_MakeHeapSafe(src->f_funcobj);
+    dest->f_globals = src->f_globals;
+    dest->f_builtins = src->f_builtins;
+    dest->f_locals = src->f_locals;
+    dest->frame_obj = src->frame_obj;
+    dest->instr_ptr = src->instr_ptr;
+#ifdef Py_GIL_DISABLED
+    dest->tlbc_index = src->tlbc_index;
+#endif
     assert(src->stackpointer != NULL);
     int stacktop = (int)(src->stackpointer - src->localsplus);
-    assert(stacktop >= _PyFrame_GetCode(src)->co_nlocalsplus);
+    assert(stacktop >= 0);
     dest->stackpointer = dest->localsplus + stacktop;
-    for (int i = 1; i < stacktop; i++) {
-        dest->localsplus[i] = src->localsplus[i];
+    for (int i = 0; i < stacktop; i++) {
+        dest->localsplus[i] = PyStackRef_MakeHeapSafe(src->localsplus[i]);
     }
-    // Don't leave a dangling pointer to the old frame when creating generators
-    // and coroutines:
-    dest->previous = NULL;
 }
 
 #ifdef Py_GIL_DISABLED
@@ -393,7 +402,7 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, 
PyCodeObject *code, int
 
 PyAPI_FUNC(_PyInterpreterFrame *)
 _PyEvalFramePushAndInit(PyThreadState *tstate, _PyStackRef func,
-                        PyObject *locals, _PyStackRef const* args,
+                        PyObject *locals, _PyStackRef const *args,
                         size_t argcount, PyObject *kwnames,
                         _PyInterpreterFrame *previous);
 
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index 53403ebcfc0043..1c4c3e30fd2f6d 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -82,7 +82,7 @@ PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *);
 #define _PyObject_HEAD_INIT(type)         \
     {                                     \
         .ob_refcnt = _Py_IMMORTAL_INITIAL_REFCNT,  \
-        .ob_flags = _Py_STATICALLY_ALLOCATED_FLAG, \
+        .ob_flags = _Py_STATIC_FLAG_BITS, \
         .ob_type = (type)                 \
     }
 #else
diff --git a/Include/internal/pycore_opcode_metadata.h 
b/Include/internal/pycore_opcode_metadata.h
index d236206e295e1e..eda9b53629ce2d 100644
--- a/Include/internal/pycore_opcode_metadata.h
+++ b/Include/internal/pycore_opcode_metadata.h
@@ -2024,7 +2024,7 @@ const struct opcode_metadata 
_PyOpcode_opcode_metadata[266] = {
     [BINARY_OP_SUBSCR_GETITEM] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG },
     [BINARY_OP_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | 
HAS_ESCAPES_FLAG },
     [BINARY_OP_SUBSCR_STR_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | 
HAS_ESCAPES_FLAG },
-    [BINARY_OP_SUBSCR_TUPLE_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | 
HAS_ESCAPES_FLAG },
+    [BINARY_OP_SUBSCR_TUPLE_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG },
     [BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | 
HAS_ERROR_FLAG },
     [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | 
HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [BINARY_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
@@ -2048,9 +2048,9 @@ const struct opcode_metadata 
_PyOpcode_opcode_metadata[266] = {
     [CALL_INTRINSIC_2] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | 
HAS_ESCAPES_FLAG },
     [CALL_ISINSTANCE] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG 
| HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
     [CALL_KW] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_ERROR_FLAG | 
HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
-    [CALL_KW_BOUND_METHOD] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | 
HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | 
HAS_ESCAPES_FLAG },
+    [CALL_KW_BOUND_METHOD] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | 
HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [CALL_KW_NON_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | 
HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
-    [CALL_KW_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | 
HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
+    [CALL_KW_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | 
HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [CALL_LEN] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | 
HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
     [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | 
HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [CALL_METHOD_DESCRIPTOR_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | 
HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
diff --git a/Include/internal/pycore_stackref.h 
b/Include/internal/pycore_stackref.h
index 699f31371a2c7f..98ebf66946d726 100644
--- a/Include/internal/pycore_stackref.h
+++ b/Include/internal/pycore_stackref.h
@@ -60,8 +60,6 @@ extern "C" {
 
 #if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
 
-
-
 typedef union _PyStackRef {
     uint64_t index;
 } _PyStackRef;
@@ -153,6 +151,16 @@ _PyStackRef_CLOSE(_PyStackRef ref, const char *filename, 
int linenumber)
 }
 #define PyStackRef_CLOSE(REF) _PyStackRef_CLOSE((REF), __FILE__, __LINE__)
 
+static inline void
+PyStackRef_XCLOSE(_PyStackRef ref)
+{
+    if (PyStackRef_IsNull(ref)) {
+        return;
+    }
+    PyObject *obj = _Py_stackref_close(ref);
+    Py_DECREF(obj);
+}
+
 static inline _PyStackRef
 _PyStackRef_DUP(_PyStackRef ref, const char *filename, int linenumber)
 {
@@ -162,7 +170,36 @@ _PyStackRef_DUP(_PyStackRef ref, const char *filename, int 
linenumber)
 }
 #define PyStackRef_DUP(REF) _PyStackRef_DUP(REF, __FILE__, __LINE__)
 
-#define PyStackRef_CLOSE_SPECIALIZED(stackref, dealloc) 
PyStackRef_CLOSE(stackref)
+extern void PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct);
+
+static inline _PyStackRef
+PyStackRef_MakeHeapSafe(_PyStackRef ref)
+{
+    return ref;
+}
+
+#define PyStackRef_CLEAR(REF) \
+    do { \
+        _PyStackRef *_tmp_op_ptr = &(REF); \
+        _PyStackRef _tmp_old_op = (*_tmp_op_ptr); \
+        *_tmp_op_ptr = PyStackRef_NULL; \
+        PyStackRef_XCLOSE(_tmp_old_op); \
+    } while (0)
+
+static inline _PyStackRef
+_PyStackRef_FromPyObjectStealMortal(PyObject *obj, const char *filename, int 
linenumber)
+{
+    assert(!_Py_IsImmortal(obj));
+    return _Py_stackref_create(obj, filename, linenumber);
+}
+#define PyStackRef_FromPyObjectStealMortal(obj) 
_PyStackRef_FromPyObjectStealMortal(_PyObject_CAST(obj), __FILE__, __LINE__)
+
+static inline bool
+PyStackRef_IsHeapSafe(_PyStackRef ref)
+{
+    return true;
+}
+
 
 #else
 
@@ -171,12 +208,13 @@ typedef union _PyStackRef {
 } _PyStackRef;
 
 
+#ifdef Py_GIL_DISABLED
+
 #define Py_TAG_DEFERRED (1)
 
 #define Py_TAG_PTR      ((uintptr_t)0)
 #define Py_TAG_BITS     ((uintptr_t)1)
 
-#ifdef Py_GIL_DISABLED
 
 static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED};
 #define PyStackRef_IsNull(stackref) ((stackref).bits == PyStackRef_NULL.bits)
@@ -184,6 +222,11 @@ static const _PyStackRef PyStackRef_NULL = { .bits = 
Py_TAG_DEFERRED};
 #define PyStackRef_False ((_PyStackRef){.bits = ((uintptr_t)&_Py_FalseStruct) 
| Py_TAG_DEFERRED })
 #define PyStackRef_None ((_PyStackRef){.bits = ((uintptr_t)&_Py_NoneStruct) | 
Py_TAG_DEFERRED })
 
+// Checks that mask out the deferred bit in the free threading build.
+#define PyStackRef_IsNone(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_None)
+#define PyStackRef_IsTrue(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_True)
+#define PyStackRef_IsFalse(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_False)
+
 static inline PyObject *
 PyStackRef_AsPyObjectBorrow(_PyStackRef stackref)
 {
@@ -220,6 +263,17 @@ _PyStackRef_FromPyObjectSteal(PyObject *obj)
 }
 #   define PyStackRef_FromPyObjectSteal(obj) 
_PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj))
 
+
+static inline _PyStackRef
+PyStackRef_FromPyObjectStealMortal(PyObject *obj)
+{
+    assert(obj != NULL);
+    assert(!_Py_IsImmortal(obj));
+    // Make sure we don't take an already tagged value.
+    assert(((uintptr_t)obj & Py_TAG_BITS) == 0);
+    return (_PyStackRef){ .bits = (uintptr_t)obj };
+}
+
 static inline _PyStackRef
 PyStackRef_FromPyObjectNew(PyObject *obj)
 {
@@ -255,6 +309,13 @@ PyStackRef_FromPyObjectImmortal(PyObject *obj)
             }                                                           \
         } while (0)
 
+static inline void
+PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct)
+{
+    (void)destruct;
+    PyStackRef_CLOSE(ref);
+}
+
 static inline _PyStackRef
 PyStackRef_DUP(_PyStackRef stackref)
 {
@@ -269,6 +330,18 @@ PyStackRef_DUP(_PyStackRef stackref)
     return stackref;
 }
 
+static inline bool
+PyStackRef_IsHeapSafe(_PyStackRef ref)
+{
+    return true;
+}
+
+static inline _PyStackRef
+PyStackRef_MakeHeapSafe(_PyStackRef ref)
+{
+    return ref;
+}
+
 // Convert a possibly deferred reference to a strong reference.
 static inline _PyStackRef
 PyStackRef_AsStrongReference(_PyStackRef stackref)
@@ -276,35 +349,131 @@ PyStackRef_AsStrongReference(_PyStackRef stackref)
     return PyStackRef_FromPyObjectSteal(PyStackRef_AsPyObjectSteal(stackref));
 }
 
-#define PyStackRef_CLOSE_SPECIALIZED(stackref, dealloc) 
PyStackRef_CLOSE(stackref)
+#define PyStackRef_XCLOSE(stackref) \
+    do {                            \
+        _PyStackRef _tmp = (stackref); \
+        if (!PyStackRef_IsNull(_tmp)) { \
+            PyStackRef_CLOSE(_tmp); \
+        } \
+    } while (0);
 
+#define PyStackRef_CLEAR(op) \
+    do { \
+        _PyStackRef *_tmp_op_ptr = &(op); \
+        _PyStackRef _tmp_old_op = (*_tmp_op_ptr); \
+        if (!PyStackRef_IsNull(_tmp_old_op)) { \
+            *_tmp_op_ptr = PyStackRef_NULL; \
+            PyStackRef_CLOSE(_tmp_old_op); \
+        } \
+    } while (0)
+
+#define PyStackRef_FromPyObjectNewMortal PyStackRef_FromPyObjectNew
 
 #else // Py_GIL_DISABLED
 
 // With GIL
-static const _PyStackRef PyStackRef_NULL = { .bits = 0 };
-#define PyStackRef_IsNull(stackref) ((stackref).bits == 0)
-#define PyStackRef_True ((_PyStackRef){.bits = (uintptr_t)&_Py_TrueStruct })
-#define PyStackRef_False ((_PyStackRef){.bits = ((uintptr_t)&_Py_FalseStruct) 
})
-#define PyStackRef_None ((_PyStackRef){.bits = ((uintptr_t)&_Py_NoneStruct) })
 
-#define PyStackRef_AsPyObjectBorrow(stackref) ((PyObject *)(stackref).bits)
+/* References to immortal objects always have their tag bit set to 
Py_TAG_REFCNT
+ * as they can (must) have their reclamation deferred */
 
-#define PyStackRef_AsPyObjectSteal(stackref) 
PyStackRef_AsPyObjectBorrow(stackref)
+#define Py_TAG_BITS 1
+#define Py_TAG_REFCNT 1
+#if _Py_IMMORTAL_FLAGS != Py_TAG_REFCNT
+#  error "_Py_IMMORTAL_FLAGS != Py_TAG_REFCNT"
+#endif
 
-#define PyStackRef_FromPyObjectSteal(obj) ((_PyStackRef){.bits = 
((uintptr_t)(obj))})
+#define BITS_TO_PTR(REF) ((PyObject *)((REF).bits))
+#define BITS_TO_PTR_MASKED(REF) ((PyObject *)(((REF).bits) & (~Py_TAG_BITS)))
+
+#define PyStackRef_NULL_BITS Py_TAG_REFCNT
+static const _PyStackRef PyStackRef_NULL = { .bits = PyStackRef_NULL_BITS };
+
+#define PyStackRef_IsNull(ref) ((ref).bits == PyStackRef_NULL_BITS)
+#define PyStackRef_True ((_PyStackRef){.bits = ((uintptr_t)&_Py_TrueStruct) | 
Py_TAG_REFCNT })
+#define PyStackRef_False ((_PyStackRef){.bits = ((uintptr_t)&_Py_FalseStruct) 
| Py_TAG_REFCNT })
+#define PyStackRef_None ((_PyStackRef){.bits = ((uintptr_t)&_Py_NoneStruct) | 
Py_TAG_REFCNT })
+
+#define PyStackRef_IsTrue(REF) ((REF).bits == (((uintptr_t)&_Py_TrueStruct) | 
Py_TAG_REFCNT))
+#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 void PyStackRef_CheckValid(_PyStackRef ref) {
+    assert(ref.bits != 0);
+    int tag = ref.bits & Py_TAG_BITS;
+    PyObject *obj = BITS_TO_PTR_MASKED(ref);
+    switch (tag) {
+        case 0:
+            /* Can be immortal if object was made immortal after reference 
came into existence */
+            assert(!_Py_IsStaticImmortal(obj));
+            break;
+        case Py_TAG_REFCNT:
+            assert(obj == NULL || _Py_IsImmortal(obj));
+            break;
+        default:
+            assert(0);
+    }
+}
 
-#define PyStackRef_FromPyObjectNew(obj) ((_PyStackRef){ .bits = 
(uintptr_t)(Py_NewRef(obj)) })
+#else
 
-#define PyStackRef_FromPyObjectImmortal(obj) ((_PyStackRef){ .bits = 
(uintptr_t)(obj) })
+#define PyStackRef_CheckValid(REF) ((void)0)
 
-#define PyStackRef_CLOSE(stackref) 
Py_DECREF(PyStackRef_AsPyObjectBorrow(stackref))
+#endif
 
-#define PyStackRef_DUP(stackref) 
PyStackRef_FromPyObjectSteal(Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)))
+#ifdef _WIN32
+#define PyStackRef_RefcountOnObject(REF) (((REF).bits & Py_TAG_BITS) == 0)
+#define PyStackRef_AsPyObjectBorrow BITS_TO_PTR_MASKED
+#else
+/* Does this ref not have an embedded refcount and thus not refer to a 
declared immmortal object? */
+static inline int
+PyStackRef_RefcountOnObject(_PyStackRef ref)
+{
+    return (ref.bits & Py_TAG_BITS) == 0;
+}
 
-#define PyStackRef_CLOSE_SPECIALIZED(stackref, dealloc) 
_Py_DECREF_SPECIALIZED(PyStackRef_AsPyObjectBorrow(stackref), dealloc)
+static inline PyObject *
+PyStackRef_AsPyObjectBorrow(_PyStackRef ref)
+{
+    return BITS_TO_PTR_MASKED(ref);
+}
+#endif
 
-#endif // Py_GIL_DISABLED
+static inline PyObject *
+PyStackRef_AsPyObjectSteal(_PyStackRef ref)
+{
+    if (PyStackRef_RefcountOnObject(ref)) {
+        return BITS_TO_PTR(ref);
+    }
+    else {
+        return Py_NewRef(BITS_TO_PTR_MASKED(ref));
+    }
+}
+
+static inline _PyStackRef
+PyStackRef_FromPyObjectSteal(PyObject *obj)
+{
+    assert(obj != NULL);
+#if SIZEOF_VOID_P > 4
+    unsigned int tag = obj->ob_flags & Py_TAG_BITS;
+#else
+    unsigned int tag = _Py_IsImmortal(obj) ? Py_TAG_REFCNT : 0;
+#endif
+    _PyStackRef ref = ((_PyStackRef){.bits = ((uintptr_t)(obj)) | tag});
+    PyStackRef_CheckValid(ref);
+    return ref;
+}
+
+static inline _PyStackRef
+PyStackRef_FromPyObjectStealMortal(PyObject *obj)
+{
+    assert(obj != NULL);
+    assert(!_Py_IsImmortal(obj));
+    _PyStackRef ref = ((_PyStackRef){.bits = ((uintptr_t)(obj)) });
+    PyStackRef_CheckValid(ref);
+    return ref;
+}
 
 // Check if a stackref is exactly the same as another stackref, including the
 // the deferred bit. This can only be used safely if you know that the deferred
@@ -312,39 +481,136 @@ static const _PyStackRef PyStackRef_NULL = { .bits = 0 };
 #define PyStackRef_IsExactly(a, b) \
     (assert(((a).bits & Py_TAG_BITS) == ((b).bits & Py_TAG_BITS)), (a).bits == 
(b).bits)
 
-// Checks that mask out the deferred bit in the free threading build.
-#define PyStackRef_IsNone(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_None)
-#define PyStackRef_IsTrue(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_True)
-#define PyStackRef_IsFalse(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_False)
+static inline _PyStackRef
+_PyStackRef_FromPyObjectNew(PyObject *obj)
+{
+    assert(obj != NULL);
+    if (_Py_IsImmortal(obj)) {
+        return (_PyStackRef){ .bits = ((uintptr_t)obj) | Py_TAG_REFCNT};
+    }
+    Py_INCREF_MORTAL(obj);
+    _PyStackRef ref = (_PyStackRef){ .bits = (uintptr_t)obj };
+    PyStackRef_CheckValid(ref);
+    return ref;
+}
+#define PyStackRef_FromPyObjectNew(obj) 
_PyStackRef_FromPyObjectNew(_PyObject_CAST(obj))
 
+static inline _PyStackRef
+_PyStackRef_FromPyObjectNewMortal(PyObject *obj)
+{
+    assert(obj != NULL);
+    Py_INCREF_MORTAL(obj);
+    _PyStackRef ref = (_PyStackRef){ .bits = (uintptr_t)obj };
+    PyStackRef_CheckValid(ref);
+    return ref;
+}
+#define PyStackRef_FromPyObjectNewMortal(obj) 
_PyStackRef_FromPyObjectNewMortal(_PyObject_CAST(obj))
+
+/* Create a new reference from an object with an embedded reference count */
+static inline _PyStackRef
+PyStackRef_FromPyObjectImmortal(PyObject *obj)
+{
+    assert(_Py_IsImmortal(obj));
+    return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT};
+}
+
+/* WARNING: This macro evaluates its argument more than once */
+#ifdef _WIN32
+#define PyStackRef_DUP(REF) \
+    (PyStackRef_RefcountOnObject(REF) ? (Py_INCREF_MORTAL(BITS_TO_PTR(REF)), 
(REF)) : (REF))
+#else
+static inline _PyStackRef
+PyStackRef_DUP(_PyStackRef ref)
+{
+    assert(!PyStackRef_IsNull(ref));
+    if (PyStackRef_RefcountOnObject(ref)) {
+        Py_INCREF_MORTAL(BITS_TO_PTR(ref));
+    }
+    return ref;
+}
 #endif
 
-// Converts a PyStackRef back to a PyObject *, converting the
-// stackref to a new reference.
-#define PyStackRef_AsPyObjectNew(stackref) 
Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref))
+static inline bool
+PyStackRef_IsHeapSafe(_PyStackRef ref)
+{
+    return (ref.bits & Py_TAG_BITS) == 0 || ref.bits == PyStackRef_NULL_BITS 
||  _Py_IsImmortal(BITS_TO_PTR_MASKED(ref));
+}
 
-#define PyStackRef_TYPE(stackref) 
Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref))
+static inline _PyStackRef
+PyStackRef_MakeHeapSafe(_PyStackRef ref)
+{
+    if (PyStackRef_IsHeapSafe(ref)) {
+        return ref;
+    }
+    PyObject *obj = BITS_TO_PTR_MASKED(ref);
+    Py_INCREF(obj);
+    ref.bits = (uintptr_t)obj;
+    PyStackRef_CheckValid(ref);
+    return ref;
+}
 
+#ifdef _WIN32
+#define PyStackRef_CLOSE(REF) \
+do { \
+    _PyStackRef _temp = (REF); \
+    if (PyStackRef_RefcountOnObject(_temp)) 
Py_DECREF_MORTAL(BITS_TO_PTR(_temp)); \
+} while (0)
+#else
+static inline void
+PyStackRef_CLOSE(_PyStackRef ref)
+{
+    assert(!PyStackRef_IsNull(ref));
+    if (PyStackRef_RefcountOnObject(ref)) {
+        Py_DECREF_MORTAL(BITS_TO_PTR(ref));
+    }
+}
+#endif
 
-#define PyStackRef_CLEAR(op) \
+static inline void
+PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct)
+{
+    assert(!PyStackRef_IsNull(ref));
+    if (PyStackRef_RefcountOnObject(ref)) {
+        Py_DECREF_MORTAL_SPECIALIZED(BITS_TO_PTR(ref), destruct);
+    }
+}
+
+#ifdef _WIN32
+#define PyStackRef_XCLOSE PyStackRef_CLOSE
+#else
+static inline void
+PyStackRef_XCLOSE(_PyStackRef ref)
+{
+    assert(ref.bits != 0);
+    if (PyStackRef_RefcountOnObject(ref)) {
+        assert(!PyStackRef_IsNull(ref));
+        Py_DECREF_MORTAL(BITS_TO_PTR(ref));
+    }
+}
+#endif
+
+#define PyStackRef_CLEAR(REF) \
     do { \
-        _PyStackRef *_tmp_op_ptr = &(op); \
+        _PyStackRef *_tmp_op_ptr = &(REF); \
         _PyStackRef _tmp_old_op = (*_tmp_op_ptr); \
-        if (!PyStackRef_IsNull(_tmp_old_op)) { \
-            *_tmp_op_ptr = PyStackRef_NULL; \
-            PyStackRef_CLOSE(_tmp_old_op); \
-        } \
+        *_tmp_op_ptr = PyStackRef_NULL; \
+        PyStackRef_XCLOSE(_tmp_old_op); \
     } while (0)
 
-#define PyStackRef_XCLOSE(stackref) \
-    do {                            \
-        _PyStackRef _tmp = (stackref); \
-        if (!PyStackRef_IsNull(_tmp)) { \
-            PyStackRef_CLOSE(_tmp); \
-        } \
-    } while (0);
 
+#endif // Py_GIL_DISABLED
+
+// Note: this is a macro because MSVC (Windows) has trouble inlining it.
+
+#define PyStackRef_Is(a, b) (((a).bits & (~Py_TAG_REFCNT)) == ((b).bits & 
(~Py_TAG_REFCNT)))
+
+#endif // !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
 
+#define PyStackRef_TYPE(stackref) 
Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref))
+
+// Converts a PyStackRef back to a PyObject *, converting the
+// stackref to a new reference.
+#define PyStackRef_AsPyObjectNew(stackref) 
Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref))
 
 // StackRef type checks
 
diff --git a/Include/internal/pycore_uop_metadata.h 
b/Include/internal/pycore_uop_metadata.h
index fe4857e827478c..883a07a408ca92 100644
--- a/Include/internal/pycore_uop_metadata.h
+++ b/Include/internal/pycore_uop_metadata.h
@@ -88,7 +88,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
     [_STORE_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
     [_BINARY_OP_SUBSCR_LIST_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
     [_BINARY_OP_SUBSCR_STR_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
-    [_BINARY_OP_SUBSCR_TUPLE_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
+    [_BINARY_OP_SUBSCR_TUPLE_INT] = HAS_DEOPT_FLAG,
     [_BINARY_OP_SUBSCR_DICT] = HAS_DEOPT_FLAG | HAS_ERROR_FLAG | 
HAS_ESCAPES_FLAG,
     [_BINARY_OP_SUBSCR_CHECK_FUNC] = HAS_DEOPT_FLAG,
     [_BINARY_OP_SUBSCR_INIT_CALL] = 0,
@@ -242,7 +242,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
     [_CALL_METHOD_DESCRIPTOR_NOARGS] = HAS_ARG_FLAG | HAS_EXIT_FLAG | 
HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
     [_CALL_METHOD_DESCRIPTOR_FAST] = HAS_ARG_FLAG | HAS_EXIT_FLAG | 
HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
     [_MAYBE_EXPAND_METHOD_KW] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG,
-    [_PY_FRAME_KW] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | 
HAS_ESCAPES_FLAG,
+    [_PY_FRAME_KW] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
     [_CHECK_FUNCTION_VERSION_KW] = HAS_ARG_FLAG | HAS_EXIT_FLAG,
     [_CHECK_METHOD_VERSION_KW] = HAS_ARG_FLAG | HAS_EXIT_FLAG,
     [_EXPAND_METHOD_KW] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG,
diff --git a/Include/object.h b/Include/object.h
index 7d1667d889cb43..8cc83abb8574e3 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -124,11 +124,13 @@ struct _object {
         PY_INT64_T ob_refcnt_full; /* This field is needed for efficient 
initialization with Clang on ARM */
         struct {
 #  if PY_BIG_ENDIAN
-            PY_UINT32_T ob_flags;
-            PY_UINT32_T ob_refcnt;
+            uint16_t ob_flags;
+            uint16_t ob_overflow;
+            uint32_t ob_refcnt;
 #  else
-            PY_UINT32_T ob_refcnt;
-            PY_UINT32_T ob_flags;
+            uint32_t ob_refcnt;
+            uint16_t ob_overflow;
+            uint16_t ob_flags;
 #  endif
         };
 #else
diff --git a/Include/refcount.h b/Include/refcount.h
index dbc69ee02b017d..88918409d934f6 100644
--- a/Include/refcount.h
+++ b/Include/refcount.h
@@ -19,8 +19,8 @@ immortal. The latter should be the only instances that require
 cleanup during runtime finalization.
 */
 
-/* Leave the low bits for refcount overflow for old stable ABI code */
-#define _Py_STATICALLY_ALLOCATED_FLAG (1 << 7)
+#define _Py_STATICALLY_ALLOCATED_FLAG 4
+#define _Py_IMMORTAL_FLAGS 1
 
 #if SIZEOF_VOID_P > 4
 /*
@@ -43,7 +43,8 @@ be done by checking the bit sign flag in the lower 32 bits.
 
 */
 #define _Py_IMMORTAL_INITIAL_REFCNT (3UL << 30)
-#define _Py_STATIC_IMMORTAL_INITIAL_REFCNT 
((Py_ssize_t)(_Py_IMMORTAL_INITIAL_REFCNT | 
(((Py_ssize_t)_Py_STATICALLY_ALLOCATED_FLAG) << 32)))
+#define _Py_STATIC_FLAG_BITS ((Py_ssize_t)(_Py_STATICALLY_ALLOCATED_FLAG | 
_Py_IMMORTAL_FLAGS))
+#define _Py_STATIC_IMMORTAL_INITIAL_REFCNT 
(((Py_ssize_t)_Py_IMMORTAL_INITIAL_REFCNT) | (_Py_STATIC_FLAG_BITS << 48))
 
 #else
 /*
@@ -114,7 +115,6 @@ PyAPI_FUNC(Py_ssize_t) Py_REFCNT(PyObject *ob);
     #endif
 #endif
 
-
 static inline Py_ALWAYS_INLINE int _Py_IsImmortal(PyObject *op)
 {
 #if defined(Py_GIL_DISABLED)
@@ -242,6 +242,18 @@ PyAPI_FUNC(void) Py_DecRef(PyObject *);
 PyAPI_FUNC(void) _Py_IncRef(PyObject *);
 PyAPI_FUNC(void) _Py_DecRef(PyObject *);
 
+#ifndef Py_GIL_DISABLED
+static inline Py_ALWAYS_INLINE void Py_INCREF_MORTAL(PyObject *op)
+{
+    assert(!_Py_IsStaticImmortal(op));
+    op->ob_refcnt++;
+    _Py_INCREF_STAT_INC();
+#if defined(Py_REF_DEBUG) && !defined(Py_LIMITED_API)
+    _Py_INCREF_IncRefTotal();
+#endif
+}
+#endif
+
 static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
 {
 #if defined(Py_LIMITED_API) && (Py_LIMITED_API+0 >= 0x030c0000 || 
defined(Py_REF_DEBUG))
@@ -372,6 +384,36 @@ static inline void Py_DECREF(PyObject *op)
 #define Py_DECREF(op) Py_DECREF(_PyObject_CAST(op))
 
 #elif defined(Py_REF_DEBUG)
+static inline void Py_DECREF_MORTAL(const char *filename, int lineno, PyObject 
*op)
+{
+    if (op->ob_refcnt <= 0) {
+        _Py_NegativeRefcount(filename, lineno, op);
+    }
+    _Py_DECREF_STAT_INC();
+    assert(!_Py_IsStaticImmortal(op));
+    _Py_DECREF_DecRefTotal();
+    if (--op->ob_refcnt == 0) {
+        _Py_Dealloc(op);
+    }
+}
+#define Py_DECREF_MORTAL(op) Py_DECREF_MORTAL(__FILE__, __LINE__, 
_PyObject_CAST(op))
+
+
+
+static inline void _Py_DECREF_MORTAL_SPECIALIZED(const char *filename, int 
lineno, PyObject *op, destructor destruct)
+{
+    if (op->ob_refcnt <= 0) {
+        _Py_NegativeRefcount(filename, lineno, op);
+    }
+    _Py_DECREF_STAT_INC();
+    assert(!_Py_IsStaticImmortal(op));
+    _Py_DECREF_DecRefTotal();
+    if (--op->ob_refcnt == 0) {
+        destruct(op);
+    }
+}
+#define Py_DECREF_MORTAL_SPECIALIZED(op, destruct) 
_Py_DECREF_MORTAL_SPECIALIZED(__FILE__, __LINE__, op, destruct)
+
 static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
 {
 #if SIZEOF_VOID_P > 4
@@ -396,6 +438,26 @@ static inline void Py_DECREF(const char *filename, int 
lineno, PyObject *op)
 #define Py_DECREF(op) Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
 
 #else
+static inline void Py_DECREF_MORTAL(PyObject *op)
+{
+    assert(!_Py_IsStaticImmortal(op));
+    _Py_DECREF_STAT_INC();
+    if (--op->ob_refcnt == 0) {
+        _Py_Dealloc(op);
+    }
+}
+#define Py_DECREF_MORTAL(op) Py_DECREF_MORTAL(_PyObject_CAST(op))
+
+static inline void Py_DECREF_MORTAL_SPECIALIZED(PyObject *op, destructor 
destruct)
+{
+    assert(!_Py_IsStaticImmortal(op));
+    _Py_DECREF_STAT_INC();
+    if (--op->ob_refcnt == 0) {
+        destruct(op);
+    }
+}
+#define Py_DECREF_MORTAL_SPECIALIZED(op, destruct) 
Py_DECREF_MORTAL_SPECIALIZED(_PyObject_CAST(op), destruct)
+
 static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
 {
     // Non-limited C API and limited C API for Python 3.9 and older access
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index c3cc13614250f0..0d4b42eb2ab7be 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -2715,18 +2715,15 @@ def __del__(self):
 class ImmortalTests(unittest.TestCase):
 
     if sys.maxsize < (1 << 32):
-        if support.Py_GIL_DISABLED:
-            IMMORTAL_REFCOUNT = 5 << 28
-        else:
-            IMMORTAL_REFCOUNT = 7 << 28
+        IMMORTAL_REFCOUNT_MINIMUM = 1 << 30
     else:
-        IMMORTAL_REFCOUNT = 3 << 30
+        IMMORTAL_REFCOUNT_MINIMUM = 1 << 31
 
     IMMORTALS = (None, True, False, Ellipsis, NotImplemented, *range(-5, 257))
 
     def assert_immortal(self, immortal):
         with self.subTest(immortal):
-            self.assertEqual(sys.getrefcount(immortal), self.IMMORTAL_REFCOUNT)
+            self.assertGreater(sys.getrefcount(immortal), 
self.IMMORTAL_REFCOUNT_MINIMUM)
 
     def test_immortals(self):
         for immortal in self.IMMORTALS:
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-17-12-48-00.gh-issue-127705.Qad2hx.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-17-12-48-00.gh-issue-127705.Qad2hx.rst
new file mode 100644
index 00000000000000..bf7e6eaba911f6
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-17-12-48-00.gh-issue-127705.Qad2hx.rst
@@ -0,0 +1,5 @@
+Use tagged references (``_PyStackRef``) for the default build as well as for
+the free-threading build. This has a small negative performance impact
+short-term but will enable larger speedups in the future and signficantly
+reduce maintenance costs by allowing a single implementation of tagged
+references in the future.
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index 3b72a1e7c37b7f..309b8ddcd23839 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -136,34 +136,37 @@ PyFloat_FromDouble(double fval)
 
 #ifdef Py_GIL_DISABLED
 
-PyObject *_PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, _PyStackRef 
right, double value)
+_PyStackRef _PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, _PyStackRef 
right, double value)
 {
-    PyStackRef_CLOSE(left);
-    PyStackRef_CLOSE(right);
-    return PyFloat_FromDouble(value);
+    PyStackRef_CLOSE_SPECIALIZED(left, _PyFloat_ExactDealloc);
+    PyStackRef_CLOSE_SPECIALIZED(right, _PyFloat_ExactDealloc);
+    return PyStackRef_FromPyObjectSteal(PyFloat_FromDouble(value));
 }
 
 #else // Py_GIL_DISABLED
 
-PyObject *_PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, _PyStackRef 
right, double value)
+_PyStackRef _PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, _PyStackRef 
right, double value)
 {
-    PyObject *left_o = PyStackRef_AsPyObjectSteal(left);
-    PyObject *right_o = PyStackRef_AsPyObjectSteal(right);
+    PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
+    PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
     if (Py_REFCNT(left_o) == 1) {
         ((PyFloatObject *)left_o)->ob_fval = value;
-        _Py_DECREF_SPECIALIZED(right_o, _PyFloat_ExactDealloc);
-        return left_o;
+        PyStackRef_CLOSE_SPECIALIZED(right, _PyFloat_ExactDealloc);
+        return left;
     }
     else if (Py_REFCNT(right_o) == 1)  {
         ((PyFloatObject *)right_o)->ob_fval = value;
-        _Py_DECREF_NO_DEALLOC(left_o);
-        return right_o;
+        PyStackRef_CLOSE_SPECIALIZED(left, _PyFloat_ExactDealloc);
+        return right;
     }
     else {
         PyObject *result = PyFloat_FromDouble(value);
-        _Py_DECREF_NO_DEALLOC(left_o);
-        _Py_DECREF_NO_DEALLOC(right_o);
-        return result;
+        PyStackRef_CLOSE_SPECIALIZED(left, _PyFloat_ExactDealloc);
+        PyStackRef_CLOSE_SPECIALIZED(right, _PyFloat_ExactDealloc);
+        if (result == NULL) {
+            return PyStackRef_NULL;
+        }
+        return PyStackRef_FromPyObjectStealMortal(result);
     }
 }
 
diff --git a/Objects/object.c b/Objects/object.c
index 051b668dfcbb64..58aec5f2b5b3e1 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -2540,6 +2540,9 @@ _Py_SetImmortalUntracked(PyObject *op)
     op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
     op->ob_ref_shared = 0;
     _Py_atomic_or_uint8(&op->ob_gc_bits, _PyGC_BITS_DEFERRED);
+#elif SIZEOF_VOID_P > 4
+    op->ob_flags = _Py_IMMORTAL_FLAGS;
+    op->ob_refcnt = _Py_IMMORTAL_INITIAL_REFCNT;
 #else
     op->ob_refcnt = _Py_IMMORTAL_INITIAL_REFCNT;
 #endif
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 5191b5785f7654..17dc0c5bfb5c9f 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -311,7 +311,7 @@ dummy_func(
 
         inst(LOAD_CONST_MORTAL, (-- value)) {
             PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
-            value = PyStackRef_FromPyObjectNew(obj);
+            value = PyStackRef_FromPyObjectNewMortal(obj);
         }
 
         inst(LOAD_CONST_IMMORTAL, (-- value)) {
@@ -327,6 +327,10 @@ dummy_func(
         }
 
         replicate(8) inst(STORE_FAST, (value --)) {
+            assert(
+                ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             DEAD(value);
@@ -338,6 +342,10 @@ dummy_func(
         };
 
         inst(STORE_FAST_LOAD_FAST, (value1 -- value2)) {
+            assert(
+                ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                PyStackRef_IsHeapSafe(value1)
+            );
             uint32_t oparg1 = oparg >> 4;
             uint32_t oparg2 = oparg & 15;
             _PyStackRef tmp = GETLOCAL(oparg1);
@@ -348,6 +356,14 @@ dummy_func(
         }
 
         inst(STORE_FAST_STORE_FAST, (value2, value1 --)) {
+            assert(
+                ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                PyStackRef_IsHeapSafe(value1)
+            );
+            assert(
+                ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                PyStackRef_IsHeapSafe(value2)
+            );
             uint32_t oparg1 = oparg >> 4;
             uint32_t oparg2 = oparg & 15;
             _PyStackRef tmp = GETLOCAL(oparg1);
@@ -642,10 +658,9 @@ dummy_func(
             double dres =
                 ((PyFloatObject *)left_o)->ob_fval *
                 ((PyFloatObject *)right_o)->ob_fval;
-            PyObject *res_o = _PyFloat_FromDouble_ConsumeInputs(left, right, 
dres);
+            res = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
             INPUTS_DEAD();
-            ERROR_IF(res_o == NULL, error);
-            res = PyStackRef_FromPyObjectSteal(res_o);
+            ERROR_IF(PyStackRef_IsNull(res), error);
         }
 
         pure op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) {
@@ -658,10 +673,9 @@ dummy_func(
             double dres =
                 ((PyFloatObject *)left_o)->ob_fval +
                 ((PyFloatObject *)right_o)->ob_fval;
-            PyObject *res_o = _PyFloat_FromDouble_ConsumeInputs(left, right, 
dres);
+            res = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
             INPUTS_DEAD();
-            ERROR_IF(res_o == NULL, error);
-            res = PyStackRef_FromPyObjectSteal(res_o);
+            ERROR_IF(PyStackRef_IsNull(res), error);
         }
 
         pure op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) {
@@ -674,10 +688,9 @@ dummy_func(
             double dres =
                 ((PyFloatObject *)left_o)->ob_fval -
                 ((PyFloatObject *)right_o)->ob_fval;
-            PyObject *res_o = _PyFloat_FromDouble_ConsumeInputs(left, right, 
dres);
+            res = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
             INPUTS_DEAD();
-            ERROR_IF(res_o == NULL, error);
-            res = PyStackRef_FromPyObjectSteal(res_o);
+            ERROR_IF(PyStackRef_IsNull(res), error);
         }
 
         macro(BINARY_OP_MULTIPLY_FLOAT) =
@@ -733,6 +746,7 @@ dummy_func(
             next_oparg = CURRENT_OPERAND0();
         #endif
             _PyStackRef *target_local = &GETLOCAL(next_oparg);
+            assert(PyUnicode_CheckExact(left_o));
             DEOPT_IF(PyStackRef_AsPyObjectBorrow(*target_local) != left_o);
             STAT_INC(BINARY_OP, hit);
             /* Handle `left = left + right` or `left += right` for str.
@@ -856,17 +870,16 @@ dummy_func(
             PyObject *res_o = _PyList_GetItemRef((PyListObject*)list, index);
             DEOPT_IF(res_o == NULL);
             STAT_INC(BINARY_OP, hit);
+            res = PyStackRef_FromPyObjectSteal(res_o);
 #else
             DEOPT_IF(index >= PyList_GET_SIZE(list));
             STAT_INC(BINARY_OP, hit);
             PyObject *res_o = PyList_GET_ITEM(list, index);
             assert(res_o != NULL);
-            Py_INCREF(res_o);
+            res = PyStackRef_FromPyObjectNew(res_o);
 #endif
-            PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
-            DEAD(sub_st);
-            PyStackRef_CLOSE(list_st);
-            res = PyStackRef_FromPyObjectSteal(res_o);
+            STAT_INC(BINARY_SUBSCR, hit);
+            DECREF_INPUTS();
         }
 
         inst(BINARY_OP_SUBSCR_STR_INT, (unused/5, str_st, sub_st -- res)) {
@@ -886,7 +899,7 @@ dummy_func(
             PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
             DEAD(sub_st);
             PyStackRef_CLOSE(str_st);
-            res = PyStackRef_FromPyObjectSteal(res_o);
+            res = PyStackRef_FromPyObjectImmortal(res_o);
         }
 
         inst(BINARY_OP_SUBSCR_TUPLE_INT, (unused/5, tuple_st, sub_st -- res)) {
@@ -903,11 +916,9 @@ dummy_func(
             STAT_INC(BINARY_OP, hit);
             PyObject *res_o = PyTuple_GET_ITEM(tuple, index);
             assert(res_o != NULL);
-            Py_INCREF(res_o);
             PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
-            DEAD(sub_st);
-            PyStackRef_CLOSE(tuple_st);
-            res = PyStackRef_FromPyObjectSteal(res_o);
+            res = PyStackRef_FromPyObjectNew(res_o);
+            DECREF_INPUTS();
         }
 
         inst(BINARY_OP_SUBSCR_DICT, (unused/5, dict_st, sub_st -- res)) {
@@ -1094,6 +1105,7 @@ dummy_func(
         inst(RETURN_VALUE, (retval -- res)) {
             assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
             _PyStackRef temp = retval;
+            assert(PyStackRef_IsHeapSafe(temp));
             DEAD(retval);
             SAVE_STACK();
             assert(EMPTY());
@@ -1855,7 +1867,7 @@ dummy_func(
                 ERROR_NO_POP();
             }
             INPUTS_DEAD();
-            tup = PyStackRef_FromPyObjectSteal(tup_o);
+            tup = PyStackRef_FromPyObjectStealMortal(tup_o);
         }
 
         inst(BUILD_LIST, (values[oparg] -- list)) {
@@ -1864,7 +1876,7 @@ dummy_func(
                 ERROR_NO_POP();
             }
             INPUTS_DEAD();
-            list = PyStackRef_FromPyObjectSteal(list_o);
+            list = PyStackRef_FromPyObjectStealMortal(list_o);
         }
 
         inst(LIST_EXTEND, (list_st, unused[oparg-1], iterable_st -- list_st, 
unused[oparg-1])) {
@@ -1913,7 +1925,7 @@ dummy_func(
                 Py_DECREF(set_o);
                 ERROR_IF(true, error);
             }
-            set = PyStackRef_FromPyObjectSteal(set_o);
+            set = PyStackRef_FromPyObjectStealMortal(set_o);
         }
 
         inst(BUILD_MAP, (values[oparg*2] -- map)) {
@@ -1929,7 +1941,7 @@ dummy_func(
             STACKREFS_TO_PYOBJECTS_CLEANUP(values_o);
             DECREF_INPUTS();
             ERROR_IF(map_o == NULL, error);
-            map = PyStackRef_FromPyObjectSteal(map_o);
+            map = PyStackRef_FromPyObjectStealMortal(map_o);
         }
 
         inst(SETUP_ANNOTATIONS, (--)) {
@@ -3789,7 +3801,7 @@ dummy_func(
             DEOPT_IF(callable_o != (PyObject *)&PyType_Type);
             DEAD(callable);
             STAT_INC(CALL, hit);
-            res = PyStackRef_FromPyObjectSteal(Py_NewRef(Py_TYPE(arg_o)));
+            res = PyStackRef_FromPyObjectNew(Py_TYPE(arg_o));
             PyStackRef_CLOSE(arg);
         }
 
@@ -4413,9 +4425,7 @@ dummy_func(
             // The frame has stolen all the arguments from the stack,
             // so there is no need to clean them up.
             SYNC_SP();
-            if (temp == NULL) {
-                ERROR_NO_POP();
-            }
+            ERROR_IF(temp == NULL, error);
             new_frame = temp;
         }
 
@@ -4695,7 +4705,7 @@ dummy_func(
             frame = tstate->current_frame = prev;
             LOAD_IP(frame->return_offset);
             RELOAD_STACK();
-            res = PyStackRef_FromPyObjectSteal((PyObject *)gen);
+            res = PyStackRef_FromPyObjectStealMortal((PyObject *)gen);
             LLTRACE_RESUME_FRAME();
         }
 
@@ -4706,7 +4716,7 @@ dummy_func(
             PyObject *slice_o = PySlice_New(start_o, stop_o, step_o);
             DECREF_INPUTS();
             ERROR_IF(slice_o == NULL, error);
-            slice = PyStackRef_FromPyObjectSteal(slice_o);
+            slice = PyStackRef_FromPyObjectStealMortal(slice_o);
         }
 
         inst(CONVERT_VALUE, (value -- result)) {
diff --git a/Python/ceval.c b/Python/ceval.c
index 0a3b30513733bd..a702d296e2247a 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -133,36 +133,55 @@
 
 
 #ifdef Py_DEBUG
+static void
+dump_item(_PyStackRef item)
+{
+    if (PyStackRef_IsNull(item)) {
+        printf("<NULL>");
+        return;
+    }
+    PyObject *obj = PyStackRef_AsPyObjectBorrow(item);
+    if (obj == NULL) {
+        printf("<nil>");
+        return;
+    }
+    if (
+        obj == Py_None
+        || PyBool_Check(obj)
+        || PyLong_CheckExact(obj)
+        || PyFloat_CheckExact(obj)
+        || PyUnicode_CheckExact(obj)
+    ) {
+        if (PyObject_Print(obj, stdout, 0) == 0) {
+            return;
+        }
+        PyErr_Clear();
+    }
+    // Don't call __repr__(), it might recurse into the interpreter.
+    printf("<%s at %p>", Py_TYPE(obj)->tp_name, (void *)obj);
+}
+
 static void
 dump_stack(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer)
 {
     _PyFrame_SetStackPointer(frame, stack_pointer);
+    _PyStackRef *locals_base = _PyFrame_GetLocalsArray(frame);
     _PyStackRef *stack_base = _PyFrame_Stackbase(frame);
     PyObject *exc = PyErr_GetRaisedException();
+    printf("    locals=[");
+    for (_PyStackRef *ptr = locals_base; ptr < stack_base; ptr++) {
+        if (ptr != locals_base) {
+            printf(", ");
+        }
+        dump_item(*ptr);
+    }
+    printf("]\n");
     printf("    stack=[");
     for (_PyStackRef *ptr = stack_base; ptr < stack_pointer; ptr++) {
         if (ptr != stack_base) {
             printf(", ");
         }
-        PyObject *obj = PyStackRef_AsPyObjectBorrow(*ptr);
-        if (obj == NULL) {
-            printf("<nil>");
-            continue;
-        }
-        if (
-            obj == Py_None
-            || PyBool_Check(obj)
-            || PyLong_CheckExact(obj)
-            || PyFloat_CheckExact(obj)
-            || PyUnicode_CheckExact(obj)
-        ) {
-            if (PyObject_Print(obj, stdout, 0) == 0) {
-                continue;
-            }
-            PyErr_Clear();
-        }
-        // Don't call __repr__(), it might recurse into the interpreter.
-        printf("<%s at %p>", Py_TYPE(obj)->tp_name, 
PyStackRef_AsPyObjectBorrow(*ptr));
+        dump_item(*ptr);
     }
     printf("]\n");
     fflush(stdout);
@@ -1390,7 +1409,6 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject 
*func,
 {
     PyCodeObject *co = (PyCodeObject*)func->func_code;
     const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount;
-
     /* Create a dictionary for keyword parameters (**kwags) */
     PyObject *kwdict;
     Py_ssize_t i;
diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h
index 1bef2b845d0738..f19ffd23161ace 100644
--- a/Python/ceval_macros.h
+++ b/Python/ceval_macros.h
@@ -440,28 +440,13 @@ do { \
 /* How much scratch space to give stackref to PyObject* conversion. */
 #define MAX_STACKREF_SCRATCH 10
 
-#if defined(Py_GIL_DISABLED) || defined(Py_STACKREF_DEBUG)
 #define STACKREFS_TO_PYOBJECTS(ARGS, ARG_COUNT, NAME) \
     /* +1 because vectorcall might use -1 to write self */ \
     PyObject *NAME##_temp[MAX_STACKREF_SCRATCH+1]; \
     PyObject **NAME = _PyObjectArray_FromStackRefArray(ARGS, ARG_COUNT, 
NAME##_temp + 1);
-#else
-#define STACKREFS_TO_PYOBJECTS(ARGS, ARG_COUNT, NAME) \
-    PyObject **NAME = (PyObject **)ARGS; \
-    assert(NAME != NULL);
-#endif
 
-#if defined(Py_GIL_DISABLED) || defined(Py_STACKREF_DEBUG)
 #define STACKREFS_TO_PYOBJECTS_CLEANUP(NAME) \
     /* +1 because we +1 previously */ \
     _PyObjectArray_Free(NAME - 1, NAME##_temp);
-#else
-#define STACKREFS_TO_PYOBJECTS_CLEANUP(NAME) \
-    (void)(NAME);
-#endif
 
-#if defined(Py_GIL_DISABLED) || defined(Py_STACKREF_DEBUG)
 #define CONVERSION_FAILED(NAME) ((NAME) == NULL)
-#else
-#define CONVERSION_FAILED(NAME) (0)
-#endif
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 94f05c62089056..8886564cded600 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -218,7 +218,7 @@
             _PyStackRef value;
             oparg = CURRENT_OPARG();
             PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
-            value = PyStackRef_FromPyObjectNew(obj);
+            value = PyStackRef_FromPyObjectNewMortal(obj);
             stack_pointer[0] = value;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
@@ -306,6 +306,10 @@
             oparg = 0;
             assert(oparg == CURRENT_OPARG());
             value = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             stack_pointer += -1;
@@ -321,6 +325,10 @@
             oparg = 1;
             assert(oparg == CURRENT_OPARG());
             value = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             stack_pointer += -1;
@@ -336,6 +344,10 @@
             oparg = 2;
             assert(oparg == CURRENT_OPARG());
             value = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             stack_pointer += -1;
@@ -351,6 +363,10 @@
             oparg = 3;
             assert(oparg == CURRENT_OPARG());
             value = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             stack_pointer += -1;
@@ -366,6 +382,10 @@
             oparg = 4;
             assert(oparg == CURRENT_OPARG());
             value = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             stack_pointer += -1;
@@ -381,6 +401,10 @@
             oparg = 5;
             assert(oparg == CURRENT_OPARG());
             value = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             stack_pointer += -1;
@@ -396,6 +420,10 @@
             oparg = 6;
             assert(oparg == CURRENT_OPARG());
             value = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             stack_pointer += -1;
@@ -411,6 +439,10 @@
             oparg = 7;
             assert(oparg == CURRENT_OPARG());
             value = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             stack_pointer += -1;
@@ -425,6 +457,10 @@
             _PyStackRef value;
             oparg = CURRENT_OPARG();
             value = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             stack_pointer += -1;
@@ -863,13 +899,13 @@
             double dres =
             ((PyFloatObject *)left_o)->ob_fval *
             ((PyFloatObject *)right_o)->ob_fval;
-            PyObject *res_o = _PyFloat_FromDouble_ConsumeInputs(left, right, 
dres);
-            if (res_o == NULL) {
-                stack_pointer += -2;
+            res = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
+            if (PyStackRef_IsNull(res)) {
+                stack_pointer[-2] = res;
+                stack_pointer += -1;
                 assert(WITHIN_STACK_BOUNDS());
                 JUMP_TO_ERROR();
             }
-            res = PyStackRef_FromPyObjectSteal(res_o);
             stack_pointer[-2] = res;
             stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());
@@ -890,13 +926,13 @@
             double dres =
             ((PyFloatObject *)left_o)->ob_fval +
             ((PyFloatObject *)right_o)->ob_fval;
-            PyObject *res_o = _PyFloat_FromDouble_ConsumeInputs(left, right, 
dres);
-            if (res_o == NULL) {
-                stack_pointer += -2;
+            res = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
+            if (PyStackRef_IsNull(res)) {
+                stack_pointer[-2] = res;
+                stack_pointer += -1;
                 assert(WITHIN_STACK_BOUNDS());
                 JUMP_TO_ERROR();
             }
-            res = PyStackRef_FromPyObjectSteal(res_o);
             stack_pointer[-2] = res;
             stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());
@@ -917,13 +953,13 @@
             double dres =
             ((PyFloatObject *)left_o)->ob_fval -
             ((PyFloatObject *)right_o)->ob_fval;
-            PyObject *res_o = _PyFloat_FromDouble_ConsumeInputs(left, right, 
dres);
-            if (res_o == NULL) {
-                stack_pointer += -2;
+            res = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
+            if (PyStackRef_IsNull(res)) {
+                stack_pointer[-2] = res;
+                stack_pointer += -1;
                 assert(WITHIN_STACK_BOUNDS());
                 JUMP_TO_ERROR();
             }
-            res = PyStackRef_FromPyObjectSteal(res_o);
             stack_pointer[-2] = res;
             stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());
@@ -991,6 +1027,7 @@
             next_oparg = CURRENT_OPERAND0();
             #endif
             _PyStackRef *target_local = &GETLOCAL(next_oparg);
+            assert(PyUnicode_CheckExact(left_o));
             if (PyStackRef_AsPyObjectBorrow(*target_local) != left_o) {
                 UOP_STAT_INC(uopcode, miss);
                 JUMP_TO_JUMP_TARGET();
@@ -1205,6 +1242,7 @@
                 JUMP_TO_JUMP_TARGET();
             }
             STAT_INC(BINARY_OP, hit);
+            res = PyStackRef_FromPyObjectSteal(res_o);
             #else
             if (index >= PyList_GET_SIZE(list)) {
                 UOP_STAT_INC(uopcode, miss);
@@ -1213,18 +1251,21 @@
             STAT_INC(BINARY_OP, hit);
             PyObject *res_o = PyList_GET_ITEM(list, index);
             assert(res_o != NULL);
-            Py_INCREF(res_o);
+            res = PyStackRef_FromPyObjectNew(res_o);
             #endif
-            PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
-            stack_pointer += -2;
-            assert(WITHIN_STACK_BOUNDS());
+            STAT_INC(BINARY_SUBSCR, hit);
             _PyFrame_SetStackPointer(frame, stack_pointer);
-            PyStackRef_CLOSE(list_st);
+            _PyStackRef tmp = list_st;
+            list_st = res;
+            stack_pointer[-2] = list_st;
+            PyStackRef_CLOSE(tmp);
             stack_pointer = _PyFrame_GetStackPointer(frame);
-            res = PyStackRef_FromPyObjectSteal(res_o);
-            stack_pointer[0] = res;
-            stack_pointer += 1;
+            stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());
+            _PyFrame_SetStackPointer(frame, stack_pointer);
+            PyStackRef_CLOSE(sub_st);
+            stack_pointer = _PyFrame_GetStackPointer(frame);
+            stack_pointer[-1] = res;
             break;
         }
 
@@ -1267,7 +1308,7 @@
             _PyFrame_SetStackPointer(frame, stack_pointer);
             PyStackRef_CLOSE(str_st);
             stack_pointer = _PyFrame_GetStackPointer(frame);
-            res = PyStackRef_FromPyObjectSteal(res_o);
+            res = PyStackRef_FromPyObjectImmortal(res_o);
             stack_pointer[0] = res;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
@@ -1303,17 +1344,17 @@
             STAT_INC(BINARY_OP, hit);
             PyObject *res_o = PyTuple_GET_ITEM(tuple, index);
             assert(res_o != NULL);
-            Py_INCREF(res_o);
             PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
-            stack_pointer += -2;
+            res = PyStackRef_FromPyObjectNew(res_o);
+            stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());
             _PyFrame_SetStackPointer(frame, stack_pointer);
-            PyStackRef_CLOSE(tuple_st);
+            _PyStackRef tmp = tuple_st;
+            tuple_st = res;
+            stack_pointer[-1] = tuple_st;
+            PyStackRef_CLOSE(tmp);
             stack_pointer = _PyFrame_GetStackPointer(frame);
-            res = PyStackRef_FromPyObjectSteal(res_o);
-            stack_pointer[0] = res;
-            stack_pointer += 1;
-            assert(WITHIN_STACK_BOUNDS());
+            stack_pointer[-1] = res;
             break;
         }
 
@@ -1654,6 +1695,7 @@
             retval = stack_pointer[-1];
             assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
             _PyStackRef temp = retval;
+            assert(PyStackRef_IsHeapSafe(temp));
             stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());
             _PyFrame_SetStackPointer(frame, stack_pointer);
@@ -2534,7 +2576,7 @@
             if (tup_o == NULL) {
                 JUMP_TO_ERROR();
             }
-            tup = PyStackRef_FromPyObjectSteal(tup_o);
+            tup = PyStackRef_FromPyObjectStealMortal(tup_o);
             stack_pointer[-oparg] = tup;
             stack_pointer += 1 - oparg;
             assert(WITHIN_STACK_BOUNDS());
@@ -2552,7 +2594,7 @@
             if (list_o == NULL) {
                 JUMP_TO_ERROR();
             }
-            list = PyStackRef_FromPyObjectSteal(list_o);
+            list = PyStackRef_FromPyObjectStealMortal(list_o);
             stack_pointer[-oparg] = list;
             stack_pointer += 1 - oparg;
             assert(WITHIN_STACK_BOUNDS());
@@ -2666,7 +2708,7 @@
                 stack_pointer = _PyFrame_GetStackPointer(frame);
                 JUMP_TO_ERROR();
             }
-            set = PyStackRef_FromPyObjectSteal(set_o);
+            set = PyStackRef_FromPyObjectStealMortal(set_o);
             stack_pointer[0] = set;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
@@ -2712,7 +2754,7 @@
             if (map_o == NULL) {
                 JUMP_TO_ERROR();
             }
-            map = PyStackRef_FromPyObjectSteal(map_o);
+            map = PyStackRef_FromPyObjectStealMortal(map_o);
             stack_pointer[0] = map;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
@@ -5047,7 +5089,7 @@
                 JUMP_TO_JUMP_TARGET();
             }
             STAT_INC(CALL, hit);
-            res = PyStackRef_FromPyObjectSteal(Py_NewRef(Py_TYPE(arg_o)));
+            res = PyStackRef_FromPyObjectNew(Py_TYPE(arg_o));
             stack_pointer[-3] = res;
             stack_pointer += -2;
             assert(WITHIN_STACK_BOUNDS());
@@ -6394,7 +6436,7 @@
             frame = tstate->current_frame = prev;
             LOAD_IP(frame->return_offset);
             stack_pointer = _PyFrame_GetStackPointer(frame);
-            res = PyStackRef_FromPyObjectSteal((PyObject *)gen);
+            res = PyStackRef_FromPyObjectStealMortal((PyObject *)gen);
             LLTRACE_RESUME_FRAME();
             stack_pointer[0] = res;
             stack_pointer += 1;
@@ -6424,7 +6466,7 @@
             if (slice_o == NULL) {
                 JUMP_TO_ERROR();
             }
-            slice = PyStackRef_FromPyObjectSteal(slice_o);
+            slice = PyStackRef_FromPyObjectStealMortal(slice_o);
             stack_pointer[0] = slice;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
diff --git a/Python/frame.c b/Python/frame.c
index 68ac2acbaee342..afb3e768491540 100644
--- a/Python/frame.c
+++ b/Python/frame.c
@@ -50,24 +50,23 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame)
 {
     assert(frame->owner < FRAME_OWNED_BY_INTERPRETER);
     assert(frame->owner != FRAME_OWNED_BY_FRAME_OBJECT);
-    Py_ssize_t size = ((char*)frame->stackpointer) - (char *)frame;
-    memcpy((_PyInterpreterFrame *)f->_f_frame_data, frame, size);
-    frame = (_PyInterpreterFrame *)f->_f_frame_data;
-    frame->stackpointer = (_PyStackRef *)(((char *)frame) + size);
-    frame->f_executable = PyStackRef_DUP(frame->f_executable);
-    f->f_frame = frame;
-    frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
-    if (_PyFrame_IsIncomplete(frame)) {
+    _PyInterpreterFrame *new_frame = (_PyInterpreterFrame *)f->_f_frame_data;
+    _PyFrame_Copy(frame, new_frame);
+    // _PyFrame_Copy takes the reference to the executable,
+    // so we need to restore it.
+    frame->f_executable = PyStackRef_DUP(new_frame->f_executable);
+    f->f_frame = new_frame;
+    new_frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
+    if (_PyFrame_IsIncomplete(new_frame)) {
         // This may be a newly-created generator or coroutine frame. Since it's
         // dead anyways, just pretend that the first RESUME ran:
-        PyCodeObject *code = _PyFrame_GetCode(frame);
-        frame->instr_ptr =
-            _PyFrame_GetBytecode(frame) + code->_co_firsttraceable + 1;
+        PyCodeObject *code = _PyFrame_GetCode(new_frame);
+        new_frame->instr_ptr =
+            _PyFrame_GetBytecode(new_frame) + code->_co_firsttraceable + 1;
     }
-    assert(!_PyFrame_IsIncomplete(frame));
+    assert(!_PyFrame_IsIncomplete(new_frame));
     assert(f->f_back == NULL);
     _PyInterpreterFrame *prev = _PyFrame_GetFirstComplete(frame->previous);
-    frame->previous = NULL;
     if (prev) {
         assert(prev->owner < FRAME_OWNED_BY_INTERPRETER);
         /* Link PyFrameObjects.f_back and remove link through 
_PyInterpreterFrame.previous */
diff --git a/Python/gc.c b/Python/gc.c
index 7faf368f35898a..f2a88657e8d8d6 100644
--- a/Python/gc.c
+++ b/Python/gc.c
@@ -1488,11 +1488,11 @@ mark_stacks(PyInterpreterState *interp, PyGC_Head 
*visited, int visited_space, b
             objects_marked += move_to_reachable(func, &reachable, 
visited_space);
             while (sp > locals) {
                 sp--;
-                if (PyStackRef_IsNull(*sp)) {
+                PyObject *op = PyStackRef_AsPyObjectBorrow(*sp);
+                if (op == NULL || _Py_IsImmortal(op)) {
                     continue;
                 }
-                PyObject *op = PyStackRef_AsPyObjectBorrow(*sp);
-                if (!_Py_IsImmortal(op) && _PyObject_IS_GC(op)) {
+                if (_PyObject_IS_GC(op)) {
                     PyGC_Head *gc = AS_GC(op);
                     if (_PyObject_GC_IS_TRACKED(op) &&
                         gc_old_space(gc) != visited_space) {
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index c9371f77e1d0bb..ad0e41188170fb 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -123,11 +123,10 @@
                 double dres =
                 ((PyFloatObject *)left_o)->ob_fval +
                 ((PyFloatObject *)right_o)->ob_fval;
-                PyObject *res_o = _PyFloat_FromDouble_ConsumeInputs(left, 
right, dres);
-                if (res_o == NULL) {
+                res = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
+                if (PyStackRef_IsNull(res)) {
                     JUMP_TO_LABEL(pop_2_error);
                 }
-                res = PyStackRef_FromPyObjectSteal(res_o);
             }
             stack_pointer[-2] = res;
             stack_pointer += -1;
@@ -352,6 +351,7 @@
                 next_oparg = CURRENT_OPERAND0();
                 #endif
                 _PyStackRef *target_local = &GETLOCAL(next_oparg);
+                assert(PyUnicode_CheckExact(left_o));
                 if (PyStackRef_AsPyObjectBorrow(*target_local) != left_o) {
                     UPDATE_MISS_STATS(BINARY_OP);
                     assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
@@ -436,11 +436,10 @@
                 double dres =
                 ((PyFloatObject *)left_o)->ob_fval *
                 ((PyFloatObject *)right_o)->ob_fval;
-                PyObject *res_o = _PyFloat_FromDouble_ConsumeInputs(left, 
right, dres);
-                if (res_o == NULL) {
+                res = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
+                if (PyStackRef_IsNull(res)) {
                     JUMP_TO_LABEL(pop_2_error);
                 }
-                res = PyStackRef_FromPyObjectSteal(res_o);
             }
             stack_pointer[-2] = res;
             stack_pointer += -1;
@@ -691,6 +690,7 @@
                 JUMP_TO_PREDICTED(BINARY_OP);
             }
             STAT_INC(BINARY_OP, hit);
+            res = PyStackRef_FromPyObjectSteal(res_o);
             #else
             if (index >= PyList_GET_SIZE(list)) {
                 UPDATE_MISS_STATS(BINARY_OP);
@@ -700,18 +700,21 @@
             STAT_INC(BINARY_OP, hit);
             PyObject *res_o = PyList_GET_ITEM(list, index);
             assert(res_o != NULL);
-            Py_INCREF(res_o);
+            res = PyStackRef_FromPyObjectNew(res_o);
             #endif
-            PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
-            stack_pointer += -2;
-            assert(WITHIN_STACK_BOUNDS());
+            STAT_INC(BINARY_SUBSCR, hit);
             _PyFrame_SetStackPointer(frame, stack_pointer);
-            PyStackRef_CLOSE(list_st);
+            _PyStackRef tmp = list_st;
+            list_st = res;
+            stack_pointer[-2] = list_st;
+            PyStackRef_CLOSE(tmp);
             stack_pointer = _PyFrame_GetStackPointer(frame);
-            res = PyStackRef_FromPyObjectSteal(res_o);
-            stack_pointer[0] = res;
-            stack_pointer += 1;
+            stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());
+            _PyFrame_SetStackPointer(frame, stack_pointer);
+            PyStackRef_CLOSE(sub_st);
+            stack_pointer = _PyFrame_GetStackPointer(frame);
+            stack_pointer[-1] = res;
             DISPATCH();
         }
 
@@ -770,7 +773,7 @@
             _PyFrame_SetStackPointer(frame, stack_pointer);
             PyStackRef_CLOSE(str_st);
             stack_pointer = _PyFrame_GetStackPointer(frame);
-            res = PyStackRef_FromPyObjectSteal(res_o);
+            res = PyStackRef_FromPyObjectImmortal(res_o);
             stack_pointer[0] = res;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
@@ -821,17 +824,17 @@
             STAT_INC(BINARY_OP, hit);
             PyObject *res_o = PyTuple_GET_ITEM(tuple, index);
             assert(res_o != NULL);
-            Py_INCREF(res_o);
             PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
-            stack_pointer += -2;
+            res = PyStackRef_FromPyObjectNew(res_o);
+            stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());
             _PyFrame_SetStackPointer(frame, stack_pointer);
-            PyStackRef_CLOSE(tuple_st);
+            _PyStackRef tmp = tuple_st;
+            tuple_st = res;
+            stack_pointer[-1] = tuple_st;
+            PyStackRef_CLOSE(tmp);
             stack_pointer = _PyFrame_GetStackPointer(frame);
-            res = PyStackRef_FromPyObjectSteal(res_o);
-            stack_pointer[0] = res;
-            stack_pointer += 1;
-            assert(WITHIN_STACK_BOUNDS());
+            stack_pointer[-1] = res;
             DISPATCH();
         }
 
@@ -877,11 +880,10 @@
                 double dres =
                 ((PyFloatObject *)left_o)->ob_fval -
                 ((PyFloatObject *)right_o)->ob_fval;
-                PyObject *res_o = _PyFloat_FromDouble_ConsumeInputs(left, 
right, dres);
-                if (res_o == NULL) {
+                res = _PyFloat_FromDouble_ConsumeInputs(left, right, dres);
+                if (PyStackRef_IsNull(res)) {
                     JUMP_TO_LABEL(pop_2_error);
                 }
-                res = PyStackRef_FromPyObjectSteal(res_o);
             }
             stack_pointer[-2] = res;
             stack_pointer += -1;
@@ -1021,7 +1023,7 @@
             if (list_o == NULL) {
                 JUMP_TO_LABEL(error);
             }
-            list = PyStackRef_FromPyObjectSteal(list_o);
+            list = PyStackRef_FromPyObjectStealMortal(list_o);
             stack_pointer[-oparg] = list;
             stack_pointer += 1 - oparg;
             assert(WITHIN_STACK_BOUNDS());
@@ -1073,7 +1075,7 @@
             if (map_o == NULL) {
                 JUMP_TO_LABEL(error);
             }
-            map = PyStackRef_FromPyObjectSteal(map_o);
+            map = PyStackRef_FromPyObjectStealMortal(map_o);
             stack_pointer[0] = map;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
@@ -1131,7 +1133,7 @@
                 stack_pointer = _PyFrame_GetStackPointer(frame);
                 JUMP_TO_LABEL(error);
             }
-            set = PyStackRef_FromPyObjectSteal(set_o);
+            set = PyStackRef_FromPyObjectStealMortal(set_o);
             stack_pointer[0] = set;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
@@ -1166,7 +1168,7 @@
             if (slice_o == NULL) {
                 JUMP_TO_LABEL(error);
             }
-            slice = PyStackRef_FromPyObjectSteal(slice_o);
+            slice = PyStackRef_FromPyObjectStealMortal(slice_o);
             stack_pointer[0] = slice;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
@@ -1235,7 +1237,7 @@
             if (tup_o == NULL) {
                 JUMP_TO_LABEL(error);
             }
-            tup = PyStackRef_FromPyObjectSteal(tup_o);
+            tup = PyStackRef_FromPyObjectStealMortal(tup_o);
             stack_pointer[-oparg] = tup;
             stack_pointer += 1 - oparg;
             assert(WITHIN_STACK_BOUNDS());
@@ -4271,7 +4273,7 @@
                 JUMP_TO_PREDICTED(CALL);
             }
             STAT_INC(CALL, hit);
-            res = PyStackRef_FromPyObjectSteal(Py_NewRef(Py_TYPE(arg_o)));
+            res = PyStackRef_FromPyObjectNew(Py_TYPE(arg_o));
             stack_pointer[-3] = res;
             stack_pointer += -2;
             assert(WITHIN_STACK_BOUNDS());
@@ -7237,6 +7239,7 @@
                 retval = val;
                 assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
                 _PyStackRef temp = retval;
+                assert(PyStackRef_IsHeapSafe(temp));
                 stack_pointer += -1;
                 assert(WITHIN_STACK_BOUNDS());
                 _PyFrame_SetStackPointer(frame, stack_pointer);
@@ -8741,7 +8744,7 @@
             static_assert(0 == 0, "incorrect cache size");
             _PyStackRef value;
             PyObject *obj = GETITEM(FRAME_CO_CONSTS, oparg);
-            value = PyStackRef_FromPyObjectNew(obj);
+            value = PyStackRef_FromPyObjectNewMortal(obj);
             stack_pointer[0] = value;
             stack_pointer += 1;
             assert(WITHIN_STACK_BOUNDS());
@@ -10254,7 +10257,7 @@
             frame = tstate->current_frame = prev;
             LOAD_IP(frame->return_offset);
             stack_pointer = _PyFrame_GetStackPointer(frame);
-            res = PyStackRef_FromPyObjectSteal((PyObject *)gen);
+            res = PyStackRef_FromPyObjectStealMortal((PyObject *)gen);
             LLTRACE_RESUME_FRAME();
             stack_pointer[0] = res;
             stack_pointer += 1;
@@ -10275,6 +10278,7 @@
             retval = stack_pointer[-1];
             assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
             _PyStackRef temp = retval;
+            assert(PyStackRef_IsHeapSafe(temp));
             stack_pointer += -1;
             assert(WITHIN_STACK_BOUNDS());
             _PyFrame_SetStackPointer(frame, stack_pointer);
@@ -10910,6 +10914,10 @@
             INSTRUCTION_STATS(STORE_FAST);
             _PyStackRef value;
             value = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value)
+            );
             _PyStackRef tmp = GETLOCAL(oparg);
             GETLOCAL(oparg) = value;
             stack_pointer += -1;
@@ -10931,6 +10939,10 @@
             _PyStackRef value1;
             _PyStackRef value2;
             value1 = stack_pointer[-1];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value1)
+            );
             uint32_t oparg1 = oparg >> 4;
             uint32_t oparg2 = oparg & 15;
             _PyStackRef tmp = GETLOCAL(oparg1);
@@ -10955,6 +10967,14 @@
             _PyStackRef value1;
             value1 = stack_pointer[-1];
             value2 = stack_pointer[-2];
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value1)
+            );
+            assert(
+                   ((_PyFrame_GetCode(frame)->co_flags & (CO_COROUTINE | 
CO_GENERATOR)) == 0) ||
+                   PyStackRef_IsHeapSafe(value2)
+            );
             uint32_t oparg1 = oparg >> 4;
             uint32_t oparg2 = oparg & 15;
             _PyStackRef tmp = GETLOCAL(oparg1);
diff --git a/Python/stackrefs.c b/Python/stackrefs.c
index 2e889b19bf38f2..a7693ba75c5dbb 100644
--- a/Python/stackrefs.c
+++ b/Python/stackrefs.c
@@ -1,6 +1,7 @@
 
 #include "Python.h"
 
+#include "pycore_object.h"
 #include "pycore_stackref.h"
 
 #if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
@@ -175,8 +176,16 @@ _Py_stackref_report_leaks(PyInterpreterState *interp)
     int leak = 0;
     _Py_hashtable_foreach(interp->open_stackrefs_table, report_leak, &leak);
     if (leak) {
+        fflush(stdout);
         Py_FatalError("Stackrefs leaked.");
     }
 }
 
+void
+PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct)
+{
+    PyObject *obj = _Py_stackref_close(ref);
+    _Py_DECREF_SPECIALIZED(obj, destruct);
+}
+
 #endif
diff --git a/Tools/cases_generator/analyzer.py 
b/Tools/cases_generator/analyzer.py
index 334beb96f4eb77..faf720e8fe67ee 100644
--- a/Tools/cases_generator/analyzer.py
+++ b/Tools/cases_generator/analyzer.py
@@ -586,6 +586,7 @@ def has_error_without_pop(op: parser.CodeDef) -> bool:
     "PySlice_New",
     "PyStackRef_AsPyObjectBorrow",
     "PyStackRef_AsPyObjectNew",
+    "PyStackRef_FromPyObjectNewMortal",
     "PyStackRef_AsPyObjectSteal",
     "PyStackRef_CLEAR",
     "PyStackRef_CLOSE_SPECIALIZED",
@@ -595,7 +596,10 @@ def has_error_without_pop(op: parser.CodeDef) -> bool:
     "PyStackRef_FromPyObjectNew",
     "PyStackRef_FromPyObjectSteal",
     "PyStackRef_IsExactly",
+    "PyStackRef_FromPyObjectStealMortal",
     "PyStackRef_IsNone",
+    "PyStackRef_Is",
+    "PyStackRef_IsHeapSafe",
     "PyStackRef_IsTrue",
     "PyStackRef_IsFalse",
     "PyStackRef_IsNull",

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to