Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relaxed ABI tagging #778

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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: >
Expand Down
2 changes: 2 additions & 0 deletions include/nanobind/nb_lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
102 changes: 102 additions & 0 deletions src/nb_abi.h
Original file line number Diff line number Diff line change
@@ -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
79 changes: 4 additions & 75 deletions src/nb_internals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,86 +10,13 @@
#include <nanobind/nanobind.h>
#include <structmember.h>
#include "nb_internals.h"
#include "nb_abi.h"
#include <thread>

#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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions tests/test_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); });
}
2 changes: 2 additions & 0 deletions tests/test_functions_ext.pyi.ref
Original file line number Diff line number Diff line change
Expand Up @@ -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]]: ...
Expand Down
Loading