Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-94673: Add Per-Interpreter tp_subclasses for Static Builtin Types #95301

Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7b299d1
Add a note about tp_cache.
ericsnowcurrently Jul 7, 2022
4fefd9b
Hide tp_subclasses behind functions.
ericsnowcurrently Jul 8, 2022
129ca1f
Distinquish between static and heap types.
ericsnowcurrently Jul 8, 2022
183b6b5
Move tp_subclasses for static types to PyInterpreterState.
ericsnowcurrently Jul 8, 2022
a707c38
Add a note about heap types and tp_subclasses.
ericsnowcurrently Jul 8, 2022
5dd2474
Use PyDict_GetItemWithError().
ericsnowcurrently Jul 8, 2022
80b1d26
Add lookup___hash__().
ericsnowcurrently Jul 8, 2022
8bced84
Handle the case of unhashable classes.
ericsnowcurrently Jul 8, 2022
533951d
Really handle the case of unhashable classes.
ericsnowcurrently Jul 8, 2022
f9ab261
Stop special-casing heap types for tp_subclasses.
ericsnowcurrently Jul 8, 2022
ccabeb5
Revert "Stop special-casing heap types for tp_subclasses."
ericsnowcurrently Jul 8, 2022
df204e8
Point to the relevant GH issue.
ericsnowcurrently Jul 9, 2022
3529f0f
Only store builtins state on the interpreter state.
ericsnowcurrently Jul 9, 2022
47c6ded
state.subclasses -> state.tp_subclasses
ericsnowcurrently Jul 19, 2022
eade37b
Do not set type.tp_subclasses for static builtin types.
ericsnowcurrently Jul 19, 2022
5687c8e
Store the index in tp_subclasses.
ericsnowcurrently Jul 25, 2022
2fa815e
Fix test_sys.
ericsnowcurrently Jul 27, 2022
62a48da
Document the change.
ericsnowcurrently Jul 29, 2022
12d682a
Make tp_subclasses void*.
ericsnowcurrently Jul 29, 2022
dace9c4
lookup_subclasses() never fails.
ericsnowcurrently Jul 29, 2022
58750a3
Merge branch 'main' into shareable-static-types-tp_subclasses
ericsnowcurrently Jul 29, 2022
e7f580d
Adjust docs for ``tp_subclasses``
encukou Jul 29, 2022
6b19763
lookup_subclasses() never fails.
ericsnowcurrently Aug 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ struct _typeobject {
inquiry tp_is_gc; /* For PyObject_IS_GC */
PyObject *tp_bases;
PyObject *tp_mro; /* method resolution order */
PyObject *tp_cache;
PyObject *tp_subclasses;
PyObject *tp_cache; /* no longer used */
PyObject *tp_subclasses; /* for static builtin types this is an index */
PyObject *tp_weaklist;
destructor tp_del;

Expand All @@ -227,7 +227,6 @@ struct _typeobject {

destructor tp_finalize;
vectorcallfunc tp_vectorcall;
size_t tp_static_builtin_index; /* 0 means "not initialized" */
};

/* This struct is used by the specializer
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ extern int _PyObject_VisitInstanceAttributes(PyObject *self, visitproc visit, vo
extern void _PyObject_ClearInstanceAttributes(PyObject *self);
extern void _PyObject_FreeInstanceAttributes(PyObject *self);
extern int _PyObject_IsInstanceDictEmpty(PyObject *);
extern int _PyType_HasSubclasses(PyTypeObject *);
extern PyObject* _PyType_GetSubclasses(PyTypeObject *);

// Access macro to the members which are floating "behind" the object
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct type_cache {

typedef struct {
PyTypeObject *type;
PyObject *tp_subclasses;
} static_builtin_state;

struct types_state {
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1507,7 +1507,7 @@ def delx(self): del self.__x
check((1,2,3), vsize('') + 3*self.P)
# type
# static type: PyTypeObject
fmt = 'P2nPI13Pl4Pn9Pn12PIPI'
fmt = 'P2nPI13Pl4Pn9Pn12PIP'
s = vsize('2P' + fmt)
check(int, s)
# class
Expand Down
3 changes: 2 additions & 1 deletion Objects/structseq.c
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,8 @@ _PyStructSequence_FiniType(PyTypeObject *type)
assert(type->tp_base == &PyTuple_Type);

// Cannot delete a type if it still has subclasses
if (type->tp_subclasses != NULL) {
if (_PyType_HasSubclasses(type)) {
PyErr_Clear();
ericsnowcurrently marked this conversation as resolved.
Show resolved Hide resolved
return;
}

Expand Down
130 changes: 106 additions & 24 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,29 +73,29 @@ static inline PyTypeObject * subclass_from_ref(PyObject *ref);
static inline int
static_builtin_index_is_set(PyTypeObject *self)
{
return self->tp_static_builtin_index > 0;
return self->tp_subclasses != NULL;
}

static inline size_t
static_builtin_index_get(PyTypeObject *self)
{
assert(static_builtin_index_is_set(self));
/* We store a 1-based index so 0 can mean "not initialized". */
return self->tp_static_builtin_index - 1;
return (size_t)self->tp_subclasses - 1;
}

