Skip to content

Commit 5a8c158

Browse files
authored
GH-95245: Move weakreflist into the pre-header. (GH-95996)
1 parent 829aab8 commit 5a8c158

File tree

6 files changed

+105
-20
lines changed

6 files changed

+105
-20
lines changed

Include/internal/pycore_object.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ static inline size_t
279279
_PyType_PreHeaderSize(PyTypeObject *tp)
280280
{
281281
return _PyType_IS_GC(tp) * sizeof(PyGC_Head) +
282-
_PyType_HasFeature(tp, Py_TPFLAGS_MANAGED_DICT) * 2 * sizeof(PyObject *);
282+
_PyType_HasFeature(tp, Py_TPFLAGS_PREHEADER) * 2 * sizeof(PyObject *);
283283
}
284284

285285
void _PyObject_GC_Link(PyObject *op);
@@ -296,7 +296,7 @@ extern int _Py_CheckSlotResult(
296296

297297
// Test if a type supports weak references
298298
static inline int _PyType_SUPPORTS_WEAKREFS(PyTypeObject *type) {
299-
return (type->tp_weaklistoffset > 0);
299+
return (type->tp_weaklistoffset != 0);
300300
}
301301

302302
extern PyObject* _PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems);
@@ -346,7 +346,7 @@ _PyDictOrValues_SetValues(PyDictOrValues *ptr, PyDictValues *values)
346346
ptr->values = ((char *)values) - 1;
347347
}
348348

349-
#define MANAGED_DICT_OFFSET (((int)sizeof(PyObject *))*-3)
349+
#define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-4)
350350

351351
extern PyObject ** _PyObject_ComputedDictPointer(PyObject *);
352352
extern void _PyObject_FreeInstanceAttributes(PyObject *obj);

Include/object.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,12 +360,18 @@ given type object has a specified feature.
360360
/* Track types initialized using _PyStaticType_InitBuiltin(). */
361361
#define _Py_TPFLAGS_STATIC_BUILTIN (1 << 1)
362362

363+
/* Placement of weakref pointers are managed by the VM, not by the type.
364+
* The VM will automatically set tp_weaklistoffset.
365+
*/
366+
#define Py_TPFLAGS_MANAGED_WEAKREF (1 << 3)
367+
363368
/* Placement of dict (and values) pointers are managed by the VM, not by the type.
364-
* The VM will automatically set tp_dictoffset. Should not be used for variable sized
365-
* classes, such as classes that extend tuple.
369+
* The VM will automatically set tp_dictoffset.
366370
*/
367371
#define Py_TPFLAGS_MANAGED_DICT (1 << 4)
368372

373+
#define Py_TPFLAGS_PREHEADER (Py_TPFLAGS_MANAGED_WEAKREF | Py_TPFLAGS_MANAGED_DICT)
374+
369375
/* Set if instances of the type object are treated as sequences for pattern matching */
370376
#define Py_TPFLAGS_SEQUENCE (1 << 5)
371377
/* Set if instances of the type object are treated as mappings for pattern matching */

Lib/test/test_capi.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,37 @@ def test_heaptype_with_weakref(self):
579579
self.assertEqual(ref(), inst)
580580
self.assertEqual(inst.weakreflist, ref)
581581

582+
def test_heaptype_with_managed_weakref(self):
583+
inst = _testcapi.HeapCTypeWithManagedWeakref()
584+
ref = weakref.ref(inst)
585+
self.assertEqual(ref(), inst)
586+
587+
def test_sublclassing_managed_weakref(self):
588+
589+
class C(_testcapi.HeapCTypeWithManagedWeakref):
590+
pass
591+
592+
inst = C()
593+
ref = weakref.ref(inst)
594+
self.assertEqual(ref(), inst)
595+
596+
def test_sublclassing_managed_both(self):
597+
598+
class C1(_testcapi.HeapCTypeWithManagedWeakref, _testcapi.HeapCTypeWithManagedDict):
599+
pass
600+
601+
class C2(_testcapi.HeapCTypeWithManagedDict, _testcapi.HeapCTypeWithManagedWeakref):
602+
pass
603+
604+
for cls in (C1, C2):
605+
inst = cls()
606+
ref = weakref.ref(inst)
607+
self.assertEqual(ref(), inst)
608+
inst.spam = inst
609+
del inst
610+
ref = weakref.ref(cls())
611+
self.assertIs(ref(), None)
612+
582613
def test_heaptype_with_buffer(self):
583614
inst = _testcapi.HeapCTypeWithBuffer()
584615
b = bytes(inst)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Reduces the size of a "simple" Python object from 8 to 6 words by moving the
2+
weakreflist pointer into the pre-header directly before the object's
3+
dict/values pointer.

