Skip to content

Commit

Permalink
Merge branch 'master' into floatingpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
hpkfft authored Jan 10, 2025
2 parents 59e449e + efbeb8a commit 44a3dca
Show file tree
Hide file tree
Showing 21 changed files with 260 additions and 79 deletions.
12 changes: 0 additions & 12 deletions docs/api_bazel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,3 @@ following flag settings.
version. Allowed values are ``"cp312"``, ``"cp313"``, which target the
stable ABI starting from Python 3.12 or 3.13, respectively. By default, all
extensions are built without any ABI limitations.

.. py:function:: @nanobind_bazel//:free_threading (boolean)
Build nanobind extensions with a Python toolchain in free-threaded mode.
If given, the currently configured Python toolchain must support free-threading,
otherwise, the build will result in a compilation error.
Only relevant for CPython 3.13+, since support for free-threaded Python was
introduced in CPython 3.13.
For more information on free-threaded extension support in nanobind, refer to the
relevant :ref:`documentation section <free-threaded>`.

*New in nanobind-bazel version 2.2.0.*
21 changes: 20 additions & 1 deletion docs/api_core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3099,7 +3099,8 @@ Miscellaneous
from the signature. To make this explicit, use the ``nb::typed<T, Ts...>``
wrapper to pass additional type parameters. This has no effect besides
clarifying the signature---in particular, nanobind does *not* insert
additional runtime checks!
additional runtime checks! At runtime, a ``nb::typed<T, Ts...>`` behaves
exactly like a ``T``.

.. code-block:: cpp
Expand All @@ -3108,3 +3109,21 @@ Miscellaneous
// ...
}
});
``nb::typed<nb::object, T>`` and ``nb::typed<nb::handle, T>`` are
treated specially: they generate a signature that refers just to ``T``,
rather than to the nonsensical ``object[T]`` that would otherwise
be produced. This can be useful if you want to replace the type of
a parameter instead of augmenting it. Note that at runtime these
perform no checks at all, since ``nb::object`` and ``nb::handle``
can refer to any Python object.

To support callable types, you can specify a C++ function signature in
``nb::typed<nb::callable, Sig>`` and nanobind will attempt to convert
it to a Python callable signature.
``nb::typed<nb::callable, int(float, std::string)>`` becomes
``Callable[[float, str], int]``, while
``nb::typed<nb::callable, int(...)>`` becomes ``Callable[..., int]``.
Type checkers will verify that any callable passed for such an argument
has a compatible signature. (At runtime, any sort of callable object
will be accepted.)
30 changes: 27 additions & 3 deletions docs/bazel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ in your MODULE.bazel file:
# Place this in your MODULE.bazel file.
# The major version of nanobind-bazel is equal to the version
# of the internally used nanobind.
# In this case, we are building bindings with nanobind v2.2.0.
bazel_dep(name = "nanobind_bazel", version = "2.2.0")
# In this case, we are building bindings with nanobind v2.4.0.
bazel_dep(name = "nanobind_bazel", version = "2.4.0")
To instead use a development version from GitHub, you can declare the
dependency as a ``git_override()`` in your MODULE.bazel:
Expand Down Expand Up @@ -57,7 +57,7 @@ and then declare it as a ``local_path_override()`` dependency:
.. note::

At minimum, Bazel version 6.4.0 is required to use nanobind-bazel.
At minimum, Bazel version 7.0.0 is required to use nanobind-bazel.


.. _bazel-build:
Expand Down Expand Up @@ -139,6 +139,30 @@ Naturally, since stub generation relies on the given shared object files, the
actual extensions are built in the process before invocation of the stub
generation script.

Building extensions for free-threaded Python
--------------------------------------------

Starting from CPython 3.13, bindings extensions can be built for a free-threaded
CPython interpreter. This requires two things: First, an eligible toolchain must
be defined in your MODULE.bazel file, e.g. like so:

.. code-block:: python
bazel_dep(name = "rules_python", version = "1.0.0")
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(python_version = "3.13")
And secondly, the ``@rules_python//python/config_settings:py_freethreaded`` flag must
be set to "yes" when building your nanobind extension target, e.g. as
``bazel build //path/to:my_ext --@rules_python//python/config_settings:py_freethreaded=yes``.

