Skip to main content

seq_core/
stack.rs

1//! Stack operations for the concatenative runtime.
2//!
3//! Provides `push` / `pop` / `peek`, the shuffle FFI ops
4//! (`dup` / `swap` / `rot` / `over` / `nip` / `tuck` / `2dup` / `pick` / `roll`),
5//! coroutine-local stack-base tracking for nested strands, stack allocation
6//! helpers, and the REPL stack-dump operation.
7//!
8//! The tagged-value encoding itself (`StackValue`, tag constants,
9//! `tag_int` / `untag_int`, `TaggedStack` backing storage) lives in
10//! `tagged_stack`.
11//!
12//! The `Stack` type is a pointer to the "current position" — where the next
13//! push goes. Push stores at `*sp` and returns `sp + 1`; pop returns
14//! `sp - 1` and reads from `*(sp - 1)`.
15
16use crate::son::{SonConfig, value_to_son};
17use crate::tagged_stack::{
18    DEFAULT_STACK_CAPACITY, StackValue, TAG_FALSE, TAG_TRUE, TaggedStack, is_tagged_int, tag_int,
19    untag_int,
20};
21use crate::value::Value;
22use std::cell::Cell;
23use std::sync::Arc;
24
25/// Stack: A pointer to the current position in a contiguous array of u64.
26pub type Stack = *mut StackValue;
27
28#[inline]
29pub fn stack_value_size() -> usize {
30    std::mem::size_of::<StackValue>()
31}
32
33/// Discriminant constants — retained for API compatibility with codegen and
34/// runtime code that switches on type. In tagged-ptr mode, these values are
35/// NOT stored in the StackValue itself (the tag is in the pointer bits).
36/// They are used only when the runtime unpacks a Value (via pop()) and needs
37/// to identify its type. Phase 2 codegen will use bit-level tag checks instead
38/// of loading these discriminants from memory.
39pub const DISC_INT: u64 = 0;
40pub const DISC_FLOAT: u64 = 1;
41pub const DISC_BOOL: u64 = 2;
42pub const DISC_STRING: u64 = 3;
43pub const DISC_VARIANT: u64 = 4;
44pub const DISC_MAP: u64 = 5;
45pub const DISC_QUOTATION: u64 = 6;
46pub const DISC_CLOSURE: u64 = 7;
47pub const DISC_CHANNEL: u64 = 8;
48pub const DISC_WEAVECTX: u64 = 9;
49pub const DISC_SYMBOL: u64 = 10;
50
51/// True if the StackValue is an inline (non-heap) encoding: a tagged Int,
52/// `false`, or `true`. Every other bit pattern is an `Arc<Value>` heap pointer.
53#[inline]
54fn is_inline(sv: StackValue) -> bool {
55    is_tagged_int(sv) || sv == TAG_FALSE || sv == TAG_TRUE
56}
57
58/// Convert a Value to a tagged StackValue
59#[inline]
60pub fn value_to_stack_value(value: Value) -> StackValue {
61    match value {
62        Value::Int(i) => tag_int(i),
63        Value::Bool(false) => TAG_FALSE,
64        Value::Bool(true) => TAG_TRUE,
65        other => {
66            // Heap-allocate via Arc for O(1) clone (refcount bump)
67            Arc::into_raw(Arc::new(other)) as u64
68        }
69    }
70}
71
72/// Convert a tagged StackValue back to a Value (takes ownership)
73///
74/// # Safety
75/// The StackValue must contain valid data — either a tagged int, bool,
76/// or a valid heap pointer from Arc::into_raw.
77#[inline]
78pub unsafe fn stack_value_to_value(sv: StackValue) -> Value {
79    if is_tagged_int(sv) {
80        Value::Int(untag_int(sv))
81    } else if sv == TAG_FALSE {
82        Value::Bool(false)
83    } else if sv == TAG_TRUE {
84        Value::Bool(true)
85    } else {
86        // Heap pointer — take ownership of the Arc<Value>
87        let arc = unsafe { Arc::from_raw(sv as *const Value) };
88        // Try to unwrap without cloning if we're the sole owner.
89        // Clone fallback happens when the value was dup'd on the stack
90        // (multiple Arc references exist and haven't been dropped yet).
91        Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone())
92    }
93}
94
95/// Clone a StackValue from LLVM IR.
96///
97/// # Safety
98/// src and dst must be valid pointers to StackValue slots.
99#[unsafe(no_mangle)]
100pub unsafe extern "C" fn patch_seq_clone_value(src: *const StackValue, dst: *mut StackValue) {
101    unsafe {
102        let sv = *src;
103        let cloned = clone_stack_value(sv);
104        *dst = cloned;
105    }
106}
107
108/// Clone a tagged StackValue, handling heap types.
109///
110/// - Int, Bool: bitwise copy (no allocation)
111/// - Heap types: clone the Value and re-box
112///
113/// # Safety
114/// The StackValue must contain valid tagged data.
115#[inline]
116pub unsafe fn clone_stack_value(sv: StackValue) -> StackValue {
117    if is_inline(sv) {
118        // Int or Bool — just copy
119        sv
120    } else {
121        // Heap pointer — increment Arc refcount (O(1), no allocation)
122        unsafe {
123            let arc = Arc::from_raw(sv as *const Value);
124            let cloned = Arc::clone(&arc);
125            std::mem::forget(arc); // Don't decrement the original
126            Arc::into_raw(cloned) as u64
127        }
128    }
129}
130
131/// Drop a tagged StackValue, freeing heap types.
132///
133/// # Safety
134/// The StackValue must be valid and not previously dropped.
135#[inline]
136pub unsafe fn drop_stack_value(sv: StackValue) {
137    if is_inline(sv) {
138        // Int or Bool — nothing to do
139        return;
140    }
141    // Heap pointer — decrement Arc refcount, free if last reference
142    unsafe {
143        let _ = Arc::from_raw(sv as *const Value);
144    }
145}
146
147// ============================================================================
148// Core Stack Operations
149// ============================================================================
150
151/// Push a value onto the stack.
152///
153/// # Safety
154/// Stack pointer must be valid and have room for the value.
155#[inline]
156pub unsafe fn push(stack: Stack, value: Value) -> Stack {
157    unsafe {
158        let sv = value_to_stack_value(value);
159        *stack = sv;
160        stack.add(1)
161    }
162}
163
164/// Push a StackValue directly onto the stack.
165///
166/// # Safety
167/// Stack pointer must be valid and have room for the value.
168#[inline]
169pub unsafe fn push_sv(stack: Stack, sv: StackValue) -> Stack {
170    unsafe {
171        *stack = sv;
172        stack.add(1)
173    }
174}
175
176/// Pop a value from the stack.
177///
178/// # Safety
179/// Stack must have at least one value.
180#[inline]
181pub unsafe fn pop(stack: Stack) -> (Stack, Value) {
182    unsafe {
183        let new_sp = stack.sub(1);
184        let sv = *new_sp;
185        (new_sp, stack_value_to_value(sv))
186    }
187}
188
189/// Pop a StackValue directly from the stack.
190///
191/// # Safety
192/// Stack must have at least one value.
193#[inline]
194pub unsafe fn pop_sv(stack: Stack) -> (Stack, StackValue) {
195    unsafe {
196        let new_sp = stack.sub(1);
197        let sv = *new_sp;
198        (new_sp, sv)
199    }
200}
201
202/// Pop two values from the stack.
203///
204/// # Safety
205/// Stack must have at least two values.
206#[inline]
207pub unsafe fn pop_two(stack: Stack, _op_name: &str) -> (Stack, Value, Value) {
208    unsafe {
209        let (sp, b) = pop(stack);
210        let (sp, a) = pop(sp);
211        (sp, a, b)
212    }
213}
214
215/// Peek at the top value without removing it.
216///
217/// # Safety
218/// Stack must have at least one value.
219#[inline]
220pub unsafe fn peek(stack: Stack) -> Value {
221    unsafe {
222        let sv = *stack.sub(1);
223        let cloned = clone_stack_value(sv);
224        stack_value_to_value(cloned)
225    }
226}
227
228/// Peek at the raw StackValue without removing it.
229///
230/// # Safety
231/// Stack must have at least one value.
232#[inline]
233pub unsafe fn peek_sv(stack: Stack) -> StackValue {
234    unsafe { *stack.sub(1) }
235}
236
237/// Get a mutable reference to a heap Value at the given stack position
238/// without popping (no Arc alloc/dealloc cycle).
239///
240/// Returns `Some(&mut Value)` if the slot is a sole-owned heap value.
241/// Returns `None` if the slot is inline (Int/Bool) or shared (refcount > 1).
242///
243/// Sole ownership is verified via `Arc::get_mut`, which atomically checks
244/// both strong and weak refcounts — the same guard used throughout the
245/// codebase for COW mutations.
246///
247/// The caller MUST NOT move or replace the Value behind the reference —
248/// it is still owned by the Arc on the stack. Mutating fields in place
249/// (e.g., Vec::push on VariantData.fields) is the intended use.
250///
251/// # Safety
252/// - `slot` must point to a valid StackValue within the stack.
253/// - The stack must not be concurrently accessed (true for strand-local stacks).
254/// - The returned reference is bounded by lifetime `'a`; the caller must
255///   ensure it does not outlive the stack slot.
256///
257/// # Tagged-value encoding
258/// The inline-value guard covers all non-heap encodings exhaustively:
259/// Int (odd bits), Bool false (0x0), Bool true (0x2). Every other value
260/// (even > 2) is a valid `Arc<Value>` heap pointer.
261#[inline]
262pub unsafe fn heap_value_mut<'a>(slot: *mut StackValue) -> Option<&'a mut Value> {
263    unsafe {
264        let sv = *slot;
265        // All non-heap encodings: Int (odd), Bool false (0x0), Bool true (0x2)
266        if is_inline(sv) {
267            return None;
268        }
269        // Reconstruct Arc, check sole ownership via Arc::get_mut (atomic check
270        // of both strong and weak refcounts), then forget to leave it on the stack.
271        let mut arc = Arc::from_raw(sv as *const Value);
272        let val_ref = Arc::get_mut(&mut arc).map(|v| &mut *(v as *mut Value));
273        std::mem::forget(arc); // Don't decrement — Arc stays on the stack
274        val_ref
275    }
276}
277
278/// Convenience: get a mutable reference to the heap Value at stack top (sp - 1).
279///
280/// # Safety
281/// Stack must have at least one value. See `heap_value_mut` for lifetime rules.
282#[inline]
283pub unsafe fn peek_heap_mut<'a>(stack: Stack) -> Option<&'a mut Value> {
284    unsafe { heap_value_mut(stack.sub(1)) }
285}
286
287/// Convenience: get a mutable reference to the heap Value at sp - 2
288/// (second from top).
289///
290/// # Safety
291/// Stack must have at least two values. See `heap_value_mut` for lifetime rules.
292#[inline]
293pub unsafe fn peek_heap_mut_second<'a>(stack: Stack) -> Option<&'a mut Value> {
294    unsafe { heap_value_mut(stack.sub(2)) }
295}
296
297// ============================================================================
298// FFI Stack Operations
299// ============================================================================
300
301/// Duplicate the top value: ( a -- a a )
302///
303/// # Safety
304/// Stack must have at least one value.
305#[unsafe(no_mangle)]
306pub unsafe extern "C" fn patch_seq_dup(stack: Stack) -> Stack {
307    unsafe {
308        let sv = peek_sv(stack);
309        let cloned = clone_stack_value(sv);
310        push_sv(stack, cloned)
311    }
312}
313
314/// Pop the top value and drop it (decrement Arc refcount for heap types).
315///
316/// Private helper shared by `patch_seq_drop_op` and any Rust-side caller
317/// that needs to discard the top of the stack without materializing a
318/// `Value`.
319///
320/// # Safety
321/// Stack must have at least one value.
322#[inline]
323pub unsafe fn drop_top(stack: Stack) -> Stack {
324    unsafe {
325        let (new_sp, sv) = pop_sv(stack);
326        drop_stack_value(sv);
327        new_sp
328    }
329}
330
331/// # Safety
332/// Stack must have at least one value.
333#[unsafe(no_mangle)]
334pub unsafe extern "C" fn patch_seq_drop_op(stack: Stack) -> Stack {
335    unsafe { drop_top(stack) }
336}
337
338/// # Safety
339/// Stack pointer must be valid and have room for the value.
340#[allow(improper_ctypes_definitions)]
341#[unsafe(no_mangle)]
342pub unsafe extern "C" fn patch_seq_push_value(stack: Stack, value: Value) -> Stack {
343    unsafe { push(stack, value) }
344}
345
346/// Swap the top two values: ( a b -- b a )
347///
348/// # Safety
349/// Stack must have at least two values.
350#[unsafe(no_mangle)]
351pub unsafe extern "C" fn patch_seq_swap(stack: Stack) -> Stack {
352    unsafe {
353        let ptr_b = stack.sub(1);
354        let ptr_a = stack.sub(2);
355        let a = *ptr_a;
356        let b = *ptr_b;
357        *ptr_a = b;
358        *ptr_b = a;
359        stack
360    }
361}
362
363/// Copy the second value to the top: ( a b -- a b a )
364///
365/// # Safety
366/// Stack must have at least two values.
367#[unsafe(no_mangle)]
368pub unsafe extern "C" fn patch_seq_over(stack: Stack) -> Stack {
369    unsafe {
370        let sv_a = *stack.sub(2);
371        let cloned = clone_stack_value(sv_a);
372        push_sv(stack, cloned)
373    }
374}
375
376/// Rotate the top three values: ( a b c -- b c a )
377///
378/// # Safety
379/// Stack must have at least three values.
380#[unsafe(no_mangle)]
381pub unsafe extern "C" fn patch_seq_rot(stack: Stack) -> Stack {
382    unsafe {
383        let ptr_c = stack.sub(1);
384        let ptr_b = stack.sub(2);
385        let ptr_a = stack.sub(3);
386        let a = *ptr_a;
387        let b = *ptr_b;
388        let c = *ptr_c;
389        *ptr_a = b;
390        *ptr_b = c;
391        *ptr_c = a;
392        stack
393    }
394}
395
396/// Remove the second value: ( a b -- b )
397///
398/// # Safety
399/// Stack must have at least two values.
400#[unsafe(no_mangle)]
401pub unsafe extern "C" fn patch_seq_nip(stack: Stack) -> Stack {
402    unsafe {
403        let ptr_b = stack.sub(1);
404        let ptr_a = stack.sub(2);
405        let a = *ptr_a;
406        let b = *ptr_b;
407        drop_stack_value(a);
408        *ptr_a = b;
409        stack.sub(1)
410    }
411}
412
413/// Copy top value below second: ( a b -- b a b )
414///
415/// # Safety
416/// Stack must have at least two values.
417#[unsafe(no_mangle)]
418pub unsafe extern "C" fn patch_seq_tuck(stack: Stack) -> Stack {
419    unsafe {
420        let ptr_b = stack.sub(1);
421        let ptr_a = stack.sub(2);
422        let a = *ptr_a;
423        let b = *ptr_b;
424        let b_clone = clone_stack_value(b);
425        *ptr_a = b;
426        *ptr_b = a;
427        push_sv(stack, b_clone)
428    }
429}
430
431/// Duplicate top two values: ( a b -- a b a b )
432///
433/// # Safety
434/// Stack must have at least two values.
435#[unsafe(no_mangle)]
436pub unsafe extern "C" fn patch_seq_2dup(stack: Stack) -> Stack {
437    unsafe {
438        let sv_a = *stack.sub(2);
439        let sv_b = *stack.sub(1);
440        let a_clone = clone_stack_value(sv_a);
441        let b_clone = clone_stack_value(sv_b);
442        let sp = push_sv(stack, a_clone);
443        push_sv(sp, b_clone)
444    }
445}
446
447/// Pop and type-check the Int index for pick/roll-style ops.
448///
449/// On success returns `(sp_after_pop, index)`. On failure sets a runtime
450/// error and returns `Err(sp_after_pop)` — callers should propagate that
451/// pointer unchanged so the stack slot stays consumed.
452///
453/// # Safety
454/// Stack must have at least one value.
455#[inline]
456unsafe fn pop_and_validate_index(stack: Stack, op_name: &str) -> Result<(Stack, usize), Stack> {
457    unsafe {
458        let (sp, n_val) = pop(stack);
459        let n_raw = match n_val {
460            Value::Int(i) => i,
461            _ => {
462                crate::error::set_runtime_error(format!(
463                    "{}: expected Int index on top of stack",
464                    op_name
465                ));
466                return Err(sp);
467            }
468        };
469        if n_raw < 0 {
470            crate::error::set_runtime_error(format!(
471                "{}: index cannot be negative (got {})",
472                op_name, n_raw
473            ));
474            return Err(sp);
475        }
476        Ok((sp, n_raw as usize))
477    }
478}
479
480/// Verify the stack holds at least `n + 1` values beyond the current base.
481/// Sets a runtime error and returns `false` on underflow.
482#[inline]
483fn check_depth_for_index(sp: Stack, n: usize, op_name: &str) -> bool {
484    let base = get_stack_base();
485    let depth = (sp as usize - base as usize) / std::mem::size_of::<StackValue>();
486    if n >= depth {
487        crate::error::set_runtime_error(format!(
488            "{}: index {} exceeds stack depth {} (need at least {} values)",
489            op_name,
490            n,
491            depth,
492            n + 1
493        ));
494        return false;
495    }
496    true
497}
498
499/// Pick: Copy the nth value to the top.
500///
501/// # Safety
502/// Stack must have at least n+2 values (n+1 data values plus the index).
503#[unsafe(no_mangle)]
504pub unsafe extern "C" fn patch_seq_pick_op(stack: Stack) -> Stack {
505    unsafe {
506        let (sp, n) = match pop_and_validate_index(stack, "pick") {
507            Ok(x) => x,
508            Err(sp) => return sp,
509        };
510        if !check_depth_for_index(sp, n, "pick") {
511            return sp;
512        }
513
514        let sv = *sp.sub(n + 1);
515        let cloned = clone_stack_value(sv);
516        push_sv(sp, cloned)
517    }
518}
519
520/// Roll: Rotate n+1 items, bringing the item at depth n to the top.
521///
522/// # Safety
523/// Stack must have at least n+2 values (n+1 data values plus the index).
524#[unsafe(no_mangle)]
525pub unsafe extern "C" fn patch_seq_roll(stack: Stack) -> Stack {
526    unsafe {
527        let (sp, n) = match pop_and_validate_index(stack, "roll") {
528            Ok(x) => x,
529            Err(sp) => return sp,
530        };
531
532        if n == 0 {
533            return sp;
534        }
535        if n == 1 {
536            return patch_seq_swap(sp);
537        }
538        if n == 2 {
539            return patch_seq_rot(sp);
540        }
541
542        if !check_depth_for_index(sp, n, "roll") {
543            return sp;
544        }
545
546        let src_ptr = sp.sub(n + 1);
547        let saved = *src_ptr;
548        std::ptr::copy(src_ptr.add(1), src_ptr, n);
549        *sp.sub(1) = saved;
550
551        sp
552    }
553}
554
555// ============================================================================
556// Coroutine-Local Stack Base Tracking
557// ============================================================================
558
559may::coroutine_local!(static STACK_BASE: Cell<usize> = Cell::new(0));
560
561/// # Safety
562/// Base pointer must be a valid stack pointer for the current strand.
563#[unsafe(no_mangle)]
564pub unsafe extern "C" fn patch_seq_set_stack_base(base: Stack) {
565    STACK_BASE.with(|cell| {
566        cell.set(base as usize);
567    });
568}
569
570/// Read the current strand's stack base, or a null pointer if unset.
571#[inline]
572pub fn get_stack_base() -> Stack {
573    STACK_BASE.with(|cell| cell.get() as *mut StackValue)
574}
575
576/// # Safety
577/// Current stack must have a valid base set via `patch_seq_set_stack_base`.
578#[unsafe(no_mangle)]
579pub unsafe extern "C" fn clone_stack(sp: Stack) -> Stack {
580    unsafe {
581        let (new_sp, _base) = clone_stack_with_base(sp);
582        new_sp
583    }
584}
585
586/// # Safety
587/// Current stack must have a valid base set and sp must point within the stack.
588pub unsafe fn clone_stack_with_base(sp: Stack) -> (Stack, Stack) {
589    let base = get_stack_base();
590    if base.is_null() {
591        panic!("clone_stack: stack base not set");
592    }
593
594    let depth = unsafe { sp.offset_from(base) as usize };
595
596    if depth == 0 {
597        let new_stack = TaggedStack::new(DEFAULT_STACK_CAPACITY);
598        let new_base = new_stack.base;
599        std::mem::forget(new_stack);
600        return (new_base, new_base);
601    }
602
603    let capacity = depth.max(DEFAULT_STACK_CAPACITY);
604    let new_stack = TaggedStack::new(capacity);
605    let new_base = new_stack.base;
606    std::mem::forget(new_stack);
607
608    unsafe {
609        for i in 0..depth {
610            let sv = *base.add(i);
611            let cloned = clone_stack_value(sv);
612            *new_base.add(i) = cloned;
613        }
614    }
615
616    unsafe { (new_base.add(depth), new_base) }
617}
618
619// ============================================================================
620// Stack Allocation Helpers
621// ============================================================================
622
623/// Allocate a fresh stack buffer and return its base pointer.
624///
625/// The caller takes ownership of the underlying `TaggedStack` storage via
626/// the raw base pointer — the `TaggedStack` wrapper is intentionally leaked
627/// here so the coroutine-native `Stack` type can be a plain `*mut StackValue`.
628pub fn alloc_stack() -> Stack {
629    let stack = TaggedStack::with_default_capacity();
630    let base = stack.base;
631    std::mem::forget(stack);
632    base
633}
634
635/// Allocate a fresh stack and register it as the current strand's base.
636///
637/// Convenience wrapper for tests: installs the stack base so ops like
638/// `pick` / `roll` / `clone_stack` that depend on `get_stack_base()`
639/// behave correctly in a single-strand test harness.
640pub fn alloc_test_stack() -> Stack {
641    let stack = alloc_stack();
642    unsafe { patch_seq_set_stack_base(stack) };
643    stack
644}
645
646/// Dump all values on the stack (for REPL debugging).
647///
648/// # Safety
649/// Stack base must have been set and sp must be valid.
650#[unsafe(no_mangle)]
651pub unsafe extern "C" fn patch_seq_stack_dump(sp: Stack) -> Stack {
652    let base = get_stack_base();
653    if base.is_null() {
654        eprintln!("[stack.dump: base not set]");
655        return sp;
656    }
657
658    let depth = (sp as usize - base as usize) / std::mem::size_of::<StackValue>();
659
660    if depth == 0 {
661        println!("»");
662    } else {
663        use std::io::Write;
664        print!("» ");
665        for i in 0..depth {
666            if i > 0 {
667                print!(" ");
668            }
669            unsafe {
670                let sv = *base.add(i);
671                print_stack_value(sv);
672            }
673        }
674        println!();
675        let _ = std::io::stdout().flush();
676
677        // Drop all heap-allocated values
678        for i in 0..depth {
679            unsafe {
680                let sv = *base.add(i);
681                drop_stack_value(sv);
682            }
683        }
684    }
685
686    base
687}
688
689fn print_stack_value(sv: StackValue) {
690    let cloned = unsafe { clone_stack_value(sv) };
691    let value = unsafe { stack_value_to_value(cloned) };
692    let son = value_to_son(&value, &SonConfig::compact());
693    print!("{}", son);
694}
695
696// ============================================================================
697// Short Aliases for Internal/Test Use
698// ============================================================================
699
700pub use patch_seq_2dup as two_dup;
701pub use patch_seq_dup as dup;
702pub use patch_seq_nip as nip;
703pub use patch_seq_over as over;
704pub use patch_seq_pick_op as pick;
705pub use patch_seq_roll as roll;
706pub use patch_seq_rot as rot;
707pub use patch_seq_swap as swap;
708pub use patch_seq_tuck as tuck;
709
710#[macro_export]
711macro_rules! test_stack {
712    () => {{ $crate::stack::alloc_test_stack() }};
713}
714
715#[cfg(test)]
716mod tests {
717    use super::*;
718
719    #[test]
720    fn test_pick_negative_index_sets_error() {
721        unsafe {
722            crate::error::clear_runtime_error();
723            let stack = alloc_test_stack();
724            let stack = push(stack, Value::Int(100));
725            let stack = push(stack, Value::Int(-1));
726
727            let _stack = patch_seq_pick_op(stack);
728
729            assert!(crate::error::has_runtime_error());
730            let error = crate::error::take_runtime_error().unwrap();
731            assert!(error.contains("negative"));
732        }
733    }
734
735    #[test]
736    fn test_pick_out_of_bounds_sets_error() {
737        unsafe {
738            crate::error::clear_runtime_error();
739            let stack = alloc_test_stack();
740            let stack = push(stack, Value::Int(100));
741            let stack = push(stack, Value::Int(10));
742
743            let _stack = patch_seq_pick_op(stack);
744
745            assert!(crate::error::has_runtime_error());
746            let error = crate::error::take_runtime_error().unwrap();
747            assert!(error.contains("exceeds stack depth"));
748        }
749    }
750
751    #[test]
752    fn test_roll_negative_index_sets_error() {
753        unsafe {
754            crate::error::clear_runtime_error();
755            let stack = alloc_test_stack();
756            let stack = push(stack, Value::Int(100));
757            let stack = push(stack, Value::Int(-1));
758
759            let _stack = patch_seq_roll(stack);
760
761            assert!(crate::error::has_runtime_error());
762            let error = crate::error::take_runtime_error().unwrap();
763            assert!(error.contains("negative"));
764        }
765    }
766
767    #[test]
768    fn test_roll_out_of_bounds_sets_error() {
769        unsafe {
770            crate::error::clear_runtime_error();
771            let stack = alloc_test_stack();
772            let stack = push(stack, Value::Int(100));
773            let stack = push(stack, Value::Int(10));
774
775            let _stack = patch_seq_roll(stack);
776
777            assert!(crate::error::has_runtime_error());
778            let error = crate::error::take_runtime_error().unwrap();
779            assert!(error.contains("exceeds stack depth"));
780        }
781    }
782
783    #[test]
784    fn test_int_roundtrip() {
785        unsafe {
786            let stack = alloc_test_stack();
787            let stack = push(stack, Value::Int(42));
788            let (_, val) = pop(stack);
789            assert_eq!(val, Value::Int(42));
790        }
791    }
792
793    #[test]
794    fn test_bool_roundtrip() {
795        unsafe {
796            let stack = alloc_test_stack();
797            let stack = push(stack, Value::Bool(true));
798            let stack = push(stack, Value::Bool(false));
799            let (stack, val_f) = pop(stack);
800            let (_, val_t) = pop(stack);
801            assert_eq!(val_f, Value::Bool(false));
802            assert_eq!(val_t, Value::Bool(true));
803        }
804    }
805
806    #[test]
807    fn test_float_roundtrip() {
808        unsafe {
809            let stack = alloc_test_stack();
810            let stack = push(stack, Value::Float(std::f64::consts::PI));
811            let (_, val) = pop(stack);
812            assert_eq!(val, Value::Float(std::f64::consts::PI));
813        }
814    }
815
816    #[test]
817    fn test_string_roundtrip() {
818        unsafe {
819            let stack = alloc_test_stack();
820            let s = crate::seqstring::SeqString::from("hello");
821            let stack = push(stack, Value::String(s));
822            let (_, val) = pop(stack);
823            match val {
824                Value::String(s) => assert_eq!(s.as_bytes(), b"hello"),
825                other => panic!("Expected String, got {:?}", other),
826            }
827        }
828    }
829
830    #[test]
831    fn test_symbol_roundtrip() {
832        unsafe {
833            let stack = alloc_test_stack();
834            let s = crate::seqstring::SeqString::from("my-sym");
835            let stack = push(stack, Value::Symbol(s));
836            let (_, val) = pop(stack);
837            match val {
838                Value::Symbol(s) => assert_eq!(s.as_bytes(), b"my-sym"),
839                other => panic!("Expected Symbol, got {:?}", other),
840            }
841        }
842    }
843
844    #[test]
845    fn test_variant_roundtrip() {
846        unsafe {
847            let stack = alloc_test_stack();
848            let tag = crate::seqstring::SeqString::from("Foo");
849            let data = crate::value::VariantData::new(tag, vec![Value::Int(1), Value::Int(2)]);
850            let stack = push(stack, Value::Variant(std::sync::Arc::new(data)));
851            let (_, val) = pop(stack);
852            match val {
853                Value::Variant(v) => {
854                    assert_eq!(v.tag.as_bytes(), b"Foo");
855                    assert_eq!(v.fields.len(), 2);
856                }
857                other => panic!("Expected Variant, got {:?}", other),
858            }
859        }
860    }
861
862    #[test]
863    fn test_map_roundtrip() {
864        unsafe {
865            let stack = alloc_test_stack();
866            let mut map = std::collections::HashMap::new();
867            map.insert(crate::value::MapKey::Int(1), Value::Int(100));
868            let stack = push(stack, Value::Map(Box::new(map)));
869            let (_, val) = pop(stack);
870            match val {
871                Value::Map(m) => {
872                    assert_eq!(m.len(), 1);
873                    assert_eq!(m.get(&crate::value::MapKey::Int(1)), Some(&Value::Int(100)));
874                }
875                other => panic!("Expected Map, got {:?}", other),
876            }
877        }
878    }
879
880    #[test]
881    fn test_quotation_roundtrip() {
882        unsafe {
883            let stack = alloc_test_stack();
884            let stack = push(
885                stack,
886                Value::Quotation {
887                    wrapper: 0x1000,
888                    impl_: 0x2000,
889                },
890            );
891            let (_, val) = pop(stack);
892            match val {
893                Value::Quotation { wrapper, impl_ } => {
894                    assert_eq!(wrapper, 0x1000);
895                    assert_eq!(impl_, 0x2000);
896                }
897                other => panic!("Expected Quotation, got {:?}", other),
898            }
899        }
900    }
901
902    #[test]
903    fn test_closure_roundtrip() {
904        unsafe {
905            let stack = alloc_test_stack();
906            let env: std::sync::Arc<[Value]> = std::sync::Arc::from(vec![Value::Int(42)]);
907            let stack = push(
908                stack,
909                Value::Closure {
910                    fn_ptr: 0x3000,
911                    env,
912                },
913            );
914            let (_, val) = pop(stack);
915            match val {
916                Value::Closure { fn_ptr, env } => {
917                    assert_eq!(fn_ptr, 0x3000);
918                    assert_eq!(env.len(), 1);
919                }
920                other => panic!("Expected Closure, got {:?}", other),
921            }
922        }
923    }
924
925    #[test]
926    fn test_channel_roundtrip() {
927        unsafe {
928            let stack = alloc_test_stack();
929            let (sender, receiver) = may::sync::mpmc::channel::<crate::value::ChannelMsg>();
930            let ch = std::sync::Arc::new(crate::value::ChannelData {
931                sender,
932                receiver,
933                closed: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
934            });
935            let stack = push(stack, Value::Channel(ch));
936            let (_, val) = pop(stack);
937            assert!(matches!(val, Value::Channel(_)));
938        }
939    }
940
941    #[test]
942    fn test_weavectx_roundtrip() {
943        unsafe {
944            let stack = alloc_test_stack();
945            let (ys, yr) = may::sync::mpmc::channel();
946            let (rs, rr) = may::sync::mpmc::channel();
947            let yield_chan = std::sync::Arc::new(crate::value::WeaveChannelData {
948                sender: ys,
949                receiver: yr,
950            });
951            let resume_chan = std::sync::Arc::new(crate::value::WeaveChannelData {
952                sender: rs,
953                receiver: rr,
954            });
955            let stack = push(
956                stack,
957                Value::WeaveCtx {
958                    yield_chan,
959                    resume_chan,
960                },
961            );
962            let (_, val) = pop(stack);
963            assert!(matches!(val, Value::WeaveCtx { .. }));
964        }
965    }
966
967    #[test]
968    fn test_dup_pop_pop_heap_type() {
969        // Verify Arc refcount handling: push a heap value, dup it (refcount 2),
970        // then pop both. No double-free or corruption should occur.
971        unsafe {
972            let stack = alloc_test_stack();
973            let stack = push(stack, Value::Float(2.5));
974            // dup: clones via Arc refcount bump
975            let stack = patch_seq_dup(stack);
976            // pop both copies
977            let (stack, val1) = pop(stack);
978            let (_, val2) = pop(stack);
979            assert_eq!(val1, Value::Float(2.5));
980            assert_eq!(val2, Value::Float(2.5));
981        }
982    }
983}