use core::fmt;
code_enum! {
IntTy {
I8 = 0 => "i8",
I16 = 1 => "i16",
I32 = 2 => "i32",
I64 = 3 => "i64",
U8 = 4 => "u8",
U16 = 5 => "u16",
U32 = 6 => "u32",
U64 = 7 => "u64",
}
}
impl IntTy {
#[must_use]
pub const fn bits(self) -> u32 {
match self {
IntTy::I8 | IntTy::U8 => 8,
IntTy::I16 | IntTy::U16 => 16,
IntTy::I32 | IntTy::U32 => 32,
IntTy::I64 | IntTy::U64 => 64,
}
}
#[must_use]
pub const fn is_signed(self) -> bool {
(self as u8) < 4
}
pub(crate) const fn from_low3(bits: u8) -> IntTy {
match bits & 7 {
0 => IntTy::I8,
1 => IntTy::I16,
2 => IntTy::I32,
3 => IntTy::I64,
4 => IntTy::U8,
5 => IntTy::U16,
6 => IntTy::U32,
_ => IntTy::U64,
}
}
}
code_enum! {
FloatTy {
F32 = 0 => "f32",
F64 = 1 => "f64",
}
}
code_enum! {
#[derive(Default)]
Overflow {
#[default]
Error = 0 => "error",
Wrap = 1 => "wrap",
Trap = 2 => "trap",
Promote = 3 => "promote",
}
}
code_enum! {
#[derive(Default)]
DivZero {
#[default]
Error = 0 => "error",
Trap = 1 => "trap",
}
}
code_enum! {
#[derive(Default)]
Shift {
#[default]
Error = 0 => "error",
Mask = 1 => "mask",
}
}
code_enum! {
#[derive(Default)]
FloatToInt {
#[default]
Error = 0 => "error",
Saturate = 1 => "saturate",
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Policy(u8);
impl Policy {
pub const DEFAULT: Policy = Policy(0);
#[must_use]
pub const fn new() -> Self {
Policy(0)
}
#[must_use]
pub const fn overflow(self) -> Overflow {
match self.0 & 3 {
0 => Overflow::Error,
1 => Overflow::Wrap,
2 => Overflow::Trap,
_ => Overflow::Promote,
}
}
#[must_use]
pub const fn div_zero(self) -> DivZero {
if self.0 & 4 == 0 {
DivZero::Error
} else {
DivZero::Trap
}
}
#[must_use]
pub const fn shift(self) -> Shift {
if self.0 & 8 == 0 {
Shift::Error
} else {
Shift::Mask
}
}
#[must_use]
pub const fn float_to_int(self) -> FloatToInt {
if self.0 & 16 == 0 {
FloatToInt::Error
} else {
FloatToInt::Saturate
}
}
#[must_use]
pub const fn with_overflow(self, overflow: Overflow) -> Self {
Policy((self.0 & !3) | overflow as u8)
}
#[must_use]
pub const fn with_div_zero(self, div_zero: DivZero) -> Self {
Policy((self.0 & !4) | ((div_zero as u8) << 2))
}
#[must_use]
pub const fn with_shift(self, shift: Shift) -> Self {
Policy((self.0 & !8) | ((shift as u8) << 3))
}
#[must_use]
pub const fn with_float_to_int(self, float_to_int: FloatToInt) -> Self {
Policy((self.0 & !16) | ((float_to_int as u8) << 4))
}
#[must_use]
pub const fn bits(self) -> u8 {
self.0
}
#[must_use]
pub const fn from_bits(bits: u8) -> Option<Self> {
if bits & !0x1f != 0 {
None
} else {
Some(Policy(bits))
}
}
#[must_use]
pub const fn is_default(self) -> bool {
self.0 == 0
}
}
impl fmt::Debug for Policy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Policy")
.field("overflow", &self.overflow())
.field("div_zero", &self.div_zero())
.field("shift", &self.shift())
.field("float_to_int", &self.float_to_int())
.finish()
}
}
impl fmt::Display for Policy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut sep = "";
let mut token = |f: &mut fmt::Formatter<'_>, text: &str| -> fmt::Result {
f.write_str(sep)?;
sep = ".";
f.write_str(text)
};
match self.overflow() {
Overflow::Error => {}
Overflow::Wrap => token(f, "wrap")?,
Overflow::Trap => token(f, "trap")?,
Overflow::Promote => token(f, "promote")?,
}
if self.div_zero() == DivZero::Trap {
token(f, "divtrap")?;
}
if self.shift() == Shift::Mask {
token(f, "mask")?;
}
if self.float_to_int() == FloatToInt::Saturate {
token(f, "sat")?;
}
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IntOp(u8);
impl IntOp {
#[must_use]
pub const fn new(ty: IntTy) -> Self {
IntOp(ty as u8)
}
#[must_use]
pub const fn with_policy(self, policy: Policy) -> Self {
IntOp((self.0 & 7) | (policy.bits() << 3))
}
#[must_use]
pub const fn ty(self) -> IntTy {
IntTy::from_low3(self.0)
}
#[must_use]
pub const fn policy(self) -> Policy {
Policy(self.0 >> 3)
}
#[must_use]
pub const fn bits(self) -> u8 {
self.0
}
#[must_use]
pub const fn from_bits(bits: u8) -> Option<Self> {
match Policy::from_bits(bits >> 3) {
Some(_) => Some(IntOp(bits)),
None => None,
}
}
}
impl fmt::Debug for IntOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IntOp")
.field("ty", &self.ty())
.field("policy", &self.policy())
.finish()
}
}
impl fmt::Display for IntOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.ty().name())?;
if !self.policy().is_default() {
write!(f, ".{}", self.policy())?;
}
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IntConv(u8);
impl IntConv {
#[must_use]
pub const fn new(from: IntTy, to: IntTy, overflow: Overflow) -> Self {
IntConv((from as u8) | ((to as u8) << 3) | ((overflow as u8) << 6))
}
#[must_use]
pub const fn from(self) -> IntTy {
IntTy::from_low3(self.0)
}
#[must_use]
pub const fn to(self) -> IntTy {
IntTy::from_low3(self.0 >> 3)
}
#[must_use]
pub const fn overflow(self) -> Overflow {
match self.0 >> 6 {
0 => Overflow::Error,
1 => Overflow::Wrap,
2 => Overflow::Trap,
_ => Overflow::Promote,
}
}
#[must_use]
pub const fn bits(self) -> u8 {
self.0
}
#[must_use]
pub const fn from_bits(bits: u8) -> Option<Self> {
Some(IntConv(bits))
}
}
impl fmt::Debug for IntConv {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IntConv")
.field("from", &self.from())
.field("to", &self.to())
.field("overflow", &self.overflow())
.finish()
}
}
impl fmt::Display for IntConv {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.from(), self.to())?;
match self.overflow() {
Overflow::Error => Ok(()),
other => write!(f, ".{other}"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IntPair(u8);
impl IntPair {
#[must_use]
pub const fn new(from: IntTy, to: IntTy) -> Self {
IntPair((from as u8) | ((to as u8) << 3))
}
#[must_use]
pub const fn from(self) -> IntTy {
IntTy::from_low3(self.0)
}
#[must_use]
pub const fn to(self) -> IntTy {
IntTy::from_low3(self.0 >> 3)
}
#[must_use]
pub const fn bits(self) -> u8 {
self.0
}
#[must_use]
pub const fn from_bits(bits: u8) -> Option<Self> {
if bits >> 6 != 0 {
None
} else {
Some(IntPair(bits))
}
}
}
impl fmt::Debug for IntPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IntPair")
.field("from", &self.from())
.field("to", &self.to())
.finish()
}
}
impl fmt::Display for IntPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.from(), self.to())
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn policy_accepts_exactly_the_valid_bit_patterns() {
let mut accepted = 0;
for bits in 0..=u8::MAX {
if let Some(p) = Policy::from_bits(bits) {
accepted += 1;
assert_eq!(p.bits(), bits);
let rebuilt = Policy::new()
.with_overflow(p.overflow())
.with_div_zero(p.div_zero())
.with_shift(p.shift())
.with_float_to_int(p.float_to_int());
assert_eq!(rebuilt, p);
}
}
assert_eq!(accepted, 32);
}
#[test]
fn int_op_round_trips_every_valid_byte() {
let mut accepted = 0;
for bits in 0..=u8::MAX {
if let Some(op) = IntOp::from_bits(bits) {
accepted += 1;
assert_eq!(IntOp::new(op.ty()).with_policy(op.policy()), op);
}
}
assert_eq!(accepted, 8 * 32);
}
#[test]
fn int_conv_and_pair_round_trip() {
for bits in 0..=u8::MAX {
if let Some(c) = IntConv::from_bits(bits) {
assert_eq!(IntConv::new(c.from(), c.to(), c.overflow()), c);
}
if let Some(p) = IntPair::from_bits(bits) {
assert_eq!(IntPair::new(p.from(), p.to()), p);
}
}
}
#[test]
fn display_lists_only_non_default_policies() {
assert_eq!(Policy::new().to_string(), "");
let all = Policy::new()
.with_overflow(Overflow::Trap)
.with_div_zero(DivZero::Trap)
.with_shift(Shift::Mask)
.with_float_to_int(FloatToInt::Saturate);
assert_eq!(all.to_string(), "trap.divtrap.mask.sat");
let promote = Policy::new().with_overflow(Overflow::Promote);
assert_eq!(promote.to_string(), "promote");
assert_eq!(Policy::from_bits(promote.bits()), Some(promote));
assert_eq!(
IntOp::new(IntTy::U64).with_policy(all).to_string(),
"u64.trap.divtrap.mask.sat"
);
}
#[test]
fn int_types_report_width_and_sign() {
let widths: [u32; 8] = [8, 16, 32, 64, 8, 16, 32, 64];
for (ty, bits) in IntTy::ALL.iter().zip(widths) {
assert_eq!(ty.bits(), bits);
assert_eq!(ty.is_signed(), ty.code() < 4);
assert_eq!(IntTy::from_low3(ty.code()), *ty);
}
}
}