diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-03-19-11-03.gh-issue-130706.01-O6U.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-03-19-11-03.gh-issue-130706.01-O6U.rst new file mode 100644 index 000000000000000..3351513347daecd --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-03-19-11-03.gh-issue-130706.01-O6U.rst @@ -0,0 +1,2 @@ +Add a fast path to ``_Py_Dealloc()`` for non-GC objects. Patch by Donghee +Na. diff --git a/Objects/object.c b/Objects/object.c index c7aeba0cee22c68..e334097d31c4325 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -3306,8 +3306,8 @@ next" object in the chain to 0. This can easily lead to stack overflows. To avoid that, if the C stack is nearing its limit, instead of calling dealloc on the object, it is added to a queue to be freed later when the stack is shallower */ -void -_Py_Dealloc(PyObject *op) +static Py_NO_INLINE void +py_dealloc(PyObject *op) { PyTypeObject *type = Py_TYPE(op); unsigned long gc_flag = type->tp_flags & Py_TPFLAGS_HAVE_GC; @@ -3372,6 +3372,28 @@ _Py_Dealloc(PyObject *op) } } +/* + * gh-130706: Keep the GC/reftracer path in the non-inlined py_dealloc(). + * Inlining it makes the compiler save callee-saved registers at entry, + * so the non-GC objects path would have unnecessary register spills. + */ +void +_Py_Dealloc(PyObject *op) +{ +#if !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) + // gh-130706: Avoid unnecessary register spills for non-GC objects. + PyTypeObject *type = Py_TYPE(op); + if (_PyRuntime.ref_tracer.tracer_func == NULL + && !(type->tp_flags & Py_TPFLAGS_HAVE_GC)) + { + type->tp_dealloc(op); + return; + } +#endif + // GC objects (trashcan), reftracer set, or debug builds. + py_dealloc(op); +} + PyObject ** PyObject_GET_WEAKREFS_LISTPTR(PyObject *op)