Skip to content

Commit

Permalink
improved internals and type construction
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wjakob committed Nov 2, 2022
1 parent 12c90c0 commit e551a7e
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 43 deletions.
60 changes: 36 additions & 24 deletions src/nb_internals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,20 +202,23 @@ static PyType_Spec nb_type_spec = {

static PyType_Slot nb_enum_slots[] = {
{ Py_tp_base, nullptr },
{ Py_tp_traverse, nullptr },
{ Py_tp_clear, nullptr },
{ 0, nullptr }
};

static PyType_Spec nb_enum_spec = {
.name = "nanobind.nb_enum",
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
.flags = Py_TPFLAGS_DEFAULT,
.slots = nb_enum_slots
};

static PyMemberDef nb_static_property_members[] = {
{ "__doc__", T_OBJECT, 0, 0, nullptr },
{ nullptr, 0, 0, 0, nullptr }
};

static PyType_Slot nb_static_property_slots[] = {
{ Py_tp_base, nullptr },
{ Py_tp_members, nullptr },
{ Py_tp_members, (void *) nb_static_property_members },
{ Py_tp_descr_get, (void *) nb_static_property_get },
{ 0, nullptr }
};
Expand Down Expand Up @@ -384,43 +387,52 @@ static void internals_make() {
if (rv || !capsule || !nb_module)
fail("nanobind::detail::internals_make(): allocation failed!");
Py_DECREF(capsule);
internals_p->nb_module = nb_module;

internals_p->type_basicsize =
cast<int>(handle(&PyType_Type).attr("__basicsize__"));

nb_type_spec.basicsize = nb_enum_spec.basicsize =
internals_p->type_basicsize + (int) sizeof(type_data);
nb_type_spec.itemsize = nb_enum_spec.itemsize =
cast<int>(handle(&PyType_Type).attr("__itemsize__"));
internals_p->nb_module = nb_module;

// Function objects
internals_p->nb_func = (PyTypeObject *) PyType_FromSpec(&nb_func_spec);
internals_p->nb_method = (PyTypeObject *) PyType_FromSpec(&nb_method_spec);
internals_p->nb_bound_method =
(PyTypeObject *) PyType_FromSpec(&nb_bound_method_spec);

nb_type_slots[0].pfunc = &PyType_Type;
internals_p->nb_type = (PyTypeObject *) PyType_FromSpec(&nb_type_spec);

nb_enum_slots[0].pfunc = internals_p->nb_type;
nb_static_property_slots[0].pfunc = &PyProperty_Type;

// Metaclass #1 (nb_type)
#if defined(Py_LIMITED_API)
nb_enum_slots[1].pfunc = PyType_GetSlot(&PyType_Type, Py_tp_traverse);
nb_enum_slots[2].pfunc = PyType_GetSlot(&PyType_Type, Py_tp_clear);
nb_static_property_slots[1].pfunc = PyType_GetSlot(&PyProperty_Type, Py_tp_members);
int tp_itemsize = cast<int>(handle(&PyType_Type).attr("__itemsize__"));
int tp_basicsize = cast<int>(handle(&PyType_Type).attr("__basicsize__"));
#else
nb_enum_slots[1].pfunc = (void *) PyType_Type.tp_traverse;
nb_enum_slots[2].pfunc = (void *) PyType_Type.tp_clear;
nb_static_property_slots[1].pfunc = PyProperty_Type.tp_members;
int tp_itemsize = (int) PyType_Type.tp_itemsize;
int tp_basicsize = (int) PyType_Type.tp_basicsize;
#endif
nb_type_spec.basicsize = nb_enum_spec.basicsize = tp_basicsize
+ (int) sizeof(type_data);
nb_type_spec.itemsize = nb_enum_spec.itemsize = tp_itemsize;
nb_type_slots[0].pfunc = &PyType_Type;
internals_p->nb_type = (PyTypeObject *) PyType_FromSpec(&nb_type_spec);

// Metaclass #2 (nb_enum)
nb_enum_slots[0].pfunc = internals_p->nb_type;
internals_p->nb_enum = (PyTypeObject *) PyType_FromSpec(&nb_enum_spec);

/// Static properties
#if defined(Py_LIMITED_API)
tp_itemsize = cast<int>(handle(&PyProperty_Type).attr("__itemsize__"));
tp_basicsize = cast<int>(handle(&PyProperty_Type).attr("__basicsize__"));
#else
tp_itemsize = (int) PyProperty_Type.tp_itemsize;
tp_basicsize = (int) PyProperty_Type.tp_basicsize;
#endif
nb_static_property_spec.basicsize = tp_basicsize + sizeof(PyObject *);
nb_static_property_spec.itemsize = tp_itemsize;
nb_static_property_members[0].offset = tp_basicsize;
nb_static_property_slots[0].pfunc = &PyProperty_Type;
internals_p->nb_static_property =
(PyTypeObject *) PyType_FromSpec(&nb_static_property_spec);
internals_p->nb_static_property_enabled = true;

// Tensor type
internals_p->nb_tensor = (PyTypeObject *) PyType_FromSpec(&nb_tensor_spec);

if (!internals_p->nb_func || !internals_p->nb_method ||
!internals_p->nb_bound_method || !internals_p->nb_type ||
!internals_p->nb_enum || !internals_p->nb_static_property ||
Expand Down
3 changes: 0 additions & 3 deletions src/nb_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,6 @@ struct nb_internals {
/// Tensor wrpaper
PyTypeObject *nb_tensor;

/// Size fields of PyTypeObject
int type_basicsize, type_itemsize;

/// Instance pointer -> Python object mapping
py_map<std::pair<void *, std::type_index>, nb_inst *, ptr_type_hash>
inst_c2p;
Expand Down
27 changes: 11 additions & 16 deletions src/nb_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,22 +413,10 @@ PyObject *nb_type_new(const type_data *t) noexcept {
if (is_enum)
nb_enum_prepare(&s, is_arithmetic);

bool has_traverse = false;
for (PyType_Slot *ts = slots; ts != s; ++ts) {
if (ts->slot == Py_tp_traverse)
spec.flags |= Py_TPFLAGS_HAVE_GC;
}

/// Inherit GC-related slots from parent class if none were specified
if (base && (spec.flags & Py_TPFLAGS_HAVE_GC) == 0) {
void *tp_traverse = PyType_GetSlot((PyTypeObject *) base, Py_tp_traverse),
*tp_clear = PyType_GetSlot((PyTypeObject *) base, Py_tp_clear);

if (tp_traverse) {
*s++ = { Py_tp_traverse, tp_traverse };
if (tp_clear)
*s++ = { Py_tp_clear, tp_clear };
spec.flags |= Py_TPFLAGS_HAVE_GC;
}
has_traverse = true;
}

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

// Install GC traverse and clear routines if not inherited/overridden
if ((spec.flags & Py_TPFLAGS_HAVE_GC) == 0) {
if (!has_traverse) {
*s++ = { Py_tp_traverse, (void *) inst_traverse };
*s++ = { Py_tp_clear, (void *) inst_clear };
spec.flags |= Py_TPFLAGS_HAVE_GC;
has_traverse = true;
}

spec.basicsize = (int) basicsize;
}

if (has_traverse && (!base || (PyType_GetFlags((PyTypeObject *) base) &
Py_TPFLAGS_HAVE_GC) == 0)) {
spec.flags |= Py_TPFLAGS_HAVE_GC;
}

*s++ = { 0, nullptr };

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

#if PY_VERSION_HEX < 0x03090000
if (has_dynamic_attr)
Expand Down

0 comments on commit e551a7e

Please sign in to comment.