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_mix(ctx: &mut WasmGenCtx) {
ctx.emit(Instruction::I32Const(0x9e37_79b9u32 as i32));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Rotl);
}
pub(super) fn emit_hash_expr_i32(ctx: &mut WasmGenCtx, expr: &Calcit) -> Result<(), String> {
match expr {
Calcit::Nil => {
ctx.emit(Instruction::I32Const(0x1357_2468u32 as i32));
Ok(())
}
Calcit::Bool(true) => {
ctx.emit(Instruction::I32Const(0x4210_abceu32 as i32));
Ok(())
}
Calcit::Bool(false) => {
ctx.emit(Instruction::I32Const(0x24ce_1357u32 as i32));
Ok(())
}
Calcit::Number(_) => {
emit_expr(ctx, expr)?;
ctx.emit(Instruction::I64ReinterpretF64);
ctx.emit(Instruction::I64Const(32));
ctx.emit(Instruction::I64ShrU);
ctx.emit(Instruction::I32WrapI64);
emit_hash_mix(ctx);
Ok(())
}
Calcit::Tag(_) => {
emit_expr(ctx, expr)?;
ctx.emit(Instruction::I32TruncF64U);
emit_hash_mix(ctx);
Ok(())
}
Calcit::Str(_) | Calcit::Local(_) | Calcit::List(_) | Calcit::Import(_) | Calcit::Registered(_) => {
emit_expr(ctx, expr)?;
ctx.emit(Instruction::I32TruncF64U);
emit_hash_mix(ctx);
Ok(())
}
_ => Err(format!("unsupported WASM hash expression: {expr}")),
}
}
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_hash_expr_i32(ctx, &args[0])?;
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);
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.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::LocalGet(count_i32));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(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.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::LocalGet(count_i32));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::LocalGet(root_i32));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
ptr
}