use core::fmt::Write as _;
use super::export::{App, literal, sort};
use crate::error::Error;
use crate::fp::node::Desc;
use crate::fp::{FpFormat, FpOp, RoundingMode};
use crate::{BitVec, Width};
pub(crate) fn rm_name(rm: RoundingMode) -> &'static str {
match rm {
RoundingMode::Rne => "RNE",
RoundingMode::Rna => "RNA",
RoundingMode::Rtp => "RTP",
RoundingMode::Rtn => "RTN",
RoundingMode::Rtz => "RTZ",
}
}
fn to_fp(f: FpFormat, bits: &str) -> String {
format!("((_ to_fp {} {}) {bits})", f.eb(), f.sb())
}
fn fp_lit(f: FpFormat, v: &BitVec) -> String {
to_fp(f, &literal(v))
}
fn bits_of(out: &mut String, tag: &str, f: FpFormat, t: &str) -> String {
let name = format!("{tag}_bits");
let w = f.width().bits();
writeln!(out, "(declare-const {name} {})", sort(w)).ok();
writeln!(
out,
"(assert (ite (fp.isNaN {t}) (= {name} {}) (= {} {t})))",
literal(&f.nan()),
to_fp(f, &name)
)
.ok();
name
}
fn power_of_two(f: FpFormat, k: u32) -> Option<String> {
let bias = (1u64 << (f.eb() - 1)) - 1;
if u64::from(k) > bias {
return None;
}
let w = f.width();
let e = BitVec::wrapping_from_u64(w, u64::from(k) + bias);
let shift = BitVec::wrapping_from_u64(w, u64::from(f.sb() - 1));
let v = BitVec::bin_unchecked(crate::BinOp::Shl, &e, &shift);
Some(fp_lit(f, &v))
}
pub(crate) fn term(out: &mut String, d: &Desc, app: &App<'_>) -> Result<String, Error> {
let f = d.format;
let arg = |k: usize| -> Result<&str, Error> {
app.args
.get(k)
.map(String::as_str)
.ok_or_else(|| Error::Contract(format!("{:?} is missing an operand", d.op)))
};
let x = |k: usize| arg(k).map(|a| to_fp(f, a));
let rm = rm_name(d.op.rounding_mode().unwrap_or(RoundingMode::Rne));
let tag = app.tag;
let one_bit = |c: &str| format!("(ite {c} #b1 #b0)");
let nan = literal(&f.nan());
Ok(match d.op {
FpOp::Add(_) => bits_of(out, tag, f, &format!("(fp.add {rm} {} {})", x(0)?, x(1)?)),
FpOp::Mul(_) => bits_of(out, tag, f, &format!("(fp.mul {rm} {} {})", x(0)?, x(1)?)),
FpOp::Div(_) => bits_of(out, tag, f, &format!("(fp.div {rm} {} {})", x(0)?, x(1)?)),
FpOp::Fma(_) => bits_of(
out,
tag,
f,
&format!("(fp.fma {rm} {} {} {})", x(0)?, x(1)?, x(2)?),
),
FpOp::Sqrt(_) => bits_of(out, tag, f, &format!("(fp.sqrt {rm} {})", x(0)?)),
FpOp::Rem => bits_of(out, tag, f, &format!("(fp.rem {} {})", x(0)?, x(1)?)),
FpOp::RoundToIntegral(_) => {
bits_of(out, tag, f, &format!("(fp.roundToIntegral {rm} {})", x(0)?))
}
FpOp::Min | FpOp::Max => {
let (a, b) = (arg(0)?, arg(1)?);
let (fa, fb) = (x(0)?, x(1)?);
let (order, zero_sign) = if d.op == FpOp::Min {
("fp.lt", "fp.isNegative")
} else {
("fp.gt", "fp.isPositive")
};
format!(
"(ite (and (fp.isNaN {fa}) (fp.isNaN {fb})) {nan} (ite (fp.isNaN {fa}) {b} \
(ite (fp.isNaN {fb}) {a} (ite (or ({order} {fa} {fb}) (and (fp.isZero {fa}) \
(fp.isZero {fb}) ({zero_sign} {fa}))) {a} {b}))))"
)
}
FpOp::Eq => one_bit(&format!("(fp.eq {} {})", x(0)?, x(1)?)),
FpOp::Lt => one_bit(&format!("(fp.lt {} {})", x(0)?, x(1)?)),
FpOp::Le => one_bit(&format!("(fp.leq {} {})", x(0)?, x(1)?)),
FpOp::Convert { to, .. } => bits_of(
out,
tag,
to,
&format!("((_ to_fp {} {}) {rm} {})", to.eb(), to.sb(), x(0)?),
),
FpOp::FromSInt(_) => bits_of(
out,
tag,
f,
&format!("((_ to_fp {} {}) {rm} {})", f.eb(), f.sb(), arg(0)?),
),
FpOp::FromUInt(_) => bits_of(
out,
tag,
f,
&format!(
"((_ to_fp_unsigned {} {}) {rm} {})",
f.eb(),
f.sb(),
arg(0)?
),
),
FpOp::ToSInt(_, w) | FpOp::ToUInt(_, w) => {
let signed = matches!(d.op, FpOp::ToSInt(..));
to_integer(out, f, rm, &x(0)?, w, signed, tag)
}
})
}
fn to_integer(
out: &mut String,
f: FpFormat,
rm: &str,
a: &str,
w: Width,
signed: bool,
tag: &str,
) -> String {
let n = u32::from(w.bits());
let r = format!("{tag}_r");
writeln!(
out,
"(define-fun {r} () (_ FloatingPoint {} {}) (fp.roundToIntegral {rm} {a}))",
f.eb(),
f.sb()
)
.ok();
let emax = (1u32 << (f.eb() - 1)) - 1;
let reaches = |m: u32, negative: bool| {
let (cmp, sign) = if negative {
("fp.leq", "fp.isNegative")
} else {
("fp.geq", "fp.isPositive")
};
match power_of_two(f, m) {
Some(t) => {
let t = if negative { format!("(fp.neg {t})") } else { t };
format!("({cmp} {r} {t})")
}
None if m == emax + 1 => format!("(and (fp.isInfinite {r}) ({sign} {r}))"),
None => format!("(and (fp.isInfinite {a}) ({sign} {a}))"),
}
};
let (max, min, hi, lo) = if signed {
(
BitVec::smax(w),
BitVec::smin(w),
reaches(n - 1, false),
reaches(n - 1, true),
)
} else {
let lo = format!("(fp.lt {r} (_ +zero {} {}))", f.eb(), f.sb());
(BitVec::ones(w), BitVec::zero(w), reaches(n, false), lo)
};
let conv = if signed { "fp.to_sbv" } else { "fp.to_ubv" };
format!(
"(ite (fp.isNaN {a}) {} (ite {hi} {} (ite {lo} {} ((_ {conv} {n}) {rm} {a}))))",
literal(&BitVec::zero(w)),
literal(&max),
literal(&min)
)
}