8000 Make immortal objects more robust, following design in PEP 683 · python/cpython@e932334 · GitHub
[go: up one dir, main page]

Skip to content

Commit e932334

Browse files
committed
Make immortal objects more robust, following design in PEP 683
1 parent 9940093 commit e932334

17 files changed

+69
-69
lines changed

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_object.h

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ extern "C" {
1616
#include "pycore_pystate.h" // _PyInterpreterState_GET()
1717
#include "pycore_uniqueid.h" // _PyType_IncrefSlow
1818

19-
20-
#define _Py_IMMORTAL_REFCNT_LOOSE ((_Py_IMMORTAL_REFCNT >> 1) + 1)
21-
2219
// This value is added to `ob_ref_shared` for objects that use deferred
2320
// reference counting so that they are not immediately deallocated when the
2421
// non-deferred reference count drops to zero.
@@ -27,25 +24,8 @@ extern "C" {
2724
// `ob_ref_shared` are used for flags.
2825
#define _Py_REF_DEFERRED (PY_SSIZE_T_MAX / 8)
2926

30-
// gh-121528, gh-118997: Similar to _Py_IsImmortal() but be more loose when
31-
// comparing the reference count to stay compatible with C extensions built
32-
// with the stable ABI 3.11 or older. Such extensions implement INCREF/DECREF
33-
// as refcnt++ and refcnt-- without taking in account immortal objects. For
34-
// example, the reference count of an immortal object can change from
35-
// _Py_IMMORTAL_REFCNT to _Py_IMMORTAL_REFCNT+1 (INCREF) or
36-
// _Py_IMMORTAL_REFCNT-1 (DECREF).
37-
//
38-
// This function should only be used in assertions. Otherwise, _Py_IsImmortal()
39-
// must be used instead.
40-
static inline int _Py_IsImmortalLoose(PyObject *op)
41-
{
42-
#if defined(Py_GIL_DISABLED)
43-
return _Py_IsImmortal(op);
44-
#else
45-
return (op->ob_refcnt >= _Py_IMMORTAL_REFCNT_LOOSE);
46-
#endif
47-
}
48-
#define _Py_IsImmortalLoose(op) _Py_IsImmortalLoose(_PyObject_CAST(op))
27+
/* For backwards compatibility -- Do not use this */
28+
#define _Py_IsImmortalLoose(op) _Py_IsImmortal
4929

5030

5131
/* Check if an object is consistent. For example, ensure that the reference
@@ -97,7 +77,7 @@ PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *);
9777
#else
9878
#define _PyObject_HEAD_INIT(type) \
9979
{ \
100-
.ob_refcnt = _Py_IMMORTAL_REFCNT, \
80+
.ob_refcnt = _Py_IMMORTAL_INITIAL_REFCNT, \
10181
.ob_type = (type) \
10282
}
10383
#endif
@@ -184,7 +164,7 @@ PyAPI_FUNC(void) _Py_SetImmortalUntracked(PyObject *op);
184164
static inline void _Py_SetMortal(PyObject *op, Py_ssize_t refcnt)
185165
{
186166
if (op) {
187-
assert(_Py_IsImmortalLoose(op));
167+
assert(_Py_IsImmortal(op));
188168
#ifdef Py_GIL_DISABLED
189169
op->ob_tid = _Py_UNOWNED_TID;
190170
op->ob_ref_local = 0;
@@ -316,7 +296,7 @@ static inline void
316296
_Py_INCREF_TYPE(PyTypeObject *type)
317297
{
318298
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
319-
assert(_Py_IsImmortalLoose(type));
299+
assert(_Py_IsImmortal(type));
320300
_Py_INCREF_IMMORTAL_STAT_INC();
321301
return;
322302
}
@@ -357,7 +337,7 @@ static inline void
357337
_Py_DECREF_TYPE(PyTypeObject *type)
358338
{
359339
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
360-
assert(_Py_IsImmortalLoose(type));
340+
assert(_Py_IsImmortal(type));
361341
_Py_DECREF_IMMORTAL_STAT_INC();
362342
return;
363343
}
@@ -393,7 +373,7 @@ _PyObject_Init(PyObject *op, PyTypeObject *typeobj)
393373
{
394374
assert(op != NULL);
395375
Py_SET_TYPE(op, typeobj);
396-
assert(_PyType_HasFeature(typeobj, Py_TPFLAGS_HEAPTYPE) || _Py_IsImmortalLoose(typeobj));
376+
assert(_PyType_HasFeature(typeobj, Py_TPFLAGS_HEAPTYPE) || _Py_IsImmortal(typeobj));
397377
_Py_INCREF_TYPE(typeobj);
398378
_Py_NewReference(op);
399379
}

Include/internal/pycore_opcode_metadata.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ whose size is determined when the object is allocated.
8181
#else
8282
#define PyObject_HEAD_INIT(type) \
8383
{ \
84-
{ _Py_IMMORTAL_REFCNT }, \
84+
{ _Py_IMMORTAL_INITIAL_REFCNT }, \
8585
(type) \
8686
},
8787
#endif

Include/refcount.h

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,35 +21,48 @@ cleanup during runtime finalization.
2121

2222
#if SIZEOF_VOID_P > 4
2323
/*
24-
In 64+ bit systems, an object will be marked as immortal by setting all of the
24+
In 64+ bit systems, any object whose 32 bit reference count is >= 2**31
25+
will be treated as immortal.
26+
27+
In order to offer some resilience to C extensions using the stable ABI
28+
compiled against 3.11 or earlier, we set the initial value near the
29+
middle of the range (2**31, 2**32 - 2**29). That way the
30+
31+
will be marked as immortal by setting all of the
2532
lower 32 bits of the reference count field, which is equal to: 0xFFFFFFFF
2633
2734
Using the lower 32 bits makes the value backwards compatible by allowing
2835
C-Extensions without the updated checks in Py_INCREF and Py_DECREF to safely
29-
increase and decrease the objects reference count. The object would lose its
30-
immortality, but the execution would still be correct.
36+
increase and decrease the objects reference count.
37+
38+
In order to offer sufficient resilience to C extensions using the stable ABI
39+
compiled against 3.11 or earlier, we set the initial value near the
40+
middle of the range (2**31, 2**32). That way the the refcount can be
41+
off by ~1 billion without affecting immortality.
3142
3243
Reference count increases will use saturated arithmetic, taking advantage of
3344
having all the lower 32 bits set, which will avoid the reference count to go
3445
beyond the refcount limit. Immortality checks for reference count decreases will
3546
be done by checking the bit sign flag in the lower 32 bits.
47+
3648
*/
37-
#define _Py_IMMORTAL_REFCNT _Py_CAST(Py_ssize_t, UINT_MAX)
49+
#define _Py_IMMORTAL_INITIAL_REFCNT ((Py_ssize_t)(3UL << 30))
3850

3951
#else
4052
/*
41-
In 32 bit systems, an object will be marked as immortal by setting all of the
42-
lower 30 bits of the reference count field, which is equal to: 0x3FFFFFFF
53+
In 32 bit systems, an object will be treated as immortal if its reference
54+
count equals or exceeds _Py_IMMORTAL_MINIMUM_REFCNT (2**30).
4355
4456
Using the lower 30 bits makes the value backwards compatible by allowing
4557
C-Extensions without the updated checks in Py_INCREF and Py_DECREF to safely
4658
increase and decrease the objects reference count. The object would lose its
4759
immortality, but the execution would still be correct.
4860
4961
Reference count increases and decreases will first go through an immortality
50-
check by comparing the reference count field to the immortality reference count.
62+
check by comparing the reference count field to the minimum immortality refcount.
5163
*/
52-
#define _Py_IMMORTAL_REFCNT _Py_CAST(Py_ssize_t, UINT_MAX >> 2)
64+
#define _Py_IMMORTAL_INITIAL_REFCNT ((Py_ssize_t)(3L << 29))
65+
#define _Py_IMMORTAL_MINIMUM_REFCNT ((Py_ssize_t)(1L << 30))
5366
#endif
5467

5568
// Py_GIL_DISABLED builds indicate immortal objects using `ob_ref_local`, which is
@@ -90,7 +103,7 @@ PyAPI_FUNC(Py_ssize_t) Py_REFCNT(PyObject *ob);
90103
#else
91104
uint32_t local = _Py_atomic_load_uint32_relaxed(&ob->ob_ref_local);
92105
if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
93-
return _Py_IMMORTAL_REFCNT;
106+
return _Py_IMMORTAL_INITIAL_REFCNT;
94107
}
95108
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&ob->ob_ref_shared);
96109
return _Py_STATIC_CAST(Py_ssize_t, local) +
@@ -109,9 +122,9 @@ static inline Py_ALWAYS_INLINE int _Py_IsImmortal(PyObject *op)
109122
return (_Py_atomic_load_uint32_relaxed(&op->ob_ref_local) ==
110123
_Py_IMMORTAL_REFCNT_LOCAL);
111124
#elif SIZEOF_VOID_P > 4
112-
return (_Py_CAST(PY_INT32_T, op->ob_refcnt) < 0);
125+
return _Py_CAST(PY_INT32_T, op->ob_refcnt) < 0;
113126
#else
114-
return (op->ob_refcnt == _Py_IMMORTAL_REFCNT);
127+
return op->ob_refcnt >= _Py_IMMORTAL_MINIMUM_REFCNT;
115128
#endif
116129
}
117130
#define _Py_IsImmortal(op) _Py_IsImmortal(_PyObject_CAST(op))
@@ -236,7 +249,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
236249
uint32_t new_local = local + 1;
237250
if (new_local == 0) {
238251
_Py_INCREF_IMMORTAL_STAT_INC();
239-
// local is equal to _Py_IMMORTAL_REFCNT: do nothing
252+
// local is equal to _Py_IMMORTAL_REFCNT_LOCAL: do nothing
240253
return;
241254
}
242255
if (_Py_IsOwnedByCurrentThread(op)) {
@@ -246,18 +259,14 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
246259
_Py_atomic_add_ssize(&op->ob_ref_shared, (1 << _Py_REF_SHARED_SHIFT));
247260
}
248261
#elif SIZEOF_VOID_P > 4
249-
// Portable saturated add, branching on the carry flag and set low bits
250262
PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN];
251-
PY_UINT32_T new_refcnt = cur_refcnt + 1;
252-
if (new_refcnt == 0) {
263+
if (((int32_t)cur_refcnt) < 0) {
264+
// the object is immortal
253265
_Py_INCREF_IMMORTAL_STAT_INC();
254-
// cur_refcnt is equal to _Py_IMMORTAL_REFCNT: the object is immortal,
255-
// do nothing
256266
return;
257267
}
258-
op->ob_refcnt_split[PY_BIG_ENDIAN] = new_refcnt;
268+
op->ob_refcnt_split[PY_BIG_ENDIAN] = cur_refcnt + 1;
259269
#else
260-
// Explicitly check immortality against the immortal value
261270
if (_Py_IsImmortal(op)) {
262271
_Py_INCREF_IMMORTAL_STAT_INC();
263272
return;

Lib/test/test_builtin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2574,9 +2574,9 @@ def __del__(self):
25742574
class ImmortalTests(unittest.TestCase):
25752575

25762576
if sys.maxsize < (1 << 32):
2577-
IMMORTAL_REFCOUNT = (1 << 30) - 1
2577+
IMMORTAL_REFCOUNT = 3 << 29
25782578
else:
2579-
IMMORTAL_REFCOUNT = (1 << 32) - 1
2579+
IMMORTAL_REFCOUNT = 3 << 30
25802580

25812581
IMMORTALS = (None, True, False, Ellipsis, NotImplemented, *range(-5, 257))
25822582

Modules/_asynciomodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1387,7 +1387,7 @@ FutureObj_get_state(FutureObj *fut, void *Py_UNUSED(ignored))
13871387
default:
13881388
assert (0);
13891389
}
1390-
assert(_Py_IsImmortalLoose(ret));
1390+
assert(_Py_IsImmortal(ret));
13911391
return ret;
13921392
}
13931393

