use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Type(u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Kind {
Void,
Int,
Float,
Ptr,
Mem,
Cap,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Float {
F16,
F32,
F64,
F80,
F128,
}
impl Float {
#[must_use]
pub const fn bits(self) -> u32 {
match self {
Self::F16 => 16,
Self::F32 => 32,
Self::F64 => 64,
Self::F80 => 80,
Self::F128 => 128,
}
}
#[must_use]
pub const fn from_bits(bits: u32) -> Option<Self> {
match bits {
16 => Some(Self::F16),
32 => Some(Self::F32),
64 => Some(Self::F64),
80 => Some(Self::F80),
128 => Some(Self::F128),
_ => None,
}
}
}
impl fmt::Display for Float {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "f{}", self.bits())
}
}
const BITS_SHIFT: u32 = 0;
const BITS_MASK: u32 = 0xffff;
const LANES_SHIFT: u32 = 16;
const LANES_MASK: u32 = 0x1fff;
const KIND_SHIFT: u32 = 29;
impl Type {
pub const MAX_BITS: u32 = BITS_MASK;
pub const MAX_LANES: u32 = LANES_MASK + 1;
pub const VOID: Self = Self::pack(Kind::Void, 0, 1);
pub const PTR: Self = Self::pack(Kind::Ptr, 0, 1);
pub const MEM: Self = Self::pack(Kind::Mem, 0, 1);
pub const CAP: Self = Self::pack(Kind::Cap, 0, 1);
pub const I1: Self = Self::pack(Kind::Int, 1, 1);
const fn pack(kind: Kind, bits: u32, lanes: u32) -> Self {
Self((kind as u32) << KIND_SHIFT | (lanes - 1) << LANES_SHIFT | bits << BITS_SHIFT)
}
#[must_use]
pub const fn int(bits: u32) -> Self {
assert!(bits > 0 && bits <= Self::MAX_BITS, "integer width out of range");
Self::pack(Kind::Int, bits, 1)
}
#[must_use]
pub const fn float(format: Float) -> Self {
Self::pack(Kind::Float, format.bits(), 1)
}
#[must_use]
pub const fn vector(lane: Self, lanes: u32) -> Self {
assert!(lanes > 0 && lanes <= Self::MAX_LANES, "lane count out of range");
assert!(lane.is_scalar(), "a vector's lane is a scalar");
assert!(
matches!(lane.kind(), Kind::Int | Kind::Float),
"a vector's lane is an integer or a floating point value"
);
Self::pack(lane.kind(), lane.bits(), lanes)
}
#[must_use]
pub const fn kind(self) -> Kind {
match self.0 >> KIND_SHIFT {
0 => Kind::Void,
1 => Kind::Int,
2 => Kind::Float,
3 => Kind::Ptr,
4 => Kind::Mem,
_ => Kind::Cap,
}
}
#[must_use]
pub const fn bits(self) -> u32 {
self.0 >> BITS_SHIFT & BITS_MASK
}
#[must_use]
pub const fn lanes(self) -> u32 {
(self.0 >> LANES_SHIFT & LANES_MASK) + 1
}
#[must_use]
pub const fn is_scalar(self) -> bool {
self.lanes() == 1
}
#[must_use]
pub const fn is_vector(self) -> bool {
self.lanes() > 1
}
#[must_use]
pub const fn lane(self) -> Self {
Self::pack(self.kind(), self.bits(), 1)
}
#[must_use]
pub const fn with_lane(self, lane: Self) -> Self {
Self::vector(lane, self.lanes())
}
#[must_use]
pub const fn is_int(self) -> bool {
matches!(self.kind(), Kind::Int)
}
#[must_use]
pub const fn is_float(self) -> bool {
matches!(self.kind(), Kind::Float)
}
#[must_use]
pub const fn is_ptr(self) -> bool {
matches!(self.kind(), Kind::Ptr)
}
#[must_use]
pub const fn is_void(self) -> bool {
matches!(self.kind(), Kind::Void)
}
#[must_use]
pub const fn is_mem(self) -> bool {
matches!(self.kind(), Kind::Mem)
}
#[must_use]
pub const fn is_cap(self) -> bool {
matches!(self.kind(), Kind::Cap)
}
#[must_use]
pub const fn format(self) -> Option<Float> {
match self.kind() {
Kind::Float => Float::from_bits(self.bits()),
_ => None,
}
}
#[must_use]
pub fn parse(text: &str) -> Option<Self> {
if text == "void" {
return Some(Self::VOID);
}
if text == "ptr" {
return Some(Self::PTR);
}
if text == "mem" {
return Some(Self::MEM);
}
if text == "cap" {
return Some(Self::CAP);
}
let (head, lanes) = match text.split_once('x') {
Some((head, lanes)) => (head, parse_u32(lanes).filter(|&n| n > 1)?),
None => (text, 1),
};
let bits = parse_u32(head.strip_prefix(['i', 'f'])?)?;
let lane = match head.as_bytes()[0] {
b'i' if bits > 0 && bits <= Self::MAX_BITS => Self::int(bits),
b'f' => Self::float(Float::from_bits(bits)?),
_ => return None,
};
if lanes > Self::MAX_LANES {
return None;
}
Some(if lanes == 1 { lane } else { Self::vector(lane, lanes) })
}
}
fn parse_u32(text: &str) -> Option<u32> {
if text.is_empty() || (text.starts_with('0') && text.len() > 1) {
return None;
}
text.bytes().all(|b| b.is_ascii_digit()).then(|| text.parse().ok())?
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind() {
Kind::Void => return f.write_str("void"),
Kind::Ptr => return f.write_str("ptr"),
Kind::Mem => return f.write_str("mem"),
Kind::Cap => return f.write_str("cap"),
Kind::Int => write!(f, "i{}", self.bits())?,
Kind::Float => write!(f, "f{}", self.bits())?,
}
if self.is_vector() {
write!(f, "x{}", self.lanes())?;
}
Ok(())
}
}
impl fmt::Debug for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_type_is_four_bytes() {
assert_eq!(size_of::<Type>(), 4);
}
#[test]
fn memory_is_its_own_kind_and_nothing_else_answers_to_it() {
assert_eq!(Type::MEM.kind(), Kind::Mem);
assert!(Type::MEM.is_mem());
assert_eq!(Type::MEM.to_string(), "mem");
assert_eq!(Type::parse("mem"), Some(Type::MEM));
for other in [Type::VOID, Type::PTR, Type::int(64), Type::float(Float::F64)] {
assert!(!other.is_mem(), "{other} answered to being memory");
assert_ne!(other, Type::MEM);
}
assert!(!Type::MEM.is_void() && !Type::MEM.is_ptr() && !Type::MEM.is_int());
}
#[test]
fn the_widest_vector_still_packs_beside_the_new_kind() {
let widest = Type::vector(Type::int(8), Type::MAX_LANES);
assert_eq!(widest.lanes(), Type::MAX_LANES);
assert_eq!(widest.lane(), Type::int(8));
let widest_int = Type::int(Type::MAX_BITS);
assert_eq!(widest_int.bits(), Type::MAX_BITS);
assert_eq!(widest_int.lanes(), 1);
assert_eq!(Type::vector(widest_int, Type::MAX_LANES).bits(), Type::MAX_BITS);
}
#[test]
fn the_parts_come_back_out() {
let v = Type::vector(Type::int(8), 16);
assert_eq!(v.kind(), Kind::Int);
assert_eq!(v.bits(), 8);
assert_eq!(v.lanes(), 16);
assert_eq!(v.lane(), Type::int(8));
assert!(v.is_vector());
assert!(!v.is_scalar());
}
#[test]
fn a_scalar_has_one_lane_and_is_its_own_lane() {
let i32_ = Type::int(32);
assert_eq!(i32_.lanes(), 1);
assert_eq!(i32_.lane(), i32_);
assert!(i32_.is_scalar());
}
#[test]
fn void_and_ptr_and_cap_have_no_width_of_their_own() {
assert_eq!(Type::VOID.bits(), 0);
assert_eq!(Type::PTR.bits(), 0);
assert_eq!(Type::CAP.bits(), 0);
assert!(Type::VOID.is_void());
assert!(Type::PTR.is_ptr());
assert!(Type::CAP.is_cap());
}
#[test]
fn a_capability_is_none_of_the_other_kinds() {
assert_eq!(Type::CAP.kind(), Kind::Cap);
assert_eq!(Type::CAP.to_string(), "cap");
assert_eq!(Type::parse("cap"), Some(Type::CAP));
for other in [Type::VOID, Type::PTR, Type::MEM, Type::int(64), Type::float(Float::F64)] {
assert!(!other.is_cap(), "{other} answered to being a capability");
assert_ne!(other, Type::CAP);
}
assert!(!Type::CAP.is_ptr() && !Type::CAP.is_void() && !Type::CAP.is_mem());
assert!(Type::CAP.is_scalar());
}
#[test]
fn a_comparison_keeps_the_lane_count() {
assert_eq!(Type::vector(Type::int(32), 4).with_lane(Type::I1), Type::vector(Type::I1, 4));
assert_eq!(Type::int(32).with_lane(Type::I1), Type::I1);
}
#[test]
fn the_extremes_are_representable() {
let widest = Type::int(Type::MAX_BITS);
assert_eq!(widest.bits(), Type::MAX_BITS);
let longest = Type::vector(Type::I1, Type::MAX_LANES);
assert_eq!(longest.lanes(), Type::MAX_LANES);
assert_eq!(longest.lane(), Type::I1);
}
#[test]
fn every_type_round_trips_through_its_text() {
let mut types = vec![Type::VOID, Type::PTR, Type::MEM, Type::CAP];
for bits in [1, 8, 16, 32, 64, 128, 3, 12, Type::MAX_BITS] {
types.push(Type::int(bits));
}
for format in [Float::F16, Float::F32, Float::F64, Float::F80, Float::F128] {
types.push(Type::float(format));
}
for lanes in [2, 4, 16, Type::MAX_LANES] {
types.push(Type::vector(Type::int(8), lanes));
types.push(Type::vector(Type::float(Float::F32), lanes));
}
for ty in types {
let text = ty.to_string();
assert_eq!(Type::parse(&text), Some(ty), "{text}");
}
}
#[test]
fn the_texts_that_are_not_types_are_refused() {
for text in [
"",
"i",
"f",
"i0",
"i8x0",
"i8x1",
"f24",
"f0",
"i-1",
"i+1",
"i08",
"i8x01",
"int",
"i32 ",
" i32",
"i8x",
"x4",
"i8x4x4",
"i65536",
"i8x8193",
"voidx2",
"ptrx2",
"capx2",
"cap ",
"Cap",
"capability",
] {
assert_eq!(Type::parse(text), None, "{text}");
}
}
#[test]
fn a_format_knows_its_width_both_ways() {
for format in [Float::F16, Float::F32, Float::F64, Float::F80, Float::F128] {
assert_eq!(Float::from_bits(format.bits()), Some(format));
assert_eq!(Type::float(format).format(), Some(format));
}
assert_eq!(Float::from_bits(24), None);
assert_eq!(Type::int(32).format(), None);
}
#[test]
#[should_panic(expected = "integer width out of range")]
fn a_zero_width_integer_is_refused() {
let _ = Type::int(0);
}
#[test]
#[should_panic(expected = "integer width out of range")]
fn an_integer_wider_than_the_packing_is_refused() {
let _ = Type::int(Type::MAX_BITS + 1);
}
#[test]
#[should_panic(expected = "lane count out of range")]
fn a_vector_with_no_lanes_is_refused() {
let _ = Type::vector(Type::int(8), 0);
}
#[test]
#[should_panic(expected = "a vector's lane is a scalar")]
fn a_vector_of_vectors_is_refused() {
let _ = Type::vector(Type::vector(Type::int(8), 2), 2);
}
#[test]
#[should_panic(expected = "an integer or a floating point value")]
fn a_vector_of_pointers_is_refused() {
let _ = Type::vector(Type::PTR, 2);
}
#[test]
#[should_panic(expected = "an integer or a floating point value")]
fn a_vector_of_capabilities_is_refused() {
let _ = Type::vector(Type::CAP, 2);
}
}