Then, ``rules_python`` will bootstrap a free-threaded version of your target interpreter,
and ``nanobind_bazel`` will define the ``NB_FREE_THREADED`` macro for the libnanobind
build, indicating that nanobind should be built with free-threading support.
For a comprehensive overview on nanobind with free-threaded Python, refer to the
:ref:`free-threading documentation <free-threaded>`.

nanobind-bazel and Python packaging
-----------------------------------

Expand Down
32 changes: 30 additions & 2 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,37 @@ case, both modules must use the same nanobind ABI version, or they will be
isolated from each other. Releases that don't explicitly mention an ABI version
below inherit that of the preceding release.

Version 2.5.0 ( tbd , 2025)
---------------------------
Version TBD (not yet released)
------------------------------

- Added some special forms for :cpp:class:`nb::typed\<T, Ts...\> <typed>`:

- ``nb::typed<nb::object, T>`` or ``nb::typed<nb::handle, T>`` produces
a parameter or return value that will be described like ``T`` in function
signatures but accepts any Python object at runtime.

- ``nb::typed<nb::callable, R(Args...)>`` produces a Python callable signature
``Callable[[Args...], R]``; similarly, ``nb::typed<nb::callable, R(...)>``
(with a literal ellipsis) produces the Python ``Callable[..., R]``.

(PR `#835 <https://github.com/wjakob/nanobind/pull/835>`__).

- Fixed the :cpp:class:`nb::int_ <int_>` constructor so that it casts to
an integer when invoked with a floating point argument.

- Fixed (benign) reference leads that could occur when ``std::shared_ptr<T>``
instances were still alive at interpreter shutdown time. (commit `fb8157
<https://github.com/wjakob/nanobind/commit/fb815762fdb8476cfd293e3717ca41c8bb890437>`__).

- Fixed a race condition in free-threaded extensions that could occur when
:cpp:func:`nb::make_iterator <make_iterator>` concurrently used by
multiple threads (PR `#832 <https://github.com/wjakob/nanobind/pull/832>`__).

- Removed double-checked locking patterns in accesses to internal data
structures to ensure correct free-threaded behavior on architectures with
weak memory ordering (specifically, ARM). (PR `#819
<https://github.com/wjakob/nanobind/pull/819>`__).

- The floating-point type_caster now only performs value-changing narrowing
conversions if the convert flag is set.
(PR `#829 <https://github.com/wjakob/nanobind/pull/829>`__)
Expand Down
18 changes: 18 additions & 0 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,24 @@ named domain to avoid conflicts with other extensions. To do so, specify the
In this case, inter-extension type visibility is furthermore restricted to
extensions in the ``"my_project"`` domain.

Can I use nanobind without RTTI or C++ exceptions?
--------------------------------------------------

Certain environments (e.g., `Google-internal development
<https://google.github.io/styleguide/cppguide.html>`__, embedded devices, etc.)
require compilation without C++ runtime type information (``-fno-rtti``) and
exceptions (``-fno-exceptions``).

nanobind requires both of these features and cannot be used when they are not
available. RTTI provides the central index to look up types of bindings.
Exceptions are needed because Python relies on exceptions that must be
converted into something equivalent on the C++ side. PRs that refactor nanobind
to work without RTTI or exceptions will not be accepted.

For Googlers: there is already an exemption from the internal rules that
specifically permits the use of RTTI/exceptions when a project relies on
pybind11. Likely, this exemption could be extended to include nanobind as well.

I'd like to use this project, but with $BUILD_SYSTEM instead of CMake
---------------------------------------------------------------------

Expand Down
25 changes: 24 additions & 1 deletion docs/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,30 @@ subclasses the type ``T`` and can be used interchangeably with ``T``. The other
arguments (``Ts...``) are used to generate a Python type signature but have no
other effect (for example, parameterizing by ``str`` on the Python end can
alternatively be achieved by passing ``nb::str``, ``std::string``, or ``const
char*`` as part of the ``Ts..`` parameter pack).
char*`` as part of the ``Ts...`` parameter pack).

There are two special forms of ``nb::typed<T, Ts...>`` that will be rendered
as something other than ``T[Ts...]``:

