use super::{
Access, Construct, Descriptor, EnumAccess, FieldAccess, Layout, MapAccess, MapStorage,
OptionAccess, Presence, RecordAccess, RecordByteOwnership, SequenceAccess, SequenceStorage,
Tag, Thunk, VariantAccess,
};
#[must_use]
pub fn scalar<SchemaRef>(schema: SchemaRef, size: usize, align: usize) -> Descriptor<SchemaRef> {
Descriptor {
schema,
layout: Layout { size, align },
access: Access::Scalar,
}
}
#[must_use]
pub fn unit<SchemaRef>(schema: SchemaRef) -> Descriptor<SchemaRef> {
scalar(schema, 0, 1)
}
#[must_use]
pub fn bool_<SchemaRef>(schema: SchemaRef) -> Descriptor<SchemaRef> {
scalar(schema, 1, 1)
}
#[must_use]
pub fn i64_<SchemaRef>(schema: SchemaRef) -> Descriptor<SchemaRef> {
scalar(schema, 8, 8)
}
#[must_use]
pub fn f64_<SchemaRef>(schema: SchemaRef) -> Descriptor<SchemaRef> {
scalar(schema, 8, 8)
}
#[must_use]
pub fn handle<SchemaRef>(schema: SchemaRef, target: SchemaRef) -> Descriptor<SchemaRef> {
Descriptor {
schema,
layout: Layout { size: 8, align: 8 },
access: Access::Handle { target },
}
}
#[must_use]
pub fn array_of<SchemaRef>(
schema: SchemaRef,
element: Descriptor<SchemaRef>,
count: usize,
) -> Descriptor<SchemaRef> {
let align = element.layout.align.max(1);
let stride = align_up(element.layout.size, align);
Descriptor {
schema,
layout: Layout {
size: stride * count,
align,
},
access: Access::Array {
element: Box::new(element),
count,
stride,
},
}
}
#[must_use]
pub fn sequence<SchemaRef>(
schema: SchemaRef,
element: Descriptor<SchemaRef>,
) -> Descriptor<SchemaRef> {
Descriptor {
schema,
layout: Layout { size: 0, align: 8 },
access: Access::Sequence(SequenceAccess {
element: Box::new(element),
storage: SequenceStorage::Thunk {
len: Thunk {
name: "sequence.len".to_string(),
},
get: Thunk {
name: "sequence.get".to_string(),
},
push: Thunk {
name: "sequence.push".to_string(),
},
},
}),
}
}
#[must_use]
pub fn map<SchemaRef>(
schema: SchemaRef,
key: Descriptor<SchemaRef>,
value: Descriptor<SchemaRef>,
) -> Descriptor<SchemaRef> {
Descriptor {
schema,
layout: Layout { size: 0, align: 8 },
access: Access::Map(MapAccess {
key: Box::new(key),
value: Box::new(value),
storage: MapStorage::Thunk {
len: Thunk {
name: "map.len".to_string(),
},
iterate: Thunk {
name: "map.iterate".to_string(),
},
insert: Thunk {
name: "map.insert".to_string(),
},
},
}),
}
}
#[must_use]
pub fn option<SchemaRef>(schema: SchemaRef, some: Descriptor<SchemaRef>) -> Descriptor<SchemaRef> {
Descriptor {
schema,
layout: Layout { size: 32, align: 8 },
access: Access::Option(OptionAccess {
presence: Presence::Tag {
offset: 0,
width: 8,
none_value: 0,
},
some: Box::new(some),
}),
}
}
#[must_use]
pub fn declared_struct<SchemaRef>(
schema: SchemaRef,
fields: Vec<Descriptor<SchemaRef>>,
) -> Descriptor<SchemaRef> {
let packed = pack(&fields.iter().map(|f| f.layout).collect::<Vec<_>>(), 0);
let layout = Layout {
size: align_up(packed.end, packed.align),
align: packed.align,
};
let field_accesses: Vec<FieldAccess<SchemaRef>> = fields
.into_iter()
.zip(&packed.offsets)
.map(|(descriptor, &offset)| FieldAccess {
offset,
descriptor,
default: None,
})
.collect();
let byte_ownership = RecordByteOwnership::from_record_layout(layout, &field_accesses);
Descriptor {
schema,
layout,
access: Access::Record(RecordAccess {
fields: field_accesses,
byte_ownership,
construct: Construct::InPlace,
}),
}
}
#[must_use]
pub fn declared_enum<SchemaRef>(
schema: SchemaRef,
variants: Vec<Vec<Descriptor<SchemaRef>>>,
) -> Descriptor<SchemaRef> {
if variants.is_empty() {
return Descriptor {
schema,
layout: Layout { size: 0, align: 1 },
access: Access::Enum(EnumAccess {
tag: Tag::Direct {
offset: 0,
width: 0,
},
variants: Vec::new(),
}),
};
}
let tag_width = tag_width_for(variants.len());
let payload_align = variants
.iter()
.flat_map(|fields| fields.iter().map(|f| f.layout.align))
.max()
.unwrap_or(1);
let payload_base = align_up(tag_width, payload_align);
let enum_align = tag_width.max(payload_align);
let mut max_end = tag_width;
let variant_accesses: Vec<VariantAccess<SchemaRef>> = variants
.into_iter()
.enumerate()
.map(|(index, fields)| {
let packed = pack(
&fields.iter().map(|f| f.layout).collect::<Vec<_>>(),
payload_base,
);
max_end = max_end.max(packed.end);
let field_accesses: Vec<FieldAccess<SchemaRef>> = fields
.into_iter()
.zip(&packed.offsets)
.map(|(descriptor, &offset)| FieldAccess {
offset,
descriptor,
default: None,
})
.collect();
let byte_ownership = RecordByteOwnership::fields_only(&field_accesses);
VariantAccess {
index: u32::try_from(index).expect("variant count fits u32"),
selector: index as u64,
payload: RecordAccess {
fields: field_accesses,
byte_ownership,
construct: Construct::InPlace,
},
}
})
.collect();
Descriptor {
schema,
layout: Layout {
size: align_up(max_end, enum_align),
align: enum_align,
},
access: Access::Enum(EnumAccess {
tag: Tag::Direct {
offset: 0,
width: tag_width,
},
variants: variant_accesses,
}),
}
}
#[must_use]
pub fn tag_width_for(variant_count: usize) -> usize {
let n = variant_count as u64;
if n <= 1 << 8 {
1
} else if n <= 1 << 16 {
2
} else if n <= 1 << 32 {
4
} else {
8
}
}
struct Packed {
offsets: Vec<usize>,
end: usize,
align: usize,
}
fn pack(layouts: &[Layout], base: usize) -> Packed {
let mut order: Vec<usize> = (0..layouts.len()).collect();
order.sort_by_key(|&i| (std::cmp::Reverse(layouts[i].align), i));
let mut offsets = vec![0usize; layouts.len()];
let mut cursor = base;
let mut align = 1usize;
for &i in &order {
let l = layouts[i];
align = align.max(l.align);
if l.size == 0 {
offsets[i] = base;
continue;
}
let offset = align_up(cursor, l.align.max(1));
offsets[i] = offset;
cursor = offset + l.size;
}
Packed {
offsets,
end: cursor,
align,
}
}
fn align_up(value: usize, align: usize) -> usize {
let align = align.max(1);
value.div_ceil(align) * align
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mem::ByteOwner;
fn offsets<S>(descriptor: &Descriptor<S>) -> Vec<usize> {
match &descriptor.access {
Access::Record(record) => record.fields.iter().map(|f| f.offset).collect(),
_ => panic!("expected a record"),
}
}
#[test]
fn structs_pack_by_descending_alignment_with_stable_declaration_indices() {
let s = declared_struct((), vec![i64_(()), bool_(()), i64_(()), bool_(())]);
assert_eq!(offsets(&s), vec![0, 16, 8, 17]);
assert_eq!(s.layout, Layout { size: 24, align: 8 });
}
#[test]
fn struct_padding_is_proven_not_assumed() {
let s = declared_struct((), vec![i64_(()), bool_(())]);
assert_eq!(s.layout, Layout { size: 16, align: 8 });
let Access::Record(record) = &s.access else {
panic!("record expected");
};
assert!(record.byte_ownership.is_padding_range(9, 7));
assert!(!record.byte_ownership.is_padding_range(0, 9));
assert_eq!(
record
.byte_ownership
.ranges
.iter()
.filter(|r| r.owner == ByteOwner::Padding)
.count(),
1
);
}
#[test]
fn zero_sized_fields_cost_nothing() {
let s = declared_struct((), vec![unit(()), bool_(()), unit(())]);
assert_eq!(s.layout, Layout { size: 1, align: 1 });
assert_eq!(offsets(&s), vec![0, 0, 0]);
}
#[test]
fn handles_are_word_sized_and_record_their_target_schema() {
let h = handle("TreeRef", "Tree");
assert_eq!(h.layout, Layout { size: 8, align: 8 });
let Access::Handle { target } = &h.access else {
panic!("handle access expected");
};
assert_eq!(*target, "Tree");
}
#[test]
fn enums_lead_with_the_smallest_fitting_tag() {
let e = declared_enum((), vec![vec![], vec![i64_(())], vec![bool_(()), bool_(())]]);
assert_eq!(e.layout, Layout { size: 16, align: 8 });
let Access::Enum(access) = &e.access else {
panic!("enum expected");
};
assert!(matches!(
access.tag,
Tag::Direct {
offset: 0,
width: 1
}
));
assert_eq!(access.variants.len(), 3);
let v1 = &access.variants[1];
assert_eq!(v1.selector, 1);
assert_eq!(v1.payload.fields[0].offset, 8);
let v2 = &access.variants[2];
assert_eq!(v2.payload.fields[0].offset, 8);
assert_eq!(v2.payload.fields[1].offset, 9);
}
#[test]
fn enum_variant_payloads_never_prove_tag_bytes_as_padding() {
let e = declared_enum((), vec![vec![i64_(())], vec![]]);
let Access::Enum(access) = &e.access else {
panic!("enum expected");
};
assert!(
!access.variants[0]
.payload
.byte_ownership
.is_padding_range(0, 1)
);
assert!(
!access.variants[1]
.payload
.byte_ownership
.is_padding_range(0, 1)
);
}
#[test]
fn tag_widths_grow_with_variant_count() {
assert_eq!(tag_width_for(1), 1);
assert_eq!(tag_width_for(256), 1);
assert_eq!(tag_width_for(257), 2);
assert_eq!(tag_width_for(1 << 16), 2);
assert_eq!(tag_width_for((1 << 16) + 1), 4);
}
#[test]
fn declared_types_nest_in_both_directions() {
let inner = declared_struct((), vec![bool_(()), i64_(())]);
assert_eq!(inner.layout, Layout { size: 16, align: 8 });
let outer = declared_struct((), vec![bool_(()), inner]);
assert_eq!(outer.layout, Layout { size: 24, align: 8 });
assert_eq!(offsets(&outer), vec![16, 0]);
}
#[test]
fn uninhabited_enums_are_zero_sized() {
let e = declared_enum((), Vec::<Vec<Descriptor<()>>>::new());
assert_eq!(e.layout, Layout { size: 0, align: 1 });
}
#[test]
fn layouts_are_deterministic() {
let a = declared_struct((), vec![i64_(()), bool_(()), f64_(())]);
let b = declared_struct((), vec![i64_(()), bool_(()), f64_(())]);
assert_eq!(offsets(&a), offsets(&b));
assert_eq!(a.layout, b.layout);
}
}