Modules/_testcapi/heaptype.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,32 @@ static PyType_Spec HeapCTypeWithManagedDict_spec = {
801801
HeapCTypeWithManagedDict_slots
802802
};
803803

804+
static void
805+
heapctypewithmanagedweakref_dealloc(PyObject* self)
806+
{
807+
808+
PyTypeObject *tp = Py_TYPE(self);
809+
PyObject_ClearWeakRefs(self);
810+
PyObject_GC_UnTrack(self);
811+
PyObject_GC_Del(self);
812+
Py_DECREF(tp);
813+
}
814+
815+
static PyType_Slot HeapCTypeWithManagedWeakref_slots[] = {
816+
{Py_tp_traverse, heapgcctype_traverse},
817+
{Py_tp_getset, heapctypewithdict_getsetlist},
818+
{Py_tp_dealloc, heapctypewithmanagedweakref_dealloc},
819+
{0, 0},
820+
};
821+
822+
static PyType_Spec HeapCTypeWithManagedWeakref_spec = {
823+
"_testcapi.HeapCTypeWithManagedWeakref",
824+
sizeof(PyObject),
825+
0,
826+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_MANAGED_WEAKREF,
827+
HeapCTypeWithManagedWeakref_slots
828+
};
829+
804830
static struct PyMemberDef heapctypewithnegativedict_members[] = {
805831
{"dictobj", T_OBJECT, offsetof(HeapCTypeWithDictObject, dict)},
806832
{"__dictoffset__", T_PYSSIZET, -(Py_ssize_t)sizeof(void*), READONLY},
@@ -1009,6 +1035,12 @@ _PyTestCapi_Init_Heaptype(PyObject *m) {
10091035
}
10101036
PyModule_AddObject(m, "HeapCTypeWithManagedDict", HeapCTypeWithManagedDict);
10111037

1038+
PyObject *HeapCTypeWithManagedWeakref = PyType_FromSpec(&HeapCTypeWithManagedWeakref_spec);
1039+
if (HeapCTypeWithManagedWeakref == NULL) {
1040+
return -1;
1041+
}
1042+
PyModule_AddObject(m, "HeapCTypeWithManagedWeakref", HeapCTypeWithManagedWeakref);
1043+
10121044
PyObject *HeapCTypeWithWeakref = PyType_FromSpec(&HeapCTypeWithWeakref_spec);
10131045
if (HeapCTypeWithWeakref == NULL) {
10141046
return -1;

Objects/typeobject.c

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2497,10 +2497,11 @@ subtype_getweakref(PyObject *obj, void *context)
24972497
return NULL;
24982498
}
24992499
_PyObject_ASSERT((PyObject *)type,
2500-
type->tp_weaklistoffset > 0);
2500+
type->tp_weaklistoffset > 0 ||
2501+
type->tp_weaklistoffset == MANAGED_WEAKREF_OFFSET);
25012502
_PyObject_ASSERT((PyObject *)type,
2502-
((type->tp_weaklistoffset + sizeof(PyObject *))
2503-
<= (size_t)(type->tp_basicsize)));
2503+
((type->tp_weaklistoffset + (Py_ssize_t)sizeof(PyObject *))
2504+
<= type->tp_basicsize));
25042505
weaklistptr = (PyObject **)((char *)obj + type->tp_weaklistoffset);
25052506
if (*weaklistptr == NULL)
25062507
result = Py_None;
@@ -3093,9 +3094,9 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type)
30933094
}
30943095