* In some cases, a function may wish to accept or return an arbitrary
Python object, but generate signatures that describe it as some more
specific type ``T``. The types ``nb::typed<nb::object, T>`` and
``nb::typed<nb::handle, T>`` will be rendered as ``T`` rather than
as the nonsensical ``object[T]`` that they would be without this rule.
(If you want nanobind to check that an argument is actually of type ``T``,
while still giving you a generic Python object to work with,
then use :cpp:class:`nb::handle_t\<T\> <handle_t>` instead.)

* Type parameters for ``nb::callable`` can be provided using a C++ function
signature, since there would otherwise be no way to express the nested
brackets used in Python callable signatures. In order to express the Python type
``Callable[[str, float], int]``, which is a function taking two parameters
(string and float) and returning an integer, you might write
``nb::typed<nb::callable, int(nb::str, float)>``. For a callable type
that accepts any arguments, like ``Callable[..., int]``, use a C-style
variadic function signature: ``nb::typed<nb::callable, int(...)>``.
(The latter could also be written without this special support, as
``nb::typed<nb::callable, nb::ellipsis, int>``.)

.. _typing_generics_creating:

Expand Down
43 changes: 23 additions & 20 deletions include/nanobind/make_iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,27 +71,30 @@ typed<iterator, ValueType> make_iterator_impl(handle scope, const char *name,
"make_iterator_impl(): the generated __next__ would copy elements, so the "
"element type must be copy-constructible");

if (!type<State>().is_valid()) {
class_<State>(scope, name)
.def("__iter__", [](handle h) { return h; })
.def("__next__",
[](State &s) -> ValueType {
if (!s.first_or_done)
++s.it;
else
s.first_or_done = false;

if (s.it == s.end) {
s.first_or_done = true;
throw stop_iteration();
}

return Access()(s.it);
},
std::forward<Extra>(extra)...,
Policy);
{
static ft_mutex mu;
ft_lock_guard lock(mu);
if (!type<State>().is_valid()) {
class_<State>(scope, name)
.def("__iter__", [](handle h) { return h; })
.def("__next__",
[](State &s) -> ValueType {
if (!s.first_or_done)
++s.it;
else
s.first_or_done = false;

if (s.it == s.end) {
s.first_or_done = true;
throw stop_iteration();
}

return Access()(s.it);
},
std::forward<Extra>(extra)...,
Policy);
}
}

return borrow<typed<iterator, ValueType>>(cast(State{
std::forward<Iterator>(first), std::forward<Sentinel>(last), true }));
}
Expand Down
44 changes: 39 additions & 5 deletions include/nanobind/nb_cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,13 +335,13 @@ template <typename T> struct type_caster<pointer_and_handle<T>> {
}
};