Objects/bytesobject.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Py_LOCAL_INLINE(Py_ssize_t) _PyBytesWriter_GetSize(_PyBytesWriter *writer,
4646
static inline PyObject* bytes_get_empty(void)
4747
{
4848
PyObject *empty = &EMPTY->ob_base.ob_base;
49-
assert(_Py_IsImmortalLoose(empty));
49+
assert(_Py_IsImmortal(empty));
5050
return empty;
5151
}
5252

@@ -119,7 +119,7 @@ PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
119119
}
120120
if (size == 1 && str != NULL) {
121121
op = CHARACTER(*str & 255);
122-
assert(_Py_IsImmortalLoose(op));
122+
assert(_Py_IsImmortal(op));
123123
return (PyObject *)op;
124124
}
125125
if (size == 0) {
@@ -155,7 +155,7 @@ PyBytes_FromString(const char *str)
155155
}
156156
else if (size == 1) {
157157
op = CHARACTER(*str & 255);
158-
assert(_Py_IsImmortalLoose(op));
158+
assert(_Py_IsImmortal(op));
159159
return (PyObject *)op;
160160
}
161161

Objects/dictobject.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ _PyDict_DebugMallocStats(FILE *out)
416416

417417
#define DK_MASK(dk) (DK_SIZE(dk)-1)
418418

