use wasm_encoder::ValType;
fn val_type_byte_size(vt: &ValType) -> u32 {
match vt {
ValType::I32 | ValType::F32 => 4,
ValType::I64 | ValType::F64 => 8,
ValType::V128 => 16,
ValType::Ref(_) => 4,
}
}
fn align_to_val(offset: u32, align: u32) -> u32 {
offset.div_ceil(align) * align
}
const EVENT_RECORD_SHAPE: &[ValType] = &[ValType::I32, ValType::I32];
const BLOCK_RESULT_SHAPE: &[ValType] = &[ValType::I32];
pub(crate) struct MemoryLayoutBuilder {
name_cursor: u32,
post_name_cursor: u32,
}
impl MemoryLayoutBuilder {
pub fn new(total_name_bytes: u32) -> Self {
let post_name_align = val_type_byte_size(&ValType::I32);
Self {
name_cursor: 0,
post_name_cursor: align_to_val(total_name_bytes, post_name_align),
}
}
pub fn alloc_name(&mut self, name_len: u32) -> u32 {
let off = self.name_cursor;
self.name_cursor += name_len;
off
}
pub fn alloc_event_slot(&mut self) -> u32 {
self.alloc_record(EVENT_RECORD_SHAPE)
}
pub fn alloc_block_result(&mut self) -> u32 {
self.alloc_record(BLOCK_RESULT_SHAPE)
}
pub fn finish_as_bump_start(self) -> u32 {
align_to_val(self.post_name_cursor, val_type_byte_size(&ValType::I64))
}
fn alloc_record(&mut self, flat: &[ValType]) -> u32 {
let size: u32 = flat.iter().map(val_type_byte_size).sum();
let align: u32 = flat.iter().map(val_type_byte_size).max().unwrap_or(1);
self.alloc_aligned(size, align)
}
pub fn alloc_aligned(&mut self, size: u32, align: u32) -> u32 {
let aligned = align_to_val(self.post_name_cursor, align);
self.post_name_cursor = aligned + size;
aligned
}
}
pub(crate) struct StaticLayout {
cursor: u32,
segments: Vec<(u32, Vec<u8>)>,
}
impl StaticLayout {
pub fn new() -> Self {
Self {
cursor: 0,
segments: Vec::new(),
}
}
pub fn place_data(&mut self, align: u32, bytes: &[u8]) -> (u32, usize) {
let offset = align_to_val(self.cursor, align);
if !bytes.is_empty() {
let coalesced = match self.segments.last_mut() {
Some(last) if last.0 + last.1.len() as u32 == offset => {
last.1.extend_from_slice(bytes);
true
}
_ => false,
};
if !coalesced {
self.segments.push((offset, bytes.to_vec()));
}
}
self.cursor = offset + bytes.len() as u32;
(offset, self.segments.len().saturating_sub(1))
}
pub fn reserve_scratch(&mut self, align: u32, size: u32) -> u32 {
let offset = align_to_val(self.cursor, align);
self.cursor = offset + size;
offset
}
pub fn end(&self) -> u32 {
self.cursor
}
pub fn into_segments(self) -> Vec<(u32, Vec<u8>)> {
self.segments
}
}
#[cfg(test)]
mod static_layout_tests {
use super::*;
#[test]
fn coalesces_adjacent_data() {
let mut l = StaticLayout::new();
let (a, ai) = l.place_data(1, b"abc");
let (b, bi) = l.place_data(1, b"de");
assert_eq!((a, b), (0, 3));
assert_eq!((ai, bi), (0, 0));
assert_eq!(l.end(), 5);
let segs = l.into_segments();
assert_eq!(segs.len(), 1);
assert_eq!(segs[0].0, 0);
assert_eq!(&segs[0].1[..], b"abcde");
}
#[test]
fn alignment_pads_cursor() {
let mut l = StaticLayout::new();
assert_eq!(l.place_data(1, b"x").0, 0); assert_eq!(l.place_data(4, b"yyyy").0, 4); assert_eq!(l.end(), 8);
}
#[test]
fn scratch_breaks_coalescing() {
let mut l = StaticLayout::new();
let (a, ai) = l.place_data(1, b"AAAA"); let scratch = l.reserve_scratch(1, 8); let (b, bi) = l.place_data(1, b"BB"); assert_eq!((a, scratch, b), (0, 4, 12));
assert_eq!((ai, bi), (0, 1));
let segs = l.into_segments();
assert_eq!(segs.len(), 2);
assert_eq!((segs[0].0, &segs[0].1[..]), (0, &b"AAAA"[..]));
assert_eq!((segs[1].0, &segs[1].1[..]), (12, &b"BB"[..]));
}
#[test]
fn aligned_data_after_scratch_does_not_coalesce() {
let mut l = StaticLayout::new();
l.place_data(1, b"AA"); l.reserve_scratch(8, 0); let (b, bi) = l.place_data(1, b"B"); assert_eq!(b, 8);
assert_eq!(bi, 1);
let segs = l.into_segments();
assert_eq!(segs.len(), 2);
}
}