seq_runtime/
tagged_stack.rs

1//! Tagged Stack Implementation
2//!
3//! A contiguous array of 40-byte values for high-performance stack operations.
4//! The 40-byte size matches Rust's `Value` enum with `#[repr(C)]` and the LLVM
5//! `%Value = type { i64, i64, i64, i64, i64 }` layout, enabling interoperability
6//! between inline IR and FFI operations.
7//!
8//! ## Stack Value Layout (40 bytes)
9//!
10//! ```text
11//! ┌─────────────────────────────────────────────────────────────────────────────┐
12//! │  slot0 (8 bytes)  │  slot1  │  slot2  │  slot3  │  slot4  │
13//! ├───────────────────┼─────────┼─────────┼─────────┼─────────┤
14//! │   Discriminant    │ Payload │  data   │  data   │  data   │
15//! └─────────────────────────────────────────────────────────────────────────────┘
16//!
17//! Value discriminants:
18//! - 0 = Int:   slot1 contains i64 value
19//! - 1 = Float: slot1 contains f64 bits
20//! - 2 = Bool:  slot1 contains 0 or 1
21//! - 3 = String, 4 = Variant, 5 = Map, 6 = Quotation, 7 = Closure
22//! ```
23//!
24//! ## Stack Layout
25//!
26//! ```text
27//! Stack: contiguous array of 40-byte StackValue slots
28//! ┌──────────┬──────────┬──────────┬──────────┬─────────┐
29//! │   v0     │   v1     │   v2     │   v3     │  ...    │
30//! │ (40 B)   │ (40 B)   │ (40 B)   │ (40 B)   │         │
31//! └──────────┴──────────┴──────────┴──────────┴─────────┘
32//!                                              ↑ SP
33//!
34//! - Grows upward
35//! - SP points to next free slot
36//! - Push: store at SP, increment SP
37//! - Pop: decrement SP, load from SP
38//! ```
39//!
40//! ## Performance Considerations
41//!
42//! The 40-byte size enables:
43//! - Direct compatibility with Rust's Value enum (#[repr(C)])
44//! - No conversion overhead when calling runtime functions
45//! - Simple inline integer/boolean operations in compiled code
46
47use std::alloc::{Layout, alloc, dealloc, realloc};
48
49// =============================================================================
50// StackValue
51// =============================================================================
52
53/// A 40-byte stack value, layout-compatible with LLVM's %Value type.
54///
55/// This matches `%Value = type { i64, i64, i64, i64, i64 }` in the generated IR.
56/// The size matches Rust's Value enum with #[repr(C)].
57#[repr(C)]
58#[derive(Clone, Copy, Default)]
59pub struct StackValue {
60    /// First slot: discriminant (0=Int, 1=Float, 2=Bool, 3=String, etc.)
61    pub slot0: u64,
62    /// Second slot: primary payload (i64 value for Int, bool for Bool, etc.)
63    pub slot1: u64,
64    /// Third slot: type-specific data
65    pub slot2: u64,
66    /// Fourth slot: type-specific data
67    pub slot3: u64,
68    /// Fifth slot: type-specific data (for largest variant)
69    pub slot4: u64,
70}
71
72impl std::fmt::Debug for StackValue {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        // Discriminant 0 = Int
75        if self.slot0 == 0 {
76            write!(f, "Int({})", self.slot1 as i64)
77        } else if self.slot0 == 2 {
78            // Discriminant 2 = Bool
79            write!(f, "Bool({})", self.slot1 != 0)
80        } else {
81            write!(
82                f,
83                "StackValue {{ slot0: 0x{:x}, slot1: 0x{:x}, slot2: 0x{:x}, slot3: 0x{:x}, slot4: 0x{:x} }}",
84                self.slot0, self.slot1, self.slot2, self.slot3, self.slot4
85            )
86        }
87    }
88}
89
90/// Size of StackValue in bytes (40 bytes = 5 x u64)
91pub const STACK_VALUE_SIZE: usize = std::mem::size_of::<StackValue>();
92
93// Compile-time assertion for StackValue size
94const _: () = assert!(STACK_VALUE_SIZE == 40, "StackValue must be 40 bytes");
95
96/// Legacy type alias for backward compatibility
97pub type TaggedValue = u64;
98
99/// Tag bit mask
100pub const TAG_MASK: u64 = 1;
101
102/// Integer tag (low bit set)
103pub const TAG_INT: u64 = 1;
104
105/// Heap pointer tag (low bit clear)
106pub const TAG_HEAP: u64 = 0;
107
108/// Check if a tagged value is an integer
109#[inline(always)]
110pub const fn is_tagged_int(val: TaggedValue) -> bool {
111    (val & TAG_MASK) == TAG_INT
112}
113
114/// Check if a tagged value is a heap pointer
115#[inline(always)]
116pub const fn is_tagged_heap(val: TaggedValue) -> bool {
117    (val & TAG_MASK) == TAG_HEAP
118}
119
120/// Create a tagged integer from an i64
121///
122/// Note: Only 63 bits of the integer are preserved.
123/// Range: -4,611,686,018,427,387,904 to 4,611,686,018,427,387,903
124#[inline(always)]
125pub const fn tag_int(val: i64) -> TaggedValue {
126    // Arithmetic left shift preserves sign, then set tag bit
127    ((val << 1) as u64) | TAG_INT
128}
129
130/// Extract an i64 from a tagged integer
131///
132/// Caller must ensure `is_tagged_int(val)` is true.
133#[inline(always)]
134pub const fn untag_int(val: TaggedValue) -> i64 {
135    // Arithmetic right shift to restore sign
136    (val as i64) >> 1
137}
138
139/// Create a tagged heap pointer from a raw pointer
140///
141/// The pointer must be 8-byte aligned (low 3 bits clear).
142#[inline(always)]
143pub fn tag_heap_ptr(ptr: *mut HeapObject) -> TaggedValue {
144    debug_assert!(
145        (ptr as usize) & 0x7 == 0,
146        "HeapObject pointer must be 8-byte aligned"
147    );
148    ptr as TaggedValue
149}
150
151/// Extract a heap pointer from a tagged value
152///
153/// Caller must ensure `is_tagged_heap(val)` is true.
154#[inline(always)]
155pub fn untag_heap_ptr(val: TaggedValue) -> *mut HeapObject {
156    val as *mut HeapObject
157}
158
159/// Heap object type tags
160#[repr(u8)]
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub enum HeapTag {
163    Float = 1,
164    Bool = 2,
165    String = 3,
166    Variant = 4,
167    Map = 5,
168    Quotation = 6,
169    Closure = 7,
170}
171
172/// Heap object header (8 bytes)
173///
174/// All heap-allocated values share this header for uniform access.
175#[repr(C)]
176pub struct HeapObject {
177    /// Type tag identifying the payload type
178    pub tag: u8,
179    /// Flags (e.g., is_static for non-refcounted values)
180    pub flags: u8,
181    /// Reserved for future use
182    pub reserved: u16,
183    /// Reference count (atomic for thread safety)
184    pub refcount: u32,
185    // Payload follows (variable size based on tag)
186}
187
188/// Flags for HeapObject
189pub mod heap_flags {
190    /// Object is statically allocated, don't decrement refcount
191    pub const IS_STATIC: u8 = 0x01;
192}
193
194impl HeapObject {
195    /// Check if this object is statically allocated
196    #[inline(always)]
197    pub fn is_static(&self) -> bool {
198        self.flags & heap_flags::IS_STATIC != 0
199    }
200}
201
202/// Float heap object
203#[repr(C)]
204pub struct FloatObject {
205    pub header: HeapObject,
206    pub value: f64,
207}
208
209/// Bool heap object
210#[repr(C)]
211pub struct BoolObject {
212    pub header: HeapObject,
213    pub value: bool,
214}
215
216/// Quotation heap object (stateless function)
217#[repr(C)]
218pub struct QuotationObject {
219    pub header: HeapObject,
220    /// C-convention wrapper function pointer
221    pub wrapper: usize,
222    /// tailcc implementation function pointer
223    pub impl_ptr: usize,
224}
225
226/// Default stack capacity (number of stack values)
227pub const DEFAULT_STACK_CAPACITY: usize = 4096;
228
229/// Stack state for the tagged value stack
230///
231/// This struct is passed to/from runtime functions and represents
232/// the complete state of a strand's value stack.
233///
234/// Uses 32-byte StackValue slots for FFI compatibility.
235#[repr(C)]
236pub struct TaggedStack {
237    /// Pointer to the base of the stack array (array of StackValue)
238    pub base: *mut StackValue,
239    /// Current stack pointer (index into array, points to next free slot)
240    pub sp: usize,
241    /// Total capacity of the stack (number of slots)
242    pub capacity: usize,
243}
244
245impl TaggedStack {
246    /// Create a new tagged stack with the given capacity
247    pub fn new(capacity: usize) -> Self {
248        let layout = Layout::array::<StackValue>(capacity).expect("stack layout overflow");
249        let base = unsafe { alloc(layout) as *mut StackValue };
250        if base.is_null() {
251            panic!("Failed to allocate tagged stack");
252        }
253
254        TaggedStack {
255            base,
256            sp: 0,
257            capacity,
258        }
259    }
260
261    /// Create a new tagged stack with default capacity
262    pub fn with_default_capacity() -> Self {
263        Self::new(DEFAULT_STACK_CAPACITY)
264    }
265
266    /// Get the current stack depth
267    #[inline(always)]
268    pub fn depth(&self) -> usize {
269        self.sp
270    }
271
272    /// Check if the stack is empty
273    #[inline(always)]
274    pub fn is_empty(&self) -> bool {
275        self.sp == 0
276    }
277
278    /// Check if the stack has room for `n` more values
279    #[inline(always)]
280    pub fn has_capacity(&self, n: usize) -> bool {
281        self.sp + n <= self.capacity
282    }
283
284    /// Grow the stack to accommodate more values
285    ///
286    /// Doubles capacity by default, or grows to `min_capacity` if larger.
287    pub fn grow(&mut self, min_capacity: usize) {
288        let new_capacity = (self.capacity * 2).max(min_capacity);
289        let old_layout = Layout::array::<StackValue>(self.capacity).expect("old layout overflow");
290        let new_layout = Layout::array::<StackValue>(new_capacity).expect("new layout overflow");
291
292        let new_base = unsafe {
293            realloc(self.base as *mut u8, old_layout, new_layout.size()) as *mut StackValue
294        };
295
296        if new_base.is_null() {
297            panic!(
298                "Failed to grow tagged stack from {} to {}",
299                self.capacity, new_capacity
300            );
301        }
302
303        self.base = new_base;
304        self.capacity = new_capacity;
305    }
306
307    /// Push a StackValue onto the stack
308    ///
309    /// Grows the stack if necessary.
310    #[inline]
311    pub fn push(&mut self, val: StackValue) {
312        if self.sp >= self.capacity {
313            self.grow(self.capacity + 1);
314        }
315        unsafe {
316            *self.base.add(self.sp) = val;
317        }
318        self.sp += 1;
319    }
320
321    /// Pop a StackValue from the stack
322    ///
323    /// Panics if the stack is empty.
324    #[inline]
325    pub fn pop(&mut self) -> StackValue {
326        assert!(self.sp > 0, "pop: stack is empty");
327        self.sp -= 1;
328        unsafe { *self.base.add(self.sp) }
329    }
330
331    /// Peek at the top value without removing it
332    ///
333    /// Panics if the stack is empty.
334    #[inline]
335    pub fn peek(&self) -> StackValue {
336        assert!(self.sp > 0, "peek: stack is empty");
337        unsafe { *self.base.add(self.sp - 1) }
338    }
339
340    /// Get a pointer to the current stack pointer position
341    ///
342    /// This is used by generated code for inline stack operations.
343    /// Returns pointer to next free StackValue slot.
344    #[inline(always)]
345    pub fn sp_ptr(&self) -> *mut StackValue {
346        unsafe { self.base.add(self.sp) }
347    }
348
349    /// Push an integer value using Value::Int layout
350    /// slot0 = 0 (Int discriminant), slot1 = value
351    #[inline]
352    pub fn push_int(&mut self, val: i64) {
353        self.push(StackValue {
354            slot0: 0, // Int discriminant
355            slot1: val as u64,
356            slot2: 0,
357            slot3: 0,
358            slot4: 0,
359        });
360    }
361
362    /// Pop and return an integer value
363    ///
364    /// Panics if the top value is not an integer.
365    #[inline]
366    pub fn pop_int(&mut self) -> i64 {
367        let val = self.pop();
368        assert!(
369            val.slot0 == 0,
370            "pop_int: expected Int (discriminant 0), got discriminant {}",
371            val.slot0
372        );
373        val.slot1 as i64
374    }
375
376    /// Clone this stack (for spawn)
377    ///
378    /// Creates a deep copy. For heap objects, properly clones them using
379    /// the clone_stack_value function which handles each type correctly.
380    pub fn clone_stack(&self) -> Self {
381        use crate::stack::clone_stack_value;
382
383        // Allocate new stack array
384        let layout = Layout::array::<StackValue>(self.capacity).expect("layout overflow");
385        let new_base = unsafe { alloc(layout) as *mut StackValue };
386        if new_base.is_null() {
387            panic!("Failed to allocate cloned stack");
388        }
389
390        // Clone each value properly (handles heap types correctly)
391        for i in 0..self.sp {
392            unsafe {
393                let sv = &*self.base.add(i);
394                let cloned = clone_stack_value(sv);
395                *new_base.add(i) = cloned;
396            }
397        }
398
399        TaggedStack {
400            base: new_base,
401            sp: self.sp,
402            capacity: self.capacity,
403        }
404    }
405}
406
407impl Drop for TaggedStack {
408    fn drop(&mut self) {
409        use crate::stack::drop_stack_value;
410
411        // Drop all values properly (handles heap types correctly)
412        for i in 0..self.sp {
413            unsafe {
414                let sv = *self.base.add(i);
415                drop_stack_value(sv);
416            }
417        }
418
419        // Free the stack array
420        if !self.base.is_null() {
421            let layout = Layout::array::<StackValue>(self.capacity).expect("layout overflow");
422            unsafe {
423                dealloc(self.base as *mut u8, layout);
424            }
425        }
426    }
427}
428
429// =============================================================================
430// FFI Functions for LLVM Codegen
431// =============================================================================
432
433/// Allocate a new tagged stack
434///
435/// Returns a pointer to a heap-allocated TaggedStack.
436/// The caller owns this memory and must call `seq_stack_free` when done.
437#[unsafe(no_mangle)]
438pub extern "C" fn seq_stack_new(capacity: usize) -> *mut TaggedStack {
439    let stack = Box::new(TaggedStack::new(capacity));
440    Box::into_raw(stack)
441}
442
443/// Allocate a new tagged stack with default capacity
444#[unsafe(no_mangle)]
445pub extern "C" fn seq_stack_new_default() -> *mut TaggedStack {
446    let stack = Box::new(TaggedStack::with_default_capacity());
447    Box::into_raw(stack)
448}
449
450/// Free a tagged stack
451///
452/// # Safety
453/// The pointer must have been returned by `seq_stack_new` or `seq_stack_new_default`.
454#[unsafe(no_mangle)]
455pub unsafe extern "C" fn seq_stack_free(stack: *mut TaggedStack) {
456    if !stack.is_null() {
457        unsafe {
458            drop(Box::from_raw(stack));
459        }
460    }
461}
462
463/// Grow a tagged stack to at least the given capacity
464///
465/// # Safety
466/// `stack` must be a valid pointer to a TaggedStack.
467#[unsafe(no_mangle)]
468pub unsafe extern "C" fn seq_stack_grow(stack: *mut TaggedStack, min_capacity: usize) {
469    assert!(!stack.is_null(), "seq_stack_grow: null stack");
470    unsafe {
471        (*stack).grow(min_capacity);
472    }
473}
474
475/// Get the base pointer of a tagged stack
476///
477/// This is used by generated code to get the array base.
478/// Returns a pointer to the first StackValue slot (32 bytes each).
479///
480/// # Safety
481/// `stack` must be a valid pointer to a TaggedStack.
482#[unsafe(no_mangle)]
483pub unsafe extern "C" fn seq_stack_base(stack: *mut TaggedStack) -> *mut StackValue {
484    assert!(!stack.is_null(), "seq_stack_base: null stack");
485    unsafe { (*stack).base }
486}
487
488/// Get the current stack pointer (as index)
489///
490/// # Safety
491/// `stack` must be a valid pointer to a TaggedStack.
492#[unsafe(no_mangle)]
493pub unsafe extern "C" fn seq_stack_sp(stack: *mut TaggedStack) -> usize {
494    assert!(!stack.is_null(), "seq_stack_sp: null stack");
495    unsafe { (*stack).sp }
496}
497
498/// Set the current stack pointer (as index)
499///
500/// # Safety
501/// The new SP must be <= capacity.
502#[unsafe(no_mangle)]
503pub unsafe extern "C" fn seq_stack_set_sp(stack: *mut TaggedStack, new_sp: usize) {
504    assert!(!stack.is_null(), "seq_stack_set_sp: null stack");
505    unsafe {
506        assert!(
507            new_sp <= (*stack).capacity,
508            "seq_stack_set_sp: sp exceeds capacity"
509        );
510        (*stack).sp = new_sp;
511    }
512}
513
514/// Get the capacity of a tagged stack
515///
516/// # Safety
517/// `stack` must be a valid pointer to a TaggedStack.
518#[unsafe(no_mangle)]
519pub unsafe extern "C" fn seq_stack_capacity(stack: *mut TaggedStack) -> usize {
520    assert!(!stack.is_null(), "seq_stack_capacity: null stack");
521    unsafe { (*stack).capacity }
522}
523
524/// Clone a tagged stack (for spawn)
525///
526/// # Safety
527/// `stack` must be a valid pointer to a TaggedStack.
528#[unsafe(no_mangle)]
529pub unsafe extern "C" fn seq_stack_clone(stack: *mut TaggedStack) -> *mut TaggedStack {
530    assert!(!stack.is_null(), "seq_stack_clone: null stack");
531    let cloned = unsafe { (*stack).clone_stack() };
532    Box::into_raw(Box::new(cloned))
533}
534
535// =============================================================================
536// Heap Object Allocation Functions
537// =============================================================================
538
539/// Allocate a float heap object
540#[unsafe(no_mangle)]
541pub extern "C" fn seq_alloc_float(value: f64) -> *mut HeapObject {
542    let layout = Layout::new::<FloatObject>();
543    let ptr = unsafe { alloc(layout) as *mut FloatObject };
544    if ptr.is_null() {
545        panic!("Failed to allocate FloatObject");
546    }
547
548    unsafe {
549        (*ptr).header = HeapObject {
550            tag: HeapTag::Float as u8,
551            flags: 0,
552            reserved: 0,
553            refcount: 1,
554        };
555        (*ptr).value = value;
556    }
557
558    ptr as *mut HeapObject
559}
560
561/// Allocate a bool heap object
562#[unsafe(no_mangle)]
563pub extern "C" fn seq_alloc_bool(value: bool) -> *mut HeapObject {
564    let layout = Layout::new::<BoolObject>();
565    let ptr = unsafe { alloc(layout) as *mut BoolObject };
566    if ptr.is_null() {
567        panic!("Failed to allocate BoolObject");
568    }
569
570    unsafe {
571        (*ptr).header = HeapObject {
572            tag: HeapTag::Bool as u8,
573            flags: 0,
574            reserved: 0,
575            refcount: 1,
576        };
577        (*ptr).value = value;
578    }
579
580    ptr as *mut HeapObject
581}
582
583/// Allocate a quotation heap object
584#[unsafe(no_mangle)]
585pub extern "C" fn seq_alloc_quotation(wrapper: usize, impl_ptr: usize) -> *mut HeapObject {
586    let layout = Layout::new::<QuotationObject>();
587    let ptr = unsafe { alloc(layout) as *mut QuotationObject };
588    if ptr.is_null() {
589        panic!("Failed to allocate QuotationObject");
590    }
591
592    unsafe {
593        (*ptr).header = HeapObject {
594            tag: HeapTag::Quotation as u8,
595            flags: 0,
596            reserved: 0,
597            refcount: 1,
598        };
599        (*ptr).wrapper = wrapper;
600        (*ptr).impl_ptr = impl_ptr;
601    }
602
603    ptr as *mut HeapObject
604}
605
606/// Free a heap object based on its type tag
607///
608/// # Safety
609/// `obj` must be a valid pointer to a HeapObject that was allocated by seq_alloc_*.
610#[unsafe(no_mangle)]
611pub unsafe extern "C" fn seq_free_heap_object(obj: *mut HeapObject) {
612    if obj.is_null() {
613        return;
614    }
615
616    unsafe {
617        let tag = (*obj).tag;
618        match tag {
619            t if t == HeapTag::Float as u8 => {
620                let layout = Layout::new::<FloatObject>();
621                dealloc(obj as *mut u8, layout);
622            }
623            t if t == HeapTag::Bool as u8 => {
624                let layout = Layout::new::<BoolObject>();
625                dealloc(obj as *mut u8, layout);
626            }
627            t if t == HeapTag::Quotation as u8 => {
628                let layout = Layout::new::<QuotationObject>();
629                dealloc(obj as *mut u8, layout);
630            }
631            // Note: String, Variant, Map, and Closure are stored inline in the 40-byte Value
632            // enum, not as separate heap objects. Only types explicitly allocated via
633            // seq_alloc_* need handling here.
634            _ => {
635                // Unknown type, use minimum HeapObject size as fallback
636                let layout = Layout::new::<HeapObject>();
637                dealloc(obj as *mut u8, layout);
638            }
639        }
640    }
641}
642
643/// Increment the reference count of a heap object
644///
645/// # Safety
646/// `obj` must be a valid pointer to a HeapObject.
647#[unsafe(no_mangle)]
648pub unsafe extern "C" fn seq_heap_incref(obj: *mut HeapObject) {
649    if obj.is_null() {
650        return;
651    }
652    unsafe {
653        if !(*obj).is_static() {
654            let rc = &(*obj).refcount as *const u32 as *mut u32;
655            std::sync::atomic::AtomicU32::from_ptr(rc)
656                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
657        }
658    }
659}
660
661/// Decrement the reference count of a heap object, freeing if zero
662///
663/// # Safety
664/// `obj` must be a valid pointer to a HeapObject.
665#[unsafe(no_mangle)]
666pub unsafe extern "C" fn seq_heap_decref(obj: *mut HeapObject) {
667    if obj.is_null() {
668        return;
669    }
670    unsafe {
671        if !(*obj).is_static() {
672            let rc = &(*obj).refcount as *const u32 as *mut u32;
673            let old = std::sync::atomic::AtomicU32::from_ptr(rc)
674                .fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
675            if old == 1 {
676                seq_free_heap_object(obj);
677            }
678        }
679    }
680}
681
682/// Get the float value from a FloatObject
683///
684/// # Safety
685/// `obj` must be a valid pointer to a FloatObject.
686#[unsafe(no_mangle)]
687pub unsafe extern "C" fn seq_get_float(obj: *mut HeapObject) -> f64 {
688    assert!(!obj.is_null(), "seq_get_float: null object");
689    unsafe {
690        assert!(
691            (*obj).tag == HeapTag::Float as u8,
692            "seq_get_float: not a float"
693        );
694        (*(obj as *mut FloatObject)).value
695    }
696}
697
698/// Get the bool value from a BoolObject
699///
700/// # Safety
701/// `obj` must be a valid pointer to a BoolObject.
702#[unsafe(no_mangle)]
703pub unsafe extern "C" fn seq_get_bool(obj: *mut HeapObject) -> bool {
704    assert!(!obj.is_null(), "seq_get_bool: null object");
705    unsafe {
706        assert!(
707            (*obj).tag == HeapTag::Bool as u8,
708            "seq_get_bool: not a bool"
709        );
710        (*(obj as *mut BoolObject)).value
711    }
712}
713
714// =============================================================================
715// Tests
716// =============================================================================
717
718#[cfg(test)]
719mod tests {
720    use super::*;
721
722    #[test]
723    fn test_tag_untag_int() {
724        // Positive integers
725        assert_eq!(untag_int(tag_int(0)), 0);
726        assert_eq!(untag_int(tag_int(1)), 1);
727        assert_eq!(untag_int(tag_int(42)), 42);
728        assert_eq!(untag_int(tag_int(1000000)), 1000000);
729
730        // Negative integers
731        assert_eq!(untag_int(tag_int(-1)), -1);
732        assert_eq!(untag_int(tag_int(-42)), -42);
733        assert_eq!(untag_int(tag_int(-1000000)), -1000000);
734
735        // Edge cases (63-bit range)
736        let max_63bit = (1i64 << 62) - 1;
737        let min_63bit = -(1i64 << 62);
738        assert_eq!(untag_int(tag_int(max_63bit)), max_63bit);
739        assert_eq!(untag_int(tag_int(min_63bit)), min_63bit);
740    }
741
742    #[test]
743    fn test_is_tagged() {
744        // Integers have low bit set
745        assert!(is_tagged_int(tag_int(0)));
746        assert!(is_tagged_int(tag_int(42)));
747        assert!(is_tagged_int(tag_int(-1)));
748
749        // Pointers have low bit clear (use a fake aligned address)
750        let fake_ptr = 0x1000u64; // 8-byte aligned
751        assert!(is_tagged_heap(fake_ptr));
752        assert!(!is_tagged_int(fake_ptr));
753    }
754
755    #[test]
756    fn test_tagged_int_examples() {
757        // From design doc: Integer `42` → `0x55` (42 << 1 | 1 = 85)
758        assert_eq!(tag_int(42), 85);
759        assert_eq!(tag_int(42), 0x55);
760
761        // Integer `-1` → `0xFFFFFFFFFFFFFFFF` (-1 << 1 | 1)
762        assert_eq!(tag_int(-1), 0xFFFFFFFFFFFFFFFFu64);
763    }
764
765    #[test]
766    fn test_stack_basic_operations() {
767        let mut stack = TaggedStack::new(16);
768
769        assert!(stack.is_empty());
770        assert_eq!(stack.depth(), 0);
771
772        // Push integers
773        stack.push_int(10);
774        stack.push_int(20);
775        stack.push_int(30);
776
777        assert!(!stack.is_empty());
778        assert_eq!(stack.depth(), 3);
779
780        // Pop and verify
781        assert_eq!(stack.pop_int(), 30);
782        assert_eq!(stack.pop_int(), 20);
783        assert_eq!(stack.pop_int(), 10);
784
785        assert!(stack.is_empty());
786    }
787
788    #[test]
789    fn test_stack_peek() {
790        let mut stack = TaggedStack::new(16);
791        stack.push_int(42);
792
793        // With Value layout: slot0 = discriminant (0 for Int), slot1 = value
794        let peeked = stack.peek();
795        assert_eq!(peeked.slot0, 0); // Int discriminant
796        assert_eq!(peeked.slot1 as i64, 42); // Value in slot1
797        assert_eq!(stack.depth(), 1); // Still there
798
799        assert_eq!(stack.pop_int(), 42);
800        assert!(stack.is_empty());
801    }
802
803    #[test]
804    fn test_stack_grow() {
805        let mut stack = TaggedStack::new(4);
806
807        // Fill beyond initial capacity
808        for i in 0..100 {
809            stack.push_int(i);
810        }
811
812        assert_eq!(stack.depth(), 100);
813        assert!(stack.capacity >= 100);
814
815        // Verify all values
816        for i in (0..100).rev() {
817            assert_eq!(stack.pop_int(), i);
818        }
819    }
820
821    #[test]
822    fn test_stack_clone() {
823        let mut stack = TaggedStack::new(16);
824        stack.push_int(1);
825        stack.push_int(2);
826        stack.push_int(3);
827
828        let mut cloned = stack.clone_stack();
829
830        // Both should have same values
831        assert_eq!(cloned.pop_int(), 3);
832        assert_eq!(cloned.pop_int(), 2);
833        assert_eq!(cloned.pop_int(), 1);
834
835        // Original should be unchanged
836        assert_eq!(stack.pop_int(), 3);
837        assert_eq!(stack.pop_int(), 2);
838        assert_eq!(stack.pop_int(), 1);
839    }
840
841    #[test]
842    fn test_ffi_stack_new_free() {
843        let stack = seq_stack_new(64);
844        assert!(!stack.is_null());
845
846        unsafe {
847            assert_eq!(seq_stack_capacity(stack), 64);
848            assert_eq!(seq_stack_sp(stack), 0);
849
850            seq_stack_free(stack);
851        }
852    }
853
854    #[test]
855    fn test_float_object() {
856        let obj = seq_alloc_float(2.5);
857        assert!(!obj.is_null());
858
859        unsafe {
860            assert_eq!((*obj).tag, HeapTag::Float as u8);
861            assert_eq!((*obj).refcount, 1);
862            assert_eq!(seq_get_float(obj), 2.5);
863
864            // Verify it's 8-byte aligned
865            assert!((obj as usize) & 0x7 == 0);
866
867            seq_free_heap_object(obj);
868        }
869    }
870
871    #[test]
872    fn test_bool_object() {
873        let obj_true = seq_alloc_bool(true);
874        let obj_false = seq_alloc_bool(false);
875
876        unsafe {
877            assert!(seq_get_bool(obj_true));
878            assert!(!seq_get_bool(obj_false));
879
880            seq_free_heap_object(obj_true);
881            seq_free_heap_object(obj_false);
882        }
883    }
884
885    #[test]
886    fn test_refcount() {
887        let obj = seq_alloc_float(1.0);
888
889        unsafe {
890            assert_eq!((*obj).refcount, 1);
891
892            seq_heap_incref(obj);
893            assert_eq!((*obj).refcount, 2);
894
895            seq_heap_incref(obj);
896            assert_eq!((*obj).refcount, 3);
897
898            seq_heap_decref(obj);
899            assert_eq!((*obj).refcount, 2);
900
901            seq_heap_decref(obj);
902            assert_eq!((*obj).refcount, 1);
903
904            // Final decref should free
905            seq_heap_decref(obj);
906            // Can't check after free, but shouldn't crash
907        }
908    }
909
910    #[test]
911    fn test_stack_with_heap_objects() {
912        let mut stack = TaggedStack::new(16);
913
914        // Push an integer
915        stack.push_int(42);
916
917        // Push a float (heap object)
918        let float_obj = seq_alloc_float(2.5);
919        stack.push(StackValue {
920            slot0: tag_heap_ptr(float_obj),
921            slot1: 0,
922            slot2: 0,
923            slot3: 0,
924            slot4: 0,
925        });
926
927        // Push another integer
928        stack.push_int(100);
929
930        assert_eq!(stack.depth(), 3);
931
932        // Pop and verify
933        assert_eq!(stack.pop_int(), 100);
934
935        let val = stack.pop();
936        assert!(is_tagged_heap(val.slot0));
937        unsafe {
938            assert_eq!(seq_get_float(untag_heap_ptr(val.slot0)), 2.5);
939        }
940
941        assert_eq!(stack.pop_int(), 42);
942
943        // Note: float_obj refcount was 1, we popped it without decref,
944        // so it's still alive. In real code, drop would decref.
945        unsafe {
946            seq_free_heap_object(float_obj);
947        }
948    }
949
950    #[test]
951    fn test_stack_value_size() {
952        // Verify StackValue is 40 bytes, matching LLVM's %Value type
953        // (5 x i64 = 5 x 8 = 40 bytes, compatible with Rust's Value with #[repr(C)])
954        assert_eq!(std::mem::size_of::<StackValue>(), 40);
955        assert_eq!(STACK_VALUE_SIZE, 40);
956    }
957}