use crate::schema::{FieldDef, StructMode, Type};
const CLASS_SIZES: [u32; 4] = [8, 4, 2, 1];
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StructLayout {
Fixed(FixedLayout),
Packed(PackedLayout),
}
impl StructLayout {
pub fn align(&self) -> u32 {
match self {
StructLayout::Fixed(f) => f.align,
StructLayout::Packed(p) => p.align,
}
}
pub fn is_packed(&self) -> bool {
matches!(self, StructLayout::Packed(_))
}
pub fn as_fixed(&self) -> &FixedLayout {
match self {
StructLayout::Fixed(f) => f,
StructLayout::Packed(_) => panic!("expected a fixed-layout struct, found packed"),
}
}
pub fn as_packed(&self) -> &PackedLayout {
match self {
StructLayout::Packed(p) => p,
StructLayout::Fixed(_) => panic!("expected a packed struct, found fixed"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FixedLayout {
pub bitmap_bytes: u32,
pub slots: Vec<u32>,
pub size: u32,
pub align: u32,
pub dense: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PackedLayout {
pub bitmap_bytes: u32,
pub data_start: u32,
pub align: u32,
pub fields: Vec<PackedField>,
pub class_masks: [u64; 4],
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PackedField {
pub size: u32,
pub align: u32,
pub class: usize,
pub same_low_mask: u64,
}
impl PackedLayout {
pub fn field_offset(&self, bitmap: u64, pos: usize) -> u32 {
let f = &self.fields[pos];
let mut off = self.data_start;
for (&mask, &size) in self
.class_masks
.iter()
.zip(CLASS_SIZES.iter())
.take(f.class)
{
off += (bitmap & mask).count_ones() * size;
}
off += (bitmap & f.same_low_mask).count_ones() * f.size;
off
}
pub fn block_size(&self, bitmap: u64) -> u32 {
let mut off = self.data_start;
for (&mask, &size) in self.class_masks.iter().zip(CLASS_SIZES.iter()) {
off += (bitmap & mask).count_ones() * size;
}
align_up(off, self.align)
}
}
pub fn align_up(x: u32, align: u32) -> u32 {
debug_assert!(align.is_power_of_two());
(x + align - 1) & !(align - 1)
}
pub fn slot_size_align(ty: &Type) -> (u32, u32) {
match ty {
Type::Bool | Type::U8 | Type::I8 => (1, 1),
Type::U16 | Type::I16 => (2, 2),
Type::U32 | Type::I32 | Type::F32 | Type::Enum(_) => (4, 4),
Type::U64 | Type::I64 | Type::F64 => (8, 8),
Type::String
| Type::Bytes
| Type::List(_)
| Type::Struct(_)
| Type::Map(_, _)
| Type::Union(_) => (4, 4),
}
}
pub fn union_payload_offset(variant: &Type) -> u32 {
let (_, align) = slot_size_align(variant);
align_up(4, align)
}
pub fn map_entry_layout(key: &Type, value: &Type) -> FixedLayout {
let fields = [
FieldDef {
id: 0,
name: String::new(),
ty: key.clone(),
default: None,
},
FieldDef {
id: 1,
name: String::new(),
ty: value.clone(),
default: None,
},
];
compute_fixed(&fields, true)
}
fn class_index(size: u32) -> usize {
match size {
8 => 0,
4 => 1,
2 => 2,
_ => 3,
}
}
pub fn compute(fields: &[FieldDef], mode: StructMode) -> StructLayout {
match mode {
StructMode::Packed => StructLayout::Packed(compute_packed(fields)),
StructMode::Dense => StructLayout::Fixed(compute_fixed(fields, true)),
StructMode::Sparse => StructLayout::Fixed(compute_fixed(fields, false)),
}
}
fn compute_fixed(fields: &[FieldDef], dense: bool) -> FixedLayout {
let n = fields.len();
let bitmap_bytes = if dense { 0 } else { (n as u32).div_ceil(8) };
let mut order: Vec<usize> = (0..n).collect();
order.sort_by_key(|&i| {
(
std::cmp::Reverse(slot_size_align(&fields[i].ty).1),
fields[i].id,
)
});
let mut cursor = bitmap_bytes;
let mut slots = vec![0u32; n];
let mut max_align = 1u32;
for i in order {
let (size, align) = slot_size_align(&fields[i].ty);
max_align = max_align.max(align);
cursor = align_up(cursor, align);
slots[i] = cursor;
cursor += size;
}
FixedLayout {
bitmap_bytes,
slots,
size: align_up(cursor, max_align),
align: max_align,
dense,
}
}
fn compute_packed(fields: &[FieldDef]) -> PackedLayout {
let n = fields.len();
debug_assert!(n <= 64, "packed structs are limited to 64 fields");
let bitmap_bytes = (n as u32).div_ceil(8);
let mut class_masks = [0u64; 4];
let mut sizes_aligns = Vec::with_capacity(n);
let mut max_align = 1u32;
for (p, f) in fields.iter().enumerate() {
let (size, align) = slot_size_align(&f.ty);
max_align = max_align.max(align);
let class = class_index(size);
class_masks[class] |= 1u64 << p;
sizes_aligns.push((size, align, class));
}
let align = max_align;
let data_start = align_up(bitmap_bytes, align);
let mut pfields = Vec::with_capacity(n);
for (p, &(size, align, class)) in sizes_aligns.iter().enumerate() {
let low_bits = if p == 0 { 0 } else { (1u64 << p) - 1 };
pfields.push(PackedField {
size,
align,
class,
same_low_mask: class_masks[class] & low_bits,
});
}
PackedLayout {
bitmap_bytes,
data_start,
align,
fields: pfields,
class_masks,
}
}