#![allow(clippy::inline_always)]
use core::sync::atomic::{AtomicI32, Ordering};
use crate::consts::{
LEAN_ARRAY, LEAN_CLOSURE, LEAN_EXTERNAL, LEAN_MAX_CTOR_TAG, LEAN_MPZ, LEAN_PROMISE, LEAN_REF, LEAN_SCALAR_ARRAY,
LEAN_STRING, LEAN_TASK, LEAN_THUNK,
};
use crate::repr::LeanObjectRepr;
use crate::types::lean_object;
unsafe extern "C" {
pub fn lean_alloc_object(sz: usize) -> *mut lean_object;
pub fn lean_free_object(o: *mut lean_object);
pub fn lean_object_byte_size(o: *mut lean_object) -> usize;
pub fn lean_object_data_byte_size(o: *mut lean_object) -> usize;
}
#[inline(always)]
pub unsafe fn lean_is_scalar(o: *mut lean_object) -> bool {
(o as usize) & 1 == 1
}
#[inline(always)]
pub unsafe fn lean_box(n: usize) -> *mut lean_object {
((n << 1) | 1) as *mut lean_object
}
#[inline(always)]
pub unsafe fn lean_unbox(o: *mut lean_object) -> usize {
(o as usize) >> 1
}
#[inline(always)]
unsafe fn header<'a>(o: *mut lean_object) -> &'a LeanObjectRepr {
unsafe { &*o.cast::<LeanObjectRepr>() }
}
#[inline(always)]
unsafe fn load_rc(o: *mut lean_object) -> i32 {
unsafe {
let repr = o.cast::<LeanObjectRepr>();
AtomicI32::from_ptr(&raw mut (*repr).m_rc).load(Ordering::Relaxed)
}
}
#[inline(always)]
pub unsafe fn lean_ptr_tag(o: *mut lean_object) -> u8 {
unsafe { header(o).m_tag }
}
#[inline(always)]
pub unsafe fn lean_ptr_other(o: *mut lean_object) -> u8 {
unsafe { header(o).m_other }
}
macro_rules! is_tag {
($name:ident, $tag:expr, $doc:expr) => {
#[doc = $doc]
#[inline(always)]
pub unsafe fn $name(o: *mut lean_object) -> bool {
unsafe { lean_ptr_tag(o) == $tag }
}
};
}
#[inline(always)]
pub unsafe fn lean_is_ctor(o: *mut lean_object) -> bool {
unsafe { lean_ptr_tag(o) <= LEAN_MAX_CTOR_TAG }
}
is_tag!(
lean_is_closure,
LEAN_CLOSURE,
"True if `o` is a closure object (`lean.h:566`)."
);
is_tag!(
lean_is_array,
LEAN_ARRAY,
"True if `o` is an object array (`lean.h:567`)."
);
is_tag!(
lean_is_sarray,
LEAN_SCALAR_ARRAY,
"True if `o` is a scalar array (`lean.h:568`)."
);
is_tag!(lean_is_string, LEAN_STRING, "True if `o` is a string (`lean.h:569`).");
is_tag!(
lean_is_mpz,
LEAN_MPZ,
"True if `o` is an MPZ (big integer) object (`lean.h:570`)."
);
is_tag!(
lean_is_thunk,
LEAN_THUNK,
"True if `o` is a thunk object (`lean.h:571`)."
);
is_tag!(lean_is_task, LEAN_TASK, "True if `o` is a task object (`lean.h:572`).");
is_tag!(
lean_is_promise,
LEAN_PROMISE,
"True if `o` is a promise object (`lean.h:573`)."
);
is_tag!(
lean_is_external,
LEAN_EXTERNAL,
"True if `o` is an external object (`lean.h:574`)."
);
is_tag!(
lean_is_ref,
LEAN_REF,
"True if `o` is a Lean `Ref` object (`lean.h:575`)."
);
#[inline(always)]
pub unsafe fn lean_obj_tag(o: *mut lean_object) -> u32 {
unsafe {
if lean_is_scalar(o) {
lean_unbox(o) as u32
} else {
u32::from(lean_ptr_tag(o))
}
}
}
#[inline(always)]
pub unsafe fn lean_is_st(o: *mut lean_object) -> bool {
unsafe { load_rc(o) > 0 }
}
#[inline(always)]
pub unsafe fn lean_is_mt(o: *mut lean_object) -> bool {
unsafe { load_rc(o) < 0 }
}
#[inline(always)]
pub unsafe fn lean_is_persistent(o: *mut lean_object) -> bool {
unsafe { load_rc(o) == 0 }
}
#[inline(always)]
pub unsafe fn lean_is_exclusive(o: *mut lean_object) -> bool {
unsafe { load_rc(o) == 1 }
}
#[inline(always)]
pub unsafe fn lean_is_shared(o: *mut lean_object) -> bool {
unsafe { load_rc(o) > 1 }
}
#[cfg(test)]
mod tests {
use super::{
lean_box, lean_is_ctor, lean_is_exclusive, lean_is_mt, lean_is_persistent, lean_is_scalar, lean_is_shared,
lean_is_st, lean_is_string, lean_obj_tag, lean_ptr_other, lean_ptr_tag, lean_unbox,
};
use crate::consts::{LEAN_MAX_CTOR_TAG, LEAN_STRING};
use crate::repr::test_support::MockObject;
#[test]
fn scalar_box_unbox_round_trips() {
unsafe {
for n in [0_usize, 1, 42, usize::MAX >> 1] {
let boxed = lean_box(n);
assert!(lean_is_scalar(boxed));
assert_eq!(lean_unbox(boxed), n);
assert_eq!(lean_obj_tag(boxed), n as u32);
}
}
}
#[test]
fn header_tag_and_other_read_back() {
let obj = MockObject::new(1, LEAN_STRING, 0x5a);
let o = obj.ptr();
unsafe {
assert!(!lean_is_scalar(o));
assert_eq!(lean_ptr_tag(o), LEAN_STRING);
assert_eq!(lean_ptr_other(o), 0x5a);
assert!(lean_is_string(o));
assert_eq!(lean_obj_tag(o), u32::from(LEAN_STRING));
}
}
#[test]
fn ctor_tag_predicate_tracks_the_boundary() {
let ctor = MockObject::new(1, LEAN_MAX_CTOR_TAG, 0);
let beyond = MockObject::new(1, LEAN_STRING, 0);
unsafe {
assert!(lean_is_ctor(ctor.ptr()));
assert!(!lean_is_ctor(beyond.ptr()));
}
}
#[test]
fn runtime_mode_reads_match_refcount_sign() {
let st = MockObject::new(1, 0, 0);
let shared = MockObject::new(3, 0, 0);
let mt = MockObject::new(-2, 0, 0);
let persistent = MockObject::new(0, 0, 0);
unsafe {
assert!(lean_is_st(st.ptr()) && lean_is_exclusive(st.ptr()));
assert!(lean_is_shared(shared.ptr()) && !lean_is_exclusive(shared.ptr()));
assert!(lean_is_mt(mt.ptr()) && !lean_is_st(mt.ptr()));
assert!(lean_is_persistent(persistent.ptr()) && !lean_is_st(persistent.ptr()));
}
}
}