template <typename T> struct typed_name {
template <typename T> struct typed_base_name {
static constexpr auto Name = type_caster<T>::Name;
};

#if PY_VERSION_HEX < 0x03090000
#define NB_TYPED_NAME_PYTHON38(type, name) \
template <> struct typed_name<type> { \
template <> struct typed_base_name<type> { \
static constexpr auto Name = detail::const_name(name); \
};

Expand All @@ -352,13 +352,47 @@ NB_TYPED_NAME_PYTHON38(dict, NB_TYPING_DICT)
NB_TYPED_NAME_PYTHON38(type_object, NB_TYPING_TYPE)
#endif

// Base case: typed<T, Ts...> renders as T[Ts...], with some adjustments to
// T for older versions of Python (typing.List instead of list, for example)
template <typename T, typename... Ts> struct typed_name {
static constexpr auto Name =
typed_base_name<intrinsic_t<T>>::Name + const_name("[") +
concat(const_name<std::is_same_v<Ts, ellipsis>>(const_name("..."),
make_caster<Ts>::Name)...) + const_name("]");
};

// typed<object, T> or typed<handle, T> renders as T, rather than as
// the nonsensical object[T]
template <typename T> struct typed_name<object, T> {
static constexpr auto Name = make_caster<T>::Name;
};
template <typename T> struct typed_name<handle, T> {
static constexpr auto Name = make_caster<T>::Name;
};

// typed<callable, R(Args...)> renders as Callable[[Args...], R]
template <typename R, typename... Args>
struct typed_name<callable, R(Args...)> {
using Ret = std::conditional_t<std::is_void_v<R>, void_type, R>;
static constexpr auto Name =
const_name(NB_TYPING_CALLABLE "[[") +
concat(make_caster<Args>::Name...) + const_name("], ") +
make_caster<Ret>::Name + const_name("]");
};
// typed<callable, R(...)> renders as Callable[..., R]
template <typename R>
struct typed_name<callable, R(...)> {
using Ret = std::conditional_t<std::is_void_v<R>, void_type, R>;
static constexpr auto Name =
const_name(NB_TYPING_CALLABLE "[..., ") +
make_caster<Ret>::Name + const_name("]");
};

template <typename T, typename... Ts> struct type_caster<typed<T, Ts...>> {
using Caster = make_caster<T>;
using Typed = typed<T, Ts...>;

NB_TYPE_CASTER(Typed, typed_name<intrinsic_t<T>>::Name + const_name("[") +
concat(const_name<std::is_same_v<Ts, ellipsis>>(const_name("..."),
make_caster<Ts>::Name)...) + const_name("]"))
NB_TYPE_CASTER(Typed, (typed_name<T, Ts...>::Name))

bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
Caster caster;
Expand Down
2 changes: 1 addition & 1 deletion include/nanobind/nb_lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ NB_CORE PyObject *nb_type_put_unique_p(const std::type_info *cpp_type,
void *value, cleanup_list *cleanup,
bool cpp_delete) noexcept;

/// Try to reliquish ownership from Python object to a unique_ptr;
/// Try to relinquish ownership from Python object to a unique_ptr;
/// return true if successful, false if not. (Failure is only
/// possible if `cpp_delete` is true.)
NB_CORE bool nb_type_relinquish_ownership(PyObject *o, bool cpp_delete) noexcept;
Expand Down
2 changes: 1 addition & 1 deletion include/nanobind/stl/shared_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ NAMESPACE_BEGIN(detail)
struct py_deleter {
void operator()(void *) noexcept {
// Don't run the deleter if the interpreter has been shut down
if (!Py_IsInitialized())
if (!is_alive())
return;
gil_scoped_acquire guard;
Py_DECREF(o);
Expand Down
5 changes: 5 additions & 0 deletions src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
if sys.version_info < (3, 8):
raise ImportError("nanobind does not support Python < 3.8.")

def source_dir() -> str:
"Return the path to the nanobind source directory."
return os.path.join(os.path.abspath(os.path.dirname(__file__)), "src")

def include_dir() -> str:
"Return the path to the nanobind include directory"
return os.path.join(os.path.abspath(os.path.dirname(__file__)), "include")
Expand All @@ -16,6 +20,7 @@ def cmake_dir() -> str:

__all__ = (
"__version__",
"source_dir",
"include_dir",
"cmake_dir",
)
2 changes: 1 addition & 1 deletion src/nb_func.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ PyObject *nb_func_new(const void *in_) noexcept {
// skips Python's usual logic where __init__ is always called
// if __new__ returns an instance of the type.
bool noargs_ok = true;
for (size_t i = 1; i < fc->nargs - has_var_kwargs; ++i) {
for (uint32_t i = 1; i < fc->nargs - (uint32_t) has_var_kwargs; ++i) {
if (has_var_args && i == fc->nargs_pos)
continue; // skip `nb::args` since it can be empty
if (has_args && fc->args[i].value != nullptr)
Expand Down
9 changes: 8 additions & 1 deletion src/nb_ndarray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,14 @@ ndarray_handle *ndarray_import(PyObject *o, const ndarray_config *c,
if (!t.strides) {
/* When the provided tensor does not have a valid
strides field, it uses the C ordering convention */
pass_order = c_order || t.ndim == 1;
if (c_order) {
pass_order = true;
} else {
int nontrivial_dims = 0;
for (int i = 0; i < t.ndim; ++i)
nontrivial_dims += (int) (t.shape[i] > 1);
pass_order = nontrivial_dims <= 1;
}
} else {
if (c_order) {
for (int64_t i = t.ndim - 1, accum = 1; i >= 0; --i) {
Expand Down
Loading

0 comments on commit 44a3dca

Please sign in to comment.