use anyhow::{bail, Result};
use wit_parser::{Function as WitFunction, Type};
use super::super::abi::emit::BlobSlice;
use super::super::mem_layout::StaticLayout;
use super::blob::{resolve, NameInterner, RecordWriter, RelocPlan, Segment, SymRef, SymbolBases};
use super::lift::plan::Cell;
use super::lift::{
back_fill_record_fields_ptrs, build_char_scratch_map, build_enum_info_blob,
build_flags_info_maps, build_handle_info_maps, build_record_info_maps,
build_tuple_indices_blob, build_variant_info_maps, char_scratch_sizes, flags_scratch_sizes,
fold_cell_side_data, CellFillSources, CellSideData, CharScratch, CharScratchMaps,
FlagsInfoMaps, FlagsRuntimeFill, HandleInfoMaps, HandleRuntimeFill, InfoCounts, ParamLayout,
RecordInfoMaps, ResultLayout, ResultLift, ResultSource, ResultSourceLayout, SideTableBlob,
TupleIndicesBlob, VariantInfoMaps,
};
use super::schema::{
SchemaLayouts, FIELD_NAME, FIELD_TREE, ON_RET_CALL, ON_RET_RESULT, TREE_CELLS, TREE_ENUM_INFOS,
TREE_FLAGS_INFOS, TREE_HANDLE_INFOS, TREE_RECORD_INFOS, TREE_ROOT, TREE_VARIANT_INFOS,
};
use super::{AfterSetup, FuncClassified, FuncDispatch, FuncShape};
const EVENT_SLOT_SIZE: u32 = 8;
const EVENT_SLOT_ALIGN: u32 = 4;
const LAYOUT_SIZE_BUDGET: u32 = i32::MAX as u32;
#[cfg(not(test))]
pub(in crate::adapter::tier2) const MAX_FLAT_SLOTS_PER_FN: u32 = 1 << 16;
#[cfg(test)]
pub(in crate::adapter::tier2) const MAX_FLAT_SLOTS_PER_FN: u32 = 16;
#[cfg(not(test))]
pub(in crate::adapter::tier2) const MAX_CELLS_PER_PARAM: u32 = 1 << 20;
#[cfg(test)]
pub(in crate::adapter::tier2) const MAX_CELLS_PER_PARAM: u32 = 8;
fn check_layout_budget(per_func: &[FuncClassified]) -> Result<()> {
for (fn_idx, fd) in per_func.iter().enumerate() {
for (p_idx, p) in fd.params.iter().enumerate() {
if p.plan.flat_slot_count > MAX_FLAT_SLOTS_PER_FN {
bail!(
"fn[{fn_idx}] param[{p_idx}]: flat-slot count {} exceeds budget {MAX_FLAT_SLOTS_PER_FN}",
p.plan.flat_slot_count,
);
}
if p.plan.cell_count() > MAX_CELLS_PER_PARAM {
bail!(
"fn[{fn_idx}] param[{p_idx}]: cell count {} exceeds budget {MAX_CELLS_PER_PARAM}",
p.plan.cell_count(),
);
}
}
if let Some(rl) = fd.result_lift.as_ref() {
let cells = rl.compound().map_or(1, |c| c.plan.cell_count());
if cells > MAX_CELLS_PER_PARAM {
bail!(
"fn[{fn_idx}] result: cell count {cells} exceeds budget {MAX_CELLS_PER_PARAM}"
);
}
}
}
Ok(())
}
struct SingleCellFills<'a> {
flags_fill: &'a Option<FlagsRuntimeFill>,
char_scratch: &'a Option<i32>,
handle_fill: &'a Option<HandleRuntimeFill>,
}
fn single_cell_side_data(cell: &Cell, fills: &SingleCellFills<'_>) -> CellSideData {
match cell {
Cell::Flags { .. } => CellSideData::Flags(Box::new(
fills
.flags_fill
.clone()
.expect("flags-info fill must exist"),
)),
Cell::Char { .. } => CellSideData::Char {
scratch: CharScratch::Static {
scratch_addr: fills.char_scratch.expect("char-info scratch must exist"),
},
},
Cell::Handle { .. } => CellSideData::Handle(Box::new(
fills
.handle_fill
.clone()
.expect("handle-info fill must exist"),
)),
Cell::Bool { .. }
| Cell::IntegerSignExt { .. }
| Cell::IntegerZeroExt { .. }
| Cell::Integer64 { .. }
| Cell::FloatingF32 { .. }
| Cell::FloatingF64 { .. }
| Cell::Text { .. }
| Cell::Bytes { .. }
| Cell::EnumCase { .. } => CellSideData::None,
Cell::RecordOf { .. }
| Cell::TupleOf { .. }
| Cell::Option { .. }
| Cell::Result { .. }
| Cell::Variant { .. }
| Cell::ListOf { .. } => {
unreachable!("single_cell_side_data reached unsupported result Cell {cell:?}")
}
}
}
pub(super) struct StaticDataPlan {
pub(super) bump_start: u32,
pub(super) event_ptr: i32,
pub(super) hook_params_ptr: Option<u32>,
pub(super) data_segments: Vec<(u32, Vec<u8>)>,
}
#[derive(Clone, Copy, Default)]
struct FieldSideTables {
enum_infos: BlobSlice,
flags_infos: BlobSlice,
record_infos: BlobSlice,
variant_infos: BlobSlice,
handle_infos: BlobSlice,
}
impl FieldSideTables {
fn write_to_tree(&self, blob: &mut [u8], tree: &RecordWriter) {
tree.write_slice(blob, TREE_ENUM_INFOS, self.enum_infos);
tree.write_slice(blob, TREE_FLAGS_INFOS, self.flags_infos);
tree.write_slice(blob, TREE_RECORD_INFOS, self.record_infos);
tree.write_slice(blob, TREE_VARIANT_INFOS, self.variant_infos);
tree.write_slice(blob, TREE_HANDLE_INFOS, self.handle_infos);
}
}
fn write_field_record(
blob: &mut Vec<u8>,
schema: &SchemaLayouts,
cells: BlobSlice,
root: u32,
name: BlobSlice,
side_tables: FieldSideTables,
) {
let field = RecordWriter::extend_zero(blob, &schema.field_layout);
field.write_slice(blob, FIELD_NAME, name);
let tree = field.nested(FIELD_TREE, &schema.tree_layout);
tree.write_slice(blob, TREE_CELLS, cells);
side_tables.write_to_tree(blob, &tree);
tree.write_i32(blob, TREE_ROOT, root as i32);
}
fn build_fields_blob(
per_func: &[FuncClassified],
schema: &SchemaLayouts,
param_side_tables: &[Vec<FieldSideTables>],
) -> Vec<u8> {
let mut blob: Vec<u8> = Vec::new();
for (fn_idx, fd) in per_func.iter().enumerate() {
for (i, p) in fd.params.iter().enumerate() {
write_field_record(
&mut blob,
schema,
BlobSlice {
off: 0,
len: p.plan.cell_count(),
},
p.plan.root(),
p.name,
param_side_tables[fn_idx][i],
);
}
}
blob
}
fn build_after_params_blob(
per_func: &[FuncClassified],
schema: &SchemaLayouts,
iface_name: BlobSlice,
result_side_tables: &[FieldSideTables],
) -> Vec<u8> {
let Some(after_layout) = schema.after_hook.as_ref().map(|h| &h.params_layout) else {
return Vec::new();
};
let mut blob: Vec<u8> = Vec::new();
for (fn_idx, fd) in per_func.iter().enumerate() {
let entry = RecordWriter::extend_zero(&mut blob, after_layout);
schema.callid_layout.store_names_in_blob(
&mut blob,
entry.field_offset(ON_RET_CALL),
iface_name,
BlobSlice {
off: fd.fn_name_offset as u32,
len: fd.fn_name_len as u32,
},
);
if fd.result_lift.is_some() {
entry.write_option_some(&mut blob, ON_RET_RESULT);
let tree_base = entry.field_offset(ON_RET_RESULT) + schema.option_payload_off as usize;
let tree = RecordWriter::at(&schema.tree_layout, tree_base);
let (cells_len, root) = fd
.result_lift
.as_ref()
.and_then(|rl| rl.compound())
.map_or((1, 0), |c| (c.plan.cell_count(), c.plan.root()));
tree.write_slice(
&mut blob,
TREE_CELLS,
BlobSlice {
off: 0,
len: cells_len,
},
);
result_side_tables[fn_idx].write_to_tree(&mut blob, &tree);
tree.write_i32(&mut blob, TREE_ROOT, root as i32);
} else {
entry.write_option_none(&mut blob, ON_RET_RESULT);
}
}
blob
}
fn place_segment(
layout: &mut StaticLayout,
symbols: &mut SymbolBases,
relocs: &mut RelocPlan,
seg: Segment,
) -> u32 {
let (base, idx) = layout.place_data(seg.align, &seg.bytes);
symbols.set(seg.id, base);
relocs.record_segment(idx, base, seg.relocs);
base
}
fn resolve_param_result_ranges(
symbols: &SymbolBases,
per_param_sym: Vec<Vec<Option<SymRef>>>,
per_result_sym: Vec<Option<SymRef>>,
) -> (Vec<Vec<BlobSlice>>, Vec<BlobSlice>) {
let per_param = per_param_sym
.into_iter()
.map(|v| v.into_iter().map(|s| resolve(s, symbols)).collect())
.collect();
let per_result = per_result_sym
.into_iter()
.map(|s| resolve(s, symbols))
.collect();
(per_param, per_result)
}
pub(super) fn lay_out_static_memory(
mut per_func: Vec<FuncClassified>,
funcs: &[&WitFunction],
schema: &SchemaLayouts,
names: NameInterner,
iface_name: BlobSlice,
) -> Result<(Vec<FuncDispatch>, StaticDataPlan)> {
let n_funcs = per_func.len();
debug_assert_eq!(
per_func.len(),
funcs.len(),
"FuncClassified list and WitFunction list must be index-aligned",
);
check_layout_budget(&per_func)?;
let mut layout = StaticLayout::new();
let mut symbols = SymbolBases::new();
let mut relocs = RelocPlan::new();
let name_blob = names.into_bytes();
let _ = layout.place_data(1, &name_blob);
let flags_scratch_addrs: Vec<u32> = flags_scratch_sizes(&per_func)
.into_iter()
.map(|n_bytes| layout.reserve_scratch(4, n_bytes))
.collect();
let char_scratch_addrs: Vec<u32> = char_scratch_sizes(&per_func)
.into_iter()
.map(|n_bytes| layout.reserve_scratch(1, n_bytes))
.collect();
let CharScratchMaps {
per_cell: char_scratch_map,
per_result_single: char_per_result_single,
} = {
let mut iter = char_scratch_addrs.iter().copied();
let maps = build_char_scratch_map(&per_func, &mut iter);
debug_assert!(iter.next().is_none());
maps
};
let enum_info_id = symbols.alloc();
let record_tuples_id = symbols.alloc();
let tuple_indices_id = symbols.alloc();
let enum_info = build_enum_info_blob(&mut per_func, &schema.enum_info_layout, enum_info_id);
let SideTableBlob {
segment: enum_segment,
per_param: enum_per_param_sym,
per_result: enum_per_result_sym,
} = enum_info;
let mut flags_scratch_iter = flags_scratch_addrs.iter().copied();
let FlagsInfoMaps {
per_cell_fill: flags_per_cell_fill,
per_result_single_fill: flags_per_result_single_fill,
per_param_count: flags_per_param_count,
per_result_count: flags_per_result_count,
} = build_flags_info_maps(&per_func, &mut flags_scratch_iter);
debug_assert!(
flags_scratch_iter.next().is_none(),
"flags scratch reservations must be consumed once per Cell::Flags",
);
let RecordInfoMaps {
tuples: record_tuples_seg,
per_cell_fill: mut record_per_cell_fill,
per_param_count: record_per_param_count,
per_result_count: record_per_result_count,
} = build_record_info_maps(
&per_func,
&schema.record_field_tuple_layout,
record_tuples_id,
);
let TupleIndicesBlob {
segment: tuple_indices_seg,
per_cell_idx: tuple_indices_per_cell,
} = build_tuple_indices_blob(&per_func, tuple_indices_id);
let VariantInfoMaps {
per_cell_fill: variant_per_cell_fill,
per_param_count: variant_per_param_count,
per_result_count: variant_per_result_count,
} = build_variant_info_maps(&per_func);
let HandleInfoMaps {
per_cell_fill: handle_per_cell_fill,
per_result_single_fill: handle_per_result_single_fill,
per_param_count: handle_per_param_count,
per_result_count: handle_per_result_count,
} = build_handle_info_maps(&per_func);
let record_tuples_base =
place_segment(&mut layout, &mut symbols, &mut relocs, record_tuples_seg);
place_segment(&mut layout, &mut symbols, &mut relocs, enum_segment);
place_segment(&mut layout, &mut symbols, &mut relocs, tuple_indices_seg);
back_fill_record_fields_ptrs(&mut record_per_cell_fill, record_tuples_base);
let (enum_per_param, enum_per_result) =
resolve_param_result_ranges(&symbols, enum_per_param_sym, enum_per_result_sym);
let param_side_tables: Vec<Vec<FieldSideTables>> = (0..n_funcs)
.map(|fn_idx| {
(0..per_func[fn_idx].params.len())
.map(|p_idx| FieldSideTables {
enum_infos: enum_per_param[fn_idx][p_idx],
flags_infos: BlobSlice {
off: 0,
len: flags_per_param_count[fn_idx][p_idx],
},
record_infos: BlobSlice {
off: 0,
len: record_per_param_count[fn_idx][p_idx],
},
variant_infos: BlobSlice {
off: 0,
len: variant_per_param_count[fn_idx][p_idx],
},
handle_infos: BlobSlice {
off: 0,
len: handle_per_param_count[fn_idx][p_idx],
},
})
.collect()
})
.collect();
let result_side_tables: Vec<FieldSideTables> = (0..n_funcs)
.map(|fn_idx| FieldSideTables {
enum_infos: enum_per_result[fn_idx],
flags_infos: BlobSlice {
off: 0,
len: flags_per_result_count[fn_idx],
},
record_infos: BlobSlice {
off: 0,
len: record_per_result_count[fn_idx],
},
variant_infos: BlobSlice {
off: 0,
len: variant_per_result_count[fn_idx],
},
handle_infos: BlobSlice {
off: 0,
len: handle_per_result_count[fn_idx],
},
})
.collect();
let fields_blob = build_fields_blob(&per_func, schema, ¶m_side_tables);
let (fields_base, _) = layout.place_data(schema.field_layout.align, &fields_blob);
let fields_buf_offsets: Vec<u32> = {
let mut cursor = fields_base;
per_func
.iter()
.map(|fd| {
let here = cursor;
cursor += fd.params.len() as u32 * schema.field_layout.size;
here
})
.collect()
};
let after_blob = build_after_params_blob(&per_func, schema, iface_name, &result_side_tables);
let after_params_offsets: Vec<Option<i32>> =
match schema.after_hook.as_ref().map(|h| &h.params_layout) {
Some(al) => {
let (after_base, _) = layout.place_data(al.align, &after_blob);
let mut cursor = after_base;
(0..n_funcs)
.map(|_| {
let here = cursor as i32;
cursor += al.size;
Some(here)
})
.collect()
}
None => vec![None; n_funcs],
};
let event_ptr = layout.reserve_scratch(EVENT_SLOT_ALIGN, EVENT_SLOT_SIZE) as i32;
let hook_params_ptr = schema
.before_hook
.as_ref()
.map(|h| layout.reserve_scratch(h.params_layout.align, h.params_layout.size));
let retptr_offsets: Vec<Option<i32>> = per_func
.iter()
.zip(funcs.iter())
.map(|(fd, func)| {
if !(fd.export_sig.retptr || fd.import_sig.retptr) {
return None;
}
let result_ty = func
.result
.as_ref()
.expect("retptr → func.result is_some()");
let size = schema.size_align.size(result_ty).size_wasm32() as u32;
let align = schema.size_align.align(result_ty).align_wasm32() as u32;
Some(layout.reserve_scratch(align, size) as i32)
})
.collect();
let params_record_offsets: Vec<Option<i32>> = per_func
.iter()
.zip(funcs.iter())
.map(|(fd, func)| {
if !(matches!(fd.shape, FuncShape::Async(_)) && fd.import_sig.indirect_params) {
return None;
}
let param_types: Vec<Type> = func.params.iter().map(|p| p.ty).collect();
let info = schema.size_align.record(¶m_types);
let size = info.size.size_wasm32() as u32;
let align = info.align.align_wasm32() as u32;
Some(layout.reserve_scratch(align, size) as i32)
})
.collect();
let bump_start = layout.end().next_multiple_of(schema.cell_layout.align);
if bump_start > LAYOUT_SIZE_BUDGET {
bail!("static-data layout end {bump_start} exceeds i32 budget {LAYOUT_SIZE_BUDGET}");
}
let mut data_segments = layout.into_segments();
relocs.resolve(&symbols, &mut data_segments);
let dispatches: Vec<FuncDispatch> = per_func
.into_iter()
.enumerate()
.map(|(i, fc)| {
let params: Vec<ParamLayout> = fc
.params
.into_iter()
.enumerate()
.map(|(p_idx, lift)| {
let tuple_slices = tuple_indices_per_cell.resolve_param(i, p_idx, &symbols);
let sources = CellFillSources {
record_fill: record_per_cell_fill.for_param(i, p_idx),
tuple_indices: &tuple_slices,
flags_fill: flags_per_cell_fill.for_param(i, p_idx),
variant_fill: variant_per_cell_fill.for_param(i, p_idx),
char_scratch: char_scratch_map.for_param(i, p_idx),
handle_fill: handle_per_cell_fill.for_param(i, p_idx),
};
let cell_side = fold_cell_side_data(&lift.plan, &sources);
ParamLayout {
lift,
cell_side,
info_counts: InfoCounts {
handle: handle_per_param_count[i][p_idx],
flags: flags_per_param_count[i][p_idx],
record: record_per_param_count[i][p_idx],
variant: variant_per_param_count[i][p_idx],
},
}
})
.collect();
let retptr_offset = retptr_offsets[i];
let result_lift = fc.result_lift.map(|rl| {
let ResultLift { source, .. } = rl;
let layout_source = match source {
ResultSource::Direct(cell) => {
let fills = SingleCellFills {
flags_fill: &flags_per_result_single_fill[i],
char_scratch: &char_per_result_single[i],
handle_fill: &handle_per_result_single_fill[i],
};
let side_data = single_cell_side_data(&cell, &fills);
ResultSourceLayout::Direct { cell, side_data }
}
ResultSource::Compound(compound) => {
let tuple_slices = tuple_indices_per_cell.resolve_result(i, &symbols);
let sources = CellFillSources {
record_fill: record_per_cell_fill.for_result(i),
tuple_indices: &tuple_slices,
flags_fill: flags_per_cell_fill.for_result(i),
variant_fill: variant_per_cell_fill.for_result(i),
char_scratch: char_scratch_map.for_result(i),
handle_fill: handle_per_cell_fill.for_result(i),
};
let cell_side = fold_cell_side_data(&compound.plan, &sources);
debug_assert_eq!(
retptr_offset.is_some(),
fc.shape.result_at_retptr(&fc.export_sig, &fc.import_sig),
"retptr scratch reservation must match \
classify-time result_at_retptr",
);
ResultSourceLayout::Compound {
compound,
retptr_offset,
cell_side,
}
}
};
ResultLayout {
source: layout_source,
info_counts: InfoCounts {
handle: handle_per_result_count[i],
flags: flags_per_result_count[i],
record: record_per_result_count[i],
variant: variant_per_result_count[i],
},
}
});
let after = after_params_offsets[i].map(|params_offset| AfterSetup { params_offset });
FuncDispatch {
shape: fc.shape,
result_ty: fc.result_ty,
import_module: fc.import_module,
import_field: fc.import_field,
export_name: fc.export_name,
export_sig: fc.export_sig,
import_sig: fc.import_sig,
needs_cabi_post: fc.needs_cabi_post,
fn_name_offset: fc.fn_name_offset,
fn_name_len: fc.fn_name_len,
params,
fields_buf_offset: fields_buf_offsets[i],
retptr_offset,
params_record_offset: params_record_offsets[i],
result_lift,
after,
borrow_drops: fc.borrow_drops,
}
})
.collect();
Ok((
dispatches,
StaticDataPlan {
bump_start,
event_ptr,
hook_params_ptr,
data_segments,
},
))
}