Skip to main content

seq_runtime/arithmetic/
bitwise.rs

1//! Bitwise operations: `band`, `bor`, `bxor`, `bnot`, `shl`, `shr`,
2//! plus bit-counting intrinsics (`popcount`, `clz`, `ctz`) and
3//! `int_bits` (the integer's bit width).
4//!
5//! Seq's `Int` is a 63-bit signed integer (the low bit of the tagged
6//! stack encoding is the tag). All bitwise operations report results in
7//! the 63-bit model, even though the underlying Rust storage is `i64`.
8//! `shl`/`shr` results that fall outside the 63-bit range return 0
9//! rather than silently truncating during retag.
10
11use crate::stack::{Stack, pop, pop_two, push};
12use crate::value::Value;
13
14/// Minimum value representable in a 63-bit signed integer (-2^62).
15const I63_MIN: i64 = -(1i64 << 62);
16/// Maximum value representable in a 63-bit signed integer (2^62 - 1).
17const I63_MAX: i64 = (1i64 << 62) - 1;
18/// Bit mask covering the 63 value-bits of a Seq Int (everything except
19/// the i64 sign-extension bit at position 63).
20const I63_MASK: u64 = (1u64 << 63) - 1;
21
22/// Whether `v` fits in the 63-bit signed range Seq's `Int` actually
23/// represents.
24#[inline]
25fn fits_in_i63(v: i64) -> bool {
26    (I63_MIN..=I63_MAX).contains(&v)
27}
28
29// ============================================================================
30// Bitwise Operations
31// ============================================================================
32
33/// Bitwise AND
34///
35/// Stack effect: ( a b -- a&b )
36///
37/// # Safety
38/// Stack must have two Int values on top
39#[unsafe(no_mangle)]
40pub unsafe extern "C" fn patch_seq_band(stack: Stack) -> Stack {
41    let (rest, a, b) = unsafe { pop_two(stack, "band") };
42    match (a, b) {
43        (Value::Int(a_val), Value::Int(b_val)) => unsafe { push(rest, Value::Int(a_val & b_val)) },
44        _ => panic!("band: expected two integers on stack"),
45    }
46}
47
48/// Bitwise OR
49///
50/// Stack effect: ( a b -- a|b )
51///
52/// # Safety
53/// Stack must have two Int values on top
54#[unsafe(no_mangle)]
55pub unsafe extern "C" fn patch_seq_bor(stack: Stack) -> Stack {
56    let (rest, a, b) = unsafe { pop_two(stack, "bor") };
57    match (a, b) {
58        (Value::Int(a_val), Value::Int(b_val)) => unsafe { push(rest, Value::Int(a_val | b_val)) },
59        _ => panic!("bor: expected two integers on stack"),
60    }
61}
62
63/// Bitwise XOR
64///
65/// Stack effect: ( a b -- a^b )
66///
67/// # Safety
68/// Stack must have two Int values on top
69#[unsafe(no_mangle)]
70pub unsafe extern "C" fn patch_seq_bxor(stack: Stack) -> Stack {
71    let (rest, a, b) = unsafe { pop_two(stack, "bxor") };
72    match (a, b) {
73        (Value::Int(a_val), Value::Int(b_val)) => unsafe { push(rest, Value::Int(a_val ^ b_val)) },
74        _ => panic!("bxor: expected two integers on stack"),
75    }
76}
77
78/// Bitwise NOT (one's complement)
79///
80/// Stack effect: ( a -- !a )
81///
82/// # Safety
83/// Stack must have one Int value on top
84#[unsafe(no_mangle)]
85pub unsafe extern "C" fn patch_seq_bnot(stack: Stack) -> Stack {
86    assert!(!stack.is_null(), "bnot: stack is empty");
87    let (rest, a) = unsafe { pop(stack) };
88    match a {
89        Value::Int(a_val) => unsafe { push(rest, Value::Int(!a_val)) },
90        _ => panic!("bnot: expected integer on stack"),
91    }
92}
93
94/// Shift left
95///
96/// Stack effect: ( value count -- result )
97/// Shifts value left by count bits. Returns 0 for negative counts,
98/// counts >= 64, or any result that falls outside the 63-bit Int
99/// range. The 63-bit clamp matters because the tagged stack encoding
100/// would otherwise silently lose bit 62 when retagging an out-of-range
101/// `i64`.
102///
103/// # Safety
104/// Stack must have two Int values on top
105#[unsafe(no_mangle)]
106pub unsafe extern "C" fn patch_seq_shl(stack: Stack) -> Stack {
107    let (rest, value, count) = unsafe { pop_two(stack, "shl") };
108    match (value, count) {
109        (Value::Int(v), Value::Int(c)) => {
110            // Reject the count *before* casting to u32: the i64 range
111            // includes counts > u32::MAX, which would otherwise truncate
112            // into a small valid count and silently violate the contract.
113            let result = if !(0..64).contains(&c) {
114                0
115            } else {
116                let raw = v.checked_shl(c as u32).unwrap_or(0);
117                if fits_in_i63(raw) { raw } else { 0 }
118            };
119            unsafe { push(rest, Value::Int(result)) }
120        }
121        _ => panic!("shl: expected two integers on stack"),
122    }
123}
124
125/// Logical shift right (zero-fill)
126///
127/// Stack effect: ( value count -- result )
128/// Shifts value right by count bits, filling with zeros. Returns 0 for
129/// negative counts, counts >= 64, or any result that falls outside the
130/// 63-bit Int range. Logical-shifting a negative produces a u64 that
131/// often exceeds `I63_MAX`; that case clamps to 0 rather than silently
132/// truncating in the tagger.
133///
134/// # Safety
135/// Stack must have two Int values on top
136#[unsafe(no_mangle)]
137pub unsafe extern "C" fn patch_seq_shr(stack: Stack) -> Stack {
138    let (rest, value, count) = unsafe { pop_two(stack, "shr") };
139    match (value, count) {
140        (Value::Int(v), Value::Int(c)) => {
141            // Reject the count *before* casting to u32 — see `shl` for why.
142            let result = if !(0..64).contains(&c) {
143                0
144            } else {
145                let raw = (v as u64).checked_shr(c as u32).unwrap_or(0) as i64;
146                if fits_in_i63(raw) { raw } else { 0 }
147            };
148            unsafe { push(rest, Value::Int(result)) }
149        }
150        _ => panic!("shr: expected two integers on stack"),
151    }
152}
153
154/// Population count (count number of 1 bits in the 63-bit Int).
155///
156/// Stack effect: ( n -- count )
157///
158/// Counts bits in the 63-bit two's-complement representation of `n`.
159/// Negatives have bit 63 set in the underlying `i64` purely as sign
160/// extension, so we mask it off before counting. `popcount(-1) = 63`.
161///
162/// # Safety
163/// Stack must have one Int value on top
164#[unsafe(no_mangle)]
165pub unsafe extern "C" fn patch_seq_popcount(stack: Stack) -> Stack {
166    assert!(!stack.is_null(), "popcount: stack is empty");
167    let (rest, a) = unsafe { pop(stack) };
168    match a {
169        Value::Int(v) => {
170            let count = ((v as u64) & I63_MASK).count_ones() as i64;
171            unsafe { push(rest, Value::Int(count)) }
172        }
173        _ => panic!("popcount: expected integer on stack"),
174    }
175}
176
177/// Count leading zeros, relative to the 63-bit Int width.
178///
179/// Stack effect: ( n -- count )
180///
181/// `clz(0) = 63`. The `i64::leading_zeros` count is one larger than the
182/// 63-bit count for any non-negative value (the implicit sign-extension
183/// bit), and one larger for `0` as well — `saturating_sub(1)` collapses
184/// both cases. For negatives the i64 sign-extension bit is 1, so
185/// `i64::leading_zeros` is already 0.
186///
187/// # Safety
188/// Stack must have one Int value on top
189#[unsafe(no_mangle)]
190pub unsafe extern "C" fn patch_seq_clz(stack: Stack) -> Stack {
191    assert!(!stack.is_null(), "clz: stack is empty");
192    let (rest, a) = unsafe { pop(stack) };
193    match a {
194        Value::Int(v) => {
195            let lz = (v.leading_zeros() as i64).saturating_sub(1);
196            unsafe { push(rest, Value::Int(lz)) }
197        }
198        _ => panic!("clz: expected integer on stack"),
199    }
200}
201
202/// Count trailing zeros, relative to the 63-bit Int width.
203///
204/// Stack effect: ( n -- count )
205///
206/// `ctz(0) = 63`. Any non-zero 63-bit value has at least one set bit at
207/// position ≤ 62, so `i64::trailing_zeros` already produces a value in
208/// `[0, 62]`; only `v == 0` needs the special case to avoid returning
209/// 64.
210///
211/// # Safety
212/// Stack must have one Int value on top
213#[unsafe(no_mangle)]
214pub unsafe extern "C" fn patch_seq_ctz(stack: Stack) -> Stack {
215    assert!(!stack.is_null(), "ctz: stack is empty");
216    let (rest, a) = unsafe { pop(stack) };
217    match a {
218        Value::Int(v) => {
219            let tz = if v == 0 {
220                63
221            } else {
222                v.trailing_zeros() as i64
223            };
224            unsafe { push(rest, Value::Int(tz)) }
225        }
226        _ => panic!("ctz: expected integer on stack"),
227    }
228}
229
230/// Push the bit width of Int (63).
231///
232/// Stack effect: ( -- 63 )
233///
234/// Seq's `Int` is a 63-bit signed integer; the low bit of the tagged
235/// stack encoding is the tag, leaving 63 bits for the value.
236///
237/// # Safety
238/// Always safe to call
239#[unsafe(no_mangle)]
240pub unsafe extern "C" fn patch_seq_int_bits(stack: Stack) -> Stack {
241    unsafe { push(stack, Value::Int(63)) }
242}