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