Skip to content

Commit 33c9383

Browse files
committed
moved free-threaded implementation details into a separate file
1 parent be0c7e6 commit 33c9383

9 files changed

+103
-77
lines changed

cmake/nanobind-config.cmake

+2
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ function (nanobind_build_library TARGET_NAME)
175175
${NB_DIR}/src/nb_enum.cpp
176176
${NB_DIR}/src/nb_ndarray.cpp
177177
${NB_DIR}/src/nb_static_property.cpp
178+
${NB_DIR}/src/nb_ft.h
179+
${NB_DIR}/src/nb_ft.cpp
178180
${NB_DIR}/src/common.cpp
179181
${NB_DIR}/src/error.cpp
180182
${NB_DIR}/src/trampoline.cpp

src/common.cpp

-16
Original file line numberDiff line numberDiff line change
@@ -1178,22 +1178,6 @@ bool issubclass(PyObject *a, PyObject *b) {
11781178

11791179
// ========================================================================
11801180

1181-
/// Make an object immortal when targeting free-threaded Python
1182-
void maybe_make_immortal(PyObject *op) {
1183-
#ifdef NB_FREE_THREADED
1184-
// See CPython's Objects/object.c
1185-
if (PyObject_IS_GC(op))
1186-
PyObject_GC_UnTrack(op);
1187-
op->ob_tid = _Py_UNOWNED_TID;
1188-
op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
1189-
op->ob_ref_shared = 0;
1190-
#else
1191-
(void) op;
1192-
#endif
1193-
}
1194-
1195-
// ========================================================================
1196-
11971181
PyObject *dict_get_item_ref_or_fail(PyObject *d, PyObject *k) {
11981182
PyObject *value;
11991183
bool error = false;

src/nb_combined.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
#include "nb_enum.cpp"
7979
#include "nb_ndarray.cpp"
8080
#include "nb_static_property.cpp"
81+
#include "nb_ft.cpp"
8182
#include "error.cpp"
8283
#include "common.cpp"
8384
#include "implicit.cpp"

src/nb_enum.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "nb_internals.h"
2+
#include "nb_ft.h"
23

34
NAMESPACE_BEGIN(NB_NAMESPACE)
45
NAMESPACE_BEGIN(detail)
@@ -95,7 +96,7 @@ PyObject *enum_create(enum_init_data *ed) noexcept {
9596
#endif
9697
}
9798

98-
maybe_make_immortal(result.ptr());
99+
make_immortal(result.ptr());
99100

100101
result.attr("__nb_enum__") = capsule(t, [](void *p) noexcept {
101102
type_init_data *t = (type_init_data *) p;

src/nb_ft.cpp

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include <nanobind/nanobind.h>
2+
#include "nb_ft.h"
3+
4+
#if defined(Py_GIL_DISABLED)
5+
/// Make an object immortal when targeting free-threaded Python
6+
void make_immortal(PyObject *op) noexcept {
7+
// See CPython's Objects/object.c
8+
if (PyObject_IS_GC(op))
9+
PyObject_GC_UnTrack(op);
10+
op->ob_tid = _Py_UNOWNED_TID;
11+
op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
12+
op->ob_ref_shared = 0;
13+
}
14+
15+
#if !(0 && PY_VERSION_HEX >= 0x030E00A5)
16+
void nb_enable_try_inc_ref(PyObject *obj) noexcept {
17+
// Since this is called during object construction, we know that we have
18+
// the only reference to the object and can use a non-atomic write.
19+
assert(obj->ob_ref_shared == 0);
20+
obj->ob_ref_shared = _Py_REF_MAYBE_WEAKREF;
21+
}
22+
23+
bool nb_try_inc_ref(PyObject *obj) noexcept {
24+
// See https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/internal/pycore_object.h#L761
25+
uint32_t local = _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local);
26+
local += 1;
27+
if (local == 0) {
28+
// immortal
29+
return true;
30+
}
31+
if (_Py_IsOwnedByCurrentThread(obj)) {
32+
_Py_atomic_store_uint32_relaxed(&obj->ob_ref_local, local);
33+
#ifdef Py_REF_DEBUG
34+
_Py_INCREF_IncRefTotal();
35+
#endif
36+
return true;
37+
}
38+
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared);
39+
for (;;) {
40+
// If the shared refcount is zero and the object is either merged
41+
// or may not have weak references, then we cannot incref it.
42+
if (shared == 0 || shared == _Py_REF_MERGED) {
43+
return false;
44+
}
45+
46+
if (_Py_atomic_compare_exchange_ssize(
47+
&obj->ob_ref_shared, &shared, shared + (1 << _Py_REF_SHARED_SHIFT))) {
48+
#ifdef Py_REF_DEBUG
49+
_Py_INCREF_IncRefTotal();
50+
#endif
51+
return true;
52+
}
53+
}
54+
}
55+
#endif
56+
#endif

src/nb_ft.h

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
src/nb_ft.h: implementation details related to free-threaded Python
3+
4+
Copyright (c) 2022 Wenzel Jakob
5+
6+
All rights reserved. Use of this source code is governed by a
7+
BSD-style license that can be found in the LICENSE file.
8+
*/
9+
10+
#if !defined(Py_GIL_DISABLED)
11+
/// Trivial implementations for non-free-threaded Python
12+
inline void make_immortal(PyObject *) noexcept { }
13+
inline void nb_enable_try_inc_ref(PyObject *) noexcept { }
14+
inline bool nb_try_inc_ref(PyObject *obj) noexcept {
15+
if (Py_REFCNT(obj) > 0) {
16+
Py_INCREF(obj);
17+
return true;
18+
}
19+
return false;
20+
}
21+
#else
22+
extern void make_immortal(PyObject *op) noexcept;
23+
24+
#if 0 && PY_VERSION_HEX >= 0x030E00A5
25+
/// Sufficiently recent CPython versions provide an API for the following operations
26+
inline void nb_enable_try_inc_ref(PyObject *obj) noexcept {
27+
PyUnstable_EnableTryIncRef(obj);
28+
}
29+
inline bool nb_try_inc_ref(PyObject *obj) noexcept {
30+
return PyUnstable_TryIncRef(obj);
31+
}
32+
#else
33+
/// Otherwise, nanabind ships with a low-level implementation
34+
extern void nb_enable_try_inc_ref(PyObject *) noexcept;
35+
extern bool nb_try_inc_ref(PyObject *obj) noexcept;
36+
#endif
37+
#endif

src/nb_func.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "nb_internals.h"
1111
#include "buffer.h"
12+
#include "nb_ft.h"
1213

1314
/// Maximum number of arguments supported by 'nb_vectorcall_simple'
1415
#define NB_MAXARGS_SIMPLE 8
@@ -289,7 +290,7 @@ PyObject *nb_func_new(const void *in_) noexcept {
289290
check(func, "nb::detail::nb_func_new(\"%s\"): alloc. failed (1).",
290291
name_cstr);
291292

292-
maybe_make_immortal((PyObject *) func);
293+
make_immortal((PyObject *) func);
293294

294295
// Check if the complex dispatch loop is needed
295296
bool complex_call = can_mutate_args || has_var_kwargs || has_var_args ||

src/nb_internals.h

-1
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,6 @@ struct lock_obj { lock_obj(PyObject *) { } };
513513

514514
extern char *strdup_check(const char *);
515515
extern void *malloc_check(size_t size);
516-
extern void maybe_make_immortal(PyObject *op);
517516

518517
extern char *extract_name(const char *cmd, const char *prefix, const char *s);
519518

src/nb_type.cpp

+3-58
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
#include "nb_internals.h"
11+
#include "nb_ft.h"
1112

1213
#if defined(_MSC_VER)
1314
# pragma warning(disable: 4706) // assignment within conditional expression
@@ -40,62 +41,6 @@ static PyObject **nb_weaklist_ptr(PyObject *self) {
4041
return weaklistoffset ? (PyObject **) ((uint8_t *) self + weaklistoffset) : nullptr;
4142
}
4243

43-
static void nb_enable_try_inc_ref(PyObject *obj) noexcept {
44-
#if 0 && defined(Py_GIL_DISABLED) && PY_VERSION_HEX >= 0x030E00A5
45-
PyUnstable_EnableTryIncRef(obj);
46-
#elif defined(Py_GIL_DISABLED)
47-
// Since this is called during object construction, we know that we have
48-
// the only reference to the object and can use a non-atomic write.
49-
assert(obj->ob_ref_shared == 0);
50-
obj->ob_ref_shared = _Py_REF_MAYBE_WEAKREF;
51-
#else
52-
(void) obj;
53-
#endif
54-
}
55-
56-
static bool nb_try_inc_ref(PyObject *obj) noexcept {
57-
#if 0 && defined(Py_GIL_DISABLED) && PY_VERSION_HEX >= 0x030E00A5
58-
return PyUnstable_TryIncRef(obj);
59-
#elif defined(Py_GIL_DISABLED)
60-
// See https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/internal/pycore_object.h#L761
61-
uint32_t local = _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local);
62-
local += 1;
63-
if (local == 0) {
64-
// immortal
65-
return true;
66-
}
67-
if (_Py_IsOwnedByCurrentThread(obj)) {
68-
_Py_atomic_store_uint32_relaxed(&obj->ob_ref_local, local);
69-
#ifdef Py_REF_DEBUG
70-
_Py_INCREF_IncRefTotal();
71-
#endif
72-
return true;
73-
}
74-
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared);
75-
for (;;) {
76-
// If the shared refcount is zero and the object is either merged
77-
// or may not have weak references, then we cannot incref it.
78-
if (shared == 0 || shared == _Py_REF_MERGED) {
79-
return false;
80-
}
81-
82-
if (_Py_atomic_compare_exchange_ssize(
83-
&obj->ob_ref_shared, &shared, shared + (1 << _Py_REF_SHARED_SHIFT))) {
84-
#ifdef Py_REF_DEBUG
85-
_Py_INCREF_IncRefTotal();
86-
#endif
87-
return true;
88-
}
89-
}
90-
#else
91-
if (Py_REFCNT(obj) > 0) {
92-
Py_INCREF(obj);
93-
return true;
94-
}
95-
return false;
96-
#endif
97-
}
98-
9944
static PyGetSetDef inst_getset[] = {
10045
{ "__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict, nullptr, nullptr },
10146
{ nullptr, nullptr, nullptr, nullptr, nullptr }
@@ -945,7 +890,7 @@ static PyTypeObject *nb_type_tp(size_t supplement) noexcept {
945890
tp = (PyTypeObject *) nb_type_from_metaclass(
946891
internals_->nb_meta, internals_->nb_module, &spec);
947892

948-
maybe_make_immortal((PyObject *) tp);
893+
make_immortal((PyObject *) tp);
949894

950895
handle(tp).attr("__module__") = "nanobind";
951896

@@ -1369,7 +1314,7 @@ PyObject *nb_type_new(const type_init_data *t) noexcept {
13691314

13701315
Py_DECREF(metaclass);
13711316

1372-
maybe_make_immortal(result);
1317+
make_immortal(result);
13731318

13741319
type_data *to = nb_type_data((PyTypeObject *) result);
13751320

0 commit comments

Comments
 (0)