Skip to content

Commit

Permalink
Resolve and cache type-specific functions when targeting the limited API
Browse files Browse the repository at this point in the history
When targeting the limited API, nanobind cannot directly call
type-specific routines like ``tp_init``, ``tp_dealloc``, etc., since the
underlying data structures are opaque. A lookup function must be called
first to obtain their address.

nanobind previously cached their return value using inline static
variables, but this led to suboptimal code generation (including several
variables protected by a mutex). This commit changes the approach so
that the addresses are resolved once when nanobind is first initialized.
  • Loading branch information
wjakob committed Apr 30, 2023
1 parent 763962b commit f0f42a5
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 53 deletions.
14 changes: 14 additions & 0 deletions src/nb_internals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,20 @@ static NB_NOINLINE nb_internals *internals_make() {
p->nb_bound_method->tp_vectorcall_offset = offsetof(nb_bound_method, vectorcall);
#endif

#if defined(Py_LIMITED_API)
// Cache important functions from PyType_Type and PyProperty_Type
p->PyType_Type_tp_free = (freefunc) PyType_GetSlot(&PyType_Type, Py_tp_free);
p->PyType_Type_tp_init = (initproc) PyType_GetSlot(&PyType_Type, Py_tp_init);
p->PyType_Type_tp_dealloc =
(destructor) PyType_GetSlot(&PyType_Type, Py_tp_dealloc);
p->PyType_Type_tp_setattro =
(setattrofunc) PyType_GetSlot(&PyType_Type, Py_tp_setattro);
p->PyProperty_Type_tp_descr_get =
(descrgetfunc) PyType_GetSlot(&PyProperty_Type, Py_tp_descr_get);
p->PyProperty_Type_tp_descr_set =
(descrsetfunc) PyType_GetSlot(&PyProperty_Type, Py_tp_descr_set);
#endif

p->translators = { default_exception_translator, nullptr, nullptr };

#if PY_VERSION_HEX < 0x030C0000 && !defined(PYPY_VERSION)
Expand Down
17 changes: 17 additions & 0 deletions src/nb_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,25 @@ struct nb_internals {

/// Should nanobind print warnings after implicit cast failures?
bool print_implicit_cast_warnings = true;

#if defined(Py_LIMITED_API)
// Cache for important functions from PyType_Type and PyProperty_Type
freefunc PyType_Type_tp_free;
initproc PyType_Type_tp_init;
destructor PyType_Type_tp_dealloc;
setattrofunc PyType_Type_tp_setattro;
descrgetfunc PyProperty_Type_tp_descr_get;
descrsetfunc PyProperty_Type_tp_descr_set;
#endif
};

/// Convenience macro to potentially access cached functions
#if defined(Py_LIMITED_API)
# define NB_SLOT(internals, type, name) internals.type##_##name
#else
# define NB_SLOT(internals, type, name) type.name
#endif

struct current_method {
const char *name;
PyObject *self;
Expand Down
22 changes: 4 additions & 18 deletions src/nb_static_property.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@ NAMESPACE_BEGIN(detail)

/// `nb_static_property.__get__()`: Always pass the class instead of the instance.
static PyObject *nb_static_property_descr_get(PyObject *self, PyObject *, PyObject *cls) {
if (internals_get().nb_static_property_enabled) {
#if defined(Py_LIMITED_API)
static descrgetfunc tp_descr_get =
(descrgetfunc) PyType_GetSlot(&PyProperty_Type, Py_tp_descr_get);
#else
descrgetfunc tp_descr_get = PyProperty_Type.tp_descr_get;
#endif

return tp_descr_get(self, cls, cls);
nb_internals &internals = internals_get();
if (internals.nb_static_property_enabled) {
return NB_SLOT(internals, PyProperty_Type, tp_descr_get)(self, cls, cls);
} else {
Py_INCREF(self);
return self;
Expand All @@ -23,15 +17,7 @@ static PyObject *nb_static_property_descr_get(PyObject *self, PyObject *, PyObje
/// `nb_static_property.__set__()`: Just like the above `__get__()`.
static int nb_static_property_descr_set(PyObject *self, PyObject *obj, PyObject *value) {
PyObject *cls = PyType_Check(obj) ? obj : (PyObject *) Py_TYPE(obj);

#if defined(Py_LIMITED_API)
static descrsetfunc tp_descr_set =
(descrsetfunc) PyType_GetSlot(&PyProperty_Type, Py_tp_descr_set);
#else
descrsetfunc tp_descr_set = PyProperty_Type.tp_descr_set;
#endif

return tp_descr_set(self, cls, value);
return NB_SLOT(internals_get(), PyProperty_Type, tp_descr_set)(self, cls, value);
}

PyTypeObject *nb_static_property_tp() noexcept {
Expand Down
41 changes: 6 additions & 35 deletions src/nb_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,18 +252,10 @@ static void inst_dealloc(PyObject *self) {
fail("nanobind::detail::inst_dealloc(\"%s\"): attempted to delete "
"an unknown instance (%p)!", t->name, p);

if (NB_UNLIKELY(gc)) {
#if defined(Py_LIMITED_API)
static freefunc tp_free =
(freefunc) PyType_GetSlot(tp, Py_tp_free);
#else
freefunc tp_free = tp->tp_free;
#endif

tp_free(self);
} else {
if (NB_UNLIKELY(gc))
NB_SLOT(internals, PyType_Type, tp_free)(self);
else
PyObject_Free(self);
}

Py_DECREF(tp);
}
Expand All @@ -287,14 +279,7 @@ static void nb_type_dealloc(PyObject *o) {

free((char *) t->name);

#if defined(Py_LIMITED_API)
static destructor tp_dealloc =
(destructor) PyType_GetSlot(&PyType_Type, Py_tp_dealloc);
#else
destructor tp_dealloc = PyType_Type.tp_dealloc;
#endif

tp_dealloc(o);
NB_SLOT(internals_get(), PyType_Type, tp_dealloc)(o);
}

/// Called when a C++ type is extended from within Python
Expand Down Expand Up @@ -325,14 +310,7 @@ static int nb_type_init(PyObject *self, PyObject *args, PyObject *kwds) {
return -1;
}

#if defined(Py_LIMITED_API)
static initproc tp_init =
(initproc) PyType_GetSlot(&PyType_Type, Py_tp_init);
#else
initproc tp_init = PyType_Type.tp_init;
#endif

int rv = tp_init(self, args, kwds);
int rv = NB_SLOT(internals_get(), PyType_Type, tp_init)(self, args, kwds);
if (rv)
return rv;

Expand Down Expand Up @@ -371,14 +349,7 @@ static int nb_type_setattro(PyObject* obj, PyObject* name, PyObject* value) {
PyErr_Clear();
}

#if defined(Py_LIMITED_API)
static setattrofunc tp_setattro =
(setattrofunc) PyType_GetSlot(&PyType_Type, Py_tp_setattro);
#else
setattrofunc tp_setattro = PyType_Type.tp_setattro;
#endif

return tp_setattro(obj, name, value);
return NB_SLOT(internals, PyType_Type, tp_setattro)(obj, name, value);
}

static PyObject *nb_type_from_metaclass(PyTypeObject *meta, PyObject *mod,
Expand Down

0 comments on commit f0f42a5

Please sign in to comment.