GH-95245: Move weakreflist into the pre-header. (GH-95996)

This commit is contained in:
Mark Shannon 2022-08-16 13:57:18 +01:00 committed by GitHub
parent 829aab8592
commit 5a8c15819c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 20 deletions

View file

@ -279,7 +279,7 @@ static inline size_t
_PyType_PreHeaderSize(PyTypeObject *tp) _PyType_PreHeaderSize(PyTypeObject *tp)
{ {
return _PyType_IS_GC(tp) * sizeof(PyGC_Head) + return _PyType_IS_GC(tp) * sizeof(PyGC_Head) +
_PyType_HasFeature(tp, Py_TPFLAGS_MANAGED_DICT) * 2 * sizeof(PyObject *); _PyType_HasFeature(tp, Py_TPFLAGS_PREHEADER) * 2 * sizeof(PyObject *);
} }
void _PyObject_GC_Link(PyObject *op); void _PyObject_GC_Link(PyObject *op);
@ -296,7 +296,7 @@ extern int _Py_CheckSlotResult(
// Test if a type supports weak references // Test if a type supports weak references
static inline int _PyType_SUPPORTS_WEAKREFS(PyTypeObject *type) { static inline int _PyType_SUPPORTS_WEAKREFS(PyTypeObject *type) {
return (type->tp_weaklistoffset > 0); return (type->tp_weaklistoffset != 0);
} }
extern PyObject* _PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems); extern PyObject* _PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems);
@ -346,7 +346,7 @@ _PyDictOrValues_SetValues(PyDictOrValues *ptr, PyDictValues *values)
ptr->values = ((char *)values) - 1; ptr->values = ((char *)values) - 1;
} }
#define MANAGED_DICT_OFFSET (((int)sizeof(PyObject *))*-3) #define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-4)
extern PyObject ** _PyObject_ComputedDictPointer(PyObject *); extern PyObject ** _PyObject_ComputedDictPointer(PyObject *);
extern void _PyObject_FreeInstanceAttributes(PyObject *obj); extern void _PyObject_FreeInstanceAttributes(PyObject *obj);

View file

@ -360,12 +360,18 @@ given type object has a specified feature.
/* Track types initialized using _PyStaticType_InitBuiltin(). */ /* Track types initialized using _PyStaticType_InitBuiltin(). */
#define _Py_TPFLAGS_STATIC_BUILTIN (1 << 1) #define _Py_TPFLAGS_STATIC_BUILTIN (1 << 1)
/* Placement of weakref pointers are managed by the VM, not by the type.
* The VM will automatically set tp_weaklistoffset.
*/
#define Py_TPFLAGS_MANAGED_WEAKREF (1 << 3)
/* Placement of dict (and values) pointers are managed by the VM, not by the type. /* Placement of dict (and values) pointers are managed by the VM, not by the type.
* The VM will automatically set tp_dictoffset. Should not be used for variable sized * The VM will automatically set tp_dictoffset.
* classes, such as classes that extend tuple.
*/ */
#define Py_TPFLAGS_MANAGED_DICT (1 << 4) #define Py_TPFLAGS_MANAGED_DICT (1 << 4)
#define Py_TPFLAGS_PREHEADER (Py_TPFLAGS_MANAGED_WEAKREF | Py_TPFLAGS_MANAGED_DICT)
/* Set if instances of the type object are treated as sequences for pattern matching */ /* Set if instances of the type object are treated as sequences for pattern matching */
#define Py_TPFLAGS_SEQUENCE (1 << 5) #define Py_TPFLAGS_SEQUENCE (1 << 5)
/* Set if instances of the type object are treated as mappings for pattern matching */ /* Set if instances of the type object are treated as mappings for pattern matching */

View file

@ -579,6 +579,37 @@ class CAPITest(unittest.TestCase):
self.assertEqual(ref(), inst) self.assertEqual(ref(), inst)
self.assertEqual(inst.weakreflist, ref) self.assertEqual(inst.weakreflist, ref)
def test_heaptype_with_managed_weakref(self):
inst = _testcapi.HeapCTypeWithManagedWeakref()
ref = weakref.ref(inst)
self.assertEqual(ref(), inst)
def test_sublclassing_managed_weakref(self):
class C(_testcapi.HeapCTypeWithManagedWeakref):
pass
inst = C()
ref = weakref.ref(inst)
self.assertEqual(ref(), inst)
def test_sublclassing_managed_both(self):
class C1(_testcapi.HeapCTypeWithManagedWeakref, _testcapi.HeapCTypeWithManagedDict):
pass
class C2(_testcapi.HeapCTypeWithManagedDict, _testcapi.HeapCTypeWithManagedWeakref):
pass
for cls in (C1, C2):
inst = cls()
ref = weakref.ref(inst)
self.assertEqual(ref(), inst)
inst.spam = inst
del inst
ref = weakref.ref(cls())
self.assertIs(ref(), None)
def test_heaptype_with_buffer(self): def test_heaptype_with_buffer(self):
inst = _testcapi.HeapCTypeWithBuffer() inst = _testcapi.HeapCTypeWithBuffer()
b = bytes(inst) b = bytes(inst)

