use core::fmt;
use crate::types::Type;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum UnaryOp {
Neg,
Abs,
Not,
Sqrt,
Floor,
Ceil,
Trunc,
Round,
RoundEven,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum BinaryOp {
Add,
Sub,
Mul,
Div,
Rem,
FloorDiv,
FloorMod,
IeeeRem,
And,
Or,
Xor,
Shl,
Shr,
Min,
Max,
TotalCmp,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum CmpOp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum ConvOp {
IntCast,
Zext,
Sext,
Narrow,
IntToFloat,
FloatToInt,
FloatCast,
Bitcast,
CharFromU32,
BoolToInt,
PtrToInt,
IntToPtr,
RefToPtr,
}
macro_rules! named {
($ty:ident { $($variant:ident => $name:literal),* $(,)? }) => {
impl $ty {
pub const ALL: &'static [$ty] = &[$($ty::$variant),*];
#[doc = concat!("use ir_lang::", stringify!($ty), ";")]
#[doc = concat!("let op = ", stringify!($ty), "::ALL[0];")]
#[doc = concat!("assert_eq!(", stringify!($ty), "::from_name(op.name()), Some(op));")]
#[must_use]
pub const fn name(self) -> &'static str {
match self {
$($ty::$variant => $name),*
}
}
#[doc = concat!("use ir_lang::", stringify!($ty), ";")]
#[doc = concat!("let op = ", stringify!($ty), "::ALL[0];")]
#[doc = concat!("assert_eq!(", stringify!($ty), "::from_name(op.name()), Some(op));")]
#[must_use]
pub fn from_name(name: &str) -> Option<$ty> {
match name {
$($name => Some($ty::$variant),)*
_ => None,
}
}
}
impl fmt::Display for $ty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
};
}
named!(UnaryOp {
Neg => "neg",
Abs => "abs",
Not => "not",
Sqrt => "sqrt",
Floor => "floor",
Ceil => "ceil",
Trunc => "trunc",
Round => "round",
RoundEven => "round_even",
});
named!(BinaryOp {
Add => "add",
Sub => "sub",
Mul => "mul",
Div => "div",
Rem => "rem",
FloorDiv => "floor_div",
FloorMod => "floor_mod",
IeeeRem => "ieee_rem",
And => "and",
Or => "or",
Xor => "xor",
Shl => "shl",
Shr => "shr",
Min => "min",
Max => "max",
TotalCmp => "total_cmp",
});
named!(CmpOp {
Eq => "eq",
Ne => "ne",
Lt => "lt",
Le => "le",
Gt => "gt",
Ge => "ge",
});
named!(ConvOp {
IntCast => "int_cast",
Zext => "zext",
Sext => "sext",
Narrow => "narrow",
IntToFloat => "int_to_float",
FloatToInt => "float_to_int",
FloatCast => "float_cast",
Bitcast => "bitcast",
CharFromU32 => "char_from_u32",
BoolToInt => "bool_to_int",
PtrToInt => "ptr_to_int",
IntToPtr => "int_to_ptr",
RefToPtr => "ref_to_ptr",
});
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum Overflow {
Error,
Wrap,
Trap,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum DivZero {
Error,
Trap,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum Shift {
Error,
Mask,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum FloatToInt {
Error,
Saturate,
}
macro_rules! policy_names {
($ty:ident { $($variant:ident => $name:literal),* }) => {
impl $ty {
#[doc = concat!("use ir_lang::", stringify!($ty), ";")]
#[doc = concat!("assert_eq!(", stringify!($ty), "::from_name(\"error\"), Some(", stringify!($ty), "::Error));")]
#[doc = concat!("assert_eq!(", stringify!($ty), "::Error.name(), \"error\");")]
#[must_use]
pub const fn name(self) -> &'static str {
match self {
$($ty::$variant => $name),*
}
}
#[doc = concat!("use ir_lang::", stringify!($ty), ";")]
#[doc = concat!("assert_eq!(", stringify!($ty), "::from_name(\"error\"), Some(", stringify!($ty), "::Error));")]
#[doc = concat!("assert_eq!(", stringify!($ty), "::Error.name(), \"error\");")]
#[must_use]
pub fn from_name(name: &str) -> Option<$ty> {
match name {
$($name => Some($ty::$variant),)*
_ => None,
}
}
}
};
}
policy_names!(Overflow { Error => "error", Wrap => "wrap", Trap => "trap" });
policy_names!(DivZero { Error => "error", Trap => "trap" });
policy_names!(Shift { Error => "error", Mask => "mask" });
policy_names!(FloatToInt { Error => "error", Saturate => "saturate" });
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct Policy {
pub overflow: Option<Overflow>,
pub div_zero: Option<DivZero>,
pub shift: Option<Shift>,
pub float_to_int: Option<FloatToInt>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
pub struct PolicyFields {
pub overflow: bool,
pub div_zero: bool,
pub shift: bool,
pub float_to_int: bool,
}
impl PolicyFields {
#[must_use]
pub const fn is_empty(self) -> bool {
!(self.overflow || self.div_zero || self.shift || self.float_to_int)
}
#[must_use]
pub const fn matches(self, policy: Policy) -> bool {
self.overflow == policy.overflow.is_some()
&& self.div_zero == policy.div_zero.is_some()
&& self.shift == policy.shift.is_some()
&& self.float_to_int == policy.float_to_int.is_some()
}
}
impl fmt::Display for PolicyFields {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let names = [
(self.overflow, "overflow"),
(self.div_zero, "div_zero"),
(self.shift, "shift"),
(self.float_to_int, "float_to_int"),
];
let mut first = true;
f.write_str("{")?;
for (on, name) in names {
if on {
if !first {
f.write_str(", ")?;
}
first = false;
f.write_str(name)?;
}
}
f.write_str("}")
}
}
impl Policy {
pub const NONE: Policy = Policy {
overflow: None,
div_zero: None,
shift: None,
float_to_int: None,
};
#[must_use]
pub const fn overflow(o: Overflow) -> Policy {
Policy {
overflow: Some(o),
..Policy::NONE
}
}
#[must_use]
pub const fn div_zero(d: DivZero) -> Policy {
Policy {
div_zero: Some(d),
..Policy::NONE
}
}
#[must_use]
pub const fn shift(s: Shift) -> Policy {
Policy {
shift: Some(s),
..Policy::NONE
}
}
#[must_use]
pub const fn float_to_int(p: FloatToInt) -> Policy {
Policy {
float_to_int: Some(p),
..Policy::NONE
}
}
#[must_use]
pub const fn with_div_zero(mut self, d: DivZero) -> Policy {
self.div_zero = Some(d);
self
}
#[must_use]
pub const fn can_error(self) -> bool {
matches!(self.overflow, Some(Overflow::Error))
|| matches!(self.div_zero, Some(DivZero::Error))
|| matches!(self.shift, Some(Shift::Error))
|| matches!(self.float_to_int, Some(FloatToInt::Error))
}
#[must_use]
pub const fn required_unary(op: UnaryOp, ty: Type) -> PolicyFields {
let int = ty.is_int();
PolicyFields {
overflow: int && matches!(op, UnaryOp::Neg | UnaryOp::Abs),
div_zero: false,
shift: false,
float_to_int: false,
}
}
#[must_use]
pub const fn required_binary(op: BinaryOp, ty: Type) -> PolicyFields {
let int = ty.is_int();
PolicyFields {
overflow: int
&& matches!(
op,
BinaryOp::Add
| BinaryOp::Sub
| BinaryOp::Mul
| BinaryOp::Div
| BinaryOp::FloorDiv
),
div_zero: int
&& matches!(
op,
BinaryOp::Div | BinaryOp::Rem | BinaryOp::FloorDiv | BinaryOp::FloorMod
),
shift: int && matches!(op, BinaryOp::Shl | BinaryOp::Shr),
float_to_int: false,
}
}
#[must_use]
pub const fn required_conversion(op: ConvOp) -> PolicyFields {
PolicyFields {
overflow: matches!(op, ConvOp::IntCast),
div_zero: false,
shift: false,
float_to_int: matches!(op, ConvOp::FloatToInt),
}
}
}
impl fmt::Display for Policy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == Policy::NONE {
return Ok(());
}
let mut sep = "<";
if let Some(o) = self.overflow {
write!(f, "{sep}overflow={}", o.name())?;
sep = ",";
}
if let Some(d) = self.div_zero {
write!(f, "{sep}div_zero={}", d.name())?;
sep = ",";
}
if let Some(s) = self.shift {
write!(f, "{sep}shift={}", s.name())?;
sep = ",";
}
if let Some(p) = self.float_to_int {
write!(f, "{sep}float_to_int={}", p.name())?;
}
f.write_str(">")
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum OpError {
ArithOverflow,
DivByZero,
ShiftOutOfRange,
InvalidConversion,
InvalidChar,
}
impl OpError {
#[must_use]
pub const fn code(self) -> u32 {
match self {
OpError::ArithOverflow => 1,
OpError::DivByZero => 2,
OpError::ShiftOutOfRange => 3,
OpError::InvalidConversion => 4,
OpError::InvalidChar => 5,
}
}
#[must_use]
pub const fn from_code(code: u32) -> Option<OpError> {
Some(match code {
1 => OpError::ArithOverflow,
2 => OpError::DivByZero,
3 => OpError::ShiftOutOfRange,
4 => OpError::InvalidConversion,
5 => OpError::InvalidChar,
_ => return None,
})
}
const fn kind_name(self) -> &'static str {
match self {
OpError::ArithOverflow => "ArithOverflow",
OpError::DivByZero => "DivByZero",
OpError::ShiftOutOfRange => "ShiftOutOfRange",
OpError::InvalidConversion => "InvalidConversion",
OpError::InvalidChar => "InvalidChar",
}
}
}
impl fmt::Display for OpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "E{:04} {}", self.code(), self.kind_name())
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn test_op_names_round_trip() {
for &op in UnaryOp::ALL {
assert_eq!(UnaryOp::from_name(op.name()), Some(op));
}
for &op in BinaryOp::ALL {
assert_eq!(BinaryOp::from_name(op.name()), Some(op));
}
for &op in CmpOp::ALL {
assert_eq!(CmpOp::from_name(op.name()), Some(op));
}
for &op in ConvOp::ALL {
assert_eq!(ConvOp::from_name(op.name()), Some(op));
}
}
#[test]
fn test_policy_display_orders_fields() {
let p = Policy {
overflow: Some(Overflow::Error),
div_zero: Some(DivZero::Error),
shift: None,
float_to_int: None,
};
assert_eq!(p.to_string(), "<overflow=error,div_zero=error>");
assert_eq!(
Policy::float_to_int(FloatToInt::Error).to_string(),
"<float_to_int=error>"
);
}
#[test]
fn test_required_fields_follow_ops_table() {
for op in [BinaryOp::Add, BinaryOp::Sub, BinaryOp::Mul] {
let f = Policy::required_binary(op, Type::I32);
assert!(f.overflow && !f.div_zero && !f.shift);
}
for op in [BinaryOp::Div, BinaryOp::FloorDiv] {
let f = Policy::required_binary(op, Type::U16);
assert!(f.overflow && f.div_zero);
}
for op in [BinaryOp::Rem, BinaryOp::FloorMod] {
let f = Policy::required_binary(op, Type::I64);
assert!(!f.overflow && f.div_zero);
}
for op in [BinaryOp::And, BinaryOp::Min, BinaryOp::TotalCmp] {
assert!(Policy::required_binary(op, Type::I64).is_empty());
}
assert!(Policy::required_binary(BinaryOp::Add, Type::F64).is_empty());
assert_eq!(PolicyFields::default().to_string(), "{}");
}
#[test]
fn test_error_codes_round_trip() {
for code in 1..=5 {
let e = OpError::from_code(code);
assert_eq!(e.map(OpError::code), Some(code));
}
}
}