Skip to content

Commit ea2c001

Browse files
gh-84436: Implement Immortal Objects (gh-19474)
This is the implementation of PEP683 Motivation: The PR introduces the ability to immortalize instances in CPython which bypasses reference counting. Tagging objects as immortal allows up to skip certain operations when we know that the object will be around for the entire execution of the runtime. Note that this by itself will bring a performance regression to the runtime due to the extra reference count checks. However, this brings the ability of having truly immutable objects that are useful in other contexts such as immutable data sharing between sub-interpreters.
1 parent 916de04 commit ea2c001

35 files changed

+483
-171
lines changed

Doc/library/sys.rst

+7
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,13 @@ always available.
670670
.. versionadded:: 3.4
671671

672672

673+
.. function:: getunicodeinternedsize()
674+
675+
Return the number of unicode objects that have been interned.
676+
677+
.. versionadded:: 3.12
678+
679+
673680
.. function:: getandroidapilevel()
674681

675682
Return the build time API version of Android as an integer.

Doc/whatsnew/3.12.rst

+19-2
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,24 @@ New Features
11291129
to replace the legacy-api :c:func:`!PyErr_Display`. (Contributed by
11301130
Irit Katriel in :gh:`102755`).
11311131

1132+
* :pep:`683`: Introduced Immortal Objects to Python which allows objects
1133+
to bypass reference counts and introduced changes to the C-API:
1134+
1135+
- ``_Py_IMMORTAL_REFCNT``: The reference count that defines an object
1136+
as immortal.
1137+
- ``_Py_IsImmortal`` Checks if an object has the immortal reference count.
1138+
- ``PyObject_HEAD_INIT`` This will now initialize reference count to
1139+
``_Py_IMMORTAL_REFCNT`` when used with ``Py_BUILD_CORE``.
1140+
- ``SSTATE_INTERNED_IMMORTAL`` An identifier for interned unicode objects
1141+
that are immortal.
1142+
- ``SSTATE_INTERNED_IMMORTAL_STATIC`` An identifier for interned unicode
1143+
objects that are immortal and static
1144+
- ``sys.getunicodeinternedsize`` This returns the total number of unicode
1145+
objects that have been interned. This is now needed for refleak.py to
1146+
correctly track reference counts and allocated blocks
1147+
1148+
(Contributed by Eddie Elizondo in :gh:`84436`.)
1149+
11321150
Porting to Python 3.12
11331151
----------------------
11341152

@@ -1293,8 +1311,7 @@ Removed
12931311
* :c:func:`!PyUnicode_GetSize`
12941312
* :c:func:`!PyUnicode_GET_DATA_SIZE`
12951313

1296-
* Remove the ``PyUnicode_InternImmortal()`` function and the
1297-
``SSTATE_INTERNED_IMMORTAL`` macro.
1314+
* Remove the ``PyUnicode_InternImmortal()`` function macro.
12981315
(Contributed by Victor Stinner in :gh:`85858`.)
12991316

13001317
* Remove ``Jython`` compatibility hacks from several stdlib modules and tests.

Include/boolobject.h

+3-4
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ PyAPI_DATA(PyTypeObject) PyBool_Type;
1111

1212
#define PyBool_Check(x) Py_IS_TYPE((x), &PyBool_Type)
1313

14-
/* Py_False and Py_True are the only two bools in existence.
15-
Don't forget to apply Py_INCREF() when returning either!!! */
14+
/* Py_False and Py_True are the only two bools in existence. */
1615

1716
/* Don't use these directly */
1817
PyAPI_DATA(PyLongObject) _Py_FalseStruct;
@@ -31,8 +30,8 @@ PyAPI_FUNC(int) Py_IsFalse(PyObject *x);
3130
#define Py_IsFalse(x) Py_Is((x), Py_False)
3231

3332
/* Macros for returning Py_True or Py_False, respectively */
34-
#define Py_RETURN_TRUE return Py_NewRef(Py_True)
35-
#define Py_RETURN_FALSE return Py_NewRef(Py_False)
33+
#define Py_RETURN_TRUE return Py_True
34+
#define Py_RETURN_FALSE return Py_False
3635

3736
/* Function to return a bool from a C long */
3837
PyAPI_FUNC(PyObject *) PyBool_FromLong(long);

Include/cpython/unicodeobject.h

