Skip to content

Commit

Permalink
Merge supplement into type object
Browse files Browse the repository at this point in the history
This commit updates the ``nb_type`` metaclass to create type objects
that directly include the supplement (if present). This involves a
dictionary of metaclasses (``nb_type_X``, where ``X`` is the size in
bytes) that are created on demand.

The commit also simplifies ``nb_type_new()`` by moving much of its
contents into a new function ``nb_type_from_metaclass()``, which simply
calls ``PyType_FromMetaclass()`` when it is available. Otherwise, it
emulates the behavior of ``PyType_FromMetaclass()``. This part has also
been simplified and no longer involves the creation of an intermediate
type.
  • Loading branch information
wjakob committed Apr 30, 2023
1 parent de018db commit d82ca9c
Show file tree
Hide file tree
Showing 6 changed files with 342 additions and 268 deletions.
9 changes: 5 additions & 4 deletions include/nanobind/nb_class.h
Expand Up @@ -101,7 +101,6 @@ struct type_data {
void (*move)(void *, void *) noexcept;
const std::type_info **implicit;
bool (**implicit_py)(PyTypeObject *, PyObject *, cleanup_list *) noexcept;
void *supplement;
void (*set_self_py)(void *, PyObject *) noexcept;
#if defined(Py_LIMITED_API)
size_t dictoffset;
Expand All @@ -114,6 +113,7 @@ struct type_init_data : type_data {
PyTypeObject *base_py;
const char *doc;
const PyType_Slot *type_slots;
size_t supplement;
};

NB_INLINE void type_extra_apply(type_init_data &t, const handle &h) {
Expand Down Expand Up @@ -148,9 +148,11 @@ NB_INLINE void type_extra_apply(type_init_data &t, dynamic_attr) {
template <typename T>
NB_INLINE void type_extra_apply(type_init_data &t, supplement<T>) {
static_assert(std::is_trivially_default_constructible_v<T>,
"The supplement type must be a POD (plain old data) type");
"The supplement must be a POD (plain old data) type");
static_assert(alignof(T) <= alignof(void *),
"The alignment requirement of the supplement is too high.");
t.flags |= (uint32_t) type_flags::has_supplement | (uint32_t) type_flags::is_final;
t.supplement = (void *) malloc(sizeof(T));
t.supplement = sizeof(T);
}

// Enum-specific annotations:
Expand Down Expand Up @@ -306,7 +308,6 @@ class class_ : public object {
d.flags = (uint32_t) detail::type_flags::has_scope;
d.align = (uint8_t) alignof(Alias);
d.size = (uint32_t) sizeof(Alias);
d.supplement = nullptr;
d.name = name;
d.scope = scope.ptr();
d.type = &typeid(T);
Expand Down
67 changes: 33 additions & 34 deletions src/nb_func.cpp
Expand Up @@ -415,8 +415,6 @@ static NB_NOINLINE void nb_func_convert_cpp_exception() noexcept {
"could not be translated!");
}

static PyTypeObject *nb_type_cache = nullptr;

/// Dispatch loop that is used to invoke functions created by nb_func_new
static PyObject *nb_func_vectorcall_complex(PyObject *self,
PyObject *const *args_in,
Expand All @@ -438,22 +436,23 @@ static PyObject *nb_func_vectorcall_complex(PyObject *self,
if (is_method) {
self_arg = nargs_in > 0 ? args_in[0] : nullptr;

if (!nb_type_cache)
nb_type_cache = internals_get().nb_type;

if (self_arg && Py_TYPE((PyObject *) Py_TYPE(self_arg)) == nb_type_cache) {
self_flags = nb_type_data(Py_TYPE(self_arg))->flags;
if (self_flags & (uint32_t) type_flags::is_trampoline)
current_method_data = current_method{ fr->name, self_arg };

is_constructor = fr->flags & (uint32_t) func_flags::is_constructor;
if (is_constructor) {
if (((nb_inst *) self_arg)->ready) {
PyErr_SetString(
PyExc_RuntimeError,
"nanobind::detail::nb_func_vectorcall(): the __init__ "
"method should not be called on an initialized object!");
return nullptr;
if (NB_LIKELY(self_arg)) {
PyTypeObject *self_tp = Py_TYPE(self_arg);

if (NB_LIKELY(nb_type_check((PyObject *) self_tp))) {
self_flags = nb_type_data(self_tp)->flags;
if (self_flags & (uint32_t) type_flags::is_trampoline)
current_method_data = current_method{ fr->name, self_arg };

is_constructor = fr->flags & (uint32_t) func_flags::is_constructor;
if (is_constructor) {
if (((nb_inst *) self_arg)->ready) {
PyErr_SetString(
PyExc_RuntimeError,
"nanobind::detail::nb_func_vectorcall(): the __init__ "
"method should not be called on an initialized object!");
return nullptr;
}
}
}
}
Expand Down Expand Up @@ -696,22 +695,22 @@ static PyObject *nb_func_vectorcall_simple(PyObject *self,
if (is_method) {
self_arg = nargs_in > 0 ? args_in[0] : nullptr;

if (NB_UNLIKELY(!nb_type_cache))
nb_type_cache = internals_get().nb_type;

if (NB_LIKELY(self_arg && Py_TYPE((PyObject *) Py_TYPE(self_arg)) == nb_type_cache)) {
self_flags = nb_type_data(Py_TYPE(self_arg))->flags;
if (NB_UNLIKELY(self_flags & (uint32_t) type_flags::is_trampoline))
current_method_data = current_method{ fr->name, self_arg };

is_constructor = fr->flags & (uint32_t) func_flags::is_constructor;
if (is_constructor) {
if (NB_UNLIKELY(((nb_inst *) self_arg)->ready)) {
PyErr_SetString(PyExc_RuntimeError,
"nanobind::detail::nb_func_vectorcall_simple():"
" the __init__ method should not be called on "
"an initialized object!");
return nullptr;
if (NB_LIKELY(self_arg)) {
PyTypeObject *self_tp = Py_TYPE(self_arg);
if (NB_LIKELY(nb_type_check((PyObject *) self_tp))) {
self_flags = nb_type_data(self_tp)->flags;
if (NB_UNLIKELY(self_flags & (uint32_t) type_flags::is_trampoline))
current_method_data = current_method{ fr->name, self_arg };

is_constructor = fr->flags & (uint32_t) func_flags::is_constructor;
if (is_constructor) {
if (NB_UNLIKELY(((nb_inst *) self_arg)->ready)) {
PyErr_SetString(PyExc_RuntimeError,
"nanobind::detail::nb_func_vectorcall_simple():"
" the __init__ method should not be called on "
"an initialized object!");
return nullptr;
}
}
}
}
Expand Down
42 changes: 30 additions & 12 deletions src/nb_internals.cpp
Expand Up @@ -91,6 +91,19 @@ extern PyObject *nb_method_descr_get(PyObject *, PyObject *, PyObject *);
# define NB_HAVE_VECTORCALL_PY39_OR_NEWER 0
#endif

static PyType_Slot nb_meta_slots[] = {
{ Py_tp_base, nullptr },
{ 0, nullptr }
};

static PyType_Spec nb_meta_spec = {
/* .name = */ "nanobind.nb_meta",
/* .basicsize = */ 0,
/* .itemsize = */ 0,
/* .flags = */ Py_TPFLAGS_DEFAULT,
/* .slots = */ nb_meta_slots
};

static PyMemberDef nb_func_members[] = {
{ "__vectorcalloffset__", T_PYSSIZET,
(Py_ssize_t) offsetof(nb_func, vectorcall), READONLY, nullptr },
Expand Down Expand Up @@ -138,13 +151,13 @@ static PyType_Slot nb_method_slots[] = {
};

static PyType_Spec nb_method_spec = {
/*.name = */"nanobind.nb_method",
/*.basicsize = */(int) sizeof(nb_func),
/*.itemsize = */(int) sizeof(func_data),
/*.flags = */Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC
| Py_TPFLAGS_METHOD_DESCRIPTOR
| NB_HAVE_VECTORCALL_PY39_OR_NEWER,
/*.slots = */nb_method_slots
/*.name = */ "nanobind.nb_method",
/*.basicsize = */ (int) sizeof(nb_func),
/*.itemsize = */ (int) sizeof(func_data),
/*.flags = */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_METHOD_DESCRIPTOR |
NB_HAVE_VECTORCALL_PY39_OR_NEWER,
/*.slots = */ nb_method_slots
};

static PyMemberDef nb_bound_method_members[] = {
Expand Down Expand Up @@ -296,19 +309,24 @@ static NB_NOINLINE nb_internals *internals_make() {

const char *internals_id = NB_INTERNALS_ID;
PyObject *capsule = PyCapsule_New(p, internals_id, nullptr);
p->nb_module = PyModule_NewObject(nb_name.ptr());
int rv = PyDict_SetItemString(dict, internals_id, capsule);
if (rv || !capsule || !p->nb_module)
if (rv || !capsule)
fail("nanobind::detail::internals_make(): allocation failed!");
Py_DECREF(capsule);

// Function objects
nb_meta_slots[0].pfunc = (PyObject *) &PyType_Type;

p->nb_module = PyModule_NewObject(nb_name.ptr());
p->nb_meta = (PyTypeObject *) PyType_FromSpec(&nb_meta_spec);
p->nb_type_dict = PyDict_New();
p->nb_func = (PyTypeObject *) PyType_FromSpec(&nb_func_spec);
p->nb_method = (PyTypeObject *) PyType_FromSpec(&nb_method_spec);
p->nb_bound_method = (PyTypeObject *) PyType_FromSpec(&nb_bound_method_spec);

if (!p->nb_func || !p->nb_method || !p->nb_bound_method)
fail("nanobind::detail::internals_make(): type initialization failed!");
PyErr_Print();
if (!p->nb_module || !p->nb_meta || !p->nb_type_dict || !p->nb_func ||
!p->nb_method || !p->nb_bound_method)
fail("nanobind::detail::internals_make(): initialization failed!");

#if PY_VERSION_HEX < 0x03090000
p->nb_func->tp_flags |= NB_HAVE_VECTORCALL;
Expand Down
7 changes: 5 additions & 2 deletions src/nb_internals.h
Expand Up @@ -164,8 +164,11 @@ struct nb_internals {
/// Internal nanobind module
PyObject *nb_module;

/// Metaclass of nanobind classes (created on demand)
PyTypeObject *nb_type = nullptr;
/// Meta-metaclass of nanobind instances
PyTypeObject *nb_meta;

/// Dictionary with nanobind metaclass(es) for different payload sizes
PyObject *nb_type_dict;

/// Types of nanobind functions and methods
PyTypeObject *nb_func, *nb_method, *nb_bound_method;
Expand Down
5 changes: 3 additions & 2 deletions src/nb_static_property.cpp
Expand Up @@ -49,7 +49,9 @@ PyTypeObject *nb_static_property_tp() noexcept {

PyType_Slot slots[] = {
{ Py_tp_base, &PyProperty_Type },
#if PY_VERSION_HEX < 0x030C0000
{ Py_tp_members, nullptr },
#endif
{ Py_tp_descr_get, (void *) nb_static_property_descr_get },
{ Py_tp_descr_set, (void *) nb_static_property_descr_set },
{ 0, nullptr }
Expand All @@ -60,11 +62,10 @@ PyTypeObject *nb_static_property_tp() noexcept {
#else
// See https://github.com/python/cpython/issues/98963
PyMemberDef members[] = {
{ "__doc__", T_OBJECT, 0, 0, nullptr },
{ "__doc__", T_OBJECT, basicsize, 0, nullptr },
{ nullptr, 0, 0, 0, nullptr }
};

members[0].offset = basicsize;
slots[1].pfunc = members;
basicsize += sizeof(PyObject *);
#endif
Expand Down

0 comments on commit d82ca9c

Please sign in to comment.