use super::*;
pub(super) fn emit_method_invoke(ctx: &mut WasmGenCtx, name: &str, args: &[Calcit]) -> Result<(), String> {
if args.is_empty() {
return Err(format!("method .{name} expects at least 1 operand"));
}
let receiver = ctx.alloc_local();
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::LocalSet(receiver));
let extra_locals: Vec<u32> = args[1..]
.iter()
.map(|arg| {
let local = ctx.alloc_local();
emit_expr(ctx, arg)?;
ctx.emit(Instruction::LocalSet(local));
Ok(local)
})
.collect::<Result<_, String>>()?;
match name {
"empty?" => emit_method_empty(ctx, receiver),
"count" => emit_method_count(ctx, receiver),
"first" => {
if !extra_locals.is_empty() {
return Err("method .first expects 0 arguments".into());
}
emit_method_first(ctx, receiver)
}
"rest" => {
if !extra_locals.is_empty() {
return Err("method .rest expects 0 arguments".into());
}
emit_method_rest(ctx, receiver)
}
"nth" => {
if extra_locals.len() != 1 {
return Err("method .nth expects 1 argument".into());
}
emit_method_nth(ctx, receiver, extra_locals[0])
}
"get" => {
if extra_locals.len() != 1 {
return Err("method .get expects 1 argument".into());
}
emit_method_get(ctx, receiver, extra_locals[0])
}
_ => Err(format!("unsupported invoke method in WASM: .{name}")),
}
}
fn emit_type_of_local(ctx: &mut WasmGenCtx, value_local: u32) {
let number_tag = get_type_tag(ctx, "number");
let is_valid_ptr = ctx.alloc_local_typed(ValType::I32);
let raw_base = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(value_local));
ctx.emit(Instruction::LocalGet(value_local));
ctx.emit(Instruction::F64Trunc);
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::LocalGet(value_local));
ctx.emit(f64_const((HEAP_BASE + 8) as f64));
ctx.emit(Instruction::F64Ge);
ctx.emit(Instruction::I32And);
ctx.emit(Instruction::LocalGet(value_local));
ctx.emit(Instruction::GlobalGet(HEAP_PTR_GLOBAL));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Lt);
ctx.emit(Instruction::I32And);
ctx.emit(Instruction::LocalSet(is_valid_ptr));
ctx.emit(Instruction::LocalGet(value_local));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalSet(raw_base));
ctx.emit(Instruction::LocalGet(is_valid_ptr));
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
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::If(wasm_encoder::BlockType::Result(ValType::F64)));
ctx.emit(Instruction::LocalGet(raw_base));
ctx.emit(Instruction::I32Load(mem_arg_i32(4)));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::Else);
ctx.emit(f64_const(number_tag));
ctx.emit(Instruction::End);
ctx.emit(Instruction::Else);
ctx.emit(f64_const(number_tag));
ctx.emit(Instruction::End);
}
fn emit_heap_count_from_local(ctx: &mut WasmGenCtx, receiver_local: u32) {
ctx.emit(Instruction::LocalGet(receiver_local));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
}
fn emit_list_nth_from_local(ctx: &mut WasmGenCtx, receiver_local: u32, index_local: u32) {
ctx.emit(Instruction::LocalGet(index_local));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::LocalGet(receiver_local));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
}
fn emit_list_first_from_local(ctx: &mut WasmGenCtx, receiver_local: u32) {
ctx.emit(Instruction::LocalGet(receiver_local));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::F64Load(mem_arg_f64(8)));
}
fn emit_list_rest_from_local(ctx: &mut WasmGenCtx, receiver_local: u32) {
let src = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(receiver_local));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(src));
let old_count = emit_load_count_i32(ctx, src);
let new_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(old_count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalSet(new_count));
let total_slots = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(new_count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(total_slots));
let dst = emit_alloc_with_count(ctx, new_count, total_slots, "list");
let dst_base = emit_addr_offset(ctx, dst, 8);
let src_base = emit_addr_offset(ctx, src, 16);
emit_copy_f64_loop(ctx, dst_base, src_base, new_count);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
}
fn emit_tuple_nth_from_local(ctx: &mut WasmGenCtx, receiver_local: u32, index_local: u32) {
ctx.emit(Instruction::LocalGet(index_local));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::LocalGet(receiver_local));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
}
fn emit_map_get_from_local(ctx: &mut WasmGenCtx, receiver_local: u32, key_local: u32) {
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_map_get_value")
.expect("runtime helper __rt_map_get_value must exist");
ctx.emit(Instruction::LocalGet(receiver_local));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalGet(key_local));
ctx.emit(Instruction::Call(fn_idx));
}
fn emit_method_empty(ctx: &mut WasmGenCtx, receiver_local: u32) -> Result<(), String> {
let nil_tag = get_type_tag(ctx, "nil");
let list_tag = get_type_tag(ctx, "list");
let map_tag = get_type_tag(ctx, "map");
let set_tag = get_type_tag(ctx, "set");
let string_tag = get_type_tag(ctx, "string");
let tuple_tag = get_type_tag(ctx, "tuple");
let record_tag = get_type_tag(ctx, "record");
let type_local = ctx.alloc_local();
emit_type_of_local(ctx, receiver_local);
ctx.emit(Instruction::LocalSet(type_local));
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(nil_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
ctx.emit(f64_const(1.0));
ctx.emit(Instruction::Else);
for tag in [list_tag, map_tag, set_tag, string_tag, tuple_tag, record_tag] {
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
ctx.emit(f64_const(1.0));
ctx.emit(f64_const(0.0));
emit_heap_count_from_local(ctx, receiver_local);
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::Select);
ctx.emit(Instruction::Else);
}
ctx.emit(f64_const(0.0));
for _ in 0..6 {
ctx.emit(Instruction::End);
}
ctx.emit(Instruction::End);
Ok(())
}
fn emit_method_count(ctx: &mut WasmGenCtx, receiver_local: u32) -> Result<(), String> {
let nil_tag = get_type_tag(ctx, "nil");
let list_tag = get_type_tag(ctx, "list");
let map_tag = get_type_tag(ctx, "map");
let set_tag = get_type_tag(ctx, "set");
let string_tag = get_type_tag(ctx, "string");
let tuple_tag = get_type_tag(ctx, "tuple");
let record_tag = get_type_tag(ctx, "record");
let type_local = ctx.alloc_local();
emit_type_of_local(ctx, receiver_local);
ctx.emit(Instruction::LocalSet(type_local));
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(nil_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::Else);
for tag in [list_tag, map_tag, set_tag, string_tag, tuple_tag, record_tag] {
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
emit_heap_count_from_local(ctx, receiver_local);
ctx.emit(Instruction::Else);
}
ctx.emit(f64_const(0.0));
for _ in 0..6 {
ctx.emit(Instruction::End);
}
ctx.emit(Instruction::End);
Ok(())
}
fn emit_method_nth(ctx: &mut WasmGenCtx, receiver_local: u32, index_local: u32) -> Result<(), String> {
let list_tag = get_type_tag(ctx, "list");
let tuple_tag = get_type_tag(ctx, "tuple");
let type_local = ctx.alloc_local();
emit_type_of_local(ctx, receiver_local);
ctx.emit(Instruction::LocalSet(type_local));
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(list_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
emit_list_nth_from_local(ctx, receiver_local, index_local);
ctx.emit(Instruction::Else);
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(tuple_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
emit_tuple_nth_from_local(ctx, receiver_local, index_local);
ctx.emit(Instruction::Else);
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
Ok(())
}
fn emit_method_first(ctx: &mut WasmGenCtx, receiver_local: u32) -> Result<(), String> {
let list_tag = get_type_tag(ctx, "list");
let tuple_tag = get_type_tag(ctx, "tuple");
let type_local = ctx.alloc_local();
emit_type_of_local(ctx, receiver_local);
ctx.emit(Instruction::LocalSet(type_local));
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(list_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
emit_list_first_from_local(ctx, receiver_local);
ctx.emit(Instruction::Else);
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(tuple_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
let zero = ctx.alloc_local();
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::LocalSet(zero));
emit_tuple_nth_from_local(ctx, receiver_local, zero);
ctx.emit(Instruction::Else);
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
Ok(())
}
fn emit_method_rest(ctx: &mut WasmGenCtx, receiver_local: u32) -> Result<(), String> {
let list_tag = get_type_tag(ctx, "list");
let type_local = ctx.alloc_local();
emit_type_of_local(ctx, receiver_local);
ctx.emit(Instruction::LocalSet(type_local));
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(list_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
emit_list_rest_from_local(ctx, receiver_local);
ctx.emit(Instruction::Else);
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::End);
Ok(())
}
fn emit_method_get(ctx: &mut WasmGenCtx, receiver_local: u32, key_local: u32) -> Result<(), String> {
let map_tag = get_type_tag(ctx, "map");
let list_tag = get_type_tag(ctx, "list");
let tuple_tag = get_type_tag(ctx, "tuple");
let type_local = ctx.alloc_local();
emit_type_of_local(ctx, receiver_local);
ctx.emit(Instruction::LocalSet(type_local));
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(map_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
emit_map_get_from_local(ctx, receiver_local, key_local);
ctx.emit(Instruction::Else);
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(list_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
emit_list_nth_from_local(ctx, receiver_local, key_local);
ctx.emit(Instruction::Else);
ctx.emit(Instruction::LocalGet(type_local));
ctx.emit(f64_const(tuple_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
emit_tuple_nth_from_local(ctx, receiver_local, key_local);
ctx.emit(Instruction::Else);
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
Ok(())
}
pub(super) fn emit_call_args(
ctx: &mut WasmGenCtx,
args_list: &[Calcit],
target_arity: u32,
rest_fixed: Option<u32>,
) -> Result<(), String> {
match rest_fixed {
Some(fixed) => {
let fixed = fixed as usize;
if args_list.len() < fixed {
return Err(format!("rest-args call expected at least {} args, got {}", fixed, args_list.len()));
}
for arg in args_list.iter().take(fixed) {
emit_expr(ctx, arg)?;
}
emit_list_new(ctx, &args_list[fixed..])?;
}
None => {
for arg in args_list {
emit_expr(ctx, arg)?;
}
for _ in args_list.len()..(target_arity as usize) {
ctx.emit(f64_const(0.0));
}
}
}
Ok(())
}