extern crate alloc;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScalarKind {
Unit,
Bool,
Byte,
Int,
Fixed,
#[cfg(feature = "floats")]
Float,
Text,
Opaque,
}
impl ScalarKind {
pub fn size_in_bytes(&self, word_bytes: usize, float_bytes: usize) -> usize {
let _ = float_bytes;
match self {
Self::Unit => 0,
Self::Bool => 1,
Self::Byte => 1,
Self::Int => word_bytes,
Self::Fixed => word_bytes,
#[cfg(feature = "floats")]
Self::Float => float_bytes,
Self::Text => 2 * word_bytes,
Self::Opaque => word_bytes,
}
}
pub fn to_tag(&self) -> u8 {
match self {
Self::Unit => 0,
Self::Bool => 1,
Self::Byte => 2,
Self::Int => 3,
Self::Fixed => 4,
#[cfg(feature = "floats")]
Self::Float => 5,
Self::Text => 6,
Self::Opaque => 7,
}
}
pub fn from_tag(tag: u8) -> Option<Self> {
match tag {
0 => Some(Self::Unit),
1 => Some(Self::Bool),
2 => Some(Self::Byte),
3 => Some(Self::Int),
4 => Some(Self::Fixed),
#[cfg(feature = "floats")]
5 => Some(Self::Float),
6 => Some(Self::Text),
7 => Some(Self::Opaque),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompositeKind {
Tuple,
Array,
Struct,
Enum,
}
impl CompositeKind {
pub fn to_tag(&self) -> u8 {
match self {
Self::Tuple => 0,
Self::Array => 1,
Self::Struct => 2,
Self::Enum => 3,
}
}
pub fn from_tag(tag: u8) -> Option<Self> {
match tag {
0 => Some(Self::Tuple),
1 => Some(Self::Array),
2 => Some(Self::Struct),
3 => Some(Self::Enum),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LayoutDescriptor {
Scalar(ScalarKind),
Tuple(Vec<LayoutDescriptor>),
Array {
element: Box<LayoutDescriptor>,
count: usize,
},
Struct {
type_name: String,
fields: Vec<(String, LayoutDescriptor)>,
},
Enum {
type_name: String,
variants: Vec<(String, Vec<LayoutDescriptor>)>,
},
}
impl LayoutDescriptor {
pub fn size_in_bytes(&self, word_bytes: usize, float_bytes: usize) -> usize {
match self {
Self::Scalar(kind) => kind.size_in_bytes(word_bytes, float_bytes),
Self::Tuple(elems) => elems
.iter()
.map(|e| e.size_in_bytes(word_bytes, float_bytes))
.fold(0usize, |a, b| a.saturating_add(b)),
Self::Array { element, count } => element
.size_in_bytes(word_bytes, float_bytes)
.saturating_mul(*count),
Self::Struct { fields, .. } => fields
.iter()
.map(|(_, t)| t.size_in_bytes(word_bytes, float_bytes))
.fold(0usize, |a, b| a.saturating_add(b)),
Self::Enum { variants, .. } => {
let payload_max = variants
.iter()
.map(|(_, payload)| {
payload
.iter()
.map(|t| t.size_in_bytes(word_bytes, float_bytes))
.fold(0usize, |a, b| a.saturating_add(b))
})
.max()
.unwrap_or(0);
word_bytes.saturating_add(payload_max)
}
}
}
pub fn field_offset(
&self,
index: usize,
word_bytes: usize,
float_bytes: usize,
) -> Option<usize> {
match self {
Self::Tuple(elems) => {
if index >= elems.len() {
None
} else {
Some(
elems
.iter()
.take(index)
.map(|e| e.size_in_bytes(word_bytes, float_bytes))
.fold(0usize, |a, b| a.saturating_add(b)),
)
}
}
Self::Struct { fields, .. } => {
if index >= fields.len() {
None
} else {
Some(
fields
.iter()
.take(index)
.map(|(_, t)| t.size_in_bytes(word_bytes, float_bytes))
.fold(0usize, |a, b| a.saturating_add(b)),
)
}
}
Self::Array { element, count } => {
if index >= *count {
None
} else {
Some(
element
.size_in_bytes(word_bytes, float_bytes)
.saturating_mul(index),
)
}
}
Self::Scalar(_) | Self::Enum { .. } => None,
}
}
pub fn field_layout(&self, index: usize) -> Option<&LayoutDescriptor> {
match self {
Self::Tuple(elems) => elems.get(index),
Self::Struct { fields, .. } => fields.get(index).map(|(_, t)| t),
Self::Array { element, count } => {
if index >= *count {
None
} else {
Some(element)
}
}
Self::Scalar(_) | Self::Enum { .. } => None,
}
}
pub fn struct_field_offset(
&self,
name: &str,
word_bytes: usize,
float_bytes: usize,
) -> Option<usize> {
match self {
Self::Struct { fields, .. } => {
let mut offset: usize = 0;
for (field_name, field_type) in fields {
if field_name == name {
return Some(offset);
}
offset =
offset.checked_add(field_type.size_in_bytes(word_bytes, float_bytes))?;
}
None
}
_ => None,
}
}
pub fn flat_scalar_kind(&self) -> Option<ScalarKind> {
match self {
Self::Scalar(k) => match k {
ScalarKind::Unit
| ScalarKind::Bool
| ScalarKind::Byte
| ScalarKind::Int
| ScalarKind::Fixed
| ScalarKind::Opaque
| ScalarKind::Text => Some(*k),
#[cfg(feature = "floats")]
ScalarKind::Float => Some(*k),
},
_ => None,
}
}
pub fn flat_composite_kind(&self) -> Option<CompositeKind> {
match self {
Self::Tuple(_) => Some(CompositeKind::Tuple),
Self::Array { .. } => Some(CompositeKind::Array),
Self::Struct { .. } => Some(CompositeKind::Struct),
Self::Enum { .. } => Some(CompositeKind::Enum),
Self::Scalar(_) => None,
}
}
pub fn flat_byte_size(&self, word_bytes: usize, float_bytes: usize) -> Option<usize> {
match self {
Self::Scalar(_) => self.flat_scalar_kind().and_then(|k| {
if matches!(k, ScalarKind::Text) && word_bytes < core::mem::size_of::<usize>() {
return None;
}
Some(k.size_in_bytes(word_bytes, float_bytes))
}),
Self::Tuple(elems) => {
let mut total = 0usize;
for e in elems {
total = total.saturating_add(e.flat_byte_size(word_bytes, float_bytes)?);
}
Some(total)
}
Self::Array { element, count } => Some(
element
.flat_byte_size(word_bytes, float_bytes)?
.saturating_mul(*count),
),
Self::Struct { fields, .. } => {
let mut total = 0usize;
for (_, t) in fields {
total = total.saturating_add(t.flat_byte_size(word_bytes, float_bytes)?);
}
Some(total)
}
Self::Enum {
type_name: _,
variants,
} => {
let mut payload_max = 0usize;
for (_, payload) in variants {
let mut sum = 0usize;
for p in payload {
sum = sum.saturating_add(p.flat_byte_size(word_bytes, float_bytes)?);
}
if sum > payload_max {
payload_max = sum;
}
}
Some(word_bytes.saturating_add(payload_max))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
const I64_BYTES: usize = 8;
const F64_BYTES: usize = 8;
const I32_BYTES: usize = 4;
const F32_BYTES: usize = 4;
#[test]
fn scalar_unit_is_zero_bytes() {
assert_eq!(ScalarKind::Unit.size_in_bytes(I64_BYTES, F64_BYTES), 0);
assert_eq!(ScalarKind::Unit.size_in_bytes(I32_BYTES, F32_BYTES), 0);
}
#[test]
fn scalar_bool_is_one_byte() {
assert_eq!(ScalarKind::Bool.size_in_bytes(I64_BYTES, F64_BYTES), 1);
assert_eq!(ScalarKind::Bool.size_in_bytes(I32_BYTES, F32_BYTES), 1);
}
#[test]
fn scalar_byte_is_one_byte() {
assert_eq!(ScalarKind::Byte.size_in_bytes(I64_BYTES, F64_BYTES), 1);
assert_eq!(ScalarKind::Byte.size_in_bytes(I32_BYTES, F32_BYTES), 1);
}
#[test]
fn scalar_int_follows_word_width() {
assert_eq!(ScalarKind::Int.size_in_bytes(I64_BYTES, F64_BYTES), 8);
assert_eq!(ScalarKind::Int.size_in_bytes(I32_BYTES, F32_BYTES), 4);
assert_eq!(ScalarKind::Int.size_in_bytes(2, F32_BYTES), 2);
assert_eq!(ScalarKind::Int.size_in_bytes(1, F32_BYTES), 1);
}
#[test]
fn scalar_fixed_follows_word_width() {
assert_eq!(ScalarKind::Fixed.size_in_bytes(I64_BYTES, F64_BYTES), 8);
assert_eq!(ScalarKind::Fixed.size_in_bytes(I32_BYTES, F32_BYTES), 4);
}
#[cfg(feature = "floats")]
#[test]
fn scalar_float_follows_float_width() {
assert_eq!(ScalarKind::Float.size_in_bytes(I64_BYTES, F64_BYTES), 8);
assert_eq!(ScalarKind::Float.size_in_bytes(I32_BYTES, F32_BYTES), 4);
}
#[test]
fn scalar_text_is_two_words() {
assert_eq!(ScalarKind::Text.size_in_bytes(I64_BYTES, F64_BYTES), 16);
assert_eq!(ScalarKind::Text.size_in_bytes(I32_BYTES, F32_BYTES), 8);
assert_eq!(ScalarKind::Text.size_in_bytes(2, F32_BYTES), 4);
assert_eq!(ScalarKind::Text.size_in_bytes(1, F32_BYTES), 2);
}
#[test]
fn scalar_opaque_is_one_word() {
assert_eq!(ScalarKind::Opaque.size_in_bytes(I64_BYTES, F64_BYTES), 8);
assert_eq!(ScalarKind::Opaque.size_in_bytes(I32_BYTES, F32_BYTES), 4);
assert_eq!(ScalarKind::Opaque.size_in_bytes(2, F32_BYTES), 2);
}
#[test]
fn tuple_size_sums_elements() {
let layout = LayoutDescriptor::Tuple(vec![
LayoutDescriptor::Scalar(ScalarKind::Int),
LayoutDescriptor::Scalar(ScalarKind::Bool),
LayoutDescriptor::Scalar(ScalarKind::Byte),
]);
assert_eq!(layout.size_in_bytes(I64_BYTES, F64_BYTES), 8 + 1 + 1);
assert_eq!(layout.size_in_bytes(I32_BYTES, F32_BYTES), 4 + 1 + 1);
}
#[test]
fn empty_tuple_is_zero_bytes() {
let layout = LayoutDescriptor::Tuple(vec![]);
assert_eq!(layout.size_in_bytes(I64_BYTES, F64_BYTES), 0);
}
#[test]
fn array_size_multiplies_count() {
let layout = LayoutDescriptor::Array {
element: Box::new(LayoutDescriptor::Scalar(ScalarKind::Int)),
count: 8,
};
assert_eq!(layout.size_in_bytes(I64_BYTES, F64_BYTES), 64);
assert_eq!(layout.size_in_bytes(I32_BYTES, F32_BYTES), 32);
}
#[test]
fn composite_size_saturates_instead_of_wrapping() {
let huge = LayoutDescriptor::Array {
element: Box::new(LayoutDescriptor::Scalar(ScalarKind::Int)),
count: usize::MAX,
};
assert_eq!(huge.size_in_bytes(I64_BYTES, F64_BYTES), usize::MAX);
assert_eq!(huge.flat_byte_size(I64_BYTES, F64_BYTES), Some(usize::MAX));
}
#[test]
fn empty_array_is_zero_bytes() {
let layout = LayoutDescriptor::Array {
element: Box::new(LayoutDescriptor::Scalar(ScalarKind::Int)),
count: 0,
};
assert_eq!(layout.size_in_bytes(I64_BYTES, F64_BYTES), 0);
}
#[test]
fn nested_tuple_size() {
let inner = LayoutDescriptor::Tuple(vec![
LayoutDescriptor::Scalar(ScalarKind::Int),
LayoutDescriptor::Scalar(ScalarKind::Int),
]);
let outer =
LayoutDescriptor::Tuple(vec![inner, LayoutDescriptor::Scalar(ScalarKind::Bool)]);
assert_eq!(outer.size_in_bytes(I64_BYTES, F64_BYTES), 16 + 1);
}
#[test]
fn struct_size_sums_fields() {
let layout = LayoutDescriptor::Struct {
type_name: "Point".to_string(),
fields: vec![
("x".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
("y".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
],
};
assert_eq!(layout.size_in_bytes(I64_BYTES, F64_BYTES), 16);
}
#[test]
fn enum_size_is_discriminant_plus_largest_variant() {
let layout = LayoutDescriptor::Enum {
type_name: "Option".to_string(),
variants: vec![
("None".to_string(), vec![]),
(
"Some".to_string(),
vec![LayoutDescriptor::Scalar(ScalarKind::Int)],
),
],
};
assert_eq!(layout.size_in_bytes(I64_BYTES, F64_BYTES), 8 + 8);
}
#[test]
fn enum_with_all_unit_variants_is_one_word() {
let layout = LayoutDescriptor::Enum {
type_name: "Color".to_string(),
variants: vec![
("Red".to_string(), vec![]),
("Green".to_string(), vec![]),
("Blue".to_string(), vec![]),
],
};
assert_eq!(layout.size_in_bytes(I64_BYTES, F64_BYTES), 8);
}
#[test]
fn enum_with_no_variants_is_one_word() {
let layout = LayoutDescriptor::Enum {
type_name: "Never".to_string(),
variants: vec![],
};
assert_eq!(layout.size_in_bytes(I64_BYTES, F64_BYTES), 8);
}
#[test]
fn tuple_field_offset() {
let layout = LayoutDescriptor::Tuple(vec![
LayoutDescriptor::Scalar(ScalarKind::Int),
LayoutDescriptor::Scalar(ScalarKind::Bool),
LayoutDescriptor::Scalar(ScalarKind::Int),
]);
assert_eq!(layout.field_offset(0, I64_BYTES, F64_BYTES), Some(0));
assert_eq!(layout.field_offset(1, I64_BYTES, F64_BYTES), Some(8));
assert_eq!(layout.field_offset(2, I64_BYTES, F64_BYTES), Some(9));
assert_eq!(layout.field_offset(3, I64_BYTES, F64_BYTES), None);
}
#[test]
fn struct_field_offset_by_index() {
let layout = LayoutDescriptor::Struct {
type_name: "Point".to_string(),
fields: vec![
("x".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
("y".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
("z".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
],
};
assert_eq!(layout.field_offset(0, I64_BYTES, F64_BYTES), Some(0));
assert_eq!(layout.field_offset(1, I64_BYTES, F64_BYTES), Some(8));
assert_eq!(layout.field_offset(2, I64_BYTES, F64_BYTES), Some(16));
assert_eq!(layout.field_offset(3, I64_BYTES, F64_BYTES), None);
}
#[test]
fn array_field_offset() {
let layout = LayoutDescriptor::Array {
element: Box::new(LayoutDescriptor::Scalar(ScalarKind::Int)),
count: 4,
};
assert_eq!(layout.field_offset(0, I64_BYTES, F64_BYTES), Some(0));
assert_eq!(layout.field_offset(1, I64_BYTES, F64_BYTES), Some(8));
assert_eq!(layout.field_offset(3, I64_BYTES, F64_BYTES), Some(24));
assert_eq!(layout.field_offset(4, I64_BYTES, F64_BYTES), None);
}
#[test]
fn struct_field_offset_by_name() {
let layout = LayoutDescriptor::Struct {
type_name: "Point".to_string(),
fields: vec![
("x".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
("y".to_string(), LayoutDescriptor::Scalar(ScalarKind::Bool)),
("z".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
],
};
assert_eq!(
layout.struct_field_offset("x", I64_BYTES, F64_BYTES),
Some(0)
);
assert_eq!(
layout.struct_field_offset("y", I64_BYTES, F64_BYTES),
Some(8)
);
assert_eq!(
layout.struct_field_offset("z", I64_BYTES, F64_BYTES),
Some(9)
);
assert_eq!(
layout.struct_field_offset("missing", I64_BYTES, F64_BYTES),
None
);
}
#[test]
fn field_layout_returns_element_types() {
let layout = LayoutDescriptor::Tuple(vec![
LayoutDescriptor::Scalar(ScalarKind::Int),
LayoutDescriptor::Scalar(ScalarKind::Bool),
]);
assert_eq!(
layout.field_layout(0),
Some(&LayoutDescriptor::Scalar(ScalarKind::Int))
);
assert_eq!(
layout.field_layout(1),
Some(&LayoutDescriptor::Scalar(ScalarKind::Bool))
);
assert_eq!(layout.field_layout(2), None);
}
#[test]
fn scalar_layout_has_no_fields() {
let layout = LayoutDescriptor::Scalar(ScalarKind::Int);
assert_eq!(layout.field_offset(0, I64_BYTES, F64_BYTES), None);
assert_eq!(layout.field_layout(0), None);
assert_eq!(layout.struct_field_offset("x", I64_BYTES, F64_BYTES), None);
}
#[test]
fn struct_field_offset_under_narrow_word() {
let layout = LayoutDescriptor::Struct {
type_name: "Point".to_string(),
fields: vec![
("x".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
("y".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
],
};
assert_eq!(layout.struct_field_offset("x", 2, F64_BYTES), Some(0));
assert_eq!(layout.struct_field_offset("y", 2, F64_BYTES), Some(2));
}
#[test]
fn flat_byte_size_scalar_eligibility() {
assert_eq!(
LayoutDescriptor::Scalar(ScalarKind::Int).flat_byte_size(I64_BYTES, F64_BYTES),
Some(8)
);
assert_eq!(
LayoutDescriptor::Scalar(ScalarKind::Bool).flat_byte_size(I64_BYTES, F64_BYTES),
Some(1)
);
assert_eq!(
LayoutDescriptor::Scalar(ScalarKind::Text).flat_byte_size(I64_BYTES, F64_BYTES),
Some(16)
);
assert_eq!(
LayoutDescriptor::Scalar(ScalarKind::Opaque).flat_byte_size(I64_BYTES, F64_BYTES),
Some(8)
);
#[cfg(feature = "floats")]
assert_eq!(
LayoutDescriptor::Scalar(ScalarKind::Float).flat_byte_size(I64_BYTES, F64_BYTES),
Some(8)
);
}
#[test]
fn flat_byte_size_nested_tuple_in_struct() {
let inner = LayoutDescriptor::Tuple(vec![
LayoutDescriptor::Scalar(ScalarKind::Int),
LayoutDescriptor::Scalar(ScalarKind::Int),
]);
let outer = LayoutDescriptor::Struct {
type_name: "Holder".to_string(),
fields: vec![
("coords".to_string(), inner),
("tag".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
],
};
assert_eq!(outer.flat_byte_size(I64_BYTES, F64_BYTES), Some(8 + 8 + 8));
}
#[test]
fn flat_byte_size_struct_with_text_field_is_flat() {
let layout = LayoutDescriptor::Struct {
type_name: "Greeting".to_string(),
fields: vec![
("id".to_string(), LayoutDescriptor::Scalar(ScalarKind::Int)),
(
"msg".to_string(),
LayoutDescriptor::Scalar(ScalarKind::Text),
),
],
};
assert_eq!(layout.flat_byte_size(I64_BYTES, F64_BYTES), Some(8 + 16));
}
#[test]
fn flat_byte_size_uniform_enum_pads_to_word_plus_max() {
let layout = LayoutDescriptor::Enum {
type_name: "Sig".to_string(),
variants: vec![
("Off".to_string(), vec![]),
(
"On".to_string(),
vec![LayoutDescriptor::Scalar(ScalarKind::Int)],
),
(
"Span".to_string(),
vec![
LayoutDescriptor::Scalar(ScalarKind::Int),
LayoutDescriptor::Scalar(ScalarKind::Int),
],
),
],
};
assert_eq!(layout.flat_byte_size(I64_BYTES, F64_BYTES), Some(8 + 16));
assert_eq!(layout.flat_composite_kind(), Some(CompositeKind::Enum));
}
#[test]
fn flat_byte_size_option_is_flat() {
let layout = LayoutDescriptor::Enum {
type_name: "Option".to_string(),
variants: vec![
("None".to_string(), vec![]),
(
"Some".to_string(),
vec![LayoutDescriptor::Scalar(ScalarKind::Int)],
),
],
};
assert_eq!(
layout.flat_byte_size(I64_BYTES, F64_BYTES),
Some(I64_BYTES + I64_BYTES)
);
}
#[test]
fn flat_byte_size_enum_with_text_payload_is_flat() {
let layout = LayoutDescriptor::Enum {
type_name: "Reply".to_string(),
variants: vec![
(
"Ok".to_string(),
vec![LayoutDescriptor::Scalar(ScalarKind::Int)],
),
(
"Err".to_string(),
vec![LayoutDescriptor::Scalar(ScalarKind::Text)],
),
],
};
assert_eq!(layout.flat_byte_size(I64_BYTES, F64_BYTES), Some(8 + 16));
}
#[test]
fn flat_scalar_kind_rejects_references() {
assert_eq!(
LayoutDescriptor::Scalar(ScalarKind::Int).flat_scalar_kind(),
Some(ScalarKind::Int)
);
assert_eq!(
LayoutDescriptor::Scalar(ScalarKind::Text).flat_scalar_kind(),
Some(ScalarKind::Text)
);
assert_eq!(LayoutDescriptor::Tuple(vec![]).flat_scalar_kind(), None);
}
}