View file

@ -0,0 +1,3 @@
Reduces the size of a "simple" Python object from 8 to 6 words by moving the
weakreflist pointer into the pre-header directly before the object's
dict/values pointer.

View file

@ -801,6 +801,32 @@ static PyType_Spec HeapCTypeWithManagedDict_spec = {
HeapCTypeWithManagedDict_slots HeapCTypeWithManagedDict_slots
}; };
static void
heapctypewithmanagedweakref_dealloc(PyObject* self)
{
PyTypeObject *tp = Py_TYPE(self);
PyObject_ClearWeakRefs(self);
PyObject_GC_UnTrack(self);
PyObject_GC_Del(self);
Py_DECREF(tp);
}
static PyType_Slot HeapCTypeWithManagedWeakref_slots[] = {
{Py_tp_traverse, heapgcctype_traverse},
{Py_tp_getset, heapctypewithdict_getsetlist},
{Py_tp_dealloc, heapctypewithmanagedweakref_dealloc},
{0, 0},
};
static PyType_Spec HeapCTypeWithManagedWeakref_spec = {
"_testcapi.HeapCTypeWithManagedWeakref",
sizeof(PyObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_MANAGED_WEAKREF,
HeapCTypeWithManagedWeakref_slots
};
static struct PyMemberDef heapctypewithnegativedict_members[] = { static struct PyMemberDef heapctypewithnegativedict_members[] = {
{"dictobj", T_OBJECT, offsetof(HeapCTypeWithDictObject, dict)}, {"dictobj", T_OBJECT, offsetof(HeapCTypeWithDictObject, dict)},
{"__dictoffset__", T_PYSSIZET, -(Py_ssize_t)sizeof(void*), READONLY}, {"__dictoffset__", T_PYSSIZET, -(Py_ssize_t)sizeof(void*), READONLY},
@ -1009,6 +1035,12 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
} }
PyModule_AddObject(m, "HeapCTypeWithManagedDict", HeapCTypeWithManagedDict); PyModule_AddObject(m, "HeapCTypeWithManagedDict", HeapCTypeWithManagedDict);
PyObject *HeapCTypeWithManagedWeakref = PyType_FromSpec(&HeapCTypeWithManagedWeakref_spec);
if (HeapCTypeWithManagedWeakref == NULL) {
return -1;
}
PyModule_AddObject(m, "HeapCTypeWithManagedWeakref", HeapCTypeWithManagedWeakref);
PyObject *HeapCTypeWithWeakref = PyType_FromSpec(&HeapCTypeWithWeakref_spec); PyObject *HeapCTypeWithWeakref = PyType_FromSpec(&HeapCTypeWithWeakref_spec);
if (HeapCTypeWithWeakref == NULL) { if (HeapCTypeWithWeakref == NULL) {
return -1; return -1;

View file

@ -2497,10 +2497,11 @@ subtype_getweakref(PyObject *obj, void *context)
return NULL; return NULL;
} }
_PyObject_ASSERT((PyObject *)type, _PyObject_ASSERT((PyObject *)type,
type->tp_weaklistoffset > 0); type->tp_weaklistoffset > 0 ||
type->tp_weaklistoffset == MANAGED_WEAKREF_OFFSET);
_PyObject_ASSERT((PyObject *)type, _PyObject_ASSERT((PyObject *)type,
((type->tp_weaklistoffset + sizeof(PyObject *)) ((type->tp_weaklistoffset + (Py_ssize_t)sizeof(PyObject *))
<= (size_t)(type->tp_basicsize))); <= type->tp_basicsize));
weaklistptr = (PyObject **)((char *)obj + type->tp_weaklistoffset); weaklistptr = (PyObject **)((char *)obj + type->tp_weaklistoffset);
if (*weaklistptr == NULL) if (*weaklistptr == NULL)
result = Py_None; result = Py_None;
@ -3093,9 +3094,9 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type)
} }
if (ctx->add_weak) { if (ctx->add_weak) {
assert(!ctx->base->tp_itemsize); assert((type->tp_flags & Py_TPFLAGS_MANAGED_WEAKREF) == 0);
type->tp_weaklistoffset = slotoffset; type->tp_flags |= Py_TPFLAGS_MANAGED_WEAKREF;
slotoffset += sizeof(PyObject *); type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET;
} }
if (ctx->add_dict) { if (ctx->add_dict) {
assert((type->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0); assert((type->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0);
@ -5116,9 +5117,9 @@ compatible_for_assignment(PyTypeObject* oldto, PyTypeObject* newto, const char*
!same_slots_added(newbase, oldbase))) { !same_slots_added(newbase, oldbase))) {
goto differs; goto differs;
} }
/* The above does not check for managed __dicts__ */ /* The above does not check for the preheader */
if ((oldto->tp_flags & Py_TPFLAGS_MANAGED_DICT) == if ((oldto->tp_flags & Py_TPFLAGS_PREHEADER) ==
((newto->tp_flags & Py_TPFLAGS_MANAGED_DICT))) ((newto->tp_flags & Py_TPFLAGS_PREHEADER)))
{ {
return 1; return 1;
} }
@ -5217,7 +5218,7 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
if (compatible_for_assignment(oldto, newto, "__class__")) { if (compatible_for_assignment(oldto, newto, "__class__")) {
/* Changing the class will change the implicit dict keys, /* Changing the class will change the implicit dict keys,
* so we must materialize the dictionary first. */ * so we must materialize the dictionary first. */
assert((oldto->tp_flags & Py_TPFLAGS_MANAGED_DICT) == (newto->tp_flags & Py_TPFLAGS_MANAGED_DICT)); assert((oldto->tp_flags & Py_TPFLAGS_PREHEADER) == (newto->tp_flags & Py_TPFLAGS_PREHEADER));
_PyObject_GetDictPtr(self); _PyObject_GetDictPtr(self);
if (oldto->tp_flags & Py_TPFLAGS_MANAGED_DICT && if (oldto->tp_flags & Py_TPFLAGS_MANAGED_DICT &&
_PyDictOrValues_IsValues(*_PyObject_DictOrValuesPointer(self))) _PyDictOrValues_IsValues(*_PyObject_DictOrValuesPointer(self)))
@ -5360,7 +5361,7 @@ object_getstate_default(PyObject *obj, int required)
{ {
basicsize += sizeof(PyObject *); basicsize += sizeof(PyObject *);
} }
if (Py_TYPE(obj)->tp_weaklistoffset) { if (Py_TYPE(obj)->tp_weaklistoffset > 0) {
basicsize += sizeof(PyObject *); basicsize += sizeof(PyObject *);
} }
if (slotnames != Py_None) { if (slotnames != Py_None) {
@ -6150,7 +6151,7 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
if (type->tp_clear == NULL) if (type->tp_clear == NULL)
type->tp_clear = base->tp_clear; type->tp_clear = base->tp_clear;
} }
type->tp_flags |= (base->tp_flags & Py_TPFLAGS_MANAGED_DICT); type->tp_flags |= (base->tp_flags & Py_TPFLAGS_PREHEADER);
if (type->tp_basicsize == 0) if (type->tp_basicsize == 0)
type->tp_basicsize = base->tp_basicsize; type->tp_basicsize = base->tp_basicsize;
@ -6571,7 +6572,7 @@ type_ready_fill_dict(PyTypeObject *type)
} }
static int static int
type_ready_dict_offset(PyTypeObject *type) type_ready_preheader(PyTypeObject *type)
{ {
if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) { if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
if (type->tp_dictoffset > 0 || type->tp_dictoffset < -1) { if (type->tp_dictoffset > 0 || type->tp_dictoffset < -1) {
@ -6583,6 +6584,18 @@ type_ready_dict_offset(PyTypeObject *type)
} }
type->tp_dictoffset = -1; type->tp_dictoffset = -1;
} }
if (type->tp_flags & Py_TPFLAGS_MANAGED_WEAKREF) {
if (type->tp_weaklistoffset != 0 &&
type->tp_weaklistoffset != MANAGED_WEAKREF_OFFSET)
{
PyErr_Format(PyExc_TypeError,
"type %s has the Py_TPFLAGS_MANAGED_WEAKREF flag "
"but tp_weaklistoffset is set",
type->tp_name);
return -1;
}
type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET;
}
return 0; return 0;
} }
@ -6802,7 +6815,7 @@ type_ready_post_checks(PyTypeObject *type)
return -1; return -1;
} }
} }
else if (type->tp_dictoffset < sizeof(PyObject)) { else if (type->tp_dictoffset < (Py_ssize_t)sizeof(PyObject)) {
if (type->tp_dictoffset + type->tp_basicsize <= 0) { if (type->tp_dictoffset + type->tp_basicsize <= 0) {
PyErr_Format(PyExc_SystemError, PyErr_Format(PyExc_SystemError,
"type %s has a tp_dictoffset that is too small"); "type %s has a tp_dictoffset that is too small");
@ -6847,7 +6860,7 @@ type_ready(PyTypeObject *type)
if (type_ready_inherit(type) < 0) { if (type_ready_inherit(type) < 0) {
return -1; return -1;
} }
if (type_ready_dict_offset(type) < 0) { if (type_ready_preheader(type) < 0) {
return -1; return -1;
} }
if (type_ready_set_hash(type) < 0) { if (type_ready_set_hash(type) < 0) {