#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FieldType {
F64,
I64,
I32,
U32,
I16,
U16,
I8,
U8,
Bool,
Ptr,
}
impl FieldType {
#[inline]
pub const fn size(self) -> u32 {
match self {
FieldType::F64 | FieldType::I64 | FieldType::Ptr => 8,
FieldType::I32 | FieldType::U32 => 4,
FieldType::I16 | FieldType::U16 => 2,
FieldType::I8 | FieldType::U8 | FieldType::Bool => 1,
}
}
#[inline]
pub const fn align(self) -> u32 {
self.size()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StructFieldLayout {
pub name: String,
pub offset: u32,
pub size: u32,
pub field_type: FieldType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StructLayout {
pub name: String,
pub total_size: u32,
pub fields: Vec<StructFieldLayout>,
}
pub const V2_HEADER_SIZE: u32 = 8;
pub const V2_HEADER_ALIGN: u32 = 8;
#[inline]
const fn align_up(offset: u32, align: u32) -> u32 {
debug_assert!(align.is_power_of_two());
let mask = align - 1;
(offset + mask) & !mask
}
pub fn compute_struct_layout(name: &str, fields: &[(String, FieldType)]) -> StructLayout {
let mut max_align = V2_HEADER_ALIGN;
let mut cursor = V2_HEADER_SIZE;
let mut field_layouts = Vec::with_capacity(fields.len());
for (field_name, field_type) in fields {
let size = field_type.size();
let align = field_type.align();
if align > max_align {
max_align = align;
}
cursor = align_up(cursor, align);
field_layouts.push(StructFieldLayout {
name: field_name.clone(),
offset: cursor,
size,
field_type: *field_type,
});
cursor += size;
}
let total_size = align_up(cursor, max_align);
StructLayout {
name: name.to_string(),
total_size,
fields: field_layouts,
}
}
impl StructLayout {
pub fn field(&self, name: &str) -> Option<&StructFieldLayout> {
self.fields.iter().find(|f| f.name == name)
}
pub fn alignment(&self) -> u32 {
let mut max_align = V2_HEADER_ALIGN;
for f in &self.fields {
let a = f.field_type.align();
if a > max_align {
max_align = a;
}
}
max_align
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_type_sizes() {
assert_eq!(FieldType::F64.size(), 8);
assert_eq!(FieldType::I64.size(), 8);
assert_eq!(FieldType::Ptr.size(), 8);
assert_eq!(FieldType::I32.size(), 4);
assert_eq!(FieldType::U32.size(), 4);
assert_eq!(FieldType::I16.size(), 2);
assert_eq!(FieldType::U16.size(), 2);
assert_eq!(FieldType::I8.size(), 1);
assert_eq!(FieldType::U8.size(), 1);
assert_eq!(FieldType::Bool.size(), 1);
}
#[test]
fn test_field_type_alignments() {
assert_eq!(FieldType::F64.align(), 8);
assert_eq!(FieldType::I64.align(), 8);
assert_eq!(FieldType::Ptr.align(), 8);
assert_eq!(FieldType::I32.align(), 4);
assert_eq!(FieldType::U32.align(), 4);
assert_eq!(FieldType::I16.align(), 2);
assert_eq!(FieldType::U16.align(), 2);
assert_eq!(FieldType::I8.align(), 1);
assert_eq!(FieldType::U8.align(), 1);
assert_eq!(FieldType::Bool.align(), 1);
}
#[test]
fn test_align_up() {
assert_eq!(align_up(0, 8), 0);
assert_eq!(align_up(1, 8), 8);
assert_eq!(align_up(7, 8), 8);
assert_eq!(align_up(8, 8), 8);
assert_eq!(align_up(9, 8), 16);
assert_eq!(align_up(3, 4), 4);
assert_eq!(align_up(4, 4), 4);
assert_eq!(align_up(5, 4), 8);
assert_eq!(align_up(0, 1), 0);
assert_eq!(align_up(7, 1), 7);
assert_eq!(align_up(1, 2), 2);
assert_eq!(align_up(2, 2), 2);
assert_eq!(align_up(3, 2), 4);
}
#[test]
fn test_point_layout() {
let layout = compute_struct_layout("Point", &[
("x".into(), FieldType::F64),
("y".into(), FieldType::F64),
]);
assert_eq!(layout.name, "Point");
assert_eq!(layout.fields.len(), 2);
assert_eq!(layout.fields[0].name, "x");
assert_eq!(layout.fields[0].offset, 8);
assert_eq!(layout.fields[0].size, 8);
assert_eq!(layout.fields[0].field_type, FieldType::F64);
assert_eq!(layout.fields[1].name, "y");
assert_eq!(layout.fields[1].offset, 16);
assert_eq!(layout.fields[1].size, 8);
assert_eq!(layout.fields[1].field_type, FieldType::F64);
assert_eq!(layout.total_size, 24);
assert_eq!(layout.alignment(), 8);
}
#[test]
fn test_color_layout() {
let layout = compute_struct_layout("Color", &[
("r".into(), FieldType::U8),
("g".into(), FieldType::U8),
("b".into(), FieldType::U8),
]);
assert_eq!(layout.name, "Color");
assert_eq!(layout.fields.len(), 3);
assert_eq!(layout.fields[0].name, "r");
assert_eq!(layout.fields[0].offset, 8);
assert_eq!(layout.fields[0].size, 1);
assert_eq!(layout.fields[1].name, "g");
assert_eq!(layout.fields[1].offset, 9);
assert_eq!(layout.fields[1].size, 1);
assert_eq!(layout.fields[2].name, "b");
assert_eq!(layout.fields[2].offset, 10);
assert_eq!(layout.fields[2].size, 1);
assert_eq!(layout.total_size, 16);
assert_eq!(layout.alignment(), 8);
}
#[test]
fn test_mixed_alignment_padding() {
let layout = compute_struct_layout("Mixed", &[
("flag".into(), FieldType::Bool),
("value".into(), FieldType::F64),
("count".into(), FieldType::I32),
]);
assert_eq!(layout.fields.len(), 3);
assert_eq!(layout.fields[0].name, "flag");
assert_eq!(layout.fields[0].offset, 8);
assert_eq!(layout.fields[0].size, 1);
assert_eq!(layout.fields[1].name, "value");
assert_eq!(layout.fields[1].offset, 16);
assert_eq!(layout.fields[1].size, 8);
assert_eq!(layout.fields[2].name, "count");
assert_eq!(layout.fields[2].offset, 24);
assert_eq!(layout.fields[2].size, 4);
assert_eq!(layout.total_size, 32);
assert_eq!(layout.alignment(), 8);
}
#[test]
fn test_empty_struct() {
let layout = compute_struct_layout("Empty", &[]);
assert_eq!(layout.name, "Empty");
assert_eq!(layout.fields.len(), 0);
assert_eq!(layout.total_size, 8);
assert_eq!(layout.alignment(), 8);
}
#[test]
fn test_single_bool_field() {
let layout = compute_struct_layout("Flag", &[
("value".into(), FieldType::Bool),
]);
assert_eq!(layout.fields[0].offset, 8);
assert_eq!(layout.fields[0].size, 1);
assert_eq!(layout.total_size, 16);
}
#[test]
fn test_single_i32_field() {
let layout = compute_struct_layout("Counter", &[
("n".into(), FieldType::I32),
]);
assert_eq!(layout.fields[0].offset, 8);
assert_eq!(layout.fields[0].size, 4);
assert_eq!(layout.total_size, 16);
}
#[test]
fn test_all_field_types() {
let layout = compute_struct_layout("AllTypes", &[
("a_f64".into(), FieldType::F64),
("b_i64".into(), FieldType::I64),
("c_ptr".into(), FieldType::Ptr),
("d_i32".into(), FieldType::I32),
("e_u32".into(), FieldType::U32),
("f_i16".into(), FieldType::I16),
("g_u16".into(), FieldType::U16),
("h_i8".into(), FieldType::I8),
("i_u8".into(), FieldType::U8),
("j_bool".into(), FieldType::Bool),
]);
assert_eq!(layout.fields[0].offset, 8); assert_eq!(layout.fields[1].offset, 16); assert_eq!(layout.fields[2].offset, 24);
assert_eq!(layout.fields[3].offset, 32); assert_eq!(layout.fields[4].offset, 36);
assert_eq!(layout.fields[5].offset, 40); assert_eq!(layout.fields[6].offset, 42);
assert_eq!(layout.fields[7].offset, 44); assert_eq!(layout.fields[8].offset, 45); assert_eq!(layout.fields[9].offset, 46);
assert_eq!(layout.total_size, 48);
assert_eq!(layout.alignment(), 8);
}
#[test]
fn test_i16_then_i64_padding() {
let layout = compute_struct_layout("T", &[
("a".into(), FieldType::I16),
("b".into(), FieldType::I64),
]);
assert_eq!(layout.fields[0].offset, 8);
assert_eq!(layout.fields[1].offset, 16);
assert_eq!(layout.total_size, 24);
}
#[test]
fn test_bool_i32_bool_padding() {
let layout = compute_struct_layout("T", &[
("a".into(), FieldType::Bool),
("b".into(), FieldType::I32),
("c".into(), FieldType::Bool),
]);
assert_eq!(layout.fields[0].offset, 8);
assert_eq!(layout.fields[1].offset, 12);
assert_eq!(layout.fields[2].offset, 16);
assert_eq!(layout.total_size, 24);
}
#[test]
fn test_struct_with_pointer_fields() {
let layout = compute_struct_layout("Node", &[
("value".into(), FieldType::I32),
("next".into(), FieldType::Ptr),
("prev".into(), FieldType::Ptr),
]);
assert_eq!(layout.fields[0].offset, 8);
assert_eq!(layout.fields[0].size, 4);
assert_eq!(layout.fields[1].offset, 16);
assert_eq!(layout.fields[1].size, 8);
assert_eq!(layout.fields[2].offset, 24);
assert_eq!(layout.fields[2].size, 8);
assert_eq!(layout.total_size, 32);
}
#[test]
fn test_field_lookup_by_name() {
let layout = compute_struct_layout("Point", &[
("x".into(), FieldType::F64),
("y".into(), FieldType::F64),
]);
let x = layout.field("x").expect("field 'x' should exist");
assert_eq!(x.offset, 8);
assert_eq!(x.field_type, FieldType::F64);
let y = layout.field("y").expect("field 'y' should exist");
assert_eq!(y.offset, 16);
assert_eq!(y.field_type, FieldType::F64);
assert!(layout.field("z").is_none());
}
#[test]
fn test_worst_case_padding() {
let layout = compute_struct_layout("Padded", &[
("a".into(), FieldType::U8),
("b".into(), FieldType::F64),
("c".into(), FieldType::U8),
("d".into(), FieldType::F64),
]);
assert_eq!(layout.fields[0].offset, 8);
assert_eq!(layout.fields[1].offset, 16);
assert_eq!(layout.fields[2].offset, 24);
assert_eq!(layout.fields[3].offset, 32);
assert_eq!(layout.total_size, 40);
}
#[test]
fn test_layout_is_deterministic() {
let fields: Vec<(String, FieldType)> = vec![
("a".into(), FieldType::I32),
("b".into(), FieldType::Bool),
("c".into(), FieldType::F64),
];
let layout1 = compute_struct_layout("T", &fields);
let layout2 = compute_struct_layout("T", &fields);
assert_eq!(layout1, layout2);
}
#[test]
fn test_only_16bit_fields() {
let layout = compute_struct_layout("Shorts", &[
("a".into(), FieldType::I16),
("b".into(), FieldType::U16),
("c".into(), FieldType::I16),
]);
assert_eq!(layout.fields[0].offset, 8); assert_eq!(layout.fields[1].offset, 10); assert_eq!(layout.fields[2].offset, 12);
assert_eq!(layout.total_size, 16);
}
#[test]
fn test_header_constants() {
assert_eq!(V2_HEADER_SIZE, 8);
assert_eq!(V2_HEADER_ALIGN, 8);
}
}