use wasm_encoder::{Function, Instruction, ValType};
use crate::ast::{Expr, Spanned};
use crate::ir::{LeafOp, classify_leaf_op};
use super::super::WasmGcError;
use super::super::types::{TypeRegistry, aver_to_wasm};
use super::builtins_wasip2::{
emit_args_get_wasip2, emit_console_print_wasip2, emit_console_read_line_wasip2,
emit_disk_append_text_wasip2, emit_disk_delete_dir_wasip2, emit_disk_delete_wasip2,
emit_disk_exists_wasip2, emit_disk_list_dir_wasip2, emit_disk_make_dir_wasip2,
emit_disk_read_text_wasip2, emit_disk_write_text_wasip2, emit_env_get_wasip2,
emit_http_delete_wasip2, emit_http_get_wasip2, emit_http_head_wasip2, emit_http_patch_wasip2,
emit_http_post_wasip2, emit_http_put_wasip2, emit_random_float_wasip2, emit_random_int_wasip2,
emit_time_now_wasip2, emit_time_sleep_wasip2, emit_time_unix_ms_wasip2,
};
use super::emit::{emit_default_value, emit_expr};
use super::infer::aver_type_str_of;
use super::{EmitCtx, SlotTable};
pub(super) fn emit_dotted_builtin(
func: &mut Function,
parent: &str,
method: &str,
args: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let dotted = format!("{parent}.{method}");
if let Some(&wasm_idx) = ctx.fn_map.builtins.get(&dotted) {
for arg in args {
emit_expr(func, arg, slots, ctx)?;
}
func.instruction(&Instruction::Call(wasm_idx));
return Ok(());
}
if dotted == "Args.get" && args.is_empty() {
if ctx.wasip2_lowering.is_some() {
return emit_args_get_wasip2(func, slots, ctx);
}
emit_args_get_inline(func, slots, ctx)?;
return Ok(());
}
if ctx.wasip2_lowering.is_some() {
if parent == "Console" && matches!(method, "print" | "error" | "warn") {
return emit_console_print_wasip2(func, method, args, slots, ctx);
}
if parent == "Time" && method == "unixMs" {
return emit_time_unix_ms_wasip2(func, args, ctx);
}
if parent == "Random" && method == "int" {
return emit_random_int_wasip2(func, args, slots, ctx);
}
if parent == "Random" && method == "float" {
return emit_random_float_wasip2(func, args, ctx);
}
if parent == "Env" && method == "get" {
return emit_env_get_wasip2(func, args, slots, ctx);
}
if parent == "Time" && method == "now" {
return emit_time_now_wasip2(func, args, ctx);
}
if parent == "Console" && method == "readLine" {
return emit_console_read_line_wasip2(func, args, ctx);
}
if parent == "Time" && method == "sleep" {
return emit_time_sleep_wasip2(func, args, slots, ctx);
}
if parent == "Disk" && method == "exists" {
return emit_disk_exists_wasip2(func, args, slots, ctx);
}
if parent == "Disk" && method == "readText" {
return emit_disk_read_text_wasip2(func, args, slots, ctx);
}
if parent == "Disk" && method == "writeText" {
return emit_disk_write_text_wasip2(func, args, slots, ctx);
}
if parent == "Disk" && method == "appendText" {
return emit_disk_append_text_wasip2(func, args, slots, ctx);
}
if parent == "Disk" && method == "delete" {
return emit_disk_delete_wasip2(func, args, slots, ctx);
}
if parent == "Disk" && method == "deleteDir" {
return emit_disk_delete_dir_wasip2(func, args, slots, ctx);
}
if parent == "Disk" && method == "makeDir" {
return emit_disk_make_dir_wasip2(func, args, slots, ctx);
}
if parent == "Disk" && method == "listDir" {
return emit_disk_list_dir_wasip2(func, args, slots, ctx);
}
if parent == "Http" && method == "get" {
return emit_http_get_wasip2(func, args, slots, ctx);
}
if parent == "Http" && method == "head" {
return emit_http_head_wasip2(func, args, slots, ctx);
}
if parent == "Http" && method == "delete" {
return emit_http_delete_wasip2(func, args, slots, ctx);
}
if parent == "Http" && method == "post" {
return emit_http_post_wasip2(func, args, slots, ctx);
}
if parent == "Http" && method == "put" {
return emit_http_put_wasip2(func, args, slots, ctx);
}
if parent == "Http" && method == "patch" {
return emit_http_patch_wasip2(func, args, slots, ctx);
}
}
if let Some(&wasm_idx) = ctx.fn_map.effects.get(&dotted) {
for arg in args {
emit_expr(func, arg, slots, ctx)?;
}
super::emit::emit_caller_fn_idx(func, ctx)?;
func.instruction(&Instruction::Call(wasm_idx));
return Ok(());
}
if parent == "BranchPath" || parent == "Trace" {
func.instruction(&Instruction::Unreachable);
return Ok(());
}
if parent == "HttpServer" && matches!(method, "listen" | "listenWith") {
return Ok(());
}
match dotted.as_str() {
"Float.fromInt" => {
if args.len() != 1 {
return Err(WasmGcError::Validation(format!(
"Float.fromInt expects 1 arg, got {}",
args.len()
)));
}
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::F64ConvertI64S);
Ok(())
}
"Int.fromFloat" => {
if args.len() != 1 {
return Err(WasmGcError::Validation(format!(
"Int.fromFloat expects 1 arg, got {}",
args.len()
)));
}
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::I64TruncF64S);
Ok(())
}
"Float.floor" if args.len() == 1 => {
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::F64Floor);
func.instruction(&Instruction::I64TruncF64S);
Ok(())
}
"Float.ceil" if args.len() == 1 => {
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::F64Ceil);
func.instruction(&Instruction::I64TruncF64S);
Ok(())
}
"Float.round" if args.len() == 1 => {
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::F64Nearest);
func.instruction(&Instruction::I64TruncF64S);
Ok(())
}
"Float.abs" if args.len() == 1 => {
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::F64Abs);
Ok(())
}
"Float.sqrt" if args.len() == 1 => {
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::F64Sqrt);
Ok(())
}
"Float.min" if args.len() == 2 => {
emit_expr(func, &args[0], slots, ctx)?;
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::F64Min);
Ok(())
}
"Float.max" if args.len() == 2 => {
emit_expr(func, &args[0], slots, ctx)?;
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::F64Max);
Ok(())
}
"Float.pi" if args.is_empty() => {
func.instruction(&Instruction::F64Const(std::f64::consts::PI.into()));
Ok(())
}
"Int.abs" if args.len() == 1 => {
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64LtS);
func.instruction(&Instruction::If(wasm_encoder::BlockType::Result(
ValType::I64,
)));
func.instruction(&Instruction::I64Const(0));
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::I64Sub);
func.instruction(&Instruction::Else);
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::End);
Ok(())
}
"Int.min" if args.len() == 2 => {
emit_expr(func, &args[0], slots, ctx)?;
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::I64LtS);
func.instruction(&Instruction::If(wasm_encoder::BlockType::Result(
ValType::I64,
)));
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::Else);
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::End);
Ok(())
}
"Int.max" if args.len() == 2 => {
emit_expr(func, &args[0], slots, ctx)?;
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::I64GtS);
func.instruction(&Instruction::If(wasm_encoder::BlockType::Result(
ValType::I64,
)));
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::Else);
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::End);
Ok(())
}
"Int.mod" if args.len() == 2 => {
let res_idx = ctx.registry.result_type_idx("Result<Int,String>").ok_or(
WasmGcError::Validation(
"Int.mod requires Result<Int,String> slot to be registered".into(),
),
)?;
let s_idx = ctx
.registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Int.mod requires String slot to be registered".into(),
))?;
let block_ty_struct = wasm_encoder::BlockType::Result(wasm_encoder::ValType::Ref(
wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(res_idx),
},
));
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64Eq);
func.instruction(&Instruction::If(block_ty_struct));
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I64Const(0));
let err_literal = "Division by zero";
let bytes = err_literal.as_bytes();
let seg_idx =
ctx.registry
.string_literal_segment(bytes)
.ok_or(WasmGcError::Validation(format!(
"Int.mod error literal `{err_literal}` not in segment table"
)))?;
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32Const(bytes.len() as i32));
func.instruction(&Instruction::ArrayNewData {
array_type_index: s_idx,
array_data_index: seg_idx,
});
func.instruction(&Instruction::StructNew(res_idx));
func.instruction(&Instruction::Else);
func.instruction(&Instruction::I32Const(1));
emit_expr(func, &args[0], slots, ctx)?;
emit_expr(func, &args[1], slots, ctx)?;
let mod_idx = ctx.fn_map.builtins.get("__int_mod_euclid").copied().ok_or(
WasmGcError::Validation(
"Int.mod requires __int_mod_euclid helper to be registered".into(),
),
)?;
func.instruction(&Instruction::Call(mod_idx));
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
s_idx,
)));
func.instruction(&Instruction::StructNew(res_idx));
func.instruction(&Instruction::End);
Ok(())
}
"Bool.and" if args.len() == 2 => {
emit_expr(func, &args[0], slots, ctx)?;
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::I32And);
Ok(())
}
"Bool.or" if args.len() == 2 => {
emit_expr(func, &args[0], slots, ctx)?;
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::I32Or);
Ok(())
}
"Bool.not" if args.len() == 1 => {
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::I32Eqz);
Ok(())
}
"String.fromInt" if args.len() == 1 => {
let to_string_idx = ctx.fn_map.builtins.get("String.fromInt").copied().ok_or(
WasmGcError::Validation("String.fromInt builtin helper not registered".into()),
)?;
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::Call(to_string_idx));
Ok(())
}
"String.fromFloat" if args.len() == 1 => {
let to_string_idx = ctx.fn_map.builtins.get("String.fromFloat").copied().ok_or(
WasmGcError::Validation("String.fromFloat builtin helper not registered".into()),
)?;
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::Call(to_string_idx));
Ok(())
}
"Vector.len" if args.len() == 1 => {
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I64ExtendI32U);
Ok(())
}
"String.length" | "String.byteLength" if args.len() == 1 => {
let len_idx =
ctx.fn_map
.builtins
.get("String.len")
.copied()
.ok_or(WasmGcError::Validation(
"String.length / byteLength require the String.len builtin".into(),
))?;
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::Call(len_idx));
Ok(())
}
"Char.toCode" if args.len() == 1 => {
let s_idx = ctx
.registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Char.toCode requires the String slot allocated".into(),
))?;
emit_expr(func, &args[0], slots, ctx)?;
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::ArrayGetU(s_idx));
func.instruction(&Instruction::I64ExtendI32U);
Ok(())
}
"Vector.new" => emit_vector_new(func, args, slots, ctx),
"Vector.get" if args.len() == 2 => {
emit_vector_get_boxed(func, &args[0], &args[1], slots, ctx)
}
"Vector.set" if args.len() == 3 => {
emit_vector_set_boxed(func, &args[0], &args[1], &args[2], slots, ctx)
}
"Option.withDefault" => emit_option_with_default(func, args, slots, ctx),
"Result.withDefault" => emit_result_with_default(func, args, slots, ctx),
"Option.toResult" if args.len() == 2 => {
emit_option_to_result(func, &args[0], &args[1], slots, ctx)
}
"Map.set" | "Map.get" | "Map.len" | "Map.has" | "Map.keys" | "Map.values"
| "Map.remove" | "Map.entries" => emit_map_kv_call(func, method, args, slots, ctx),
"Map.fromList" if args.len() == 1 => emit_map_from_list_call(func, &args[0], slots, ctx),
"List.reverse" if args.len() == 1 => {
emit_list_op_call(func, &args[0], "reverse", slots, ctx)
}
"List.len" | "List.length" if args.len() == 1 => {
emit_list_op_call(func, &args[0], "len", slots, ctx)
}
"List.concat" if args.len() == 2 => {
emit_list_op_call_2(func, &args[0], &args[1], "concat", slots, ctx)
}
"List.take" if args.len() == 2 => {
emit_list_op_call_2(func, &args[0], &args[1], "take", slots, ctx)
}
"List.drop" if args.len() == 2 => {
emit_list_op_call_2(func, &args[0], &args[1], "drop", slots, ctx)
}
"List.contains" if args.len() == 2 => {
emit_list_op_call_2(func, &args[0], &args[1], "contains", slots, ctx)
}
"List.zip" if args.len() == 2 => emit_list_zip_call(func, &args[0], &args[1], slots, ctx),
"Vector.fromList" if args.len() == 1 => emit_vec_from_list_call(func, &args[0], slots, ctx),
"List.fromVector" if args.len() == 1 => emit_vec_to_list_call(func, &args[0], slots, ctx),
"String.split" if args.len() == 2 => {
let ops = ctx.fn_map.string_split_ops.ok_or(WasmGcError::Validation(
"String.split called but split helper wasn't registered".into(),
))?;
emit_expr(func, &args[0], slots, ctx)?;
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::Call(ops.split));
Ok(())
}
"String.join" if args.len() == 2 => {
let ops = ctx.fn_map.string_split_ops.ok_or(WasmGcError::Validation(
"String.join called but join helper wasn't registered".into(),
))?;
emit_expr(func, &args[0], slots, ctx)?;
emit_expr(func, &args[1], slots, ctx)?;
func.instruction(&Instruction::Call(ops.join));
Ok(())
}
other => Err(WasmGcError::Validation(format!(
"wasm-gc: builtin or method call `{other}` is not yet implemented \
(no helper registered, no effect import, no inline lowering). \
If this looks like it should work, file at \
https://github.com/jasisz/aver/issues with the source program."
))),
}
}
pub(super) fn emit_args_get_inline(
func: &mut Function,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let [i_slot, len_slot, acc_slot, s_slot] =
slots.args_get_scratch.ok_or(WasmGcError::Validation(
"Args.get() inline expansion requires 4 reserved scratch slots — \
slots::SlotTable should have allocated them via fn_needs_args_get_scratch"
.into(),
))?;
let args_len_idx =
ctx.fn_map
.effects
.get("Args.len")
.copied()
.ok_or(WasmGcError::Validation(
"Args.get() inline needs the Args.len effect import — \
discovery walker should register it when Args.get() is reachable"
.into(),
))?;
let args_get_idx =
ctx.fn_map
.effects
.get("Args.get")
.copied()
.ok_or(WasmGcError::Validation(
"Args.get() inline needs the Args.get effect import (the i64 → String form)".into(),
))?;
let list_string_idx =
ctx.registry
.list_type_idx("List<String>")
.ok_or(WasmGcError::Validation(
"Args.get() inline needs the List<String> registry slot".into(),
))?;
super::emit::emit_caller_fn_idx(func, ctx)?;
func.instruction(&Instruction::Call(args_len_idx));
func.instruction(&Instruction::LocalSet(len_slot));
func.instruction(&Instruction::LocalGet(len_slot));
func.instruction(&Instruction::I64Const(1));
func.instruction(&Instruction::I64Sub);
func.instruction(&Instruction::LocalSet(i_slot));
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
list_string_idx,
)));
func.instruction(&Instruction::LocalSet(acc_slot));
func.instruction(&Instruction::Block(wasm_encoder::BlockType::Empty));
func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
func.instruction(&Instruction::LocalGet(i_slot));
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64LtS);
func.instruction(&Instruction::BrIf(1));
func.instruction(&Instruction::LocalGet(i_slot));
super::emit::emit_caller_fn_idx(func, ctx)?;
func.instruction(&Instruction::Call(args_get_idx));
func.instruction(&Instruction::LocalSet(s_slot));
func.instruction(&Instruction::LocalGet(s_slot));
func.instruction(&Instruction::LocalGet(acc_slot));
func.instruction(&Instruction::StructNew(list_string_idx));
func.instruction(&Instruction::LocalSet(acc_slot));
func.instruction(&Instruction::LocalGet(i_slot));
func.instruction(&Instruction::I64Const(1));
func.instruction(&Instruction::I64Sub);
func.instruction(&Instruction::LocalSet(i_slot));
func.instruction(&Instruction::Br(0));
func.instruction(&Instruction::End); func.instruction(&Instruction::End);
func.instruction(&Instruction::LocalGet(acc_slot));
Ok(())
}
pub(super) fn emit_map_kv_call(
func: &mut Function,
method: &str,
args: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let arity = match method {
"set" => 3,
"get" | "has" | "remove" => 2,
"len" | "keys" | "values" | "entries" => 1,
_ => panic!(
"internal compiler error: emit_map_kv_call invoked for unknown \
Map method `{method}`; dispatch in emit_dotted_builtin must only \
route the listed Map methods here. \
Please file at https://github.com/jasisz/aver/issues"
),
};
if args.len() != arity {
return Err(WasmGcError::Validation(format!(
"Map.{method} expects {arity} args, got {}",
args.len()
)));
}
let map_aver_raw = aver_type_str_of(&args[0]);
let canonical: String = map_aver_raw
.chars()
.filter(|c| !c.is_whitespace())
.collect();
let helpers = ctx
.fn_map
.map_helpers_lookup(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.{method}: map argument has type `{map_aver_raw}` but no helpers are registered"
)))?;
if method == "has" {
for arg in args {
emit_expr(func, arg, slots, ctx)?;
}
func.instruction(&Instruction::Call(helpers.get_pair));
func.instruction(&Instruction::Drop);
return Ok(());
}
let target_idx = match method {
"set" => {
if ctx.arg_uniquely_owned(&args[0]) {
helpers.set_in_place
} else {
helpers.set
}
}
"get" => helpers.get,
"len" => helpers.len,
"keys" => helpers.keys,
"values" => helpers.values,
"remove" => helpers.remove,
"entries" => helpers.entries,
_ => panic!(
"internal compiler error: emit_map_kv_call passed arity check \
but failed helpers dispatch for method `{method}`; the two \
match arms in this function must cover the same set. \
Please file at https://github.com/jasisz/aver/issues"
),
};
for arg in args {
emit_expr(func, arg, slots, ctx)?;
}
func.instruction(&Instruction::Call(target_idx));
Ok(())
}
pub(super) fn emit_vector_new(
func: &mut Function,
args: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if args.len() != 2 {
return Err(WasmGcError::Validation(format!(
"Vector.new expects 2 args, got {}",
args.len()
)));
}
let elem_aver = aver_type_str_of(&args[1]);
let canonical: String = format!("Vector<{}>", elem_aver)
.chars()
.filter(|c| !c.is_whitespace())
.collect();
let vec_idx = ctx
.registry
.vector_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Vector.new: instantiation `{canonical}` was not registered \
(TypeRegistry expected to discover it from a signature)"
)))?;
emit_expr(func, &args[1], slots, ctx)?; emit_expr(func, &args[0], slots, ctx)?; func.instruction(&Instruction::I32WrapI64);
func.instruction(&Instruction::ArrayNew(vec_idx));
Ok(())
}
pub(super) fn emit_option_with_default(
func: &mut Function,
args: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if args.len() != 2 {
return Err(WasmGcError::Validation(format!(
"Option.withDefault expects 2 args, got {}",
args.len()
)));
}
let opt_arg = &args[0];
let default_arg = &args[1];
let outer_call = Expr::FnCall(
Box::new(Spanned::new(
Expr::Attr(
Box::new(Spanned::new(Expr::Ident("Option".into()), 0)),
"withDefault".into(),
),
0,
)),
args.to_vec(),
);
if let Some(leaf) = classify_leaf_op(&outer_call, ctx) {
match leaf {
LeafOp::VectorSetOrDefaultSameVector {
vector,
index,
value,
} => {
return emit_vector_set_or_default(func, vector, index, value, slots, ctx);
}
LeafOp::VectorGetOrDefaultLiteral {
vector,
index,
default_literal,
} => {
let default_spanned = Spanned::new(Expr::Literal(default_literal.clone()), 0);
return emit_vector_get_or_default(
func,
vector,
index,
&default_spanned,
slots,
ctx,
);
}
_ => {}
}
}
if let Expr::FnCall(inner_callee, inner_args) = &opt_arg.node
&& let Expr::Attr(parent, member) = &inner_callee.node
&& let Expr::Ident(p) = &parent.node
&& p == "Map"
&& member == "get"
&& inner_args.len() == 2
{
return emit_map_get_or_default(
func,
&inner_args[0],
&inner_args[1],
default_arg,
slots,
ctx,
);
}
emit_option_with_default_boxed(func, opt_arg, default_arg, slots, ctx)
}
pub(super) fn emit_result_with_default(
func: &mut Function,
args: &[Spanned<Expr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if args.len() != 2 {
return Err(WasmGcError::Validation(format!(
"Result.withDefault expects 2 args, got {}",
args.len()
)));
}
let res_arg = &args[0];
let default_arg = &args[1];
if let Expr::FnCall(callee, inner_args) = &res_arg.node
&& let Expr::Attr(parent, member) = &callee.node
&& let Expr::Ident(p) = &parent.node
&& p == "Int"
&& member == "mod"
&& inner_args.len() == 2
{
let block_ty = wasm_encoder::BlockType::Result(ValType::I64);
emit_expr(func, &inner_args[1], slots, ctx)?;
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64Eq);
func.instruction(&Instruction::If(block_ty));
emit_expr(func, default_arg, slots, ctx)?;
func.instruction(&Instruction::Else);
emit_expr(func, &inner_args[0], slots, ctx)?;
emit_expr(func, &inner_args[1], slots, ctx)?;
let mod_idx =
ctx.fn_map
.builtins
.get("__int_mod_euclid")
.copied()
.ok_or(WasmGcError::Validation(
"Int.mod requires __int_mod_euclid helper to be registered".into(),
))?;
func.instruction(&Instruction::Call(mod_idx));
func.instruction(&Instruction::End);
return Ok(());
}
let res_aver = aver_type_str_of(res_arg);
let canonical: String = res_aver.chars().filter(|c| !c.is_whitespace()).collect();
let res_idx = ctx
.registry
.result_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Result.withDefault: arg of type `{res_aver}` is not a registered Result<T,E>"
)))?;
let (t_aver, _) = TypeRegistry::result_te(&canonical).ok_or(WasmGcError::Validation(
format!("Result canonical `{canonical}` malformed"),
))?;
let elem_val = aver_to_wasm(t_aver, Some(ctx.registry))?.ok_or(WasmGcError::Validation(
format!("Result.withDefault: T type `{t_aver}` has no wasm representation"),
))?;
let block_ty = wasm_encoder::BlockType::Result(elem_val);
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"Result.withDefault needs a scratch slot but none was reserved".into(),
))?;
emit_expr(func, res_arg, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: 0,
});
func.instruction(&Instruction::I32Const(1));
func.instruction(&Instruction::I32Eq);
func.instruction(&Instruction::If(block_ty));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: 1,
});
func.instruction(&Instruction::Else);
emit_expr(func, default_arg, slots, ctx)?;
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_option_to_result(
func: &mut Function,
opt_arg: &Spanned<Expr>,
err_arg: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let opt_aver = aver_type_str_of(opt_arg);
let opt_canonical: String = opt_aver.chars().filter(|c| !c.is_whitespace()).collect();
let opt_idx = ctx
.registry
.option_type_idx(&opt_canonical)
.ok_or(WasmGcError::Validation(format!(
"Option.toResult: opt arg of type `{opt_aver}` is not a registered Option<T>"
)))?;
let t_aver = super::super::types::TypeRegistry::option_element_type(&opt_canonical).ok_or(
WasmGcError::Validation(format!(
"Option.toResult: cannot parse element type from `{opt_canonical}`"
)),
)?;
let e_aver = aver_type_str_of(err_arg);
let result_canonical: String = format!("Result<{},{}>", t_aver.trim(), e_aver.trim())
.chars()
.filter(|c| !c.is_whitespace())
.collect();
let res_idx = ctx
.registry
.result_type_idx(&result_canonical)
.ok_or(WasmGcError::Validation(format!(
"Option.toResult: `{result_canonical}` slot was not registered (the Result instantiation \
needs to appear in a fn signature or be auto-discovered from a builtin's return type)"
)))?;
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"Option.toResult needs a scratch slot but none was reserved".into(),
))?;
let res_ref = ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(res_idx),
});
let block_ty = wasm_encoder::BlockType::Result(res_ref);
emit_expr(func, opt_arg, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(opt_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: opt_idx,
field_index: 0,
});
func.instruction(&Instruction::I32Const(1));
func.instruction(&Instruction::I32Eq);
func.instruction(&Instruction::If(block_ty));
func.instruction(&Instruction::I32Const(1)); func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(opt_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: opt_idx,
field_index: 1,
});
emit_default_value(func, e_aver.trim(), ctx.registry)?;
func.instruction(&Instruction::StructNew(res_idx));
func.instruction(&Instruction::Else);
func.instruction(&Instruction::I32Const(0));
emit_default_value(func, t_aver.trim(), ctx.registry)?;
emit_expr(func, err_arg, slots, ctx)?;
func.instruction(&Instruction::StructNew(res_idx));
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_option_with_default_boxed(
func: &mut Function,
opt_arg: &Spanned<Expr>,
default_arg: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let opt_aver = aver_type_str_of(opt_arg);
let canonical: String = opt_aver.chars().filter(|c| !c.is_whitespace()).collect();
let opt_idx = ctx
.registry
.option_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Option.withDefault: opt arg of type `{opt_aver}` is not a registered Option<T>"
)))?;
let element = super::super::types::TypeRegistry::option_element_type(&canonical).ok_or(
WasmGcError::Validation(format!(
"Option.withDefault: cannot parse element type from `{canonical}`"
)),
)?;
let elem_val = aver_to_wasm(element, Some(ctx.registry))?.ok_or(WasmGcError::Validation(
format!("Option.withDefault: element type `{element}` has no wasm representation"),
))?;
let block_ty = wasm_encoder::BlockType::Result(elem_val);
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"Option.withDefault (boxed) needs a scratch slot but none was reserved".into(),
))?;
emit_expr(func, opt_arg, slots, ctx)?;
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(opt_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: opt_idx,
field_index: 0,
});
func.instruction(&Instruction::I32Const(1));
func.instruction(&Instruction::I32Eq);
func.instruction(&Instruction::If(block_ty));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(opt_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: opt_idx,
field_index: 1,
});
func.instruction(&Instruction::Else);
emit_expr(func, default_arg, slots, ctx)?;
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_map_get_or_default(
func: &mut Function,
map: &Spanned<Expr>,
key: &Spanned<Expr>,
default: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let map_aver = aver_type_str_of(map);
let canonical: String = map_aver.chars().filter(|c| !c.is_whitespace()).collect();
let helpers = ctx
.fn_map
.map_helpers_lookup(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.get fusion: map argument has type `{map_aver}` but no helpers are registered"
)))?;
emit_expr(func, map, slots, ctx)?;
emit_expr(func, key, slots, ctx)?;
emit_expr(func, default, slots, ctx)?;
func.instruction(&Instruction::Call(helpers.get_or_default));
Ok(())
}
pub(super) fn emit_vector_set_or_default(
func: &mut Function,
vector: &Spanned<Expr>,
index: &Spanned<Expr>,
value: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let vec_aver = aver_type_str_of(vector);
let canonical: String = vec_aver.chars().filter(|c| !c.is_whitespace()).collect();
let vec_idx = ctx
.registry
.vector_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Vector.set: vector arg of type `{vec_aver}` is not a registered Vector<T>"
)))?;
if ctx.arg_uniquely_owned(vector) {
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64GeS);
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
emit_expr(func, vector, slots, ctx)?;
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
emit_expr(func, value, slots, ctx)?;
func.instruction(&Instruction::ArraySet(vec_idx));
func.instruction(&Instruction::End);
emit_expr(func, vector, slots, ctx)?;
return Ok(());
}
let scratch = slots
.vector_set_scratch
.get(&canonical)
.copied()
.ok_or_else(|| {
WasmGcError::Validation(format!(
"Vector.set: scratch local for `{canonical}` not reserved \
(slot-pre-pass missed this site)"
))
})?;
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::ArrayNewDefault(vec_idx));
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::I32Const(0));
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::I32Const(0));
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::ArrayCopy {
array_type_index_dst: vec_idx,
array_type_index_src: vec_idx,
});
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64GeS);
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
func.instruction(&Instruction::LocalGet(scratch));
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
emit_expr(func, value, slots, ctx)?;
func.instruction(&Instruction::ArraySet(vec_idx));
func.instruction(&Instruction::End);
func.instruction(&Instruction::LocalGet(scratch));
Ok(())
}
pub(super) fn emit_interpolated_str(
func: &mut Function,
parts: &[crate::ast::StrPart],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
use crate::ast::StrPart;
let string_type_idx = ctx
.registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"InterpolatedStr reachable but no String type slot allocated".into(),
))?;
if parts.is_empty() {
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::ArrayNewDefault(string_type_idx));
return Ok(());
}
let vec_idx = ctx
.registry
.vector_type_idx("Vector<String>")
.ok_or(WasmGcError::Validation(
"InterpolatedStr requires Vector<String> slot but it wasn't registered".into(),
))?;
let concat_idx = ctx
.fn_map
.builtins
.get("__wasmgc_concat_n")
.copied()
.ok_or(WasmGcError::Validation(
"InterpolatedStr requires __wasmgc_concat_n builtin but it wasn't registered".into(),
))?;
for part in parts {
match part {
StrPart::Literal(s) => {
let bytes = s.as_bytes();
let seg_idx =
ctx.registry
.string_literal_segment(bytes)
.ok_or(WasmGcError::Validation(format!(
"Interpolation literal `{s:?}` not in segment table"
)))?;
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32Const(bytes.len() as i32));
func.instruction(&Instruction::ArrayNewData {
array_type_index: string_type_idx,
array_data_index: seg_idx,
});
}
StrPart::Parsed(inner) => {
let aver_ty = aver_type_str_of(inner);
emit_expr(func, inner, slots, ctx)?;
match aver_ty.trim() {
"String" => { }
"Int" => {
let to_string_idx =
ctx.fn_map.builtins.get("String.fromInt").copied().ok_or(
WasmGcError::Validation(
"interpolation of Int requires String.fromInt builtin".into(),
),
)?;
func.instruction(&Instruction::Call(to_string_idx));
}
"Float" => {
let to_string_idx =
ctx.fn_map.builtins.get("String.fromFloat").copied().ok_or(
WasmGcError::Validation(
"interpolation of Float requires String.fromFloat builtin"
.into(),
),
)?;
func.instruction(&Instruction::Call(to_string_idx));
}
"Bool" => {
let to_string_idx =
ctx.fn_map.builtins.get("String.fromBool").copied().ok_or(
WasmGcError::Validation(
"interpolation of Bool requires String.fromBool builtin".into(),
),
)?;
func.instruction(&Instruction::Call(to_string_idx));
}
other => {
return Err(WasmGcError::Validation(format!(
"phase 3c — interpolation of compound type `{other}`. \
Stringify at the call site (e.g. a per-type render fn \
that pattern-matches into primitive interpolations) so \
Console.print's argument stays a plain String."
)));
}
}
}
}
}
func.instruction(&Instruction::ArrayNewFixed {
array_type_index: vec_idx,
array_size: parts.len() as u32,
});
func.instruction(&Instruction::Call(concat_idx));
Ok(())
}
pub(super) fn emit_vector_get_or_default(
func: &mut Function,
vector: &Spanned<Expr>,
index: &Spanned<Expr>,
default: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let vec_aver = aver_type_str_of(vector);
let canonical: String = vec_aver.chars().filter(|c| !c.is_whitespace()).collect();
let vec_idx = ctx
.registry
.vector_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Vector.get: vector arg of type `{vec_aver}` is not a registered Vector<T>"
)))?;
let element = super::super::types::TypeRegistry::vector_element_type(&canonical).ok_or(
WasmGcError::Validation(format!(
"Vector.get: cannot parse element type from `{canonical}`"
)),
)?;
let elem_val = aver_to_wasm(element, Some(ctx.registry))?.ok_or(WasmGcError::Validation(
format!("Vector.get: element type `{element}` has no wasm representation"),
))?;
let block_ty = wasm_encoder::BlockType::Result(elem_val);
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64GeS);
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(block_ty));
emit_expr(func, vector, slots, ctx)?;
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
func.instruction(&Instruction::ArrayGet(vec_idx));
func.instruction(&Instruction::Else);
emit_expr(func, default, slots, ctx)?;
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_list_op_call(
func: &mut Function,
list_arg: &Spanned<Expr>,
op: &str,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let list_aver = aver_type_str_of(list_arg);
let canonical = super::super::types::normalize_compound(&list_aver);
let ops = ctx
.fn_map
.list_ops_lookup(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List.{op} called but `{canonical}` helper wasn't registered"
)))?;
emit_expr(func, list_arg, slots, ctx)?;
let fn_idx = match op {
"reverse" => ops.reverse,
"len" => ops.len,
_ => {
return Err(WasmGcError::Validation(format!(
"emit_list_op_call: unknown op `{op}`"
)));
}
};
func.instruction(&Instruction::Call(fn_idx));
Ok(())
}
pub(super) fn emit_list_op_call_2(
func: &mut Function,
list_arg: &Spanned<Expr>,
second_arg: &Spanned<Expr>,
op: &str,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let list_aver = aver_type_str_of(list_arg);
let canonical = super::super::types::normalize_compound(&list_aver);
let ops = ctx
.fn_map
.list_ops_lookup(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List.{op} called but `{canonical}` helper wasn't registered"
)))?;
emit_expr(func, list_arg, slots, ctx)?;
emit_expr(func, second_arg, slots, ctx)?;
let fn_idx = match op {
"concat" => ops.concat,
"take" => ops.take,
"drop" => ops.drop,
"contains" => ops.contains.ok_or(WasmGcError::Validation(format!(
"List.contains over `{canonical}`: element type isn't natively eq-able \
(only Int/Float/Bool/String/Char are supported today)"
)))?,
_ => {
return Err(WasmGcError::Validation(format!(
"emit_list_op_call_2: unknown op `{op}`"
)));
}
};
func.instruction(&Instruction::Call(fn_idx));
Ok(())
}
pub(super) fn emit_map_from_list_call(
func: &mut Function,
list_arg: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let list_aver = aver_type_str_of(list_arg);
let list_canonical: String = list_aver.chars().filter(|c| !c.is_whitespace()).collect();
let elem = TypeRegistry::list_element_type(&list_canonical).ok_or(WasmGcError::Validation(
format!("Map.fromList: input `{list_aver}` is not a List<Tuple<K,V>>"),
))?;
let (k, v) = TypeRegistry::tuple_ab(elem).ok_or(WasmGcError::Validation(format!(
"Map.fromList: list element `{elem}` is not a Tuple<K, V>"
)))?;
let map_canonical = format!("Map<{},{}>", k.trim(), v.trim());
let helpers = ctx
.fn_map
.map_helpers_lookup(&map_canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.fromList: helpers for `{map_canonical}` not registered"
)))?;
emit_expr(func, list_arg, slots, ctx)?;
func.instruction(&Instruction::Call(helpers.from_list));
Ok(())
}
pub(super) fn emit_list_zip_call(
func: &mut Function,
la: &Spanned<Expr>,
lb: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let la_aver = aver_type_str_of(la);
let lb_aver = aver_type_str_of(lb);
let a = TypeRegistry::list_element_type(&la_aver).ok_or(WasmGcError::Validation(format!(
"List.zip: first arg type `{la_aver}` is not a List<T>"
)))?;
let b = TypeRegistry::list_element_type(&lb_aver).ok_or(WasmGcError::Validation(format!(
"List.zip: second arg type `{lb_aver}` is not a List<T>"
)))?;
let tup_canonical = format!("Tuple<{},{}>", a.trim(), b.trim());
let zip_fn = ctx
.fn_map
.zip_ops_lookup(&tup_canonical)
.ok_or(WasmGcError::Validation(format!(
"List.zip: helper for `{tup_canonical}` wasn't registered"
)))?;
emit_expr(func, la, slots, ctx)?;
emit_expr(func, lb, slots, ctx)?;
func.instruction(&Instruction::Call(zip_fn));
Ok(())
}
pub(super) fn emit_vec_from_list_call(
func: &mut Function,
list_arg: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let list_aver = aver_type_str_of(list_arg);
let canonical = super::super::types::normalize_compound(&list_aver);
let ops = ctx
.fn_map
.vfl_ops_lookup(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Vector.fromList: helper for `{canonical}` wasn't registered \
(matching Vector<T> may be missing from the registry)"
)))?;
emit_expr(func, list_arg, slots, ctx)?;
func.instruction(&Instruction::Call(ops.from_list));
Ok(())
}
pub(super) fn emit_vec_to_list_call(
func: &mut Function,
vec_arg: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let vec_aver = aver_type_str_of(vec_arg);
let vec_canonical: String = vec_aver.chars().filter(|c| !c.is_whitespace()).collect();
let elem = super::super::types::TypeRegistry::vector_element_type(&vec_canonical).ok_or(
WasmGcError::Validation(format!(
"List.fromVector: cannot parse element type from `{vec_canonical}`"
)),
)?;
let list_canonical = format!("List<{}>", elem.trim());
let ops = ctx
.fn_map
.vfl_ops
.get(&list_canonical)
.ok_or(WasmGcError::Validation(format!(
"List.fromVector: helper for `{list_canonical}` wasn't registered"
)))?;
emit_expr(func, vec_arg, slots, ctx)?;
func.instruction(&Instruction::Call(ops.to_list));
Ok(())
}
pub(super) fn emit_vector_get_boxed(
func: &mut Function,
vector: &Spanned<Expr>,
index: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let vec_aver = aver_type_str_of(vector);
let canonical: String = vec_aver.chars().filter(|c| !c.is_whitespace()).collect();
let vec_idx = ctx
.registry
.vector_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Vector.get: vector arg of type `{vec_aver}` is not a registered Vector<T>"
)))?;
let element = super::super::types::TypeRegistry::vector_element_type(&canonical).ok_or(
WasmGcError::Validation(format!(
"Vector.get: cannot parse element type from `{canonical}`"
)),
)?;
let opt_canonical = format!("Option<{}>", element.trim());
let opt_idx = ctx
.registry
.option_type_idx(&opt_canonical)
.ok_or(WasmGcError::Validation(format!(
"Vector.get: `{opt_canonical}` slot was not registered"
)))?;
let opt_ref = wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(opt_idx),
});
let block_ty = wasm_encoder::BlockType::Result(opt_ref);
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64GeS);
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(block_ty));
func.instruction(&Instruction::I32Const(1));
emit_expr(func, vector, slots, ctx)?;
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
func.instruction(&Instruction::ArrayGet(vec_idx));
func.instruction(&Instruction::StructNew(opt_idx));
func.instruction(&Instruction::Else);
func.instruction(&Instruction::I32Const(0));
emit_default_value(func, element, ctx.registry)?;
func.instruction(&Instruction::StructNew(opt_idx));
func.instruction(&Instruction::End);
Ok(())
}
pub(super) fn emit_vector_set_boxed(
func: &mut Function,
vector: &Spanned<Expr>,
index: &Spanned<Expr>,
value: &Spanned<Expr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
let vec_aver = aver_type_str_of(vector);
let canonical: String = vec_aver.chars().filter(|c| !c.is_whitespace()).collect();
let vec_idx = ctx
.registry
.vector_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Vector.set: vector arg of type `{vec_aver}` is not a registered Vector<T>"
)))?;
let opt_canonical = format!("Option<{canonical}>");
let opt_idx = ctx
.registry
.option_type_idx(&opt_canonical)
.ok_or(WasmGcError::Validation(format!(
"Vector.set: `{opt_canonical}` slot was not registered"
)))?;
let opt_ref = wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(opt_idx),
});
let block_ty = wasm_encoder::BlockType::Result(opt_ref);
if ctx.arg_uniquely_owned(vector) {
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64GeS);
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(block_ty));
emit_expr(func, vector, slots, ctx)?;
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
emit_expr(func, value, slots, ctx)?;
func.instruction(&Instruction::ArraySet(vec_idx));
func.instruction(&Instruction::I32Const(1));
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::StructNew(opt_idx));
func.instruction(&Instruction::Else);
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
vec_idx,
)));
func.instruction(&Instruction::StructNew(opt_idx));
func.instruction(&Instruction::End);
return Ok(());
}
let scratch = slots
.vector_set_scratch
.get(&canonical)
.copied()
.ok_or_else(|| {
WasmGcError::Validation(format!(
"Vector.set: scratch local for `{canonical}` not reserved \
(slot-pre-pass missed this site)"
))
})?;
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64GeS);
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(block_ty));
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::ArrayNewDefault(vec_idx));
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::I32Const(0));
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::I32Const(0));
emit_expr(func, vector, slots, ctx)?;
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::ArrayCopy {
array_type_index_dst: vec_idx,
array_type_index_src: vec_idx,
});
func.instruction(&Instruction::LocalGet(scratch));
emit_expr(func, index, slots, ctx)?;
func.instruction(&Instruction::I32WrapI64);
emit_expr(func, value, slots, ctx)?;
func.instruction(&Instruction::ArraySet(vec_idx));
func.instruction(&Instruction::I32Const(1));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::StructNew(opt_idx));
func.instruction(&Instruction::Else);
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
vec_idx,
)));
func.instruction(&Instruction::StructNew(opt_idx));
func.instruction(&Instruction::End);
Ok(())
}