blob: 8a8ffc1456100170cc808d0d53c0369d7a5e99f7 [file] [log] [blame]
// Copyright 2019 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef IREE_BINDINGS_PYTHON_IREE_BINDING_H_
#define IREE_BINDINGS_PYTHON_IREE_BINDING_H_
#include <optional>
#include <vector>
#include "iree/base/api.h"
#include "nanobind/nanobind.h"
#include "nanobind/ndarray.h"
#include "nanobind/stl/optional.h"
#include "nanobind/stl/string.h"
#include "nanobind/stl/string_view.h"
#include "nanobind/stl/vector.h"
// Uncomment the following to enable various noisy output to stderr for
// verifying sequencing and reference counting.
// #define IREE_PY_TRACE_ENABLED 1
#if IREE_PY_TRACE_ENABLED
#define IREE_PY_TRACEF(fmt, ...) \
fprintf(stderr, "[[IREE_PY_TRACE]]: " fmt "\n", __VA_ARGS__)
#define IREE_PY_TRACE(msg) fprintf(stderr, "[[IREE_PY_TRACE]]: %s", msg)
#else
#define IREE_PY_TRACEF(...)
#define IREE_PY_TRACE(...)
#endif
namespace iree {
namespace python {
namespace py = nanobind;
using namespace nanobind::literals;
template <typename T>
struct ApiPtrAdapter {};
template <typename Self, typename T>
class ApiRefCounted {
public:
using RawPtrType = T*;
ApiRefCounted() : instance_(nullptr) {}
ApiRefCounted(ApiRefCounted& other) : instance_(other.instance_) { Retain(); }
ApiRefCounted(ApiRefCounted&& other) : instance_(other.instance_) {
other.instance_ = nullptr;
}
ApiRefCounted& operator=(ApiRefCounted&& other) {
instance_ = other.instance_;
other.instance_ = nullptr;
return *this;
}
void operator=(const ApiRefCounted&) = delete;
~ApiRefCounted() { Release(); }
// Steals the reference to the object referenced by the given raw pointer and
// returns a wrapper (transfers ownership).
static Self StealFromRawPtr(T* retained_inst) {
auto self = Self();
self.instance_ = retained_inst;
return self;
}
// Retains the object referenced by the given raw pointer and returns
// a wrapper.
static Self BorrowFromRawPtr(T* non_retained_inst) {
auto self = Self();
self.instance_ = non_retained_inst;
if (non_retained_inst) {
ApiPtrAdapter<T>::Retain(non_retained_inst);
}
return self;
}
// Whether it is nullptr.
operator bool() const { return instance_; }
T* steal_raw_ptr() {
T* ret = instance_;
instance_ = nullptr;
return ret;
}
T* raw_ptr() {
if (!instance_) {
throw std::invalid_argument("API object is null");
}
return instance_;
}
const T* raw_ptr() const {
return const_cast<ApiRefCounted*>(this)->raw_ptr();
}
void Retain() {
if (instance_) {
ApiPtrAdapter<T>::Retain(instance_);
}
}
void Release() {
if (instance_) {
ApiPtrAdapter<T>::Release(instance_);
}
}
private:
T* instance_;
};
// Pybind11 had an isintance for Python objects helper. Nanobind doesn't.
inline bool is_instance_of_type_object(py::handle inst,
py::handle type_object) {
int rc = PyObject_IsInstance(inst.ptr(), type_object.ptr());
if (rc == -1) {
throw py::python_error();
}
return static_cast<bool>(rc);
}
// Nanobind's tuple class has a default constructor that creates a nullptr
// tuple. Which is not really what one wants.
inline py::object create_empty_tuple() {
return py::steal(py::handle(PyTuple_New(0)));
}
// For a bound class, binds the buffer protocol. This will result in a call
// to on the CppType:
// HandleBufferProtocol(Py_buffer *view, int flags)
// This is a low level callback and must not raise any exceptions. If
// error conditions are warranted the usual PyErr_SetString approach must be
// used (and -1 returned). Return 0 on success.
template <typename CppType>
void BindBufferProtocol(py::handle clazz) {
PyBufferProcs buffer_procs;
memset(&buffer_procs, 0, sizeof(buffer_procs));
buffer_procs.bf_getbuffer =
// It is not legal to raise exceptions from these callbacks.
+[](PyObject* raw_self, Py_buffer* view, int flags) -> int {
if (view == NULL) {
PyErr_SetString(PyExc_ValueError, "NULL view in getbuffer");
return -1;
}
// Cast must succeed due to invariants.
auto self = py::cast<CppType*>(py::handle(raw_self));
Py_INCREF(raw_self);
view->obj = raw_self;
return self->HandleBufferProtocol(view, flags);
};
buffer_procs.bf_releasebuffer =
+[](PyObject* raw_self, Py_buffer* view) -> void {};
auto heap_type = reinterpret_cast<PyHeapTypeObject*>(clazz.ptr());
assert(heap_type->ht_type.tp_flags & Py_TPFLAGS_HEAPTYPE &&
"must be heap type");
heap_type->as_buffer = buffer_procs;
}
// Nanobind 2.0 had a backwards compatibility bug where it left out the
// def_static helper. For cases that use this, we patch it here. Note that
// any def_static must be called directly on the subclassed enum as the existing
// helpers do not chain to this subclass.
// See: https://github.com/wjakob/nanobind/issues/597
// It is likely that this is fixed in the next minor version, at which time,
// this check should be changed to select only the affected versions for
// special treatment.
#if NB_VERSION_MAJOR >= 2
template <typename T>
struct nanobind1_compat_enum_ : nanobind::enum_<T> {
using py::enum_<T>::enum_;
template <typename Func, typename... Extra>
nanobind1_compat_enum_& def_static(const char* name_, Func&& f,
const Extra&... extra) {
static_assert(
!std::is_member_function_pointer_v<Func>,
"def_static(...) called with a non-static member function pointer");
cpp_function_def((::nanobind::detail::forward_t<Func>)f,
nanobind::scope(*this), nanobind::name(name_), extra...);
return *this;
}
};
#else
template <typename T>
using nanobind1_compat_enum_ = nanobind::enum_<T>;
#endif
} // namespace python
} // namespace iree
#endif // IREE_BINDINGS_PYTHON_IREE_BINDING_H_