Skip to main content

lean_rs_sys/
refcount.rs

1//! Refcount fast paths — Rust mirrors of `lean.h:536–563`.
2//!
3//! The header encodes the runtime mode in the RC sign:
4//! - `m_rc > 0`  — single-threaded: bump or decrement in place.
5//! - `m_rc < 0`  — multi-threaded: RC is negated; `fetch_sub` is the relaxed
6//!   atomic decrement, mirroring `atomic_fetch_sub_explicit(.., relaxed)` in
7//!   `lean.h`.
8//! - `m_rc == 0` — persistent (compact regions); never refcounted.
9//!
10//! Each mirror reaches `m_rc` through [`AtomicI32::from_ptr`] so the actual
11//! load / store / `fetch_sub` call site sees a safe `&AtomicI32`.
12
13// These mirrors of `lean.h`'s `static inline` helpers must inline through the
14// FFI boundary for the fast paths to be free; that is the design.
15#![allow(clippy::inline_always)]
16
17use core::sync::atomic::{AtomicI32, Ordering};
18
19use crate::object::lean_is_scalar;
20use crate::repr::LeanObjectRepr;
21use crate::types::lean_object;
22
23unsafe extern "C" {
24    /// Cold path for refcount-zero decrement (`lean.h:552`). Invoked by
25    /// [`lean_dec_ref`] when the live count would cross to zero and the
26    /// object actually needs freeing.
27    pub fn lean_dec_ref_cold(o: *mut lean_object);
28
29    /// Mark a heap object — and everything reachable from it — as
30    /// multi-threaded (`lean.h:612`).
31    pub fn lean_mark_mt(o: *mut lean_object);
32
33    /// Mark a heap object as persistent so its refcount is no longer
34    /// updated (`lean.h:613`).
35    pub fn lean_mark_persistent(o: *mut lean_object);
36}
37
38#[inline(always)]
39unsafe fn rc_atom<'a>(o: *mut lean_object) -> &'a AtomicI32 {
40    // SAFETY: caller guarantees `o` is a valid non-scalar Lean heap object.
41    // `LeanObjectRepr`'s layout is pinned by `LEAN_HEADER_DIGEST`; the
42    // `m_rc` field is at offset 0. `AtomicI32::from_ptr` requires the
43    // pointer to be aligned (Lean allocates `lean_object`s with at least
44    // 4-byte alignment — pinned by the same digest) and valid for shared
45    // access for the duration of `'a`.
46    unsafe {
47        let repr = o.cast::<LeanObjectRepr>();
48        AtomicI32::from_ptr(&raw mut (*repr).m_rc)
49    }
50}
51
52/// Bump `o`'s reference count by `n` (`lean.h:536–546`, `lean_inc_ref_n`).
53///
54/// # Safety
55///
56/// `o` must be a valid non-scalar Lean object pointer per the `lean_obj_arg`
57/// calling convention; the caller must ensure the increment cannot overflow
58/// (Lean's runtime preserves this invariant by construction). The layout of
59/// `lean_object` matches the crate-private `LeanObjectRepr` per the
60/// build-time digest check.
61///
62/// # Panics
63///
64/// Panics if adding `n` to the single-threaded refcount would overflow
65/// `i32`. This indicates a runtime invariant breach (an object referenced
66/// >2 billion times) rather than a recoverable condition.
67#[inline(always)]
68pub unsafe fn lean_inc_ref_n(o: *mut lean_object, n: usize) {
69    // SAFETY: precondition above; `n` fits in i32 in any realistic program.
70    let rc = unsafe { rc_atom(o) };
71    let cur = rc.load(Ordering::Relaxed);
72    let delta = n as i32;
73    if cur > 0 {
74        // `strict_add` panics on overflow in both debug and release; a
75        // refcount overflow indicates a runtime invariant breach (a single
76        // object held by >2 billion references), not a recoverable
77        // condition.
78        rc.store(cur.strict_add(delta), Ordering::Relaxed);
79    } else if cur != 0 {
80        // Multi-threaded: m_rc is negated, so increment-by-n is fetch_sub(n).
81        rc.fetch_sub(delta, Ordering::Relaxed);
82    }
83}
84
85/// Bump `o`'s refcount by one (`lean.h:548–550`).
86///
87/// # Safety
88///
89/// Same as [`lean_inc_ref_n`].
90#[inline(always)]
91pub unsafe fn lean_inc_ref(o: *mut lean_object) {
92    // SAFETY: forwards to lean_inc_ref_n with the same precondition.
93    unsafe { lean_inc_ref_n(o, 1) }
94}
95
96/// Bump `o`'s refcount by one, or no-op for scalar-tagged pointers
97/// (`lean.h:561`).
98///
99/// # Safety
100///
101/// `o` must be either a scalar-tagged pointer (low bit set) or a valid Lean
102/// heap object. The scalar branch is unconditionally safe; the heap branch
103/// inherits [`lean_inc_ref`]'s precondition.
104#[inline(always)]
105pub unsafe fn lean_inc(o: *mut lean_object) {
106    // SAFETY: scalar check inspects pointer bits only; the heap branch
107    // inherits `lean_inc_ref`'s precondition (non-scalar object).
108    unsafe {
109        if !lean_is_scalar(o) {
110            lean_inc_ref(o);
111        }
112    }
113}
114
115/// Bump `o`'s refcount by `n`, or no-op for scalar-tagged pointers
116/// (`lean.h:562`).
117///
118/// # Safety
119///
120/// Same as [`lean_inc`].
121#[inline(always)]
122pub unsafe fn lean_inc_n(o: *mut lean_object, n: usize) {
123    // SAFETY: scalar check inspects pointer bits only; the heap branch
124    // inherits `lean_inc_ref_n`'s precondition (non-scalar object).
125    unsafe {
126        if !lean_is_scalar(o) {
127            lean_inc_ref_n(o, n);
128        }
129    }
130}
131
132/// Decrement `o`'s refcount, taking the cold path to free if it reaches zero
133/// (`lean.h:554–560`).
134///
135/// # Safety
136///
137/// `o` must be a valid non-scalar Lean heap object. The runtime invariant
138/// that `m_rc == 0` means "persistent, no refcounting" is honoured: this
139/// function is a no-op in that case.
140///
141/// # Panics
142///
143/// The single-threaded fast path only runs when `m_rc > 1`, so subtracting
144/// 1 cannot underflow; the panic guard exists to catch a future invariant
145/// breach (e.g. unsynchronized concurrent mutation) rather than a
146/// recoverable condition.
147#[inline(always)]
148pub unsafe fn lean_dec_ref(o: *mut lean_object) {
149    // SAFETY: precondition above; cold path is `LEAN_EXPORT`'d.
150    let rc = unsafe { rc_atom(o) };
151    let cur = rc.load(Ordering::Relaxed);
152    if cur > 1 {
153        // `cur > 1` so subtracting 1 cannot wrap; `strict_sub` surfaces any
154        // future invariant breach as a panic instead of silently wrapping.
155        rc.store(cur.strict_sub(1), Ordering::Relaxed);
156    } else if cur != 0 {
157        // Either ST with cur==1, or MT with negated count whose abs value is
158        // the live count. The C source calls `lean_dec_ref_cold` in either
159        // case; the cold path handles MT subtraction internally.
160        // SAFETY: cold path is the only safe way to free; it takes ownership.
161        unsafe { lean_dec_ref_cold(o) }
162    }
163}
164
165/// Decrement `o`'s refcount, or no-op for scalar-tagged pointers
166/// (`lean.h:563`).
167///
168/// # Safety
169///
170/// Same as [`lean_inc`].
171#[inline(always)]
172pub unsafe fn lean_dec(o: *mut lean_object) {
173    // SAFETY: scalar check inspects pointer bits only; heap branch inherits
174    // `lean_dec_ref`'s precondition (non-scalar Lean heap object).
175    unsafe {
176        if !lean_is_scalar(o) {
177            lean_dec_ref(o);
178        }
179    }
180}