static inline void
static_builtin_index_set(PyTypeObject *self, size_t index)
{
assert(index < _Py_MAX_STATIC_BUILTIN_TYPES);
/* We store a 1-based index so 0 can mean "not initialized". */
self->tp_static_builtin_index = index + 1;
self->tp_subclasses = (PyObject *)(index + 1);
}

static inline void
static_builtin_index_clear(PyTypeObject *self)
{
self->tp_static_builtin_index = 0;
self->tp_subclasses = NULL;
}

static inline static_builtin_state *
Expand Down Expand Up @@ -127,6 +127,7 @@ static_builtin_state_init(PyTypeObject *self)

static_builtin_state *state = static_builtin_state_get(interp, self);
state->type = self;
/* state->tp_subclasses is left NULL until init_subclasses() sets it. */
}

static void
Expand Down Expand Up @@ -370,6 +371,8 @@ _PyTypes_Fini(PyInterpreterState *interp)
}


static PyObject * lookup_subclasses(PyTypeObject *);

void
PyType_Modified(PyTypeObject *type)
{
Expand All @@ -392,7 +395,7 @@ PyType_Modified(PyTypeObject *type)
return;
}

PyObject *subclasses = type->tp_subclasses;
PyObject *subclasses = lookup_subclasses(type);
if (subclasses != NULL) {
assert(PyDict_CheckExact(subclasses));

Expand All @@ -406,6 +409,9 @@ PyType_Modified(PyTypeObject *type)
PyType_Modified(subclass);
}
}
else if (PyErr_Occurred()) {
PyErr_Clear();
}

type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
type->tp_version_tag = 0; /* 0 is not a valid version tag */
Expand Down Expand Up @@ -778,7 +784,8 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
Py_XDECREF(old_mro);

