Skip to main content

lean_rs_sys/
object.rs

1//! Object inspection and allocation helpers — mirrors `lean.h:312–630`.
2//!
3//! Covers scalar-pointer encoding ([`lean_is_scalar`]), tag reads, the
4//! `lean_is_*` predicates, the `lean_to_*` casts (returned as raw `*mut
5//! lean_object` for opacity), and the runtime-mode reads ([`lean_is_st`],
6//! [`lean_is_mt`], [`lean_is_persistent`], [`lean_is_exclusive`],
7//! [`lean_is_shared`]). Allocation primitives exported by `libleanshared`
8//! (`lean_alloc_object`, `lean_free_object`) are declared here; the
9//! higher-level inline allocators (`lean_alloc_ctor`, `lean_alloc_closure`,
10//! `lean_alloc_array`, …) live in their category modules.
11
12#![allow(clippy::inline_always)]
13
14use core::sync::atomic::{AtomicI32, Ordering};
15
16use crate::consts::{
17    LEAN_ARRAY, LEAN_CLOSURE, LEAN_EXTERNAL, LEAN_MAX_CTOR_TAG, LEAN_MPZ, LEAN_PROMISE, LEAN_REF, LEAN_SCALAR_ARRAY,
18    LEAN_STRING, LEAN_TASK, LEAN_THUNK,
19};
20use crate::repr::LeanObjectRepr;
21use crate::types::lean_object;
22
23unsafe extern "C" {
24    /// Allocate an uninitialized Lean heap object of size `sz` bytes
25    /// (`lean.h:490`). The caller is responsible for initializing the
26    /// header via `lean_set_st_header`-equivalent writes, which only this
27    /// crate's helpers should perform.
28    pub fn lean_alloc_object(sz: usize) -> *mut lean_object;
29
30    /// Free a Lean heap object previously allocated with
31    /// `lean_alloc_object` (`lean.h:491`).
32    pub fn lean_free_object(o: *mut lean_object);
33
34    /// Total byte size of `o`'s allocation (`lean.h:506`).
35    pub fn lean_object_byte_size(o: *mut lean_object) -> usize;
36
37    /// Byte size of `o`'s salient (initialized) storage
38    /// (`lean.h:513`).
39    pub fn lean_object_data_byte_size(o: *mut lean_object) -> usize;
40}
41
42/// Scalar-pointer test (`lean.h:312`). Scalar values are tagged with the
43/// low bit set; the pointer never aliases a real allocation.
44///
45/// # Safety
46///
47/// No precondition: this only inspects the pointer bits. It is safe to call
48/// on any value that the runtime might hand us, including null and
49/// uninitialized values.
50#[inline(always)]
51pub unsafe fn lean_is_scalar(o: *mut lean_object) -> bool {
52    (o as usize) & 1 == 1
53}
54
55/// Box an unsigned integer into a scalar Lean object (`lean.h:313`).
56///
57/// # Safety
58///
59/// Pointer-bit arithmetic only; no memory access. The caller is responsible
60/// for ensuring `n` fits inside `usize >> 1` if the value is intended to be
61/// recoverable via [`lean_unbox`].
62#[inline(always)]
63pub unsafe fn lean_box(n: usize) -> *mut lean_object {
64    ((n << 1) | 1) as *mut lean_object
65}
66
67/// Unbox a scalar Lean object (`lean.h:314`).
68///
69/// # Safety
70///
71/// `o` must be scalar-tagged (low bit set). Otherwise the returned `usize`
72/// is the raw pointer right-shifted by one and meaningless.
73#[inline(always)]
74pub unsafe fn lean_unbox(o: *mut lean_object) -> usize {
75    (o as usize) >> 1
76}
77
78#[inline(always)]
79unsafe fn header<'a>(o: *mut lean_object) -> &'a LeanObjectRepr {
80    // SAFETY: caller guarantees `o` is a valid non-scalar heap pointer;
81    // layout pinned by build digest.
82    unsafe { &*o.cast::<LeanObjectRepr>() }
83}
84
85#[inline(always)]
86unsafe fn load_rc(o: *mut lean_object) -> i32 {
87    // SAFETY: caller guarantees `o` is a valid non-scalar heap pointer.
88    // We materialize a safe `&AtomicI32` for the `Relaxed` load even on the
89    // single-threaded path so reads stay consistent with concurrent
90    // mutations from `lean_inc*` / `lean_dec*` on MT objects.
91    unsafe {
92        let repr = o.cast::<LeanObjectRepr>();
93        AtomicI32::from_ptr(&raw mut (*repr).m_rc).load(Ordering::Relaxed)
94    }
95}
96
97/// Read the object's tag byte (`lean.h:493–495`).
98///
99/// # Safety
100///
101/// `o` must be a valid non-scalar heap object pointer.
102#[inline(always)]
103pub unsafe fn lean_ptr_tag(o: *mut lean_object) -> u8 {
104    // SAFETY: precondition above.
105    unsafe { header(o).m_tag }
106}
107
108/// Read the object's `m_other` byte (`lean.h:497–499`).
109///
110/// # Safety
111///
112/// Same as [`lean_ptr_tag`].
113#[inline(always)]
114pub unsafe fn lean_ptr_other(o: *mut lean_object) -> u8 {
115    // SAFETY: precondition above.
116    unsafe { header(o).m_other }
117}
118
119macro_rules! is_tag {
120    ($name:ident, $tag:expr, $doc:expr) => {
121        #[doc = $doc]
122        ///
123        /// # Safety
124        ///
125        /// `o` must be a valid non-scalar Lean heap object pointer.
126        #[inline(always)]
127        pub unsafe fn $name(o: *mut lean_object) -> bool {
128            // SAFETY: precondition above.
129            unsafe { lean_ptr_tag(o) == $tag }
130        }
131    };
132}
133
134/// Constructor objects share tags `0..=LEAN_MAX_CTOR_TAG`; this is the
135/// `lean_is_ctor` predicate from `lean.h:565`.
136///
137/// # Safety
138///
139/// `o` must be a valid non-scalar Lean heap object pointer.
140#[inline(always)]
141pub unsafe fn lean_is_ctor(o: *mut lean_object) -> bool {
142    // SAFETY: precondition above.
143    unsafe { lean_ptr_tag(o) <= LEAN_MAX_CTOR_TAG }
144}
145
146is_tag!(
147    lean_is_closure,
148    LEAN_CLOSURE,
149    "True if `o` is a closure object (`lean.h:566`)."
150);
151is_tag!(
152    lean_is_array,
153    LEAN_ARRAY,
154    "True if `o` is an object array (`lean.h:567`)."
155);
156is_tag!(
157    lean_is_sarray,
158    LEAN_SCALAR_ARRAY,
159    "True if `o` is a scalar array (`lean.h:568`)."
160);
161is_tag!(lean_is_string, LEAN_STRING, "True if `o` is a string (`lean.h:569`).");
162is_tag!(
163    lean_is_mpz,
164    LEAN_MPZ,
165    "True if `o` is an MPZ (big integer) object (`lean.h:570`)."
166);
167is_tag!(
168    lean_is_thunk,
169    LEAN_THUNK,
170    "True if `o` is a thunk object (`lean.h:571`)."
171);
172is_tag!(lean_is_task, LEAN_TASK, "True if `o` is a task object (`lean.h:572`).");
173is_tag!(
174    lean_is_promise,
175    LEAN_PROMISE,
176    "True if `o` is a promise object (`lean.h:573`)."
177);
178is_tag!(
179    lean_is_external,
180    LEAN_EXTERNAL,
181    "True if `o` is an external object (`lean.h:574`)."
182);
183is_tag!(
184    lean_is_ref,
185    LEAN_REF,
186    "True if `o` is a Lean `Ref` object (`lean.h:575`)."
187);
188
189/// Read the object's "logical" tag (`lean.h:577–579`): for scalar values
190/// this is the unboxed payload; otherwise it is the heap tag.
191///
192/// # Safety
193///
194/// `o` must be either a scalar-tagged pointer or a valid non-scalar Lean
195/// heap object pointer.
196#[inline(always)]
197pub unsafe fn lean_obj_tag(o: *mut lean_object) -> u32 {
198    // SAFETY: scalar check first; heap branch inherits lean_ptr_tag's contract.
199    unsafe {
200        if lean_is_scalar(o) {
201            lean_unbox(o) as u32
202        } else {
203            u32::from(lean_ptr_tag(o))
204        }
205    }
206}
207
208/// Single-threaded test (`lean.h:519–521`).
209///
210/// # Safety
211///
212/// `o` must be a valid non-scalar Lean heap object pointer.
213#[inline(always)]
214pub unsafe fn lean_is_st(o: *mut lean_object) -> bool {
215    // SAFETY: precondition above.
216    unsafe { load_rc(o) > 0 }
217}
218
219/// Multi-threaded test (`lean.h:515–517`).
220///
221/// # Safety
222///
223/// Same as [`lean_is_st`].
224#[inline(always)]
225pub unsafe fn lean_is_mt(o: *mut lean_object) -> bool {
226    // SAFETY: precondition above.
227    unsafe { load_rc(o) < 0 }
228}
229
230/// Persistent test (`lean.h:524–526`).
231///
232/// # Safety
233///
234/// Same as [`lean_is_st`].
235#[inline(always)]
236pub unsafe fn lean_is_persistent(o: *mut lean_object) -> bool {
237    // SAFETY: precondition above.
238    unsafe { load_rc(o) == 0 }
239}
240
241/// True if `o` is single-threaded with refcount exactly one
242/// (`lean.h:592–598`).
243///
244/// # Safety
245///
246/// Same as [`lean_is_st`].
247#[inline(always)]
248pub unsafe fn lean_is_exclusive(o: *mut lean_object) -> bool {
249    // SAFETY: precondition above.
250    unsafe { load_rc(o) == 1 }
251}
252
253/// True if `o` is single-threaded with refcount > 1 (`lean.h:604–610`).
254///
255/// # Safety
256///
257/// Same as [`lean_is_st`].
258#[inline(always)]
259pub unsafe fn lean_is_shared(o: *mut lean_object) -> bool {
260    // SAFETY: precondition above.
261    unsafe { load_rc(o) > 1 }
262}