diff --git a/Include/cpython/longintrepr.h b/Include/cpython/longintrepr.h index 357477b60d9a5a..1b1a1bf6b7cf75 100644 --- a/Include/cpython/longintrepr.h +++ b/Include/cpython/longintrepr.h @@ -76,8 +76,8 @@ typedef long stwodigits; /* signed variant of twodigits */ - 1: Zero - 2: Negative - The third lowest bit of lv_tag is reserved for an immortality flag, but is - not currently used. + The third lowest bit of lv_tag is + set to 1 for the small ints. In a normalized number, ob_digit[ndigits-1] (the most significant digit) is never zero. Also, in all cases, for all valid i, diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h index 8bead00e70640c..c52eb77692dd6a 100644 --- a/Include/internal/pycore_long.h +++ b/Include/internal/pycore_long.h @@ -159,13 +159,14 @@ PyAPI_FUNC(int) _PyLong_Size_t_Converter(PyObject *, void *); /* Long value tag bits: * 0-1: Sign bits value = (1-sign), ie. negative=2, positive=0, zero=1. - * 2: Reserved for immortality bit + * 2: Set to 1 for the small ints * 3+ Unsigned digit count */ #define SIGN_MASK 3 #define SIGN_ZERO 1 #define SIGN_NEGATIVE 2 #define NON_SIZE_BITS 3 +#define IMMORTALITY_BIT_MASK (1 << 2) /* The functions _PyLong_IsCompact and _PyLong_CompactValue are defined * in Include/cpython/longobject.h, since they need to be inline. @@ -196,7 +197,7 @@ PyAPI_FUNC(int) _PyLong_Size_t_Converter(PyObject *, void *); static inline int _PyLong_IsNonNegativeCompact(const PyLongObject* op) { assert(PyLong_Check(op)); - return op->long_value.lv_tag <= (1 << NON_SIZE_BITS); + return ((op->long_value.lv_tag & ~IMMORTALITY_BIT_MASK) <= (1 << NON_SIZE_BITS)); } @@ -298,7 +299,7 @@ _PyLong_FlipSign(PyLongObject *op) { .long_value = { \ .lv_tag = TAG_FROM_SIGN_AND_SIZE( \ (val) == 0 ? 0 : ((val) < 0 ? -1 : 1), \ - (val) == 0 ? 0 : 1), \ + (val) == 0 ? 0 : 1) | IMMORTALITY_BIT_MASK, \ { ((val) >= 0 ? (val) : -(val)) }, \ } \ } diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-12-04-22-14-40.gh-issue-127119._hpyFE.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-04-22-14-40.gh-issue-127119._hpyFE.rst new file mode 100644 index 00000000000000..f021bd490f488c --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-04-22-14-40.gh-issue-127119._hpyFE.rst @@ -0,0 +1 @@ +Slightly optimize the :class:`int` deallocator. diff --git a/Modules/_testcapi/immortal.c b/Modules/_testcapi/immortal.c index 9f81389811c645..2d366762717ff4 100644 --- a/Modules/_testcapi/immortal.c +++ b/Modules/_testcapi/immortal.c @@ -1,5 +1,8 @@ #include "parts.h" +#define Py_BUILD_CORE +#include "internal/pycore_long.h" // IMMORTALITY_BIT_MASK + int verify_immortality(PyObject *object) { assert(_Py_IsImmortal(object)); @@ -26,7 +29,17 @@ static PyObject * test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored)) { for (int i = -5; i <= 256; i++) { - assert(verify_immortality(PyLong_FromLong(i))); + PyObject *obj = PyLong_FromLong(i); + assert(verify_immortality(obj)); + int has_int_immortal_bit = ((PyLongObject *)obj)->long_value.lv_tag & IMMORTALITY_BIT_MASK; + assert(has_int_immortal_bit); + } + for (int i = 257; i <= 260; i++) { + PyObject *obj = PyLong_FromLong(i); + assert(obj); + int has_int_immortal_bit = ((PyLongObject *)obj)->long_value.lv_tag & IMMORTALITY_BIT_MASK; + assert(!has_int_immortal_bit); + Py_DECREF(obj); } Py_RETURN_NONE; } diff --git a/Objects/longobject.c b/Objects/longobject.c index bd7ff68d0899c6..58776c06edaac4 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -3616,32 +3616,23 @@ long_richcompare(PyObject *self, PyObject *other, int op) } static inline int -compact_int_is_small(PyObject *self) +/// Return 1 if the object is one of the immortal small ints +_long_is_small_int(PyObject *op) { - PyLongObject *pylong = (PyLongObject *)self; - assert(_PyLong_IsCompact(pylong)); - stwodigits ival = medium_value(pylong); - if (IS_SMALL_INT(ival)) { - PyLongObject *small_pylong = (PyLongObject *)get_small_int((sdigit)ival); - if (pylong == small_pylong) { - return 1; - } - } - return 0; + PyLongObject *long_object = (PyLongObject *)op; + return (long_object->long_value.lv_tag & IMMORTALITY_BIT_MASK) != 0; } void _PyLong_ExactDealloc(PyObject *self) { assert(PyLong_CheckExact(self)); + if (_long_is_small_int(self)) { + // See PEP 683, section Accidental De-Immortalizing for details + _Py_SetImmortal(self); + return; + } if (_PyLong_IsCompact((PyLongObject *)self)) { - #ifndef Py_GIL_DISABLED - if (compact_int_is_small(self)) { - // See PEP 683, section Accidental De-Immortalizing for details - _Py_SetImmortal(self); - return; - } - #endif _Py_FREELIST_FREE(ints, self, PyObject_Free); return; } @@ -3651,24 +3642,26 @@ _PyLong_ExactDealloc(PyObject *self) static void long_dealloc(PyObject *self) { - assert(self); - if (_PyLong_IsCompact((PyLongObject *)self)) { - if (compact_int_is_small(self)) { - /* This should never get called, but we also don't want to SEGV if - * we accidentally decref small Ints out of existence. Instead, - * since small Ints are immortal, re-set the reference count. - * - * See PEP 683, section Accidental De-Immortalizing for details - */ - _Py_SetImmortal(self); - return; - } - if (PyLong_CheckExact(self)) { - _Py_FREELIST_FREE(ints, self, PyObject_Free); - return; - } + // test what happens if we do hit the "never should get called": we should expect memory leaks, but no crashes + PyLongObject *pylong = (PyLongObject *)self; + int dotest = PyLong_CheckExact(self) && _PyLong_IsCompact(pylong) && (medium_value(pylong)==5); + + if (_long_is_small_int(self) || dotest) { + /* This should never get called, but we also don't want to SEGV if + * we accidentally decref small Ints out of existence. Instead, + * since small Ints are immortal, re-set the reference count. + * + * See PEP 683, section Accidental De-Immortalizing for details + */ + //assert(0); + printf("call _Py_SetImmortal on %d: %d\n", PyLong_CheckExact(self), medium_value(pylong)); + _Py_SetImmortal(self); + return; + } + if (PyLong_CheckExact(self) && _PyLong_IsCompact((PyLongObject *)self)) { + _Py_FREELIST_FREE(ints, self, PyObject_Free); + return; } - Py_TYPE(self)->tp_free(self); } @@ -6030,7 +6023,7 @@ long_subtype_new(PyTypeObject *type, PyObject *x, PyObject *obase) return NULL; } assert(PyLong_Check(newobj)); - newobj->long_value.lv_tag = tmp->long_value.lv_tag; + newobj->long_value.lv_tag = tmp->long_value.lv_tag & ~IMMORTALITY_BIT_MASK; for (i = 0; i < n; i++) { newobj->long_value.ob_digit[i] = tmp->long_value.ob_digit[i]; } diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index 698ecbd3b549aa..97d7517208d2a7 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -890,7 +890,7 @@ class PyLongObjectPtr(PyObjectPtr): def proxyval(self, visited): ''' - Python's Include/longinterpr.h has this declaration: + Python's Include/cpython/longinterpr.h has this declaration: typedef struct _PyLongValue { uintptr_t lv_tag; /* Number of digits, sign and flags */ @@ -909,8 +909,7 @@ def proxyval(self, visited): - 0: Positive - 1: Zero - 2: Negative - The third lowest bit of lv_tag is reserved for an immortality flag, but is - not currently used. + The third lowest bit of lv_tag is set to 1 for the small ints and 0 otherwise. where SHIFT can be either: #define PyLong_SHIFT 30