419+
#define _Py_DICT_IMMORTAL_INITIAL_REFCNT PY_SSIZE_T_MIN
420+
419421
static void free_keys_object(PyDictKeysObject *keys, bool use_qsbr);
420422

421423
/* PyDictKeysObject has refcounts like PyObject does, so we have the
@@ -428,7 +430,8 @@ static void free_keys_object(PyDictKeysObject *keys, bool use_qsbr);
428430
static inline void
429431
dictkeys_incref(PyDictKeysObject *dk)
430432
{
431-
if (FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) == _Py_IMMORTAL_REFCNT) {
433+
if (FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) < 0) {
434+
assert(FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) == _Py_DICT_IMMORTAL_INITIAL_REFCNT);
432435
return;
433436
}
434437
#ifdef Py_REF_DEBUG
@@ -440,7 +443,8 @@ dictkeys_incref(PyDictKeysObject *dk)
440443
static inline void
441444
dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk, bool use_qsbr)
442445
{
443-
if (FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) == _Py_IMMORTAL_REFCNT) {
446+
if (FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) < 0) {
447+
assert(FT_ATOMIC_LOAD_SSIZE_RELAXED(dk->dk_refcnt) == _Py_DICT_IMMORTAL_INITIAL_REFCNT);
444448
return;
445449
}
446450
assert(FT_ATOMIC_LOAD_SSIZE(dk->dk_refcnt) > 0);
@@ -586,7 +590,7 @@ estimate_log2_keysize(Py_ssize_t n)
586590
* (which cannot fail and thus can do no allocation).
587591
*/
588592
static PyDictKeysObject empty_keys_struct = {
589-
_Py_IMMORTAL_REFCNT, /* dk_refcnt */
593+
_Py_DICT_IMMORTAL_INITIAL_REFCNT, /* dk_refcnt */
590594
0, /* dk_log2_size */
591595
0, /* dk_log2_index_bytes */
592596
DICT_KEYS_UNICODE, /* dk_kind */

Objects/object.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2452,7 +2452,7 @@ _Py_SetImmortalUntracked(PyObject *op)
24522452
op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
24532453
op->ob_ref_shared = 0;
24542454
#else
2455-
op->ob_refcnt = _Py_IMMORTAL_REFCNT;
2455+
op->ob_refcnt = _Py_IMMORTAL_INITIAL_REFCNT;
24562456
#endif
24572457
}
24582458

