use std::collections::HashMap;
use std::fs;
use std::path::Path;
use wasm_encoder::{
CodeSection, ConstExpr, ExportKind, ExportSection, Function, FunctionSection, GlobalSection, GlobalType, Ieee64, Instruction,
MemorySection, MemoryType, Module, TypeSection, ValType,
};
use crate::builtins::syntax::get_raw_args_fn;
use crate::calcit::{
Calcit, CalcitArgLabel, CalcitFnArgs, CalcitImport, CalcitLocal, CalcitProc, CalcitStruct, CalcitSyntax, MethodKind,
};
use crate::program;
#[path = "emit_wasm/methods.rs"]
mod methods;
#[path = "emit_wasm/records.rs"]
mod records;
#[path = "emit_wasm/runtime.rs"]
mod runtime;
use methods::{emit_call_args, emit_method_invoke};
use records::{
emit_record_count, emit_record_field_tag, emit_record_get, emit_record_matches, emit_record_new, emit_record_nth, emit_tuple_assoc,
emit_tuple_count, emit_tuple_new, emit_tuple_nth, resolve_struct_ref, try_parse_defrecord_form,
};
use runtime::{HOST_IMPORTS, build_runtime_fns, build_wasm_module};
const HEAP_BASE: i32 = 16;
const HEAP_PTR_GLOBAL: u32 = 0;
const HEAP_MAGIC: i32 = 0xCA1C_17A9u32 as i32;
fn f64_const(v: f64) -> Instruction<'static> {
Instruction::F64Const(Ieee64::from(v))
}
fn mem_arg_f64(offset: u64) -> wasm_encoder::MemArg {
wasm_encoder::MemArg {
offset,
align: 3, memory_index: 0,
}
}
fn mem_arg_i32(offset: u64) -> wasm_encoder::MemArg {
wasm_encoder::MemArg {
offset,
align: 2, memory_index: 0,
}
}
pub fn emit_wasm(init_ns: &str, emit_path: &str) -> Result<(), String> {
let program_data = program::clone_compiled_program_snapshot()?;
let mut fn_defs: Vec<(String, String, CalcitFnArgs, Vec<Calcit>)> = Vec::new();
let mut ns_order: Vec<&str> = Vec::new();
if program_data.contains_key(init_ns) {
ns_order.push(init_ns);
}
for ns in program_data.keys() {
if ns.as_ref() != init_ns {
ns_order.push(ns);
}
}
for &ns in &ns_order {
let Some(file_info) = program_data.get(ns) else {
continue;
};
for (def_name, compiled) in &file_info.defs {
if compiled.kind != program::CompiledDefKind::Fn {
continue;
}
match extract_fn_parts(&compiled.preprocessed_code) {
Ok((args, body)) => {
fn_defs.push((ns.to_string(), def_name.to_string(), args, body));
}
Err(e) => {
eprintln!("[wasm] skipping {ns}/{def_name}: {e}");
}
}
}
}
if fn_defs.is_empty() {
return Err(format!("namespace not found or no functions: {init_ns}"));
}
let num_imports = HOST_IMPORTS.len() as u32;
let (mut compiled_fns, runtime_fn_index) = build_runtime_fns(num_imports);
let runtime_fn_count = compiled_fns.len() as u32;
let mut export_name_counts: HashMap<String, usize> = HashMap::new();
for (_, name, _, _) in &fn_defs {
*export_name_counts.entry(name.clone()).or_insert(0) += 1;
}
let mut fn_index: HashMap<String, u32> = HashMap::new();
let mut fn_arity: HashMap<String, u32> = HashMap::new();
let mut fn_has_rest: HashMap<String, u32> = HashMap::new();
for (i, (ns, name, args, _)) in fn_defs.iter().enumerate() {
let idx = num_imports + runtime_fn_count + i as u32;
let qualified = format!("{ns}/{name}");
fn_index.insert(qualified.clone(), idx);
fn_index.insert(name.clone(), idx);
let (arity, rest_fixed) = compute_fn_arity(args);
fn_arity.insert(qualified.clone(), arity);
fn_arity.insert(name.clone(), arity);
if let Some(fixed) = rest_fixed {
fn_has_rest.insert(qualified, fixed);
fn_has_rest.insert(name.clone(), fixed);
}
}
let tag_index = collect_all_tags_from(&fn_defs);
let record_field_tags = collect_record_field_tags_from_program(&program_data, &tag_index);
let (string_pool, string_data_segment, heap_start) = build_string_pool(&fn_defs, &tag_index);
let env = WasmCompileEnv {
fn_index,
fn_arity,
fn_has_rest,
runtime_fn_index,
tag_index,
record_field_tags,
string_pool,
};
for (ns, def_name, args, body) in &fn_defs {
let export_name = if export_name_counts.get(def_name).copied().unwrap_or(0) > 1 {
format!("{ns}/{def_name}")
} else {
def_name.clone()
};
match compile_fn(def_name, &export_name, args, body, &env) {
Ok(func) => compiled_fns.push(func),
Err(e) => {
eprintln!("[wasm] skipping {ns}/{def_name}: {e}");
let (arity, _) = compute_fn_arity(args);
compiled_fns.push(CompiledFn {
export_name: Some(export_name),
params: vec![ValType::F64; arity as usize],
results: vec![ValType::F64],
locals: vec![],
instructions: vec![f64_const(0.0)],
});
}
}
}
if compiled_fns.is_empty() {
return Err("no functions could be compiled to WASM".into());
}
let wasm_bytes = build_wasm_module(&compiled_fns, heap_start, &string_data_segment)?;
let out_path = Path::new(emit_path);
if !out_path.exists() {
fs::create_dir_all(out_path).map_err(|e| format!("failed to create dir: {e}"))?;
}
let wasm_file = out_path.join("program.wasm");
fs::write(&wasm_file, &wasm_bytes).map_err(|e| format!("failed to write WASM: {e}"))?;
println!("wrote WASM to: {}", wasm_file.display());
Ok(())
}
struct CompiledFn {
export_name: Option<String>,
params: Vec<ValType>,
results: Vec<ValType>,
locals: Vec<ValType>,
instructions: Vec<Instruction<'static>>,
}
#[derive(Clone)]
struct WasmCompileEnv {
fn_index: HashMap<String, u32>,
fn_arity: HashMap<String, u32>,
fn_has_rest: HashMap<String, u32>,
runtime_fn_index: HashMap<String, u32>,
tag_index: HashMap<String, u32>,
record_field_tags: HashMap<u32, Vec<u32>>,
string_pool: HashMap<String, u32>,
}
fn extract_fn_parts(code: &Calcit) -> Result<(CalcitFnArgs, Vec<Calcit>), String> {
let Calcit::List(items) = code else {
return Err(format!("expected preprocessed defn list, got: {code}"));
};
match (items.first(), items.get(1), items.get(2)) {
(Some(Calcit::Syntax(CalcitSyntax::Defn, _)), Some(Calcit::Symbol { .. }), Some(Calcit::List(args))) => {
let raw_args = get_raw_args_fn(args)?;
Ok((raw_args, items.drop_left().drop_left().drop_left().to_vec()))
}
_ => Err(format!("expected preprocessed defn form, got: {code}")),
}
}
struct WasmGenCtx {
locals: HashMap<String, u32>,
extra_locals: Vec<ValType>,
next_local: u32,
uses_recur: bool,
arg_indices: Vec<u32>,
instructions: Vec<Instruction<'static>>,
fn_index: HashMap<String, u32>,
fn_arity: HashMap<String, u32>,
fn_has_rest: HashMap<String, u32>,
runtime_fn_index: HashMap<String, u32>,
tag_index: HashMap<String, u32>,
record_field_tags: HashMap<u32, Vec<u32>>,
block_depth: u32,
string_pool: HashMap<String, u32>,
}
impl WasmGenCtx {
fn new(num_params: u32, env: WasmCompileEnv) -> Self {
WasmGenCtx {
locals: HashMap::new(),
extra_locals: Vec::new(),
next_local: num_params,
uses_recur: false,
arg_indices: Vec::new(),
instructions: Vec::new(),
fn_index: env.fn_index,
fn_arity: env.fn_arity,
fn_has_rest: env.fn_has_rest,
runtime_fn_index: env.runtime_fn_index,
tag_index: env.tag_index,
record_field_tags: env.record_field_tags,
block_depth: 0,
string_pool: env.string_pool,
}
}
fn alloc_local(&mut self) -> u32 {
self.alloc_local_typed(ValType::F64)
}
fn alloc_local_typed(&mut self, vt: ValType) -> u32 {
let idx = self.next_local;
self.next_local += 1;
self.extra_locals.push(vt);
idx
}
fn declare_local(&mut self, name: &str) -> u32 {
let idx = self.alloc_local();
self.locals.insert(name.to_owned(), idx);
idx
}
fn emit(&mut self, instr: Instruction<'static>) {
self.instructions.push(instr);
}
}
fn compute_fn_arity(args: &CalcitFnArgs) -> (u32, Option<u32>) {
match args {
CalcitFnArgs::Args(v) => (v.len() as u32, None),
CalcitFnArgs::MarkedArgs(labels) => {
let mut fixed: u32 = 0;
let mut rest_param_count: u32 = 0;
let mut rest_seen = false;
for label in labels {
match label {
CalcitArgLabel::Idx(_) => {
if rest_seen {
rest_param_count += 1;
} else {
fixed += 1;
}
}
CalcitArgLabel::OptionalMark => {}
CalcitArgLabel::RestMark => {
rest_seen = true;
}
}
}
if rest_seen && rest_param_count > 0 {
(fixed + 1, Some(fixed))
} else {
(fixed, None)
}
}
}
}
fn compile_fn(
_name: &str,
export_name: &str,
args: &CalcitFnArgs,
body: &[Calcit],
env: &WasmCompileEnv,
) -> Result<CompiledFn, String> {
let mut param_names = Vec::new();
match args {
CalcitFnArgs::Args(idxs) => {
for idx in idxs {
param_names.push(CalcitLocal::read_name(*idx));
}
}
CalcitFnArgs::MarkedArgs(labels) => {
let mut seen_rest = false;
for label in labels {
match label {
CalcitArgLabel::Idx(idx) => {
param_names.push(CalcitLocal::read_name(*idx));
if seen_rest {
seen_rest = false;
}
}
CalcitArgLabel::OptionalMark => {
}
CalcitArgLabel::RestMark => {
seen_rest = true;
}
}
}
}
}
let arity = param_names.len();
let mut ctx = WasmGenCtx::new(arity as u32, env.clone());
for (i, pname) in param_names.iter().enumerate() {
ctx.locals.insert(pname.clone(), i as u32);
ctx.arg_indices.push(i as u32);
}
ctx.uses_recur = body.iter().any(check_uses_recur);
if ctx.uses_recur {
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Result(ValType::F64)));
emit_body(&mut ctx, body)?;
ctx.emit(Instruction::End); } else {
emit_body(&mut ctx, body)?;
}
Ok(CompiledFn {
export_name: Some(export_name.to_owned()),
params: vec![ValType::F64; arity],
results: vec![ValType::F64],
locals: ctx.extra_locals,
instructions: ctx.instructions,
})
}
fn check_uses_recur(expr: &Calcit) -> bool {
match expr {
Calcit::Proc(CalcitProc::Recur) => true,
Calcit::List(xs) => {
if let Some(Calcit::Syntax(CalcitSyntax::Defn, _)) = xs.first() {
return false;
}
xs.iter().any(check_uses_recur)
}
_ => false,
}
}
fn emit_body(ctx: &mut WasmGenCtx, exprs: &[Calcit]) -> Result<(), String> {
if exprs.is_empty() {
ctx.emit(f64_const(0.0));
return Ok(());
}
for (i, expr) in exprs.iter().enumerate() {
emit_expr(ctx, expr)?;
if i < exprs.len() - 1 {
ctx.emit(Instruction::Drop);
}
}
Ok(())
}
fn emit_expr(ctx: &mut WasmGenCtx, expr: &Calcit) -> Result<(), String> {
match expr {
Calcit::Number(n) => {
ctx.emit(f64_const(*n));
}
Calcit::Bool(true) => {
ctx.emit(f64_const(1.0));
}
Calcit::Bool(false) | Calcit::Nil => {
ctx.emit(f64_const(0.0));
}
Calcit::Tag(t) => {
let tag_str = t.to_string();
let id = *ctx
.tag_index
.get(&tag_str)
.ok_or_else(|| format!("unknown tag in WASM codegen: {tag_str}"))?;
ctx.emit(f64_const(id as f64));
}
Calcit::Local(local) => {
let name = &*local.sym;
let idx = *ctx.locals.get(name).ok_or_else(|| format!("undefined local variable: {name}"))?;
ctx.emit(Instruction::LocalGet(idx));
}
Calcit::List(xs) if !xs.is_empty() => {
emit_call_expr(ctx, xs)?;
}
Calcit::Import(import) if import.def.as_ref() == "do" => {
ctx.emit(f64_const(0.0));
}
Calcit::Str(s) => {
let ptr = ctx
.string_pool
.get(s.as_ref())
.ok_or_else(|| format!("string literal not found in pool: {s}"))?;
ctx.emit(f64_const(*ptr as f64));
}
Calcit::Record(_) => return Err("Record literals not supported in WASM codegen (use constructor)".into()),
Calcit::Tuple(_) => return Err("Tuple literals not supported in WASM codegen (use constructor)".into()),
_ => return Err(format!("unsupported WASM expression: {expr}")),
}
Ok(())
}
fn emit_call_expr(ctx: &mut WasmGenCtx, xs: &crate::calcit::CalcitList) -> Result<(), String> {
let head = &xs[0];
let args_list: Vec<Calcit> = xs.drop_left().to_vec();
match head {
Calcit::Syntax(syn, _) => match syn {
CalcitSyntax::If => emit_if(ctx, &args_list),
CalcitSyntax::CoreLet => emit_let(ctx, &args_list),
CalcitSyntax::Match => emit_match(ctx, &args_list),
CalcitSyntax::HintFn => {
ctx.emit(f64_const(0.0));
Ok(())
}
CalcitSyntax::AssertType => {
if args_list.is_empty() {
return Err("assert-type expects at least 1 arg".into());
}
emit_expr(ctx, &args_list[0])
}
CalcitSyntax::Defn => Err("nested defn not supported in WASM".into()),
_ => Err(format!("unsupported syntax in WASM: {syn}")),
},
Calcit::Proc(proc) => emit_proc_call(ctx, proc, &args_list),
Calcit::Method(name, kind) => match kind {
MethodKind::Invoke(_) => emit_method_invoke(ctx, name.as_ref(), &args_list),
_ => Err(format!("unsupported method in WASM: .{name}")),
},
Calcit::Import(import) => {
if import.def.as_ref() == "do" {
return emit_body(ctx, &args_list);
}
let qualified = format!("{}/{}", import.ns, import.def);
let fn_idx = ctx
.fn_index
.get(&qualified)
.or_else(|| ctx.fn_index.get(import.def.as_ref()))
.ok_or_else(|| format!("unknown function: {qualified}"))?;
let fn_idx = *fn_idx;
let target_arity = ctx
.fn_arity
.get(&qualified)
.or_else(|| ctx.fn_arity.get(import.def.as_ref()))
.copied()
.unwrap_or(args_list.len() as u32);
let rest_fixed = ctx
.fn_has_rest
.get(&qualified)
.or_else(|| ctx.fn_has_rest.get(import.def.as_ref()))
.copied();
emit_call_args(ctx, &args_list, target_arity, rest_fixed)?;
ctx.emit(Instruction::Call(fn_idx));
Ok(())
}
Calcit::Symbol { sym, .. } => {
let name = sym.as_ref();
if matches!(name, "println" | "eprintln" | "echo") {
let log_idx = HOST_IMPORTS
.iter()
.position(|imp| imp.module == "io" && imp.name == "log_value")
.expect("log_value host import") as u32;
for arg in &args_list {
emit_expr(ctx, arg)?;
ctx.emit(Instruction::Call(log_idx));
ctx.emit(Instruction::Drop); }
ctx.emit(f64_const(0.0)); return Ok(());
}
let fn_idx = *ctx.fn_index.get(name).ok_or_else(|| format!("unknown function: {sym}"))?;
let target_arity = ctx.fn_arity.get(name).copied().unwrap_or(args_list.len() as u32);
let rest_fixed = ctx.fn_has_rest.get(name).copied();
emit_call_args(ctx, &args_list, target_arity, rest_fixed)?;
ctx.emit(Instruction::Call(fn_idx));
Ok(())
}
Calcit::Registered(name) => {
let name = name.as_ref();
if matches!(name, "println" | "eprintln" | "echo") {
let log_idx = HOST_IMPORTS
.iter()
.position(|imp| imp.module == "io" && imp.name == "log_value")
.expect("log_value host import") as u32;
for arg in &args_list {
emit_expr(ctx, arg)?;
ctx.emit(Instruction::Call(log_idx));
ctx.emit(Instruction::Drop);
}
ctx.emit(f64_const(0.0)); return Ok(());
}
Err(format!("unsupported registered proc in WASM: {name}"))
}
_ => Err(format!("unsupported call head in WASM: {head}")),
}
}
fn emit_proc_call(ctx: &mut WasmGenCtx, proc: &CalcitProc, args: &[Calcit]) -> Result<(), String> {
match proc {
CalcitProc::NativeAdd => emit_binary(ctx, Instruction::F64Add, args),
CalcitProc::NativeMinus => emit_binary(ctx, Instruction::F64Sub, args),
CalcitProc::NativeMultiply => emit_binary(ctx, Instruction::F64Mul, args),
CalcitProc::NativeDivide => emit_binary(ctx, Instruction::F64Div, args),
CalcitProc::NativeNumberRem => {
if args.len() != 2 {
return Err("rem expects 2 args".into());
}
emit_expr(ctx, &args[0])?; emit_expr(ctx, &args[0])?; emit_expr(ctx, &args[1])?; ctx.emit(Instruction::F64Div);
ctx.emit(Instruction::F64Trunc);
emit_expr(ctx, &args[1])?; ctx.emit(Instruction::F64Mul);
ctx.emit(Instruction::F64Sub);
Ok(())
}
CalcitProc::NativeLessThan => emit_cmp(ctx, Instruction::F64Lt, args),
CalcitProc::NativeGreaterThan => emit_cmp(ctx, Instruction::F64Gt, args),
CalcitProc::NativeEquals | CalcitProc::Identical => emit_cmp(ctx, Instruction::F64Eq, args),
CalcitProc::NativeCompare => {
if args.len() != 2 {
return Err(format!("&compare expects 2 args, got {}", args.len()));
}
let a = ctx.alloc_local();
let b = ctx.alloc_local();
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::LocalSet(a));
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(b));
ctx.emit(Instruction::LocalGet(a));
ctx.emit(Instruction::LocalGet(b));
ctx.emit(Instruction::F64Lt);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
ctx.emit(f64_const(-1.0));
ctx.emit(Instruction::Else);
ctx.emit(Instruction::LocalGet(a));
ctx.emit(Instruction::LocalGet(b));
ctx.emit(Instruction::F64Gt);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
ctx.emit(f64_const(1.0));
ctx.emit(Instruction::Else);
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
Ok(())
}
CalcitProc::Not => {
if args.len() != 1 {
return Err("not expects 1 arg".into());
}
ctx.emit(f64_const(1.0)); ctx.emit(f64_const(0.0)); emit_expr(ctx, &args[0])?;
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::F64Eq); ctx.emit(Instruction::Select);
Ok(())
}
CalcitProc::Floor => emit_unary(ctx, Instruction::F64Floor, args),
CalcitProc::Ceil => emit_unary(ctx, Instruction::F64Ceil, args),
CalcitProc::Round => emit_unary(ctx, Instruction::F64Nearest, args),
CalcitProc::Sqrt => emit_unary(ctx, Instruction::F64Sqrt, args),
CalcitProc::Sin => emit_host_call(ctx, "sin", args),
CalcitProc::Cos => emit_host_call(ctx, "cos", args),
CalcitProc::Pow => emit_host_call(ctx, "pow", args),
CalcitProc::TypeOf => emit_type_of(ctx, args),
CalcitProc::ListQuestion => emit_type_predicate(ctx, "list", args),
CalcitProc::TagQuestion => emit_type_predicate(ctx, "tag", args),
CalcitProc::SymbolQuestion => emit_type_predicate(ctx, "symbol", args),
CalcitProc::NilQuestion => emit_type_predicate(ctx, "nil", args),
CalcitProc::StringQuestion => emit_type_predicate(ctx, "string", args),
CalcitProc::MapQuestion => emit_type_predicate(ctx, "map", args),
CalcitProc::NumberQuestion => emit_type_predicate(ctx, "number", args),
CalcitProc::BoolQuestion => emit_type_predicate(ctx, "bool", args),
CalcitProc::SetQuestion => emit_type_predicate(ctx, "set", args),
CalcitProc::TupleQuestion => emit_type_predicate(ctx, "tuple", args),
CalcitProc::RecordQuestion => emit_type_predicate(ctx, "record", args),
CalcitProc::FnQuestion => emit_type_predicate(ctx, "fn", args),
CalcitProc::Recur => {
if args.len() != ctx.arg_indices.len() {
return Err(format!(
"recur arity mismatch: expected {}, got {}",
ctx.arg_indices.len(),
args.len()
));
}
let mut temps = Vec::new();
for arg in args {
let tmp = ctx.alloc_local();
emit_expr(ctx, arg)?;
ctx.emit(Instruction::LocalSet(tmp));
temps.push(tmp);
}
for (i, &tmp) in temps.iter().enumerate() {
ctx.emit(Instruction::LocalGet(tmp));
ctx.emit(Instruction::LocalSet(ctx.arg_indices[i]));
}
ctx.emit(Instruction::Br(ctx.block_depth)); ctx.emit(Instruction::Unreachable);
Ok(())
}
CalcitProc::NativeRecord => emit_record_new(ctx, args),
CalcitProc::NativeRecordNth => emit_record_nth(ctx, args),
CalcitProc::NativeRecordGet => emit_record_get(ctx, args),
CalcitProc::NativeRecordCount => emit_record_count(ctx, args),
CalcitProc::NativeRecordFieldTag => emit_record_field_tag(ctx, args),
CalcitProc::NativeRecordAssoc | CalcitProc::NativeRecordAssocAt | CalcitProc::NativeRecordWith => {
Err("Record mutation (assoc/with) not yet supported in WASM codegen".into())
}
CalcitProc::NativeRecordFromMap
| CalcitProc::NativeRecordToMap
| CalcitProc::NativeRecordExtendAs
| CalcitProc::NativeRecordPartial
| CalcitProc::NativeRecordContains
| CalcitProc::NativeRecordStruct
| CalcitProc::NativeRecordGetName
| CalcitProc::NativeRecordImpls
| CalcitProc::NativeRecordWithAt
| CalcitProc::NativeLooseRecord => Err(format!("Record operation {proc} not yet supported in WASM codegen")),
CalcitProc::NativeRecordMatches => emit_record_matches(ctx, args),
CalcitProc::NativeTuple => emit_tuple_new(ctx, args),
CalcitProc::NativeTupleNth => emit_tuple_nth(ctx, args),
CalcitProc::NativeTupleCount => emit_tuple_count(ctx, args),
CalcitProc::NativeTupleValidateEnum => {
for arg in args {
emit_expr(ctx, arg)?;
ctx.emit(Instruction::Drop);
}
ctx.emit(f64_const(0.0)); Ok(())
}
CalcitProc::NativeEnumTupleNew
| CalcitProc::NativeTupleImpls
| CalcitProc::NativeTupleParams
| CalcitProc::NativeTupleEnum
| CalcitProc::NativeTupleImplTraits
| CalcitProc::NativeTupleEnumHasVariant
| CalcitProc::NativeTupleEnumVariantArity => Err(format!("Tuple operation {proc} not yet supported in WASM codegen")),
CalcitProc::NativeTupleAssoc => emit_tuple_assoc(ctx, args),
CalcitProc::BitShl => emit_bitwise_binary(ctx, Instruction::I32Shl, args),
CalcitProc::BitShr => emit_bitwise_binary(ctx, Instruction::I32ShrS, args),
CalcitProc::BitAnd => emit_bitwise_binary(ctx, Instruction::I32And, args),
CalcitProc::BitOr => emit_bitwise_binary(ctx, Instruction::I32Or, args),
CalcitProc::BitXor => emit_bitwise_binary(ctx, Instruction::I32Xor, args),
CalcitProc::BitNot => {
if args.len() != 1 {
return Err("bit-not expects 1 arg".into());
}
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::I32TruncF64S);
ctx.emit(Instruction::I32Const(-1)); ctx.emit(Instruction::I32Xor);
ctx.emit(Instruction::F64ConvertI32S);
Ok(())
}
CalcitProc::Raise => {
for arg in args {
emit_expr(ctx, arg)?;
ctx.emit(Instruction::Drop);
}
ctx.emit(Instruction::Unreachable);
Ok(())
}
CalcitProc::List => emit_list_new(ctx, args),
CalcitProc::Append => emit_list_append(ctx, args),
CalcitProc::Prepend => emit_list_prepend(ctx, args),
CalcitProc::Butlast => emit_list_butlast(ctx, args),
CalcitProc::NativeListCount => emit_ds_count(ctx, args),
CalcitProc::NativeListNth => emit_list_nth(ctx, args),
CalcitProc::NativeListFirst => emit_list_first(ctx, args),
CalcitProc::NativeListRest => emit_list_rest(ctx, args),
CalcitProc::NativeListEmpty => emit_ds_empty(ctx, args),
CalcitProc::NativeListSlice => emit_list_slice(ctx, args),
CalcitProc::NativeListReverse => emit_list_reverse(ctx, args),
CalcitProc::NativeListConcat => emit_list_concat(ctx, args),
CalcitProc::NativeListAssoc => emit_list_assoc(ctx, args),
CalcitProc::NativeListDissoc => emit_list_dissoc(ctx, args),
CalcitProc::NativeListContains => emit_list_contains(ctx, args),
CalcitProc::NativeListIncludes => emit_list_includes(ctx, args),
CalcitProc::NativeListQ => emit_list_q(ctx, args),
CalcitProc::NativeBufListNew => emit_buf_list_new(ctx, args),
CalcitProc::NativeBufListPush => emit_buf_list_push(ctx, args),
CalcitProc::NativeBufListConcat => emit_buf_list_concat(ctx, args),
CalcitProc::NativeBufListToList => emit_buf_list_to_list(ctx, args),
CalcitProc::NativeBufListCount => emit_buf_list_count(ctx, args),
CalcitProc::NativeMap => emit_map_new(ctx, args),
CalcitProc::NativeMapGet => emit_map_get_op(ctx, args),
CalcitProc::NativeMapAssoc => emit_map_assoc(ctx, args),
CalcitProc::NativeMapDissoc => emit_map_dissoc(ctx, args),
CalcitProc::NativeMapCount => emit_ds_count(ctx, args),
CalcitProc::NativeMapEmpty => emit_ds_empty(ctx, args),
CalcitProc::NativeMapContains => emit_map_contains(ctx, args),
CalcitProc::NativeMapIncludes => emit_map_includes(ctx, args),
CalcitProc::ToPairs => emit_map_to_pairs(ctx, args),
CalcitProc::NativeMapToList => emit_map_to_list(ctx, args),
CalcitProc::Set => emit_set_new(ctx, args),
CalcitProc::NativeInclude => emit_set_include(ctx, args),
CalcitProc::NativeExclude => emit_set_exclude(ctx, args),
CalcitProc::NativeSetCount => emit_ds_count(ctx, args),
CalcitProc::NativeSetEmpty => emit_ds_empty(ctx, args),
CalcitProc::NativeSetIncludes => emit_set_includes(ctx, args),
CalcitProc::NativeDifference => emit_set_difference(ctx, args),
CalcitProc::NativeUnion => emit_set_union(ctx, args),
CalcitProc::NativeMerge => emit_map_merge(ctx, args),
CalcitProc::NativeMapDiffNew => emit_map_diff_new(ctx, args),
CalcitProc::NativeMapDiffKeys => emit_map_diff_keys(ctx, args),
CalcitProc::NativeMapCommonKeys => emit_map_common_keys(ctx, args),
CalcitProc::Range => emit_range(ctx, args),
CalcitProc::NativeHash => emit_hash_proc(ctx, args),
_ => Err(format!("unsupported proc in WASM: {proc}")),
}
}
fn emit_unary(ctx: &mut WasmGenCtx, instr: Instruction<'static>, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err(format!("{instr:?} expects 1 arg, got {}", args.len()));
}
emit_expr(ctx, &args[0])?;
ctx.emit(instr);
Ok(())
}
fn emit_type_of(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err(format!("type-of expects 1 arg, got {}", args.len()));
}
let number_tag = get_type_tag(ctx, "number");
let v_local = ctx.alloc_local_typed(ValType::F64);
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::LocalSet(v_local));
let is_valid_ptr = ctx.alloc_local_typed(ValType::I32);
let raw_base = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(v_local));
ctx.emit(Instruction::LocalGet(v_local));
ctx.emit(Instruction::F64Trunc);
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::LocalGet(v_local));
ctx.emit(f64_const((HEAP_BASE + 8) as f64));
ctx.emit(Instruction::F64Ge);
ctx.emit(Instruction::I32And);
ctx.emit(Instruction::LocalGet(v_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(v_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);
Ok(())
}
fn emit_type_predicate(ctx: &mut WasmGenCtx, type_name: &str, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err(format!("{}? expects 1 arg, got {}", type_name, args.len()));
}
let expected_tag = get_type_tag(ctx, type_name);
emit_type_of(ctx, args)?;
ctx.emit(f64_const(expected_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_host_call(ctx: &mut WasmGenCtx, name: &str, args: &[Calcit]) -> Result<(), String> {
let import_idx = HOST_IMPORTS
.iter()
.position(|imp| imp.name == name)
.ok_or_else(|| format!("unknown host import: {name}"))?;
let expected_arity = HOST_IMPORTS[import_idx].arity;
if args.len() != expected_arity {
return Err(format!("{name} expects {expected_arity} args, got {}", args.len()));
}
for arg in args {
emit_expr(ctx, arg)?;
}
ctx.emit(Instruction::Call(import_idx as u32));
Ok(())
}
fn emit_binary(ctx: &mut WasmGenCtx, instr: Instruction<'static>, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err(format!("{instr:?} expects 2 args, got {}", args.len()));
}
emit_expr(ctx, &args[0])?;
emit_expr(ctx, &args[1])?;
ctx.emit(instr);
Ok(())
}
fn emit_cmp(ctx: &mut WasmGenCtx, instr: Instruction<'static>, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err(format!("{instr:?} expects 2 args, got {}", args.len()));
}
ctx.emit(f64_const(1.0));
ctx.emit(f64_const(0.0));
emit_expr(ctx, &args[0])?;
emit_expr(ctx, &args[1])?;
ctx.emit(instr);
ctx.emit(Instruction::Select);
Ok(())
}
fn emit_bitwise_binary(ctx: &mut WasmGenCtx, instr: Instruction<'static>, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err(format!("{instr:?} expects 2 args, got {}", args.len()));
}
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::I32TruncF64S);
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::I32TruncF64S);
ctx.emit(instr);
ctx.emit(Instruction::F64ConvertI32S);
Ok(())
}
fn emit_if(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() < 2 || args.len() > 3 {
return Err(format!("if expects 2-3 args, got {}", args.len()));
}
emit_expr(ctx, &args[0])?;
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::F64Ne);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
ctx.block_depth += 1;
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::Else);
if args.len() == 3 {
emit_expr(ctx, &args[2])?;
} else {
ctx.emit(f64_const(0.0));
}
ctx.block_depth -= 1;
ctx.emit(Instruction::End);
Ok(())
}
fn emit_let(ctx: &mut WasmGenCtx, body: &[Calcit]) -> Result<(), String> {
if body.is_empty() {
ctx.emit(f64_const(0.0));
return Ok(());
}
let pair = &body[0];
let rest = &body[1..];
match pair {
Calcit::Nil => emit_body(ctx, rest),
Calcit::List(xs) if xs.is_empty() => emit_body(ctx, rest),
Calcit::List(xs) if xs.len() == 2 => {
let var_name = match &xs[0] {
Calcit::Local(CalcitLocal { sym, .. }) => sym.to_string(),
Calcit::Symbol { sym, .. } => sym.to_string(),
other => return Err(format!("let binding expected symbol, got: {other}")),
};
emit_expr(ctx, &xs[1])?;
let idx = ctx.declare_local(&var_name);
ctx.emit(Instruction::LocalSet(idx));
if rest.len() == 1 {
if let Calcit::List(inner) = &rest[0] {
if let Some(Calcit::Syntax(CalcitSyntax::CoreLet, _)) = inner.first() {
let inner_body: Vec<Calcit> = inner.drop_left().to_vec();
return emit_let(ctx, &inner_body);
}
}
}
emit_body(ctx, rest)
}
_ => Err(format!("unsupported let binding form: {pair}")),
}
}
fn emit_match(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.is_empty() {
return Err("match requires a value and branches".into());
}
emit_expr(ctx, &args[0])?;
let ptr_f64 = ctx.alloc_local();
ctx.emit(Instruction::LocalSet(ptr_f64));
let tag_local = ctx.alloc_local();
ctx.emit(Instruction::LocalGet(ptr_f64));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::F64Load(mem_arg_f64(8)));
ctx.emit(Instruction::LocalSet(tag_local));
let branches = &args[1..];
let mut tag_branches: Vec<(&Calcit, &Calcit)> = Vec::new(); let mut wildcard_body: Option<&Calcit> = None;
for branch in branches {
let Calcit::List(pair) = branch else {
return Err(format!("match branch expected a pair, got: {branch}"));
};
if pair.len() != 2 {
return Err(format!("match branch expected 2 elements, got {}", pair.len()));
}
let pattern = &pair[0];
let body = &pair[1];
match pattern {
Calcit::Symbol { sym, .. } | Calcit::Local(CalcitLocal { sym, .. }) if sym.as_ref() == "_" => {
wildcard_body = Some(body);
}
Calcit::List(_) => {
tag_branches.push((pattern, body));
}
other => return Err(format!("unsupported match pattern: {other}")),
}
}
let num_tag_branches = tag_branches.len();
if num_tag_branches == 0 {
if let Some(body) = wildcard_body {
emit_expr(ctx, body)?;
} else {
ctx.emit(f64_const(0.0));
}
return Ok(());
}
for (i, (pattern, body)) in tag_branches.iter().enumerate() {
let Calcit::List(pat_xs) = pattern else {
return Err(format!("match pattern expected list, got: {pattern}"));
};
let tag_str = match &pat_xs[0] {
Calcit::Tag(t) => t.to_string(),
other => return Err(format!("match pattern expected tag, got: {other}")),
};
let tag_id = *ctx
.tag_index
.get(&tag_str)
.ok_or_else(|| format!("unknown tag in match pattern: {tag_str}"))?;
ctx.emit(Instruction::LocalGet(tag_local));
ctx.emit(f64_const(tag_id as f64));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Result(ValType::F64)));
ctx.block_depth += 1;
let binding_count = pat_xs.len() - 1;
for bind_idx in 0..binding_count {
let binding = &pat_xs[1 + bind_idx];
let bind_name = match binding {
Calcit::Local(CalcitLocal { sym, .. }) => sym.to_string(),
Calcit::Symbol { sym, .. } => sym.to_string(),
other => return Err(format!("match binding expected symbol, got: {other}")),
};
let offset = ((2 + bind_idx) * 8) as u64;
ctx.emit(Instruction::LocalGet(ptr_f64));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::F64Load(mem_arg_f64(offset)));
let idx = ctx.declare_local(&bind_name);
ctx.emit(Instruction::LocalSet(idx));
}
emit_expr(ctx, body)?;
ctx.emit(Instruction::Else);
if i == num_tag_branches - 1 {
if let Some(wb) = wildcard_body {
emit_expr(ctx, wb)?;
} else {
ctx.emit(f64_const(0.0));
}
}
}
for _ in 0..num_tag_branches {
ctx.block_depth -= 1;
ctx.emit(Instruction::End);
}
Ok(())
}
const BUILTIN_TYPE_TAGS: &[&str] = &[
"buf-list", "list", "map", "set", "tuple", "record", "number", "bool", "nil", "tag", "fn", "string", "symbol",
];
fn collect_all_tags_from(fn_defs: &[(String, String, CalcitFnArgs, Vec<Calcit>)]) -> HashMap<String, u32> {
let mut tags: Vec<String> = Vec::new();
for t in BUILTIN_TYPE_TAGS {
tags.push((*t).to_string());
}
for (_, _, _, body) in fn_defs {
for expr in body {
collect_tags_from_expr(expr, &mut tags);
}
}
tags.sort();
tags.dedup();
tags.into_iter().enumerate().map(|(i, t)| (t, (i + 1) as u32)).collect()
}
fn collect_tags_from_expr(expr: &Calcit, tags: &mut Vec<String>) {
match expr {
Calcit::Tag(t) => {
tags.push(t.to_string());
}
Calcit::List(xs) => {
for x in xs.iter() {
collect_tags_from_expr(x, tags);
}
}
Calcit::Struct(s) => {
tags.push(s.name.to_string());
for f in s.fields.iter() {
tags.push(f.to_string());
}
}
Calcit::Import(CalcitImport { ns, def, .. }) => {
if let Ok(struct_def) = resolve_struct_ref(expr) {
tags.push(struct_def.name.to_string());
for f in struct_def.fields.iter() {
tags.push(f.to_string());
}
}
let _ = (ns, def); }
_ => {}
}
}
fn build_string_pool(
fn_defs: &[(String, String, CalcitFnArgs, Vec<Calcit>)],
tag_index: &HashMap<String, u32>,
) -> (HashMap<String, u32>, Vec<u8>, i32) {
let mut strings: Vec<String> = Vec::new();
for (_, _, _, body) in fn_defs {
for expr in body {
collect_strings_from_expr(expr, &mut strings);
}
}
strings.sort();
strings.dedup();
if strings.is_empty() {
return (HashMap::new(), Vec::new(), HEAP_BASE);
}
let string_tag_id = *tag_index.get("string").expect("string type tag must exist") as i32;
let mut pool: HashMap<String, u32> = HashMap::new();
let mut data: Vec<u8> = Vec::new();
let mut offset = HEAP_BASE as u32;
for s in &strings {
let byte_len = s.len() as u32;
data.extend_from_slice(&(HEAP_MAGIC as u32).to_le_bytes());
data.extend_from_slice(&(string_tag_id as u32).to_le_bytes());
let logical_ptr = offset + 8;
pool.insert(s.clone(), logical_ptr);
data.extend_from_slice(&(byte_len as f64).to_le_bytes());
data.extend_from_slice(s.as_bytes());
let padded_len = (byte_len + 7) & !7;
for _ in byte_len..padded_len {
data.push(0);
}
offset += 8 + 8 + padded_len;
}
let heap_start = offset as i32;
(pool, data, heap_start)
}
fn collect_record_field_tags_from_program(
program_data: &program::CompiledProgram,
tag_index: &HashMap<String, u32>,
) -> HashMap<u32, Vec<u32>> {
let mut result = HashMap::new();
for (_, file_info) in program_data {
for (_, compiled) in &file_info.defs {
let struct_def =
try_parse_defrecord_form(&compiled.preprocessed_code).or_else(|| try_parse_defrecord_form(&compiled.codegen_form));
let Some(struct_def) = struct_def else {
continue;
};
let Some(struct_tag_id) = tag_index.get(struct_def.name.ref_str()) else {
continue;
};
let field_tag_ids = struct_def
.fields
.iter()
.filter_map(|field| tag_index.get(field.ref_str()).copied())
.collect::<Vec<_>>();
result.insert(*struct_tag_id, field_tag_ids);
}
}
result
}
fn collect_strings_from_expr(expr: &Calcit, strings: &mut Vec<String>) {
match expr {
Calcit::Str(s) => {
strings.push(s.to_string());
}
Calcit::List(xs) => {
for x in xs.iter() {
collect_strings_from_expr(x, strings);
}
}
_ => {}
}
}
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
}
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);
}
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}")),
}
}
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(())
}
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));
}
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));
}
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)
}
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
}
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
}
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));
}
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
}
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
}
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(())
}
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(())
}
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
}
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
}
fn emit_list_new(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
let count = args.len();
let total_bytes = ((1 + count) * 8) as i32;
let ptr = ctx.alloc_local_typed(ValType::I32);
emit_bump_alloc(ctx, total_bytes, ptr, "list");
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(f64_const(count as f64));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
for (i, arg) in args.iter().enumerate() {
ctx.emit(Instruction::LocalGet(ptr));
emit_expr(ctx, arg)?;
ctx.emit(Instruction::F64Store(mem_arg_f64(((1 + i) * 8) as u64)));
}
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_list_nth(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&list:nth expects 2 args".into());
}
let ptr = emit_ptr_to_i32(ctx, &args[0])?;
emit_expr(ctx, &args[1])?;
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(ptr));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
Ok(())
}
fn emit_list_first(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("&list:first expects 1 arg".into());
}
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::F64Load(mem_arg_f64(8)));
Ok(())
}
fn emit_list_rest(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("&list:rest expects 1 arg".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
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);
Ok(())
}
fn emit_list_append(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("append expects 2 args".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
let old_count = emit_load_count_i32(ctx, src);
let elem = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(elem));
let new_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(old_count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
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, 8);
emit_copy_f64_loop(ctx, dst_base, src_base, old_count);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(old_count));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(elem));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_list_prepend(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("prepend expects 2 args".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
let old_count = emit_load_count_i32(ctx, src);
let elem = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(elem));
let new_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(old_count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
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");
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::LocalGet(elem));
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
let dst_base = emit_addr_offset(ctx, dst, 16);
let src_base = emit_addr_offset(ctx, src, 8);
emit_copy_f64_loop(ctx, dst_base, src_base, old_count);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_list_butlast(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("butlast expects 1 arg".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
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, 8);
emit_copy_f64_loop(ctx, dst_base, src_base, new_count);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_list_slice(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() < 2 || args.len() > 3 {
return Err("&list:slice expects 2-3 args".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
let count = emit_load_count_i32(ctx, src);
let start = ctx.alloc_local_typed(ValType::I32);
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(start));
let end = ctx.alloc_local_typed(ValType::I32);
if args.len() == 3 {
emit_expr(ctx, &args[2])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(end));
} else {
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::LocalSet(end));
}
let new_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(end));
ctx.emit(Instruction::LocalGet(start));
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 src_base = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(src));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(start));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(src_base));
let dst_base = emit_addr_offset(ctx, dst, 8);
emit_copy_f64_loop(ctx, dst_base, src_base, new_count);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_list_reverse(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("&list:reverse expects 1 arg".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
let count = emit_load_count_i32(ctx, src);
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));
let dst = emit_alloc_with_count(ctx, count, total_slots, "list");
let i = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(src));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_list_concat(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&list:concat expects 2 args".into());
}
let src_a = emit_ptr_to_i32(ctx, &args[0])?;
let count_a = emit_load_count_i32(ctx, src_a);
let src_b = emit_ptr_to_i32(ctx, &args[1])?;
let count_b = emit_load_count_i32(ctx, src_b);
let new_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(count_a));
ctx.emit(Instruction::LocalGet(count_b));
ctx.emit(Instruction::I32Add);
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_a = emit_addr_offset(ctx, dst, 8);
let src_base_a = emit_addr_offset(ctx, src_a, 8);
emit_copy_f64_loop(ctx, dst_base_a, src_base_a, count_a);
let dst_base_b = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(count_a));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(dst_base_b));
let src_base_b = emit_addr_offset(ctx, src_b, 8);
emit_copy_f64_loop(ctx, dst_base_b, src_base_b, count_b);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_list_assoc(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 3 {
return Err("&list:assoc expects 3 args".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
let count = emit_load_count_i32(ctx, src);
let idx = ctx.alloc_local_typed(ValType::I32);
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(idx));
let val = ctx.alloc_local();
emit_expr(ctx, &args[2])?;
ctx.emit(Instruction::LocalSet(val));
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));
let dst = emit_alloc_with_count(ctx, count, total_slots, "list");
let dst_base = emit_addr_offset(ctx, dst, 8);
let src_base = emit_addr_offset(ctx, src, 8);
emit_copy_f64_loop(ctx, dst_base, src_base, count);
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)));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_list_dissoc(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&list:dissoc expects 2 args".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
let count = emit_load_count_i32(ctx, src);
let idx = ctx.alloc_local_typed(ValType::I32);
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(idx));
let new_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(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 before_n = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(idx));
ctx.emit(Instruction::LocalSet(before_n));
let dst_b1 = emit_addr_offset(ctx, dst, 8);
let src_b1 = emit_addr_offset(ctx, src, 8);
emit_copy_f64_loop(ctx, dst_b1, src_b1, before_n);
let after_n = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::LocalGet(idx));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalSet(after_n));
let dst_b2 = ctx.alloc_local_typed(ValType::I32);
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::LocalSet(dst_b2));
let src_b2 = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(src));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(idx));
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(src_b2));
emit_copy_f64_loop(ctx, dst_b2, src_b2, after_n);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_list_q(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err(format!("list? expects 1 arg, got {}", args.len()));
}
let list_tag = get_type_tag(ctx, "list");
ctx.emit(f64_const(1.0));
ctx.emit(f64_const(0.0));
emit_type_of(ctx, args)?;
ctx.emit(f64_const(list_tag));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::Select);
Ok(())
}
fn emit_list_contains(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&list:contains? expects 2 args".into());
}
let ptr = emit_ptr_to_i32(ctx, &args[0])?;
let count = emit_load_count_i32(ctx, ptr);
ctx.emit(f64_const(1.0));
ctx.emit(f64_const(0.0));
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32LtU);
ctx.emit(Instruction::Select);
Ok(())
}
fn emit_list_includes(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&list:includes? expects 2 args".into());
}
let ptr = emit_ptr_to_i32(ctx, &args[0])?;
let count = emit_load_count_i32(ctx, ptr);
let target = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(target));
let result = ctx.alloc_local();
ctx.emit(f64_const(0.0)); ctx.emit(Instruction::LocalSet(result));
let i = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(target));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(f64_const(1.0));
ctx.emit(Instruction::LocalSet(result));
ctx.emit(Instruction::Br(2)); ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End); ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(result));
Ok(())
}
const BUF_LIST_INITIAL_CAPACITY: i32 = 8;
fn emit_buf_list_new(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if !args.is_empty() {
return Err("&buf-list:new expects 0 args".into());
}
let total_slots = 2 + BUF_LIST_INITIAL_CAPACITY;
let byte_size = total_slots * 8;
let ptr = ctx.alloc_local_typed(ValType::I32);
emit_bump_alloc(ctx, byte_size, ptr, "buf-list");
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(f64_const(BUF_LIST_INITIAL_CAPACITY as f64));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_buf_list_push(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&buf-list:push expects 2 args".into());
}
let buf_ptr = emit_ptr_to_i32(ctx, &args[0])?;
let count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::F64Load(mem_arg_f64(8)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(count));
let capacity = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(capacity));
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::LocalGet(capacity));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
{
let new_cap = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(capacity));
ctx.emit(Instruction::I32Const(2));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::LocalSet(new_cap));
let new_size = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(new_cap));
ctx.emit(Instruction::I32Const(2));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::LocalSet(new_size));
let new_ptr = ctx.alloc_local_typed(ValType::I32);
emit_bump_alloc_dynamic(ctx, new_size, new_ptr, "buf-list");
ctx.emit(Instruction::LocalGet(new_ptr));
ctx.emit(Instruction::LocalGet(new_cap));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(new_ptr));
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
let dst_base = emit_addr_offset(ctx, new_ptr, 16);
let src_base = emit_addr_offset(ctx, buf_ptr, 16);
emit_copy_f64_loop(ctx, dst_base, src_base, count);
ctx.emit(Instruction::LocalGet(new_ptr));
ctx.emit(Instruction::LocalSet(buf_ptr));
}
ctx.emit(Instruction::End);
let elem_addr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(elem_addr));
ctx.emit(Instruction::LocalGet(elem_addr));
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
let new_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(new_count));
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::LocalGet(new_count));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_buf_list_concat(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&buf-list:concat expects 2 args".into());
}
let buf_ptr = emit_ptr_to_i32(ctx, &args[0])?;
let list_ptr = emit_ptr_to_i32(ctx, &args[1])?;
let list_count = emit_load_count_i32(ctx, list_ptr);
let i = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty)); ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::LocalGet(list_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
let b_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::F64Load(mem_arg_f64(8)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(b_count));
let b_cap = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(b_cap));
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::LocalGet(b_cap));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
{
let new_cap = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(b_cap));
ctx.emit(Instruction::I32Const(2));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::LocalSet(new_cap));
let needed = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::LocalGet(list_count));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(needed));
ctx.emit(Instruction::LocalGet(new_cap));
ctx.emit(Instruction::LocalGet(needed));
ctx.emit(Instruction::I32LtU);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(needed));
ctx.emit(Instruction::LocalSet(new_cap));
ctx.emit(Instruction::End);
let new_size = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(new_cap));
ctx.emit(Instruction::I32Const(2));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::LocalSet(new_size));
let new_ptr = ctx.alloc_local_typed(ValType::I32);
emit_bump_alloc_dynamic(ctx, new_size, new_ptr, "buf-list");
ctx.emit(Instruction::LocalGet(new_ptr));
ctx.emit(Instruction::LocalGet(new_cap));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(new_ptr));
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
let dst_base = emit_addr_offset(ctx, new_ptr, 16);
let src_base = emit_addr_offset(ctx, buf_ptr, 16);
emit_copy_f64_loop(ctx, dst_base, src_base, b_count);
ctx.emit(Instruction::LocalGet(new_ptr));
ctx.emit(Instruction::LocalSet(buf_ptr));
}
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::F64Load(mem_arg_f64(8)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(b_count));
let elem_addr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(elem_addr));
let list_elem_addr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(list_ptr));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(list_elem_addr));
ctx.emit(Instruction::LocalGet(elem_addr));
ctx.emit(Instruction::LocalGet(list_elem_addr));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
let new_b_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(new_b_count));
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::LocalGet(new_b_count));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Br(0)); ctx.emit(Instruction::End); ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_buf_list_to_list(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("&buf-list:to-list expects 1 arg".into());
}
let buf_ptr = emit_ptr_to_i32(ctx, &args[0])?;
let count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::F64Load(mem_arg_f64(8)));
ctx.emit(Instruction::I32TruncF64U);
ctx.emit(Instruction::LocalSet(count));
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));
let dst = emit_alloc_with_count(ctx, count, total_slots, "list");
let dst_base = emit_addr_offset(ctx, dst, 8);
let src_base = emit_addr_offset(ctx, buf_ptr, 16);
emit_copy_f64_loop(ctx, dst_base, src_base, count);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_buf_list_count(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("&buf-list:count expects 1 arg".into());
}
let buf_ptr = emit_ptr_to_i32(ctx, &args[0])?;
ctx.emit(Instruction::LocalGet(buf_ptr));
ctx.emit(Instruction::F64Load(mem_arg_f64(8))); Ok(())
}
fn emit_map_new(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() % 2 != 0 {
return Err("&{} expects even number of args (key-value pairs)".into());
}
let count = args.len() / 2;
let count_local = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(count as i32));
ctx.emit(Instruction::LocalSet(count_local));
let total_slots = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(count_local));
ctx.emit(Instruction::I32Const(2));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(total_slots));
let root = emit_alloc_with_count(ctx, count_local, total_slots, "map");
for i in 0..count {
ctx.emit(Instruction::LocalGet(root));
emit_expr(ctx, &args[i * 2])?;
ctx.emit(Instruction::F64Store(mem_arg_f64((8 + i * 16) as u64)));
ctx.emit(Instruction::LocalGet(root));
emit_expr(ctx, &args[i * 2 + 1])?;
ctx.emit(Instruction::F64Store(mem_arg_f64((16 + i * 16) as u64)));
}
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_map_root_from_flat")
.expect("runtime helper __rt_map_root_from_flat must exist");
ctx.emit(Instruction::LocalGet(root));
ctx.emit(Instruction::Call(fn_idx));
let hashed_root = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalSet(hashed_root));
let ptr = emit_alloc_map_with_root(ctx, count_local, hashed_root);
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_map_get_op(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&map:get expects 2 args".into());
}
let ptr = emit_ptr_to_i32(ctx, &args[0])?;
let target = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(target));
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(ptr));
ctx.emit(Instruction::LocalGet(target));
ctx.emit(Instruction::Call(fn_idx));
Ok(())
}
fn emit_map_contains(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&map:contains? expects 2 args".into());
}
let ptr = emit_ptr_to_i32(ctx, &args[0])?;
let target = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(target));
let found_idx = emit_runtime_lookup_i32_f64_to_i32(ctx, "__rt_map_contains_key", ptr, target);
ctx.emit(f64_const(1.0));
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::Select);
Ok(())
}
fn emit_map_includes(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&map:includes? expects 2 args".into());
}
let ptr = emit_ptr_to_i32(ctx, &args[0])?;
let target = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(target));
let found_idx = emit_runtime_lookup_i32_f64_to_i32(ctx, "__rt_map_contains_value", ptr, target);
ctx.emit(f64_const(1.0));
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::Select);
Ok(())
}
fn emit_map_assoc(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 3 {
return Err("&map:assoc expects 3 args".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
let key = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(key));
let val = ctx.alloc_local();
emit_expr(ctx, &args[2])?;
ctx.emit(Instruction::LocalSet(val));
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_map_assoc")
.expect("runtime helper __rt_map_assoc must exist");
ctx.emit(Instruction::LocalGet(src));
ctx.emit(Instruction::LocalGet(key));
ctx.emit(Instruction::LocalGet(val));
ctx.emit(Instruction::Call(fn_idx));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_map_dissoc(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&map:dissoc expects 2 args".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
let target = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(target));
let fn_idx = *ctx
.runtime_fn_index
.get("__rt_map_dissoc")
.expect("runtime helper __rt_map_dissoc must exist");
ctx.emit(Instruction::LocalGet(src));
ctx.emit(Instruction::LocalGet(target));
ctx.emit(Instruction::Call(fn_idx));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_map_to_pairs(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("to-pairs expects 1 arg".into());
}
emit_map_to_pair_list(ctx, args, "set")
}
fn emit_map_to_list(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 1 {
return Err("&map:to-list expects 1 arg".into());
}
emit_map_to_pair_list(ctx, args, "list")
}
fn emit_map_to_pair_list(ctx: &mut WasmGenCtx, args: &[Calcit], outer_tag: &str) -> Result<(), String> {
let map_ptr = emit_ptr_to_i32(ctx, &args[0])?;
let count = emit_load_count_i32(ctx, map_ptr);
let flat_ptr = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_linearize", map_ptr);
let outer_ts = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(outer_ts));
let outer = emit_alloc_with_count(ctx, count, outer_ts, outer_tag);
let i = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
let pair = ctx.alloc_local_typed(ValType::I32);
emit_bump_alloc(ctx, 24, pair, "list");
ctx.emit(Instruction::LocalGet(pair));
ctx.emit(f64_const(2.0));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(pair));
ctx.emit(Instruction::LocalGet(flat_ptr));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
ctx.emit(Instruction::LocalGet(pair));
ctx.emit(Instruction::LocalGet(flat_ptr));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::F64Store(mem_arg_f64(16)));
ctx.emit(Instruction::LocalGet(outer));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(pair));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(outer));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_set_new(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
let count = args.len();
let total_bytes = ((1 + count) * 8) as i32;
let ptr = ctx.alloc_local_typed(ValType::I32);
emit_bump_alloc(ctx, total_bytes, ptr, "set");
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(f64_const(count as f64));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
for (i, arg) in args.iter().enumerate() {
ctx.emit(Instruction::LocalGet(ptr));
emit_expr(ctx, arg)?;
ctx.emit(Instruction::F64Store(mem_arg_f64(((1 + i) * 8) as u64)));
}
ctx.emit(Instruction::LocalGet(ptr));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_set_includes(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&set:includes? expects 2 args".into());
}
let ptr = emit_ptr_to_i32(ctx, &args[0])?;
let target = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(target));
let found_idx = emit_runtime_lookup_i32_f64_to_i32(ctx, "__rt_set_find_elem", ptr, target);
ctx.emit(f64_const(1.0));
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::I32Const(-1));
ctx.emit(Instruction::I32Ne);
ctx.emit(Instruction::Select);
Ok(())
}
fn emit_set_include(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&include expects 2 args".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
let count = emit_load_count_i32(ctx, src);
let elem = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(elem));
let found_idx = emit_runtime_lookup_i32_f64_to_i32(ctx, "__rt_set_find_elem", src, elem);
let dst = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(src));
ctx.emit(Instruction::LocalSet(dst));
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::I32Const(-1));
ctx.emit(Instruction::I32Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
{
let nc = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(nc));
let ts = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(nc));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(ts));
let d = emit_alloc_with_count(ctx, nc, ts, "set");
ctx.emit(Instruction::LocalGet(d));
ctx.emit(Instruction::LocalSet(dst));
let db = emit_addr_offset(ctx, d, 8);
let sb = emit_addr_offset(ctx, src, 8);
emit_copy_f64_loop(ctx, db, sb, count);
ctx.emit(Instruction::LocalGet(d));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(elem));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
}
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_set_exclude(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&exclude expects 2 args".into());
}
let src = emit_ptr_to_i32(ctx, &args[0])?;
let count = emit_load_count_i32(ctx, src);
let target = ctx.alloc_local();
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(target));
let found_idx = emit_runtime_lookup_i32_f64_to_i32(ctx, "__rt_set_find_elem", src, target);
let dst = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(src));
ctx.emit(Instruction::LocalSet(dst));
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::I32Const(-1));
ctx.emit(Instruction::I32Ne);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
{
let nc = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalSet(nc));
let ts = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(nc));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(ts));
let d = emit_alloc_with_count(ctx, nc, ts, "set");
ctx.emit(Instruction::LocalGet(d));
ctx.emit(Instruction::LocalSet(dst));
let before_n = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::LocalSet(before_n));
let db1 = emit_addr_offset(ctx, d, 8);
let sb1 = emit_addr_offset(ctx, src, 8);
emit_copy_f64_loop(ctx, db1, sb1, before_n);
let after_n = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalSet(after_n));
let db2 = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(d));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(db2));
let sb2 = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(src));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(found_idx));
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(sb2));
emit_copy_f64_loop(ctx, db2, sb2, after_n);
}
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_set_difference(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&difference expects 2 args".into());
}
let a = emit_ptr_to_i32(ctx, &args[0])?;
let a_count = emit_load_count_i32(ctx, a);
let b = emit_ptr_to_i32(ctx, &args[1])?;
let total_slots = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(total_slots));
let dst = emit_alloc_with_count(ctx, a_count, total_slots, "set");
let write_idx = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(write_idx));
let ai = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(ai));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
let elem = ctx.alloc_local();
ctx.emit(Instruction::LocalGet(a));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalSet(elem));
let found_idx = emit_runtime_lookup_i32_f64_to_i32(ctx, "__rt_set_find_elem", b, elem);
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::I32Const(-1));
ctx.emit(Instruction::I32Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(elem));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(write_idx));
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(ai));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_set_union(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&union expects 2 args".into());
}
let a = emit_ptr_to_i32(ctx, &args[0])?;
let a_count = emit_load_count_i32(ctx, a);
let b = emit_ptr_to_i32(ctx, &args[1])?;
let b_count = emit_load_count_i32(ctx, b);
let max_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(max_count));
let total_slots = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(max_count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(total_slots));
let dst = emit_alloc_with_count(ctx, max_count, total_slots, "set");
let db = emit_addr_offset(ctx, dst, 8);
let sb = emit_addr_offset(ctx, a, 8);
emit_copy_f64_loop(ctx, db, sb, a_count);
let write_idx = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::LocalSet(write_idx));
let bi = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(bi));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
let elem = ctx.alloc_local();
ctx.emit(Instruction::LocalGet(b));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalSet(elem));
let found_idx = emit_runtime_lookup_i32_f64_to_i32(ctx, "__rt_set_find_elem", a, elem);
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::I32Const(-1));
ctx.emit(Instruction::I32Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(elem));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(write_idx));
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(bi));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_map_merge(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&merge expects 2 args".into());
}
let a = emit_ptr_to_i32(ctx, &args[0])?;
let a_count = emit_load_count_i32(ctx, a);
let a_flat = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_linearize", a);
let b = emit_ptr_to_i32(ctx, &args[1])?;
let b_count = emit_load_count_i32(ctx, b);
let b_flat = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_linearize", b);
let max_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(max_count));
let total_slots = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(max_count));
ctx.emit(Instruction::I32Const(2));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(total_slots));
let dst_root = emit_alloc_with_count(ctx, max_count, total_slots, "map");
let copy_n = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::I32Const(2));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::LocalSet(copy_n));
let db = emit_addr_offset(ctx, dst_root, 8);
let sb = emit_addr_offset(ctx, a_flat, 8);
emit_copy_f64_loop(ctx, db, sb, copy_n);
let write_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::LocalSet(write_count));
let bi = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(bi));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
let bk = ctx.alloc_local();
ctx.emit(Instruction::LocalGet(b_flat));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
let bkv_addr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalTee(bkv_addr));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalSet(bk));
let bv = ctx.alloc_local();
ctx.emit(Instruction::LocalGet(bkv_addr));
ctx.emit(Instruction::F64Load(mem_arg_f64(8)));
ctx.emit(Instruction::LocalSet(bv));
let found_idx = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(-1));
ctx.emit(Instruction::LocalSet(found_idx));
let di = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(di));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(di));
ctx.emit(Instruction::LocalGet(write_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
ctx.emit(Instruction::LocalGet(dst_root));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(di));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(bk));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(di));
ctx.emit(Instruction::LocalSet(found_idx));
ctx.emit(Instruction::Br(2));
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(di));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(di));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::I32Const(-1));
ctx.emit(Instruction::I32Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
{
let addr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(dst_root));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(write_count));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(addr));
ctx.emit(Instruction::LocalGet(addr));
ctx.emit(Instruction::LocalGet(bk));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(addr));
ctx.emit(Instruction::LocalGet(bv));
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
ctx.emit(Instruction::LocalGet(write_count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(write_count));
}
ctx.emit(Instruction::Else);
{
ctx.emit(Instruction::LocalGet(dst_root));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(found_idx));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(bv));
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
}
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(bi));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(dst_root));
ctx.emit(Instruction::LocalGet(write_count));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
let dst = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_from_flat", dst_root);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_map_diff_new(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&map:diff-new expects 2 args".into());
}
let a = emit_ptr_to_i32(ctx, &args[0])?;
let a_count = emit_load_count_i32(ctx, a);
let a_flat = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_linearize", a);
let b = emit_ptr_to_i32(ctx, &args[1])?;
let b_count = emit_load_count_i32(ctx, b);
let b_flat = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_linearize", b);
let total_slots = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::I32Const(2));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(total_slots));
let dst_root = emit_alloc_with_count(ctx, b_count, total_slots, "map");
let write_idx = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(write_idx));
let bi = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(bi));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
let bk = ctx.alloc_local();
let bkv_addr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(b_flat));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalTee(bkv_addr));
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalSet(bk));
let found = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(found));
let ai = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(ai));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
ctx.emit(Instruction::LocalGet(a_flat));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(bk));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::LocalSet(found));
ctx.emit(Instruction::Br(2));
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(ai));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(found));
ctx.emit(Instruction::I32Eqz);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
{
let addr = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(dst_root));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(addr));
ctx.emit(Instruction::LocalGet(addr));
ctx.emit(Instruction::LocalGet(bk));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(addr));
ctx.emit(Instruction::LocalGet(bkv_addr));
ctx.emit(Instruction::F64Load(mem_arg_f64(8)));
ctx.emit(Instruction::F64Store(mem_arg_f64(8)));
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(write_idx));
}
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(bi));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(dst_root));
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
let dst = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_from_flat", dst_root);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_map_diff_keys(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&map:diff-keys expects 2 args".into());
}
let a = emit_ptr_to_i32(ctx, &args[0])?;
let a_count = emit_load_count_i32(ctx, a);
let a_flat = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_linearize", a);
let b = emit_ptr_to_i32(ctx, &args[1])?;
let b_count = emit_load_count_i32(ctx, b);
let b_flat = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_linearize", b);
let total_slots = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(total_slots));
let dst = emit_alloc_with_count(ctx, a_count, total_slots, "set");
let write_idx = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(write_idx));
let ai = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(ai));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
let ak = ctx.alloc_local();
ctx.emit(Instruction::LocalGet(a_flat));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalSet(ak));
let found = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(found));
let bi = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(bi));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
ctx.emit(Instruction::LocalGet(b_flat));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(ak));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::LocalSet(found));
ctx.emit(Instruction::Br(2));
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(bi));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(found));
ctx.emit(Instruction::I32Eqz);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(ak));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(write_idx));
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(ai));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_map_common_keys(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.len() != 2 {
return Err("&map:common-keys expects 2 args".into());
}
let a = emit_ptr_to_i32(ctx, &args[0])?;
let a_count = emit_load_count_i32(ctx, a);
let a_flat = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_linearize", a);
let b = emit_ptr_to_i32(ctx, &args[1])?;
let b_count = emit_load_count_i32(ctx, b);
let b_flat = emit_runtime_lookup_i32_to_i32(ctx, "__rt_map_linearize", b);
let total_slots = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(total_slots));
let dst = emit_alloc_with_count(ctx, a_count, total_slots, "set");
let write_idx = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(write_idx));
let ai = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(ai));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::LocalGet(a_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
let ak = ctx.alloc_local();
ctx.emit(Instruction::LocalGet(a_flat));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalSet(ak));
let found = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(found));
let bi = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(bi));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::LocalGet(b_count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
ctx.emit(Instruction::LocalGet(b_flat));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::I32Const(16));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::F64Load(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(ak));
ctx.emit(Instruction::F64Eq);
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::LocalSet(found));
ctx.emit(Instruction::Br(2));
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(bi));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(bi));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(found));
ctx.emit(Instruction::If(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(ak));
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(write_idx));
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(ai));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(ai));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::LocalGet(write_idx));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}
fn emit_range(ctx: &mut WasmGenCtx, args: &[Calcit]) -> Result<(), String> {
if args.is_empty() || args.len() > 2 {
return Err("range expects 1 or 2 args".into());
}
let start = ctx.alloc_local();
let end = ctx.alloc_local();
if args.len() == 1 {
ctx.emit(f64_const(0.0));
ctx.emit(Instruction::LocalSet(start));
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::LocalSet(end));
} else {
emit_expr(ctx, &args[0])?;
ctx.emit(Instruction::LocalSet(start));
emit_expr(ctx, &args[1])?;
ctx.emit(Instruction::LocalSet(end));
}
let count = ctx.alloc_local_typed(ValType::I32);
let raw_count = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::LocalGet(end));
ctx.emit(Instruction::I32TruncF64S);
ctx.emit(Instruction::LocalGet(start));
ctx.emit(Instruction::I32TruncF64S);
ctx.emit(Instruction::I32Sub);
ctx.emit(Instruction::LocalSet(raw_count));
ctx.emit(Instruction::LocalGet(raw_count)); ctx.emit(Instruction::I32Const(0)); ctx.emit(Instruction::LocalGet(raw_count));
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::I32GtS); ctx.emit(Instruction::Select);
ctx.emit(Instruction::LocalSet(count));
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));
let dst = emit_alloc_with_count(ctx, count, total_slots, "list");
let i = ctx.alloc_local_typed(ValType::I32);
ctx.emit(Instruction::I32Const(0));
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Block(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::Loop(wasm_encoder::BlockType::Empty));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::LocalGet(count));
ctx.emit(Instruction::I32GeU);
ctx.emit(Instruction::BrIf(1));
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(8));
ctx.emit(Instruction::I32Mul);
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalGet(start));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::F64ConvertI32U);
ctx.emit(Instruction::F64Add);
ctx.emit(Instruction::F64Store(mem_arg_f64(0)));
ctx.emit(Instruction::LocalGet(i));
ctx.emit(Instruction::I32Const(1));
ctx.emit(Instruction::I32Add);
ctx.emit(Instruction::LocalSet(i));
ctx.emit(Instruction::Br(0));
ctx.emit(Instruction::End);
ctx.emit(Instruction::End);
ctx.emit(Instruction::LocalGet(dst));
ctx.emit(Instruction::F64ConvertI32U);
Ok(())
}