seq_runtime/
arithmetic.rs

1//! Arithmetic operations for Seq
2//!
3//! These functions are exported with C ABI for LLVM codegen to call.
4//!
5//! # Safety Contract
6//!
7//! **IMPORTANT:** These functions are designed to be called ONLY by compiler-generated code,
8//! not by end users or arbitrary C code. The compiler's type checker is responsible for:
9//!
10//! - Ensuring stack has correct number of values
11//! - Ensuring values are the correct types (Int for arithmetic, Int for comparisons)
12//! - Preventing division by zero at compile time when possible
13//!
14//! # Overflow Behavior
15//!
16//! All arithmetic operations use **wrapping semantics** for predictable, defined behavior:
17//! - `add`: i64::MAX + 1 wraps to i64::MIN
18//! - `subtract`: i64::MIN - 1 wraps to i64::MAX
19//! - `multiply`: overflow wraps around
20//! - `divide`: i64::MIN / -1 wraps to i64::MIN (special case)
21//!
22//! This matches the behavior of Forth and Factor, providing consistency for low-level code.
23
24use crate::error::set_runtime_error;
25#[cfg(not(feature = "nanbox"))]
26use crate::stack::DISC_INT;
27use crate::stack::{Stack, peek_sv, pop, pop_two, push};
28use crate::value::Value;
29
30/// Push an integer literal onto the stack (for compiler-generated code)
31///
32/// Stack effect: ( -- n )
33///
34/// # Safety
35/// Always safe to call
36#[unsafe(no_mangle)]
37pub unsafe extern "C" fn patch_seq_push_int(stack: Stack, value: i64) -> Stack {
38    unsafe { push(stack, Value::Int(value)) }
39}
40
41/// Push a boolean literal onto the stack (for compiler-generated code)
42///
43/// Stack effect: ( -- bool )
44///
45/// # Safety
46/// Always safe to call
47#[unsafe(no_mangle)]
48pub unsafe extern "C" fn patch_seq_push_bool(stack: Stack, value: bool) -> Stack {
49    unsafe { push(stack, Value::Bool(value)) }
50}
51
52/// Add two integers
53///
54/// Stack effect: ( a b -- a+b )
55///
56/// # Safety
57/// Stack must have two Int values on top
58#[unsafe(no_mangle)]
59pub unsafe extern "C" fn patch_seq_add(stack: Stack) -> Stack {
60    let (rest, a, b) = unsafe { pop_two(stack, "add") };
61    match (a, b) {
62        (Value::Int(a_val), Value::Int(b_val)) => unsafe {
63            push(rest, Value::Int(a_val.wrapping_add(b_val)))
64        },
65        _ => panic!("add: expected two integers on stack"),
66    }
67}
68
69/// Subtract two integers (a - b)
70///
71/// Stack effect: ( a b -- a-b )
72///
73/// # Safety
74/// Stack must have two Int values on top
75#[unsafe(no_mangle)]
76pub unsafe extern "C" fn patch_seq_subtract(stack: Stack) -> Stack {
77    let (rest, a, b) = unsafe { pop_two(stack, "subtract") };
78    match (a, b) {
79        (Value::Int(a_val), Value::Int(b_val)) => unsafe {
80            push(rest, Value::Int(a_val.wrapping_sub(b_val)))
81        },
82        _ => panic!("subtract: expected two integers on stack"),
83    }
84}
85
86/// Multiply two integers
87///
88/// Stack effect: ( a b -- a*b )
89///
90/// # Safety
91/// Stack must have two Int values on top
92#[unsafe(no_mangle)]
93pub unsafe extern "C" fn patch_seq_multiply(stack: Stack) -> Stack {
94    let (rest, a, b) = unsafe { pop_two(stack, "multiply") };
95    match (a, b) {
96        (Value::Int(a_val), Value::Int(b_val)) => unsafe {
97            push(rest, Value::Int(a_val.wrapping_mul(b_val)))
98        },
99        _ => panic!("multiply: expected two integers on stack"),
100    }
101}
102
103/// Divide two integers (a / b)
104///
105/// Stack effect: ( a b -- a/b )
106///
107/// # Error Handling
108/// - Division by zero: Sets runtime error and returns 0
109/// - Type mismatch: Sets runtime error and returns 0
110///
111/// # Safety
112/// Stack must have at least two values on top
113#[unsafe(no_mangle)]
114pub unsafe extern "C" fn patch_seq_divide(stack: Stack) -> Stack {
115    let (rest, a, b) = unsafe { pop_two(stack, "divide") };
116    match (a, b) {
117        (Value::Int(a_val), Value::Int(0)) => {
118            // Division by zero - set error and return 0
119            set_runtime_error(format!(
120                "divide: division by zero (attempted {} / 0)",
121                a_val
122            ));
123            unsafe { push(rest, Value::Int(0)) }
124        }
125        (Value::Int(a_val), Value::Int(b_val)) => {
126            // Use wrapping_div to handle i64::MIN / -1 overflow edge case
127            unsafe { push(rest, Value::Int(a_val.wrapping_div(b_val))) }
128        }
129        _ => {
130            // Type error - should not happen with type-checked code
131            set_runtime_error("divide: expected two integers on stack");
132            unsafe { push(rest, Value::Int(0)) }
133        }
134    }
135}
136
137/// Modulo (remainder) of two integers (a % b)
138///
139/// Stack effect: ( a b -- a%b )
140///
141/// # Error Handling
142/// - Division by zero: Sets runtime error and returns 0
143/// - Type mismatch: Sets runtime error and returns 0
144///
145/// # Safety
146/// Stack must have at least two values on top
147#[unsafe(no_mangle)]
148pub unsafe extern "C" fn patch_seq_modulo(stack: Stack) -> Stack {
149    let (rest, a, b) = unsafe { pop_two(stack, "modulo") };
150    match (a, b) {
151        (Value::Int(a_val), Value::Int(0)) => {
152            // Division by zero - set error and return 0
153            set_runtime_error(format!(
154                "modulo: division by zero (attempted {} % 0)",
155                a_val
156            ));
157            unsafe { push(rest, Value::Int(0)) }
158        }
159        (Value::Int(a_val), Value::Int(b_val)) => {
160            // Use wrapping_rem to handle i64::MIN % -1 overflow edge case
161            unsafe { push(rest, Value::Int(a_val.wrapping_rem(b_val))) }
162        }
163        _ => {
164            // Type error - should not happen with type-checked code
165            set_runtime_error("modulo: expected two integers on stack");
166            unsafe { push(rest, Value::Int(0)) }
167        }
168    }
169}
170
171/// Integer equality: =
172///
173/// Returns 1 if equal, 0 if not (Forth-style boolean)
174/// Stack effect: ( a b -- flag )
175///
176/// # Safety
177/// Stack must have two Int values on top
178#[unsafe(no_mangle)]
179pub unsafe extern "C" fn patch_seq_eq(stack: Stack) -> Stack {
180    let (rest, a, b) = unsafe { pop_two(stack, "=") };
181    match (a, b) {
182        (Value::Int(a_val), Value::Int(b_val)) => unsafe {
183            push(rest, Value::Bool(a_val == b_val))
184        },
185        _ => panic!("=: expected two integers on stack"),
186    }
187}
188
189/// Less than: <
190///
191/// Returns 1 if a < b, 0 otherwise (Forth-style boolean)
192/// Stack effect: ( a b -- flag )
193///
194/// # Safety
195/// Stack must have two Int values on top
196#[unsafe(no_mangle)]
197pub unsafe extern "C" fn patch_seq_lt(stack: Stack) -> Stack {
198    let (rest, a, b) = unsafe { pop_two(stack, "<") };
199    match (a, b) {
200        (Value::Int(a_val), Value::Int(b_val)) => unsafe { push(rest, Value::Bool(a_val < b_val)) },
201        _ => panic!("<: expected two integers on stack"),
202    }
203}
204
205/// Greater than: >
206///
207/// Returns 1 if a > b, 0 otherwise (Forth-style boolean)
208/// Stack effect: ( a b -- flag )
209///
210/// # Safety
211/// Stack must have two Int values on top
212#[unsafe(no_mangle)]
213pub unsafe extern "C" fn patch_seq_gt(stack: Stack) -> Stack {
214    let (rest, a, b) = unsafe { pop_two(stack, ">") };
215    match (a, b) {
216        (Value::Int(a_val), Value::Int(b_val)) => unsafe { push(rest, Value::Bool(a_val > b_val)) },
217        _ => panic!(">: expected two integers on stack"),
218    }
219}
220
221/// Less than or equal: <=
222///
223/// Returns 1 if a <= b, 0 otherwise (Forth-style boolean)
224/// Stack effect: ( a b -- flag )
225///
226/// # Safety
227/// Stack must have two Int values on top
228#[unsafe(no_mangle)]
229pub unsafe extern "C" fn patch_seq_lte(stack: Stack) -> Stack {
230    let (rest, a, b) = unsafe { pop_two(stack, "<=") };
231    match (a, b) {
232        (Value::Int(a_val), Value::Int(b_val)) => unsafe {
233            push(rest, Value::Bool(a_val <= b_val))
234        },
235        _ => panic!("<=: expected two integers on stack"),
236    }
237}
238
239/// Greater than or equal: >=
240///
241/// Returns 1 if a >= b, 0 otherwise (Forth-style boolean)
242/// Stack effect: ( a b -- flag )
243///
244/// # Safety
245/// Stack must have two Int values on top
246#[unsafe(no_mangle)]
247pub unsafe extern "C" fn patch_seq_gte(stack: Stack) -> Stack {
248    let (rest, a, b) = unsafe { pop_two(stack, ">=") };
249    match (a, b) {
250        (Value::Int(a_val), Value::Int(b_val)) => unsafe {
251            push(rest, Value::Bool(a_val >= b_val))
252        },
253        _ => panic!(">=: expected two integers on stack"),
254    }
255}
256
257/// Not equal: <>
258///
259/// Returns 1 if a != b, 0 otherwise (Forth-style boolean)
260/// Stack effect: ( a b -- flag )
261///
262/// # Safety
263/// Stack must have two Int values on top
264#[unsafe(no_mangle)]
265pub unsafe extern "C" fn patch_seq_neq(stack: Stack) -> Stack {
266    let (rest, a, b) = unsafe { pop_two(stack, "<>") };
267    match (a, b) {
268        (Value::Int(a_val), Value::Int(b_val)) => unsafe {
269            push(rest, Value::Bool(a_val != b_val))
270        },
271        _ => panic!("<>: expected two integers on stack"),
272    }
273}
274
275/// Logical AND operation (Forth-style: multiply for boolean values)
276///
277/// Stack effect: ( a b -- result )
278/// where 0 is false, non-zero is true
279/// Returns 1 if both are true (non-zero), 0 otherwise
280///
281/// # Safety
282/// Stack must have at least two Int values
283#[unsafe(no_mangle)]
284pub unsafe extern "C" fn patch_seq_and(stack: Stack) -> Stack {
285    let (rest, a, b) = unsafe { pop_two(stack, "and") };
286    match (a, b) {
287        (Value::Int(a_val), Value::Int(b_val)) => unsafe {
288            push(
289                rest,
290                Value::Int(if a_val != 0 && b_val != 0 { 1 } else { 0 }),
291            )
292        },
293        _ => panic!("and: expected two integers on stack"),
294    }
295}
296
297/// Logical OR operation (Forth-style)
298///
299/// Stack effect: ( a b -- result )
300/// where 0 is false, non-zero is true
301/// Returns 1 if either is true (non-zero), 0 otherwise
302///
303/// # Safety
304/// Stack must have at least two Int values
305#[unsafe(no_mangle)]
306pub unsafe extern "C" fn patch_seq_or(stack: Stack) -> Stack {
307    let (rest, a, b) = unsafe { pop_two(stack, "or") };
308    match (a, b) {
309        (Value::Int(a_val), Value::Int(b_val)) => unsafe {
310            push(
311                rest,
312                Value::Int(if a_val != 0 || b_val != 0 { 1 } else { 0 }),
313            )
314        },
315        _ => panic!("or: expected two integers on stack"),
316    }
317}
318
319/// Logical NOT operation
320///
321/// Stack effect: ( a -- result )
322/// where 0 is false, non-zero is true
323/// Returns 1 if false (0), 0 otherwise
324///
325/// # Safety
326/// Stack must have at least one Int value
327#[unsafe(no_mangle)]
328pub unsafe extern "C" fn patch_seq_not(stack: Stack) -> Stack {
329    assert!(!stack.is_null(), "not: stack is empty");
330    let (rest, a) = unsafe { pop(stack) };
331
332    match a {
333        Value::Int(a_val) => unsafe { push(rest, Value::Int(if a_val == 0 { 1 } else { 0 })) },
334        _ => panic!("not: expected integer on stack"),
335    }
336}
337
338// ============================================================================
339// Bitwise Operations
340// ============================================================================
341
342/// Bitwise AND
343///
344/// Stack effect: ( a b -- a&b )
345///
346/// # Safety
347/// Stack must have two Int values on top
348#[unsafe(no_mangle)]
349pub unsafe extern "C" fn patch_seq_band(stack: Stack) -> Stack {
350    let (rest, a, b) = unsafe { pop_two(stack, "band") };
351    match (a, b) {
352        (Value::Int(a_val), Value::Int(b_val)) => unsafe { push(rest, Value::Int(a_val & b_val)) },
353        _ => panic!("band: expected two integers on stack"),
354    }
355}
356
357/// Bitwise OR
358///
359/// Stack effect: ( a b -- a|b )
360///
361/// # Safety
362/// Stack must have two Int values on top
363#[unsafe(no_mangle)]
364pub unsafe extern "C" fn patch_seq_bor(stack: Stack) -> Stack {
365    let (rest, a, b) = unsafe { pop_two(stack, "bor") };
366    match (a, b) {
367        (Value::Int(a_val), Value::Int(b_val)) => unsafe { push(rest, Value::Int(a_val | b_val)) },
368        _ => panic!("bor: expected two integers on stack"),
369    }
370}
371
372/// Bitwise XOR
373///
374/// Stack effect: ( a b -- a^b )
375///
376/// # Safety
377/// Stack must have two Int values on top
378#[unsafe(no_mangle)]
379pub unsafe extern "C" fn patch_seq_bxor(stack: Stack) -> Stack {
380    let (rest, a, b) = unsafe { pop_two(stack, "bxor") };
381    match (a, b) {
382        (Value::Int(a_val), Value::Int(b_val)) => unsafe { push(rest, Value::Int(a_val ^ b_val)) },
383        _ => panic!("bxor: expected two integers on stack"),
384    }
385}
386
387/// Bitwise NOT (one's complement)
388///
389/// Stack effect: ( a -- !a )
390///
391/// # Safety
392/// Stack must have one Int value on top
393#[unsafe(no_mangle)]
394pub unsafe extern "C" fn patch_seq_bnot(stack: Stack) -> Stack {
395    assert!(!stack.is_null(), "bnot: stack is empty");
396    let (rest, a) = unsafe { pop(stack) };
397    match a {
398        Value::Int(a_val) => unsafe { push(rest, Value::Int(!a_val)) },
399        _ => panic!("bnot: expected integer on stack"),
400    }
401}
402
403/// Shift left
404///
405/// Stack effect: ( value count -- result )
406/// Shifts value left by count bits. Negative count or count >= 64 returns 0.
407///
408/// # Safety
409/// Stack must have two Int values on top
410#[unsafe(no_mangle)]
411pub unsafe extern "C" fn patch_seq_shl(stack: Stack) -> Stack {
412    let (rest, value, count) = unsafe { pop_two(stack, "shl") };
413    match (value, count) {
414        (Value::Int(v), Value::Int(c)) => {
415            // Use checked_shl to avoid undefined behavior for out-of-range shifts
416            // Negative counts become large u32 values, which correctly return None
417            let result = if c < 0 {
418                0
419            } else {
420                v.checked_shl(c as u32).unwrap_or(0)
421            };
422            unsafe { push(rest, Value::Int(result)) }
423        }
424        _ => panic!("shl: expected two integers on stack"),
425    }
426}
427
428/// Logical shift right (zero-fill)
429///
430/// Stack effect: ( value count -- result )
431/// Shifts value right by count bits, filling with zeros.
432/// Negative count or count >= 64 returns 0.
433///
434/// # Safety
435/// Stack must have two Int values on top
436#[unsafe(no_mangle)]
437pub unsafe extern "C" fn patch_seq_shr(stack: Stack) -> Stack {
438    let (rest, value, count) = unsafe { pop_two(stack, "shr") };
439    match (value, count) {
440        (Value::Int(v), Value::Int(c)) => {
441            // Use checked_shr to avoid undefined behavior for out-of-range shifts
442            // Cast to u64 for logical (zero-fill) shift behavior
443            let result = if c < 0 {
444                0
445            } else {
446                (v as u64).checked_shr(c as u32).unwrap_or(0) as i64
447            };
448            unsafe { push(rest, Value::Int(result)) }
449        }
450        _ => panic!("shr: expected two integers on stack"),
451    }
452}
453
454/// Population count (count number of 1 bits)
455///
456/// Stack effect: ( n -- count )
457///
458/// # Safety
459/// Stack must have one Int value on top
460#[unsafe(no_mangle)]
461pub unsafe extern "C" fn patch_seq_popcount(stack: Stack) -> Stack {
462    assert!(!stack.is_null(), "popcount: stack is empty");
463    let (rest, a) = unsafe { pop(stack) };
464    match a {
465        Value::Int(v) => unsafe { push(rest, Value::Int(v.count_ones() as i64)) },
466        _ => panic!("popcount: expected integer on stack"),
467    }
468}
469
470/// Count leading zeros
471///
472/// Stack effect: ( n -- count )
473///
474/// # Safety
475/// Stack must have one Int value on top
476#[unsafe(no_mangle)]
477pub unsafe extern "C" fn patch_seq_clz(stack: Stack) -> Stack {
478    assert!(!stack.is_null(), "clz: stack is empty");
479    let (rest, a) = unsafe { pop(stack) };
480    match a {
481        Value::Int(v) => unsafe { push(rest, Value::Int(v.leading_zeros() as i64)) },
482        _ => panic!("clz: expected integer on stack"),
483    }
484}
485
486/// Count trailing zeros
487///
488/// Stack effect: ( n -- count )
489///
490/// # Safety
491/// Stack must have one Int value on top
492#[unsafe(no_mangle)]
493pub unsafe extern "C" fn patch_seq_ctz(stack: Stack) -> Stack {
494    assert!(!stack.is_null(), "ctz: stack is empty");
495    let (rest, a) = unsafe { pop(stack) };
496    match a {
497        Value::Int(v) => unsafe { push(rest, Value::Int(v.trailing_zeros() as i64)) },
498        _ => panic!("ctz: expected integer on stack"),
499    }
500}
501
502/// Push the bit width of Int (64)
503///
504/// Stack effect: ( -- 64 )
505///
506/// # Safety
507/// Always safe to call
508#[unsafe(no_mangle)]
509pub unsafe extern "C" fn patch_seq_int_bits(stack: Stack) -> Stack {
510    unsafe { push(stack, Value::Int(64)) }
511}
512
513/// Helper for peeking at the top integer value without popping
514///
515/// Returns the integer value on top of the stack without modifying the stack.
516/// Used in conjunction with pop_stack for conditional branching.
517///
518/// **Why separate peek and pop?**
519/// In LLVM IR for conditionals, we need to:
520/// 1. Extract the integer value to test it (peek_int_value)
521/// 2. Branch based on that value (icmp + br)
522/// 3. Free the stack node in both branches (pop_stack)
523///
524/// A combined pop_int_value would leak memory since we'd need the value
525/// before branching but couldn't return the updated stack pointer through
526/// both branches. Separating these operations prevents memory leaks.
527///
528/// Stack effect: ( n -- n ) returns n
529///
530/// # Safety
531/// Stack must have an Int value on top
532#[cfg(not(feature = "nanbox"))]
533#[unsafe(no_mangle)]
534pub unsafe extern "C" fn patch_seq_peek_int_value(stack: Stack) -> i64 {
535    assert!(!stack.is_null(), "peek_int_value: stack is empty");
536
537    // Stack points to next push location, so top is at stack - 1
538    let sv = unsafe { peek_sv(stack) };
539    if sv.slot0 == DISC_INT {
540        sv.slot1 as i64
541    } else {
542        panic!(
543            "peek_int_value: expected Int on stack, got discriminant {}",
544            sv.slot0
545        )
546    }
547}
548
549/// Peek at an int value on top of stack without popping (NaN-boxed version)
550///
551/// # Safety
552/// Stack must have an Int value on top
553#[cfg(feature = "nanbox")]
554#[unsafe(no_mangle)]
555pub unsafe extern "C" fn patch_seq_peek_int_value(stack: Stack) -> i64 {
556    assert!(!stack.is_null(), "peek_int_value: stack is empty");
557
558    let sv = unsafe { peek_sv(stack) };
559    if sv.0.is_int() {
560        sv.0.as_int()
561    } else {
562        panic!("peek_int_value: expected Int on stack")
563    }
564}
565
566/// Peek at a bool value on top of stack without popping (for pattern matching)
567///
568/// # Safety
569/// Stack must have a Bool value on top
570#[cfg(not(feature = "nanbox"))]
571#[unsafe(no_mangle)]
572pub unsafe extern "C" fn patch_seq_peek_bool_value(stack: Stack) -> bool {
573    use crate::stack::DISC_BOOL;
574    assert!(!stack.is_null(), "peek_bool_value: stack is empty");
575
576    // Stack points to next push location, so top is at stack - 1
577    let sv = unsafe { peek_sv(stack) };
578    if sv.slot0 == DISC_BOOL {
579        sv.slot1 != 0
580    } else {
581        panic!(
582            "peek_bool_value: expected Bool on stack, got discriminant {}",
583            sv.slot0
584        )
585    }
586}
587
588/// Peek at a bool value on top of stack without popping (NaN-boxed version)
589///
590/// # Safety
591/// Stack must have a Bool value on top
592#[cfg(feature = "nanbox")]
593#[unsafe(no_mangle)]
594pub unsafe extern "C" fn patch_seq_peek_bool_value(stack: Stack) -> bool {
595    assert!(!stack.is_null(), "peek_bool_value: stack is empty");
596
597    let sv = unsafe { peek_sv(stack) };
598    if sv.0.is_bool() {
599        sv.0.as_bool()
600    } else {
601        panic!("peek_bool_value: expected Bool on stack")
602    }
603}
604
605/// Helper for popping without extracting the value (for conditionals)
606///
607/// Pops the top stack node and returns the updated stack pointer.
608/// Used after peek_int_value to free the condition value's stack node.
609///
610/// Stack effect: ( n -- )
611///
612/// # Safety
613/// Stack must not be empty
614#[unsafe(no_mangle)]
615pub unsafe extern "C" fn patch_seq_pop_stack(stack: Stack) -> Stack {
616    assert!(!stack.is_null(), "pop_stack: stack is empty");
617    let (rest, _value) = unsafe { pop(stack) };
618    rest
619}
620
621// Public re-exports with short names for internal use
622pub use patch_seq_add as add;
623pub use patch_seq_and as and;
624pub use patch_seq_band as band;
625pub use patch_seq_bnot as bnot;
626pub use patch_seq_bor as bor;
627pub use patch_seq_bxor as bxor;
628pub use patch_seq_clz as clz;
629pub use patch_seq_ctz as ctz;
630pub use patch_seq_divide as divide;
631pub use patch_seq_eq as eq;
632pub use patch_seq_gt as gt;
633pub use patch_seq_gte as gte;
634pub use patch_seq_int_bits as int_bits;
635pub use patch_seq_lt as lt;
636pub use patch_seq_lte as lte;
637pub use patch_seq_multiply as multiply;
638pub use patch_seq_neq as neq;
639pub use patch_seq_not as not;
640pub use patch_seq_or as or;
641pub use patch_seq_popcount as popcount;
642pub use patch_seq_push_bool as push_bool;
643pub use patch_seq_push_int as push_int;
644pub use patch_seq_shl as shl;
645pub use patch_seq_shr as shr;
646pub use patch_seq_subtract as subtract;
647
648#[cfg(test)]
649mod tests {
650    use super::*;
651
652    #[test]
653    fn test_add() {
654        unsafe {
655            let stack = crate::stack::alloc_test_stack();
656            let stack = push_int(stack, 5);
657            let stack = push_int(stack, 3);
658            let stack = add(stack);
659
660            let (_stack, result) = pop(stack);
661            assert_eq!(result, Value::Int(8));
662        }
663    }
664
665    #[test]
666    fn test_subtract() {
667        unsafe {
668            let stack = crate::stack::alloc_test_stack();
669            let stack = push_int(stack, 10);
670            let stack = push_int(stack, 3);
671            let stack = subtract(stack);
672
673            let (_stack, result) = pop(stack);
674            assert_eq!(result, Value::Int(7));
675        }
676    }
677
678    #[test]
679    fn test_multiply() {
680        unsafe {
681            let stack = crate::stack::alloc_test_stack();
682            let stack = push_int(stack, 4);
683            let stack = push_int(stack, 5);
684            let stack = multiply(stack);
685
686            let (_stack, result) = pop(stack);
687            assert_eq!(result, Value::Int(20));
688        }
689    }
690
691    #[test]
692    fn test_divide() {
693        unsafe {
694            let stack = crate::stack::alloc_test_stack();
695            let stack = push_int(stack, 20);
696            let stack = push_int(stack, 4);
697            let stack = divide(stack);
698
699            let (_stack, result) = pop(stack);
700            assert_eq!(result, Value::Int(5));
701        }
702    }
703
704    #[test]
705    fn test_comparisons() {
706        unsafe {
707            // Test eq (returns true/false Bool)
708            let stack = crate::stack::alloc_test_stack();
709            let stack = push_int(stack, 5);
710            let stack = push_int(stack, 5);
711            let stack = eq(stack);
712            let (_stack, result) = pop(stack);
713            assert_eq!(result, Value::Bool(true));
714
715            // Test lt
716            let stack = push_int(stack, 3);
717            let stack = push_int(stack, 5);
718            let stack = lt(stack);
719            let (_stack, result) = pop(stack);
720            assert_eq!(result, Value::Bool(true));
721
722            // Test gt
723            let stack = push_int(stack, 7);
724            let stack = push_int(stack, 5);
725            let stack = gt(stack);
726            let (_stack, result) = pop(stack);
727            assert_eq!(result, Value::Bool(true));
728        }
729    }
730
731    // This test uses i64::MAX and i64::MIN which are outside the 44-bit NaN-boxing range
732    #[test]
733    #[cfg(not(feature = "nanbox"))]
734    fn test_overflow_wrapping() {
735        // Test that arithmetic uses wrapping semantics (defined overflow behavior)
736        unsafe {
737            // Test add overflow: i64::MAX + 1 should wrap
738            let stack = crate::stack::alloc_test_stack();
739            let stack = push_int(stack, i64::MAX);
740            let stack = push_int(stack, 1);
741            let stack = add(stack);
742            let (_stack, result) = pop(stack);
743            assert_eq!(result, Value::Int(i64::MIN)); // Wraps to minimum
744
745            // Test multiply overflow
746            let stack = push_int(stack, i64::MAX);
747            let stack = push_int(stack, 2);
748            let stack = multiply(stack);
749            let (_stack, result) = pop(stack);
750            // Result is well-defined (wrapping)
751            assert!(matches!(result, Value::Int(_)));
752
753            // Test subtract underflow
754            let stack = push_int(stack, i64::MIN);
755            let stack = push_int(stack, 1);
756            let stack = subtract(stack);
757            let (_stack, result) = pop(stack);
758            assert_eq!(result, Value::Int(i64::MAX)); // Wraps to maximum
759        }
760    }
761
762    #[test]
763    fn test_negative_division() {
764        unsafe {
765            // Test negative dividend
766            let stack = crate::stack::alloc_test_stack();
767            let stack = push_int(stack, -10);
768            let stack = push_int(stack, 3);
769            let stack = divide(stack);
770            let (_stack, result) = pop(stack);
771            assert_eq!(result, Value::Int(-3)); // Truncates toward zero
772
773            // Test negative divisor
774            let stack = push_int(stack, 10);
775            let stack = push_int(stack, -3);
776            let stack = divide(stack);
777            let (_stack, result) = pop(stack);
778            assert_eq!(result, Value::Int(-3));
779
780            // Test both negative
781            let stack = push_int(stack, -10);
782            let stack = push_int(stack, -3);
783            let stack = divide(stack);
784            let (_stack, result) = pop(stack);
785            assert_eq!(result, Value::Int(3));
786        }
787    }
788
789    // This test uses i64::MIN which is outside the 44-bit NaN-boxing range
790    #[test]
791    #[cfg(not(feature = "nanbox"))]
792    fn test_division_overflow_edge_case() {
793        // Critical edge case: i64::MIN / -1 would overflow
794        // Regular division panics, but wrapping_div wraps to i64::MIN
795        unsafe {
796            let stack = crate::stack::alloc_test_stack();
797            let stack = push_int(stack, i64::MIN);
798            let stack = push_int(stack, -1);
799            let stack = divide(stack);
800            let (_stack, result) = pop(stack);
801            // i64::MIN / -1 would be i64::MAX + 1, which wraps to i64::MIN
802            assert_eq!(result, Value::Int(i64::MIN));
803        }
804    }
805
806    #[test]
807    fn test_and_true_true() {
808        unsafe {
809            let stack = crate::stack::alloc_test_stack();
810            let stack = push_int(stack, 1);
811            let stack = push_int(stack, 1);
812            let stack = and(stack);
813            let (_stack, result) = pop(stack);
814            assert_eq!(result, Value::Int(1));
815        }
816    }
817
818    #[test]
819    fn test_and_true_false() {
820        unsafe {
821            let stack = crate::stack::alloc_test_stack();
822            let stack = push_int(stack, 1);
823            let stack = push_int(stack, 0);
824            let stack = and(stack);
825            let (_stack, result) = pop(stack);
826            assert_eq!(result, Value::Int(0));
827        }
828    }
829
830    #[test]
831    fn test_and_false_false() {
832        unsafe {
833            let stack = crate::stack::alloc_test_stack();
834            let stack = push_int(stack, 0);
835            let stack = push_int(stack, 0);
836            let stack = and(stack);
837            let (_stack, result) = pop(stack);
838            assert_eq!(result, Value::Int(0));
839        }
840    }
841
842    #[test]
843    fn test_or_true_true() {
844        unsafe {
845            let stack = crate::stack::alloc_test_stack();
846            let stack = push_int(stack, 1);
847            let stack = push_int(stack, 1);
848            let stack = or(stack);
849            let (_stack, result) = pop(stack);
850            assert_eq!(result, Value::Int(1));
851        }
852    }
853
854    #[test]
855    fn test_or_true_false() {
856        unsafe {
857            let stack = crate::stack::alloc_test_stack();
858            let stack = push_int(stack, 1);
859            let stack = push_int(stack, 0);
860            let stack = or(stack);
861            let (_stack, result) = pop(stack);
862            assert_eq!(result, Value::Int(1));
863        }
864    }
865
866    #[test]
867    fn test_or_false_false() {
868        unsafe {
869            let stack = crate::stack::alloc_test_stack();
870            let stack = push_int(stack, 0);
871            let stack = push_int(stack, 0);
872            let stack = or(stack);
873            let (_stack, result) = pop(stack);
874            assert_eq!(result, Value::Int(0));
875        }
876    }
877
878    #[test]
879    fn test_not_true() {
880        unsafe {
881            let stack = crate::stack::alloc_test_stack();
882            let stack = push_int(stack, 1);
883            let stack = not(stack);
884            let (_stack, result) = pop(stack);
885            assert_eq!(result, Value::Int(0));
886        }
887    }
888
889    #[test]
890    fn test_not_false() {
891        unsafe {
892            let stack = crate::stack::alloc_test_stack();
893            let stack = push_int(stack, 0);
894            let stack = not(stack);
895            let (_stack, result) = pop(stack);
896            assert_eq!(result, Value::Int(1));
897        }
898    }
899
900    #[test]
901    fn test_and_nonzero_values() {
902        // Forth-style: any non-zero is true
903        unsafe {
904            let stack = crate::stack::alloc_test_stack();
905            let stack = push_int(stack, 42);
906            let stack = push_int(stack, -5);
907            let stack = and(stack);
908            let (_stack, result) = pop(stack);
909            assert_eq!(result, Value::Int(1));
910        }
911    }
912
913    #[test]
914    fn test_divide_by_zero_sets_error() {
915        unsafe {
916            crate::error::clear_runtime_error();
917            let stack = crate::stack::alloc_test_stack();
918            let stack = push_int(stack, 42);
919            let stack = push_int(stack, 0);
920            let stack = divide(stack);
921
922            // Should have set an error
923            assert!(crate::error::has_runtime_error());
924            let error = crate::error::take_runtime_error().unwrap();
925            assert!(error.contains("division by zero"));
926
927            // Should have pushed 0 as sentinel
928            let (_stack, result) = pop(stack);
929            assert_eq!(result, Value::Int(0));
930        }
931    }
932}