Skip to content

Commit e9fb244

Browse files
committed
Relaxed ABI tagging
This commit incorporates some of the suggestions from pybind11 PR pybind/pybind11#4953 (and linked discussion) to relax ABI tagging for libstdc++ and MSVC. The goal is to ensure that ABI-incompatible extensions are separated from each other. These checks were previously too fine-grained and caused isolation in cases where an actual ABI incompatibility was not present. This is a long-standing problem in both pybind11 and nanobind causing inconvenience for users. While looking into this topic, I realized that libc++ has an opt-in unstable ABI feature (https://libcxx.llvm.org/DesignDocs/ABIVersioning.html). The PR also adds checks for this. Merging this PR will imply an ABI version bump for nanobind due to the different naming convention of the associated ABI tag.
1 parent 0272db4 commit e9fb244

File tree

5 files changed

+73
-15
lines changed

5 files changed

+73
-15
lines changed

.github/workflows/ci.yml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,23 @@ jobs:
5555
5656
- name: Configure
5757
run: >
58-
cmake -S . -B build -DNB_TEST_STABLE_ABI=ON -DNB_TEST_SHARED_BUILD="$(python3 -c 'import sys; print(int(sys.version_info.minor>=11))')"
58+
cmake -S . -B build -DNB_TEST_STABLE_ABI=ON -DNB_TEST_SHARED_BUILD="$(python -c 'import sys; print(int(sys.version_info.minor>=11))')"
5959
6060
- name: Build C++
6161
run: cmake --build build -j 2
6262

63+
- name: Check ABI tag
64+
if: ${{ !startsWith(matrix.os, 'windows') }}
65+
run: >
66+
cd build/tests;
67+
python -c 'import test_functions_ext as t; print(f"ABI tag is \"{ t.abi_tag() }\"")'
68+
69+
- name: Check ABI tag
70+
if: ${{ startsWith(matrix.os, 'windows') }}
71+
run: >
72+
cd build/tests/Debug;
73+
python -c 'import test_functions_ext as t; print(f"ABI tag is \"{ t.abi_tag() }\"")'
74+
6375
- name: Run tests
6476
run: >
6577
cd build;
@@ -85,6 +97,11 @@ jobs:
8597
- name: Build C++
8698
run: cmake --build build -j 2
8799

100+
- name: Check ABI tag
101+
run: >
102+
cd build/tests;
103+
python3 -c 'import test_functions_ext as t; print(f"ABI tag is \"{ t.abi_tag() }\"")'
104+
88105
- name: Run tests
89106
run: >
90107
cd build;
@@ -134,6 +151,11 @@ jobs:
134151
- name: Build C++
135152
run: cmake --build build -j 2
136153

154+
- name: Check ABI tag
155+
run: >
156+
cd build/tests;
157+
python -c 'import test_functions_ext as t; print(f"ABI tag is \"{ t.abi_tag() }\"")'
158+
137159
- name: Run tests
138160
run: >
139161
cd build;
@@ -165,7 +187,13 @@ jobs:
165187
cmake -S . -B build -DNB_TEST_FREE_THREADED=ON
166188
167189
- name: Build C++
168-
run: cmake --build build -j 2
190+
run: >
191+
cmake --build build -j 2
192+
193+
- name: Check ABI tag
194+
run: >
195+
cd build/tests;
196+
python -c 'import test_functions_ext as t; print(f"ABI tag is \"{ t.abi_tag() }\"")'
169197
170198
- name: Run tests
171199
run: >

include/nanobind/nb_lib.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,8 @@ NB_CORE void *type_get_slot(PyTypeObject *t, int slot_id);
557557

558558
NB_CORE PyObject *dict_get_item_ref_or_fail(PyObject *d, PyObject *k);
559559

560+
NB_CORE const char *abi_tag();
561+
560562
NAMESPACE_END(detail)
561563

562564
using detail::raise;

src/nb_internals.cpp

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
/// Tracks the ABI of nanobind
2020
#ifndef NB_INTERNALS_VERSION
21-
# define NB_INTERNALS_VERSION 15
21+
# define NB_INTERNALS_VERSION 16
2222
#endif
2323

2424
/// On MSVC, debug and release builds are not ABI-compatible!
@@ -49,30 +49,52 @@
4949

5050
/// Also standard libs
5151
#if defined(_LIBCPP_VERSION)
52-
# define NB_STDLIB "_libcpp"
53-
#elif defined(__GLIBCXX__) || defined(__GLIBCPP__)
54-
# define NB_STDLIB "_libstdcpp"
52+
# define NB_STDLIB "_libc++"
53+
#elif defined(__GLIBCXX__)
54+
# define NB_STDLIB "_libstdc++"
5555
#else
5656
# define NB_STDLIB ""
5757
#endif
5858

