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