use alloc::{collections::BTreeMap, vec::Vec};
use miden_core::{
Felt,
field::{Field, QuadFelt},
utils::RowMajorMatrix,
};
use super::{
CELL_B_ON, CELL_C_ON, CELL_D_W, CELL_D_WS, CELL_HI, CELL_IS_B_ZERO, CELL_IS_C_ZERO, CELL_K,
COL_A_PTR, COL_NZ, GAMMA_SLOTS, NUM_GAMMA, NUM_LIMBS, NUM_MAIN_COLS, PERIOD, ROW_AB, ROW_CP,
TERM_CELL_MULT, UintAddAir,
};
use crate::{
logup::build_logup_aux_trace,
math::{U256, add_reduce, from_limbs32, to_limbs32},
relations::ProvideMult,
uint::trace::{UintPtr, UintStoreRequires},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct AddOp {
a: UintPtr,
b: Option<UintPtr>,
c: Option<UintPtr>,
bound: UintPtr,
nz: bool,
}
fn add_carries(limbs: impl Fn(usize) -> u64) -> ([u16; 7], u16) {
let mut out = [0u16; 7];
let mut carry: u64 = 0;
for (j, out_j) in out.iter_mut().enumerate() {
let s = limbs(j) + carry;
carry = s >> 32;
*out_j = carry as u16;
}
let top = ((limbs(7) + carry) >> 32) as u16;
(out, top)
}
#[derive(Debug, Default)]
pub struct UintAddRequires {
ops: Vec<(AddOp, ProvideMult)>,
dedup: BTreeMap<AddOp, usize>,
}
impl UintAddRequires {
pub fn new() -> Self {
Self::default()
}
pub fn record(
&mut self,
a: UintPtr,
b: UintPtr,
c: UintPtr,
bound: UintPtr,
mult: ProvideMult,
) {
self.push(
AddOp {
a,
b: Some(b),
c: Some(c),
bound,
nz: false,
},
mult,
);
}
pub fn record_nz(
&mut self,
a: UintPtr,
b: UintPtr,
c: UintPtr,
bound: UintPtr,
mult: ProvideMult,
) {
self.push(
AddOp {
a,
b: Some(b),
c: Some(c),
bound,
nz: true,
},
mult,
);
}
pub fn record_to_zero(&mut self, a: UintPtr, b: UintPtr, bound: UintPtr, mult: ProvideMult) {
self.push(AddOp { a, b: Some(b), c: None, bound, nz: false }, mult);
}
pub fn record_eq(&mut self, a: UintPtr, c: UintPtr, bound: UintPtr, mult: ProvideMult) {
self.push(AddOp { a, b: None, c: Some(c), bound, nz: false }, mult);
}
fn push(&mut self, op: AddOp, mult: ProvideMult) {
match self.dedup.get(&op) {
Some(&i) => self.ops[i].1 += mult,
None => {
self.dedup.insert(op, self.ops.len());
self.ops.push((op, mult));
},
}
}
}
struct Witness {
a: [u32; 8],
b: [u32; 8],
c: [u32; 8],
bound: [u32; 8],
k: u32,
gamma: [Felt; NUM_GAMMA],
d_w: Felt,
}
fn witness(op: &AddOp, store: &UintStoreRequires) -> Witness {
let value = |ptr: UintPtr| -> U256 { store.uint(ptr).value };
let bound_v = value(op.bound);
let b_v = op.b.map_or(U256::ZERO, value);
let c_v = op.c.map_or(U256::ZERO, value);
debug_assert_eq!(add_reduce(value(op.a), b_v, bound_v), c_v, "a + b must reduce to c",);
let (a, b, c, bound) =
(to_limbs32(value(op.a)), to_limbs32(b_v), to_limbs32(c_v), to_limbs32(bound_v));
let (gamma_pos, top) = add_carries(|j| a[j] as u64 + b[j] as u64);
let k = u32::from(top != 0 || from_limbs32(&a) + from_limbs32(&b) > from_limbs32(&bound));
let (gamma_neg, top_neg) = add_carries(|j| {
let kb = (k as u64) * (bound[j] as u64);
let ku = if j == 0 { k as u64 } else { 0 };
c[j] as u64 + kb + ku
});
debug_assert_eq!(
top, top_neg,
"a + b and c + k·p must share the bit-256 carry (a + b = c + k·p)",
);
let mut gamma = [Felt::ZERO; NUM_GAMMA];
for j in 0..NUM_GAMMA {
gamma[j] = Felt::from(gamma_pos[j]) - Felt::from(gamma_neg[j]);
}
let d_w = if op.nz {
let s: u64 = b.iter().map(|&limb| u64::from(limb)).sum();
debug_assert_ne!(s, 0, "nz certifies b ≠ 0, but b's limbs sum to 0");
Felt::new(s).expect("S < 2^35 < Goldilocks p").inverse()
} else {
Felt::ZERO
};
Witness { a, b, c, bound, k, gamma, d_w }
}
pub fn generate_trace(
requires: UintAddRequires,
store: &mut UintStoreRequires,
) -> RowMajorMatrix<Felt> {
let n_ops = requires.ops.len().max(1);
let height = (n_ops * PERIOD).next_power_of_two();
let mut vals = Vec::with_capacity(height * NUM_MAIN_COLS);
for (op, mult) in &requires.ops {
store.require_uintval(op.a);
if let Some(b) = op.b {
store.require_uintval(b);
}
if let Some(c) = op.c {
store.require_uintval(c);
}
store.require_uintval(op.bound);
let w = witness(op, store);
let mut block = [[Felt::ZERO; NUM_MAIN_COLS]; PERIOD];
for j in 0..NUM_LIMBS {
block[ROW_AB][j] = Felt::from(w.a[j]);
block[ROW_AB][CELL_HI + j] = Felt::from(w.b[j]);
block[ROW_CP][j] = Felt::from(w.c[j]);
block[ROW_CP][CELL_HI + j] = Felt::from(w.bound[j]);
}
for (j, &(row, cell)) in GAMMA_SLOTS.iter().enumerate() {
block[row][cell] = w.gamma[j];
}
block[ROW_AB][CELL_IS_B_ZERO] = Felt::from(op.b.is_none() as u32);
block[ROW_CP][CELL_IS_C_ZERO] = Felt::from(op.c.is_none() as u32);
block[ROW_AB][CELL_B_ON] = Felt::from(op.b.is_some() as u32);
block[ROW_CP][CELL_C_ON] = Felt::from(op.c.is_some() as u32);
block[ROW_CP][CELL_K] = Felt::from(w.k);
block[ROW_CP][TERM_CELL_MULT] = Felt::from(*mult);
if op.nz {
let s_sum: Felt = block[ROW_AB][CELL_HI..CELL_HI + NUM_LIMBS].iter().copied().sum();
block[ROW_AB][CELL_D_W] = w.d_w;
block[ROW_AB][CELL_D_WS] = w.d_w * s_sum;
}
let meta: [Felt; 6] = [
Felt::from(op.a.addr()),
Felt::from(op.b.map_or(0, UintPtr::addr)),
Felt::from(op.c.map_or(0, UintPtr::addr)),
Felt::from(op.bound.addr()),
Felt::ONE, Felt::from(op.nz as u32),
];
for row in block.iter_mut() {
row[COL_A_PTR..=COL_NZ].copy_from_slice(&meta);
vals.extend_from_slice(row);
}
}
vals.resize(height * NUM_MAIN_COLS, Felt::ZERO);
RowMajorMatrix::new(vals, NUM_MAIN_COLS)
}
pub(crate) fn build_aux(
main: &RowMajorMatrix<Felt>,
challenges: &[QuadFelt],
) -> (RowMajorMatrix<QuadFelt>, Vec<QuadFelt>) {
build_logup_aux_trace(&UintAddAir, main, challenges)
}