Objects/structseq.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,7 @@ _PyStructSequence_FiniBuiltin(PyInterpreterState *interp, PyTypeObject *type)
708708
assert(type->tp_name != NULL);
709709
assert(type->tp_base == &PyTuple_Type);
710710
assert((type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
711-
assert(_Py_IsImmortalLoose(type));
711+
assert(_Py_IsImmortal(type));
712712

713713
// Cannot delete a type if it still has subclasses
714714
if (_PyType_HasSubclasses(type)) {

Objects/typeobject.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ set_tp_bases(PyTypeObject *self, PyObject *bases, int initial)
476476
assert(PyTuple_GET_SIZE(bases) == 1);
477477
assert(PyTuple_GET_ITEM(bases, 0) == (PyObject *)self->tp_base);
478478
assert(self->tp_base->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN);
479-
assert(_Py_IsImmortalLoose(self->tp_base));
479+
assert(_Py_IsImmortal(self->tp_base));
480480
}
481481
_Py_SetImmortal(bases);
482482
}
@@ -493,7 +493,7 @@ clear_tp_bases(PyTypeObject *self, int final)
493493
Py_CLEAR(self->tp_bases);
494494
}
495495
else {
496-
assert(_Py_IsImmortalLoose(self->tp_bases));
496+
assert(_Py_IsImmortal(self->tp_bases));
497497
_Py_ClearImmortal(self->tp_bases);
498498
}
499499
}
@@ -558,7 +558,7 @@ clear_tp_mro(PyTypeObject *self, int final)
558558
Py_CLEAR(self->tp_mro);
559559
}
560560
else {
561-
assert(_Py_IsImmortalLoose(self->tp_mro));
561+
assert(_Py_IsImmortal(self->tp_mro));
562562
_Py_ClearImmortal(self->tp_mro);
563563
}
564564
}
@@ -6003,7 +6003,7 @@ fini_static_type(PyInterpreterState *interp, PyTypeObject *type,
60036003
int isbuiltin, int final)
60046004
{
60056005
assert(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN);
6006-
assert(_Py_IsImmortalLoose((PyObject *)type));
6006+
assert(_Py_IsImmortal((PyObject *)type));
60076007

60086008
type_dealloc_common(type);
60096009

Python/executor_cases.c.h

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
0