Skip to content

Commit 87223f3

Browse files
jbradaricencukouerlend-aasland
authored
gh-103743: Add PyUnstable_Object_GC_NewWithExtraData (GH-103744)
Co-authored-by: Petr Viktorin <encukou@gmail.com> Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com>
1 parent f6314b9 commit 87223f3

6 files changed

Lines changed: 156 additions & 2 deletions

File tree

‎Doc/c-api/gcsupport.rst‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,31 @@ rules:
5959
Analogous to :c:func:`PyObject_New` but for container objects with the
6060
:const:`Py_TPFLAGS_HAVE_GC` flag set.
6161
62-
6362
.. c:function::TYPE* PyObject_GC_NewVar(TYPE, PyTypeObject *type, Py_ssize_t size)
6463
6564
Analogous to :c:func:`PyObject_NewVar` but for container objects with the
6665
:const:`Py_TPFLAGS_HAVE_GC` flag set.
6766
67+
.. c:function:: PyObject* PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *type, size_t extra_size)
68+
69+
Analogous to :c:func:`PyObject_GC_New` but allocates *extra_size*
70+
bytes at the end of the object (at offset
71+
:c:member:`~PyTypeObject.tp_basicsize`).
72+
The allocated memory is initialized to zeros,
73+
except for the :c:type:`Python object header <PyObject>`.
74+
75+
The extra data will be deallocated with the object, but otherwise it is
76+
not managed by Python.
77+
78+
.. warning::
79+
The function is marked as unstable because the final mechanism
80+
for reserving extra data after an instance is not yet decided.
81+
For allocating a variable number of fields, prefer using
82+
:c:type:`PyVarObject` and :c:member:`~PyTypeObject.tp_itemsize`
83+
instead.
84+
85+
.. versionadded:: 3.12
86+
6887
6988
.. c:function:: TYPE* PyObject_GC_Resize(TYPE, PyVarObject *op, Py_ssize_t newsize)
7089

‎Include/cpython/objimpl.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,6 @@ PyAPI_FUNC(int) PyObject_IS_GC(PyObject *obj);
9090
PyAPI_FUNC(int) PyType_SUPPORTS_WEAKREFS(PyTypeObject*type);
9191

9292
PyAPI_FUNC(PyObject**) PyObject_GET_WEAKREFS_LISTPTR(PyObject*op);
93+
94+
PyAPI_FUNC(PyObject*) PyUnstable_Object_GC_NewWithExtraData(PyTypeObject*,
95+
size_t);

‎Lib/test/test_capi/test_misc.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,20 @@ class dictsub(dict): ... # dict subclasses must work
10431043
self.assertEqual(_testcapi.function_get_kw_defaults(some), None)
10441044
self.assertEqual(some.__kwdefaults__, None)
10451045

1046+
deftest_unstable_gc_new_with_extra_data(self):
1047+
classData(_testcapi.ObjExtraData):
1048+
__slots__= ('x', 'y')
1049+
1050+
d=Data()
1051+
d.x=10
1052+
d.y=20
1053+
d.extra=30
1054+
self.assertEqual(d.x, 10)
1055+
self.assertEqual(d.y, 20)
1056+
self.assertEqual(d.extra, 30)
1057+
deld.extra
1058+
self.assertIsNone(d.extra)
1059+
10461060

10471061
classTestPendingCalls(unittest.TestCase):
10481062

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`PyUnstable_Object_GC_NewWithExtraData` function that can be used to
2+
allocate additional memory after an object for data not managed by Python.

