use nebu::Goldilocks;
use crate::data::{Reduction, Order, Data, NIL};
use crate::call::CallProvider;
use crate::trace::{Tracer, TraceRow};
use crate::patterns;
use crate::jets::registry::{JetRegistry, digest_key};
const COSTS: [u64; 18] = [
1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 64, 32, 32, 32, 32, 25, 1, 1, ];
const TAG_INV: u64 = 8;
const TAG_LT: u64 = 10;
const TAG_XOR: u64 = 11;
const TAG_AND: u64 = 12;
const TAG_NOT: u64 = 13;
const TAG_SHL: u64 = 14;
const TAG_HASH: u64 = 15;
pub(crate) const WORD_MASK: u64 = 0xFFFF_FFFF;
const MAX_DEPTH: u64 = 1000;
#[derive(Debug)]
pub enum Outcome {
Ok(Order, u64),
Halt(u64),
Error(ErrorKind),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
TypeError = 0,
AxisError = 1,
InvZero = 2,
Unavailable = 3,
Malformed = 4,
CallRejected = 5,
}
fn cost(tag: u64) -> u64 {
if (tag as usize) < COSTS.len() { COSTS[tag as usize] } else { 1 }
}
pub fn reduce<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order, formula: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T,
) -> Outcome {
reduce_inner(reduction, object, formula, budget, hints, tracer, 0, &JetRegistry::empty())
}
pub fn reduce_with_registry<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order, formula: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T,
registry: &JetRegistry<N>,
) -> Outcome {
reduce_inner(reduction, object, formula, budget, hints, tracer, 0, registry)
}
pub(crate) fn reduce_inner<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order, formula: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T, depth: u64,
registry: &JetRegistry<N>,
) -> Outcome {
if depth > MAX_DEPTH {
emit_error_row(tracer, object, formula, 0, budget, ErrorKind::Malformed);
return Outcome::Error(ErrorKind::Malformed);
}
let (tag_ref, body) = match reduction.get(formula) {
Some(e) => match e.inner {
Data::Pair { left, right } => (left, right),
Data::Atom { .. } => {
emit_error_row(tracer, object, formula, 0, budget, ErrorKind::Malformed);
return Outcome::Error(ErrorKind::Malformed);
}
},
None => {
emit_error_row(tracer, object, formula, 0, budget, ErrorKind::Malformed);
return Outcome::Error(ErrorKind::Malformed);
}
};
let tag = match reduction.atom_value(tag_ref) {
Some(v) => v.as_u64(),
None => {
emit_error_row(tracer, object, formula, 0, budget, ErrorKind::Malformed);
return Outcome::Error(ErrorKind::Malformed);
}
};
let budget_in = budget;
let mut row = TraceRow::default();
row.r[0] = tag;
row.r[1] = object as u64;
row.r[2] = formula as u64;
row.r[8] = budget_in;
if let Some(fkey) = reduction.digest(formula).map(digest_key) {
let jet = registry.lookup_exact(&fkey)
.or_else(|| registry.lookup_template(reduction, formula));
if let Some(jet_fn) = jet {
let outcome = jet_fn(reduction, object, body, budget_in, hints,
tracer as &mut dyn Tracer, depth, &mut row);
row.r[3] = match &outcome { Outcome::Ok(r, _) => *r as u64, _ => NIL as u64 };
row.r[9] = match &outcome {
Outcome::Ok(_, b) | Outcome::Halt(b) => *b,
Outcome::Error(_) => 0,
};
row.r[10] = match &outcome { Outcome::Error(k) => *k as u64, _ => 0 };
tracer.record(row);
return outcome;
}
}
let c = cost(tag);
if budget_in < c {
emit_halt_row(tracer, object, formula, tag, budget_in);
return Outcome::Halt(budget_in);
}
let budget = budget_in - c;
let is_multi_row = matches!(
tag,
TAG_INV | TAG_LT | TAG_XOR | TAG_AND | TAG_NOT | TAG_SHL | TAG_HASH
);
let outcome = match tag {
0 => patterns::axis::axis(reduction, object, body, budget, hints, &mut row),
1 => patterns::quote::quote(body, budget, &mut row),
2 => patterns::compose::compose(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
3 => patterns::cons::cons(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
4 => patterns::branch::branch(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
5 => patterns::add::add(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
6 => patterns::sub::sub(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
7 => patterns::mul::mul(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
8 => patterns::inv::inv(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
9 => patterns::eq::eq(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
10 => patterns::lt::lt(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
11 => patterns::xor::xor(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
12 => patterns::and::and(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
13 => patterns::not::not(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
14 => patterns::shl::shl(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
15 => patterns::hash::hash(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
16 => patterns::call::call_witness(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
17 => patterns::look::look(reduction, object, body, budget, hints, tracer, depth, &mut row, registry),
_ => Outcome::Error(ErrorKind::Malformed),
};
if !is_multi_row {
row.r[3] = match &outcome { Outcome::Ok(r, _) => *r as u64, _ => NIL as u64 };
row.r[9] = match &outcome { Outcome::Ok(_, b) | Outcome::Halt(b) => *b, Outcome::Error(_) => budget };
row.r[10] = match &outcome { Outcome::Error(k) => *k as u64, _ => 0 };
tracer.record(row);
}
outcome
}
fn emit_error_row<T: Tracer>(
tracer: &mut T, object: Order, formula: Order,
tag: u64, budget_in: u64, kind: ErrorKind,
) {
let mut row = TraceRow::default();
row.r[0] = tag;
row.r[1] = object as u64;
row.r[2] = formula as u64;
row.r[3] = NIL as u64;
row.r[8] = budget_in;
row.r[9] = budget_in; row.r[10] = kind as u64;
tracer.record(row);
}
fn emit_halt_row<T: Tracer>(
tracer: &mut T, object: Order, formula: Order, tag: u64, budget_in: u64,
) {
let mut row = TraceRow::default();
row.r[0] = tag;
row.r[1] = object as u64;
row.r[2] = formula as u64;
row.r[3] = NIL as u64;
row.r[8] = budget_in;
row.r[9] = budget_in;
tracer.record(row);
}
pub(crate) fn pair_children<const N: usize>(reduction: &Reduction<N>, r: Order) -> Option<(Order, Order)> {
match reduction.get(r)?.inner {
Data::Pair { left, right } => Some((left, right)),
_ => None,
}
}
pub(crate) fn evaluate<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order, formula: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T, depth: u64,
registry: &JetRegistry<N>,
) -> core::result::Result<(Order, u64), Outcome> {
match reduce_inner(reduction, object, formula, budget, hints, tracer, depth + 1, registry) {
Outcome::Ok(r, b) => Ok((r, b)),
other => Err(other),
}
}
pub(crate) fn make_field<const N: usize>(reduction: &mut Reduction<N>, v: Goldilocks, budget: u64) -> Outcome {
match reduction.atom(v) {
Some(r) => Outcome::Ok(r, budget),
None => Outcome::Error(ErrorKind::Unavailable),
}
}
pub(crate) fn evaluate_binary<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order,
a: Order, b: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T, depth: u64,
registry: &JetRegistry<N>,
) -> core::result::Result<(Order, Order, u64), Outcome> {
let ba_cost = crate::bound::bound(reduction, a);
let bb_cost = crate::bound::bound(reduction, b);
let ba = ba_cost.value();
let bb = bb_cost.value();
let can_partition = !ba_cost.is_dynamic()
&& !bb_cost.is_dynamic()
&& ba.saturating_add(bb) <= budget;
if can_partition {
#[cfg(feature = "std")]
{
return crate::parallel::par_binary(
reduction, object, a, b, ba, bb, budget, hints, tracer, depth,
);
}
#[allow(unreachable_code)]
{
let (va, ra) = evaluate(reduction, object, a, ba, hints, tracer, depth, registry)?;
let (vb, rb) = evaluate(reduction, object, b, bb, hints, tracer, depth, registry)?;
let used = (ba - ra).saturating_add(bb - rb);
Ok((va, vb, budget - used))
}
} else {
let (va, b1) = evaluate(reduction, object, a, budget, hints, tracer, depth, registry)?;
let (vb, b2) = evaluate(reduction, object, b, b1, hints, tracer, depth, registry)?;
Ok((va, vb, b2))
}
}
pub(crate) fn evaluate_unary<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order, a: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T, depth: u64,
registry: &JetRegistry<N>,
) -> core::result::Result<(Order, u64), Outcome> {
let ba_cost = crate::bound::bound(reduction, a);
let ba = ba_cost.value();
let can_partition = !ba_cost.is_dynamic() && ba <= budget;
if can_partition {
let (va, ra) = evaluate(reduction, object, a, ba, hints, tracer, depth, registry)?;
Ok((va, budget - (ba - ra)))
} else {
evaluate(reduction, object, a, budget, hints, tracer, depth, registry)
}
}
pub(crate) fn evaluate_binary_field<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order,
a: Order, b: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T, depth: u64,
registry: &JetRegistry<N>,
) -> core::result::Result<(Goldilocks, Goldilocks, u64), Outcome> {
let (ra, rb, remaining) = evaluate_binary(reduction, object, a, b, budget, hints, tracer, depth, registry)?;
let va = match reduction.atom_value(ra) {
Some(v) => v,
None => return Err(Outcome::Error(ErrorKind::TypeError)),
};
let vb = match reduction.atom_value(rb) {
Some(v) => v,
None => return Err(Outcome::Error(ErrorKind::TypeError)),
};
Ok((va, vb, remaining))
}
pub(crate) fn evaluate_binary_word<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order,
a: Order, b: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T, depth: u64,
registry: &JetRegistry<N>,
) -> core::result::Result<(u64, u64, u64), Outcome> {
let (ra, rb, remaining) = evaluate_binary(reduction, object, a, b, budget, hints, tracer, depth, registry)?;
let va = match reduction.atom_value(ra) {
Some(v) if v.as_u64() < (1u64 << 32) => v.as_u64(),
_ => return Err(Outcome::Error(ErrorKind::TypeError)),
};
let vb = match reduction.atom_value(rb) {
Some(v) if v.as_u64() < (1u64 << 32) => v.as_u64(),
_ => return Err(Outcome::Error(ErrorKind::TypeError)),
};
Ok((va, vb, remaining))
}
pub(crate) fn evaluate_unary_field<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order, a: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T, depth: u64,
registry: &JetRegistry<N>,
) -> core::result::Result<(Goldilocks, u64), Outcome> {
let (ra, remaining) = evaluate_unary(reduction, object, a, budget, hints, tracer, depth, registry)?;
match reduction.atom_value(ra) {
Some(v) => Ok((v, remaining)),
None => Err(Outcome::Error(ErrorKind::TypeError)),
}
}
pub(crate) fn evaluate_unary_word<const N: usize, T: Tracer>(
reduction: &mut Reduction<N>, object: Order, a: Order, budget: u64,
hints: &dyn CallProvider<N>, tracer: &mut T, depth: u64,
registry: &JetRegistry<N>,
) -> core::result::Result<(u64, u64), Outcome> {
let (ra, remaining) = evaluate_unary(reduction, object, a, budget, hints, tracer, depth, registry)?;
match reduction.atom_value(ra) {
Some(v) if v.as_u64() < (1u64 << 32) => Ok((v.as_u64(), remaining)),
_ => Err(Outcome::Error(ErrorKind::TypeError)),
}
}
pub(crate) fn field_binary_op<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>,
op: fn(Goldilocks, Goldilocks) -> Goldilocks,
) -> Outcome {
let (a, b) = match pair_children(reduction, body) {
Some(p) => p,
None => return Outcome::Error(ErrorKind::Malformed),
};
let (va, vb, budget) = match evaluate_binary_field(reduction, object, a, b, budget, hints, tracer, depth, registry) {
Ok(v) => v, Err(o) => return o,
};
let vc = op(va, vb);
row.r[4] = va.as_u64();
row.r[5] = vb.as_u64();
row.r[6] = vc.as_u64();
make_field(reduction, vc, budget)
}
pub(crate) fn emit_bit_row<T: Tracer>(
template: &TraceRow, tracer: &mut T,
a: u64, b: u64, c: u64,
k: u32, a_k: u64, b_k: u64, c_k: u64,
is_last: bool, result_id: Order, budget_out: u64,
) {
let mut r = TraceRow::default();
r.r[0] = template.r[0];
r.r[1] = template.r[1];
r.r[2] = template.r[2];
r.r[4] = a;
r.r[5] = b;
r.r[6] = c;
r.r[7] = k as u64;
r.r[8] = template.r[8];
r.r[10] = a_k;
r.r[11] = b_k;
r.r[12] = c_k;
if is_last {
r.r[3] = result_id as u64;
r.r[9] = budget_out;
}
tracer.record(r);
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::vec::Vec;
use super::*;
use crate::call::NullCalls;
use crate::trace::{NoTrace, VecTrace};
use nebu::Goldilocks;
fn g(v: u64) -> Goldilocks { Goldilocks::new(v) }
#[test]
fn atom_formula_is_malformed_and_records_row() {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let formula = ar.atom(g(1)).unwrap(); let mut tr = VecTrace::default();
match reduce(&mut ar, obj, formula, 100, &NullCalls, &mut tr) {
Outcome::Error(ErrorKind::Malformed) => {}
o => panic!("expected Malformed, got {:?}", o),
}
assert_eq!(tr.0.len(), 1, "error path emits one synthetic row");
assert_eq!(tr.0[0].col(10), ErrorKind::Malformed as u64);
}
#[test]
fn budget_below_cost_halts_and_records_row() {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let t15 = ar.atom(g(15)).unwrap();
let t1 = ar.atom(g(1)).unwrap();
let body = ar.pair(t1, obj).unwrap();
let formula = ar.pair(t15, body).unwrap();
let mut tr = VecTrace::default();
match reduce(&mut ar, obj, formula, 10, &NullCalls, &mut tr) {
Outcome::Halt(b) => assert_eq!(b, 10),
o => panic!("expected Halt(10), got {:?}", o),
}
assert_eq!(tr.0.len(), 1, "halt path emits one synthetic row");
assert_eq!(tr.0[0].col(0), 15);
assert_eq!(tr.0[0].col(8), 10);
assert_eq!(tr.0[0].col(9), 10);
}
#[test]
fn malformed_compose_continuation_returns_malformed() {
let mut ar = Reduction::<256>::new();
let obj = ar.atom(g(0)).unwrap();
let t2 = ar.atom(g(2)).unwrap();
let t1 = ar.atom(g(1)).unwrap();
let zero = ar.atom(g(0)).unwrap();
let quote_zero = ar.pair(t1, zero).unwrap();
let body = ar.pair(quote_zero, quote_zero).unwrap();
let formula = ar.pair(t2, body).unwrap();
match reduce(&mut ar, obj, formula, 1_000_000, &NullCalls, &mut NoTrace) {
Outcome::Error(ErrorKind::Malformed) => {}
o => panic!("expected Malformed from compose's atom-formula continuation, got {:?}", o),
}
}
#[test]
fn ok_variant_returns_result() {
let mut ar = Reduction::<1024>::new();
let obj = ar.atom(g(0)).unwrap();
let t1 = ar.atom(g(1)).unwrap();
let v = ar.atom(g(42)).unwrap();
let formula = ar.pair(t1, v).unwrap();
match reduce(&mut ar, obj, formula, 10, &NullCalls, &mut NoTrace) {
Outcome::Ok(r, b) => {
assert_eq!(ar.atom_value(r).unwrap(), g(42));
assert_eq!(b, 9, "quote costs 1");
}
o => panic!("{:?}", o),
}
}
#[test]
fn cost_table_length_matches_dispatch() {
let _: [u64; 18] = COSTS;
}
#[test]
fn cost_table_multi_row_values() {
assert_eq!(cost(8), 64); assert_eq!(cost(10), 64); assert_eq!(cost(11), 32); assert_eq!(cost(15), 25); assert_eq!(cost(99), 1); }
#[test]
fn _vec_used() { let _: Vec<u64> = Vec::new(); }
}