diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 79c1c44ae72d60..001be56fab8207 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -30,6 +30,55 @@ _PyType_HasFeature(PyTypeObject *type, unsigned long feature) { extern void _PyType_InitCache(PyInterpreterState *interp); +/* Immortal Objects + * + * An "immortal" object is one for which Py_DECREF() will never try + * to deallocate it. + * + * At the moment this API is strictly internal. However, if it proves + * helpful for extension authors we may move it to the public API. */ + +#define _Py_IMMORTAL_OBJECTS 1 + +/* The implementation-independent API is only the following functions: */ +PyAPI_FUNC(int) _PyObject_IsImmortal(PyObject *); +PyAPI_FUNC(void) _PyObject_SetImmortal(PyObject *); + +/* In the actual implementation we set the refcount to some positive + * value that we would never expect to be reachable through use of + * Py_INCREF() in a program. + * + * The only parts that should be used directly are the two + * _Py*Object_HEAD_IMMORTAL_INIT() macros. + */ + +/* _PyObject_IMMORTAL_BIT is the bit in the refcount value (Py_ssize_t) + * that we use to mark an object as immortal. It shouldn't ever be + * part of the public API. + * + * The GC bit-shifts refcounts left by two, and after that shift we still + * need this to be >> 0, so leave three high zero bits (the sign bit and + * room for a shift of two.) */ +#define _PyObject_IMMORTAL_BIT (1LL << (8 * sizeof(Py_ssize_t) - 4)) + +/* _PyObject_IMMORTAL_INIT_REFCNT is the initial value we use for + * immortal objects. It shouldn't ever be part of the public API. + * + * We leave plenty of room to preserve _PyObject_IMMORTAL_BIT. */ +#define _PyObject_IMMORTAL_INIT_REFCNT \ + (_PyObject_IMMORTAL_BIT + (_PyObject_IMMORTAL_BIT / 2)) + +/* These macros are drop-in replacements for the corresponding + * Py*Object_HEAD_INIT() macros. They will probably become + * part of the public API. */ +#define _PyObject_HEAD_IMMORTAL_INIT(type) \ + { _PyObject_EXTRA_INIT _PyObject_IMMORTAL_INIT_REFCNT, type }, +#define _PyVarObject_HEAD_IMMORTAL_INIT(type, size) \ + { PyObject_HEAD_IMMORTAL_INIT(type) size }, + +/* end Immortal Objects */ + + /* Inline functions trading binary compatibility for speed: _PyObject_Init() is the fast version of PyObject_Init(), and _PyObject_InitVar() is the fast version of PyObject_InitVar(). diff --git a/Include/object.h b/Include/object.h index 14545839341f4a..c2f626bf4f93b0 100644 --- a/Include/object.h +++ b/Include/object.h @@ -141,9 +141,7 @@ static inline int _Py_IS_TYPE(const PyObject *ob, const PyTypeObject *type) { #define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST_CONST(ob), type) -static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) { - ob->ob_refcnt = refcnt; -} +PyAPI_FUNC(void) _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt); #define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt) @@ -427,6 +425,10 @@ PyAPI_FUNC(void) _Py_NegativeRefcount(const char *filename, int lineno, PyAPI_FUNC(void) _Py_Dealloc(PyObject *); +#ifdef Py_IMMORTAL_CONST_REFCOUNTS +static inline int _py_is_immortal(PyObject *); // forward +#endif + static inline void _Py_INCREF(PyObject *op) { #ifdef Py_REF_DEBUG diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-03-11-13-03-15.bpo-40255._1LfiG.rst b/Misc/NEWS.d/next/Core and Builtins/2021-03-11-13-03-15.bpo-40255._1LfiG.rst new file mode 100644 index 00000000000000..d7236aa94278ff --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-03-11-13-03-15.bpo-40255._1LfiG.rst @@ -0,0 +1,5 @@ +There is now internal support for "immortal" objects (with a "private" +C-API). Those are objects that will never be deleted, like the +singletons and static types. This will benefit the C-API and +subinterpreters. For now the API is currently intended only +for internal use. diff --git a/Objects/object.c b/Objects/object.c index 0a8621b3503b31..2ff92779d5ac8a 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -136,6 +136,30 @@ Py_DecRef(PyObject *o) Py_XDECREF(o); } +void +_Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) { + if (_PyObject_IsImmortal(ob)) { + // XXX It may be worth emitting a warning here. + return; + } + ob->ob_refcnt = refcnt; +} + +int +_PyObject_IsImmortal(PyObject *ob) +{ + if ((ob->ob_refcnt & _PyObject_IMMORTAL_BIT) == 0) { + return 0; + } + return 1; +} + +void +_PyObject_SetImmortal(PyObject *ob) +{ + ob->ob_refcnt = _PyObject_IMMORTAL_INIT_REFCNT; +} + PyObject * PyObject_Init(PyObject *op, PyTypeObject *tp) { @@ -1728,11 +1752,14 @@ _PyTypes_Init(void) return status; } + // XXX We can stop calling _PyObject_SetImmortal() once we change + // all the static types to use PyVarObject_HEAD_IMMORTAL_INIT. #define INIT_TYPE(TYPE, NAME) \ do { \ if (PyType_Ready(TYPE) < 0) { \ return _PyStatus_ERR("Can't initialize " NAME " type"); \ } \ + _PyObject_SetImmortal((PyObject *)TYPE); \ } while (0) INIT_TYPE(&PyBaseObject_Type, "object"); @@ -1807,10 +1834,19 @@ _PyTypes_Init(void) #undef INIT_TYPE } +/* _Py_NewReference() is called in the following situations: + * - for newly allocatoed objects, via _PyObject_Init() + * - when resizing immutable objects (bytes, unicode, tuple) + * - when "allocating" from a freelist + * - when resurrecting an object + */ void _Py_NewReference(PyObject *op) { + // None of the cases above should ever apply to immortal objects. + assert(!_PyObject_IsImmortal(op)); + if (_Py_tracemalloc_config.tracing) { _PyTraceMalloc_NewReference(op); }