Skip to content

Commit e551a7e

Browse files
committed
improved internals and type construction
This commit ports a few improvements from the 'limited_api' branch of nanobind. In particular - 'nb_static_property' is constructed in a different way by exposing a writeable '__doc__' member field. This is to work around an issue in CPython (related discussion can be found here: python/cpython#98963) - Instead of copy-pasting GC ``tp_clear`` and ``tp_traverse`` slots from parent to child classes, we rely on ``PyType_Ready`` doing this automatically.
1 parent 12c90c0 commit e551a7e

3 files changed

Lines changed: 47 additions & 43 deletions

File tree

‎src/nb_internals.cpp‎

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -202,20 +202,23 @@ static PyType_Spec nb_type_spec = {
202202

203203
static PyType_Slot nb_enum_slots[] = {
204204
{ Py_tp_base, nullptr },
205-
{ Py_tp_traverse, nullptr },
206-
{ Py_tp_clear, nullptr },
207205
{ 0, nullptr }
208206
};
209207

210208
static PyType_Spec nb_enum_spec = {
211209
.name = "nanobind.nb_enum",
212-
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
210+
.flags = Py_TPFLAGS_DEFAULT,
213211
.slots = nb_enum_slots
214212
};
215213

214+
static PyMemberDef nb_static_property_members[] = {
215+
{ "__doc__", T_OBJECT, 0, 0, nullptr },
216+
{ nullptr, 0, 0, 0, nullptr }
217+
};
218+
216219
static PyType_Slot nb_static_property_slots[] = {
217220
{ Py_tp_base, nullptr },
218-
{ Py_tp_members, nullptr },
221+
{ Py_tp_members, (void *) nb_static_property_members },
219222
{ Py_tp_descr_get, (void *) nb_static_property_get },
220223
{ 0, nullptr }
221224
};
@@ -384,43 +387,52 @@ static void internals_make() {
384387
if (rv || !capsule || !nb_module)
385388
fail("nanobind::detail::internals_make(): allocation failed!");
386389
Py_DECREF(capsule);
387-
internals_p->nb_module = nb_module;
388390

389-
internals_p->type_basicsize =
390-
cast<int>(handle(&PyType_Type).attr("__basicsize__"));
391-
392-
nb_type_spec.basicsize = nb_enum_spec.basicsize =
393-
internals_p->type_basicsize + (int) sizeof(type_data);
394-
nb_type_spec.itemsize = nb_enum_spec.itemsize =
395-
cast<int>(handle(&PyType_Type).attr("__itemsize__"));
391+
internals_p->nb_module = nb_module;
396392

393+
// Function objects
397394
internals_p->nb_func = (PyTypeObject *) PyType_FromSpec(&nb_func_spec);
398395
internals_p->nb_method = (PyTypeObject *) PyType_FromSpec(&nb_method_spec);
399396
internals_p->nb_bound_method =
400397
(PyTypeObject *) PyType_FromSpec(&nb_bound_method_spec);
401398

402-
nb_type_slots[0].pfunc = &PyType_Type;
403-
internals_p->nb_type = (PyTypeObject *) PyType_FromSpec(&nb_type_spec);
404-
405-
nb_enum_slots[0].pfunc = internals_p->nb_type;
406-
nb_static_property_slots[0].pfunc = &PyProperty_Type;
407-
399+
// Metaclass #1 (nb_type)
408400
#if defined(Py_LIMITED_API)
409-
nb_enum_slots[1].pfunc = PyType_GetSlot(&PyType_Type, Py_tp_traverse);
410-
nb_enum_slots[2].pfunc = PyType_GetSlot(&PyType_Type, Py_tp_clear);
411-
nb_static_property_slots[1].pfunc = PyType_GetSlot(&PyProperty_Type, Py_tp_members);
401+
int tp_itemsize = cast<int>(handle(&PyType_Type).attr("__itemsize__"));
402+
int tp_basicsize = cast<int>(handle(&PyType_Type).attr("__basicsize__"));
412403
#else
413-
nb_enum_slots[1].pfunc = (void *) PyType_Type.tp_traverse;
414-
nb_enum_slots[2].pfunc = (void *) PyType_Type.tp_clear;
415-
nb_static_property_slots[1].pfunc = PyProperty_Type.tp_members;
404+
int tp_itemsize = (int) PyType_Type.tp_itemsize;
405+
int tp_basicsize = (int) PyType_Type.tp_basicsize;
416406
#endif
407+
nb_type_spec.basicsize = nb_enum_spec.basicsize = tp_basicsize
408+
+ (int) sizeof(type_data);
409+
nb_type_spec.itemsize = nb_enum_spec.itemsize = tp_itemsize;
410+
nb_type_slots[0].pfunc = &PyType_Type;
411+
internals_p->nb_type = (PyTypeObject *) PyType_FromSpec(&nb_type_spec);
417412

413+
// Metaclass #2 (nb_enum)
414+
nb_enum_slots[0].pfunc = internals_p->nb_type;
418415
internals_p->nb_enum = (PyTypeObject *) PyType_FromSpec(&nb_enum_spec);
416+
417+
/// Static properties
418+
#if defined(Py_LIMITED_API)
419+
tp_itemsize = cast<int>(handle(&PyProperty_Type).attr("__itemsize__"));
420+
tp_basicsize = cast<int>(handle(&PyProperty_Type).attr("__basicsize__"));
421+
#else
422+
tp_itemsize = (int) PyProperty_Type.tp_itemsize;
423+
tp_basicsize = (int) PyProperty_Type.tp_basicsize;
424+
#endif
425+
nb_static_property_spec.basicsize = tp_basicsize + sizeof(PyObject *);
426+
nb_static_property_spec.itemsize = tp_itemsize;
427+
nb_static_property_members[0].offset = tp_basicsize;
428+
nb_static_property_slots[0].pfunc = &PyProperty_Type;
419429
internals_p->nb_static_property =
420430
(PyTypeObject *) PyType_FromSpec(&nb_static_property_spec);
421431
internals_p->nb_static_property_enabled = true;
422432

433+
// Tensor type
423434
internals_p->nb_tensor = (PyTypeObject *) PyType_FromSpec(&nb_tensor_spec);
435+
424436
if (!internals_p->nb_func || !internals_p->nb_method ||
425437
!internals_p->nb_bound_method || !internals_p->nb_type ||
426438
!internals_p->nb_enum || !internals_p->nb_static_property ||

‎src/nb_internals.h‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,6 @@ struct nb_internals {
186186
/// Tensor wrpaper
187187
PyTypeObject *nb_tensor;
188188

189-
/// Size fields of PyTypeObject
190-
int type_basicsize, type_itemsize;
191-
192189
/// Instance pointer -> Python object mapping
193190
py_map<std::pair<void *, std::type_index>, nb_inst *, ptr_type_hash>
194191
inst_c2p;

‎src/nb_type.cpp‎

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -413,22 +413,10 @@ PyObject *nb_type_new(const type_data *t) noexcept {
413413
if (is_enum)
414414
nb_enum_prepare(&s, is_arithmetic);
415415

416+
bool has_traverse = false;
416417
for (PyType_Slot *ts = slots; ts != s; ++ts) {
417418
if (ts->slot == Py_tp_traverse)
418-
spec.flags |= Py_TPFLAGS_HAVE_GC;
419-
}
420-
421-
/// Inherit GC-related slots from parent class if none were specified
422-
if (base && (spec.flags & Py_TPFLAGS_HAVE_GC) == 0) {
423-
void *tp_traverse = PyType_GetSlot((PyTypeObject *) base, Py_tp_traverse),
424-
*tp_clear = PyType_GetSlot((PyTypeObject *) base, Py_tp_clear);
425-
426-
if (tp_traverse) {
427-
*s++ = { Py_tp_traverse, tp_traverse };
428-
if (tp_clear)
429-
*s++ = { Py_tp_clear, tp_clear };
430-
spec.flags |= Py_TPFLAGS_HAVE_GC;
431-
}
419+
has_traverse = true;
432420
}
433421

434422
if (has_dynamic_attr) {
@@ -442,15 +430,20 @@ PyObject *nb_type_new(const type_data *t) noexcept {
442430
*s++ = { Py_tp_members, (void *) members };
443431

444432
// Install GC traverse and clear routines if not inherited/overridden
445-
if ((spec.flags & Py_TPFLAGS_HAVE_GC) == 0) {
433+
if (!has_traverse) {
446434
*s++ = { Py_tp_traverse, (void *) inst_traverse };
447435
*s++ = { Py_tp_clear, (void *) inst_clear };
448-
spec.flags |= Py_TPFLAGS_HAVE_GC;
436+
has_traverse = true;
449437
}
450438

451439
spec.basicsize = (int) basicsize;
452440
}
453441

442+
if (has_traverse && (!base || (PyType_GetFlags((PyTypeObject *) base) &
443+
Py_TPFLAGS_HAVE_GC) == 0)) {
444+
spec.flags |= Py_TPFLAGS_HAVE_GC;
445+
}
446+
454447
*s++ = { 0, nullptr };
455448

456449
PyTypeObject *metaclass = is_enum ? internals.nb_enum
@@ -507,6 +500,8 @@ PyObject *nb_type_new(const type_data *t) noexcept {
507500
tp->tp_name = name_copy;
508501
tp->tp_doc = tp_doc;
509502
tp->tp_flags = spec.flags | Py_TPFLAGS_HEAPTYPE;
503+
if (temp_tp->tp_flags & Py_TPFLAGS_HAVE_GC)
504+
tp->tp_flags |= Py_TPFLAGS_HAVE_GC;
510505

511506
#if PY_VERSION_HEX < 0x03090000
512507
if (has_dynamic_attr)

0 commit comments

Comments
 (0)