use crate::format::checksum::checksum_metadata;
use crate::format::chunk_index::btree_v2::{Bt2Tree, BT2_TYPE_FHEAP_HUGE_INDIR};
use std::collections::HashMap;
use crate::format::fractal_heap::{
indirect_nrows, FractalHeapHeader, HeapParams, FHDB_SIGNATURE, FHIB_SIGNATURE,
};
use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
const HUGE_BT2_NODE_SIZE: u32 = 512;
const ID_FLAGS_MANAGED: u8 = 0x00;
const ID_FLAGS_HUGE: u8 = 0x10;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeapBlock {
pub addr: u64,
pub len: u64,
pub image: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuiltHeap {
pub header_addr: u64,
pub blocks: Vec<HeapBlock>,
pub ids: Vec<Vec<u8>>,
}
pub struct PlannedHeap {
header: FractalHeapHeader,
ctx: FormatContext,
header_addr: u64,
lengths: Vec<usize>,
ids: Vec<Vec<u8>>,
managed_meta: Vec<HeapBlock>,
huge_meta: Vec<HeapBlock>,
managed: ManagedPlan,
huge: Vec<HugeSlot>,
}
struct ManagedPlan {
built: Vec<DirectBlock>,
addrs: Vec<u64>,
slots: Vec<(usize, usize, usize)>,
overhead: usize,
}
struct HugeSlot {
addr: u64,
len: u64,
object: usize,
}
impl PlannedHeap {
pub fn ids(&self) -> &[Vec<u8>] {
&self.ids
}
pub fn header_addr(&self) -> u64 {
self.header_addr
}
pub fn finish(self, objects: &[Vec<u8>]) -> FormatResult<BuiltHeap> {
if objects.len() != self.lengths.len()
|| objects
.iter()
.zip(&self.lengths)
.any(|(o, &n)| o.len() != n)
{
return Err(FormatError::InvalidData(
"fractal heap objects do not match the lengths their layout was planned from"
.into(),
));
}
let Self {
header,
ctx,
header_addr,
ids,
managed_meta,
huge_meta,
managed,
huge,
..
} = self;
let mut blocks = managed_meta;
let mut images: Vec<Vec<u8>> = managed
.built
.iter()
.map(|b| direct_prefix(&header, &ctx, header_addr, b.block_off, b.size))
.collect();
for &(object, bi, off) in &managed.slots {
let start = managed.overhead + off;
images[bi][start..start + objects[object].len()].copy_from_slice(&objects[object]);
}
for (image, b) in images.iter_mut().zip(&managed.built) {
finish_direct_block(&header, &ctx, image, b.size as usize);
}
for (image, (&addr, b)) in images
.into_iter()
.zip(managed.addrs.iter().zip(&managed.built))
{
blocks.push(HeapBlock {
addr,
len: b.size,
image,
});
}
for slot in huge {
blocks.push(HeapBlock {
addr: slot.addr,
len: slot.len,
image: objects[slot.object].clone(),
});
}
blocks.extend(huge_meta);
let image = header.encode(&ctx);
blocks.insert(
0,
HeapBlock {
addr: header_addr,
len: image.len() as u64,
image,
},
);
Ok(BuiltHeap {
header_addr,
blocks,
ids,
})
}
}
pub fn build_heap(
params: &HeapParams,
ctx: &FormatContext,
objects: &[Vec<u8>],
alloc: &mut dyn FnMut(u64) -> u64,
) -> FormatResult<BuiltHeap> {
let lengths: Vec<usize> = objects.iter().map(Vec::len).collect();
plan_heap(params, ctx, &lengths, alloc)?.finish(objects)
}
pub fn plan_heap(
params: &HeapParams,
ctx: &FormatContext,
lengths: &[usize],
alloc: &mut dyn FnMut(u64) -> u64,
) -> FormatResult<PlannedHeap> {
let mut header = FractalHeapHeader::new(params, ctx);
let header_addr = alloc(FractalHeapHeader::encoded_size(ctx) as u64);
let mut ids: Vec<Vec<u8>> = vec![Vec::new(); lengths.len()];
let managed: Vec<usize> = (0..lengths.len())
.filter(|&i| (lengths[i] as u64) < params.max_man_size as u64)
.collect();
let huge: Vec<usize> = (0..lengths.len())
.filter(|&i| (lengths[i] as u64) >= params.max_man_size as u64)
.collect();
let mut builder = HeapBuilder {
ctx,
header_addr,
alloc,
blocks: Vec::new(),
};
let managed = builder.plan_managed(&mut header, lengths, &managed, &mut ids)?;
let managed_meta = std::mem::take(&mut builder.blocks);
let huge = builder.plan_huge(&mut header, lengths, &huge, &mut ids);
let huge_meta = builder.blocks;
Ok(PlannedHeap {
header,
ctx: *ctx,
header_addr,
lengths: lengths.to_vec(),
ids,
managed_meta,
huge_meta,
managed,
huge,
})
}
struct HeapBuilder<'a> {
ctx: &'a FormatContext,
header_addr: u64,
alloc: &'a mut dyn FnMut(u64) -> u64,
blocks: Vec<HeapBlock>,
}
fn direct_overhead(header: &FractalHeapHeader, ctx: &FormatContext) -> usize {
4 + 1
+ ctx.sizeof_addr as usize
+ header.heap_off_size as usize
+ if header.checksum_dblocks { 4 } else { 0 }
}
struct DirectBlock {
seq: usize,
size: u64,
block_off: u64,
used: usize,
}
fn direct_block_counts(header: &FractalHeapHeader) -> Vec<usize> {
let width = header.table_width as usize;
let mut counts = Vec::with_capacity(header.row_block_size.len() + 1);
counts.push(0);
for row in 0..header.row_block_size.len() {
let this_row = if row < header.max_direct_rows as usize {
width
} else {
let child = indirect_nrows(header, header.row_block_size[row]) as usize;
width * counts[child]
};
counts.push(counts[row] + this_row);
}
counts
}
fn row_span(header: &FractalHeapHeader, nrows: usize) -> u64 {
match header.row_block_off.get(nrows) {
Some(&off) => off,
None => {
let last = header.row_block_off.len() - 1;
header.row_block_off[last] + header.row_block_size[last] * header.table_width as u64
}
}
}
fn nth_direct_block(
header: &FractalHeapHeader,
counts: &[usize],
base_off: u64,
nrows: usize,
mut n: usize,
) -> Option<(u64, u64)> {
let width = header.table_width as usize;
for row in 0..nrows.min(header.row_block_size.len()) {
let size = header.row_block_size[row];
let row_base = base_off + header.row_block_off[row];
if row < header.max_direct_rows as usize {
if n < width {
return Some((size, row_base + size * n as u64));
}
n -= width;
} else {
let child_nrows = indirect_nrows(header, size) as usize;
let per_child = counts[child_nrows];
for col in 0..width {
if n < per_child {
return nth_direct_block(
header,
counts,
row_base + size * col as u64,
child_nrows,
n,
);
}
n -= per_child;
}
}
}
None
}
impl HeapBuilder<'_> {
fn plan_managed(
&mut self,
header: &mut FractalHeapHeader,
lengths: &[usize],
managed: &[usize],
ids: &mut [Vec<u8>],
) -> FormatResult<ManagedPlan> {
if managed.is_empty() {
return Ok(ManagedPlan {
built: Vec::new(),
addrs: Vec::new(),
slots: Vec::new(),
overhead: 0,
});
}
let overhead = direct_overhead(header, self.ctx);
let counts = direct_block_counts(header);
let root_rows = header.row_block_size.len();
let mut built: Vec<DirectBlock> = Vec::new();
let mut placement: Vec<(usize, usize)> = Vec::with_capacity(managed.len());
let mut cursor = 0usize;
for &i in managed {
let len = lengths[i];
loop {
let Some((size, block_off)) =
nth_direct_block(header, &counts, 0, root_rows, cursor)
else {
return Err(FormatError::UnsupportedFeature(format!(
"fractal heap needs more than the {} bytes its {}-bit address space \
addresses",
row_span(header, root_rows),
header.max_heap_size_bits
)));
};
let capacity = size as usize - overhead;
if len > capacity {
cursor += 1;
continue;
}
let last = built.len().wrapping_sub(1);
match built.last().map(|b| (b.seq, b.used)) {
Some((seq, used)) if seq == cursor && used + len <= capacity => {
placement.push((last, used));
built[last].used += len;
break;
}
Some((seq, _)) if seq == cursor => {
cursor += 1;
continue;
}
_ => built.push(DirectBlock {
seq: cursor,
size,
block_off,
used: 0,
}),
}
}
}
let addrs: Vec<u64> = built.iter().map(|b| (self.alloc)(b.size)).collect();
let mut slots = Vec::with_capacity(managed.len());
for (&i, &(bi, off)) in managed.iter().zip(&placement) {
let start = overhead + off;
slots.push((i, bi, off));
ids[i] = managed_id(
header,
built[bi].block_off + start as u64,
lengths[i] as u64,
);
}
let last = built.last().expect("a managed object built a block");
let root_is_direct = built.len() == 1 && last.block_off == 0;
if root_is_direct {
header.table_addr = addrs[0];
header.curr_root_rows = 0;
header.man_size = header.start_block_size;
header.man_iter_off = 0;
} else {
let end = last.block_off + last.size;
let nrows = (1..=root_rows)
.find(|&r| row_span(header, r) >= end)
.expect("the placement loop never runs past the addressable rows");
let block_addrs: HashMap<u64, u64> = built
.iter()
.zip(&addrs)
.map(|(b, &addr)| (b.block_off, addr))
.collect();
let iblock_addr = self.encode_indirect(header, &block_addrs, 0, nrows);
header.table_addr = iblock_addr;
header.curr_root_rows = nrows as u16;
header.man_size = row_span(header, nrows);
header.man_iter_off = end;
}
header.man_alloc_size = built.iter().map(|b| b.size).sum();
header.man_nobjs = managed.len() as u64;
header.total_man_free = 0;
header.fs_addr = UNDEF_ADDR;
Ok(ManagedPlan {
built,
addrs,
slots,
overhead,
})
}
fn encode_indirect(
&mut self,
header: &FractalHeapHeader,
block_addrs: &HashMap<u64, u64>,
base_off: u64,
nrows: usize,
) -> u64 {
let sa = self.ctx.sizeof_addr as usize;
let width = header.table_width as usize;
let entries = nrows * width;
let mut image =
Vec::with_capacity(4 + 1 + sa + header.heap_off_size as usize + entries * sa + 4);
image.extend_from_slice(&FHIB_SIGNATURE);
image.push(0); image.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
image.extend_from_slice(&base_off.to_le_bytes()[..header.heap_off_size as usize]);
for row in 0..nrows {
let size = header.row_block_size[row];
for col in 0..width {
let off = base_off + header.row_block_off[row] + size * col as u64;
let addr = if row < header.max_direct_rows as usize {
block_addrs.get(&off).copied().unwrap_or(UNDEF_ADDR)
} else if block_addrs
.keys()
.any(|&b| b >= off && b < off.saturating_add(size))
{
self.encode_indirect(
header,
block_addrs,
off,
indirect_nrows(header, size) as usize,
)
} else {
UNDEF_ADDR
};
image.extend_from_slice(&addr.to_le_bytes()[..sa]);
}
}
let cksum = checksum_metadata(&image);
image.extend_from_slice(&cksum.to_le_bytes());
let len = image.len() as u64;
let addr = (self.alloc)(len);
self.blocks.push(HeapBlock { addr, len, image });
addr
}
fn plan_huge(
&mut self,
header: &mut FractalHeapHeader,
lengths: &[usize],
huge: &[usize],
ids: &mut [Vec<u8>],
) -> Vec<HugeSlot> {
if huge.is_empty() {
return Vec::new();
}
let sa = self.ctx.sizeof_addr as usize;
let ss = self.ctx.sizeof_size as usize;
let record_size = (sa + ss + ss) as u16;
let mut slots = Vec::with_capacity(huge.len());
let mut records = Vec::with_capacity(huge.len() * record_size as usize);
for (n, &i) in huge.iter().enumerate() {
let len = lengths[i] as u64;
let addr = (self.alloc)(len);
let huge_id = n as u64 + 1;
slots.push(HugeSlot {
addr,
len,
object: i,
});
records.extend_from_slice(&addr.to_le_bytes()[..sa]);
records.extend_from_slice(&len.to_le_bytes()[..ss]);
records.extend_from_slice(&huge_id.to_le_bytes()[..ss]);
let mut id = Vec::with_capacity(header.id_len as usize);
id.push(ID_FLAGS_HUGE);
id.extend_from_slice(&huge_id.to_le_bytes()[..header.huge_id_size as usize]);
id.resize(header.id_len as usize, 0);
ids[i] = id;
header.huge_size += len;
}
header.huge_nobjs = huge.len() as u64;
header.huge_next_id = huge.len() as u64;
let tree = Bt2Tree::build(
BT2_TYPE_FHEAP_HUGE_INDIR,
record_size,
HUGE_BT2_NODE_SIZE,
self.ctx.sizeof_addr,
&records,
);
let bt2_addr = (self.alloc)(tree.header(UNDEF_ADDR).encoded_size(self.ctx) as u64);
let node_addrs: Vec<u64> = tree
.nodes
.iter()
.map(|_| (self.alloc)(tree.node_size as u64))
.collect();
for (image, &addr) in tree
.encode(self.ctx, &node_addrs)
.into_iter()
.zip(&node_addrs)
{
self.blocks.push(HeapBlock {
addr,
len: tree.node_size as u64,
image,
});
}
let root_addr = node_addrs.last().copied().unwrap_or(UNDEF_ADDR);
let image = tree.header(root_addr).encode(self.ctx);
self.blocks.push(HeapBlock {
addr: bt2_addr,
len: image.len() as u64,
image,
});
header.huge_bt2_addr = bt2_addr;
slots
}
}
fn direct_prefix(
header: &FractalHeapHeader,
ctx: &FormatContext,
header_addr: u64,
block_off: u64,
size: u64,
) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let mut image = vec![0u8; size as usize];
image[0..4].copy_from_slice(&FHDB_SIGNATURE);
image[4] = 0; image[5..5 + sa].copy_from_slice(&header_addr.to_le_bytes()[..sa]);
let off_at = 5 + sa;
let off_size = header.heap_off_size as usize;
image[off_at..off_at + off_size].copy_from_slice(&block_off.to_le_bytes()[..off_size]);
image
}
fn finish_direct_block(
header: &FractalHeapHeader,
ctx: &FormatContext,
image: &mut [u8],
_size: usize,
) {
if !header.checksum_dblocks {
return;
}
let at = 4 + 1 + ctx.sizeof_addr as usize + header.heap_off_size as usize;
let cksum = checksum_metadata(image);
image[at..at + 4].copy_from_slice(&cksum.to_le_bytes());
}
fn managed_id(header: &FractalHeapHeader, offset: u64, length: u64) -> Vec<u8> {
let mut id = Vec::with_capacity(header.id_len as usize);
id.push(ID_FLAGS_MANAGED);
id.extend_from_slice(&offset.to_le_bytes()[..header.heap_off_size as usize]);
id.extend_from_slice(&length.to_le_bytes()[..header.heap_len_size as usize]);
id.resize(header.id_len as usize, 0);
id
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::fractal_heap::{collect_managed_blocks, read_heap_object, HeapId};
use crate::format::BlockReader;
struct MemFile {
bytes: Vec<u8>,
}
impl MemFile {
fn new() -> Self {
Self { bytes: vec![0; 16] }
}
fn alloc(&mut self, len: u64) -> u64 {
let addr = self.bytes.len() as u64;
self.bytes.resize(self.bytes.len() + len as usize, 0);
addr
}
fn put(&mut self, block: &HeapBlock) {
let at = block.addr as usize;
self.bytes[at..at + block.image.len()].copy_from_slice(&block.image);
}
}
impl BlockReader for MemFile {
fn read_block(&mut self, offset: u64, len: usize) -> FormatResult<Vec<u8>> {
let start = offset as usize;
if start > self.bytes.len() {
return Err(FormatError::BufferTooShort {
needed: start,
available: self.bytes.len(),
});
}
let end = (start + len).min(self.bytes.len());
Ok(self.bytes[start..end].to_vec())
}
}
fn ctx() -> FormatContext {
FormatContext {
sizeof_addr: 8,
sizeof_size: 8,
}
}
fn round_trip(objects: &[Vec<u8>]) -> Vec<Vec<u8>> {
let ctx = ctx();
let params = HeapParams::object_header();
let mut file = MemFile::new();
let built = {
let mut alloc = |len: u64| file.alloc(len);
build_heap(¶ms, &ctx, objects, &mut alloc).unwrap()
};
for block in &built.blocks {
file.put(block);
}
let heap_buf = file.read_block(built.header_addr, 512).unwrap();
let header = FractalHeapHeader::decode(&heap_buf, &ctx).unwrap();
let blocks = collect_managed_blocks(&header, &ctx, &mut file).unwrap();
built
.ids
.iter()
.map(|id| {
let parsed = HeapId::parse(id, &header, &ctx).unwrap();
read_heap_object(&parsed, &header, &ctx, &blocks, &mut file).unwrap()
})
.collect()
}
fn obj(seed: u8, len: usize) -> Vec<u8> {
(0..len).map(|i| seed.wrapping_add(i as u8)).collect()
}
#[test]
fn a_single_object_round_trips_through_a_root_direct_block() {
let objects = vec![obj(1, 40)];
assert_eq!(round_trip(&objects), objects);
}
#[test]
fn the_root_stays_a_direct_block_while_one_block_holds_everything() {
let ctx = ctx();
let params = HeapParams::object_header();
let objects: Vec<Vec<u8>> = (0..10).map(|i| obj(i, 33)).collect();
let mut file = MemFile::new();
let built = {
let mut alloc = |len: u64| file.alloc(len);
build_heap(¶ms, &ctx, &objects, &mut alloc).unwrap()
};
for block in &built.blocks {
file.put(block);
}
let heap_buf = file.read_block(built.header_addr, 512).unwrap();
let header = FractalHeapHeader::decode(&heap_buf, &ctx).unwrap();
assert_eq!(header.curr_root_rows, 0);
assert_eq!(header.man_size, 1024);
assert_eq!(header.man_alloc_size, 1024);
assert_eq!(header.man_nobjs, 10);
assert_eq!(round_trip(&objects), objects);
}
#[test]
fn objects_past_one_block_grow_a_root_indirect_block() {
let objects: Vec<Vec<u8>> = (0..60).map(|i| obj(i, 100)).collect();
let ctx = ctx();
let params = HeapParams::object_header();
let mut file = MemFile::new();
let built = {
let mut alloc = |len: u64| file.alloc(len);
build_heap(¶ms, &ctx, &objects, &mut alloc).unwrap()
};
for block in &built.blocks {
file.put(block);
}
let heap_buf = file.read_block(built.header_addr, 512).unwrap();
let header = FractalHeapHeader::decode(&heap_buf, &ctx).unwrap();
assert!(header.curr_root_rows >= 2, "{}", header.curr_root_rows);
assert_eq!(round_trip(&objects), objects);
}
#[test]
fn objects_past_the_direct_rows_grow_child_indirect_blocks() {
let objects: Vec<Vec<u8>> = (0..130).map(|i| obj(i as u8, 4000)).collect();
let ctx = ctx();
let params = HeapParams::object_header();
let mut file = MemFile::new();
let built = {
let mut alloc = |len: u64| file.alloc(len);
build_heap(¶ms, &ctx, &objects, &mut alloc).unwrap()
};
for block in &built.blocks {
file.put(block);
}
let heap_buf = file.read_block(built.header_addr, 512).unwrap();
let header = FractalHeapHeader::decode(&heap_buf, &ctx).unwrap();
assert!(
header.curr_root_rows as u32 > header.max_direct_rows,
"{} rows does not reach the indirect ones ({} direct)",
header.curr_root_rows,
header.max_direct_rows
);
assert_eq!(header.man_nobjs, 130);
assert_eq!(round_trip(&objects), objects);
}
#[test]
fn a_heap_deep_enough_nests_indirect_blocks_two_levels() {
let objects: Vec<Vec<u8>> = (0..1000).map(|i| obj(i as u8, 4000)).collect();
let ctx = ctx();
let params = HeapParams::object_header();
let mut file = MemFile::new();
let built = {
let mut alloc = |len: u64| file.alloc(len);
build_heap(¶ms, &ctx, &objects, &mut alloc).unwrap()
};
for block in &built.blocks {
file.put(block);
}
let heap_buf = file.read_block(built.header_addr, 512).unwrap();
let header = FractalHeapHeader::decode(&heap_buf, &ctx).unwrap();
assert!(header.curr_root_rows >= 12, "{}", header.curr_root_rows);
assert_eq!(round_trip(&objects), objects);
}
#[test]
fn an_object_too_big_for_a_managed_block_goes_huge() {
let objects = vec![obj(7, 4096), obj(9, 20), obj(3, 100_000)];
let ctx = ctx();
let params = HeapParams::object_header();
let mut file = MemFile::new();
let built = {
let mut alloc = |len: u64| file.alloc(len);
build_heap(¶ms, &ctx, &objects, &mut alloc).unwrap()
};
for block in &built.blocks {
file.put(block);
}
let heap_buf = file.read_block(built.header_addr, 512).unwrap();
let header = FractalHeapHeader::decode(&heap_buf, &ctx).unwrap();
assert_eq!(header.huge_nobjs, 2);
assert_eq!(header.huge_size, 4096 + 100_000);
assert_eq!(header.man_nobjs, 1);
assert_ne!(header.huge_bt2_addr, UNDEF_ADDR);
assert!(matches!(
HeapId::parse(&built.ids[0], &header, &ctx).unwrap(),
HeapId::HugeIndirect { .. }
));
assert!(matches!(
HeapId::parse(&built.ids[1], &header, &ctx).unwrap(),
HeapId::Managed { .. }
));
assert_eq!(round_trip(&objects), objects);
}
#[test]
fn a_heap_of_only_huge_objects_has_no_managed_blocks() {
let objects: Vec<Vec<u8>> = (0..3).map(|i| obj(i, 5000)).collect();
assert_eq!(round_trip(&objects), objects);
}
#[test]
fn an_object_larger_than_a_row_skips_to_a_row_that_fits() {
let objects = vec![obj(1, 50), obj(2, 3000), obj(3, 50)];
assert_eq!(round_trip(&objects), objects);
}
#[test]
fn header_round_trips_with_its_derived_widths() {
let ctx = ctx();
let params = HeapParams::object_header();
let built = FractalHeapHeader::new(¶ms, &ctx);
let decoded = FractalHeapHeader::decode(&built.encode(&ctx), &ctx).unwrap();
assert_eq!(decoded.heap_off_size, 5);
assert_eq!(decoded.heap_len_size, 2);
assert_eq!(decoded.huge_id_size, 7);
assert!(!decoded.huge_ids_direct);
assert_eq!(decoded.max_direct_rows, 8);
assert_eq!(decoded.row_block_size[..4], [1024, 1024, 2048, 4096]);
assert_eq!(decoded.row_block_off[..4], [0, 4096, 8192, 16384]);
assert_eq!(decoded.start_root_rows, params.start_root_rows);
}
}