+13-4
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,16 @@ typedef struct {
9898
Py_ssize_t length; /* Number of code points in the string */
9999
Py_hash_t hash; /* Hash value; -1 if not set */
100100
struct {
101-
/* If interned is set, the two references from the
102-
dictionary to this object are *not* counted in ob_refcnt. */
103-
unsigned int interned:1;
101+
/* If interned is non-zero, the two references from the
102+
dictionary to this object are *not* counted in ob_refcnt.
103+
The possible values here are:
104+
0: Not Interned
105+
1: Interned
106+
2: Interned and Immortal
107+
3: Interned, Immortal, and Static
108+
This categorization allows the runtime to determine the right
109+
cleanup mechanism at runtime shutdown. */
110+
unsigned int interned:2;
104111
/* Character size:
105112
106113
- PyUnicode_1BYTE_KIND (1):
@@ -135,7 +142,7 @@ typedef struct {
135142
unsigned int ascii:1;
136143
/* Padding to ensure that PyUnicode_DATA() is always aligned to
137144
4 bytes (see issue #19537 on m68k). */
138-
unsigned int :26;
145+
unsigned int :25;
139146
} state;
140147
} PyASCIIObject;
141148

@@ -183,6 +190,8 @@ PyAPI_FUNC(int) _PyUnicode_CheckConsistency(
183190
/* Interning state. */
184191
#define SSTATE_NOT_INTERNED 0
185192
#define SSTATE_INTERNED_MORTAL 1
193+
#define SSTATE_INTERNED_IMMORTAL 2
194+
#define SSTATE_INTERNED_IMMORTAL_STATIC 3
186195

187196
/* Use only if you know it's a string */
188197
static inline unsigned int PyUnicode_CHECK_INTERNED(PyObject *op) {

Include/internal/pycore_global_objects_fini_generated.h

+2-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_long.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ _PyLong_FlipSign(PyLongObject *op) {
245245

246246
#define _PyLong_DIGIT_INIT(val) \
247247
{ \
248-
.ob_base = _PyObject_IMMORTAL_INIT(&PyLong_Type), \
248+
.ob_base = _PyObject_HEAD_INIT(&PyLong_Type) \
249249
.long_value = { \
250250
.lv_tag = TAG_FROM_SIGN_AND_SIZE( \
251251
(val) == 0 ? 0 : ((val) < 0 ? -1 : 1), \

Include/internal/pycore_object.h

+33-15
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,25 @@ extern "C" {
1414
#include "pycore_pystate.h" // _PyInterpreterState_GET()
1515
#include "pycore_runtime.h" // _PyRuntime
1616

17-
/* This value provides *effective* immortality, meaning the object should never
18-
be deallocated (until runtime finalization). See PEP 683 for more details about
19-
immortality, as well as a proposed mechanism for proper immortality. */
20-
#define _PyObject_IMMORTAL_REFCNT 999999999
21-
22-
#define _PyObject_IMMORTAL_INIT(type) \
23-
{ \
24-
.ob_refcnt = _PyObject_IMMORTAL_REFCNT, \
25-
.ob_type = (type), \
26-
}
27-
#define _PyVarObject_IMMORTAL_INIT(type, size) \
28-
{ \
29-
.ob_base = _PyObject_IMMORTAL_INIT(type), \
30-
.ob_size = size, \
31-
}
17+
/* We need to maintain an internal copy of Py{Var}Object_HEAD_INIT to avoid
18+
designated initializer conflicts in C++20. If we use the deinition in
19+
object.h, we will be mixing designated and non-designated initializers in
20+
pycore objects which is forbiddent in C++20. However, if we then use
21+
designated initializers in object.h then Extensions without designated break.
22+
Furthermore, we can't use designated initializers in Extensions since these
23+
are not supported pre-C++20. Thus, keeping an internal copy here is the most
24+
backwards compatible solution */
25+
#define _PyObject_HEAD_INIT(type) \
26+
{ \
27+
_PyObject_EXTRA_INIT \
28+
.ob_refcnt = _Py_IMMORTAL_REFCNT, \
29+
.ob_type = (type) \
30+
},
31+
#define _PyVarObject_HEAD_INIT(type, size) \
32+
{ \
33+
.ob_base = _PyObject_HEAD_INIT(type) \
34+
.ob_size = size \
35+
},
3236

3337
PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc(
3438
const char *func,
@@ -61,9 +65,20 @@ static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n)
6165
}
6266
#define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n)
6367

68+
static inline void _Py_SetImmortal(PyObject *op)
69+
{
70+
if (op) {
71+
op->ob_refcnt = _Py_IMMORTAL_REFCNT;
72+
}
73+
}
74+
#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op))
75+
6476
static inline void
6577
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
6678
{
79+
if (_Py_IsImmortal(op)) {
80+
return;
81+
}
6782
_Py_DECREF_STAT_INC();
6883
#ifdef Py_REF_DEBUG
6984
_Py_DEC_REFTOTAL(_PyInterpreterState_GET());
@@ -82,6 +97,9 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
8297
static inline void
8398
_Py_DECREF_NO_DEALLOC(PyObject *op)
8499
{
100+
if (_Py_IsImmortal(op)) {
101+
return;
102+
}
85103
_Py_DECREF_STAT_INC();
86104
#ifdef Py_REF_DEBUG
87105
_Py_DEC_REFTOTAL(_PyInterpreterState_GET());

Include/internal/pycore_runtime_init.h

+7-7
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,13 @@ extern PyTypeObject _PyExc_MemoryError;
7676
.latin1 = _Py_str_latin1_INIT, \
7777
}, \
7878
.tuple_empty = { \
79-
.ob_base = _PyVarObject_IMMORTAL_INIT(&PyTuple_Type, 0) \
79+
.ob_base = _PyVarObject_HEAD_INIT(&PyTuple_Type, 0) \
8080
}, \
8181
.hamt_bitmap_node_empty = { \
82-
.ob_base = _PyVarObject_IMMORTAL_INIT(&_PyHamt_BitmapNode_Type, 0) \
82+
.ob_base = _PyVarObject_HEAD_INIT(&_PyHamt_BitmapNode_Type, 0) \
8383
}, \
8484
.context_token_missing = { \
85-
.ob_base = _PyObject_IMMORTAL_INIT(&_PyContextTokenMissing_Type), \
85+
.ob_base = _PyObject_HEAD_INIT(&_PyContextTokenMissing_Type) \
8686
}, \
8787
}, \
8888
}, \
@@ -116,11 +116,11 @@ extern PyTypeObject _PyExc_MemoryError;
116116
.singletons = { \
117117
._not_used = 1, \
118118
.hamt_empty = { \
119-
.ob_base = _PyObject_IMMORTAL_INIT(&_PyHamt_Type), \
119+
.ob_base = _PyObject_HEAD_INIT(&_PyHamt_Type) \
120120
.h_root = (PyHamtNode*)&_Py_SINGLETON(hamt_bitmap_node_empty), \
121121
}, \
122122
.last_resort_memory_error = { \
123-
_PyObject_IMMORTAL_INIT(&_PyExc_MemoryError), \
123+
_PyObject_HEAD_INIT(&_PyExc_MemoryError) \
124124
}, \
125125
}, \
126126
}, \
@@ -138,7 +138,7 @@ extern PyTypeObject _PyExc_MemoryError;
138138

139139
#define _PyBytes_SIMPLE_INIT(CH, LEN) \
140140
{ \
141-
_PyVarObject_IMMORTAL_INIT(&PyBytes_Type, (LEN)), \
141+
_PyVarObject_HEAD_INIT(&PyBytes_Type, (LEN)) \
142142
.ob_shash = -1, \
143143
.ob_sval = { (CH) }, \
144144
}
@@ -149,7 +149,7 @@ extern PyTypeObject _PyExc_MemoryError;
149149

150150
#define _PyUnicode_ASCII_BASE_INIT(LITERAL, ASCII) \
151151
{ \
152-
.ob_base = _PyObject_IMMORTAL_INIT(&PyUnicode_Type), \
152+
.ob_base = _PyObject_HEAD_INIT(&PyUnicode_Type) \
153153
.length = sizeof(LITERAL) - 1, \
154154
.hash = -1, \
155155
.state = { \

Include/internal/pycore_unicodeobject.h

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extern "C" {
1212
#include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI
1313

1414
void _PyUnicode_ExactDealloc(PyObject *op);
15+
Py_ssize_t _PyUnicode_InternedSize(void);
1516

1617
/* runtime lifecycle */
1718

0 commit comments

Comments
 (0)