Skip to main content

pyo3_ffi/
refcount.rs

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