pyo3_ffi/
refcount.rs

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