1use crate::codegen_sva::fabric_target::Precision;
27use crate::spatial::target::SpatialTarget;
28use logicaffeine_base::numeric::BigInt;
29use logicaffeine_kernel::prelude::StandardLibrary;
30use logicaffeine_kernel::tactic_cert::{syn_binary, syn_typed_eq, term_of_syn, CertifiablePrimitive, Syn};
31use logicaffeine_kernel::{double_check, infer_type, Context, DoubleCheck, Term};
32use logicaffeine_proof::aig::{Aig, AigLit};
33use logicaffeine_proof::fraig;
34use std::collections::BTreeMap;
35
36pub fn needs_limbs(target: &SpatialTarget) -> bool {
38 !target.storable_ints().contains(&Precision::Int64)
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
43pub enum Op64 {
44 Add,
46 Sub,
48 Mul,
50 And,
51 Or,
52 Xor,
53 Shl(u32),
55 Shr(u32),
57 Rem(u32),
59}
60
61impl Op64 {
62 pub fn arity(self) -> usize {
64 match self {
65 Op64::Shl(_) | Op64::Shr(_) | Op64::Rem(_) => 1,
66 _ => 2,
67 }
68 }
69
70 pub fn apply(self, a: u64, b: u64) -> u64 {
72 match self {
73 Op64::Add => a.wrapping_add(b),
74 Op64::Sub => a.wrapping_sub(b),
75 Op64::Mul => a.wrapping_mul(b),
76 Op64::And => a & b,
77 Op64::Or => a | b,
78 Op64::Xor => a ^ b,
79 Op64::Shl(k) => a << k,
80 Op64::Shr(k) => a >> k,
81 Op64::Rem(d) => a % u64::from(d),
82 }
83 }
84
85 fn check(self) {
86 match self {
87 Op64::Shl(k) | Op64::Shr(k) => assert!(k < 64, "a 64-bit shift by {k}"),
88 Op64::Rem(d) => assert!(d >= 1, "a remainder by zero"),
89 _ => {}
90 }
91 }
92}
93
94pub type Reg = usize;
96
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99pub enum LaneOp {
100 Input(usize),
102 Const(u32),
103 Add(Reg, Reg),
105 Sub(Reg, Reg),
107 MulLo(Reg, Reg),
109 And(Reg, Reg),
110 Or(Reg, Reg),
111 Xor(Reg, Reg),
112 Shl(Reg, u32),
114 Shr(Reg, u32),
116}
117
118#[derive(Clone, Debug, PartialEq, Eq)]
120struct RemMarks {
121 product: Vec<Reg>,
123 diff: Vec<Reg>,
125 probe: Vec<Reg>,
127 over: Reg,
129}
130
131#[derive(Clone, Debug, PartialEq, Eq)]
134pub struct LaneProgram {
135 op: Op64,
136 ops: Vec<LaneOp>,
137 outputs: [Reg; 2],
138 rem: Option<RemMarks>,
139}
140
141impl LaneProgram {
142 pub fn op(&self) -> Op64 {
143 self.op
144 }
145
146 pub fn ops(&self) -> &[LaneOp] {
147 &self.ops
148 }
149
150 pub fn len(&self) -> usize {
151 self.ops.len()
152 }
153
154 pub fn is_empty(&self) -> bool {
155 self.ops.is_empty()
156 }
157
158 pub fn inputs(&self) -> usize {
160 2 * self.op.arity()
161 }
162
163 pub fn outputs(&self) -> usize {
165 self.outputs.len()
166 }
167
168 pub fn eval(&self, lanes: &[u32]) -> Vec<u32> {
170 assert_eq!(lanes.len(), self.inputs(), "{:?} takes {} lanes", self.op, self.inputs());
171 let mut v: Vec<u32> = Vec::with_capacity(self.ops.len());
172 for op in &self.ops {
173 let x = match *op {
174 LaneOp::Input(i) => lanes[i],
175 LaneOp::Const(c) => c,
176 LaneOp::Add(a, b) => v[a].wrapping_add(v[b]),
177 LaneOp::Sub(a, b) => v[a].wrapping_sub(v[b]),
178 LaneOp::MulLo(a, b) => v[a].wrapping_mul(v[b]),
179 LaneOp::And(a, b) => v[a] & v[b],
180 LaneOp::Or(a, b) => v[a] | v[b],
181 LaneOp::Xor(a, b) => v[a] ^ v[b],
182 LaneOp::Shl(a, k) => v[a] << k,
183 LaneOp::Shr(a, k) => v[a] >> k,
184 };
185 v.push(x);
186 }
187 v
188 }
189
190 pub fn with_op(&self, i: usize, op: LaneOp) -> LaneProgram {
193 let mut p = self.clone();
194 p.ops[i] = op;
195 p
196 }
197
198 pub fn run(&self, a: u64, b: u64) -> u64 {
200 let mut lanes = vec![a as u32, (a >> 32) as u32];
201 if self.op.arity() == 2 {
202 lanes.extend([b as u32, (b >> 32) as u32]);
203 }
204 let v = self.eval(&lanes);
205 u64::from(v[self.outputs[0]]) | u64::from(v[self.outputs[1]]) << 32
206 }
207}
208
209#[derive(Clone, Copy, Debug)]
211enum Limb {
212 Reg(Reg),
213 K(u32),
214}
215
216struct Builder {
217 ops: Vec<LaneOp>,
218 consts: BTreeMap<u32, Reg>,
219}
220
221const MASK16: u32 = 0xFFFF;
222
223impl Builder {
224 fn new() -> Builder {
225 Builder { ops: Vec::new(), consts: BTreeMap::new() }
226 }
227
228 fn push(&mut self, op: LaneOp) -> Reg {
229 self.ops.push(op);
230 self.ops.len() - 1
231 }
232
233 fn konst(&mut self, c: u32) -> Reg {
234 if let Some(&r) = self.consts.get(&c) {
235 return r;
236 }
237 let r = self.push(LaneOp::Const(c));
238 self.consts.insert(c, r);
239 r
240 }
241
242 fn lo16(&mut self, x: Reg) -> Reg {
243 let m = self.konst(MASK16);
244 self.push(LaneOp::And(x, m))
245 }
246
247 fn hi16(&mut self, x: Reg) -> Reg {
248 self.push(LaneOp::Shr(x, 16))
249 }
250
251 fn limbs(&mut self, lo: Reg, hi: Reg) -> Vec<Limb> {
253 vec![Limb::Reg(self.lo16(lo)), Limb::Reg(self.hi16(lo)), Limb::Reg(self.lo16(hi)), Limb::Reg(self.hi16(hi))]
254 }
255
256 fn const_limbs(value: u128, count: usize) -> Vec<Limb> {
258 (0..count).map(|i| Limb::K((value >> (16 * i)) as u32 & MASK16)).collect()
259 }
260
261 fn reg(&mut self, limb: Limb) -> Reg {
263 match limb {
264 Limb::Reg(r) => r,
265 Limb::K(k) => self.konst(k),
266 }
267 }
268
269 fn sum(&mut self, terms: &[Reg]) -> Option<Reg> {
271 let mut acc: Option<Reg> = None;
272 for &t in terms {
273 acc = Some(match acc {
274 None => t,
275 Some(a) => self.push(LaneOp::Add(a, t)),
276 });
277 }
278 acc
279 }
280
281 fn carry(&mut self, columns: Vec<Vec<Reg>>) -> Vec<Reg> {
284 let mut out = Vec::with_capacity(columns.len());
285 let mut carry: Option<Reg> = None;
286 for mut terms in columns {
287 terms.extend(carry);
288 let s = match self.sum(&terms) {
289 Some(s) => s,
290 None => self.konst(0),
291 };
292 out.push(self.lo16(s));
293 carry = Some(self.hi16(s));
294 }
295 out
296 }
297
298 fn add_limbs(&mut self, a: &[Limb], b: &[Limb], n: usize) -> Vec<Reg> {
300 let columns = (0..n)
301 .map(|k| {
302 let mut c = Vec::new();
303 for x in [a.get(k), b.get(k)].into_iter().flatten() {
304 if !matches!(x, Limb::K(0)) {
305 c.push(self.reg(*x));
306 }
307 }
308 c
309 })
310 .collect();
311 self.carry(columns)
312 }
313
314 fn sub_limbs(&mut self, a: &[Limb], b: &[Limb], n: usize) -> Vec<Reg> {
316 let mut columns: Vec<Vec<Reg>> = Vec::with_capacity(n);
317 for k in 0..n {
318 let mut c = Vec::new();
319 if let Some(x) = a.get(k) {
320 if !matches!(x, Limb::K(0)) {
321 c.push(self.reg(*x));
322 }
323 }
324 let complement = match b.get(k) {
325 Some(Limb::K(v)) => self.konst(MASK16 - v),
326 Some(Limb::Reg(r)) => {
327 let m = self.konst(MASK16);
328 self.push(LaneOp::Sub(m, *r))
329 }
330 None => self.konst(MASK16),
331 };
332 c.push(complement);
333 if k == 0 {
334 c.push(self.konst(1));
335 }
336 columns.push(c);
337 }
338 self.carry(columns)
339 }
340
341 fn mul_limbs(&mut self, a: &[Limb], b: &[Limb], n: usize) -> Vec<Reg> {
343 let mut columns: Vec<Vec<Reg>> = vec![Vec::new(); n];
344 for (i, x) in a.iter().enumerate() {
345 for (j, y) in b.iter().enumerate() {
346 if i + j >= n || matches!(x, Limb::K(0)) || matches!(y, Limb::K(0)) {
347 continue;
348 }
349 let (xr, yr) = (self.reg(*x), self.reg(*y));
350 let p = self.push(LaneOp::MulLo(xr, yr));
351 let (lo, hi) = (self.lo16(p), self.hi16(p));
352 columns[i + j].push(lo);
353 if i + j + 1 < n {
354 columns[i + j + 1].push(hi);
355 }
356 }
357 }
358 self.carry(columns)
359 }
360
361 fn join(&mut self, l: &[Reg]) -> [Reg; 2] {
363 let lo_hi = self.push(LaneOp::Shl(l[1], 16));
364 let lo = self.push(LaneOp::Add(l[0], lo_hi));
365 let hi_hi = self.push(LaneOp::Shl(l[3], 16));
366 let hi = self.push(LaneOp::Add(l[2], hi_hi));
367 [lo, hi]
368 }
369}
370
371pub fn lower(op: Op64) -> LaneProgram {
373 op.check();
374 let mut b = Builder::new();
375 let a_lo = b.push(LaneOp::Input(0));
376 let a_hi = b.push(LaneOp::Input(1));
377 let (b_lo, b_hi) = if op.arity() == 2 { (b.push(LaneOp::Input(2)), b.push(LaneOp::Input(3))) } else { (0, 0) };
378 let mut rem = None;
379 let outputs = match op {
380 Op64::Add | Op64::Sub | Op64::Mul => {
381 let (x, y) = (b.limbs(a_lo, a_hi), b.limbs(b_lo, b_hi));
382 let limbs = match op {
383 Op64::Add => b.add_limbs(&x, &y, 4),
384 Op64::Sub => b.sub_limbs(&x, &y, 4),
385 _ => b.mul_limbs(&x, &y, 4),
386 };
387 b.join(&limbs)
388 }
389 Op64::And => [b.push(LaneOp::And(a_lo, b_lo)), b.push(LaneOp::And(a_hi, b_hi))],
390 Op64::Or => [b.push(LaneOp::Or(a_lo, b_lo)), b.push(LaneOp::Or(a_hi, b_hi))],
391 Op64::Xor => [b.push(LaneOp::Xor(a_lo, b_lo)), b.push(LaneOp::Xor(a_hi, b_hi))],
392 Op64::Shl(k) => match k {
393 0 => [a_lo, a_hi],
394 1..=31 => {
395 let up = b.push(LaneOp::Shl(a_hi, k));
396 let across = b.push(LaneOp::Shr(a_lo, 32 - k));
397 [b.push(LaneOp::Shl(a_lo, k)), b.push(LaneOp::Or(up, across))]
398 }
399 32 => [b.konst(0), a_lo],
400 _ => [b.konst(0), b.push(LaneOp::Shl(a_lo, k - 32))],
401 },
402 Op64::Shr(k) => match k {
403 0 => [a_lo, a_hi],
404 1..=31 => {
405 let down = b.push(LaneOp::Shr(a_lo, k));
406 let across = b.push(LaneOp::Shl(a_hi, 32 - k));
407 [b.push(LaneOp::Or(down, across)), b.push(LaneOp::Shr(a_hi, k))]
408 }
409 32 => [a_hi, b.konst(0)],
410 _ => [b.push(LaneOp::Shr(a_hi, k - 32)), b.konst(0)],
411 },
412 Op64::Rem(1) => [b.konst(0), b.konst(0)],
413 Op64::Rem(d) => {
414 let m = (1u128 << 64) / u128::from(d);
415 let x = b.limbs(a_lo, a_hi);
416 let product = b.mul_limbs(&x, &Builder::const_limbs(m, 4), 8);
417 let q: Vec<Limb> = product[4..].iter().map(|&r| Limb::Reg(r)).collect();
418 let qd = b.mul_limbs(&q, &Builder::const_limbs(u128::from(d), 2), 4);
419 let qd: Vec<Limb> = qd.into_iter().map(Limb::Reg).collect();
420 let diff = b.sub_limbs(&x, &qd, 4);
421 let diff_limbs: Vec<Limb> = diff.iter().map(|&r| Limb::Reg(r)).collect();
422 let probe = b.add_limbs(&diff_limbs, &Builder::const_limbs((1u128 << 34) - u128::from(d), 4), 4);
423 let over = b.push(LaneOp::Shr(probe[2], 2));
424 let dk = b.konst(d);
425 let correction = b.push(LaneOp::MulLo(over, dk));
426 let correction = [Limb::Reg(b.lo16(correction)), Limb::Reg(b.hi16(correction))];
427 let result = b.sub_limbs(&diff_limbs, &correction, 4);
428 rem = Some(RemMarks { product, diff, probe, over });
429 b.join(&result)
430 }
431 };
432 LaneProgram { op, ops: b.ops, outputs, rem }
433}
434
435#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
441enum Sym {
442 In(usize),
443 Hi(usize),
444 Lo(usize),
445}
446
447#[derive(Clone, Debug, PartialEq, Eq, Default)]
449struct Poly(BTreeMap<Vec<(Sym, u32)>, BigInt>);
450
451fn big(x: u128) -> BigInt {
452 BigInt::from_u64((x >> 64) as u64).shl_bits(64).add(&BigInt::from_u64(x as u64))
453}
454
455fn pow2(k: u64) -> BigInt {
456 BigInt::from_i64(1).shl_bits(k)
457}
458
459impl Poly {
460 fn constant(c: BigInt) -> Poly {
461 let mut m = BTreeMap::new();
462 if !c.is_zero() {
463 m.insert(Vec::new(), c);
464 }
465 Poly(m)
466 }
467
468 fn var(s: Sym) -> Poly {
469 let mut m = BTreeMap::new();
470 m.insert(vec![(s, 1)], BigInt::from_i64(1));
471 Poly(m)
472 }
473
474 fn add(&self, o: &Poly) -> Poly {
475 let mut m = self.0.clone();
476 for (mono, c) in &o.0 {
477 let e = m.entry(mono.clone()).or_insert_with(BigInt::zero);
478 *e = e.add(c);
479 if e.is_zero() {
480 m.remove(mono);
481 }
482 }
483 Poly(m)
484 }
485
486 fn scale(&self, k: &BigInt) -> Poly {
487 if k.is_zero() {
488 return Poly::default();
489 }
490 Poly(self.0.iter().map(|(mono, c)| (mono.clone(), c.mul(k))).collect())
491 }
492
493 fn sub(&self, o: &Poly) -> Poly {
494 self.add(&o.scale(&BigInt::from_i64(-1)))
495 }
496
497 fn mul(&self, o: &Poly) -> Poly {
498 let mut out = Poly::default();
499 for (m1, c1) in &self.0 {
500 for (m2, c2) in &o.0 {
501 let mut powers: BTreeMap<Sym, u32> = m1.iter().copied().collect();
502 for &(s, e) in m2 {
503 *powers.entry(s).or_insert(0) += e;
504 }
505 let mono: Vec<(Sym, u32)> = powers.into_iter().collect();
506 out = out.add(&Poly(BTreeMap::from([(mono, c1.mul(c2))])));
507 }
508 }
509 out
510 }
511
512 fn div_exact(&self, k: &BigInt) -> Option<Poly> {
514 let mut m = BTreeMap::new();
515 for (mono, c) in &self.0 {
516 let (q, r) = c.div_rem(k)?;
517 if !r.is_zero() {
518 return None;
519 }
520 m.insert(mono.clone(), q);
521 }
522 Some(Poly(m))
523 }
524
525 fn interval(&self, iv: &dyn Fn(Sym) -> Interval) -> Interval {
527 let mut out = Interval::point(BigInt::zero());
528 for (mono, c) in &self.0 {
529 let mut term = Interval::point(c.clone());
530 for &(s, e) in mono {
531 for _ in 0..e {
532 term = term.mul(&iv(s));
533 }
534 }
535 out = out.add(&term);
536 }
537 out
538 }
539
540 fn syn(&self) -> Syn {
542 let mut terms = self.0.iter().map(|(mono, c)| {
543 let mut t = syn_int(c);
544 for &(s, e) in mono {
545 for _ in 0..e {
546 t = syn_binary("mul", t, syn_sym(s));
547 }
548 }
549 t
550 });
551 match terms.next() {
552 None => Syn::Lit(0),
553 Some(first) => terms.fold(first, |acc, t| syn_binary("add", acc, t)),
554 }
555 }
556}
557
558fn syn_sym(s: Sym) -> Syn {
559 Syn::Name(match s {
560 Sym::In(i) => format!("in{i}"),
561 Sym::Hi(j) => format!("hi{j}"),
562 Sym::Lo(j) => format!("lo{j}"),
563 })
564}
565
566fn syn_int(c: &BigInt) -> Syn {
568 if let Some(v) = c.to_i64() {
569 return Syn::Lit(v);
570 }
571 if c.is_negative() {
572 return syn_binary("sub", Syn::Lit(0), syn_int(&c.negated()));
573 }
574 let base = BigInt::from_u64(1 << 32);
575 let (q, r) = c.div_rem(&base).expect("a nonzero base");
576 syn_binary("add", syn_binary("mul", syn_int(&q), Syn::Lit(1 << 32)), syn_int(&r))
577}
578
579#[derive(Clone, Debug, PartialEq, Eq)]
581struct Interval {
582 lo: BigInt,
583 hi: BigInt,
584}
585
586impl Interval {
587 fn new(lo: BigInt, hi: BigInt) -> Interval {
588 Interval { lo, hi }
589 }
590
591 fn point(v: BigInt) -> Interval {
592 Interval { lo: v.clone(), hi: v }
593 }
594
595 fn add(&self, o: &Interval) -> Interval {
596 Interval::new(self.lo.add(&o.lo), self.hi.add(&o.hi))
597 }
598
599 fn mul(&self, o: &Interval) -> Interval {
600 let c = [self.lo.mul(&o.lo), self.lo.mul(&o.hi), self.hi.mul(&o.lo), self.hi.mul(&o.hi)];
601 Interval::new(c.iter().min().unwrap().clone(), c.iter().max().unwrap().clone())
602 }
603
604 fn within(&self, lo: &BigInt, hi: &BigInt) -> bool {
605 &self.lo >= lo && &self.hi <= hi
606 }
607}
608
609#[derive(Clone, Debug)]
611struct Cut {
612 of: Reg,
613 k: u32,
614 hi: Interval,
615 lo: Interval,
616}
617
618#[derive(Clone, Debug)]
622struct Value {
623 atom: Poly,
624 elim: Poly,
625 tree: Syn,
626 iv: Interval,
627}
628
629struct Algebra {
631 values: Vec<Value>,
632 cuts: Vec<Cut>,
633}
634
635fn lane_max() -> BigInt {
636 BigInt::from_u64(u64::from(u32::MAX))
637}
638
639impl Algebra {
640 fn symbol_interval(&self, s: Sym) -> Interval {
641 match s {
642 Sym::In(_) => Interval::new(BigInt::zero(), lane_max()),
643 Sym::Hi(j) => self.cuts[j].hi.clone(),
644 Sym::Lo(j) => self.cuts[j].lo.clone(),
645 }
646 }
647
648 fn bound(&self, p: &Poly) -> Interval {
650 p.interval(&|s| self.symbol_interval(s))
651 }
652
653 fn run(program: &LaneProgram, narrowed: &BTreeMap<Reg, Interval>) -> Result<Algebra, String> {
656 let mut alg = Algebra { values: Vec::new(), cuts: Vec::new() };
657 let mut memo: BTreeMap<(Reg, u32), usize> = BTreeMap::new();
658 let ops = &program.ops;
659 for (r, op) in ops.iter().enumerate() {
660 let exact = |iv: &Interval, what: &str| -> Result<(), String> {
661 if iv.within(&BigInt::zero(), &lane_max()) {
662 Ok(())
663 } else {
664 Err(format!("register {r} ({what}) can leave a 32-bit lane: [{}, {}]", iv.lo, iv.hi))
665 }
666 };
667 let value = match *op {
668 LaneOp::Input(i) => Value {
669 atom: Poly::var(Sym::In(i)),
670 elim: Poly::var(Sym::In(i)),
671 tree: syn_sym(Sym::In(i)),
672 iv: Interval::new(BigInt::zero(), lane_max()),
673 },
674 LaneOp::Const(c) => {
675 let c = BigInt::from_u64(u64::from(c));
676 Value { atom: Poly::constant(c.clone()), elim: Poly::constant(c.clone()), tree: syn_int(&c), iv: Interval::point(c) }
677 }
678 LaneOp::Add(a, b) => {
679 let (x, y) = (&alg.values[a], &alg.values[b]);
680 let iv = x.iv.add(&y.iv);
681 exact(&iv, "an add")?;
682 Value {
683 atom: x.atom.add(&y.atom),
684 elim: x.elim.add(&y.elim),
685 tree: syn_binary("add", x.tree.clone(), y.tree.clone()),
686 iv,
687 }
688 }
689 LaneOp::Sub(a, b) => {
690 let (x, y) = (&alg.values[a], &alg.values[b]);
691 let iv = Interval::new(x.iv.lo.sub(&y.iv.hi), x.iv.hi.sub(&y.iv.lo));
692 exact(&iv, "a subtract")?;
693 Value {
694 atom: x.atom.sub(&y.atom),
695 elim: x.elim.sub(&y.elim),
696 tree: syn_binary("sub", x.tree.clone(), y.tree.clone()),
697 iv,
698 }
699 }
700 LaneOp::MulLo(a, b) => {
701 let (x, y) = (&alg.values[a], &alg.values[b]);
702 let iv = x.iv.mul(&y.iv);
703 exact(&iv, "a multiply")?;
704 Value {
705 atom: x.atom.mul(&y.atom),
706 elim: x.elim.mul(&y.elim),
707 tree: syn_binary("mul", x.tree.clone(), y.tree.clone()),
708 iv,
709 }
710 }
711 LaneOp::Shl(a, k) => {
712 let x = &alg.values[a];
713 let f = pow2(u64::from(k));
714 let iv = x.iv.mul(&Interval::point(f.clone()));
715 exact(&iv, "a shift left")?;
716 Value { atom: x.atom.scale(&f), elim: x.elim.scale(&f), tree: syn_binary("mul", x.tree.clone(), syn_int(&f)), iv }
717 }
718 LaneOp::Shr(a, k) => {
719 let (hi, _) = alg.cut(&mut memo, a, k);
720 match narrowed.get(&r) {
721 None => hi,
722 Some(iv) => {
723 let j = memo[&(a, k)];
724 if !iv.within(&alg.cuts[j].hi.lo, &alg.cuts[j].hi.hi) {
725 return Err(format!("register {r}: a narrowing outside the quotient's own interval"));
726 }
727 alg.cuts[j].hi = iv.clone();
728 Value { iv: iv.clone(), ..hi }
729 }
730 }
731 }
732 LaneOp::And(a, m) => {
733 let mask = match ops[m] {
734 LaneOp::Const(c) if c != 0 && (c & c.wrapping_add(1)) == 0 => c,
735 _ => return Err(format!("register {r}: an and with no low mask is not arithmetic")),
736 };
737 alg.cut(&mut memo, a, 32 - mask.leading_zeros()).1
738 }
739 LaneOp::Or(..) | LaneOp::Xor(..) => return Err(format!("register {r}: a bitwise operation is not arithmetic")),
740 };
741 alg.values.push(value);
742 }
743 Ok(alg)
744 }
745
746 fn cut(&mut self, memo: &mut BTreeMap<(Reg, u32), usize>, a: Reg, k: u32) -> (Value, Value) {
748 let j = match memo.get(&(a, k)) {
749 Some(&j) => j,
750 None => {
751 let iv = &self.values[a].iv;
752 let f = pow2(u64::from(k));
753 let (hi_top, _) = iv.hi.div_rem(&f).expect("a power of two");
754 let (hi_bottom, _) = iv.lo.div_rem(&f).expect("a power of two");
755 let lo_top = if iv.hi < f { iv.hi.clone() } else { f.sub(&BigInt::from_i64(1)) };
756 self.cuts.push(Cut {
757 of: a,
758 k,
759 hi: Interval::new(hi_bottom, hi_top),
760 lo: Interval::new(BigInt::zero(), lo_top),
761 });
762 memo.insert((a, k), self.cuts.len() - 1);
763 self.cuts.len() - 1
764 }
765 };
766 let f = pow2(u64::from(k));
767 let hi = Value {
768 atom: Poly::var(Sym::Hi(j)),
769 elim: Poly::var(Sym::Hi(j)),
770 tree: syn_sym(Sym::Hi(j)),
771 iv: self.symbol_interval(Sym::Hi(j)),
772 };
773 let of = &self.values[a];
774 let lo = Value {
775 atom: Poly::var(Sym::Lo(j)),
776 elim: of.elim.sub(&Poly::var(Sym::Hi(j)).scale(&f)),
777 tree: syn_binary("sub", of.tree.clone(), syn_binary("mul", syn_sym(Sym::Hi(j)), syn_int(&f))),
778 iv: self.cuts[j].lo.clone(),
779 };
780 (hi, lo)
781 }
782
783 fn limb_sum(&self, limbs: &[Reg]) -> (Poly, Poly, Syn) {
785 let mut atom = Poly::default();
786 let mut elim = Poly::default();
787 let mut tree: Option<Syn> = None;
788 for (k, &r) in limbs.iter().enumerate() {
789 let f = pow2(16 * k as u64);
790 atom = atom.add(&self.values[r].atom.scale(&f));
791 elim = elim.add(&self.values[r].elim.scale(&f));
792 let t = syn_binary("mul", self.values[r].tree.clone(), syn_int(&f));
793 tree = Some(match tree {
794 None => t,
795 Some(acc) => syn_binary("add", acc, t),
796 });
797 }
798 (atom, elim, tree.unwrap_or(Syn::Lit(0)))
799 }
800
801 fn result(&self, program: &LaneProgram) -> (Poly, Poly, Syn) {
803 let [lo, hi] = program.outputs;
804 let f = pow2(32);
805 let atom = self.values[lo].atom.add(&self.values[hi].atom.scale(&f));
806 let elim = self.values[lo].elim.add(&self.values[hi].elim.scale(&f));
807 let tree = syn_binary("add", self.values[lo].tree.clone(), syn_binary("mul", self.values[hi].tree.clone(), syn_int(&f)));
808 (atom, elim, tree)
809 }
810}
811
812fn operand(i: usize) -> Poly {
814 Poly::var(Sym::In(2 * i)).add(&Poly::var(Sym::In(2 * i + 1)).scale(&pow2(32)))
815}
816
817pub type Identity = (Syn, Syn);
819
820fn modular_identity(elim: &Poly, tree: Syn, spec: &Poly) -> Result<(Identity, Poly), String> {
822 let modulus = pow2(64);
823 let k = elim.sub(spec).div_exact(&modulus).ok_or("the result differs from its definition modulo 2^64")?;
824 let rhs = spec.add(&k.scale(&modulus));
825 Ok(((tree, rhs.syn()), k))
826}
827
828fn ring_identities(program: &LaneProgram) -> Result<Vec<Identity>, String> {
830 let alg = Algebra::run(program, &BTreeMap::new())?;
831 let (_, elim, tree) = alg.result(program);
832 let (a, b) = (operand(0), operand(1));
833 let spec = match program.op {
834 Op64::Add => a.add(&b),
835 Op64::Sub => a.sub(&b),
836 Op64::Mul => a.mul(&b),
837 op => return Err(format!("{op:?} is not a ring operation")),
838 };
839 Ok(vec![modular_identity(&elim, tree, &spec)?.0])
840}
841
842fn congruent_means_equal(a: &Interval, b: &Interval) -> bool {
844 let top = pow2(64).sub(&BigInt::from_i64(1));
845 a.within(&BigInt::zero(), &top) && b.within(&BigInt::zero(), &top)
846}
847
848fn remainder_identities(program: &LaneProgram, d: u32) -> Result<Vec<Identity>, String> {
863 let marks = program.rem.as_ref().ok_or("a remainder's program without its marks")?;
864 let one = BigInt::from_i64(1);
865 let d_big = BigInt::from_u64(u64::from(d));
866 let two_d = d_big.add(&d_big);
867 let m = (1u128 << 64) / u128::from(d);
868 let (m_big, e_big) = (big(m), big((1u128 << 64) - m * u128::from(d)));
869 let (two64, two128) = (pow2(64), pow2(128));
870 let x = operand(0);
871 let mut identities = Vec::new();
872
873 let bit = Interval::new(BigInt::zero(), one.clone());
875 let alg = Algebra::run(program, &BTreeMap::from([(marks.over, bit)]))?;
876
877 let (p_atom, p_elim, p_tree) = alg.limb_sum(&marks.product);
879 let top = *marks.product.last().ok_or("an empty product")?;
880 let top_cut = match alg.values[top].atom.0.keys().next().map(|m| m.as_slice()) {
881 Some([(Sym::Lo(j), 1)]) if alg.values[top].atom.0.len() == 1 => *j,
882 _ => return Err("the product's top limb is not a cut's remainder".into()),
883 };
884 let carry_out = Poly::var(Sym::Hi(top_cut));
885 let xm = x.scale(&m_big);
886 let claim = xm.sub(&carry_out.scale(&two128));
887 if p_elim != claim {
888 return Err("the product chain is not x · m".into());
889 }
890 identities.push((p_tree, claim.syn()));
891 if alg.bound(&xm).hi >= two128 || alg.bound(&p_atom).lo.is_negative() || alg.cuts[top_cut].hi.lo.is_negative() {
892 return Err("the product's carry out is not shown zero".into());
893 }
894
895 let (q_atom, q_elim, q_tree) = alg.limb_sum(&marks.product[4..]);
897 let (r_atom, r_elim, r_tree) = alg.limb_sum(&marks.product[..4]);
898 let t_elim = x.sub(&q_elim.scale(&d_big));
899 let barrett_lhs = t_elim.scale(&two64);
900 let barrett_rhs = x.scale(&e_big).add(&r_elim.scale(&d_big)).add(&carry_out.scale(&d_big.mul(&two128)));
901 if barrett_lhs != barrett_rhs {
902 return Err("Barrett's identity does not hold".into());
903 }
904 identities.push((
905 syn_binary("mul", syn_binary("sub", x.syn(), syn_binary("mul", q_tree, syn_int(&d_big))), syn_int(&two64)),
906 syn_binary(
907 "add",
908 syn_binary("add", syn_binary("mul", x.syn(), syn_int(&e_big)), syn_binary("mul", syn_int(&d_big), r_tree)),
909 syn_binary("mul", carry_out.syn(), syn_int(&d_big.mul(&two128))),
910 ),
911 ));
912 let (x_bound, r_bound) = (alg.bound(&x), alg.bound(&r_atom));
913 if x_bound.lo.is_negative() || r_bound.lo.is_negative() || r_bound.hi >= two64 || alg.bound(&q_atom).lo.is_negative() {
914 return Err("Barrett's terms are not bounded".into());
915 }
916 let (t_top, _) = x_bound.hi.mul(&e_big).add(&d_big.mul(&r_bound.hi)).div_rem(&two64).ok_or("a zero modulus")?;
918 if t_top >= two_d {
919 return Err("Barrett's difference is not shown below 2d".into());
920 }
921 let t = Interval::new(BigInt::zero(), t_top);
922
923 let (diff_atom, diff_elim, diff_tree) = alg.limb_sum(&marks.diff);
925 identities.push(modular_identity(&diff_elim, diff_tree, &t_elim)?.0);
926 if !congruent_means_equal(&alg.bound(&diff_atom), &t) {
927 return Err("the difference is not shown to be Barrett's".into());
928 }
929
930 let (probe_atom, probe_elim, probe_tree) = alg.limb_sum(&marks.probe);
932 let shift = big((1u128 << 34) - u128::from(d));
933 identities.push(modular_identity(&probe_elim, probe_tree, &diff_elim.add(&Poly::constant(shift.clone())))?.0);
934 let probe = Interval::new(t.lo.add(&shift), t.hi.add(&shift));
935 if !congruent_means_equal(&alg.bound(&probe_atom), &probe) || probe.hi >= pow2(35) {
936 return Err("the probe is not shown exact and below 2^35".into());
937 }
938 let over = match alg.values[marks.over].atom.0.keys().next().map(|m| m.as_slice()) {
939 Some([(Sym::Hi(j), 1)]) => *j,
940 _ => return Err("the correction bit is not a cut's quotient".into()),
941 };
942 let limb2 = marks.probe[2];
943 if alg.cuts[over].of != limb2 || alg.cuts[over].k != 2 {
944 return Err("the correction bit is not the probe's limb 2 over 4".into());
945 }
946 let (bit_top, _) = probe.hi.div_rem(&pow2(34)).ok_or("a zero modulus")?;
949 if bit_top > one || probe.hi >= pow2(48) {
950 return Err("the correction bit is not shown to be one bit".into());
951 }
952
953 let (out_atom, out_elim, out_tree) = alg.result(program);
955 let over_sym = Poly::var(Sym::Hi(over));
956 identities.push(modular_identity(&out_elim, out_tree, &diff_elim.sub(&over_sym.scale(&d_big)))?.0);
957 let out_bound = alg.bound(&out_atom);
958 for b in [0i64, 1] {
959 let bit_scale = BigInt::from_i64(b).mul(&pow2(34));
961 let case = Interval::new(
962 std::cmp::max(probe.lo.clone(), bit_scale.clone()),
963 std::cmp::min(probe.hi.clone(), bit_scale.add(&pow2(34)).sub(&one)),
964 );
965 if case.lo > case.hi {
966 continue;
967 }
968 let less = d_big.mul(&BigInt::from_i64(b));
970 let value = Interval::new(case.lo.sub(&shift).sub(&less), case.hi.sub(&shift).sub(&less));
971 if value.lo.is_negative() || value.hi >= d_big || !congruent_means_equal(&out_bound, &value) {
972 return Err(format!("with the correction bit {b}, the result is not shown in [0, d)"));
973 }
974 }
975 Ok(identities)
976}
977
978#[derive(Clone, Copy, Debug, PartialEq, Eq)]
980pub enum KernelVerdict {
981 Both,
984 FirstOnly,
987 Rejected,
989}
990
991fn kernels_on(ctx: &Context, (lhs, rhs): &Identity) -> KernelVerdict {
995 let goal = term_of_syn(&syn_typed_eq("Int", lhs.clone(), rhs.clone()));
996 let g = |n: &str| Term::Global(n.into());
997 let app = |f: Term, a: Term| Term::App(Box::new(f), Box::new(a));
998 let conclusion = app(g("concludes"), app(g(CertifiablePrimitive::Ring.rule_name()), goal.clone()));
999 let ty = app(app(app(g("Eq"), g("Syntax")), conclusion), goal.clone());
1000 let witness = app(
1001 Term::Lambda { param: "proof".into(), param_type: Box::new(ty), body: Box::new(Term::Var("proof".into())) },
1002 app(app(g("refl"), g("Syntax")), goal),
1003 );
1004 if infer_type(ctx, &witness).is_err() {
1005 return KernelVerdict::Rejected;
1006 }
1007 match double_check(ctx, &witness) {
1008 DoubleCheck::Agreed => KernelVerdict::Both,
1009 DoubleCheck::MainOnlyReCheckerIncomplete(_) => KernelVerdict::FirstOnly,
1010 DoubleCheck::Disagree(_) => KernelVerdict::Rejected,
1011 }
1012}
1013
1014fn program_circuit(program: &LaneProgram) -> Result<Aig, String> {
1020 let mut g = Aig::new();
1021 let mut operand_bits: Vec<Vec<AigLit>> = Vec::new();
1022 for o in 0..program.op.arity() {
1023 let name = if o == 0 { "a" } else { "b" };
1024 operand_bits.push((0..64).map(|i| g.add_input(Some(format!("{name}{i}")))).collect());
1025 }
1026 let mut lanes: Vec<Vec<AigLit>> = Vec::new();
1027 for (r, op) in program.ops.iter().enumerate() {
1028 let bits: Vec<AigLit> = match *op {
1029 LaneOp::Input(i) => operand_bits[i / 2][32 * (i % 2)..32 * (i % 2) + 32].to_vec(),
1030 LaneOp::Const(c) => (0..32).map(|i| if c >> i & 1 == 1 { AigLit::TRUE } else { AigLit::FALSE }).collect(),
1031 LaneOp::And(a, b) => (0..32).map(|i| g.and(lanes[a][i], lanes[b][i])).collect(),
1032 LaneOp::Or(a, b) => (0..32).map(|i| g.or(lanes[a][i], lanes[b][i])).collect(),
1033 LaneOp::Xor(a, b) => (0..32).map(|i| g.xor(lanes[a][i], lanes[b][i])).collect(),
1034 LaneOp::Shl(a, k) => (0..32).map(|i| if i >= k as usize { lanes[a][i - k as usize] } else { AigLit::FALSE }).collect(),
1035 LaneOp::Shr(a, k) => (0..32).map(|i| if i + (k as usize) < 32 { lanes[a][i + k as usize] } else { AigLit::FALSE }).collect(),
1036 LaneOp::Add(..) | LaneOp::Sub(..) | LaneOp::MulLo(..) => {
1037 return Err(format!("register {r}: arithmetic in a wiring program"));
1038 }
1039 };
1040 lanes.push(bits);
1041 }
1042 for (half, &r) in program.outputs.iter().enumerate() {
1043 for i in 0..32 {
1044 g.add_output(lanes[r][i], Some(format!("o{}", 32 * half + i)));
1045 }
1046 }
1047 Ok(g)
1048}
1049
1050fn definition_circuit(op: Op64) -> Aig {
1052 let mut g = Aig::new();
1053 let a: Vec<AigLit> = (0..64).map(|i| g.add_input(Some(format!("a{i}")))).collect();
1054 let b: Vec<AigLit> =
1055 if op.arity() == 2 { (0..64).map(|i| g.add_input(Some(format!("b{i}")))).collect() } else { Vec::new() };
1056 let out: Vec<AigLit> = (0..64)
1057 .map(|i| match op {
1058 Op64::And => g.and(a[i], b[i]),
1059 Op64::Or => g.or(a[i], b[i]),
1060 Op64::Xor => g.xor(a[i], b[i]),
1061 Op64::Shl(k) => {
1062 if i >= k as usize {
1063 a[i - k as usize]
1064 } else {
1065 AigLit::FALSE
1066 }
1067 }
1068 Op64::Shr(k) => {
1069 if i + (k as usize) < 64 {
1070 a[i + k as usize]
1071 } else {
1072 AigLit::FALSE
1073 }
1074 }
1075 _ => unreachable!("only wiring has a circuit definition"),
1076 })
1077 .collect();
1078 for (i, l) in out.into_iter().enumerate() {
1079 g.add_output(l, Some(format!("o{i}")));
1080 }
1081 g
1082}
1083
1084fn circuits_agree(program: &LaneProgram) -> Result<bool, String> {
1086 let left = program_circuit(program)?;
1087 let right = definition_circuit(program.op);
1088 let report = fraig::equivalent(&left, &right, &fraig::Options::default()).map_err(|e| format!("{e:?}"))?;
1089 Ok(report.verdicts.len() == 64 && report.all_equal() && report.check())
1090}
1091
1092#[derive(Clone, Debug, PartialEq, Eq)]
1098pub enum ProofKind {
1099 Algebra(Vec<(Identity, KernelVerdict)>),
1102 Circuit,
1104}
1105
1106#[derive(Clone, Debug)]
1108pub struct Proof {
1109 program: LaneProgram,
1110 kind: ProofKind,
1111}
1112
1113impl Proof {
1114 pub fn kind(&self) -> &ProofKind {
1115 &self.kind
1116 }
1117
1118 pub fn program(&self) -> &LaneProgram {
1119 &self.program
1120 }
1121
1122 pub fn check(&self) -> bool {
1127 if self.program != lower(self.program.op) {
1128 return false;
1129 }
1130 match &self.kind {
1131 ProofKind::Circuit => circuits_agree(&self.program).unwrap_or(false),
1132 ProofKind::Algebra(recorded) => {
1133 let derived = match identities_of(&self.program) {
1134 Ok(Some(ids)) => ids,
1135 _ => return false,
1136 };
1137 if derived.len() != recorded.len() || derived.iter().zip(recorded).any(|(d, (r, _))| d != r) {
1138 return false;
1139 }
1140 KERNEL.with(|ctx| {
1141 recorded.iter().all(|(id, verdict)| {
1142 let now = kernels_on(ctx, id);
1143 now != KernelVerdict::Rejected && now == *verdict
1144 })
1145 })
1146 }
1147 }
1148 }
1149
1150 pub fn with_kind(&self, kind: ProofKind) -> Proof {
1152 Proof { program: self.program.clone(), kind }
1153 }
1154
1155 pub fn second_kernel_decided(&self) -> (usize, usize) {
1157 match &self.kind {
1158 ProofKind::Algebra(ids) => (ids.iter().filter(|(_, v)| *v == KernelVerdict::Both).count(), ids.len()),
1159 ProofKind::Circuit => (0, 0),
1160 }
1161 }
1162}
1163
1164fn identities_of(program: &LaneProgram) -> Result<Option<Vec<Identity>>, String> {
1166 match program.op {
1167 Op64::Add | Op64::Sub | Op64::Mul => ring_identities(program).map(Some),
1168 Op64::Rem(1) => {
1169 let alg = Algebra::run(program, &BTreeMap::new())?;
1170 let (_, elim, tree) = alg.result(program);
1171 if !elim.0.is_empty() {
1172 return Err("a remainder by one is not zero".into());
1173 }
1174 Ok(Some(vec![(tree, Syn::Lit(0))]))
1175 }
1176 Op64::Rem(d) => remainder_identities(program, d).map(Some),
1177 Op64::And | Op64::Or | Op64::Xor | Op64::Shl(_) | Op64::Shr(_) => Ok(None),
1178 }
1179}
1180
1181thread_local! {
1182 static KERNEL: Context = {
1184 let mut ctx = Context::new();
1185 StandardLibrary::register(&mut ctx);
1186 ctx
1187 };
1188}
1189
1190pub fn prove(op: Op64) -> Result<Proof, String> {
1192 prove_program(lower(op))
1193}
1194
1195pub fn prove_program(program: LaneProgram) -> Result<Proof, String> {
1198 let op = program.op;
1199 let kind = match identities_of(&program)? {
1200 Some(ids) => {
1201 let mut verdicts = Vec::with_capacity(ids.len());
1202 for (i, id) in ids.into_iter().enumerate() {
1203 let verdict = KERNEL.with(|ctx| kernels_on(ctx, &id));
1204 if verdict == KernelVerdict::Rejected {
1205 return Err(format!("{op:?}: the kernel rejects identity {i}"));
1206 }
1207 verdicts.push((id, verdict));
1208 }
1209 ProofKind::Algebra(verdicts)
1210 }
1211 None => {
1212 if !circuits_agree(&program)? {
1213 return Err(format!("{op:?}: the program's circuit and the definition's differ"));
1214 }
1215 ProofKind::Circuit
1216 }
1217 };
1218 let proof = Proof { program, kind };
1219 Ok(proof)
1220}