30953096
if (ctx->add_weak) {
3096-
assert(!ctx->base->tp_itemsize);
3097-
type->tp_weaklistoffset = slotoffset;
3098-
slotoffset += sizeof(PyObject *);
3097+
assert((type->tp_flags & Py_TPFLAGS_MANAGED_WEAKREF) == 0);
3098+
type->tp_flags |= Py_TPFLAGS_MANAGED_WEAKREF;
3099+
type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET;
30993100
}
31003101
if (ctx->add_dict) {
31013102
assert((type->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0);
@@ -5116,9 +5117,9 @@ compatible_for_assignment(PyTypeObject* oldto, PyTypeObject* newto, const char*
51165117
!same_slots_added(newbase, oldbase))) {
51175118
goto differs;
51185119
}
5119-
/* The above does not check for managed __dicts__ */
5120-
if ((oldto->tp_flags & Py_TPFLAGS_MANAGED_DICT) ==
5121-
((newto->tp_flags & Py_TPFLAGS_MANAGED_DICT)))
5120+
/* The above does not check for the preheader */
5121+
if ((oldto->tp_flags & Py_TPFLAGS_PREHEADER) ==
5122+
((newto->tp_flags & Py_TPFLAGS_PREHEADER)))
51225123
{
51235124
return 1;
51245125
}
@@ -5217,7 +5218,7 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
52175218
if (compatible_for_assignment(oldto, newto, "__class__")) {
52185219
/* Changing the class will change the implicit dict keys,
52195220
* so we must materialize the dictionary first. */
5220-
assert((oldto->tp_flags & Py_TPFLAGS_MANAGED_DICT) == (newto->tp_flags & Py_TPFLAGS_MANAGED_DICT));
5221+
assert((oldto->tp_flags & Py_TPFLAGS_PREHEADER) == (newto->tp_flags & Py_TPFLAGS_PREHEADER));
52215222
_PyObject_GetDictPtr(self);
52225223
if (oldto->tp_flags & Py_TPFLAGS_MANAGED_DICT &&
52235224
_PyDictOrValues_IsValues(*_PyObject_DictOrValuesPointer(self)))
@@ -5360,7 +5361,7 @@ object_getstate_default(PyObject *obj, int required)
53605361
{
53615362
basicsize += sizeof(PyObject *);
53625363
}
5363-
if (Py_TYPE(obj)->tp_weaklistoffset) {
5364+
if (Py_TYPE(obj)->tp_weaklistoffset > 0) {
53645365
basicsize += sizeof(PyObject *);
53655366
}
53665367
if (slotnames != Py_None) {
@@ -6150,7 +6151,7 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
61506151
if (type->tp_clear == NULL)
61516152
type->tp_clear = base->tp_clear;
61526153
}
6153-
type->tp_flags |= (base->tp_flags & Py_TPFLAGS_MANAGED_DICT);
6154+
type->tp_flags |= (base->tp_flags & Py_TPFLAGS_PREHEADER);
61546155

61556156
if (type->tp_basicsize == 0)
61566157
type->tp_basicsize = base->tp_basicsize;
@@ -6571,7 +6572,7 @@ type_ready_fill_dict(PyTypeObject *type)
65716572
}
65726573

65736574
static int
6574-
type_ready_dict_offset(PyTypeObject *type)
6575+
type_ready_preheader(PyTypeObject *type)
65756576
{
65766577
if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
65776578
if (type->tp_dictoffset > 0 || type->tp_dictoffset < -1) {
@@ -6583,6 +6584,18 @@ type_ready_dict_offset(PyTypeObject *type)
65836584
}
65846585
type->tp_dictoffset = -1;
65856586
}
6587+
if (type->tp_flags & Py_TPFLAGS_MANAGED_WEAKREF) {
6588+
if (type->tp_weaklistoffset != 0 &&
6589+
type->tp_weaklistoffset != MANAGED_WEAKREF_OFFSET)
6590+
{
6591+
PyErr_Format(PyExc_TypeError,
6592+
"type %s has the Py_TPFLAGS_MANAGED_WEAKREF flag "
6593+
"but tp_weaklistoffset is set",
6594+
type->tp_name);
6595+
return -1;
6596+
}
6597+
type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET;
6598+
}
65866599
return 0;
65876600
}
65886601

@@ -6802,7 +6815,7 @@ type_ready_post_checks(PyTypeObject *type)
68026815
return -1;
68036816
}
68046817
}
6805-
else if (type->tp_dictoffset < sizeof(PyObject)) {
6818+
else if (type->tp_dictoffset < (Py_ssize_t)sizeof(PyObject)) {
68066819
if (type->tp_dictoffset + type->tp_basicsize <= 0) {
68076820
PyErr_Format(PyExc_SystemError,
68086821
"type %s has a tp_dictoffset that is too small");
@@ -6847,7 +6860,7 @@ type_ready(PyTypeObject *type)
68476860
if (type_ready_inherit(type) < 0) {
68486861
return -1;
68496862
}
6850-
if (type_ready_dict_offset(type) < 0) {
6863+
if (type_ready_preheader(type) < 0) {
68516864
return -1;
68526865
}
68536866
if (type_ready_set_hash(type) < 0) {

0 commit comments

Comments
 (0)