59-
/// On Linux/OSX, changes in __GXX_ABI_VERSION__ indicate ABI incompatibility.
60-
/// Also keep potentially ABI-incompatible visual studio builds apart.
61-
#if defined(__GXX_ABI_VERSION)
62-
# define NB_BUILD_ABI "_cxxabi" NB_TOSTRING(__GXX_ABI_VERSION)
63-
#elif defined(_MSC_VER)
64-
# define NB_BUILD_ABI "_mscver" NB_TOSTRING(_MSC_VER)
59+
// Catch other conditions that imply ABI incompatibility
60+
// - MSVC builds with different CRT versions
61+
// - An anticipated MSVC ABI break ("vNext")
62+
// - Builds using libc++ with unstable ABIs
63+
// - Builds using libstdc++ with the legacy (pre-C++11) ABI
64+
#if defined(_MSC_VER)
65+
# if defined(_MT) && defined(_DLL) // catches /MD or /MDd
66+
# define NB_BUILD_LIB "_md"
67+
# elif defined(_MT)
68+
# define NB_BUILD_LIB "_mt" // catches /MT or /MTd
69+
# else
70+
# define NB_BUILD_LIB ""
71+
# endif
72+
# if (_MSC_VER) / 100 == 19
73+
# define NB_BUILD_ABI NB_BUILD_LIB "_19"
74+
# else
75+
# define NB_BUILD_ABI NB_BUILD_LIB "_unknown"
76+
# endif
77+
#elif defined(_LIBCPP_ABI_VERSION)
78+
# define NB_BUILD_ABI "_abi" NB_TOSTRING(_LIBCPP_ABI_VERSION)
79+
#elif defined(__GLIBCXX__)
80+
# if _GLIBCXX_USE_CXX11_ABI
81+
# define NB_BUILD_ABI ""
82+
# else
83+
# define NB_BUILD_ABI "_legacy"
84+
# endif
6585
#else
6686
# define NB_BUILD_ABI ""
6787
#endif
6888

69-
// Can have limited and non-limited-API extensions in the same process, and they might be incompatible
89+
// Can have limited and non-limited-API extensions in the same process.
90+
// Nanobind data structures will differ, so these can't talk to each other
7091
#if defined(Py_LIMITED_API)
7192
# define NB_STABLE_ABI "_stable"
7293
#else
7394
# define NB_STABLE_ABI ""
7495
#endif
7596

97+
// As above, but for free-threaded extensions
7698
#if defined(NB_FREE_THREADED)
7799
# define NB_FREE_THREADED_ABI "_ft"
78100
#else
@@ -85,7 +107,7 @@
85107
#define NB_VERSION_DEV_STR ""
86108
#endif
87109

88-
#define NB_INTERNALS_ID \
110+
#define NB_ABI_TAG \
89111
"v" NB_TOSTRING(NB_INTERNALS_VERSION) \
90112
NB_VERSION_DEV_STR NB_COMPILER_TYPE NB_STDLIB NB_BUILD_ABI \
91113
NB_BUILD_TYPE NB_STABLE_ABI NB_FREE_THREADED_ABI
@@ -241,6 +263,8 @@ static bool is_alive_value = false;
241263
static bool *is_alive_ptr = &is_alive_value;
242264
bool is_alive() noexcept { return *is_alive_ptr; }
243265

266+
const char *abi_tag() { return NB_ABI_TAG; }
267+
244268
static void internals_cleanup() {
245269
nb_internals *p = internals;
246270
if (!p)
@@ -382,7 +406,7 @@ NB_NOINLINE void init(const char *name) {
382406
check(dict, "nanobind::detail::init(): could not access internals dictionary!");
383407

384408
PyObject *key = PyUnicode_FromFormat("__nb_internals_%s_%s__",
385-
NB_INTERNALS_ID, name ? name : "");
409+
abi_tag(), name ? name : "");
386410
check(key, "nanobind::detail::init(): could not create dictionary key!");
387411

388412
PyObject *capsule = dict_get_item_ref_or_fail(dict, key);

tests/test_functions.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,4 +479,6 @@ NB_MODULE(test_functions_ext, m) {
479479
auto ret = std::move(example_policy::calls);
480480
return ret;
481481
});
482+
483+
m.def("abi_tag", [](){ return nb::detail::abi_tag(); });
482484
}

tests/test_functions_ext.pyi.ref

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import types
33
from typing import Annotated, Any, overload
44

55

6+
def abi_tag() -> str: ...
7+
68
def call_guard_value() -> int: ...
79

810
def call_policy_record() -> list[tuple[tuple, object]]: ...

0 commit comments

Comments
 (0)