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}