Skip to main content

logicaffeine_compile/accel/
int64.rs

1//! The VM's 64-bit integer operations on narrow lanes: each is the checked semantics of
2//! `fabric_rtl::{AluOp, FuncUnit, CmpKind}::eval_checked`, built from the multi-word core. A result that
3//! leaves `i64`, a zero divisor or an exponent the VM refuses is reported in the fault word as the
4//! divergence's code, never as a wrapped value.
5
6use crate::codegen_sva::fabric_rtl::Divergence;
7
8use super::multiword::{Builder, Mw};
9
10/// The bits of a routine's fault word: 0, or a [`Divergence`] code.
11pub const FAULT_BITS: u32 = 8;
12
13/// The words of a 64-bit value on this builder's lanes.
14pub fn n64(b: &Builder) -> usize {
15    b.shape().words_for(64)
16}
17
18/// The fault words of this builder's lanes.
19pub fn nfault(b: &Builder) -> usize {
20    b.shape().words_for(FAULT_BITS)
21}
22
23/// A fault word that is `d`'s code when `cond` is 1, else `otherwise`.
24pub fn fault_when(b: &mut Builder, cond: u32, d: Divergence, otherwise: &Mw) -> Mw {
25    let n = otherwise.len();
26    let code = b.mw_const(d.code() as u128, n);
27    b.mw_sel(cond, &code, otherwise)
28}
29
30/// No fault.
31pub fn no_fault(b: &mut Builder) -> Mw {
32    let n = nfault(b);
33    b.mw_zero(n)
34}
35
36/// `(|a|, sign)` of a two's-complement value; `|MIN|` is `2^63`, exact in the unsigned width.
37pub fn magnitude(b: &mut Builder, a: &Mw) -> (Mw, u32) {
38    let s = b.mw_top_bit(a);
39    let neg = b.mw_neg(a);
40    (b.mw_sel(s, &neg, a), s)
41}
42
43/// `a + b`, faulting where the VM promotes.
44pub fn add(b: &mut Builder, x: &Mw, y: &Mw) -> (Mw, Mw) {
45    let z = b.k(0);
46    let (s, _) = b.mw_add(x, y, z);
47    let sx = b.mw_top_bit(x);
48    let sy = b.mw_top_bit(y);
49    let ss = b.mw_top_bit(&s);
50    let same = b.xor(sx, sy);
51    let same = b.bit_not(same);
52    let flipped = b.xor(sx, ss);
53    let ovf = b.and(same, flipped);
54    let ok = no_fault(b);
55    (s, fault_when(b, ovf, Divergence::IntOverflow, &ok))
56}
57
58/// `a − b`, faulting where the VM promotes.
59pub fn sub(b: &mut Builder, x: &Mw, y: &Mw) -> (Mw, Mw) {
60    let z = b.k(0);
61    let (d, _) = b.mw_sub(x, y, z);
62    let sx = b.mw_top_bit(x);
63    let sy = b.mw_top_bit(y);
64    let sd = b.mw_top_bit(&d);
65    let differ = b.xor(sx, sy);
66    let flipped = b.xor(sx, sd);
67    let ovf = b.and(differ, flipped);
68    let ok = no_fault(b);
69    (d, fault_when(b, ovf, Divergence::IntOverflow, &ok))
70}
71
72/// The signed product of two 64-bit values and 1 when it leaves `i64`.
73pub fn mul_checked(b: &mut Builder, x: &Mw, y: &Mw) -> (Mw, u32) {
74    let n = x.len();
75    let (mx, sx) = magnitude(b, x);
76    let (my, sy) = magnitude(b, y);
77    let p = b.mw_mul(&mx, &my);
78    let lo = p.slice(0, n);
79    let hi = p.slice(n, 2 * n);
80    let neg = b.xor(sx, sy);
81    let hi_clear = b.mw_is_zero(&hi);
82    let top = b.mw_top_bit(&lo);
83    let min_mag = b.mw_const(1u128 << 63, n);
84    let is_min_mag = b.mw_eq(&lo, &min_mag);
85    let fits_pos = b.bit_not(top);
86    let fits_neg = b.or(fits_pos, is_min_mag);
87    let fits_low = b.sel(neg, fits_neg, fits_pos);
88    let fits = b.and(hi_clear, fits_low);
89    let neg_lo = b.mw_neg(&lo);
90    let value = b.mw_sel(neg, &neg_lo, &lo);
91    let ovf = b.bit_not(fits);
92    (value, ovf)
93}
94
95/// `a × b`, faulting where the VM promotes.
96pub fn mul(b: &mut Builder, x: &Mw, y: &Mw) -> (Mw, Mw) {
97    let (v, ovf) = mul_checked(b, x, y);
98    let ok = no_fault(b);
99    (v, fault_when(b, ovf, Divergence::IntOverflow, &ok))
100}
101
102/// The truncated quotient and remainder of two's-complement `x ÷ y`, with the zero-divisor flag and the
103/// `MIN ÷ −1` flag. A zero divisor divides by one instead, so nothing downstream sees an undefined word.
104fn divmod_signed(b: &mut Builder, x: &Mw, y: &Mw) -> (Mw, Mw, u32, u32) {
105    let n = x.len();
106    let y_zero = b.mw_is_zero(y);
107    let one = b.mw_const(1, n);
108    let y_safe = b.mw_sel(y_zero, &one, y);
109    let (mx, sx) = magnitude(b, x);
110    let (my, sy) = magnitude(b, &y_safe);
111    let (q, r) = b.mw_divrem(&mx, &my);
112    let qneg = b.xor(sx, sy);
113    let nq = b.mw_neg(&q);
114    let q = b.mw_sel(qneg, &nq, &q);
115    let nr = b.mw_neg(&r);
116    let r = b.mw_sel(sx, &nr, &r);
117    let min = b.mw_const(1u128 << 63, n);
118    let neg_one = b.mw_const(u64::MAX as u128, n);
119    let x_min = b.mw_eq(x, &min);
120    let y_m1 = b.mw_eq(y, &neg_one);
121    let min_by_m1 = b.and(x_min, y_m1);
122    (q, r, y_zero, min_by_m1)
123}
124
125/// `a / b` truncating, as the VM's `Div` on two `Int`s.
126pub fn div(b: &mut Builder, x: &Mw, y: &Mw) -> (Mw, Mw) {
127    let (q, _, zero, min_by_m1) = divmod_signed(b, x, y);
128    let ok = no_fault(b);
129    let f = fault_when(b, min_by_m1, Divergence::IntOverflow, &ok);
130    (q, fault_when(b, zero, Divergence::DivideByZero, &f))
131}
132
133/// `a % b` taking the dividend's sign, as the VM's `Mod` on two `Int`s.
134pub fn rem(b: &mut Builder, x: &Mw, y: &Mw) -> (Mw, Mw) {
135    let (_, r, zero, _) = divmod_signed(b, x, y);
136    let ok = no_fault(b);
137    (r, fault_when(b, zero, Divergence::DivideByZero, &ok))
138}
139
140/// `a // b` rounding toward negative infinity, as the VM's `FloorDiv` on two `Int`s.
141pub fn floor_div(b: &mut Builder, x: &Mw, y: &Mw) -> (Mw, Mw) {
142    let (q, r, zero, min_by_m1) = divmod_signed(b, x, y);
143    let sx = b.mw_top_bit(x);
144    let sy = b.mw_top_bit(y);
145    let differ = b.xor(sx, sy);
146    let r_zero = b.mw_is_zero(&r);
147    let inexact = b.bit_not(r_zero);
148    let down = b.and(differ, inexact);
149    let n = q.len();
150    let zero_w = b.k(0);
151    let dec = b.mw_from_word(down, n);
152    let (q2, _) = b.mw_sub(&q, &dec, zero_w);
153    let ok = no_fault(b);
154    let f = fault_when(b, min_by_m1, Divergence::IntOverflow, &ok);
155    (q2, fault_when(b, zero, Divergence::DivideByZero, &f))
156}
157
158/// `a ** b` by square-and-multiply over the exponent's 32 bits, on magnitudes: a factor that overflowed
159/// counts only when a later set exponent bit multiplies it in, which is exactly when the VM's exact power
160/// leaves `i64`.
161pub fn pow(b: &mut Builder, x: &Mw, e: &Mw) -> (Mw, Mw) {
162    let n = x.len();
163    let (mx, sx) = magnitude(b, x);
164    let e_neg = b.mw_top_bit(e);
165    let hi32 = b.mw_shr(e, 32);
166    let hi_set = b.mw_or_reduce(&hi32);
167    let hi_set = b.nonzero(hi_set);
168    let not_neg = b.bit_not(e_neg);
169    let too_large = b.and(hi_set, not_neg);
170    let one = b.mw_const(1, n);
171    let acc = b.mw_state(&one);
172    let base = b.mw_state(&mx);
173    let z = b.k(0);
174    let acc_ovf = b.state(z);
175    let base_ovf = b.state(z);
176    let exp = b.mw_state(e);
177    b.repeat(32, |s| {
178        let bit = s.mw_bit(&exp, 0);
179        let p = s.mw_mul(&acc, &base);
180        let p_lo = p.slice(0, n);
181        let p_hi = p.slice(n, 2 * n);
182        let p_over = s.mw_or_reduce(&p_hi);
183        let p_over = s.nonzero(p_over);
184        let over_now = s.or(p_over, base_ovf);
185        let acc2 = s.mw_sel(bit, &p_lo, &acc);
186        let took = s.and(bit, over_now);
187        let acc_ovf2 = s.or(acc_ovf, took);
188        let sq = s.mw_mul(&base, &base);
189        let sq_lo = sq.slice(0, n);
190        let sq_hi = sq.slice(n, 2 * n);
191        let sq_over = s.mw_or_reduce(&sq_hi);
192        let sq_over = s.nonzero(sq_over);
193        let base_ovf2 = s.or(base_ovf, sq_over);
194        let exp2 = s.mw_shr(&exp, 1);
195        s.mw_assign(&acc, &acc2);
196        s.assign(acc_ovf, acc_ovf2);
197        s.mw_assign(&base, &sq_lo);
198        s.assign(base_ovf, base_ovf2);
199        s.mw_assign(&exp, &exp2);
200    });
201    let odd = b.mw_bit(e, 0);
202    let neg = b.and(sx, odd);
203    let top = b.mw_top_bit(&acc);
204    let min_mag = b.mw_const(1u128 << 63, n);
205    let is_min_mag = b.mw_eq(&acc, &min_mag);
206    let fits_pos = b.bit_not(top);
207    let fits_neg = b.or(fits_pos, is_min_mag);
208    let fits = b.sel(neg, fits_neg, fits_pos);
209    let range_ovf = b.bit_not(fits);
210    let ovf = b.or(acc_ovf, range_ovf);
211    let nacc = b.mw_neg(&acc);
212    let value = b.mw_sel(neg, &nacc, &acc);
213    let ok = no_fault(b);
214    let f = fault_when(b, ovf, Divergence::IntOverflow, &ok);
215    let f = fault_when(b, too_large, Divergence::ExponentTooLarge, &f);
216    (
217        value,
218        fault_when(b, e_neg, Divergence::NegativeExponent, &f),
219    )
220}
221
222/// The VM's shift count is `b as u32` masked to the word size: the low six bits of `b`, which is exactly the
223/// part of the amount a six-stage barrel reads.
224const SHIFT_BITS: u32 = 6;
225
226/// `a.wrapping_shl(b as u32)`.
227pub fn shl(b: &mut Builder, x: &Mw, y: &Mw) -> Mw {
228    b.mw_shl_var(x, y, SHIFT_BITS)
229}
230
231/// `a.wrapping_shr(b as u32)` on the signed value (arithmetic).
232pub fn shr(b: &mut Builder, x: &Mw, y: &Mw) -> Mw {
233    b.mw_sar_var(x, y, SHIFT_BITS)
234}
235
236/// `(a as u64).wrapping_shr(b as u32)` (logical).
237pub fn ushr(b: &mut Builder, x: &Mw, y: &Mw) -> Mw {
238    b.mw_shr_var_sticky(x, y, SHIFT_BITS).0
239}
240
241/// The high word of the unsigned 128-bit product.
242pub fn mul_hi_u(b: &mut Builder, x: &Mw, y: &Mw) -> Mw {
243    let n = x.len();
244    let p = b.mw_mul(x, y);
245    p.slice(n, 2 * n)
246}
247
248/// 1 when the signed product leaves `i64`, as a 64-bit value.
249pub fn mul_ovf_s(b: &mut Builder, x: &Mw, y: &Mw) -> Mw {
250    let n = x.len();
251    let (_, ovf) = mul_checked(b, x, y);
252    b.mw_from_word(ovf, n)
253}
254
255/// The set bits of the 64-bit pattern, as a 64-bit value.
256pub fn popcount(b: &mut Builder, x: &Mw) -> Mw {
257    let n = x.len();
258    let c = b.mw_popcount(x, b.shape().words_for(8));
259    b.mw_resize(&c, n)
260}
261
262/// A 0/1 predicate as a 64-bit value.
263pub fn bool64(b: &mut Builder, p: u32, n: usize) -> Mw {
264    b.mw_from_word(p, n)
265}