Lines
76.39 %
Functions
30 %
Branches
100 %
use std::mem::offset_of;
use anodized::spec;
use scripting_format::{
DEBUG_VALUE_DATA_SIZE, DebugValueData, ENTITY_HEADER_SIZE, EntityHeader, EntityType,
MAGIC_OUTP, OUTPUT_HEADER_SIZE, Operation, OutputHeader, TAG_DATA_SIZE, TagData, ValueType,
};
use tracing::debug;
use super::emit::FunctionEmitter;
pub struct GcLocals {
pub type_idx: u32,
pub arr: u32,
pub idx: u32,
}
pub struct TagGcData {
pub name_data_idx: u32,
pub name_len: u32,
pub value_data_idx: u32,
pub value_len: u32,
pub struct OutputSerializer {
cursor: u32,
entity_count: u32,
output_start_idx: u32,
output_base_local: u32,
strings_end: u32,
impl OutputSerializer {
pub fn new(output_base_local: u32) -> Self {
Self {
cursor: 0,
entity_count: 0,
output_start_idx: 0,
output_base_local,
strings_end: 0,
#[spec(
requires: [self.cursor == 0, self.entity_count == 0],
ensures: [self.cursor == OUTPUT_HEADER_SIZE as u32],
)]
pub fn begin_output(&mut self, emit: &mut FunctionEmitter) {
debug!(cursor = self.cursor, "writing output header");
let local = self.output_base_local;
emit.store_i32_dynamic(
local,
self.cursor + offset_of!(OutputHeader, magic) as u32,
MAGIC_OUTP as i32,
);
self.cursor + offset_of!(OutputHeader, output_entity_count) as u32,
0,
self.cursor + offset_of!(OutputHeader, output_start_idx) as u32,
self.output_start_idx as i32,
self.cursor + offset_of!(OutputHeader, next_write_pos) as u32,
OUTPUT_HEADER_SIZE as i32,
self.cursor += OUTPUT_HEADER_SIZE as u32;
captures: [self.entity_count as prev_count, self.cursor as prev_cursor],
ensures: [
self.entity_count == prev_count + 1,
self.cursor == prev_cursor + ENTITY_HEADER_SIZE as u32,
*output == prev_cursor + ENTITY_HEADER_SIZE as u32,
],
fn append_entity_header(
&mut self,
emit: &mut FunctionEmitter,
entity_type: EntityType,
operation: Operation,
flags: u8,
parent_idx: i32,
data_size: u32,
) -> u32 {
debug!(
cursor = self.cursor,
?entity_type,
"appending entity header"
let data_offset = self.cursor + ENTITY_HEADER_SIZE as u32;
emit.store_u8_dynamic(
self.cursor + offset_of!(EntityHeader, entity_type) as u32,
entity_type as u8,
self.cursor + offset_of!(EntityHeader, operation) as u32,
operation as u8,
self.cursor + offset_of!(EntityHeader, flags) as u32,
flags,
self.cursor + offset_of!(EntityHeader, parent_idx) as u32,
parent_idx,
// data_offset is relative — the runtime adds output_base
self.cursor + offset_of!(EntityHeader, data_offset) as u32,
data_offset as i32,
self.cursor + offset_of!(EntityHeader, data_size) as u32,
data_size as i32,
self.cursor += ENTITY_HEADER_SIZE as u32;
self.entity_count += 1;
offset_of!(OutputHeader, output_entity_count) as u32,
self.entity_count as i32,
data_offset
fn begin_debug_value_entity(&mut self, emit: &mut FunctionEmitter) -> u32 {
self.append_entity_header(
emit,
EntityType::DebugValue,
Operation::Nop,
-1,
DEBUG_VALUE_DATA_SIZE as u32,
)
captures: [self.entity_count as prev_count],
ensures: [self.entity_count == prev_count + 1],
pub fn write_debug_nil(&mut self, emit: &mut FunctionEmitter) {
let data_start = self.begin_debug_value_entity(emit);
debug!(data_start, "writing nil value");
data_start + offset_of!(DebugValueData, value_type) as u32,
ValueType::Nil as u8,
self.cursor = data_start + DEBUG_VALUE_DATA_SIZE as u32;
self.finalize_cursor(emit);
pub fn write_debug_bool(&mut self, emit: &mut FunctionEmitter, value: bool) {
debug!(data_start, value, "writing bool value");
ValueType::Bool as u8,
emit.store_i64_dynamic(
data_start + offset_of!(DebugValueData, data1) as u32,
i64::from(value),
pub fn write_debug_number(&mut self, emit: &mut FunctionEmitter, numer: i64, denom: i64) {
debug!(data_start, numer, denom, "writing number value");
ValueType::Number as u8,
numer,
data_start + offset_of!(DebugValueData, data2) as u32,
denom,
pub fn write_debug_string_gc(
value_type: ValueType,
data_idx: u32,
len: u32,
gc: &GcLocals,
) {
?value_type,
data_idx,
len,
"writing string value with GC"
let strings_pool = data_start + DEBUG_VALUE_DATA_SIZE as u32;
value_type as u8,
i64::from(len),
self.copy_data_gc(emit, data_idx, len, strings_pool, gc);
self.cursor += len;
self.strings_end = strings_pool;
self.cursor == prev_cursor + ENTITY_HEADER_SIZE as u32 + TAG_DATA_SIZE as u32 + name_len + value_len,
self.strings_end >= self.cursor,
pub fn write_tag_gc(
name_data_idx: u32,
name_len: u32,
value_data_idx: u32,
value_len: u32,
let total_data_size = TAG_DATA_SIZE as u32 + name_len + value_len;
let data_start = self.append_entity_header(
EntityType::Tag,
Operation::Create,
total_data_size,
let strings_start = data_start + TAG_DATA_SIZE as u32;
let name_start = strings_start;
let value_start = name_start + name_len;
let strings_end = value_start + value_len;
let name_offset = strings_end - name_start;
let value_offset = strings_end - value_start;
data_start + offset_of!(TagData, name_offset) as u32,
name_offset as i32,
data_start + offset_of!(TagData, value_offset) as u32,
value_offset as i32,
data_start + offset_of!(TagData, name_len) as u32,
((value_len as i32) << 16) | (name_len as i32),
emit.store_i32_dynamic(local, data_start + offset_of!(TagData, reserved) as u32, 0);
self.copy_data_gc(emit, name_data_idx, name_len, name_start, gc);
self.copy_data_gc(emit, value_data_idx, value_len, value_start, gc);
self.cursor = strings_end;
self.strings_end = strings_end;
fn append_entity_header_dynamic_parent(
parent_idx_local: u32,
// parent_idx from WASM local
emit.local_get(local);
emit.i32_const((self.cursor + offset_of!(EntityHeader, parent_idx) as u32) as i32);
emit.i32_add();
emit.local_get(parent_idx_local);
emit.i32_store_raw();
self.cursor == prev_cursor + ENTITY_HEADER_SIZE as u32 + TAG_DATA_SIZE as u32 + tag.name_len + tag.value_len,
pub fn write_tag_gc_dynamic_parent(
tag: &TagGcData,
let total_data_size = TAG_DATA_SIZE as u32 + tag.name_len + tag.value_len;
let data_start = self.append_entity_header_dynamic_parent(
parent_idx_local,
let value_start = name_start + tag.name_len;
let strings_end = value_start + tag.value_len;
((tag.value_len as i32) << 16) | (tag.name_len as i32),
self.copy_data_gc(emit, tag.name_data_idx, tag.name_len, name_start, gc);
self.copy_data_gc(emit, tag.value_data_idx, tag.value_len, value_start, gc);
fn copy_data_gc(
&self,
dst_offset: u32,
emit.i32_const(0);
emit.i32_const(len as i32);
emit.array_new_data(gc.type_idx, data_idx);
emit.local_set(gc.arr);
emit.local_set(gc.idx);
emit.block_start();
emit.loop_start();
emit.local_get(gc.idx);
emit.i32_ge_u();
emit.br_if(1);
emit.i32_const(dst_offset as i32);
emit.local_get(gc.arr);
emit.array_get_u(gc.type_idx);
emit.i32_store8_raw();
emit.i32_const(1);
emit.br(0);
emit.block_end();
pub fn write_debug_string_from_stack(&mut self, emit: &mut FunctionEmitter, gc: &GcLocals) {
ValueType::String as u8,
// data2 = array.len (runtime)
emit.i32_const((data_start + offset_of!(DebugValueData, data2) as u32) as i32);
emit.array_len();
emit.i64_extend_i32_u();
emit.i64_store_raw();
// Copy GC array bytes to output
emit.i32_const(strings_pool as i32);
offset_of!(OutputHeader, strings_offset) as u32,
strings_pool as i32,
// next_write_pos = strings_pool + array.len
emit.i32_const(offset_of!(OutputHeader, next_write_pos) as i32);
pub fn write_debug_number_from_stack(
ratio_type_idx: u32,
ratio_local: u32,
emit.local_set(ratio_local);
emit.i32_const((data_start + offset_of!(DebugValueData, data1) as u32) as i32);
emit.local_get(ratio_local);
emit.struct_get(ratio_type_idx, 0);
emit.struct_get(ratio_type_idx, 1);
pub fn write_debug_i32_from_stack(&mut self, emit: &mut FunctionEmitter, i32_local: u32) {
emit.local_set(i32_local);
emit.local_get(i32_local);
1,
pub fn write_debug_bool_from_stack(&mut self, emit: &mut FunctionEmitter, bool_local: u32) {
emit.local_set(bool_local);
emit.local_get(bool_local);
pub fn write_delete_entity(
entity_type_local: u32,
input_header_local: u32,
let header_start = self.cursor;
// entity_type from runtime local
emit.i32_const((header_start + offset_of!(EntityHeader, entity_type) as u32) as i32);
emit.local_get(entity_type_local);
header_start + offset_of!(EntityHeader, operation) as u32,
Operation::Delete as u8,
header_start + offset_of!(EntityHeader, flags) as u32,
// Copy 16-byte id (4 × i32)
for i in 0..4u32 {
emit.i32_const((header_start + offset_of!(EntityHeader, id) as u32 + i * 4) as i32);
emit.local_get(input_header_local);
emit.i32_load((offset_of!(EntityHeader, id) + (i as usize) * 4) as u64);
header_start + offset_of!(EntityHeader, parent_idx) as u32,
let data_offset = header_start + ENTITY_HEADER_SIZE as u32;
header_start + offset_of!(EntityHeader, data_offset) as u32,
header_start + offset_of!(EntityHeader, data_size) as u32,
requires: [self.cursor > 0],
fn finalize_cursor(&self, emit: &mut FunctionEmitter) {
let end = self.cursor.max(self.strings_end);
self.strings_end as i32,
offset_of!(OutputHeader, next_write_pos) as u32,
end as i32,