Skip to content

Commit 87223f3

Browse files
jbradaricencukouerlend-aasland
authored
gh-103743: Add PyUnstable_Object_GC_NewWithExtraData (GH-103744)
Co-authored-by: Petr Viktorin <[email protected]> Co-authored-by: Erlend E. Aasland <[email protected]>
1 parent f6314b9 commit 87223f3

File tree

6 files changed

+156
-2
lines changed

6 files changed

+156
-2
lines changed

Doc/c-api/gcsupport.rst

+20-1
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

+3
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

+14
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+
def test_unstable_gc_new_with_extra_data(self):
1047+
class Data(_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+
del d.extra
1058+
self.assertIsNone(d.extra)
1059+
10461060

10471061
class TestPendingCalls(unittest.TestCase):
10481062

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

+104-1
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+
typedef struct {
3404+
PyObject_HEAD
3405+
} ObjExtraData;
3406+
3407+
static PyObject *
3408+
obj_extra_data_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
3409+
{
3410+
size_t extra_size = sizeof(PyObject *);
3411+
PyObject *obj = PyUnstable_Object_GC_NewWithExtraData(type, extra_size);
3412+
if (obj == NULL) {
3413+
return PyErr_NoMemory();
3414+
}
3415+
PyObject_GC_Track(obj);
3416+
return obj;
3417+
}
3418+
3419+
static PyObject **
3420+
obj_extra_data_get_extra_storage(PyObject *self)
3421+
{
3422+
return (PyObject **)((char *)self + Py_TYPE(self)->tp_basicsize);
3423+
}
3424+
3425+
static PyObject *
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+
return Py_NewRef(value);
3434+
}
3435+
3436+
static int
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+
return 0;
3445+
}
3446+
3447+
static PyGetSetDef obj_extra_data_getset[] = {
3448+
{"extra", (getter)obj_extra_data_get, (setter)obj_extra_data_set, NULL},
3449+
{NULL}
3450+
};
3451+
3452+
static int
3453+
obj_extra_data_traverse(PyObject *self, visitproc visit, void *arg)
3454+
{
3455+
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
3456+
PyObject *value = *extra_storage;
3457+
Py_VISIT(value);
3458+
return 0;
3459+
}
3460+
3461+
static int
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+
return 0;
3467+
}
3468+
3469+
static void
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+
static PyType_Slot ObjExtraData_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+
static PyType_Spec ObjExtraData_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
struct atexit_data {
34053497
int called;
@@ -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+
return NULL;
4223+
}
4224+
int ret = PyModule_AddType(m, (PyTypeObject*)ObjExtraData_Type);
4225+
Py_DECREF(&ObjExtraData_Type);
4226+
if (ret < 0) {
4227+
return NULL;
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

+13
Original file line numberDiff line numberDiff line change
@@ -2367,6 +2367,19 @@ _PyObject_GC_NewVar(PyTypeObject *tp, Py_ssize_t nitems)
23672367
return op;
23682368
}
23692369

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

0 commit comments

Comments
 (0)