use core::fmt;
use super::budget::Counter;
use super::{Accept, By, Fin, Reject, Runner, Step, Stop};
use crate::error::Error;
use crate::expr::{Context, Expr, View};
use crate::facts::{Facts, KnownBits};
use crate::ops::{BinOp, CmpOpExt, UnOp};
use crate::{BitVec, Width};
pub trait Rewrite: Send + Sync + 'static {
fn name(&self) -> &str;
fn group(&self) -> &str {
"host"
}
fn revision(&self) -> u32 {
1
}
fn rewrite(&self, site: &mut Site<'_>, e: Expr) -> Option<Expr>;
}
pub struct Site<'s> {
cx: &'s mut Context,
run: &'s mut dyn SiteRun,
fin: Fin,
stop: Option<Stop>,
}
impl fmt::Debug for Site<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Site").finish_non_exhaustive()
}
}
pub(super) trait SiteRun {
fn make(
&mut self,
cx: &mut Context,
f: &mut dyn FnMut(&mut Context) -> Result<Expr, Error>,
) -> Result<Result<Expr, Error>, Stop>;
fn facts_of(&mut self, cx: &mut Context, n: u32) -> Result<(Option<Facts>, Fin), Stop>;
}
impl SiteRun for Runner<'_, '_> {
fn make(
&mut self,
cx: &mut Context,
f: &mut dyn FnMut(&mut Context) -> Result<Expr, Error>,
) -> Result<Result<Expr, Error>, Stop> {
let r = self.build(cx, |cx| Ok(f(cx)))?;
match r {
Err(Error::ArenaFull { .. }) => Err(Stop::Exhausted(super::Exhausted::ArenaCapacity)),
r => Ok(r),
}
}
fn facts_of(&mut self, cx: &mut Context, n: u32) -> Result<(Option<Facts>, Fin), Stop> {
super::pass::facts(self, cx, n)
}
}
#[cfg_attr(not(feature = "check"), allow(dead_code))]
struct Standalone;
impl SiteRun for Standalone {
fn make(
&mut self,
cx: &mut Context,
f: &mut dyn FnMut(&mut Context) -> Result<Expr, Error>,
) -> Result<Result<Expr, Error>, Stop> {
Ok(f(cx))
}
fn facts_of(&mut self, cx: &mut Context, n: u32) -> Result<(Option<Facts>, Fin), Stop> {
let e = cx.handle(n);
let f = cx.try_facts(e).map_err(Stop::Error)?;
Ok((f, Fin::FINAL))
}
}
#[cfg_attr(not(feature = "check"), allow(dead_code))]
pub(crate) fn apply_standalone(cx: &mut Context, r: &dyn Rewrite, e: Expr) -> Option<Expr> {
let mut run = Standalone;
let mut site = Site {
cx,
run: &mut run,
fin: Fin::FINAL,
stop: None,
};
r.rewrite(&mut site, e)
}
impl Site<'_> {
pub fn context(&self) -> &Context {
self.cx
}
pub fn view(&self, e: Expr) -> Option<View> {
self.cx.view(e).ok()
}
pub fn width(&self, e: Expr) -> Option<Width> {
self.cx.width(e).ok()
}
pub fn as_const(&self, e: Expr) -> Option<BitVec> {
self.cx.as_const(e).ok().flatten()
}
pub fn as_u64(&self, e: Expr) -> Option<u64> {
self.as_const(e)?.to_u64()
}
pub fn facts(&mut self, e: Expr) -> Option<Facts> {
if self.stop.is_some() {
return None;
}
let n = self.cx.id(e).ok()?;
match self.run.facts_of(self.cx, n) {
Ok((f, fin)) => {
self.fin = self.fin.and(fin);
f
}
Err(stop) => {
self.stop = Some(stop);
None
}
}
}
pub fn known(&mut self, e: Expr) -> Option<KnownBits> {
self.facts(e).map(|f| f.known())
}
fn make(&mut self, mut f: impl FnMut(&mut Context) -> Result<Expr, Error>) -> Option<Expr> {
if self.stop.is_some() {
return None;
}
match self.run.make(self.cx, &mut f) {
Ok(r) => r.ok(),
Err(stop) => {
self.stop = Some(stop);
None
}
}
}
pub fn constant(&mut self, v: &BitVec) -> Option<Expr> {
self.make(|cx| cx.constant(v))
}
pub fn constant_u64(&mut self, width: Width, v: u64) -> Option<Expr> {
self.make(|cx| cx.constant_u64(width, v))
}
pub fn un(&mut self, op: UnOp, a: Expr) -> Option<Expr> {
self.make(|cx| cx.un(op, a))
}
pub fn bin(&mut self, op: BinOp, a: Expr, b: Expr) -> Option<Expr> {
self.make(|cx| cx.bin(op, a, b))
}
pub fn cmp(&mut self, op: impl Into<CmpOpExt>, a: Expr, b: Expr) -> Option<Expr> {
let op = op.into();
self.make(|cx| cx.cmp(op, a, b))
}
pub fn zext(&mut self, a: Expr, to: Width) -> Option<Expr> {
self.make(|cx| cx.zext(a, to))
}
pub fn sext(&mut self, a: Expr, to: Width) -> Option<Expr> {
self.make(|cx| cx.sext(a, to))
}
pub fn trunc(&mut self, a: Expr, to: Width) -> Option<Expr> {
self.make(|cx| cx.trunc(a, to))
}
pub fn extract(&mut self, a: Expr, lo: u16, len: Width) -> Option<Expr> {
self.make(|cx| cx.extract(a, lo, len))
}
pub fn concat(&mut self, hi: Expr, lo: Expr) -> Option<Expr> {
self.make(|cx| cx.concat(hi, lo))
}
pub fn select(&mut self, cond: Expr, then: Expr, els: Expr) -> Option<Expr> {
self.make(|cx| cx.select(cond, then, els))
}
pub fn ext(&mut self, op: crate::ext::ExtId, output: usize, args: &[Expr]) -> Option<Expr> {
self.make(|cx| cx.ext_output(op, output, args))
}
}
#[derive(Clone)]
pub(crate) struct Linked {
pub(crate) op: std::sync::Arc<dyn Rewrite>,
pub(crate) trusted: bool,
}
impl Runner<'_, '_> {
pub(super) fn try_host(
&mut self,
cx: &mut Context,
n: u32,
list: &[u32],
) -> Result<Step, Stop> {
let inner = self.inner;
let mut fin = Fin::FINAL;
for &hi in list {
let linked = &inner.host[hi as usize];
let name = linked.op.name();
if self
.host_quarantined
.get(hi as usize)
.copied()
.unwrap_or(false)
{
fin = fin.and(Fin::PROVISIONAL);
continue;
}
self.meter.charge(Counter::Candidates, 1)?;
self.stats.host.calls += 1;
let e = cx.handle(n);
let (out, site_fin, stop) = {
let mut site = Site {
cx: &mut *cx,
run: &mut *self,
fin: Fin::FINAL,
stop: None,
};
let out = linked.op.rewrite(&mut site, e);
(out, site.fin, site.stop)
};
if let Some(stop) = stop {
return Err(stop);
}
self.meter.check()?;
fin = fin.and(site_fin.unchanged());
let Some(out) = out else {
continue;
};
let Ok(r) = cx.id(out) else {
self.stats.host.rejected += 1;
self.quarantine_host(hi);
continue;
};
if r == n {
self.stats.host.noop += 1;
continue;
}
if cx.wid(r) == cx.wid(n) && crate::rules::order::ground_greater(cx, n, r) != Some(true)
{
self.stats.host.rejected_cost += 1;
self.stats.rejected += 1;
self.emit(super::Event::Rejected {
by: By::Rewrite(name),
reason: Reject::Termination,
});
continue;
}
match self.accept(cx, By::Rewrite(name), linked.trusted, n, r, site_fin.rel)? {
Accept::Yes => {
self.stats.host.changed += 1;
return Ok(Step::To(r, fin.and(site_fin)));
}
Accept::Vetoed => self.stats.host.rejected += 1,
Accept::Rejected(reason) => {
self.stats.host.rejected += 1;
if matches!(reason, Reject::Width | Reject::Verify | Reject::Tripwire) {
self.quarantine_host(hi);
}
}
}
}
Ok(Step::Normal(fin))
}
fn quarantine_host(&mut self, hi: u32) {
let hi = hi as usize;
if self.host_quarantined.len() <= hi {
self.host_quarantined.resize(hi + 1, false);
}
self.host_quarantined[hi] = true;
self.stats.quarantined += 1;
}
}