use super::*;
pub(super) fn get_type_tag(ctx: &WasmGenCtx, name: &str) -> f64 {
*ctx
.tag_index
.get(name)
.unwrap_or_else(|| panic!("builtin type tag not registered: {name}")) as f64
}
pub(super) fn emit_hash_proc(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("&hash expects 1 arg".into());
}
emit_expr(ctx, &args[0])?;
let val = ctx.alloc_local(); ctx.emit(Instruction::LocalSet(val));
let mem_size_f64 = ctx.alloc_local(); ctx.emit(Instruction::MemorySize(0));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Shl);
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::LocalSet(mem_size_f64));
let is_heap = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(val));
ctx.emit(f64_const((HEAP_BASE + 8) as f64));
ctx.emit(Instruction::F64Ge);
ctx.emit(Instruction::LocalGet(val));
ctx.emit(Instruction::LocalGet(mem_size_f64));
ctx.emit(Instruction::F64Lt);
ctx.emit(Instruction::I32And);
ctx.emit(Instruction::LocalSet(is_heap));
ctx.emit(Instruction::LocalGet(is_heap));
ctx.begin_block_if();
let raw_base = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(val));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalSet(raw_base));
ctx.emit(Instruction::LocalGet(raw_base));
ctx.emit(Instruction::I32Load(mem_arg_i32(0)));
ctx.emit(Instruction::I32Const(HEAP_MAGIC));
ctx.emit(Instruction::I32Eq);
ctx.emit(Instruction::LocalSet(is_heap));
ctx.emit(Instruction::End);
let result_i32 = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(is_heap));
ctx.begin_block_if();
ctx.emit(Instruction::LocalGet(val));
ctx.emit(Instruction::I32TruncF64U);
let hash_list_idx = *ctx.runtime_fn_index.get("__rt_hash_list_or_set").expect("hash_list_or_set");
ctx.emit(Instruction::Call(hash_list_idx));
ctx.emit(Instruction::LocalSet(result_i32));
ctx.emit(Instruction::Else);
ctx.emit(Instruction::LocalGet(val));
let hash_f64_idx = *ctx.runtime_fn_index.get("__rt_hash_f64").expect("hash_f64");
ctx.emit(Instruction::Call(hash_f64_idx));
ctx.emit(Instruction::LocalSet(result_i32));
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(result_i32));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
pub(super) fn emit_bump_alloc(ctx: &mut WasmGenCtx, byte_size: i32, ptr_local: u32, type_tag: &str) {
let tag_val = get_type_tag(ctx, type_tag) as i32;
ctx.emit(Instruction::GlobalGet(HEAP_PTR_GLOBAL));
ctx.emit(Instruction::I32Const(HEAP_MAGIC));
ctx.emit(Instruction::I32Store(mem_arg_i32(0)));
ctx.emit(Instruction::GlobalGet(HEAP_PTR_GLOBAL));
ctx.emit(Instruction::I32Const(tag_val));
ctx.emit(Instruction::I32Store(mem_arg_i32(4)));
ctx.emit(Instruction::GlobalGet(HEAP_PTR_GLOBAL));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalTee(ptr_local));
ctx.emit(Instruction::I32Const(byte_size));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::GlobalSet(HEAP_PTR_GLOBAL));
}
pub(super) fn emit_bump_alloc_dynamic(ctx: &mut WasmGenCtx, size_local: u32, ptr_local: u32, type_tag: &str) {
let tag_val = get_type_tag(ctx, type_tag) as i32;
ctx.emit(Instruction::GlobalGet(HEAP_PTR_GLOBAL));
ctx.emit(Instruction::I32Const(HEAP_MAGIC));
ctx.emit(Instruction::I32Store(mem_arg_i32(0)));
ctx.emit(Instruction::GlobalGet(HEAP_PTR_GLOBAL));
ctx.emit(Instruction::I32Const(tag_val));
ctx.emit(Instruction::I32Store(mem_arg_i32(4)));
ctx.emit(Instruction::GlobalGet(HEAP_PTR_GLOBAL));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalTee(ptr_local));
ctx.emit(Instruction::LocalGet(size_local));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::GlobalSet(HEAP_PTR_GLOBAL));
}
pub(super) fn emit_ptr_to_i32(ctx: &mut WasmGenCtx, expr: &Calcit) -> Result<u32, String> {
let local = ctx.alloc_local_typed(ValType::I32);
if let Calcit::Tag(t) = expr {
let tag_name = t.to_string();
let ptr = ctx
.string_pool
.get(&tag_name)
.ok_or_else(|| format!("tag '{tag_name}' not found in string pool; it was not collected during build_string_pool"))?;
ctx.emit(Instruction::I32Const(*ptr as i32));
ctx.emit(Instruction::LocalSet(local));
return Ok(local);
}
emit_expr(ctx, expr)?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(local));
Ok(local)
}
pub(super) fn emit_load_count_i32(ctx: &mut WasmGenCtx, ptr_i32: u32) -> u32 {
let count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(ptr_i32));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(count));
count
}
pub(super) fn emit_addr_offset(ctx: &mut WasmGenCtx, base: u32, byte_offset: i32) -> u32 {
let local = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(base));
ctx.emit(Instruction::I32Const(byte_offset));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(local));
local
}
pub(super) fn emit_copy_f64_loop(ctx: &mut WasmGenCtx, dst_base: u32, src_base: u32, n: u32) {
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_copy_f64_slots")
.expect("runtime helper __rt_copy_f64_slots must exist");
ctx.emit(Instruction::LocalGet(dst_base));
ctx.emit(Instruction::LocalGet(src_base));
ctx.emit(Instruction::LocalGet(n));
ctx.emit(Instruction::Call(fn_idx));
}
pub(super) fn emit_runtime_lookup_i32_f64_to_i32(ctx: &mut WasmGenCtx, helper: &str, ptr_local: u32, target_local: u32) -> u32 {
let result = ctx.alloc_local_typed(ValType::I32);
let fn_idx = *ctx
.runtime_fn_index
.get(helper)
.unwrap_or_else(|| panic!("runtime helper missing: {helper}"));
ctx.emit(Instruction::LocalGet(ptr_local));
ctx.emit(Instruction::LocalGet(target_local));
ctx.emit(Instruction::Call(fn_idx));
ctx.emit(Instruction::LocalSet(result));
result
}
pub(super) fn emit_runtime_lookup_i32_to_i32(ctx: &mut WasmGenCtx, helper: &str, ptr_local: u32) -> u32 {
let result = ctx.alloc_local_typed(ValType::I32);
let fn_idx = *ctx
.runtime_fn_index
.get(helper)
.unwrap_or_else(|| panic!("runtime helper missing: {helper}"));
ctx.emit(Instruction::LocalGet(ptr_local));
ctx.emit(Instruction::Call(fn_idx));
ctx.emit(Instruction::LocalSet(result));
result
}
pub(super) fn emit_ds_count(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("count expects 1 arg".into());
}
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
Ok(())
}
pub(super) fn emit_ds_empty(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("empty? expects 1 arg".into());
}
ctx.emit(f64_const(1.0));
ctx.emit(f64_const(0.0));
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::Select);
Ok(())
}
pub(super) fn emit_alloc_with_count(ctx: &mut WasmGenCtx, count_i32: u32, total_slots_i32: u32, type_tag: &str) -> u32 {
let size = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(total_slots_i32));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::LocalSet(size));
let ptr = ctx.alloc_local_typed(ValType::I32);
emit_bump_alloc_dynamic(ctx, size, ptr, type_tag);
ctx.store_i32_as_f64(ptr, count_i32, 0);
ptr
}
pub(super) fn emit_alloc_map_with_root(ctx: &mut WasmGenCtx, count_i32: u32, root_i32: u32) -> u32 {
let ptr = ctx.alloc_local_typed(ValType::I32);
emit_bump_alloc(ctx, 16, ptr, "map");
ctx.store_i32_as_f64(ptr, count_i32, 0);
ctx.store_i32_as_f64(ptr, root_i32, 8);
ptr
}
pub(super) fn emit_alloc_list(ctx: &mut WasmGenCtx, count: u32) -> u32 {
let total_slots = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(total_slots));
emit_alloc_with_count(ctx, count, total_slots, "list")
}
pub(super) fn emit_list_load_elem(ctx: &mut WasmGenCtx, list_ptr: u32, i: u32) {
ctx.emit(Instruction::LocalGet(list_ptr));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
}
#[allow(dead_code)]
pub(super) fn emit_list_elem_addr(ctx: &mut WasmGenCtx, list_ptr: u32, i: u32) -> u32 {
let addr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(list_ptr));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(addr));
addr
}
pub(super) fn emit_list_load_ptr(ctx: &mut WasmGenCtx, list_ptr: u32, i: u32) -> u32 {
let ptr = ctx.alloc_local_typed(ValType::I32);
emit_list_load_elem(ctx, list_ptr, i);
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(ptr));
ptr
}
pub(super) fn emit_list_store_elem(ctx: &mut WasmGenCtx, dst: u32, idx: u32, val: u32) {
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(idx));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(val));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
}
pub(super) fn emit_list_iter_begin(ctx: &mut WasmGenCtx, src_ptr: u32, count: u32) -> (u32, u32) {
let i = ctx.alloc_i32(0);
let elem = ctx.alloc_local();
ctx.begin_block();
ctx.begin_loop();
ctx.loop_exit_if_ge(i, count);
emit_list_load_elem(ctx, src_ptr, i);
ctx.emit(Instruction::LocalSet(elem));
(i, elem)
}
pub(super) fn emit_list_iter_end(ctx: &mut WasmGenCtx, i: u32) {
ctx.i32_inc(i);
ctx.br_loop();
ctx.end_block_loop();
}