// Avoid creating an empty list if there is no subclass
if (type->tp_subclasses != NULL) {
res = _PyType_HasSubclasses(type);
if (res > 0) {
/* Obtain a copy of subclasses list to iterate over.

Otherwise type->tp_subclasses might be altered
Expand Down Expand Up @@ -4300,10 +4307,14 @@ type_dealloc_common(PyTypeObject *type)
}


static void clear_subclasses(PyTypeObject *self);

static void
clear_static_tp_subclasses(PyTypeObject *type)
{
if (type->tp_subclasses == NULL) {
PyObject *subclasses = lookup_subclasses(type);
if (subclasses == NULL) {
PyErr_Clear();
ericsnowcurrently marked this conversation as resolved.
Show resolved Hide resolved
return;
}

Expand All @@ -4327,9 +4338,19 @@ clear_static_tp_subclasses(PyTypeObject *type)
going to leak. This mostly only affects embedding scenarios.
*/

// For now we just clear tp_subclasses.
// For now we just do a sanity check and then clear tp_subclasses.
Py_ssize_t i = 0;
PyObject *key, *ref; // borrowed ref
while (PyDict_Next(subclasses, &i, &key, &ref)) {
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
if (subclass == NULL) {
continue;
}
// All static builtin subtypes should have been finalized already.
assert(!(subclass->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
}

Py_CLEAR(type->tp_subclasses);
clear_subclasses(type);
}

void
Expand Down Expand Up @@ -4378,7 +4399,7 @@ type_dealloc(PyTypeObject *type)
Py_XDECREF(type->tp_bases);
Py_XDECREF(type->tp_mro);
Py_XDECREF(type->tp_cache);
Py_XDECREF(type->tp_subclasses);
clear_subclasses(type);

/* A type's tp_doc is heap allocated, unlike the tp_doc slots
* of most other objects. It's okay to cast it to char *.
Expand All @@ -4398,6 +4419,30 @@ type_dealloc(PyTypeObject *type)
}


static PyObject *
lookup_subclasses(PyTypeObject *self)
{
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
static_builtin_state *state = _PyStaticType_GetState(self);
assert(state != NULL);
return state->tp_subclasses;
}
return self->tp_subclasses;
}

int
_PyType_HasSubclasses(PyTypeObject *self)
{
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN &&
_PyStaticType_GetState(self) == NULL) {
return 0;
}
if (lookup_subclasses(self) == NULL) {
return PyErr_Occurred() ? -1 : 0;
ericsnowcurrently marked this conversation as resolved.
Show resolved Hide resolved
}
return 1;
}

PyObject*
_PyType_GetSubclasses(PyTypeObject *self)
{
Expand All @@ -4406,8 +4451,11 @@ _PyType_GetSubclasses(PyTypeObject *self)
return NULL;
}

PyObject *subclasses = self->tp_subclasses; // borrowed ref
PyObject *subclasses = lookup_subclasses(self); // borrowed ref
if (subclasses == NULL) {
if (PyErr_Occurred()) {
return NULL;
}
return list;
}
assert(PyDict_CheckExact(subclasses));
Expand Down Expand Up @@ -6784,6 +6832,36 @@ _PyStaticType_InitBuiltin(PyTypeObject *self)
}


static PyObject *
init_subclasses(PyTypeObject *self)
{
PyObject *subclasses = PyDict_New();
if (subclasses == NULL) {
return NULL;
}
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
static_builtin_state *state = _PyStaticType_GetState(self);
state->tp_subclasses = subclasses;
return subclasses;
}
self->tp_subclasses = subclasses;
return subclasses;
}

static void
clear_subclasses(PyTypeObject *self)
{
/* Delete the dictionary to save memory. _PyStaticType_Dealloc()
callers also test if tp_subclasses is NULL to check if a static type
has no subclass. */
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
static_builtin_state *state = _PyStaticType_GetState(self);
Py_CLEAR(state->tp_subclasses);
return;
}
Py_CLEAR(self->tp_subclasses);
}

static int
add_subclass(PyTypeObject *base, PyTypeObject *type)
{
Expand All @@ -6800,9 +6878,12 @@ add_subclass(PyTypeObject *base, PyTypeObject *type)
// Only get tp_subclasses after creating the key and value.
// PyWeakref_NewRef() can trigger a garbage collection which can execute
// arbitrary Python code and so modify base->tp_subclasses.
PyObject *subclasses = base->tp_subclasses;
PyObject *subclasses = lookup_subclasses(base);
if (subclasses == NULL) {
base->tp_subclasses = subclasses = PyDict_New();
if (PyErr_Occurred()) {
return -1;
}
subclasses = init_subclasses(base);
if (subclasses == NULL) {
Py_DECREF(key);
Py_DECREF(ref);
Expand Down Expand Up @@ -6859,10 +6940,13 @@ get_subclasses_key(PyTypeObject *type, PyTypeObject *base)
We fall back to manually traversing the values. */
Py_ssize_t i = 0;
PyObject *ref; // borrowed ref
while (PyDict_Next((PyObject *)base->tp_subclasses, &i, &key, &ref)) {
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
if (subclass == type) {
return Py_NewRef(key);
PyObject *subclasses = lookup_subclasses(base);
if (subclasses != NULL) {
while (PyDict_Next(subclasses, &i, &key, &ref)) {
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
if (subclass == type) {
return Py_NewRef(key);
}
}
}
/* It wasn't found. */
Expand All @@ -6872,8 +6956,9 @@ get_subclasses_key(PyTypeObject *type, PyTypeObject *base)
static void
remove_subclass(PyTypeObject *base, PyTypeObject *type)
{
PyObject *subclasses = base->tp_subclasses; // borrowed ref
PyObject *subclasses = lookup_subclasses(base); // borrowed ref
if (subclasses == NULL) {
PyErr_Clear();
return;
}
assert(PyDict_CheckExact(subclasses));
Expand All @@ -6888,10 +6973,7 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type)
Py_XDECREF(key);

if (PyDict_Size(subclasses) == 0) {
// Delete the dictionary to save memory. _PyStaticType_Dealloc()
// callers also test if tp_subclasses is NULL to check if a static type
// has no subclass.
Py_CLEAR(base->tp_subclasses);
clear_subclasses(base);
}
}

Expand Down Expand Up @@ -8963,9 +9045,9 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name,
// It is safe to use a borrowed reference because update_subclasses() is
// only used with update_slots_callback() which doesn't modify
// tp_subclasses.
PyObject *subclasses = type->tp_subclasses; // borrowed ref
PyObject *subclasses = lookup_subclasses(type); // borrowed ref
if (subclasses == NULL) {
return 0;
return PyErr_Occurred() ? -1 : 0;
}
assert(PyDict_CheckExact(subclasses));

Expand Down