use std::alloc::{Layout, alloc};
use shape_value::ValueWordExt;
use super::heap_header::{HEAP_KIND_TYPED_ENUM, HeapHeader};
use super::struct_layout::FieldKind;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VariantLayout {
pub name: String,
pub tag: u8,
pub field_offsets: Vec<usize>,
pub field_kinds: Vec<FieldKind>,
pub size: usize,
}
impl VariantLayout {
fn compute(name: String, tag: u8, fields: &[FieldKind]) -> Self {
let mut current_offset = 0usize;
let mut field_offsets = Vec::with_capacity(fields.len());
let mut field_kinds = Vec::with_capacity(fields.len());
for kind in fields {
let align = kind.alignment();
let size = kind.size();
current_offset = (current_offset + align - 1) & !(align - 1);
field_offsets.push(current_offset);
field_kinds.push(*kind);
current_offset += size;
}
VariantLayout {
name,
tag,
field_offsets,
field_kinds,
size: current_offset,
}
}
#[inline]
pub fn field_count(&self) -> usize {
self.field_offsets.len()
}
#[inline]
pub fn is_unit(&self) -> bool {
self.field_offsets.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumLayout {
pub name: String,
pub variants: Vec<VariantLayout>,
pub max_payload_size: usize,
pub total_size: usize,
}
pub const ENUM_TAG_OFFSET: usize = 8;
pub const ENUM_PAYLOAD_OFFSET: usize = 16;
pub fn compute_enum_layout(name: &str, variants: &[(String, Vec<FieldKind>)]) -> EnumLayout {
assert!(
variants.len() <= 256,
"enum {} has {} variants — exceeds u8 tag limit (256)",
name,
variants.len()
);
let mut variant_layouts = Vec::with_capacity(variants.len());
let mut max_payload_size = 0usize;
for (i, (vname, fields)) in variants.iter().enumerate() {
let tag = i as u8;
let layout = VariantLayout::compute(vname.clone(), tag, fields);
if layout.size > max_payload_size {
max_payload_size = layout.size;
}
variant_layouts.push(layout);
}
let raw_total = ENUM_PAYLOAD_OFFSET + max_payload_size;
let total_size = (raw_total + 7) & !7;
EnumLayout {
name: name.to_string(),
variants: variant_layouts,
max_payload_size,
total_size,
}
}
impl EnumLayout {
#[inline]
pub fn variant_count(&self) -> usize {
self.variants.len()
}
pub fn variant_tag(&self, name: &str) -> Option<u8> {
self.variants
.iter()
.find(|v| v.name == name)
.map(|v| v.tag)
}
pub fn variant_layout(&self, tag: u8) -> Option<&VariantLayout> {
self.variants.get(tag as usize)
}
pub fn variant_by_name(&self, name: &str) -> Option<&VariantLayout> {
self.variants.iter().find(|v| v.name == name)
}
pub fn alloc(&self) -> *mut u8 {
let layout = Layout::from_size_align(self.total_size, 8)
.expect("enum layout size/align is invalid");
let ptr = unsafe { alloc(layout) };
assert!(!ptr.is_null(), "allocation failed for typed enum");
ptr
}
pub unsafe fn dealloc(&self, ptr: *mut u8) {
let layout = Layout::from_size_align(self.total_size, 8)
.expect("enum layout size/align is invalid");
unsafe { std::alloc::dealloc(ptr, layout) };
}
pub unsafe fn init_header(&self, ptr: *mut u8, tag: u8) {
unsafe {
std::ptr::write(ptr as *mut HeapHeader, HeapHeader::new(HEAP_KIND_TYPED_ENUM));
*ptr.add(ENUM_TAG_OFFSET) = tag;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shape_enum_layout() {
let variants = vec![
("Circle".to_string(), vec![FieldKind::F64]),
("Rectangle".to_string(), vec![FieldKind::F64, FieldKind::F64]),
];
let layout = compute_enum_layout("Shape", &variants);
assert_eq!(layout.name, "Shape");
assert_eq!(layout.variant_count(), 2);
let circle = &layout.variants[0];
assert_eq!(circle.name, "Circle");
assert_eq!(circle.tag, 0);
assert_eq!(circle.field_offsets, vec![0]);
assert_eq!(circle.field_kinds, vec![FieldKind::F64]);
assert_eq!(circle.size, 8);
let rect = &layout.variants[1];
assert_eq!(rect.name, "Rectangle");
assert_eq!(rect.tag, 1);
assert_eq!(rect.field_offsets, vec![0, 8]);
assert_eq!(rect.field_kinds, vec![FieldKind::F64, FieldKind::F64]);
assert_eq!(rect.size, 16);
assert_eq!(layout.max_payload_size, 16);
assert_eq!(layout.total_size, 32);
}
#[test]
fn test_color_unit_variants() {
let variants = vec![
("Red".to_string(), vec![]),
("Green".to_string(), vec![]),
("Blue".to_string(), vec![]),
];
let layout = compute_enum_layout("Color", &variants);
assert_eq!(layout.variant_count(), 3);
assert_eq!(layout.max_payload_size, 0);
assert_eq!(layout.total_size, 16);
for (i, name) in ["Red", "Green", "Blue"].iter().enumerate() {
let v = &layout.variants[i];
assert_eq!(v.name, *name);
assert_eq!(v.tag, i as u8);
assert!(v.is_unit());
assert_eq!(v.field_count(), 0);
assert_eq!(v.size, 0);
}
}
#[test]
fn test_result_enum_with_f64() {
let variants = vec![
("Ok".to_string(), vec![FieldKind::F64]),
("Err".to_string(), vec![FieldKind::F64]),
];
let layout = compute_enum_layout("Result", &variants);
assert_eq!(layout.variant_count(), 2);
assert_eq!(layout.max_payload_size, 8);
assert_eq!(layout.total_size, 24);
let ok = &layout.variants[0];
assert_eq!(ok.tag, 0);
assert_eq!(ok.field_offsets, vec![0]);
assert_eq!(ok.size, 8);
let err = &layout.variants[1];
assert_eq!(err.tag, 1);
assert_eq!(err.field_offsets, vec![0]);
assert_eq!(err.size, 8);
}
#[test]
fn test_variant_tag_lookup() {
let variants = vec![
("Red".to_string(), vec![]),
("Green".to_string(), vec![]),
("Blue".to_string(), vec![]),
];
let layout = compute_enum_layout("Color", &variants);
assert_eq!(layout.variant_tag("Red"), Some(0));
assert_eq!(layout.variant_tag("Green"), Some(1));
assert_eq!(layout.variant_tag("Blue"), Some(2));
assert_eq!(layout.variant_tag("Unknown"), None);
}
#[test]
fn test_variant_layout_lookup() {
let variants = vec![
("Circle".to_string(), vec![FieldKind::F64]),
("Rectangle".to_string(), vec![FieldKind::F64, FieldKind::F64]),
];
let layout = compute_enum_layout("Shape", &variants);
let v0 = layout.variant_layout(0).unwrap();
assert_eq!(v0.name, "Circle");
let v1 = layout.variant_layout(1).unwrap();
assert_eq!(v1.name, "Rectangle");
assert!(layout.variant_layout(2).is_none());
assert!(layout.variant_layout(255).is_none());
}
#[test]
fn test_variant_by_name_lookup() {
let variants = vec![
("Some".to_string(), vec![FieldKind::I64]),
("None".to_string(), vec![]),
];
let layout = compute_enum_layout("Option", &variants);
let some = layout.variant_by_name("Some").unwrap();
assert_eq!(some.tag, 0);
assert_eq!(some.field_kinds, vec![FieldKind::I64]);
let none = layout.variant_by_name("None").unwrap();
assert_eq!(none.tag, 1);
assert!(none.is_unit());
assert!(layout.variant_by_name("Other").is_none());
}
#[test]
fn test_mixed_alignment_within_variant() {
let variants = vec![(
"Mixed".to_string(),
vec![FieldKind::I32, FieldKind::F64, FieldKind::I8],
)];
let layout = compute_enum_layout("MixedEnum", &variants);
let v = &layout.variants[0];
assert_eq!(v.field_offsets, vec![0, 8, 16]);
assert_eq!(v.size, 17);
assert_eq!(layout.max_payload_size, 17);
assert_eq!(layout.total_size, 40);
}
#[test]
fn test_alloc_and_init_header() {
let variants = vec![
("Circle".to_string(), vec![FieldKind::F64]),
("Rectangle".to_string(), vec![FieldKind::F64, FieldKind::F64]),
];
let layout = compute_enum_layout("Shape", &variants);
let ptr = layout.alloc();
assert!(!ptr.is_null());
unsafe {
layout.init_header(ptr, 1);
let header = &*(ptr as *const HeapHeader);
assert_eq!(header.kind(), HEAP_KIND_TYPED_ENUM);
assert_eq!(header.get_refcount(), 1);
assert_eq!(*ptr.add(ENUM_TAG_OFFSET), 1);
layout.dealloc(ptr);
}
}
#[test]
fn test_total_size_rounding() {
let variants = vec![("V".to_string(), vec![FieldKind::U8])];
let layout = compute_enum_layout("E", &variants);
assert_eq!(layout.max_payload_size, 1);
assert_eq!(layout.total_size, 24);
}
#[test]
fn test_empty_enum_total_size() {
let layout = compute_enum_layout("Empty", &[]);
assert_eq!(layout.variant_count(), 0);
assert_eq!(layout.max_payload_size, 0);
assert_eq!(layout.total_size, 16);
}
#[test]
fn test_sequential_tags() {
let variants = vec![
("A".to_string(), vec![]),
("B".to_string(), vec![FieldKind::F64]),
("C".to_string(), vec![FieldKind::I32]),
("D".to_string(), vec![FieldKind::Bool]),
];
let layout = compute_enum_layout("Many", &variants);
for (i, v) in layout.variants.iter().enumerate() {
assert_eq!(v.tag, i as u8);
}
}
}