Skip to main content

rustpython_common/
refcount.rs

1use crate::atomic::{Ordering, PyAtomic, Radium};
2
3// State layout (usize):
4//   [1 bit: destructed] [1 bit: published] [1 bit: leaked] [1 bit: immortal]
5//   [M bits: strong_count]
6// 64-bit: M=60.  32-bit: M=28.
7//
8// Weak references live in the object's `WeakRefList`, not in this word, so the
9// strong count takes every bit the flags leave. A 32-bit target reaches its
10// ceiling at 268 435 455 references rather than the 32 767 that half the word
11// would allow — a number two ordinary module imports pass on `wasm32`.
12const FLAG_BITS: u32 = 4;
13const DESTRUCTED: usize = 1 << (usize::BITS - 1);
14/// Object was published to a lock-free cache; memory reclamation is
15/// deferred through QSBR so concurrent try-incref readers never touch
16/// freed memory. Sticky once set.
17const PUBLISHED: usize = 1 << (usize::BITS - 2);
18const LEAKED: usize = 1 << (usize::BITS - 3);
19/// Object lives for the whole process (PEP 683). Sticky once set.
20///
21/// # The immortal invariant
22///
23/// Once [`RefCount::make_immortal`] has run, the word never changes again:
24/// `inc`, `inc_by`, `dec` and `safe_inc` read the bit and return without
25/// touching the counter, so every reference operation on such an object is a
26/// relaxed load and a well-predicted branch rather than an atomic
27/// read-modify-write — and `dec` in particular drops its `Release` store,
28/// which is the expensive half of a refcount pair on a weakly ordered target.
29///
30/// The consequences the rest of the tree may rely on:
31///
32/// * `dec` never reports the object collectable, so it is never deallocated
33///   and no `__del__` or weakref callback ever fires for it. Immortality may
34///   therefore only be granted to something an owner keeps for the whole
35///   process (a `static_cell`, the interpreter's `Context`).
36/// * `get` reports [`IMMORTAL_COUNT`], a number far past any real reference
37///   total. Unique-ownership fast paths spelled `strong_count() == 1` can
38///   therefore never fire on an immortal object, and the cycle collector's
39///   `start_gc_refs` clamps a count that large straight to `GC_REACHABLE`, so
40///   an immortal object is a permanent root that keeps its referents alive.
41/// * It is not the same answer as [`LEAKED`], which means "the string pool
42///   owns this copy" and is what `PyObject::is_interned` — and the dict
43///   pointer-equality key fast path behind it — reads. Interning implies
44///   immortality ([`RefCount::leak`] sets both, which is what lets `dec`
45///   decide "a leaked object never reaches zero" from the immortal bit
46///   alone), but an immortal object is not thereby interned.
47const IMMORTAL: usize = 1 << (usize::BITS - 4);
48const STRONG_WIDTH: u32 = usize::BITS - FLAG_BITS;
49const STRONG: usize = (1 << STRONG_WIDTH) - 1;
50const COUNT: usize = 1;
51
52/// The strong count an immortal object's word is parked at.
53///
54/// On a 64-bit target this is CPython's `_Py_IMMORTAL_REFCNT`, `UINT_MAX`, so
55/// `sys.getrefcount(None)` reports the same 4294967295 CPython does — and it
56/// is exactly `GC_REACHABLE`, which is what makes the collector treat an
57/// immortal candidate as reachable without a special case. A 32-bit strong
58/// field cannot hold that, so there it takes half the field instead: still
59/// more references than a subtraction pass could ever walk down.
60const IMMORTAL_COUNT: usize = if (u32::MAX as u64) < STRONG as u64 {
61    u32::MAX as usize
62} else {
63    STRONG / 2
64};
65
66#[inline(never)]
67#[cold]
68#[allow(
69    clippy::disallowed_methods,
70    reason = "refcount overflow must preserve upstream abort semantics"
71)]
72fn refcount_overflow() -> ! {
73    cfg_select! {
74        feature = "std" => std::process::abort(),
75        _ => core::panic!("refcount overflow"),
76    }
77}
78
79/// State wraps reference count + flags in a single word (platform usize)
80#[derive(Clone, Copy)]
81struct State {
82    inner: usize,
83}
84
85impl State {
86    #[inline]
87    fn from_raw(inner: usize) -> Self {
88        Self { inner }
89    }
90
91    #[inline]
92    fn as_raw(self) -> usize {
93        self.inner
94    }
95
96    #[inline]
97    fn strong(self) -> usize {
98        (self.inner & STRONG) / COUNT
99    }
100
101    #[inline]
102    fn destructed(self) -> bool {
103        (self.inner & DESTRUCTED) != 0
104    }
105
106    #[inline]
107    fn leaked(self) -> bool {
108        (self.inner & LEAKED) != 0
109    }
110
111    #[inline]
112    const fn immortal(self) -> bool {
113        (self.inner & IMMORTAL) != 0
114    }
115
116    #[inline]
117    fn add_strong(self, val: u32) -> Self {
118        Self::from_raw(self.inner + (val as usize) * COUNT)
119    }
120
121    #[inline]
122    fn with_leaked(self, leaked: bool) -> Self {
123        Self::from_raw((self.inner & !LEAKED) | if leaked { LEAKED } else { 0 })
124    }
125
126    /// The same flags, with [`IMMORTAL`] set and the strong count parked at
127    /// [`IMMORTAL_COUNT`].
128    #[inline]
129    fn immortalized(self) -> Self {
130        Self::from_raw((self.inner & !STRONG) | IMMORTAL | (IMMORTAL_COUNT * COUNT))
131    }
132}
133
134/// Reference count using state layout with LEAKED and IMMORTAL support.
135///
136/// State layout (usize):
137/// 64-bit: [destructed] [published] [leaked] [immortal] [60 bits: strong_count]
138/// 32-bit: [destructed] [published] [leaked] [immortal] [28 bits: strong_count]
139///
140/// See [`IMMORTAL`] for what the immortal bit promises.
141pub struct RefCount {
142    state: PyAtomic<usize>,
143}
144
145impl Default for RefCount {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151impl RefCount {
152    /// Create a new RefCount with strong count = 1
153    #[must_use]
154    pub fn new() -> Self {
155        Self {
156            state: Radium::new(COUNT),
157        }
158    }
159
160    /// Get current strong count
161    #[inline]
162    pub fn get(&self) -> usize {
163        State::from_raw(self.state.load(Ordering::Relaxed)).strong()
164    }
165
166    /// Whether this object lives for the whole process.
167    ///
168    /// A plain relaxed load: the bit is sticky and is set before the object is
169    /// shared, so no reader can observe it flipping.
170    #[inline(always)]
171    #[must_use]
172    pub fn is_immortal(&self) -> bool {
173        State::from_raw(self.state.load(Ordering::Relaxed)).immortal()
174    }
175
176    /// Increment strong count
177    #[inline(always)]
178    pub fn inc(&self) {
179        if self.is_immortal() {
180            return;
181        }
182        let val = State::from_raw(self.state.fetch_add(COUNT, Ordering::Relaxed));
183        // One comparison stands in for the three cases that are not an
184        // ordinary increment. Masking to the count and the destructed bit and
185        // subtracting one leaves an ordinary count below `STRONG - COUNT`: a
186        // count of zero wraps above it, a count at the ceiling lands on it,
187        // and a destructed word carries a bit far above the count field. The
188        // three of them written out separately cost the hot path two extra
189        // compares, which is what the immortal test above wants back.
190        if (val.as_raw() & (DESTRUCTED | STRONG)).wrapping_sub(COUNT) >= STRONG - COUNT {
191            self.inc_uncommon(val);
192        }
193    }
194
195    /// The `inc` cases that are not an ordinary increment: an overflowed or
196    /// destructed word, and a count of zero — where the `fetch_add` that just
197    /// ran created a permission to run the decrement again.
198    #[cold]
199    #[inline(never)]
200    fn inc_uncommon(&self, val: State) {
201        if val.destructed() || val.strong() > STRONG - 1 {
202            refcount_overflow();
203        }
204        self.state.fetch_add(COUNT, Ordering::Relaxed);
205    }
206
207    #[inline(always)]
208    pub fn inc_by(&self, n: usize) {
209        debug_assert!(n <= STRONG);
210        if self.is_immortal() {
211            return;
212        }
213        let val = State::from_raw(self.state.fetch_add(n * COUNT, Ordering::Relaxed));
214        if val.destructed() || val.strong() > STRONG - n {
215            refcount_overflow();
216        }
217    }
218
219    /// Returns true if successful
220    #[inline]
221    #[must_use]
222    pub fn safe_inc(&self) -> bool {
223        let mut old = State::from_raw(self.state.load(Ordering::Relaxed));
224        loop {
225            if old.immortal() {
226                return true;
227            }
228            if old.destructed() || old.strong() == 0 {
229                return false;
230            }
231            if old.strong() >= STRONG {
232                refcount_overflow();
233            }
234            let new_state = old.add_strong(1);
235            match self.state.compare_exchange_weak(
236                old.as_raw(),
237                new_state.as_raw(),
238                Ordering::Relaxed,
239                Ordering::Relaxed,
240            ) {
241                Ok(_) => return true,
242                Err(curr) => old = State::from_raw(curr),
243            }
244        }
245    }
246
247    /// Decrement strong count. Returns true when count drops to 0.
248    #[inline(always)]
249    #[must_use]
250    pub fn dec(&self) -> bool {
251        // The whole point of the immortal bit: this returns before the
252        // `Release` read-modify-write, which is the costly half of a refcount
253        // pair on a weakly ordered target.
254        //
255        // The test also stands in for the one this used to make on the result
256        // of the decrement — "LEAKED objects never reach 0". `leak` sets
257        // `IMMORTAL` alongside `LEAKED`, so an interned object leaves here on
258        // the line above and the counter it would have walked down is never
259        // touched. That is what keeps the guard from costing an instruction
260        // on the mortal path: two go in at the top, one comes out below.
261        if self.is_immortal() {
262            return false;
263        }
264        let old = State::from_raw(self.state.fetch_sub(COUNT, Ordering::Release));
265        debug_assert!(!old.leaked(), "a leaked object must also be immortal");
266
267        if old.strong() == 1 {
268            core::sync::atomic::fence(Ordering::Acquire);
269            return true;
270        }
271        false
272    }
273
274    /// Mark this object as leaked (interned). It will never be deallocated.
275    ///
276    /// This also makes the object immortal, and [`RefCount::dec`] depends on
277    /// that: leaked and immortal are separate answers — `is_leaked` means "the
278    /// string pool owns this copy" and is what pointer-equality key lookups
279    /// read — but every leaked object is immortal, so `dec` can decide both
280    /// from the immortal bit alone.
281    pub fn leak(&self) {
282        debug_assert!(!self.is_leaked());
283        let mut old = State::from_raw(self.state.load(Ordering::Relaxed));
284        loop {
285            let new_state = old.with_leaked(true).immortalized();
286            match self.state.compare_exchange_weak(
287                old.as_raw(),
288                new_state.as_raw(),
289                Ordering::AcqRel,
290                Ordering::Relaxed,
291            ) {
292                Ok(_) => return,
293                Err(curr) => old = State::from_raw(curr),
294            }
295        }
296    }
297
298    /// Make this object immortal: every later `inc`/`dec` becomes a branch and
299    /// the object is never deallocated. See [`IMMORTAL`] for the invariant.
300    ///
301    /// Idempotent, and safe to call on an object that is already interned —
302    /// the other flags are preserved.
303    pub fn make_immortal(&self) {
304        let mut old = State::from_raw(self.state.load(Ordering::Relaxed));
305        loop {
306            if old.immortal() {
307                return;
308            }
309            debug_assert!(!old.destructed() && old.strong() > 0);
310            match self.state.compare_exchange_weak(
311                old.as_raw(),
312                old.immortalized().as_raw(),
313                Ordering::AcqRel,
314                Ordering::Relaxed,
315            ) {
316                Ok(_) => return,
317                Err(curr) => old = State::from_raw(curr),
318            }
319        }
320    }
321
322    /// Check if this object is leaked (interned).
323    pub fn is_leaked(&self) -> bool {
324        State::from_raw(self.state.load(Ordering::Acquire)).leaked()
325    }
326
327    /// Mark the object as published to a lock-free cache (sticky).
328    #[inline]
329    pub fn mark_published(&self) {
330        self.state.fetch_or(PUBLISHED, Ordering::Release);
331    }
332
333    #[inline]
334    pub fn is_published(&self) -> bool {
335        (self.state.load(Ordering::Acquire) & PUBLISHED) != 0
336    }
337}
338
339// Deferred Drop Infrastructure
340//
341// This mechanism allows untrack_object() calls to be deferred until after
342// the GC collection phase completes, preventing deadlocks that occur when
343// clear (pop_edges) triggers object destruction while holding the tracked_objects lock.
344
345#[cfg(feature = "std")]
346use core::cell::{Cell, RefCell};
347
348#[cfg(feature = "std")]
349thread_local! {
350    /// Flag indicating if we're inside a deferred drop context.
351    /// When true, drop operations should defer untrack calls.
352    static IN_DEFERRED_CONTEXT: Cell<bool> = const { Cell::new(false) };
353
354    /// Queue of deferred untrack operations.
355    /// No Send bound needed - this is thread-local and only accessed from the same thread.
356    static DEFERRED_QUEUE: RefCell<Vec<Box<dyn FnOnce()>>> = const { RefCell::new(Vec::new()) };
357}
358
359#[cfg(feature = "std")]
360struct DeferredDropGuard {
361    was_in_context: bool,
362}
363
364#[cfg(feature = "std")]
365impl Drop for DeferredDropGuard {
366    fn drop(&mut self) {
367        IN_DEFERRED_CONTEXT.with(|in_ctx| {
368            in_ctx.set(self.was_in_context);
369        });
370        // Only flush if we're the outermost context and not already panicking
371        // (flushing during unwinding risks double-panic → process abort).
372        if !self.was_in_context && !std::thread::panicking() {
373            flush_deferred_drops();
374        }
375    }
376}
377
378/// Execute a function within a deferred drop context.
379/// Any calls to `try_defer_drop` within this context will be queued
380/// and executed when the context exits (even on panic).
381#[cfg(feature = "std")]
382#[inline]
383pub fn with_deferred_drops<F, R>(f: F) -> R
384where
385    F: FnOnce() -> R,
386{
387    let _guard = IN_DEFERRED_CONTEXT.with(|in_ctx| {
388        let was_in_context = in_ctx.get();
389        in_ctx.set(true);
390        DeferredDropGuard { was_in_context }
391    });
392    f()
393}
394
395/// Try to defer a drop-related operation.
396/// If inside a deferred context, the operation is queued.
397/// Otherwise, it executes immediately.
398#[cfg(feature = "std")]
399#[inline]
400pub fn try_defer_drop<F>(f: F)
401where
402    F: FnOnce() + 'static,
403{
404    let should_defer = IN_DEFERRED_CONTEXT.with(|in_ctx| in_ctx.get());
405
406    if should_defer {
407        DEFERRED_QUEUE.with(|q| {
408            q.borrow_mut().push(Box::new(f));
409        });
410    } else {
411        f();
412    }
413}
414
415/// Flush all deferred drop operations.
416/// This is automatically called when exiting a deferred context.
417#[cfg(feature = "std")]
418#[inline]
419pub fn flush_deferred_drops() {
420    DEFERRED_QUEUE.with(|q| {
421        // Take all queued operations
422        let ops: Vec<_> = q.borrow_mut().drain(..).collect();
423        // Execute them outside the borrow
424        for op in ops {
425            op();
426        }
427    });
428}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433
434    /// The strong count reaches far past a 16-bit ceiling on every target.
435    ///
436    /// The count shares its word with the flag bits, so its width follows the
437    /// pointer width. A 32-bit target is the one this guards: a second counter
438    /// packed beside the strong count once left it 15 bits, and `wasm32`
439    /// aborted at 32 767 references — a total two ordinary module imports
440    /// pass. The check is a no-op on a 64-bit host, where 31 bits already
441    /// covered this; run the crate's tests against a 32-bit target to exercise
442    /// it.
443    #[test]
444    fn strong_count_reaches_past_a_16_bit_ceiling() {
445        const REFERENCES: usize = 1 << 20;
446
447        let rc = RefCount::new();
448        rc.inc_by(REFERENCES);
449        assert_eq!(rc.get(), REFERENCES + 1);
450    }
451
452    /// `inc` and `dec` reach the same ceiling as `inc_by`.
453    ///
454    /// The aborts reported against this layout came one reference at a time
455    /// through `inc`, whose overflow check is written separately from
456    /// `inc_by`'s, and the count has to come back down through `dec` without
457    /// reporting the object collectable before the last reference goes.
458    #[test]
459    fn inc_and_dec_reach_past_a_16_bit_ceiling() {
460        const REFERENCES: usize = 1 << 20;
461
462        let rc = RefCount::new(); // strong = 1
463        for _ in 1..REFERENCES {
464            rc.inc();
465        }
466        assert_eq!(rc.get(), REFERENCES);
467        for _ in 1..REFERENCES {
468            assert!(!rc.dec());
469        }
470        assert_eq!(rc.get(), 1);
471        assert!(rc.dec());
472    }
473
474    /// A fresh count holds exactly one strong reference and no stray bits.
475    ///
476    /// `get` masks the flags away, so a spare field left in the word would not
477    /// show up there. Reading the raw state keeps the layout honest.
478    #[test]
479    fn a_new_refcount_holds_one_strong_reference_and_nothing_else() {
480        let rc = RefCount::new();
481        assert_eq!(rc.get(), 1);
482        assert_eq!(rc.state.load(Ordering::Relaxed), COUNT);
483    }
484
485    /// An immortal count neither moves nor ever reports the object collectable.
486    ///
487    /// This is the whole contract the singletons rely on: `inc`/`dec` become
488    /// branches, so a `dec` that would have been the last one still answers
489    /// "not collectable", and `sys.getrefcount` keeps reporting the same
490    /// number however much reference traffic passes through.
491    #[test]
492    fn an_immortal_count_never_moves() {
493        let rc = RefCount::new(); // strong = 1
494        assert!(!rc.is_immortal());
495        rc.make_immortal();
496        assert!(rc.is_immortal());
497        assert_eq!(rc.get(), IMMORTAL_COUNT);
498
499        rc.inc();
500        rc.inc_by(1000);
501        assert!(rc.safe_inc());
502        assert_eq!(rc.get(), IMMORTAL_COUNT);
503
504        for _ in 0..1000 {
505            assert!(!rc.dec());
506        }
507        assert_eq!(rc.get(), IMMORTAL_COUNT);
508
509        // Idempotent.
510        rc.make_immortal();
511        assert_eq!(rc.get(), IMMORTAL_COUNT);
512    }
513
514    /// Immortality does not imply interning, and interning does imply
515    /// immortality.
516    ///
517    /// `PyObject::is_interned` answers from `LEAKED`, and the dict key fast
518    /// path turns that answer into pointer equality, so immortalizing an
519    /// object must not make it look interned. The other direction is the
520    /// implication `dec` leans on when it decides "leaked objects never reach
521    /// zero" from the immortal bit alone.
522    #[test]
523    fn interning_implies_immortality_but_not_the_other_way() {
524        let immortal = RefCount::new();
525        immortal.make_immortal();
526        assert!(immortal.is_immortal());
527        assert!(!immortal.is_leaked());
528
529        let interned = RefCount::new();
530        interned.leak();
531        assert!(interned.is_leaked());
532        assert!(interned.is_immortal());
533        assert_eq!(interned.get(), IMMORTAL_COUNT);
534
535        interned.inc();
536        assert!(!interned.dec());
537        assert!(!interned.dec());
538        assert_eq!(interned.get(), IMMORTAL_COUNT);
539    }
540
541    /// The parked count is large enough for the collector's reachable clamp.
542    ///
543    /// `PyObject::start_gc_refs` treats a strong count of `u32::MAX` or more
544    /// as reachable outright; on a 64-bit host that clamp is the only thing
545    /// keeping an immortal object out of a collection's dead set.
546    #[test]
547    fn the_parked_count_outruns_any_real_reference_total() {
548        const {
549            assert!(IMMORTAL_COUNT > 1);
550            assert!(IMMORTAL_COUNT <= STRONG);
551        }
552        if usize::BITS >= 64 {
553            assert_eq!(IMMORTAL_COUNT, u32::MAX as usize);
554        }
555    }
556
557    #[test]
558    fn published_bit_survives_refcount_traffic() {
559        let rc = RefCount::new(); // strong = 1
560        assert!(!rc.is_published());
561        rc.mark_published();
562        assert!(rc.is_published());
563        rc.inc(); // strong = 2
564        assert!(rc.is_published());
565        assert!(!rc.dec()); // strong = 1
566        assert!(rc.is_published());
567        assert!(rc.safe_inc()); // strong = 2
568        assert!(!rc.dec()); // strong = 1
569        assert!(rc.dec()); // strong = 0 -> true
570        assert!(rc.is_published());
571    }
572}