use crate::ir::tree::lower::Noun;
pub const PATTERN_REDUCTIONS: [u64; 18] = [
1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 64, 32, 32, 32, 32, 25, 1, 1, ];
pub const PATTERN_NAMES: [&str; 18] = [
"axis", "quote", "compose", "cons", "branch", "add", "sub", "mul", "inv", "eq", "lt", "xor",
"and", "not", "shl", "hash", "call", "look",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Bill {
pub min: u64,
pub max: u64,
}
impl Bill {
fn exact(c: u64) -> Self {
Bill { min: c, max: c }
}
fn add(self, other: Bill) -> Self {
Bill {
min: self.min + other.min,
max: self.max + other.max,
}
}
fn branch(a: Bill, b: Bill) -> Self {
Bill {
min: a.min.min(b.min),
max: a.max.max(b.max),
}
}
pub fn is_exact(&self) -> bool {
self.min == self.max
}
}
#[derive(Debug, Clone)]
pub struct NoxCost {
pub bill: Bill,
pub pattern_counts: [u64; 18],
pub nodes: u64,
}
impl NoxCost {
pub fn analyze(formula: &Noun) -> Self {
let mut counts = [0u64; 18];
let bill = walk(formula, &mut counts);
NoxCost {
bill,
pattern_counts: counts,
nodes: count_nodes(formula),
}
}
pub fn format_report(&self) -> String {
let mut out = String::new();
out.push_str("Cost model: reductions (nox)\n");
if self.bill.is_exact() {
out.push_str(&format!(" reductions: {}\n", self.bill.max));
} else {
out.push_str(&format!(
" reductions: {}..={} (β€ {}; branch-dependent)\n",
self.bill.min, self.bill.max, self.bill.max
));
}
out.push_str(&format!(" formula nodes: {}\n", self.nodes));
out.push_str(" by pattern (static, all arms):\n");
let mut rows: Vec<(usize, u64, u64)> = (0..18)
.filter(|&t| self.pattern_counts[t] > 0)
.map(|t| (t, self.pattern_counts[t], self.pattern_counts[t] * PATTERN_REDUCTIONS[t]))
.collect();
rows.sort_by(|a, b| b.2.cmp(&a.2).then(a.0.cmp(&b.0)));
for (t, n, sub) in rows {
out.push_str(&format!(
" {:<8} Γ{:<5} = {} reductions\n",
PATTERN_NAMES[t], n, sub
));
}
out
}
pub fn max_reductions(&self) -> u64 {
self.bill.max
}
}
fn as_quote(n: &Noun) -> Option<&Noun> {
match n {
Noun::Cell(h, t) => match h.as_ref() {
Noun::Atom(1) => Some(t),
_ => None,
},
Noun::Atom(_) => None,
}
}
fn pair(n: &Noun) -> Option<(&Noun, &Noun)> {
match n {
Noun::Cell(a, b) => Some((a, b)),
Noun::Atom(_) => None,
}
}
fn walk(formula: &Noun, counts: &mut [u64; 18]) -> Bill {
let Some((head, body)) = pair(formula) else {
return Bill::default();
};
let tag = match head {
Noun::Atom(v) => *v,
Noun::Cell(..) => return Bill::default(),
};
if (tag as usize) < 18 {
counts[tag as usize] += 1;
}
let base = Bill::exact(cost(tag));
match tag {
0 | 1 => base,
2 => match pair(body) {
Some((a, b)) => {
let bill = base.add(walk(a, counts));
match as_quote(b) {
Some(cont) => {
if (1usize) < 18 {
counts[1] += 1;
}
bill.add(Bill::exact(PATTERN_REDUCTIONS[1]))
.add(walk(cont, counts))
}
None => bill.add(walk(b, counts)),
}
}
None => base,
},
3 => match pair(body) {
Some((a, b)) => base.add(walk(a, counts)).add(walk(b, counts)),
None => base,
},
4 => match pair(body).and_then(|(t, rest)| pair(rest).map(|(y, n)| (t, y, n))) {
Some((test, yes, no)) => base
.add(walk(test, counts))
.add(Bill::branch(walk(yes, counts), walk(no, counts))),
None => base,
},
5 | 6 | 7 | 9 | 10 | 11 | 12 | 14 => match pair(body) {
Some((a, b)) => base.add(walk(a, counts)).add(walk(b, counts)),
None => base,
},
8 | 13 | 15 => base.add(walk(body, counts)),
16 | 17 => match pair(body) {
Some((a, b)) => base.add(walk(a, counts)).add(walk(b, counts)),
None => base,
},
_ => base,
}
}
fn cost(tag: u64) -> u64 {
if (tag as usize) < PATTERN_REDUCTIONS.len() {
PATTERN_REDUCTIONS[tag as usize]
} else {
1
}
}
fn count_nodes(n: &Noun) -> u64 {
match n {
Noun::Atom(_) => 1,
Noun::Cell(a, b) => 1 + count_nodes(a) + count_nodes(b),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::tree::lower::Noun;
fn atom(v: u64) -> Noun {
Noun::atom(v)
}
fn cell(a: Noun, b: Noun) -> Noun {
Noun::cell(a, b)
}
#[test]
fn pattern_costs_match_nox_semantics() {
assert_eq!(PATTERN_REDUCTIONS[0], 1); assert_eq!(PATTERN_REDUCTIONS[8], 64); assert_eq!(PATTERN_REDUCTIONS[10], 64); assert_eq!(PATTERN_REDUCTIONS[11], 32); assert_eq!(PATTERN_REDUCTIONS[15], 25); assert_eq!(PATTERN_REDUCTIONS[16], 1); }
#[test]
fn quote_is_one_reduction() {
let f = cell(atom(1), atom(42));
let c = NoxCost::analyze(&f);
assert_eq!(c.bill, Bill { min: 1, max: 1 });
assert!(c.bill.is_exact());
}
#[test]
fn add_of_two_quotes_is_three() {
let f = cell(atom(5), cell(cell(atom(1), atom(3)), cell(atom(1), atom(5))));
let c = NoxCost::analyze(&f);
assert_eq!(c.bill.max, 3);
assert!(c.bill.is_exact());
assert_eq!(c.pattern_counts[5], 1);
assert_eq!(c.pattern_counts[1], 2);
}
#[test]
fn does_not_walk_quoted_data() {
let data = cell(atom(5), cell(atom(1), atom(1)));
let f = cell(atom(1), data);
let c = NoxCost::analyze(&f);
assert_eq!(c.bill.max, 1, "quoted data must not be counted as code");
assert_eq!(c.pattern_counts[5], 0);
}
#[test]
fn hash_costs_twenty_five_plus_operand() {
let f = cell(atom(15), cell(atom(1), atom(7)));
let c = NoxCost::analyze(&f);
assert_eq!(c.bill.max, 26);
assert_eq!(c.pattern_counts[15], 1);
}
#[test]
fn compose_runs_the_quoted_continuation() {
let t = cell(atom(1), atom(0));
let rest = cell(atom(5), cell(cell(atom(1), atom(1)), cell(atom(1), atom(2))));
let f = cell(atom(2), cell(t, cell(atom(1), rest)));
let c = NoxCost::analyze(&f);
assert_eq!(c.bill.max, 6);
assert!(c.bill.is_exact());
assert_eq!(c.pattern_counts[5], 1, "continuation's add must be counted");
}
#[test]
fn branch_produces_a_range() {
let test = cell(atom(9), cell(cell(atom(1), atom(0)), cell(atom(1), atom(0))));
let yes = cell(atom(15), cell(atom(1), atom(1)));
let no = cell(atom(1), atom(0));
let f = cell(atom(4), cell(test, cell(yes, no)));
let c = NoxCost::analyze(&f);
assert_eq!(c.bill.min, 5);
assert_eq!(c.bill.max, 30);
assert!(!c.bill.is_exact());
}
}