‎Modules/_testcapimodule.c‎

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3363,7 +3363,7 @@ test_gc_visit_objects_basic(PyObject *Py_UNUSED(self),
33633363
}
33643364
state.target=obj;
33653365
state.found=0;
3366-
3366+
33673367
PyUnstable_GC_VisitObjects(gc_visit_callback_basic, &state);
33683368
Py_DECREF(obj);
33693369
if (!state.found) {
@@ -3400,6 +3400,98 @@ test_gc_visit_objects_exit_early(PyObject *Py_UNUSED(self),
34003400
Py_RETURN_NONE;
34013401
}
34023402

3403+
typedefstruct {
3404+
PyObject_HEAD
3405+
} ObjExtraData;
3406+
3407+
staticPyObject*
3408+
obj_extra_data_new(PyTypeObject*type, PyObject*args, PyObject*kwds)
3409+
{
3410+
size_textra_size=sizeof(PyObject*);
3411+
PyObject*obj=PyUnstable_Object_GC_NewWithExtraData(type, extra_size);
3412+
if (obj==NULL) {
3413+
returnPyErr_NoMemory();
3414+
}
3415+
PyObject_GC_Track(obj);
3416+
returnobj;
3417+
}
3418+
3419+
staticPyObject**
3420+
obj_extra_data_get_extra_storage(PyObject*self)
3421+
{
3422+
return (PyObject**)((char*)self+Py_TYPE(self)->tp_basicsize);
3423+
}
3424+
3425+
staticPyObject*
3426+
obj_extra_data_get(PyObject*self, void*Py_UNUSED(ignored))
3427+
{
3428+
PyObject**extra_storage=obj_extra_data_get_extra_storage(self);
3429+
PyObject*value=*extra_storage;
3430+
if (!value) {
3431+
Py_RETURN_NONE;
3432+
}
3433+
returnPy_NewRef(value);
3434+
}
3435+
3436+
staticint
3437+
obj_extra_data_set(PyObject*self, PyObject*newval, void*Py_UNUSED(ignored))
3438+
{
3439+
PyObject**extra_storage=obj_extra_data_get_extra_storage(self);
3440+
Py_CLEAR(*extra_storage);
3441+
if (newval) {
3442+
*extra_storage=Py_NewRef(newval);
3443+
}
3444+
return0;
3445+
}
3446+
3447+
staticPyGetSetDefobj_extra_data_getset[] = {
3448+
{"extra", (getter)obj_extra_data_get, (setter)obj_extra_data_set, NULL},
3449+
{NULL}
3450+
};
3451+
3452+
staticint
3453+
obj_extra_data_traverse(PyObject*self, visitprocvisit, void*arg)
3454+
{
3455+
PyObject**extra_storage=obj_extra_data_get_extra_storage(self);
3456+
PyObject*value=*extra_storage;
3457+
Py_VISIT(value);
3458+
return0;
3459+
}
3460+
3461+
staticint
3462+
obj_extra_data_clear(PyObject*self)
3463+
{
3464+
PyObject**extra_storage=obj_extra_data_get_extra_storage(self);
3465+
Py_CLEAR(*extra_storage);
3466+
return0;
3467+
}
3468+
3469+
staticvoid
3470+
obj_extra_data_dealloc(PyObject*self)
3471+
{
3472+
PyTypeObject*tp=Py_TYPE(self);
3473+
PyObject_GC_UnTrack(self);
3474+
obj_extra_data_clear(self);
3475+
tp->tp_free(self);
3476+
Py_DECREF(tp);
3477+
}
3478+
3479+
staticPyType_SlotObjExtraData_Slots[] = {
3480+
{Py_tp_getset, obj_extra_data_getset},
3481+
{Py_tp_dealloc, obj_extra_data_dealloc},
3482+
{Py_tp_traverse, obj_extra_data_traverse},
3483+
{Py_tp_clear, obj_extra_data_clear},
3484+
{Py_tp_new, obj_extra_data_new},
3485+
{Py_tp_free, PyObject_GC_Del},
3486+
{0, NULL},
3487+
};
3488+
3489+
staticPyType_SpecObjExtraData_TypeSpec= {
3490+
.name="_testcapi.ObjExtraData",
3491+
.basicsize=sizeof(ObjExtraData),
3492+
.flags=Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
3493+
.slots=ObjExtraData_Slots,
3494+
};
34033495

34043496
structatexit_data {
34053497
intcalled;
@@ -4124,6 +4216,17 @@ PyInit__testcapi(void)
41244216
Py_INCREF(&MethStatic_Type);
41254217
PyModule_AddObject(m, "MethStatic", (PyObject*)&MethStatic_Type);
41264218

4219+
PyObject*ObjExtraData_Type=PyType_FromModuleAndSpec(
4220+
m, &ObjExtraData_TypeSpec, NULL);
4221+
if (ObjExtraData_Type==0) {
4222+
returnNULL;
4223+
}
4224+
intret=PyModule_AddType(m, (PyTypeObject*)ObjExtraData_Type);
4225+
Py_DECREF(&ObjExtraData_Type);
4226+
if (ret<0) {
4227+
returnNULL;
4228+
}
4229+
41274230
PyModule_AddObject(m, "CHAR_MAX", PyLong_FromLong(CHAR_MAX));
41284231
PyModule_AddObject(m, "CHAR_MIN", PyLong_FromLong(CHAR_MIN));
41294232
PyModule_AddObject(m, "UCHAR_MAX", PyLong_FromLong(UCHAR_MAX));

‎Modules/gcmodule.c‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,6 +2367,19 @@ _PyObject_GC_NewVar(PyTypeObject *tp, Py_ssize_t nitems)
23672367
returnop;
23682368
}
23692369

2370+
PyObject*
2371+
PyUnstable_Object_GC_NewWithExtraData(PyTypeObject*tp, size_textra_size)
2372+
{
2373+
size_tpresize=_PyType_PreHeaderSize(tp);
2374+
PyObject*op=gc_alloc(_PyObject_SIZE(tp) +extra_size, presize);
2375+
if (op==NULL) {
2376+
returnNULL;
2377+
}
2378+
memset(op, 0, _PyObject_SIZE(tp) +extra_size);
2379+
_PyObject_Init(op, tp);
2380+
returnop;
2381+
}
2382+
23702383
PyVarObject*
23712384
_PyObject_GC_Resize(PyVarObject*op, Py_ssize_tnitems)
23722385
{

0 commit comments

Comments
 (0)