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,
}
#[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 = 0x3fff;
const KIND_SHIFT: u32 = 30;
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 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,
_ => Kind::Ptr,
}
}
#[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 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);
}
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::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 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_have_no_width_of_their_own() {
assert_eq!(Type::VOID.bits(), 0);
assert_eq!(Type::PTR.bits(), 0);
assert!(Type::VOID.is_void());
assert!(Type::PTR.is_ptr());
}
#[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];
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", "i8x16385", "voidx2", "ptrx2",
] {
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);
}
}