use super::node::{Desc, Kind};
use super::{FpFormat, FpOp, FpTest, RoundingMode};
use crate::Width;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum Base {
Op(Kind),
Sub,
Neg,
Abs,
CopySign,
Gt,
Ge,
Test(FpTest),
X87Load,
X87Store,
}
const NAMES: &[(&str, Base)] = &[
("add", Base::Op(Kind::Add)),
("sub", Base::Sub),
("mul", Base::Op(Kind::Mul)),
("div", Base::Op(Kind::Div)),
("fma", Base::Op(Kind::Fma)),
("sqrt", Base::Op(Kind::Sqrt)),
("rem", Base::Op(Kind::Rem)),
("round", Base::Op(Kind::RoundToIntegral)),
("min", Base::Op(Kind::Min)),
("max", Base::Op(Kind::Max)),
("eq", Base::Op(Kind::Eq)),
("lt", Base::Op(Kind::Lt)),
("le", Base::Op(Kind::Le)),
("gt", Base::Gt),
("ge", Base::Ge),
("neg", Base::Neg),
("abs", Base::Abs),
("copysign", Base::CopySign),
("isnan", Base::Test(FpTest::Nan)),
("isinf", Base::Test(FpTest::Infinite)),
("iszero", Base::Test(FpTest::Zero)),
("isnormal", Base::Test(FpTest::Normal)),
("issubnormal", Base::Test(FpTest::Subnormal)),
("isneg", Base::Test(FpTest::Negative)),
("ispos", Base::Test(FpTest::Positive)),
("convert", Base::Op(Kind::Convert)),
("from_sbv", Base::Op(Kind::FromSInt)),
("from_ubv", Base::Op(Kind::FromUInt)),
("to_sbv", Base::Op(Kind::ToSInt)),
("to_ubv", Base::Op(Kind::ToUInt)),
("x87_load", Base::X87Load),
("x87_store", Base::X87Store),
];
impl Base {
pub(crate) fn rounds(self) -> bool {
match self {
Base::Op(k) => matches!(
k,
Kind::Add
| Kind::Mul
| Kind::Div
| Kind::Fma
| Kind::Sqrt
| Kind::RoundToIntegral
| Kind::Convert
| Kind::FromSInt
| Kind::FromUInt
| Kind::ToSInt
| Kind::ToUInt
),
Base::Sub => true,
_ => false,
}
}
pub(crate) fn formats(self) -> usize {
match self {
Base::Op(Kind::Convert) => 2,
Base::X87Load | Base::X87Store => 0,
_ => 1,
}
}
pub(crate) fn int_width(self) -> bool {
matches!(self, Base::Op(Kind::ToSInt | Kind::ToUInt))
}
pub(crate) fn arity(self) -> usize {
match self {
Base::Op(k) => k.arity(),
Base::Sub | Base::CopySign | Base::Gt | Base::Ge => 2,
_ => 1,
}
}
}
fn op_name(k: Kind) -> &'static str {
NAMES
.iter()
.find(|(_, b)| *b == Base::Op(k))
.map_or("?", |&(n, _)| n)
}
pub(crate) fn call_name(d: &Desc) -> String {
let mut s = format!("fp.{}", op_name(d.kind()));
if let Some(rm) = d.op.rounding_mode() {
s.push('.');
s.push_str(rm.name());
}
let mut generics: Vec<u32> = Vec::new();
let mut formats = vec![d.format];
if let FpOp::Convert { to, .. } = d.op {
formats.push(to);
}
if formats.iter().all(|f| f.name().is_some()) {
for f in &formats {
s.push('.');
s.push_str(f.name().unwrap_or("?"));
}
} else {
for f in &formats {
generics.extend([f.eb(), f.sb()]);
}
}
if let FpOp::ToSInt(_, w) | FpOp::ToUInt(_, w) = d.op {
generics.push(u32::from(w.bits()));
}
if !generics.is_empty() {
let g: Vec<String> = generics.iter().map(u32::to_string).collect();
s.push('<');
s.push_str(&g.join(", "));
s.push('>');
}
s
}
#[derive(Clone, Debug)]
pub(crate) struct Name {
pub(crate) base: Base,
pub(crate) rm: RoundingMode,
pub(crate) named: Vec<FpFormat>,
}
impl Name {
pub(crate) fn generics(&self) -> usize {
let unnamed = self.base.formats() - self.named.len();
2 * unnamed + usize::from(self.base.int_width())
}
pub(crate) fn finish(&self, generics: &[u16]) -> Result<Call, String> {
let mut formats = self.named.clone();
let mut g = generics.iter().map(|&v| u32::from(v));
while formats.len() < self.base.formats() {
let (eb, sb) = (g.next().unwrap_or(0), g.next().unwrap_or(0));
formats.push(FpFormat::new(eb, sb).map_err(|e| e.to_string())?);
}
let int_width = if self.base.int_width() {
let n = g.next().unwrap_or(0);
Some(Width::new(n as u16).map_err(|e| e.to_string())?)
} else {
None
};
Ok(Call {
base: self.base,
rm: self.rm,
formats,
int_width,
})
}
}
#[derive(Clone, Debug)]
pub(crate) struct Call {
pub(crate) base: Base,
pub(crate) rm: RoundingMode,
pub(crate) formats: Vec<FpFormat>,
pub(crate) int_width: Option<Width>,
}
impl Call {
pub(crate) fn format(&self) -> FpFormat {
self.formats.first().copied().unwrap_or(FpFormat::X87)
}
pub(crate) fn desc(&self) -> Option<Desc> {
let Base::Op(k) = self.base else {
return None;
};
let rm = self.rm;
let op = match k {
Kind::Add => FpOp::Add(rm),
Kind::Mul => FpOp::Mul(rm),
Kind::Div => FpOp::Div(rm),
Kind::Fma => FpOp::Fma(rm),
Kind::Sqrt => FpOp::Sqrt(rm),
Kind::Rem => FpOp::Rem,
Kind::RoundToIntegral => FpOp::RoundToIntegral(rm),
Kind::Min => FpOp::Min,
Kind::Max => FpOp::Max,
Kind::Eq => FpOp::Eq,
Kind::Lt => FpOp::Lt,
Kind::Le => FpOp::Le,
Kind::Convert => FpOp::Convert {
to: *self.formats.get(1)?,
rm,
},
Kind::FromSInt => FpOp::FromSInt(rm),
Kind::FromUInt => FpOp::FromUInt(rm),
Kind::ToSInt => FpOp::ToSInt(rm, self.int_width?),
Kind::ToUInt => FpOp::ToUInt(rm, self.int_width?),
};
Some(Desc {
op,
format: self.format(),
})
}
pub(crate) fn operand_width(&self) -> Option<u16> {
match self.base {
Base::Op(Kind::FromSInt | Kind::FromUInt) => None,
Base::X87Load => Some(80),
Base::X87Store => Some(FpFormat::X87.width().bits()),
_ => Some(self.format().width().bits()),
}
}
pub(crate) fn result_width(&self) -> u16 {
match self.base {
Base::Gt | Base::Ge | Base::Test(_) => 1,
Base::X87Load => FpFormat::X87.width().bits(),
Base::X87Store => 80,
Base::Sub | Base::Neg | Base::Abs | Base::CopySign => self.format().width().bits(),
Base::Op(_) => self.desc().map_or(1, |d| d.width()),
}
}
}
pub(crate) fn parse_name(ident: &str) -> Result<Name, String> {
let (base, rm, named) = split_name(ident)?;
let rm = match rm {
Some(m) => RoundingMode::from_name(m)
.ok_or_else(|| format!("`{m}` is not a rounding mode (rne, rna, rtp, rtn, rtz)"))?,
None => RoundingMode::Rne,
};
Ok(Name { base, rm, named })
}
pub(crate) fn split_name(ident: &str) -> Result<(Base, Option<&str>, Vec<FpFormat>), String> {
let mut parts = ident.split('.');
parts.next(); let op = parts.next().unwrap_or("");
let base = NAMES
.iter()
.find(|(n, _)| *n == op)
.map(|&(_, b)| b)
.ok_or_else(|| format!("unknown floating-point operation `fp.{op}`"))?;
let rm = if base.rounds() {
Some(
parts
.next()
.ok_or_else(|| format!("`fp.{op}` needs a rounding mode (`fp.{op}.rne`, …)"))?,
)
} else {
None
};
let mut named = Vec::new();
for p in parts {
let f = FpFormat::from_name(p).ok_or_else(|| {
format!("`{p}` is not a format name (f16, bf16, f32, f64, f128, f256)")
})?;
named.push(f);
}
if named.len() > base.formats() || (!named.is_empty() && named.len() < base.formats()) {
return Err(format!(
"`fp.{op}` names {} format(s): all by name, or all as `<eb, sb>`",
base.formats()
));
}
Ok((base, rm, named))
}