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}
263
264#[cfg(test)]
265mod tests {
266 //! Pure-Rust coverage of scalar encoding and the header/runtime-mode
267 //! reads over a synthetic, Rust-owned object header. No allocation crosses
268 //! into the Lean runtime, so these run unchanged under `cargo miri test`,
269 //! validating the `o.cast::<LeanObjectRepr>()` field-offset provenance and
270 //! the `AtomicI32::from_ptr` read in `load_rc`.
271
272 use super::{
273 lean_box, lean_is_ctor, lean_is_exclusive, lean_is_mt, lean_is_persistent, lean_is_scalar, lean_is_shared,
274 lean_is_st, lean_is_string, lean_obj_tag, lean_ptr_other, lean_ptr_tag, lean_unbox,
275 };
276 use crate::consts::{LEAN_MAX_CTOR_TAG, LEAN_STRING};
277 use crate::repr::test_support::MockObject;
278
279 #[test]
280 fn scalar_box_unbox_round_trips() {
281 // SAFETY: pointer-bit arithmetic only; no memory access.
282 unsafe {
283 for n in [0_usize, 1, 42, usize::MAX >> 1] {
284 let boxed = lean_box(n);
285 assert!(lean_is_scalar(boxed));
286 assert_eq!(lean_unbox(boxed), n);
287 // For scalars, the logical tag is the unboxed payload.
288 assert_eq!(lean_obj_tag(boxed), n as u32);
289 }
290 }
291 }
292
293 #[test]
294 fn header_tag_and_other_read_back() {
295 let obj = MockObject::new(1, LEAN_STRING, 0x5a);
296 let o = obj.ptr();
297 // SAFETY: `o` is a valid non-scalar header; these read header bytes
298 // through the `LeanObjectRepr` cast.
299 unsafe {
300 assert!(!lean_is_scalar(o));
301 assert_eq!(lean_ptr_tag(o), LEAN_STRING);
302 assert_eq!(lean_ptr_other(o), 0x5a);
303 assert!(lean_is_string(o));
304 assert_eq!(lean_obj_tag(o), u32::from(LEAN_STRING));
305 }
306 }
307
308 #[test]
309 fn ctor_tag_predicate_tracks_the_boundary() {
310 let ctor = MockObject::new(1, LEAN_MAX_CTOR_TAG, 0);
311 let beyond = MockObject::new(1, LEAN_STRING, 0);
312 // SAFETY: both are valid non-scalar headers.
313 unsafe {
314 assert!(lean_is_ctor(ctor.ptr()));
315 assert!(!lean_is_ctor(beyond.ptr()));
316 }
317 }
318
319 #[test]
320 fn runtime_mode_reads_match_refcount_sign() {
321 // Single-threaded exclusive (rc == 1).
322 let st = MockObject::new(1, 0, 0);
323 // Single-threaded shared (rc > 1).
324 let shared = MockObject::new(3, 0, 0);
325 // Multi-threaded (rc < 0, negated live count).
326 let mt = MockObject::new(-2, 0, 0);
327 // Persistent (rc == 0).
328 let persistent = MockObject::new(0, 0, 0);
329 // SAFETY: all four are valid non-scalar headers; the reads go through
330 // `load_rc`'s `AtomicI32::from_ptr` relaxed load.
331 unsafe {
332 assert!(lean_is_st(st.ptr()) && lean_is_exclusive(st.ptr()));
333 assert!(lean_is_shared(shared.ptr()) && !lean_is_exclusive(shared.ptr()));
334 assert!(lean_is_mt(mt.ptr()) && !lean_is_st(mt.ptr()));
335 assert!(lean_is_persistent(persistent.ptr()) && !lean_is_st(persistent.ptr()));
336 }
337 }
338}