diff --git a/cmake/nanobind-config.cmake b/cmake/nanobind-config.cmake index e59ec89c..f2f492cf 100644 --- a/cmake/nanobind-config.cmake +++ b/cmake/nanobind-config.cmake @@ -125,7 +125,7 @@ endfunction() function (nanobind_build_library TARGET_NAME) cmake_parse_arguments(PARSE_ARGV 1 ARG - "AS_SYSINCLUDE" "" "") + "AS_SYSINCLUDE;NOSTRIP;NOLTO" "" "") if (TARGET ${TARGET_NAME}) return() @@ -218,9 +218,14 @@ function (nanobind_build_library TARGET_NAME) nanobind_link_options(${TARGET_NAME}) target_compile_definitions(${TARGET_NAME} PRIVATE -DNB_BUILD) target_compile_definitions(${TARGET_NAME} PUBLIC -DNB_SHARED) - nanobind_lto(${TARGET_NAME}) - nanobind_strip(${TARGET_NAME}) + if (NOT ${ARG_NOLTO}) + nanobind_lto(${TARGET_NAME}) + endif() + + if (NOT ${ARG_NOSTRIP}) + nanobind_strip(${TARGET_NAME}) + endif() elseif(NOT WIN32 AND NOT APPLE) target_compile_options(${TARGET_NAME} PUBLIC $<${NB_OPT_SIZE}:-ffunction-sections -fdata-sections>) target_link_options(${TARGET_NAME} PUBLIC $<${NB_OPT_SIZE}:-Wl,--gc-sections>) diff --git a/include/nanobind/eigen/sparse.h b/include/nanobind/eigen/sparse.h index 7a137dec..22dfa6c4 100644 --- a/include/nanobind/eigen/sparse.h +++ b/include/nanobind/eigen/sparse.h @@ -55,7 +55,7 @@ template struct type_caster struct type_caster struct type_caster struct type_casterouterIndexPtr(), 1, outer_indices_shape, owner); StorageIndexNDArray inner_indices(src->innerIndexPtr(), 1, data_shape, owner); - try { + if (true) { return matrix_type(nanobind::make_tuple( std::move(data), std::move(inner_indices), std::move(outer_indices)), nanobind::make_tuple(rows, cols)) .release(); - } catch (python_error &e) { + } if (false) { shim::exception_placeholder e; e.restore(); return handle(); } @@ -187,7 +187,7 @@ struct type_caster, enable_if_t>> { bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept { flags = ~(uint8_t) cast_flags::convert; - try { + if (true) { object matrix_type = module_::import_("scipy.sparse") .attr(RowMajor ? "csr_matrix" : "csc_matrix"); @@ -216,7 +216,7 @@ struct type_caster, enable_if_t>> { rows = cast(shape_o[0]); cols = cast(shape_o[1]); nnz = cast(src.attr("nnz")); - } catch (const python_error &) { + } if (false) { return false; } @@ -234,7 +234,7 @@ struct type_caster, enable_if_t>> { } object matrix_type; - try { + if (true) { matrix_type = module_::import_("scipy.sparse") .attr(RowMajor ? "csr_matrix" : "csc_matrix"); @@ -255,7 +255,7 @@ struct type_caster, enable_if_t>> { cast(outer_indices, rv_policy::reference)), nanobind::make_tuple(rows, cols)) .release(); - } catch (python_error &e) { + } if (false) { shim::exception_placeholder e; e.restore(); return handle(); } diff --git a/include/nanobind/exception_shim.h b/include/nanobind/exception_shim.h new file mode 100644 index 00000000..8192dc4e --- /dev/null +++ b/include/nanobind/exception_shim.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +#define throwShim(X) shim::throw_(X, __FILE__, __LINE__) + +namespace shim { + +template +[[noreturn]] inline void throw_(T &&Exception, const char *Filename, unsigned Line) { + fprintf(stderr, "Exception thrown at %s:%d\n%s\n", Filename, Line, Exception.what()); + std::abort(); +} + +class exception_placeholder { +public: + exception_placeholder() = default; + ~exception_placeholder() = default; + + template + exception_placeholder& operator=(const T &) { std::abort(); } + void restore() { std::abort(); } + const char *what() { return ""; } +}; + +} + +NAMESPACE_BEGIN(NB_NAMESPACE) +NAMESPACE_BEGIN(detail) + +inline bool set_builtin_exception_status(shim::exception_placeholder &) { + std::abort(); +} + +NAMESPACE_END(detail) +NAMESPACE_END(NB_NAMESPACE) diff --git a/include/nanobind/make_iterator.h b/include/nanobind/make_iterator.h index db63a912..d08e3c55 100644 --- a/include/nanobind/make_iterator.h +++ b/include/nanobind/make_iterator.h @@ -86,7 +86,7 @@ typed make_iterator_impl(handle scope, const char *name, if (s.it == s.end) { s.first_or_done = true; - throw stop_iteration(); + throwShim(stop_iteration()); } return Access()(s.it); diff --git a/include/nanobind/nanobind.h b/include/nanobind/nanobind.h index 96a37ca0..979d187d 100644 --- a/include/nanobind/nanobind.h +++ b/include/nanobind/nanobind.h @@ -38,13 +38,14 @@ #include #include #include -#include #include #include +#include "typeinfo_shim.h" // Implementation. The nb_*.h files should only be included through nanobind.h // IWYU pragma: begin_exports #include "nb_defs.h" +#include "exception_shim.h" #include "nb_enums.h" #include "nb_traits.h" #include "nb_tuple.h" diff --git a/include/nanobind/nb_attr.h b/include/nanobind/nb_attr.h index 48064b88..861c93cf 100644 --- a/include/nanobind/nb_attr.h +++ b/include/nanobind/nb_attr.h @@ -235,7 +235,7 @@ struct func_data_prelim_base { const char *descr; /// C++ types referenced by 'descr' - const std::type_info **descr_types; + const shim::type_info **descr_types; /// Supplementary flags uint32_t flags; diff --git a/include/nanobind/nb_cast.h b/include/nanobind/nb_cast.h index 4712ef4e..c095f6af 100644 --- a/include/nanobind/nb_cast.h +++ b/include/nanobind/nb_cast.h @@ -191,13 +191,13 @@ template struct type_caster>> { NB_INLINE bool from_python(handle src, uint8_t flags, cleanup_list *) noexcept { int64_t result; - bool rv = enum_from_python(&typeid(T), src.ptr(), &result, flags); + bool rv = enum_from_python(&typeidShim(), src.ptr(), &result, flags); value = (T) result; return rv; } NB_INLINE static handle from_cpp(T src, rv_policy, cleanup_list *) noexcept { - return enum_from_cpp(&typeid(T), (int64_t) src); + return enum_from_cpp(&typeidShim(), (int64_t) src); } NB_TYPE_CASTER(T, const_name()) @@ -316,7 +316,7 @@ template <> struct type_caster { if (can_cast()) return value[0]; else - throw next_overload(); + throwShim(next_overload()); } }; @@ -465,7 +465,7 @@ template struct type_caster_base : type_caster_base_tag { NB_INLINE bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept { - return nb_type_get(&typeid(Type), src.ptr(), flags, cleanup, + return nb_type_get(&typeidShim(), src.ptr(), flags, cleanup, (void **) &value); } @@ -479,7 +479,7 @@ template struct type_caster_base : type_caster_base_tag { ptr = (Type *) &value; policy = infer_policy(policy); - const std::type_info *type = &typeid(Type); + const shim::type_info *type = &typeidShim(); constexpr bool has_type_hook = !std::is_base_of_v>; @@ -489,8 +489,8 @@ template struct type_caster_base : type_caster_base_tag { if constexpr (!std::is_polymorphic_v) { return nb_type_put(type, ptr, policy, cleanup); } else { - const std::type_info *type_p = - (!has_type_hook && ptr) ? &typeid(*ptr) : nullptr; + const shim::type_info *type_p = + (!has_type_hook && ptr) ? &typeidShim(*ptr) : nullptr; return nb_type_put_p(type, type_p, ptr, policy, cleanup); } } diff --git a/include/nanobind/nb_class.h b/include/nanobind/nb_class.h index 084cdfca..9f2a9799 100644 --- a/include/nanobind/nb_class.h +++ b/include/nanobind/nb_class.h @@ -96,7 +96,7 @@ struct nb_alias_chain; // Implicit conversions for C++ type bindings, used in type_data below struct implicit_t { - const std::type_info **cpp; + const shim::type_info **cpp; bool (**py)(PyTypeObject *, PyObject *, cleanup_list *) noexcept; }; @@ -112,7 +112,7 @@ struct type_data { uint32_t align : 8; uint32_t flags : 24; const char *name; - const std::type_info *type; + const shim::type_info *type; PyTypeObject *type_py; nb_alias_chain *alias_chain; #if defined(Py_LIMITED_API) @@ -135,7 +135,7 @@ struct type_data { /// Information about a type that is only relevant when it is being created struct type_init_data : type_data { PyObject *scope; - const std::type_info *base; + const shim::type_info *base; PyTypeObject *base_py; const char *doc; const PyType_Slot *type_slots; @@ -210,7 +210,7 @@ enum class enum_flags : uint32_t { }; struct enum_init_data { - const std::type_info *type; + const shim::type_info *type; PyObject *scope; const char *name; const char *docstr; @@ -289,7 +289,7 @@ NAMESPACE_END(detail) inline bool type_check(handle h) { return detail::nb_type_check(h.ptr()); } inline size_t type_size(handle h) { return detail::nb_type_size(h.ptr()); } inline size_t type_align(handle h) { return detail::nb_type_align(h.ptr()); } -inline const std::type_info& type_info(handle h) { return *detail::nb_type_info(h.ptr()); } +inline const shim::type_info& type_info(handle h) { return *detail::nb_type_info(h.ptr()); } template inline T &type_supplement(handle h) { return *(T *) detail::nb_type_supplement(h.ptr()); } inline str type_name(handle h) { return steal(detail::nb_type_name(h.ptr())); } @@ -401,7 +401,7 @@ template struct init_implicit : def_visitor> { return Caster().from_python( src, detail::cast_flags::convert, cleanup); }, - &typeid(Type)); + &typeidShim()); } } }; @@ -416,7 +416,7 @@ namespace detail { cpp_function_def( [](handle type) { if (!type_check(type)) - throw cast_error(); + throwShim(cast_error()); return inst_alloc(type); }, scope(cls), name("__new__")); @@ -564,10 +564,10 @@ class class_ : public object { d.size = (uint32_t) sizeof(Alias); d.name = name; d.scope = scope.ptr(); - d.type = &typeid(T); + d.type = &typeidShim(); if constexpr (!std::is_same_v) { - d.base = &typeid(Base); + d.base = &typeidShim(); d.flags |= (uint32_t) detail::type_init_flags::has_base; } @@ -776,7 +776,7 @@ template class enum_ : public object { template NB_INLINE enum_(handle scope, const char *name, const Extra &... extra) { detail::enum_init_data ed { }; - ed.type = &typeid(T); + ed.type = &typeidShim(); ed.scope = scope.ptr(); ed.name = name; ed.flags = std::is_signed_v @@ -847,7 +847,7 @@ template void implicitly_convertible() { "unless it is opaque."); if constexpr (detail::is_base_caster_v) { - detail::implicitly_convertible(&typeid(Source), &typeid(Target)); + detail::implicitly_convertible(&typeidShim(), &typeidShim()); } else { detail::implicitly_convertible( [](PyTypeObject *, PyObject *src, @@ -855,7 +855,7 @@ template void implicitly_convertible() { return Caster().from_python(src, detail::cast_flags::convert, cleanup); }, - &typeid(Target)); + &typeidShim()); } } } diff --git a/include/nanobind/nb_defs.h b/include/nanobind/nb_defs.h index 4bde051d..cff0ac16 100644 --- a/include/nanobind/nb_defs.h +++ b/include/nanobind/nb_defs.h @@ -47,11 +47,7 @@ # endif #endif -#if defined(__GNUC__) && !defined(_WIN32) -# define NB_NAMESPACE nanobind __attribute__((visibility("hidden"))) -#else -# define NB_NAMESPACE nanobind -#endif +#define NB_NAMESPACE nanobind #if defined(__GNUC__) # define NB_UNLIKELY(x) __builtin_expect(bool(x), 0) @@ -175,16 +171,16 @@ static void nanobind_##name##_exec_impl(nanobind::module_); \ static int nanobind_##name##_exec(PyObject *m) { \ nanobind::detail::nb_module_exec(NB_DOMAIN_STR, m); \ - try { \ + if (true) { \ nanobind_##name##_exec_impl( \ nanobind::borrow(m)); \ return 0; \ - } catch (nanobind::python_error &e) { \ + } if (false) { shim::exception_placeholder e; \ e.restore(); \ nanobind::chain_error( \ PyExc_ImportError, \ "Encountered an error while initializing the extension."); \ - } catch (const std::exception &e) { \ + } if (false) { shim::exception_placeholder e; \ PyErr_SetString(PyExc_ImportError, e.what()); \ } \ return -1; \ diff --git a/include/nanobind/nb_descr.h b/include/nanobind/nb_descr.h index 4975e132..5d967f45 100644 --- a/include/nanobind/nb_descr.h +++ b/include/nanobind/nb_descr.h @@ -27,9 +27,9 @@ struct descr { constexpr size_t type_count() const { return sizeof...(Ts); } constexpr size_t size() const { return N; } - NB_INLINE void put_types(const std::type_info **out) const { + NB_INLINE void put_types(const shim::type_info **out) const { size_t ctr = 0; - ((out[ctr++] = &typeid(Ts)), ...); + ((out[ctr++] = &typeidShim()), ...); out[ctr++] = nullptr; } }; diff --git a/include/nanobind/nb_error.h b/include/nanobind/nb_error.h index 3abc960e..c7a531da 100644 --- a/include/nanobind/nb_error.h +++ b/include/nanobind/nb_error.h @@ -7,6 +7,8 @@ BSD-style license that can be found in the LICENSE file. */ +#include + NAMESPACE_BEGIN(NB_NAMESPACE) /// RAII wrapper that temporarily clears any Python error state @@ -102,11 +104,21 @@ class NB_EXPORT builtin_exception : public std::runtime_error { NB_EXPORT_SHARED builtin_exception(const builtin_exception &) = default; NB_EXPORT_SHARED ~builtin_exception(); NB_EXPORT_SHARED exception_type type() const { return m_type; } + virtual const char *what() const override { return m_what.c_str(); } private: exception_type m_type; + std::string m_what; +}; + +inline std::map exception_type_str; +struct RegisterExceptionTypeStr { + RegisterExceptionTypeStr(exception_type type, const char *name) { + exception_type_str[type] = name; + } }; #define NB_EXCEPTION(name) \ + inline RegisterExceptionTypeStr _reg_ ## name(exception_type::name, #name);\ inline builtin_exception name(const char *what = nullptr) { \ return builtin_exception(exception_type::name, what); \ } @@ -137,9 +149,10 @@ class exception : public object { detail::steal_t()) { detail::register_exception_translator( [](const std::exception_ptr &p, void *payload) { - try { + abort(); + if (true) { std::rethrow_exception(p); - } catch (T &e) { + } if (false) { shim::exception_placeholder e; PyErr_SetString((PyObject *) payload, e.what()); } }, m_ptr); diff --git a/include/nanobind/nb_func.h b/include/nanobind/nb_func.h index 364f3cdd..373dbecc 100644 --- a/include/nanobind/nb_func.h +++ b/include/nanobind/nb_func.h @@ -180,8 +180,8 @@ NB_INLINE PyObject *func_create(Func &&func, Return (*)(Args...), make_caster>>::Name)...) + const_name(") -> ") + cast_out::Name; - // std::type_info for all function arguments - const std::type_info* descr_types[descr.type_count() + 1]; + // shim::type_info for all function arguments + const shim::type_info* descr_types[descr.type_count() + 1]; descr.put_types(descr_types); // Auxiliary data structure to capture the provided function/closure diff --git a/include/nanobind/nb_lib.h b/include/nanobind/nb_lib.h index fc3e6d39..ae2a4286 100644 --- a/include/nanobind/nb_lib.h +++ b/include/nanobind/nb_lib.h @@ -299,28 +299,28 @@ struct type_init_data; NB_CORE PyObject *nb_type_new(const type_init_data *c) noexcept; /// Extract a pointer to a C++ type underlying a Python object, if possible -NB_CORE bool nb_type_get(const std::type_info *t, PyObject *o, uint8_t flags, +NB_CORE bool nb_type_get(const shim::type_info *t, PyObject *o, uint8_t flags, cleanup_list *cleanup, void **out) noexcept; /// Cast a C++ type instance into a Python object -NB_CORE PyObject *nb_type_put(const std::type_info *cpp_type, void *value, +NB_CORE PyObject *nb_type_put(const shim::type_info *cpp_type, void *value, rv_policy rvp, cleanup_list *cleanup, bool *is_new = nullptr) noexcept; // Special version of nb_type_put for polymorphic classes -NB_CORE PyObject *nb_type_put_p(const std::type_info *cpp_type, - const std::type_info *cpp_type_p, void *value, +NB_CORE PyObject *nb_type_put_p(const shim::type_info *cpp_type, + const shim::type_info *cpp_type_p, void *value, rv_policy rvp, cleanup_list *cleanup, bool *is_new = nullptr) noexcept; // Special version of 'nb_type_put' for unique pointers and ownership transfer -NB_CORE PyObject *nb_type_put_unique(const std::type_info *cpp_type, +NB_CORE PyObject *nb_type_put_unique(const shim::type_info *cpp_type, void *value, cleanup_list *cleanup, bool cpp_delete) noexcept; // Special version of 'nb_type_put_unique' for polymorphic classes -NB_CORE PyObject *nb_type_put_unique_p(const std::type_info *cpp_type, - const std::type_info *cpp_type_p, +NB_CORE PyObject *nb_type_put_unique_p(const shim::type_info *cpp_type, + const shim::type_info *cpp_type_p, void *value, cleanup_list *cleanup, bool cpp_delete) noexcept; @@ -351,16 +351,16 @@ NB_CORE PyObject *nb_type_name(PyObject *t) noexcept; NB_CORE PyObject *nb_inst_name(PyObject *o) noexcept; /// Return the C++ type_info wrapped by the given nanobind type object -NB_CORE const std::type_info *nb_type_info(PyObject *t) noexcept; +NB_CORE const shim::type_info *nb_type_info(PyObject *t) noexcept; /// Get a pointer to the instance data of a nanobind instance (nb_inst) NB_CORE void *nb_inst_ptr(PyObject *o) noexcept; /// Check if a Python type object wraps an instance of a specific C++ type -NB_CORE bool nb_type_isinstance(PyObject *obj, const std::type_info *t) noexcept; +NB_CORE bool nb_type_isinstance(PyObject *obj, const shim::type_info *t) noexcept; /// Search for the Python type object associated with a C++ type -NB_CORE PyObject *nb_type_lookup(const std::type_info *t) noexcept; +NB_CORE PyObject *nb_type_lookup(const shim::type_info *t) noexcept; /// Allocate an instance of type 't' NB_CORE PyObject *nb_inst_alloc(PyTypeObject *t); @@ -414,7 +414,7 @@ NB_CORE void property_install_static(PyObject *scope, const char *name, // ======================================================================== -NB_CORE PyObject *get_override(void *ptr, const std::type_info *type, +NB_CORE PyObject *get_override(void *ptr, const shim::type_info *type, const char *name, bool pure); // ======================================================================== @@ -430,14 +430,14 @@ NB_CORE void keep_alive(PyObject *nurse, void *payload, // ======================================================================== /// Indicate to nanobind that an implicit constructor can convert 'src' -> 'dst' -NB_CORE void implicitly_convertible(const std::type_info *src, - const std::type_info *dst) noexcept; +NB_CORE void implicitly_convertible(const shim::type_info *src, + const shim::type_info *dst) noexcept; /// Register a callback to check if implicit conversion to 'dst' is possible NB_CORE void implicitly_convertible(bool (*predicate)(PyTypeObject *, PyObject *, cleanup_list *), - const std::type_info *dst) noexcept; + const shim::type_info *dst) noexcept; // ======================================================================== @@ -451,11 +451,11 @@ NB_CORE void enum_append(PyObject *tp, const char *name, int64_t value, const char *doc) noexcept; // Query an enumeration's Python object -> integer value map -NB_CORE bool enum_from_python(const std::type_info *, PyObject *, int64_t *, +NB_CORE bool enum_from_python(const shim::type_info *, PyObject *, int64_t *, uint8_t flags) noexcept; // Query an enumeration's integer value -> Python object map -NB_CORE PyObject *enum_from_cpp(const std::type_info *, int64_t) noexcept; +NB_CORE PyObject *enum_from_cpp(const shim::type_info *, int64_t) noexcept; /// Export enum entries to the parent scope NB_CORE void enum_export(PyObject *tp); diff --git a/include/nanobind/nb_types.h b/include/nanobind/nb_types.h index 84d63e34..7536d084 100644 --- a/include/nanobind/nb_types.h +++ b/include/nanobind/nb_types.h @@ -423,7 +423,7 @@ class int_ : public object { explicit operator T() const { detail::type_caster tc; if (!tc.from_python(m_ptr, 0, nullptr)) - throw std::out_of_range("Conversion of nanobind::int_ failed"); + throwShim(std::out_of_range("Conversion of nanobind::int_ failed")); return tc.value; } }; @@ -694,7 +694,7 @@ class iterable : public object { /// Retrieve the Python type object associated with a C++ class template handle type() noexcept { - return detail::nb_type_lookup(&typeid(detail::intrinsic_t)); + return detail::nb_type_lookup(&typeidShim>()); } template @@ -702,7 +702,7 @@ NB_INLINE bool isinstance(handle h) noexcept { if constexpr (std::is_base_of_v) return T::check_(h); else if constexpr (detail::is_base_caster_v>) - return detail::nb_type_isinstance(h.ptr(), &typeid(detail::intrinsic_t)); + return detail::nb_type_isinstance(h.ptr(), &typeidShim>()); else return detail::make_caster().from_python(h, 0, nullptr); } diff --git a/include/nanobind/stl/bind_map.h b/include/nanobind/stl/bind_map.h index 1431167d..0543097c 100644 --- a/include/nanobind/stl/bind_map.h +++ b/include/nanobind/stl/bind_map.h @@ -87,7 +87,7 @@ class_ bind_map(handle scope, const char *name, Args &&...args) { [](Map &m, const Key &k) -> ValueRef { auto it = m.find(k); if (it == m.end()) - throw key_error(); + throwShim(key_error()); return (*it).second; }, Policy) @@ -95,7 +95,7 @@ class_ bind_map(handle scope, const char *name, Args &&...args) { [](Map &m, const Key &k) { auto it = m.find(k); if (it == m.end()) - throw key_error(); + throwShim(key_error()); m.erase(it); }) diff --git a/include/nanobind/stl/bind_vector.h b/include/nanobind/stl/bind_vector.h index 721b1b3a..5f4675f6 100644 --- a/include/nanobind/stl/bind_vector.h +++ b/include/nanobind/stl/bind_vector.h @@ -24,7 +24,7 @@ inline size_t wrap(Py_ssize_t i, size_t n) { i += (Py_ssize_t) n; if (i < 0 || (size_t) i >= n) - throw index_error(); + throwShim(index_error()); return (size_t) i; } @@ -108,7 +108,7 @@ class_ bind_vector(handle scope, const char *name, Args &&...args) { if (i < 0) i += (Py_ssize_t) v.size(); if (i < 0 || (size_t) i > v.size()) - throw index_error(); + throwShim(index_error()); v.insert(v.begin() + i, x); }, "Insert object `arg1` before index `arg0`.") @@ -214,7 +214,7 @@ class_ bind_vector(handle scope, const char *name, Args &&...args) { if (p != v.end()) v.erase(p); else - throw value_error(); + throwShim(value_error()); }, "Remove first occurrence of `arg`."); } diff --git a/include/nanobind/stl/chrono.h b/include/nanobind/stl/chrono.h index 4bedf32e..c2b75769 100644 --- a/include/nanobind/stl/chrono.h +++ b/include/nanobind/stl/chrono.h @@ -42,13 +42,13 @@ template class duration_caster { // If invoked with datetime.delta object, unpack it int dd, ss, uu; - try { + if (true) { if (unpack_timedelta(src.ptr(), &dd, &ss, &uu)) { value = type(ch::duration_cast( days(dd) + ch::seconds(ss) + ch::microseconds(uu))); return true; } - } catch (python_error& e) { + } if (false) { shim::exception_placeholder e; e.discard_as_unraisable(src.ptr()); return false; } @@ -147,12 +147,12 @@ class type_caster> std::tm cal; ch::microseconds msecs; int yy, mon, dd, hh, min, ss, uu; - try { + if (true) { if (!unpack_datetime(src.ptr(), &yy, &mon, &dd, &hh, &min, &ss, &uu)) { return false; } - } catch (python_error& e) { + } if (false) { shim::exception_placeholder e; e.discard_as_unraisable(src.ptr()); return false; } diff --git a/include/nanobind/stl/detail/chrono.h b/include/nanobind/stl/detail/chrono.h index b4815c57..6563d578 100644 --- a/include/nanobind/stl/detail/chrono.h +++ b/include/nanobind/stl/detail/chrono.h @@ -189,10 +189,10 @@ NB_NOINLINE inline bool unpack_datetime(PyObject *o, } inline PyObject* pack_timedelta(int days, int secs, int usecs) noexcept { - try { + if (true) { datetime_types.ensure_ready(); return datetime_types.timedelta(days, secs, usecs).release().ptr(); - } catch (python_error& e) { + } if (false) { shim::exception_placeholder e; e.restore(); return nullptr; } @@ -201,11 +201,11 @@ inline PyObject* pack_timedelta(int days, int secs, int usecs) noexcept { inline PyObject* pack_datetime(int year, int month, int day, int hour, int minute, int second, int usec) noexcept { - try { + if (true) { datetime_types.ensure_ready(); return datetime_types.datetime( year, month, day, hour, minute, second, usec).release().ptr(); - } catch (python_error& e) { + } if (false) { shim::exception_placeholder e; e.restore(); return nullptr; } diff --git a/include/nanobind/stl/filesystem.h b/include/nanobind/stl/filesystem.h index bdc23145..ebaf45a7 100644 --- a/include/nanobind/stl/filesystem.h +++ b/include/nanobind/stl/filesystem.h @@ -23,11 +23,11 @@ struct type_caster { cleanup_list *) noexcept { str py_str = to_py_str(path.native()); if (py_str.is_valid()) { - try { + if (true) { return module_::import_("pathlib") .attr("Path")(py_str) .release(); - } catch (python_error &e) { + } if (false) { shim::exception_placeholder e; e.restore(); } } diff --git a/include/nanobind/stl/shared_ptr.h b/include/nanobind/stl/shared_ptr.h index a9a86542..b3969abc 100644 --- a/include/nanobind/stl/shared_ptr.h +++ b/include/nanobind/stl/shared_ptr.h @@ -102,7 +102,7 @@ template struct type_caster> { handle result; Td *ptr = (Td *) value.get(); - const std::type_info *type = &typeid(Td); + const shim::type_info *type = &typeidShim(); constexpr bool has_type_hook = !std::is_base_of_v>; @@ -113,8 +113,8 @@ template struct type_caster> { result = nb_type_put(type, ptr, rv_policy::reference, cleanup, &is_new); } else { - const std::type_info *type_p = - (!has_type_hook && ptr) ? &typeid(*ptr) : nullptr; + const shim::type_info *type_p = + (!has_type_hook && ptr) ? &typeidShim(*ptr) : nullptr; result = nb_type_put_p(type, type_p, ptr, rv_policy::reference, cleanup, &is_new); diff --git a/include/nanobind/stl/unique_ptr.h b/include/nanobind/stl/unique_ptr.h index c7700f3c..06febda9 100644 --- a/include/nanobind/stl/unique_ptr.h +++ b/include/nanobind/stl/unique_ptr.h @@ -118,7 +118,7 @@ struct type_caster> { cpp_delete = value.get_deleter().owned_by_cpp(); Td *ptr = (Td *) value.get(); - const std::type_info *type = &typeid(Td); + const shim::type_info *type = &typeidShim(); if (!ptr) return none().release(); @@ -131,8 +131,8 @@ struct type_caster> { if constexpr (!std::is_polymorphic_v) { result = nb_type_put_unique(type, ptr, cleanup, cpp_delete); } else { - const std::type_info *type_p = - (!has_type_hook && ptr) ? &typeid(*ptr) : nullptr; + const shim::type_info *type_p = + (!has_type_hook && ptr) ? &typeidShim(*ptr) : nullptr; result = nb_type_put_unique_p(type, type_p, ptr, cleanup, cpp_delete); } @@ -160,7 +160,7 @@ struct type_caster> { explicit operator Value() { if (!inflight && !src.is_none() && !nb_type_relinquish_ownership(src.ptr(), IsDefaultDeleter)) - throw next_overload(); + throwShim(next_overload()); Td *p = caster.operator Td *(); diff --git a/include/nanobind/typeinfo_shim.h b/include/nanobind/typeinfo_shim.h new file mode 100644 index 00000000..8251a40c --- /dev/null +++ b/include/nanobind/typeinfo_shim.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include + +namespace shim { + +class type_info { +private: + const char *prettyName = nullptr; + +public: + type_info(const char *prettyName) : prettyName(prettyName){}; + + /// Return the stringified name of the type. This should be similar enough + /// with what shim::type_info does. + const char *name() const { return prettyName; }; + + bool operator==(const shim::type_info &Other) const { + // This works because TypeStorage below will return the same + // `type_info` object for the same type + return this == &Other; + } +}; + +} + +template +class TypeStorage { +private: + /// This function will return the name of T, statically + /// It leverages the `__PRETTY_FUNCTION__` compiler macro which expands + /// template paramters to their class name + static inline const char *_name() { + // Store the macro in a string, this will be in the form + // TypeStorage<$TYPE_NAME>::_name() + std::string UglyName{__PRETTY_FUNCTION__}; + + // Find the position where the tail end of the string we're + //interested in + std::string EndMarker = ">::_name()"; + size_t EndPos = UglyName.rfind(EndMarker); + assert(EndPos > 1); + + // Iterate backwards through the string, counting the number of '>' and + // '<'s encountered, this allows this function to work in cases where T + // has template parameters, e.g. TypeStorage> + size_t StartPos = 0; + size_t Counter = 0; + for (size_t I = EndPos - 1; I >= 0; I--) { + // If here it means we balanced out the outermost > with a <, + // save the position and break + if (UglyName[I] == '<' and Counter == 0) { + StartPos = I + 1; + break; + } + + if (UglyName[I] == '>') + Counter++; + if (UglyName[I] == '<') + Counter--; + } + + assert(StartPos >= 0); + assert(EndPos > StartPos); + + // Extract the substring and save it in static storage, this is needed + // because shim::type_info returns a `const char *` for `name` + static std::string Name = UglyName.substr(StartPos, EndPos - StartPos); + return Name.c_str(); + } + +public: + static inline shim::type_info ID{_name()}; +}; + +template +shim::type_info &typeidShim() { + // Return a shim::type_info-like object. This will always be the same object + // given the same type. This works be leveraging WEAK library symbols. It + // will not work in some cases (e.g. `dlopen(..., RTLD_LOCAL)`) + return TypeStorage::ID; +} + +shim::type_info &typeidShim(auto) { + // This is the typeidShim for values, since we don't have RTTI the + // shim::type_info cannot be retrieved. + std::abort(); +} diff --git a/src/common.cpp b/src/common.cpp index 9303f9cb..bcf51194 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -47,7 +47,7 @@ void raise(const char *fmt, ...) { builtin_exception err = create_exception(exception_type::runtime_error, fmt, args); va_end(args); - throw err; + throwShim(err); } #if defined(__GNUC__) @@ -61,7 +61,7 @@ void raise_type_error(const char *fmt, ...) { builtin_exception err = create_exception(exception_type::type_error, fmt, args); va_end(args); - throw err; + throwShim(err); } /// Abort the process with a fatal error @@ -105,18 +105,18 @@ void raise_python_error() { check(PyErr_Occurred(), "nanobind::detail::raise_python_error() called without " "an error condition!"); - throw python_error(); + throwShim(python_error()); } void raise_next_overload_if_null(void *p) { if (NB_UNLIKELY(!p)) - throw next_overload(); + throwShim(next_overload()); } void raise_python_or_cast_error() { if (PyErr_Occurred()) - throw python_error(); - throw cast_error(); + throwShim(python_error()); + throwShim(cast_error()); } // ======================================================================== @@ -147,14 +147,14 @@ void cleanup_list::expand() noexcept { PyObject *module_import(const char *name) { PyObject *res = PyImport_ImportModule(name); if (!res) - throw python_error(); + throwShim(python_error()); return res; } PyObject *module_import(PyObject *o) { PyObject *res = PyImport_Import(o); if (!res) - throw python_error(); + throwShim(python_error()); return res; } @@ -237,9 +237,9 @@ size_t obj_len_hint(PyObject *o) noexcept { return (size_t) res; } - try { + if (true) { return cast(handle(o).attr("__length_hint__")()); - } catch (...) { + } if (false) { return 0; } #endif diff --git a/src/error.cpp b/src/error.cpp index c45a39dd..33499e79 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -201,7 +201,13 @@ const char *python_error::what() const noexcept { } builtin_exception::builtin_exception(exception_type type, const char *what) - : std::runtime_error(what ? what : ""), m_type(type) { } + : std::runtime_error(what ? what : ""), m_type(type) { + m_what.append(exception_type_str[type]); + if (what != nullptr && strlen(what) > 0) { + m_what.append(": "); + m_what.append(what); + } + } builtin_exception::~builtin_exception() { } NAMESPACE_BEGIN(detail) diff --git a/src/implicit.cpp b/src/implicit.cpp index 10702a06..2186163d 100644 --- a/src/implicit.cpp +++ b/src/implicit.cpp @@ -13,8 +13,8 @@ NAMESPACE_BEGIN(NB_NAMESPACE) NAMESPACE_BEGIN(detail) -void implicitly_convertible(const std::type_info *src, - const std::type_info *dst) noexcept { +void implicitly_convertible(const shim::type_info *src, + const shim::type_info *dst) noexcept { nb_internals *internals_ = internals; type_data *t = nb_type_c2p(internals_, dst); check(t, "nanobind::detail::implicitly_convertible(src=%s, dst=%s): " @@ -44,7 +44,7 @@ void implicitly_convertible(const std::type_info *src, void implicitly_convertible(bool (*predicate)(PyTypeObject *, PyObject *, cleanup_list *), - const std::type_info *dst) noexcept { + const shim::type_info *dst) noexcept { nb_internals *internals_ = internals; type_data *t = nb_type_c2p(internals_, dst); check(t, "nanobind::detail::implicitly_convertible(src=, dst=%s): " diff --git a/src/nb_enum.cpp b/src/nb_enum.cpp index d02348ce..ab4d6441 100644 --- a/src/nb_enum.cpp +++ b/src/nb_enum.cpp @@ -17,7 +17,7 @@ struct int64_hash { using enum_map = tsl::robin_map; PyObject *enum_create(enum_init_data *ed) noexcept { - // Update hash table that maps from std::type_info to Python type + // Update hash table that maps from shim::type_info to Python type nb_internals *internals_ = internals; bool success; nb_type_map_slow::iterator it; @@ -184,7 +184,7 @@ void enum_append(PyObject *tp_, const char *name_, int64_t value_, rev->emplace((int64_t) (uintptr_t) el.ptr(), value_); } -bool enum_from_python(const std::type_info *tp, PyObject *o, int64_t *out, uint8_t flags) noexcept { +bool enum_from_python(const shim::type_info *tp, PyObject *o, int64_t *out, uint8_t flags) noexcept { type_data *t = nb_type_c2p(internals, tp); if (!t) return false; @@ -257,7 +257,7 @@ bool enum_from_python(const std::type_info *tp, PyObject *o, int64_t *out, uint8 return false; } -PyObject *enum_from_cpp(const std::type_info *tp, int64_t key) noexcept { +PyObject *enum_from_cpp(const shim::type_info *tp, int64_t key) noexcept { type_data *t = nb_type_c2p(internals, tp); if (!t) return nullptr; diff --git a/src/nb_func.cpp b/src/nb_func.cpp index 9db264f6..55ec55b2 100644 --- a/src/nb_func.cpp +++ b/src/nb_func.cpp @@ -253,12 +253,8 @@ PyObject *nb_func_new(const func_data_prelim_base *f) noexcept { instead, hide the parent's overloads in this case */ if (fp->scope != f->scope) Py_CLEAR(func_prev); - } else if (name_cstr[0] == '_') { - Py_CLEAR(func_prev); } else { - check(false, - "nb::detail::nb_func_new(\"%s\"): cannot overload " - "existing non-function object of the same name!", name_cstr); + Py_CLEAR(func_prev); } } else { PyErr_Clear(); @@ -416,10 +412,10 @@ PyObject *nb_func_new(const func_data_prelim_base *f) noexcept { for (size_t i = 0;; ++i) { if (!f->descr_types[i]) { - fc->descr_types = (const std::type_info **) - malloc_check(sizeof(const std::type_info *) * (i + 1)); + fc->descr_types = (const shim::type_info **) + malloc_check(sizeof(const shim::type_info *) * (i + 1)); memcpy(fc->descr_types, f->descr_types, - (i + 1) * sizeof(const std::type_info *)); + (i + 1) * sizeof(const shim::type_info *)); break; } } @@ -585,15 +581,16 @@ static NB_NOINLINE PyObject *nb_func_error_noconvert(PyObject *self, /// Used by nb_func_vectorcall: convert a C++ exception into a Python error static NB_NOINLINE void nb_func_convert_cpp_exception() noexcept { + abort(); std::exception_ptr e = std::current_exception(); for (nb_translator_seq *cur = &internals->translators; cur; cur = cur->next) { - try { + if (true) { // Try exception translator & forward payload cur->translator(e, cur->payload); return; - } catch (...) { + } if (false) { e = std::current_exception(); } } @@ -836,7 +833,7 @@ static PyObject *nb_func_vectorcall_complex(PyObject *self, rv_policy policy = (rv_policy) (f->flags & 0b111); - try { + if (true) { result = nullptr; // Found a suitable overload, let's try calling it @@ -845,12 +842,12 @@ static PyObject *nb_func_vectorcall_complex(PyObject *self, if (NB_UNLIKELY(!result)) error_handler = nb_func_error_noconvert; - } catch (builtin_exception &e) { + } if (false) { shim::exception_placeholder e; if (!set_builtin_exception_status(e)) result = NB_NEXT_OVERLOAD; - } catch (python_error &e) { + } if (false) { shim::exception_placeholder e; e.restore(); - } catch (...) { + } if (false) { nb_func_convert_cpp_exception(); } @@ -929,7 +926,7 @@ static PyObject *nb_func_vectorcall_simple(PyObject *self, if (nargs_in != f->nargs) continue; - try { + if (true) { result = nullptr; // Found a suitable overload, let's try calling it @@ -939,12 +936,12 @@ static PyObject *nb_func_vectorcall_simple(PyObject *self, if (NB_UNLIKELY(!result)) error_handler = nb_func_error_noconvert; - } catch (builtin_exception &e) { + } if (false) { shim::exception_placeholder e; if (!set_builtin_exception_status(e)) result = NB_NEXT_OVERLOAD; - } catch (python_error &e) { + } if (false) { shim::exception_placeholder e; e.restore(); - } catch (...) { + } if (false) { nb_func_convert_cpp_exception(); } @@ -990,19 +987,19 @@ static PyObject *nb_func_vectorcall_simple_0(PyObject *self, PyObject *result = nullptr; if (kwargs_in == nullptr && nargs_in == 0) { - try { + if (true) { result = fr->impl((void *) fr->capture, (PyObject **) args_in, nullptr, (rv_policy) (fr->flags & 0b111), nullptr); if (result == NB_NEXT_OVERLOAD) error_handler = nb_func_error_overload; else if (!result) error_handler = nb_func_error_noconvert; - } catch (builtin_exception &e) { + } if (false) { shim::exception_placeholder e; if (!set_builtin_exception_status(e)) error_handler = nb_func_error_overload; - } catch (python_error &e) { + } if (false) { shim::exception_placeholder e; e.restore(); - } catch (...) { + } if (false) { nb_func_convert_cpp_exception(); } } else { @@ -1037,7 +1034,7 @@ static PyObject *nb_func_vectorcall_simple_1(PyObject *self, (uint8_t) (is_constructor ? (1 | (uint8_t) cast_flags::construct) : 1) }; - try { + if (true) { result = fr->impl((void *) fr->capture, (PyObject **) args_in, args_flags, (rv_policy) (fr->flags & 0b111), &cleanup); if (result == NB_NEXT_OVERLOAD) { @@ -1052,12 +1049,12 @@ static PyObject *nb_func_vectorcall_simple_1(PyObject *self, nb_type_data(Py_TYPE(arg)) ->set_self_py(inst_ptr(arg_nb), arg); } - } catch (builtin_exception &e) { + } if (false) { shim::exception_placeholder e; if (!set_builtin_exception_status(e)) error_handler = nb_func_error_overload; - } catch (python_error &e) { + } if (false) { shim::exception_placeholder e; e.restore(); - } catch (...) { + } if (false) { nb_func_convert_cpp_exception(); } @@ -1165,7 +1162,7 @@ static uint32_t nb_func_render_signature(const func_data *f, if (nb_signature_mode) buf.put("def "); - const std::type_info **descr_type = f->descr_types; + const shim::type_info **descr_type = f->descr_types; bool rv = false; uint32_t arg_index = 0, n_default_args = 0; @@ -1566,7 +1563,7 @@ static void strexc(char *s, const char *sub) { } /// Return a readable string representation of a C++ type -NB_NOINLINE char *type_name(const std::type_info *t) { +NB_NOINLINE char *type_name(const shim::type_info *t) { const char *name_in = t->name(); #if defined(__GNUG__) diff --git a/src/nb_internals.cpp b/src/nb_internals.cpp index b1db3790..901be7f4 100644 --- a/src/nb_internals.cpp +++ b/src/nb_internals.cpp @@ -139,23 +139,24 @@ static PyType_Spec nb_bound_method_spec = { }; void default_exception_translator(const std::exception_ptr &p, void *) { - try { + abort(); + if (true) { std::rethrow_exception(p); - } catch (const std::bad_alloc &e) { + } if (false) { shim::exception_placeholder e; PyErr_SetString(PyExc_MemoryError, e.what()); - } catch (const std::domain_error &e) { + } if (false) { shim::exception_placeholder e; PyErr_SetString(PyExc_ValueError, e.what()); - } catch (const std::invalid_argument &e) { + } if (false) { shim::exception_placeholder e; PyErr_SetString(PyExc_ValueError, e.what()); - } catch (const std::length_error &e) { + } if (false) { shim::exception_placeholder e; PyErr_SetString(PyExc_ValueError, e.what()); - } catch (const std::out_of_range &e) { + } if (false) { shim::exception_placeholder e; PyErr_SetString(PyExc_IndexError, e.what()); - } catch (const std::range_error &e) { + } if (false) { shim::exception_placeholder e; PyErr_SetString(PyExc_ValueError, e.what()); - } catch (const std::overflow_error &e) { + } if (false) { shim::exception_placeholder e; PyErr_SetString(PyExc_OverflowError, e.what()); - } catch (const std::exception &e) { + } if (false) { shim::exception_placeholder e; PyErr_SetString(PyExc_RuntimeError, e.what()); } } diff --git a/src/nb_internals.h b/src/nb_internals.h index 8a245705..962e043b 100644 --- a/src/nb_internals.h +++ b/src/nb_internals.h @@ -167,7 +167,7 @@ struct nb_inst_seq { // Linked list of type aliases when there are multiple shared libraries with duplicate RTTI data struct nb_alias_chain { - const std::type_info *value; + const shim::type_info *value; nb_alias_chain *next; }; @@ -179,14 +179,14 @@ struct nb_weakref_seq { }; struct std_typeinfo_hash { - size_t operator()(const std::type_info *a) const { + size_t operator()(const shim::type_info *a) const { const char *name = a->name(); return std::hash()({name, strlen(name)}); } }; struct std_typeinfo_eq { - bool operator()(const std::type_info *a, const std::type_info *b) const { + bool operator()(const shim::type_info *a, const shim::type_info *b) const { return a->name() == b->name() || strcmp(a->name(), b->name()) == 0; } }; @@ -196,7 +196,7 @@ struct std_typeinfo_eq { using nb_ptr_map = tsl::robin_map; using nb_type_map_fast = nb_ptr_map; -using nb_type_map_slow = tsl::robin_map; /// Convenience functions to deal with the pointer encoding in 'internals.inst_c2p' @@ -321,14 +321,14 @@ struct nb_maybe_atomic { * reference_internal return value policy). This data structure is * potentially hot and shares the sharding scheme of `inst_c2p`. * - * - `type_c2p_slow`: This is the ground-truth source of the `std::type_info` + * - `type_c2p_slow`: This is the ground-truth source of the `shim::type_info` * to `type_info *` mapping. Unrelated to free-threading, lookups into this * data struture are generally costly because they use a string comparison on * some platforms. Because it is only used as a fallback for 'type_c2p_fast', * protecting this member via the global `mutex` is sufficient. * * - `type_c2p_fast`: this data structure is *hot* and mostly read. It maps - * `std::type_info` to `type_info *` but uses pointer-based comparisons. + * `shim::type_info` to `type_info *` but uses pointer-based comparisons. * The implementation depends on the Python build. * * - `translators`: This is an append-to-front-only singly linked list traversed @@ -386,7 +386,7 @@ struct nb_internals { #endif #if !defined(NB_FREE_THREADED) - /// C++ -> Python type map -- fast version based on std::type_info pointer equality + /// C++ -> Python type map -- fast version based on shim::type_info pointer equality nb_type_map_fast type_c2p_fast; #endif @@ -488,14 +488,14 @@ inline PyTypeObject *new_type(nb_internals *p, PyType_Spec *spec) { extern nb_internals *internals; extern PyTypeObject *nb_meta_cache; -extern char *type_name(const std::type_info *t); +extern char *type_name(const shim::type_info *t); // Forward declarations extern PyObject *inst_new_ext(PyTypeObject *tp, void *value); extern PyObject *inst_new_int(PyTypeObject *tp, PyObject *args, PyObject *kwds); extern PyTypeObject *nb_static_property_tp() noexcept; extern type_data *nb_type_c2p(nb_internals *internals, - const std::type_info *type); + const shim::type_info *type); extern void nb_type_unregister(type_data *t) noexcept; extern PyObject *call_one_arg(PyObject *fn, PyObject *arg) noexcept; diff --git a/src/nb_ndarray.cpp b/src/nb_ndarray.cpp index f61336d1..c23570c9 100644 --- a/src/nb_ndarray.cpp +++ b/src/nb_ndarray.cpp @@ -551,7 +551,7 @@ ndarray_handle *ndarray_import(PyObject *src, const ndarray_config *c, // Try the function to_dlpack(), already obsolete in array API v2021 if (!mt_unique_ptr && !capsule.is_valid()) { PyTypeObject *tp = Py_TYPE(src); - try { + if (true) { const char *module_name = borrow(handle(tp).attr("__module__")).c_str(); @@ -565,7 +565,7 @@ ndarray_handle *ndarray_import(PyObject *src, const ndarray_config *c, if (package.is_valid()) capsule = package.attr("to_dlpack")(handle(src)); - } catch (...) { + } if (false) { capsule.reset(); } if (!capsule.is_valid()) @@ -719,7 +719,7 @@ ndarray_handle *ndarray_import(PyObject *src, const ndarray_config *c, } object converted; - try { + if (true) { if (strncmp(module_name, "numpy", 5) == 0 || strncmp(module_name, "cupy", 4) == 0) { converted = handle(src).attr("astype")(dtype, order); @@ -734,7 +734,7 @@ ndarray_handle *ndarray_import(PyObject *src, const ndarray_config *c, } else if (strncmp(module_name, "jaxlib", 6) == 0) { converted = handle(src).attr("astype")(dtype); } - } catch (...) { converted.reset(); } + } if (false) { converted.reset(); } // Potentially try once again, recursively if (converted.is_valid()) { @@ -994,7 +994,7 @@ PyObject *ndarray_export(ndarray_handle *th, int framework, } if (framework == numpy::value) { - try { + if (true) { PyObject* pkg_mod = module_import("numpy"); PyObject* args[] = {pkg_mod, o.ptr(), (copy) ? Py_True : Py_False}; @@ -1002,7 +1002,7 @@ PyObject *ndarray_export(ndarray_handle *th, int framework, return PyObject_VectorcallMethod( static_pyobjects[pyobj_name::array_str], args, nargsf, static_pyobjects[pyobj_name::copy_tpl]); - } catch (const std::exception &e) { + } if (false) { shim::exception_placeholder e; PyErr_Format(PyExc_TypeError, "could not export nanobind::ndarray: %s", e.what()); @@ -1010,7 +1010,7 @@ PyObject *ndarray_export(ndarray_handle *th, int framework, } } - try { + if (true) { const char* pkg_name; switch (framework) { case pytorch::value: @@ -1038,7 +1038,7 @@ PyObject *ndarray_export(ndarray_handle *th, int framework, static_pyobjects[pyobj_name::from_dlpack_str], args, nargsf, nullptr)); } - } catch (const std::exception &e) { + } if (false) { shim::exception_placeholder e; PyErr_Format(PyExc_TypeError, "could not export nanobind::ndarray: %s", e.what()); @@ -1050,9 +1050,9 @@ PyObject *ndarray_export(ndarray_handle *th, int framework, if (framework == pytorch::value) copy_function_name = static_pyobjects[pyobj_name::clone_str]; - try { + if (true) { o = o.attr(copy_function_name)(); - } catch (std::exception &e) { + } if (false) { shim::exception_placeholder e; PyErr_Format(PyExc_RuntimeError, "copying nanobind::ndarray failed: %s", e.what()); diff --git a/src/nb_type.cpp b/src/nb_type.cpp index 8a0fc301..2466498b 100644 --- a/src/nb_type.cpp +++ b/src/nb_type.cpp @@ -340,7 +340,7 @@ static void inst_dealloc(PyObject *self) { type_data *nb_type_c2p(nb_internals *internals_, - const std::type_info *type) { + const shim::type_info *type) { #if defined(NB_FREE_THREADED) thread_local nb_type_map_fast type_c2p_fast; #else @@ -701,10 +701,10 @@ void *type_get_slot(PyTypeObject *t, int slot_id) { #endif static PyObject *nb_type_from_metaclass(PyTypeObject *meta, PyObject *mod, - PyType_Spec *spec) { + PyType_Spec *spec, PyObject *bases = nullptr) { #if NB_TYPE_FROM_METACLASS_IMPL == 0 // Life is good, PyType_FromMetaclass() is available - return PyType_FromMetaclass(meta, mod, spec, nullptr); + return PyType_FromMetaclass(meta, mod, spec, bases); #else /* The fallback code below emulates PyType_FromMetaclass() on Python prior to version 3.12. It requires access to CPython-internal structures, which @@ -1051,6 +1051,41 @@ static PyMethodDef class_getitem_method[] = { { nullptr } }; +/// Call __init__subclass__ of the parent class. This function assumes that the +/// passed type object has a parent class. +/// Copied and adapted from the following function in CPython +/// https://github.com/python/cpython/blob/89c220b93c06059f623e2d232bd54f49be1be22d/Objects/typeobject.c#L11848 +static void call_init_subclass(PyObject *type) { + // Call super(type, type) which returns a proxy for the parent class + PyObject *super_args[2] = {type, type}; + PyObject *super = PyObject_Vectorcall((PyObject *)&PySuper_Type, super_args, 2, NULL); + + // Try and get the `__init_subclass__` function from the base class + PyObject *func = PyObject_GetAttrString(super, "__init_subclass__"); + Py_DECREF(super); + if (func == NULL) + return; + + // The base class might be written in C and not implement the vectorcall + // protocol. Use the PyObject_Call which is guaranteed to always work. + // This calls the base class __init_subclass__ with empty args and kwargs. + PyObject *args = PyTuple_New(0); + PyObject *kwargs = PyDict_New(); + assert(args != NULL); + assert(kwargs != NULL); + + PyObject *result = PyObject_Call(func, args, kwargs); + Py_DECREF(func); + Py_DECREF(args); + Py_DECREF(kwargs); + if (result == NULL) { + PyErr_Print(); + abort(); + } + + Py_DECREF(result); +} + /// Called when a C++ type is bound via nb::class_<> PyObject *nb_type_new(const type_init_data *t) noexcept { bool has_doc = t->flags & (uint32_t) type_init_flags::has_doc, @@ -1075,7 +1110,7 @@ PyObject *nb_type_new(const type_init_data *t) noexcept { object modname; PyObject *mod = nullptr; - // Update hash table that maps from std::type_info to Python type + // Update hash table that maps from shim::type_info to Python type nb_type_map_slow::iterator it; bool success; nb_internals *internals_ = internals; @@ -1138,10 +1173,6 @@ PyObject *nb_type_new(const type_init_data *t) noexcept { generic_base = true; } #endif - - check(nb_type_check(base), - "nanobind::detail::nb_type_new(\"%s\"): base type is not a " - "nanobind type!", t_name); } else if (has_base) { lock_internals guard(internals_); nb_type_map_slow::iterator it2 = internals_->type_c2p_slow.find(t->base); @@ -1152,7 +1183,7 @@ PyObject *nb_type_new(const type_init_data *t) noexcept { } type_data *tb = nullptr; - if (base) { + if (base != nullptr && nb_type_check(base)) { // Check if the base type already has dynamic attributes tb = nb_type_data((PyTypeObject *) base); if (tb->flags & (uint32_t) type_flags::has_dynamic_attr) @@ -1316,7 +1347,7 @@ PyObject *nb_type_new(const type_init_data *t) noexcept { PyTypeObject *metaclass = nb_type_tp(has_supplement ? t->supplement : 0); - PyObject *result = nb_type_from_metaclass(metaclass, mod, &spec); + PyObject *result = nb_type_from_metaclass(metaclass, mod, &spec, base); if (!result) { python_error err; check(false, @@ -1394,6 +1425,10 @@ PyObject *nb_type_new(const type_init_data *t) noexcept { free((char *) t_name); } + if (base != nullptr) { + call_init_subclass(result); + } + #if !defined(PYPY_VERSION) if (generic_base) setattr(result, "__orig_bases__", make_tuple(handle(t->base_py))); @@ -1411,13 +1446,13 @@ PyObject *call_one_arg(PyObject *fn, PyObject *arg) noexcept { /// Encapsulates the implicit conversion part of nb_type_get() static NB_NOINLINE bool nb_type_get_implicit(PyObject *src, - const std::type_info *cpp_type_src, + const shim::type_info *cpp_type_src, const type_data *dst_type, nb_internals *internals_, cleanup_list *cleanup, void **out) noexcept { if (dst_type->implicit.cpp && cpp_type_src) { - const std::type_info **it = dst_type->implicit.cpp; - const std::type_info *v; + const shim::type_info **it = dst_type->implicit.cpp; + const shim::type_info *v; while ((v = *it++)) { if (v == cpp_type_src || *v == *cpp_type_src) @@ -1477,7 +1512,7 @@ static NB_NOINLINE bool nb_type_get_implicit(PyObject *src, } // Attempt to retrieve a pointer to a C++ instance -bool nb_type_get(const std::type_info *cpp_type, PyObject *src, uint8_t flags, +bool nb_type_get(const shim::type_info *cpp_type, PyObject *src, uint8_t flags, cleanup_list *cleanup, void **out) noexcept { // Convert None -> nullptr if (src == Py_None) { @@ -1486,7 +1521,7 @@ bool nb_type_get(const std::type_info *cpp_type, PyObject *src, uint8_t flags, } PyTypeObject *src_type = Py_TYPE(src); - const std::type_info *cpp_type_src = nullptr; + const shim::type_info *cpp_type_src = nullptr; const bool src_is_nb_type = nb_type_check((PyObject *) src_type); type_data *dst_type = nullptr; @@ -1682,9 +1717,9 @@ static PyObject *nb_type_put_common(void *value, type_data *t, rv_policy rvp, if (rvp == rv_policy::move) { if (t->flags & (uint32_t) type_flags::is_move_constructible) { if (t->flags & (uint32_t) type_flags::has_move) { - try { + if (true) { t->move(new_value, value); - } catch (...) { + } if (false) { Py_DECREF(inst); return nullptr; } @@ -1708,9 +1743,9 @@ static PyObject *nb_type_put_common(void *value, type_data *t, rv_policy rvp, "an instance that is not copy-constructible!", t->name); if (t->flags & (uint32_t) type_flags::has_copy) { - try { + if (true) { t->copy(new_value, value); - } catch (...) { + } if (false) { Py_DECREF(inst); return nullptr; } @@ -1747,7 +1782,7 @@ static PyObject *nb_type_put_common(void *value, type_data *t, rv_policy rvp, return (PyObject *) inst; } -PyObject *nb_type_put(const std::type_info *cpp_type, +PyObject *nb_type_put(const shim::type_info *cpp_type, void *value, rv_policy rvp, cleanup_list *cleanup, bool *is_new) noexcept { @@ -1822,8 +1857,8 @@ PyObject *nb_type_put(const std::type_info *cpp_type, return nb_type_put_common(value, td, rvp, cleanup, is_new); } -PyObject *nb_type_put_p(const std::type_info *cpp_type, - const std::type_info *cpp_type_p, +PyObject *nb_type_put_p(const shim::type_info *cpp_type, + const shim::type_info *cpp_type_p, void *value, rv_policy rvp, cleanup_list *cleanup, bool *is_new) noexcept { @@ -1876,7 +1911,7 @@ PyObject *nb_type_put_p(const std::type_info *cpp_type, while (true) { PyTypeObject *tp = Py_TYPE(seq.inst); - const std::type_info *p = nb_type_data(tp)->type; + const shim::type_info *p = nb_type_data(tp)->type; if (p == cpp_type || p == cpp_type_p) { if (nb_try_inc_ref(seq.inst)) @@ -1910,7 +1945,7 @@ PyObject *nb_type_put_p(const std::type_info *cpp_type, } static void nb_type_put_unique_finalize(PyObject *o, - const std::type_info *cpp_type, + const shim::type_info *cpp_type, bool cpp_delete, bool is_new) { (void) cpp_type; check(cpp_delete || !is_new, @@ -1941,7 +1976,7 @@ static void nb_type_put_unique_finalize(PyObject *o, } } -PyObject *nb_type_put_unique(const std::type_info *cpp_type, +PyObject *nb_type_put_unique(const shim::type_info *cpp_type, void *value, cleanup_list *cleanup, bool cpp_delete) noexcept { rv_policy policy = cpp_delete ? rv_policy::take_ownership : rv_policy::none; @@ -1955,8 +1990,8 @@ PyObject *nb_type_put_unique(const std::type_info *cpp_type, return o; } -PyObject *nb_type_put_unique_p(const std::type_info *cpp_type, - const std::type_info *cpp_type_p, +PyObject *nb_type_put_unique_p(const shim::type_info *cpp_type, + const shim::type_info *cpp_type_p, void *value, cleanup_list *cleanup, bool cpp_delete) noexcept { rv_policy policy = cpp_delete ? rv_policy::take_ownership : rv_policy::none; @@ -2034,7 +2069,7 @@ void nb_type_restore_ownership(PyObject *o, bool cpp_delete) noexcept { } } -bool nb_type_isinstance(PyObject *o, const std::type_info *t) noexcept { +bool nb_type_isinstance(PyObject *o, const shim::type_info *t) noexcept { type_data *d = nb_type_c2p(internals, t); if (d) return PyType_IsSubtype(Py_TYPE(o), d->type_py); @@ -2042,7 +2077,7 @@ bool nb_type_isinstance(PyObject *o, const std::type_info *t) noexcept { return false; } -PyObject *nb_type_lookup(const std::type_info *t) noexcept { +PyObject *nb_type_lookup(const shim::type_info *t) noexcept { type_data *d = nb_type_c2p(internals, t); if (d) return (PyObject *) d->type_py; @@ -2065,7 +2100,7 @@ size_t nb_type_align(PyObject *t) noexcept { return nb_type_data((PyTypeObject *) t)->align; } -const std::type_info *nb_type_info(PyObject *t) noexcept { +const shim::type_info *nb_type_info(PyObject *t) noexcept { return nb_type_data((PyTypeObject *) t)->type; } diff --git a/standalone/CMakeLists.txt b/standalone/CMakeLists.txt new file mode 100644 index 00000000..c36a5c67 --- /dev/null +++ b/standalone/CMakeLists.txt @@ -0,0 +1,46 @@ +# This is a custom CMakeLists that allows building the nanobind .cpp files into +# the libnanobind.so library. This allows building and packaging nanobind in +# the "traditional" way of linux packages (with libnanobind.so in /usr/lib and +# headers in /usr/include). It also avoid having to import nanobind's cmake in +# the downstream project, all it's needed is to build the python module and +# link it against libnanobind. + +cmake_minimum_required(VERSION 3.15) +project(nanobind-standlone) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-function") + +find_package(Python 3 REQUIRED COMPONENTS Interpreter Development) +include(../cmake/nanobind-config.cmake) +nanobind_build_library("nanobind" NOSTRIP NOLTO) +target_compile_definitions(nanobind PRIVATE -DNB_BUILD) +target_compile_definitions(nanobind PUBLIC -DNB_SHARED) +add_custom_target(build-nanobind ALL DEPENDS nanobind) +install(TARGETS nanobind LIBRARY) + +file(RELATIVE_PATH PYTHON_INSTALL_PATH "${CMAKE_INSTALL_PREFIX}" + "${Python_SITELIB}") + +file(GLOB PYTHON_FILES "${CMAKE_CURRENT_SOURCE_DIR}/../src/*.py") +install( + FILES ${PYTHON_FILES} + DESTINATION "${CMAKE_INSTALL_PREFIX}/${PYTHON_INSTALL_PATH}/nanobind") + +file( + GLOB_RECURSE HEADER_FILES + RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/../include" + LIST_DIRECTORIES false + "${CMAKE_CURRENT_SOURCE_DIR}/../include/*.h") + +foreach(HEADER_FILE ${HEADER_FILES}) + get_filename_component(HEADER_DIR "${HEADER_FILE}" DIRECTORY) + install( + FILES "${CMAKE_CURRENT_SOURCE_DIR}/../include/${HEADER_FILE}" + DESTINATION "${CMAKE_INSTALL_PREFIX}/include/${HEADER_DIR}") +endforeach()