use std::ptr;
use super::enum_layout::{ENUM_PAYLOAD_OFFSET, ENUM_TAG_OFFSET, EnumLayout};
use super::heap_header::{HEAP_KIND_V2_TYPED_ENUM, HeapHeader};
use super::struct_layout::FieldKind;
pub fn alloc_typed_enum(layout: &EnumLayout) -> *mut u8 {
let ptr = layout.alloc();
unsafe {
ptr::write(ptr as *mut HeapHeader, HeapHeader::new(HEAP_KIND_V2_TYPED_ENUM));
}
ptr
}
pub unsafe fn dealloc_typed_enum(layout: &EnumLayout, ptr: *mut u8) {
unsafe { layout.dealloc(ptr) };
}
#[inline]
pub unsafe fn read_tag(ptr: *const u8) -> u8 {
unsafe { *ptr.add(ENUM_TAG_OFFSET) }
}
#[inline]
pub unsafe fn write_tag(ptr: *mut u8, tag: u8) {
unsafe {
*ptr.add(ENUM_TAG_OFFSET) = tag;
}
}
#[inline]
pub unsafe fn payload_ptr(ptr: *mut u8) -> *mut u8 {
unsafe { ptr.add(ENUM_PAYLOAD_OFFSET) }
}
#[inline]
pub unsafe fn read_payload_field(ptr: *const u8, field_offset: usize, kind: FieldKind) -> u64 {
unsafe {
let field_ptr = ptr.add(ENUM_PAYLOAD_OFFSET + field_offset);
match kind {
FieldKind::F64 => {
let v = ptr::read_unaligned(field_ptr as *const f64);
v.to_bits()
}
FieldKind::I64 => {
let v = ptr::read_unaligned(field_ptr as *const i64);
v as u64
}
FieldKind::U64 | FieldKind::Ptr => ptr::read_unaligned(field_ptr as *const u64),
FieldKind::I32 => {
let v = ptr::read_unaligned(field_ptr as *const i32);
v as i64 as u64
}
FieldKind::U32 => {
let v = ptr::read_unaligned(field_ptr as *const u32);
v as u64
}
FieldKind::I16 => {
let v = ptr::read_unaligned(field_ptr as *const i16);
v as i64 as u64
}
FieldKind::U16 => {
let v = ptr::read_unaligned(field_ptr as *const u16);
v as u64
}
FieldKind::I8 => {
let v = ptr::read_unaligned(field_ptr as *const i8);
v as i64 as u64
}
FieldKind::U8 => {
let v = ptr::read_unaligned(field_ptr as *const u8);
v as u64
}
FieldKind::Bool => {
let v = ptr::read_unaligned(field_ptr as *const u8);
v as u64
}
}
}
}
#[inline]
pub unsafe fn write_payload_field(
ptr: *mut u8,
field_offset: usize,
kind: FieldKind,
bits: u64,
) {
unsafe {
let field_ptr = ptr.add(ENUM_PAYLOAD_OFFSET + field_offset);
match kind {
FieldKind::F64 => {
ptr::write_unaligned(field_ptr as *mut f64, f64::from_bits(bits));
}
FieldKind::I64 => {
ptr::write_unaligned(field_ptr as *mut i64, bits as i64);
}
FieldKind::U64 | FieldKind::Ptr => {
ptr::write_unaligned(field_ptr as *mut u64, bits);
}
FieldKind::I32 => {
ptr::write_unaligned(field_ptr as *mut i32, bits as i32);
}
FieldKind::U32 => {
ptr::write_unaligned(field_ptr as *mut u32, bits as u32);
}
FieldKind::I16 => {
ptr::write_unaligned(field_ptr as *mut i16, bits as i16);
}
FieldKind::U16 => {
ptr::write_unaligned(field_ptr as *mut u16, bits as u16);
}
FieldKind::I8 => {
ptr::write_unaligned(field_ptr as *mut i8, bits as i8);
}
FieldKind::U8 => {
ptr::write_unaligned(field_ptr as *mut u8, bits as u8);
}
FieldKind::Bool => {
ptr::write_unaligned(field_ptr as *mut u8, (bits != 0) as u8);
}
}
}
}
#[cfg(test)]
mod tests {
use super::super::enum_layout::compute_enum_layout;
use super::*;
#[test]
fn test_alloc_writes_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 = alloc_typed_enum(&layout);
assert!(!ptr.is_null());
unsafe {
let header = &*(ptr as *const HeapHeader);
assert_eq!(header.kind(), HEAP_KIND_V2_TYPED_ENUM);
assert_eq!(header.get_refcount(), 1);
dealloc_typed_enum(&layout, ptr);
}
}
#[test]
fn test_write_then_read_tag() {
let variants = vec![
("A".to_string(), vec![]),
("B".to_string(), vec![]),
("C".to_string(), vec![]),
];
let layout = compute_enum_layout("E", &variants);
let ptr = alloc_typed_enum(&layout);
unsafe {
write_tag(ptr, 0);
assert_eq!(read_tag(ptr), 0);
write_tag(ptr, 1);
assert_eq!(read_tag(ptr), 1);
write_tag(ptr, 2);
assert_eq!(read_tag(ptr), 2);
let header = &*(ptr as *const HeapHeader);
assert_eq!(header.kind(), HEAP_KIND_V2_TYPED_ENUM);
assert_eq!(header.get_refcount(), 1);
dealloc_typed_enum(&layout, ptr);
}
}
#[test]
fn test_payload_ptr_offset() {
let variants = vec![("V".to_string(), vec![FieldKind::F64])];
let layout = compute_enum_layout("E", &variants);
let ptr = alloc_typed_enum(&layout);
unsafe {
let pp = payload_ptr(ptr);
let diff = pp as usize - ptr as usize;
assert_eq!(diff, ENUM_PAYLOAD_OFFSET);
assert_eq!(diff, 16);
dealloc_typed_enum(&layout, ptr);
}
}
#[test]
fn test_read_write_f64_field() {
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 = alloc_typed_enum(&layout);
unsafe {
write_tag(ptr, 0);
let circle = layout.variant_layout(0).unwrap();
assert_eq!(circle.field_offsets[0], 0);
write_payload_field(ptr, 0, FieldKind::F64, 3.14_f64.to_bits());
assert_eq!(read_tag(ptr), 0);
let bits = read_payload_field(ptr, 0, FieldKind::F64);
assert_eq!(f64::from_bits(bits), 3.14);
dealloc_typed_enum(&layout, ptr);
}
let ptr = alloc_typed_enum(&layout);
unsafe {
write_tag(ptr, 1);
let rect = layout.variant_layout(1).unwrap();
assert_eq!(rect.field_offsets[0], 0);
assert_eq!(rect.field_offsets[1], 8);
write_payload_field(ptr, 0, FieldKind::F64, 4.0_f64.to_bits());
write_payload_field(ptr, 8, FieldKind::F64, 5.0_f64.to_bits());
assert_eq!(read_tag(ptr), 1);
let w = f64::from_bits(read_payload_field(ptr, 0, FieldKind::F64));
let h = f64::from_bits(read_payload_field(ptr, 8, FieldKind::F64));
assert_eq!(w, 4.0);
assert_eq!(h, 5.0);
dealloc_typed_enum(&layout, ptr);
}
}
#[test]
fn test_read_write_int_fields() {
let variants = vec![(
"V".to_string(),
vec![FieldKind::I32, FieldKind::I64, FieldKind::I8],
)];
let layout = compute_enum_layout("E", &variants);
let v = layout.variant_layout(0).unwrap();
assert_eq!(v.field_offsets, vec![0, 8, 16]);
let ptr = alloc_typed_enum(&layout);
unsafe {
write_tag(ptr, 0);
write_payload_field(ptr, 0, FieldKind::I32, (-42_i32) as u32 as u64);
write_payload_field(ptr, 8, FieldKind::I64, i64::MAX as u64);
write_payload_field(ptr, 16, FieldKind::I8, (-7_i8) as u8 as u64);
let a = read_payload_field(ptr, 0, FieldKind::I32) as i32;
let b = read_payload_field(ptr, 8, FieldKind::I64) as i64;
let c = read_payload_field(ptr, 16, FieldKind::I8) as i8;
assert_eq!(a, -42);
assert_eq!(b, i64::MAX);
assert_eq!(c, -7);
dealloc_typed_enum(&layout, ptr);
}
}
#[test]
fn test_read_write_bool_and_ptr() {
let variants = vec![(
"V".to_string(),
vec![FieldKind::Bool, FieldKind::Ptr],
)];
let layout = compute_enum_layout("E", &variants);
let v = layout.variant_layout(0).unwrap();
assert_eq!(v.field_offsets, vec![0, 8]);
let ptr = alloc_typed_enum(&layout);
unsafe {
write_tag(ptr, 0);
write_payload_field(ptr, 0, FieldKind::Bool, 1);
let fake_ptr_bits: u64 = 0xDEAD_BEEF_CAFE_F00D;
write_payload_field(ptr, 8, FieldKind::Ptr, fake_ptr_bits);
assert_eq!(read_payload_field(ptr, 0, FieldKind::Bool), 1);
assert_eq!(read_payload_field(ptr, 8, FieldKind::Ptr), fake_ptr_bits);
write_payload_field(ptr, 0, FieldKind::Bool, 0xFF_FF_FF_FF);
assert_eq!(read_payload_field(ptr, 0, FieldKind::Bool), 1);
write_payload_field(ptr, 0, FieldKind::Bool, 0);
assert_eq!(read_payload_field(ptr, 0, FieldKind::Bool), 0);
dealloc_typed_enum(&layout, ptr);
}
}
#[test]
fn test_alloc_zero_payload() {
let variants = vec![
("Red".to_string(), vec![]),
("Green".to_string(), vec![]),
("Blue".to_string(), vec![]),
];
let layout = compute_enum_layout("Color", &variants);
let ptr = alloc_typed_enum(&layout);
unsafe {
write_tag(ptr, 2);
assert_eq!(read_tag(ptr), 2);
dealloc_typed_enum(&layout, ptr);
}
}
}