diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b67bc95..7429928b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,11 +55,23 @@ jobs: - name: Configure run: > - cmake -S . -B build -DNB_TEST_STABLE_ABI=ON -DNB_TEST_SHARED_BUILD="$(python3 -c 'import sys; print(int(sys.version_info.minor>=11))')" + cmake -S . -B build -DNB_TEST_STABLE_ABI=ON -DNB_TEST_SHARED_BUILD="$(python -c 'import sys; print(int(sys.version_info.minor>=11))')" - name: Build C++ run: cmake --build build -j 2 + - name: Check ABI tag + if: ${{ !startsWith(matrix.os, 'windows') }} + run: > + cd build/tests; + python -c 'import test_functions_ext as t; print(f"ABI tag is \"{ t.abi_tag() }\"")' + + - name: Check ABI tag + if: ${{ startsWith(matrix.os, 'windows') }} + run: > + cd build/tests/Debug; + python -c 'import test_functions_ext as t; print(f"ABI tag is \"{ t.abi_tag() }\"")' + - name: Run tests run: > cd build; @@ -85,6 +97,11 @@ jobs: - name: Build C++ run: cmake --build build -j 2 + - name: Check ABI tag + run: > + cd build/tests; + python3 -c 'import test_functions_ext as t; print(f"ABI tag is \"{ t.abi_tag() }\"")' + - name: Run tests run: > cd build; @@ -134,6 +151,11 @@ jobs: - name: Build C++ run: cmake --build build -j 2 + - name: Check ABI tag + run: > + cd build/tests; + python -c 'import test_functions_ext as t; print(f"ABI tag is \"{ t.abi_tag() }\"")' + - name: Run tests run: > cd build; @@ -165,7 +187,13 @@ jobs: cmake -S . -B build -DNB_TEST_FREE_THREADED=ON - name: Build C++ - run: cmake --build build -j 2 + run: > + cmake --build build -j 2 + + - name: Check ABI tag + run: > + cd build/tests; + python -c 'import test_functions_ext as t; print(f"ABI tag is \"{ t.abi_tag() }\"")' - name: Run tests run: > diff --git a/include/nanobind/nb_lib.h b/include/nanobind/nb_lib.h index 7ea1703c..b02f10fd 100644 --- a/include/nanobind/nb_lib.h +++ b/include/nanobind/nb_lib.h @@ -557,6 +557,8 @@ NB_CORE void *type_get_slot(PyTypeObject *t, int slot_id); NB_CORE PyObject *dict_get_item_ref_or_fail(PyObject *d, PyObject *k); +NB_CORE const char *abi_tag(); + NAMESPACE_END(detail) using detail::raise; diff --git a/src/nb_abi.h b/src/nb_abi.h new file mode 100644 index 00000000..da704d99 --- /dev/null +++ b/src/nb_abi.h @@ -0,0 +1,102 @@ +/* + src/nb_abi.h: this file computes tags that are used to isolate extensions + from each other in the case of platform or nanobind-related ABI + incompatibilities. The file is included by ``nb_internals.cpp`` and should + not be used directly. + + The implementation of this file (specifically, the NB_PLATFORM_ABI_TAG) is + designed to be compatible with @rwgk's + https://github.com/pybind/pybind11/blob/master/include/pybind11/conduit/pybind11_platform_abi_id.h + + Use of this source code is governed by a BSD-style license that can be found + in the LICENSE file. +*/ + +/// Tracks the version of nanobind's internal data structures +#ifndef NB_INTERNALS_VERSION +# define NB_INTERNALS_VERSION 16 +#endif + +#if defined(__MINGW32__) +# define NB_COMPILER_TYPE "mingw" +#elif defined(__CYGWIN__) +# define NB_COMPILER_TYPE "gcc_cygwin" +#elif defined(_MSC_VER) +# define NB_COMPILER_TYPE "msvc" +#elif defined(__clang__) || defined(__GNUC__) +# define NB_COMPILER_TYPE "system" // Assumed compatible with system compiler. +#else +# error "Unknown compiler type. Please revise this code." +#endif + +// Catch other conditions that imply ABI incompatibility +// - MSVC builds with different CRT versions +// - An anticipated MSVC ABI break ("vNext") +// - Builds using libc++ with unstable ABIs +// - Builds using libstdc++ with the legacy (pre-C++11) ABI, etc. +#if defined(_MSC_VER) +# if defined(_MT) && defined(_DLL) // Corresponding to CL command line options /MD or /MDd. +# if (_MSC_VER) / 100 == 19 +# define NB_BUILD_ABI "_md_mscver19" +# else +# error "Unknown MSVC major version. Please revise this code." +# endif +# elif defined(_MT) // Corresponding to CL command line options /MT or /MTd. +# define NB_BUILD_ABI "_mt_mscver" NB_TOSTRING(_MSC_VER) +# else +# if (_MSC_VER) / 100 == 19 +# define NB_BUILD_ABI "_none_mscver19" +# else +# error "Unknown MSVC major version. Please revise this code." +# endif +# endif +#elif defined(_LIBCPP_ABI_VERSION) // https://libcxx.llvm.org/DesignDocs/ABIVersioning.html +# define NB_BUILD_ABI "_libcpp_abi" NB_TOSTRING(_LIBCPP_ABI_VERSION) +#elif defined(_GLIBCXX_USE_CXX11_ABI) +# if defined(__NVCOMPILER) && !defined(__GXX_ABI_VERSION) +# error "Unknown platform or compiler (_GLIBCXX_USE_CXX11_ABI). Please revise this code." +# endif +# if defined(__GXX_ABI_VERSION) && __GXX_ABI_VERSION < 1002 || __GXX_ABI_VERSION >= 2000 +# error "Unknown platform or compiler (__GXX_ABI_VERSION). Please revise this code." +# endif +# define NB_BUILD_ABI "_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_" NB_TOSTRING(_GLIBCXX_USE_CXX11_ABI) +#else +# error "Unknown platform or compiler. Please revise this code." +#endif + +// On MSVC, debug and release builds are not ABI-compatible! +#if defined(_MSC_VER) && defined(_DEBUG) +# define NB_BUILD_TYPE "_debug" +#else +# define NB_BUILD_TYPE "" +#endif + +// Tag to determine if inter-library C++ function can be safely dispatched +#define NB_PLATFORM_ABI_TAG \ + NB_COMPILER_TYPE NB_BUILD_ABI NB_BUILD_TYPE + +// Can have limited and non-limited-API extensions in the same process. +// Nanobind data structures will differ, so these can't talk to each other +#if defined(Py_LIMITED_API) +# define NB_STABLE_ABI "_stable" +#else +# define NB_STABLE_ABI "" +#endif + +// As above, but for free-threaded extensions +#if defined(NB_FREE_THREADED) +# define NB_FREE_THREADED_ABI "_ft" +#else +# define NB_FREE_THREADED_ABI "" +#endif + +#if NB_VERSION_DEV > 0 + #define NB_VERSION_DEV_STR "_dev" NB_TOSTRING(NB_VERSION_DEV) +#else + #define NB_VERSION_DEV_STR "" +#endif + +#define NB_ABI_TAG \ + "v" NB_TOSTRING(NB_INTERNALS_VERSION) \ + NB_VERSION_DEV_STR "_" NB_PLATFORM_ABI_TAG NB_STABLE_ABI \ + NB_FREE_THREADED_ABI diff --git a/src/nb_internals.cpp b/src/nb_internals.cpp index b4a3f1d9..2ccae09e 100644 --- a/src/nb_internals.cpp +++ b/src/nb_internals.cpp @@ -10,86 +10,13 @@ #include #include #include "nb_internals.h" +#include "nb_abi.h" #include #if defined(__GNUC__) && !defined(__clang__) # pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif -/// Tracks the ABI of nanobind -#ifndef NB_INTERNALS_VERSION -# define NB_INTERNALS_VERSION 15 -#endif - -/// On MSVC, debug and release builds are not ABI-compatible! -#if defined(_MSC_VER) && defined(_DEBUG) -# define NB_BUILD_TYPE "_debug" -#else -# define NB_BUILD_TYPE "" -#endif - -/// Let's assume that different compilers are ABI-incompatible. -#if defined(_MSC_VER) -# define NB_COMPILER_TYPE "_msvc" -#elif defined(__INTEL_COMPILER) -# define NB_COMPILER_TYPE "_icc" -#elif defined(__clang__) -# define NB_COMPILER_TYPE "_clang" -#elif defined(__PGI) -# define NB_COMPILER_TYPE "_pgi" -#elif defined(__MINGW32__) -# define NB_COMPILER_TYPE "_mingw" -#elif defined(__CYGWIN__) -# define NB_COMPILER_TYPE "_gcc_cygwin" -#elif defined(__GNUC__) -# define NB_COMPILER_TYPE "_gcc" -#else -# define NB_COMPILER_TYPE "_unknown" -#endif - -/// Also standard libs -#if defined(_LIBCPP_VERSION) -# define NB_STDLIB "_libcpp" -#elif defined(__GLIBCXX__) || defined(__GLIBCPP__) -# define NB_STDLIB "_libstdcpp" -#else -# define NB_STDLIB "" -#endif - -/// On Linux/OSX, changes in __GXX_ABI_VERSION__ indicate ABI incompatibility. -/// Also keep potentially ABI-incompatible visual studio builds apart. -#if defined(__GXX_ABI_VERSION) -# define NB_BUILD_ABI "_cxxabi" NB_TOSTRING(__GXX_ABI_VERSION) -#elif defined(_MSC_VER) -# define NB_BUILD_ABI "_mscver" NB_TOSTRING(_MSC_VER) -#else -# define NB_BUILD_ABI "" -#endif - -// Can have limited and non-limited-API extensions in the same process, and they might be incompatible -#if defined(Py_LIMITED_API) -# define NB_STABLE_ABI "_stable" -#else -# define NB_STABLE_ABI "" -#endif - -#if defined(NB_FREE_THREADED) -# define NB_FREE_THREADED_ABI "_ft" -#else -# define NB_FREE_THREADED_ABI "" -#endif - -#if NB_VERSION_DEV > 0 - #define NB_VERSION_DEV_STR "_dev" NB_TOSTRING(NB_VERSION_DEV) -#else - #define NB_VERSION_DEV_STR "" -#endif - -#define NB_INTERNALS_ID \ - "v" NB_TOSTRING(NB_INTERNALS_VERSION) \ - NB_VERSION_DEV_STR NB_COMPILER_TYPE NB_STDLIB NB_BUILD_ABI \ - NB_BUILD_TYPE NB_STABLE_ABI NB_FREE_THREADED_ABI - NAMESPACE_BEGIN(NB_NAMESPACE) NAMESPACE_BEGIN(detail) @@ -241,6 +168,8 @@ static bool is_alive_value = false; static bool *is_alive_ptr = &is_alive_value; bool is_alive() noexcept { return *is_alive_ptr; } +const char *abi_tag() { return NB_ABI_TAG; } + static void internals_cleanup() { nb_internals *p = internals; if (!p) @@ -382,7 +311,7 @@ NB_NOINLINE void init(const char *name) { check(dict, "nanobind::detail::init(): could not access internals dictionary!"); PyObject *key = PyUnicode_FromFormat("__nb_internals_%s_%s__", - NB_INTERNALS_ID, name ? name : ""); + abi_tag(), name ? name : ""); check(key, "nanobind::detail::init(): could not create dictionary key!"); PyObject *capsule = dict_get_item_ref_or_fail(dict, key); diff --git a/tests/test_functions.cpp b/tests/test_functions.cpp index 66718737..51136785 100644 --- a/tests/test_functions.cpp +++ b/tests/test_functions.cpp @@ -479,4 +479,6 @@ NB_MODULE(test_functions_ext, m) { auto ret = std::move(example_policy::calls); return ret; }); + + m.def("abi_tag", [](){ return nb::detail::abi_tag(); }); } diff --git a/tests/test_functions_ext.pyi.ref b/tests/test_functions_ext.pyi.ref index 773c3868..81ca9a6e 100644 --- a/tests/test_functions_ext.pyi.ref +++ b/tests/test_functions_ext.pyi.ref @@ -3,6 +3,8 @@ import types from typing import Annotated, Any, overload +def abi_tag() -> str: ... + def call_guard_value() -> int: ... def call_policy_record() -> list[tuple[tuple, object]]: ...