pyo3_ffi/
refcount.rs

1use crate::pyport::Py_ssize_t;
2use crate::PyObject;
3#[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))]
4use std::os::raw::c_char;
5#[cfg(Py_3_12)]
6use std::os::raw::c_int;
7#[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))]
8use std::os::raw::c_long;
9#[cfg(any(Py_GIL_DISABLED, all(Py_3_12, not(Py_3_14))))]
10use std::os::raw::c_uint;
11#[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))]
12use std::os::raw::c_ulong;
13use std::ptr;
14#[cfg(Py_GIL_DISABLED)]
15use std::sync::atomic::Ordering::Relaxed;
16
17#[cfg(Py_3_14)]
18pub const _Py_STATICALLY_ALLOCATED_FLAG: c_int = 1 << 7;
19
20#[cfg(all(Py_3_12, not(Py_3_14)))]
21const _Py_IMMORTAL_REFCNT: Py_ssize_t = {
22    if cfg!(target_pointer_width = "64") {
23        c_uint::MAX as Py_ssize_t
24    } else {
25        // for 32-bit systems, use the lower 30 bits (see comment in CPython's object.h)
26        (c_uint::MAX >> 2) as Py_ssize_t
27    }
28};
29
30// comments in Python.h about the choices for these constants
31
32#[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))]
33const _Py_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = {
34    if cfg!(target_pointer_width = "64") {
35        ((3 as c_ulong) << (30 as c_ulong)) as Py_ssize_t
36    } else {
37        ((5 as c_long) << (28 as c_long)) as Py_ssize_t
38    }
39};
40
41#[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))]
42const _Py_STATIC_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = {
43    if cfg!(target_pointer_width = "64") {
44        _Py_IMMORTAL_INITIAL_REFCNT
45            | ((_Py_STATICALLY_ALLOCATED_FLAG as Py_ssize_t) << (32 as Py_ssize_t))
46    } else {
47        ((7 as c_long) << (28 as c_long)) as Py_ssize_t
48    }
49};
50
51#[cfg(all(Py_3_14, target_pointer_width = "32"))]
52const _Py_IMMORTAL_MINIMUM_REFCNT: Py_ssize_t = ((1 as c_long) << (30 as c_long)) as Py_ssize_t;
53
54#[cfg(all(Py_3_14, target_pointer_width = "32"))]
55const _Py_STATIC_IMMORTAL_MINIMUM_REFCNT: Py_ssize_t =
56    ((6 as c_long) << (28 as c_long)) as Py_ssize_t;
57
58#[cfg(all(Py_3_14, Py_GIL_DISABLED))]
59pub const _Py_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = c_uint::MAX as Py_ssize_t;
60
61#[cfg(Py_GIL_DISABLED)]
62pub const _Py_IMMORTAL_REFCNT_LOCAL: u32 = u32::MAX;
63
64#[cfg(Py_GIL_DISABLED)]
65pub const _Py_REF_SHARED_SHIFT: isize = 2;
66// skipped private _Py_REF_SHARED_FLAG_MASK
67
68// skipped private _Py_REF_SHARED_INIT
69// skipped private _Py_REF_MAYBE_WEAKREF
70// skipped private _Py_REF_QUEUED
71// skipped private _Py_REF_MERGED
72
73// skipped private _Py_REF_SHARED
74
75#[inline]
76pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t {
77    #[cfg(Py_GIL_DISABLED)]
78    {
79        let local = (*ob).ob_ref_local.load(Relaxed);
80        if local == _Py_IMMORTAL_REFCNT_LOCAL {
81            #[cfg(not(Py_3_14))]
82            return _Py_IMMORTAL_REFCNT;
83            #[cfg(Py_3_14)]
84            return _Py_IMMORTAL_INITIAL_REFCNT;
85        }
86        let shared = (*ob).ob_ref_shared.load(Relaxed);
87        local as Py_ssize_t + Py_ssize_t::from(shared >> _Py_REF_SHARED_SHIFT)
88    }
89
90    #[cfg(all(not(Py_GIL_DISABLED), Py_3_12))]
91    {
92        (*ob).ob_refcnt.ob_refcnt
93    }
94
95    #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), not(GraalPy)))]
96    {
97        (*ob).ob_refcnt
98    }
99
100    #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), GraalPy))]
101    {
102        _Py_REFCNT(ob)
103    }
104}
105
106#[cfg(Py_3_12)]
107#[inline(always)]
108unsafe fn _Py_IsImmortal(op: *mut PyObject) -> c_int {
109    #[cfg(all(target_pointer_width = "64", not(Py_GIL_DISABLED)))]
110    {
111        (((*op).ob_refcnt.ob_refcnt as crate::PY_INT32_T) < 0) as c_int
112    }
113
114    #[cfg(all(target_pointer_width = "32", not(Py_GIL_DISABLED)))]
115    {
116        #[cfg(not(Py_3_14))]
117        {
118            ((*op).ob_refcnt.ob_refcnt == _Py_IMMORTAL_REFCNT) as c_int
119        }
120
121        #[cfg(Py_3_14)]
122        {
123            ((*op).ob_refcnt >= _Py_IMMORTAL_MINIMUM_REFCNT) as c_int
124        }
125    }
126
127    #[cfg(Py_GIL_DISABLED)]
128    {
129        ((*op).ob_ref_local.load(Relaxed) == _Py_IMMORTAL_REFCNT_LOCAL) as c_int
130    }
131}
132
133// skipped _Py_IsStaticImmortal
134
135// TODO: Py_SET_REFCNT
136
137extern "C" {
138    #[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))]
139    fn _Py_NegativeRefcount(filename: *const c_char, lineno: c_int, op: *mut PyObject);
140    #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))]
141    fn _Py_INCREF_IncRefTotal();
142    #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))]
143    fn _Py_DECREF_DecRefTotal();
144
145    #[cfg_attr(PyPy, link_name = "_PyPy_Dealloc")]
146    fn _Py_Dealloc(arg1: *mut PyObject);
147
148    #[cfg_attr(PyPy, link_name = "PyPy_IncRef")]
149    #[cfg_attr(GraalPy, link_name = "_Py_IncRef")]
150    pub fn Py_IncRef(o: *mut PyObject);
151    #[cfg_attr(PyPy, link_name = "PyPy_DecRef")]
152    #[cfg_attr(GraalPy, link_name = "_Py_DecRef")]
153    pub fn Py_DecRef(o: *mut PyObject);
154
155    #[cfg(all(Py_3_10, not(PyPy)))]
156    fn _Py_IncRef(o: *mut PyObject);
157    #[cfg(all(Py_3_10, not(PyPy)))]
158    fn _Py_DecRef(o: *mut PyObject);
159
160    #[cfg(GraalPy)]
161    fn _Py_REFCNT(arg1: *const PyObject) -> Py_ssize_t;
162
163    #[cfg(GraalPy)]
164    fn _Py_TYPE(arg1: *const PyObject) -> *mut PyTypeObject;
165
166    #[cfg(GraalPy)]
167    fn _Py_SIZE(arg1: *const PyObject) -> Py_ssize_t;
168}
169
170#[inline(always)]
171pub unsafe fn Py_INCREF(op: *mut PyObject) {
172    // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting
173    // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance.
174    #[cfg(any(
175        Py_GIL_DISABLED,
176        Py_LIMITED_API,
177        py_sys_config = "Py_REF_DEBUG",
178        GraalPy
179    ))]
180    {
181        // _Py_IncRef was added to the ABI in 3.10; skips null checks
182        #[cfg(all(Py_3_10, not(PyPy)))]
183        {
184            _Py_IncRef(op);
185        }
186
187        #[cfg(any(not(Py_3_10), PyPy))]
188        {
189            Py_IncRef(op);
190        }
191    }
192
193    // version-specific builds are allowed to directly manipulate the reference count
194    #[cfg(not(any(
195        Py_GIL_DISABLED,
196        Py_LIMITED_API,
197        py_sys_config = "Py_REF_DEBUG",
198        GraalPy
199    )))]
200    {
201        #[cfg(all(Py_3_14, target_pointer_width = "64"))]
202        {
203            let cur_refcnt = (*op).ob_refcnt.ob_refcnt;
204            if (cur_refcnt as i32) < 0 {
205                return;
206            }
207            (*op).ob_refcnt.ob_refcnt = cur_refcnt.wrapping_add(1);
208        }
209
210        #[cfg(all(Py_3_12, not(Py_3_14), target_pointer_width = "64"))]
211        {
212            let cur_refcnt = (*op).ob_refcnt.ob_refcnt_split[crate::PY_BIG_ENDIAN];
213            let new_refcnt = cur_refcnt.wrapping_add(1);
214            if new_refcnt == 0 {
215                return;
216            }
217            (*op).ob_refcnt.ob_refcnt_split[crate::PY_BIG_ENDIAN] = new_refcnt;
218        }
219
220        #[cfg(all(Py_3_12, target_pointer_width = "32"))]
221        {
222            if _Py_IsImmortal(op) != 0 {
223                return;
224            }
225            (*op).ob_refcnt.ob_refcnt += 1
226        }
227
228        #[cfg(not(Py_3_12))]
229        {
230            (*op).ob_refcnt += 1
231        }
232
233        // Skipped _Py_INCREF_STAT_INC - if anyone wants this, please file an issue
234        // or submit a PR supporting Py_STATS build option and pystats.h
235    }
236}
237
238// skipped _Py_DecRefShared
239// skipped _Py_DecRefSharedDebug
240// skipped _Py_MergeZeroLocalRefcount
241
242#[inline(always)]
243#[cfg_attr(
244    all(py_sys_config = "Py_REF_DEBUG", Py_3_12, not(Py_LIMITED_API)),
245    track_caller
246)]
247pub unsafe fn Py_DECREF(op: *mut PyObject) {
248    // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting
249    // On 3.12+ we implement refcount debugging to get better assertion locations on negative refcounts
250    // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance.
251    #[cfg(any(
252        Py_GIL_DISABLED,
253        Py_LIMITED_API,
254        all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)),
255        GraalPy
256    ))]
257    {
258        // _Py_DecRef was added to the ABI in 3.10; skips null checks
259        #[cfg(all(Py_3_10, not(PyPy)))]
260        {
261            _Py_DecRef(op);
262        }
263
264        #[cfg(any(not(Py_3_10), PyPy))]
265        {
266            Py_DecRef(op);
267        }
268    }
269
270    #[cfg(not(any(
271        Py_GIL_DISABLED,
272        Py_LIMITED_API,
273        all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)),
274        GraalPy
275    )))]
276    {
277        #[cfg(Py_3_12)]
278        if _Py_IsImmortal(op) != 0 {
279            return;
280        }
281
282        // Skipped _Py_DECREF_STAT_INC - if anyone needs this, please file an issue
283        // or submit a PR supporting Py_STATS build option and pystats.h
284
285        #[cfg(py_sys_config = "Py_REF_DEBUG")]
286        _Py_DECREF_DecRefTotal();
287
288        #[cfg(Py_3_12)]
289        {
290            (*op).ob_refcnt.ob_refcnt -= 1;
291
292            #[cfg(py_sys_config = "Py_REF_DEBUG")]
293            if (*op).ob_refcnt.ob_refcnt < 0 {
294                let location = std::panic::Location::caller();
295                let filename = std::ffi::CString::new(location.file()).unwrap();
296                _Py_NegativeRefcount(filename.as_ptr(), location.line() as i32, op);
297            }
298
299            if (*op).ob_refcnt.ob_refcnt == 0 {
300                _Py_Dealloc(op);
301            }
302        }
303
304        #[cfg(not(Py_3_12))]
305        {
306            (*op).ob_refcnt -= 1;
307
308            if (*op).ob_refcnt == 0 {
309                _Py_Dealloc(op);
310            }
311        }
312    }
313}
314
315#[inline]
316pub unsafe fn Py_CLEAR(op: *mut *mut PyObject) {
317    let tmp = *op;
318    if !tmp.is_null() {
319        *op = ptr::null_mut();
320        Py_DECREF(tmp);
321    }
322}
323
324#[inline]
325pub unsafe fn Py_XINCREF(op: *mut PyObject) {
326    if !op.is_null() {
327        Py_INCREF(op)
328    }
329}
330
331#[inline]
332pub unsafe fn Py_XDECREF(op: *mut PyObject) {
333    if !op.is_null() {
334        Py_DECREF(op)
335    }
336}
337
338extern "C" {
339    #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))]
340    #[cfg_attr(docsrs, doc(cfg(Py_3_10)))]
341    pub fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject;
342    #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))]
343    #[cfg_attr(docsrs, doc(cfg(Py_3_10)))]
344    pub fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject;
345}
346
347// macro _Py_NewRef not public; reimplemented directly inside Py_NewRef here
348// macro _Py_XNewRef not public; reimplemented directly inside Py_XNewRef here
349
350#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))]
351#[cfg_attr(docsrs, doc(cfg(Py_3_10)))]
352#[inline]
353pub unsafe fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject {
354    Py_INCREF(obj);
355    obj
356}
357
358#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))]
359#[cfg_attr(docsrs, doc(cfg(Py_3_10)))]
360#[inline]
361pub unsafe fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject {
362    Py_XINCREF(obj);
363    obj
364}