use super::{Fin, PassKind, Runner, Step, Stop, finish};
use crate::BitVec;
use crate::engine::budget::Counter;
use crate::expr::{Context, OpCode};
use crate::hash::IdMap;
use crate::ops::{BinOp, UnOp};
const MAX_PUSHES: u32 = 128;
struct Narrow {
memo: IdMap<(u32, u16, u16), u32>,
stops: Vec<u32>,
pushes: u32,
}
fn narrow(
r: &mut Runner<'_, '_>,
cx: &mut Context,
st: &mut Narrow,
x: u32,
lo: u16,
n: u16,
) -> Result<u32, Stop> {
if let Some(&v) = st.memo.get(&(x, lo, n)) {
return Ok(v);
}
let w = cx.wid(x);
if lo == 0 && n == w {
st.stops.push(x);
return Ok(x);
}
r.meter.charge(Counter::PassWork, 1)?;
r.meter.check()?;
let node = cx.node(x);
let stop = |r: &mut Runner<'_, '_>, cx: &mut Context, st: &mut Narrow| -> Result<u32, Stop> {
st.stops.push(x);
r.build(cx, |cx| cx.c_extract(x, lo, n))
};
st.pushes += 1;
let v = if st.pushes > MAX_PUSHES {
stop(r, cx, st)?
} else if let Some(c) = cx.const_val(x) {
let v = c
.extract(lo, crate::Width::new(n).map_err(|e| Stop::Error(e.into()))?)
.map_err(|e| Stop::Error(e.into()))?;
r.build(cx, |cx| cx.mk_const(&v))?
} else {
match node.op {
OpCode::Add | OpCode::Sub | OpCode::Mul if lo == 0 => {
let a = narrow(r, cx, st, node.a, 0, n)?;
let b = narrow(r, cx, st, node.b, 0, n)?;
let op = node.op.as_bin().unwrap_or(BinOp::Add);
r.build(cx, |cx| cx.c_bin(op, a, b))?
}
OpCode::Neg if lo == 0 => {
let a = narrow(r, cx, st, node.a, 0, n)?;
r.build(cx, |cx| cx.c_un(UnOp::Neg, a))?
}
OpCode::And | OpCode::Or | OpCode::Xor => {
let a = narrow(r, cx, st, node.a, lo, n)?;
let b = narrow(r, cx, st, node.b, lo, n)?;
let op = node.op.as_bin().unwrap_or(BinOp::And);
r.build(cx, |cx| cx.c_bin(op, a, b))?
}
OpCode::Not => {
let a = narrow(r, cx, st, node.a, lo, n)?;
r.build(cx, |cx| cx.c_un(UnOp::Not, a))?
}
OpCode::Select => {
let t = narrow(r, cx, st, node.b, lo, n)?;
let f = narrow(r, cx, st, node.c, lo, n)?;
let c = node.a;
st.stops.push(c);
r.build(cx, |cx| cx.c_select(c, t, f))?
}
OpCode::Shl | OpCode::LShr if cx.const_val(node.b).is_some() => {
let k = cx
.const_val(node.b)
.and_then(|k| k.to_u64())
.unwrap_or(u64::MAX);
let (lo64, n64, w64) = (u64::from(lo), u64::from(n), u64::from(w));
if node.op == OpCode::Shl {
if lo64 + n64 <= k {
r.build(cx, |cx| cx.mk_const(&BitVec::zero(crate::Width::new(n)?)))?
} else if lo64 >= k {
narrow(r, cx, st, node.a, (lo64 - k) as u16, n)?
} else {
stop(r, cx, st)?
}
} else if lo64 + k >= w64 {
r.build(cx, |cx| cx.mk_const(&BitVec::zero(crate::Width::new(n)?)))?
} else if lo64 + k + n64 <= w64 {
narrow(r, cx, st, node.a, (lo64 + k) as u16, n)?
} else {
stop(r, cx, st)?
}
}
OpCode::Zext | OpCode::Sext => {
let wa = cx.wid(node.a);
if lo + n <= wa {
narrow(r, cx, st, node.a, lo, n)?
} else if node.op == OpCode::Zext && lo >= wa {
r.build(cx, |cx| cx.mk_const(&BitVec::zero(crate::Width::new(n)?)))?
} else {
let (plo, pn) = if lo >= wa { (wa - 1, 1) } else { (lo, wa - lo) };
let p = narrow(r, cx, st, node.a, plo, pn)?;
let sext = node.op == OpCode::Sext;
r.build(cx, |cx| {
if sext {
cx.c_sext(p, n)
} else {
cx.c_zext(p, n)
}
})?
}
}
OpCode::Concat => {
let wl = cx.wid(node.b);
if lo + n <= wl {
narrow(r, cx, st, node.b, lo, n)?
} else if lo >= wl {
narrow(r, cx, st, node.a, lo - wl, n)?
} else {
stop(r, cx, st)?
}
}
OpCode::Extract => narrow(r, cx, st, node.a, node.b as u16 + lo, n)?,
_ => stop(r, cx, st)?,
}
};
st.memo.insert((x, lo, n), v);
Ok(v)
}
fn count<'x>(r: &'x mut Runner<'_, '_>) -> &'x mut crate::engine::PassCounts {
r.stats.passes.entry("casts").or_default()
}
fn sext_idiom(cx: &Context, n: u32) -> Option<(u16, u32)> {
let node = cx.node(n);
let w = node.width;
let small = |i: u32| cx.const_val(i).and_then(|v| v.to_u64());
match node.op {
OpCode::AShr => {
let c = small(node.b)?;
let inner = cx.node(node.a);
(inner.op == OpCode::Shl && small(inner.b) == Some(c) && c >= 1 && c < u64::from(w))
.then_some((w - c as u16, inner.a))
}
OpCode::Add => {
let (x, k) = (node.a, cx.const_val(node.b)?);
let neg_h = BitVec::apply_un(UnOp::Neg, &k).ok()?;
let h = neg_h.to_u64()?;
if !h.is_power_of_two() {
return None;
}
let kk = h.trailing_zeros() as u16 + 1;
if kk >= w {
return None;
}
let xor = cx.node(x);
if xor.op != OpCode::Xor || small(xor.b) != Some(h) {
return None;
}
let t = cx.node(xor.a);
match t.op {
OpCode::And if small(t.b) == Some((1u64 << kk) - 1) => Some((kk, t.a)),
OpCode::Zext => {
let e = cx.node(t.a);
(e.op == OpCode::Extract && e.b == 0 && e.width == kk).then_some((kk, e.a))
}
_ => None,
}
}
_ => None,
}
}
pub(super) fn step(r: &mut Runner<'_, '_>, cx: &mut Context, n: u32) -> Result<Step, Stop> {
let node = cx.node(n);
if let Some((k, x)) = sext_idiom(cx, n) {
let before = cx.len() as u32;
let w = node.width;
let e = r.build(cx, |cx| {
let t = cx.c_extract(x, 0, k)?;
cx.c_sext(t, w)
})?;
return finish(r, cx, PassKind::Casts, n, e, before, &[x], Fin::FINAL);
}
if node.op != OpCode::Extract {
return Ok(Step::Normal(Fin::FINAL));
}
let before = cx.len() as u32;
let mut st = Narrow {
memo: IdMap::default(),
stops: Vec::new(),
pushes: 0,
};
let e = narrow(r, cx, &mut st, node.a, node.b as u16, node.width)?;
if st.pushes > MAX_PUSHES {
count(r).atomized += 1;
}
finish(r, cx, PassKind::Casts, n, e, before, &st.stops, Fin::FINAL)
}