use crate::data::{Reduction, Order};
use crate::reduce::{Outcome, ErrorKind, pair_children, evaluate_binary_word, WORD_MASK};
use crate::call::CallProvider;
use crate::trace::{Tracer, TraceRow};
use crate::jets::registry::JetRegistry;
use nebu::Goldilocks;
const SHIFT_OUT_OF_RANGE: u64 = 32;
pub fn shl<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order, body: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T, depth: u64,
row: &mut TraceRow, registry: &JetRegistry<N>,
) -> Outcome {
let (af, nf) = match pair_children(reduction, body) {
Some(p) => p,
None => return Outcome::Error(ErrorKind::Malformed),
};
let (a, n, budget) = match evaluate_binary_word(reduction, object, af, nf, budget, hints, tracer, depth, registry) {
Ok(v) => v, Err(o) => return o,
};
let c = if n >= 32 { 0 } else { (a << n) & WORD_MASK };
let result = match reduction.atom(Goldilocks::new(c)) {
Some(r) => r,
None => return Outcome::Error(ErrorKind::Unavailable),
};
for k in 0..32u32 {
let a_k = (a >> k) & 1;
let c_k = (c >> k) & 1;
let (src_bit, src_idx) = if (k as u64) >= n && ((k as u64) - n) < 32 {
let idx = (k as u64) - n;
((a >> idx) & 1, idx)
} else {
(0, SHIFT_OUT_OF_RANGE)
};
let mut r = TraceRow::default();
r.r[0] = row.r[0];
r.r[1] = row.r[1];
r.r[2] = row.r[2];
r.r[4] = a;
r.r[5] = n;
r.r[6] = c;
r.r[7] = k as u64;
r.r[8] = row.r[8];
r.r[10] = a_k;
r.r[11] = src_bit;
r.r[12] = c_k;
r.r[13] = src_idx;
if k == 31 {
r.r[3] = result as u64;
r.r[9] = budget;
}
tracer.record(r);
}
Outcome::Ok(result, budget)
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::vec::Vec;
use crate::reduce::{reduce, Outcome};
use crate::call::NullCalls;
use crate::trace::{NoTrace, VecTrace};
use crate::data::{Reduction};
use nebu::Goldilocks;
fn g(v: u64) -> Goldilocks { Goldilocks::new(v) }
fn make_shl<const N: usize>(ar: &mut Reduction<N>, v: u64, sh: u64) -> crate::data::Order {
let t = ar.atom(g(14)).unwrap();
let t1 = ar.atom(g(1)).unwrap();
let vv = ar.atom(g(v)).unwrap();
let vs = ar.atom(g(sh)).unwrap();
let qa = ar.pair(t1, vv).unwrap();
let qb = ar.pair(t1, vs).unwrap();
let body = ar.pair(qa, qb).unwrap();
ar.pair(t, body).unwrap()
}
#[test]
fn shl_basic() {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_shl(&mut ar, 1, 3);
match reduce(&mut ar, obj, formula, 1000, &NullCalls, &mut NoTrace) {
Outcome::Ok(r, _) => assert_eq!(ar.atom_value(r).unwrap().as_u64(), 8),
o => panic!("{:?}", o),
}
}
#[test]
fn shl_large_shifts_clamp_to_zero() {
for sh in [32u64, 33, 63, 64, u32::MAX as u64] {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_shl(&mut ar, 1, sh);
match reduce(&mut ar, obj, formula, 1000, &NullCalls, &mut NoTrace) {
Outcome::Ok(r, _) => assert_eq!(ar.atom_value(r).unwrap().as_u64(), 0,
"shift {} should give 0", sh),
o => panic!("shift {}: {:?}", sh, o),
}
}
}
#[test]
fn shl_overflow_clamps_to_zero() {
for sh in [32u64, 33, 63, 64, u32::MAX as u64] {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_shl(&mut ar, 1, sh);
match reduce(&mut ar, obj, formula, 1000, &NullCalls, &mut NoTrace) {
Outcome::Ok(r, _) => assert_eq!(ar.atom_value(r).unwrap().as_u64(), 0,
"shift {} should give 0", sh),
o => panic!("{:?}", o),
}
}
}
#[test]
fn shl_masks_to_32_bits() {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_shl(&mut ar, 0xFFFF_FFFF, 1);
match reduce(&mut ar, obj, formula, 1000, &NullCalls, &mut NoTrace) {
Outcome::Ok(r, _) => assert_eq!(ar.atom_value(r).unwrap().as_u64(), 0xFFFF_FFFE),
o => panic!("{:?}", o),
}
}
#[test]
fn shl_emits_32_rows() {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = make_shl(&mut ar, 0xDEADBEEF, 4);
let mut tr = VecTrace::default();
reduce(&mut ar, obj, formula, 1000, &NullCalls, &mut tr);
let n = tr.0.iter().filter(|r| r.r[0] == 14).count();
assert_eq!(n, 32);
}
#[test]
fn shl_bit_witnesses_correct() {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let a = 0xDEAD_BEEFu64;
let n_shift = 4u64;
let c = (a << n_shift) & 0xFFFF_FFFF;
let formula = make_shl(&mut ar, a, n_shift);
let mut tr = VecTrace::default();
reduce(&mut ar, obj, formula, 1000, &NullCalls, &mut tr);
let rows: Vec<_> = tr.0.iter().filter(|r| r.r[0] == 14).collect();
for (k, r) in rows.iter().enumerate() {
let k = k as u32;
assert_eq!(r.r[10], (a >> k) & 1);
assert_eq!(r.r[12], (c >> k) & 1);
if (k as u64) >= n_shift && ((k as u64) - n_shift) < 32 {
let src = (k as u64) - n_shift;
assert_eq!(r.r[11], (a >> src) & 1);
assert_eq!(r.r[13], src);
} else {
assert_eq!(r.r[11], 0);
assert_eq!(r.r[13], 32);
}
}
}
}