use super::{Fin, PassKind, Runner, Step, Stop, bitwise, finish, linear, worth_building};
use crate::BitVec;
use crate::engine::budget::Counter;
use crate::expr::{Context, OpCode};
use crate::hash::IdMap;
use crate::ops::{BinOp, UnOp};
pub(crate) const MAX_ATOMS: usize = 6;
const MAX_REGION: usize = 256;
struct Region {
atoms: Vec<u32>,
order: Vec<u32>,
mixed: bool,
}
fn is_linear(op: OpCode) -> bool {
matches!(
op,
OpCode::Add | OpCode::Sub | OpCode::Neg | OpCode::Mul | OpCode::Shl
)
}
fn is_bitwise(op: OpCode) -> bool {
matches!(op, OpCode::And | OpCode::Or | OpCode::Xor | OpCode::Not)
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum Mode {
Linear,
Bitwise,
}
fn interior(cx: &Context, i: u32, mode: Mode) -> bool {
let n = cx.node(i);
if let Some(v) = cx.const_val(i) {
return mode == Mode::Linear || v.is_zero() || v.is_ones();
}
match n.op {
OpCode::Mul => {
mode == Mode::Linear && (cx.const_val(n.a).is_some() || cx.const_val(n.b).is_some())
}
OpCode::Shl => mode == Mode::Linear && cx.const_val(n.b).is_some(),
op if is_linear(op) => mode == Mode::Linear,
op if is_bitwise(op) => n
.children()
.all(|c| cx.const_val(c).is_none_or(|v| v.is_zero() || v.is_ones())),
_ => false,
}
}
fn operand_mode(cx: &Context, i: u32, mode: Mode) -> Mode {
match cx.node(i).op {
OpCode::And | OpCode::Or | OpCode::Xor => Mode::Bitwise,
OpCode::Not => mode,
_ => Mode::Linear,
}
}
enum Walk {
Conflict(u32),
Over,
}
fn walk(cx: &Context, root: u32, forced: &IdMap<u32, ()>) -> Result<Region, Walk> {
let mut seen: IdMap<u32, (bool, [bool; 2])> = IdMap::default();
let mut order = Vec::new();
let mut atoms = Vec::new();
let (mut lin, mut bit) = (false, false);
let mut stack: Vec<(u32, Mode, bool)> = vec![(root, Mode::Linear, false)];
while let Some((i, mode, expanded)) = stack.pop() {
if expanded {
order.push(i);
continue;
}
let inner = !forced.contains_key(&i) && interior(cx, i, mode);
let slot = mode as usize;
match seen.get_mut(&i) {
Some((was, modes)) => {
if *was != inner {
return Err(Walk::Conflict(i));
}
if modes[slot] {
continue;
}
modes[slot] = true;
if !inner {
continue;
}
}
None => {
let mut modes = [false, false];
modes[slot] = true;
seen.insert(i, (inner, modes));
if seen.len() > MAX_REGION {
return Err(Walk::Over);
}
if !inner {
atoms.push(i);
if atoms.len() > MAX_ATOMS {
return Err(Walk::Over);
}
continue;
}
stack.push((i, mode, true));
}
}
let op = cx.node(i).op;
lin |= is_linear(op);
bit |= matches!(op, OpCode::And | OpCode::Or | OpCode::Xor);
let m = operand_mode(cx, i, mode);
for c in cx.node(i).children() {
stack.push((c, m, false));
}
}
let mut kept: IdMap<u32, ()> = IdMap::default();
order.retain(|&i| kept.insert(i, ()).is_none());
atoms.sort_by(|a, b| cx.order(*a, *b));
Ok(Region {
atoms,
order,
mixed: lin && bit,
})
}
fn region(cx: &Context, root: u32) -> Option<Region> {
let mut forced: IdMap<u32, ()> = IdMap::default();
for _ in 0..32 {
match walk(cx, root, &forced) {
Ok(r) => return Some(r),
Err(Walk::Conflict(i)) if i != root => {
forced.insert(i, ());
}
Err(_) => return None,
}
}
None
}
fn signature(cx: &Context, reg: &Region, root: u32) -> Vec<BitVec> {
let w = cx.width_of(root);
let t = reg.atoms.len();
let corners = 1usize << t;
let mut vals: IdMap<u32, Vec<BitVec>> = IdMap::default();
for (j, &a) in reg.atoms.iter().enumerate() {
let v = (0..corners)
.map(|p| {
if p >> j & 1 == 1 {
BitVec::ones(w)
} else {
BitVec::zero(w)
}
})
.collect();
vals.insert(a, v);
}
for &i in ®.order {
let n = cx.node(i);
let v: Vec<BitVec> = if let Some(c) = cx.const_val(i) {
vec![c; corners]
} else {
(0..corners)
.map(|p| {
let get = |j: u32| vals[&j][p];
match n.op {
OpCode::Not => BitVec::un_unchecked(UnOp::Not, &get(n.a)),
OpCode::Neg => BitVec::un_unchecked(UnOp::Neg, &get(n.a)),
op => {
let b = op.as_bin().unwrap_or(BinOp::Add);
BitVec::bin_unchecked(b, &get(n.a), &get(n.b))
}
}
})
.collect()
};
vals.insert(i, v);
}
vals.remove(&root).unwrap_or_default()
}
fn mobius(sig: &[BitVec]) -> Vec<BitVec> {
let mut d: Vec<BitVec> = sig
.iter()
.map(|v| BitVec::un_unchecked(UnOp::Neg, v))
.collect();
let n = d.len();
let mut bit = 1;
while bit < n {
for p in 0..n {
if p & bit != 0 {
d[p] = BitVec::bin_unchecked(BinOp::Sub, &d[p], &d[p ^ bit]);
}
}
bit <<= 1;
}
d
}
enum Candidate {
Affine(BitVec, Vec<(usize, BitVec)>),
Indicators(BitVec, Vec<(BitVec, u8)>),
Conjunctions(BitVec, Vec<(usize, BitVec)>),
}
fn count<'x>(r: &'x mut Runner<'_, '_>) -> &'x mut crate::engine::PassCounts {
r.stats.passes.entry("linear_mba").or_default()
}
pub(super) fn step(r: &mut Runner<'_, '_>, cx: &mut Context, n: u32) -> Result<Step, Stop> {
let op = cx.node(n).op;
if !(is_linear(op) || is_bitwise(op)) || !interior(cx, n, Mode::Linear) {
return Ok(Step::Normal(Fin::FINAL));
}
let Some(reg) = region(cx, n) else {
count(r).atomized += 1;
return Ok(Step::Normal(Fin::FINAL));
};
if !reg.mixed || reg.atoms.is_empty() {
return Ok(Step::Normal(Fin::FINAL));
}
let t = reg.atoms.len();
r.meter
.charge(Counter::PassWork, (reg.order.len() << t) as u64)?;
let sig = signature(cx, ®, n);
let w = cx.width_of(n);
let d = mobius(&sig);
let canon = reg.atoms.clone();
let mut cands: Vec<(u32, Candidate)> = Vec::new();
if (0..d.len()).all(|s| (s as u32).count_ones() < 2 || d[s].is_zero()) {
let konst = BitVec::un_unchecked(UnOp::Neg, &d[0]);
let terms: Vec<(usize, BitVec)> = (0..t)
.filter(|&j| !d[1 << j].is_zero())
.map(|j| (j, d[1 << j]))
.collect();
let placeholder: Vec<(u32, BitVec)> =
terms.iter().map(|(j, k)| (reg.atoms[*j], *k)).collect();
let est = linear::estimate(&linear::Form::of(konst, &placeholder));
cands.push((est, Candidate::Affine(konst, terms)));
}
if (0..d.len()).any(|s| (s as u32).count_ones() >= 2 && !d[s].is_zero()) {
let konst = BitVec::un_unchecked(UnOp::Neg, &d[0]);
let terms: Vec<(usize, BitVec)> = (1..d.len())
.filter(|&s| !d[s].is_zero())
.map(|s| (s, d[s]))
.collect();
let ands: u32 = terms
.iter()
.map(|&(s, _)| (s as u32).count_ones() - 1)
.sum();
let placeholder: Vec<(u32, BitVec)> = terms
.iter()
.enumerate()
.map(|(k, &(_, c))| (u32::MAX - k as u32, c))
.collect();
let est = ands + linear::estimate(&linear::Form::of(konst, &placeholder));
cands.push((est, Candidate::Conjunctions(konst, terms)));
}
if t <= 3 {
let mut distinct: Vec<BitVec> = Vec::new();
for v in &sig {
if !distinct.contains(v) {
distinct.push(*v);
}
}
let pos: Vec<usize> = canon
.iter()
.map(|a| reg.atoms.iter().position(|b| b == a).unwrap_or(0))
.collect();
for &u0 in &distinct {
let mut ind: Vec<(BitVec, u8)> = Vec::new();
let mut funcs = 0u32;
let mut placeholder: Vec<(u32, BitVec)> = Vec::new();
for &uk in distinct.iter().filter(|&&u| u != u0) {
let mut tt = 0u8;
for i in 0..8usize {
let mut p = 0usize;
for (k, &j) in pos.iter().enumerate() {
p |= ((i >> k) & 1) << j;
}
if sig[p & ((1 << t) - 1)] == uk {
tt |= 1 << i;
}
}
funcs += bitwise::min_forms().forms[tt as usize]
.0
.iter()
.filter(|t| !matches!(t, bitwise::T::Var(_)))
.count() as u32;
let k = BitVec::bin_unchecked(BinOp::Sub, &u0, &uk);
placeholder.push((u32::MAX - placeholder.len() as u32, k));
ind.push((k, tt));
}
let est = funcs + linear::estimate(&linear::Form::of(u0, &placeholder));
cands.push((est, Candidate::Indicators(u0, ind)));
}
}
cands.sort_by_key(|c| c.0);
let Some((est, cand)) = cands.into_iter().next() else {
return Ok(Step::Normal(Fin::FINAL));
};
if let Some(f) = worth_building(r, cx, n, ®.atoms, est)? {
count(r).rejected_cost += 1;
return Ok(Step::Normal(f));
}
let before = cx.len() as u32;
let form = match cand {
Candidate::Affine(konst, terms) => {
let terms: Vec<(u32, BitVec)> =
terms.iter().map(|(j, k)| (reg.atoms[*j], *k)).collect();
linear::Form::of(konst, &terms)
}
Candidate::Indicators(u0, ind) => {
let mut terms = Vec::new();
for (k, tt) in ind {
let g = bitwise::emit(r, cx, tt, &canon)?;
terms.push((g, k));
}
linear::Form::of(u0, &terms)
}
Candidate::Conjunctions(konst, sets) => {
let mut terms = Vec::new();
for (set, k) in sets {
let mut acc: Option<u32> = None;
for (j, &a) in reg.atoms.iter().enumerate() {
if set & (1 << j) != 0 {
acc = Some(match acc {
None => a,
Some(x) => r.build(cx, |cx| cx.c_bin(BinOp::And, x, a))?,
});
}
}
if let Some(g) = acc {
terms.push((g, k));
}
}
linear::Form::of(konst, &terms)
}
};
let _ = w;
let e = linear::emit(r, cx, &form)?;
finish(
r,
cx,
PassKind::LinearMba,
n,
e,
before,
®.atoms,
Fin::FINAL,
)
}