use super::*;
pub(crate) enum MirBuiltinEmit {
NotHandled,
Fallback,
Produced(bool),
}
fn aint_helper(ctx: &EmitCtx<'_>, name: &str) -> Result<u32, WasmGcError> {
ctx.fn_map
.builtins
.get(name)
.copied()
.ok_or(WasmGcError::Validation(format!(
"bignum active but {name} helper not registered"
)))
}
pub(crate) fn lift_i64_result_to_aint(
func: &mut Function,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if ctx.registry.bignum {
let from_i64 = aint_helper(ctx, "__aint_from_i64")?;
func.instruction(&Instruction::Call(from_i64));
}
Ok(())
}
fn emit_aint_arg_as_i64_sat(
func: &mut Function,
arg: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<bool, WasmGcError> {
if emit_mir_expr(func, arg, slots, ctx)?.is_none() {
return Ok(false);
}
if ctx.registry.bignum {
let to_sat = aint_helper(ctx, "__aint_to_i64_sat")?;
func.instruction(&Instruction::Call(to_sat));
}
Ok(true)
}
fn emit_index_i32(
func: &mut Function,
index: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<bool, WasmGcError> {
if emit_mir_expr(func, index, slots, ctx)?.is_none() {
return Ok(false);
}
if ctx.registry.bignum {
let to_index = aint_helper(ctx, "__aint_to_index")?;
func.instruction(&Instruction::Call(to_index));
} else {
func.instruction(&Instruction::I32WrapI64);
}
Ok(true)
}
fn emit_index_nonneg(
func: &mut Function,
index: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<bool, WasmGcError> {
if ctx.registry.bignum {
if !emit_index_i32(func, index, slots, ctx)? {
return Ok(false);
}
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32GeS);
} else {
if emit_mir_expr(func, index, slots, ctx)?.is_none() {
return Ok(false);
}
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64GeS);
}
Ok(true)
}
fn aint_block_ty(ctx: &EmitCtx<'_>) -> Result<wasm_encoder::BlockType, WasmGcError> {
let idx = ctx.registry.aint_struct_idx.ok_or(WasmGcError::Validation(
"bignum active but no $AverInt struct slot was allocated".into(),
))?;
Ok(wasm_encoder::BlockType::Result(
crate::codegen::wasm_gc::types::struct_ref(idx),
))
}
pub(crate) fn emit_mir_wasip2_effect(
func: &mut Function,
dotted: &str,
args: &[Spanned<MirExpr>],
expr: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, WasmGcError> {
use super::super::builtins_wasip2::*;
if ctx.wasip2_lowering.is_none() {
return Ok(MirBuiltinEmit::NotHandled);
}
let produces = aver_type_str_of(expr).trim() != "Unit";
let Some((parent, method)) = dotted.split_once('.') else {
return Ok(MirBuiltinEmit::NotHandled);
};
match (parent, method) {
("Args", "get") if args.is_empty() => emit_args_get_wasip2(func, slots, ctx)?,
("Console", "print" | "error" | "warn") => {
emit_console_print_wasip2(func, method, args, slots, ctx)?
}
("Console", "readLine") => emit_console_read_line_wasip2(func, args, ctx)?,
("Time", "unixMs") => emit_time_unix_ms_wasip2(func, args, ctx)?,
("Time", "now") => emit_time_now_wasip2(func, args, ctx)?,
("Time", "sleep") => emit_time_sleep_wasip2(func, args, slots, ctx)?,
("Random", "int") => emit_random_int_wasip2(func, args, slots, ctx)?,
("Random", "float") => emit_random_float_wasip2(func, args, ctx)?,
("Env", "get") => emit_env_get_wasip2(func, args, slots, ctx)?,
("Disk", "exists") => emit_disk_exists_wasip2(func, args, slots, ctx)?,
("Disk", "readText") => emit_disk_read_text_wasip2(func, args, slots, ctx)?,
("Disk", "writeText") => emit_disk_write_text_wasip2(func, args, slots, ctx)?,
("Disk", "appendText") => emit_disk_append_text_wasip2(func, args, slots, ctx)?,
("Disk", "delete") => emit_disk_delete_wasip2(func, args, slots, ctx)?,
("Disk", "deleteDir") => emit_disk_delete_dir_wasip2(func, args, slots, ctx)?,
("Disk", "makeDir") => emit_disk_make_dir_wasip2(func, args, slots, ctx)?,
("Disk", "listDir") => emit_disk_list_dir_wasip2(func, args, slots, ctx)?,
("Http", "get") => emit_http_get_wasip2(func, args, slots, ctx)?,
("Http", "head") => emit_http_head_wasip2(func, args, slots, ctx)?,
("Http", "delete") => emit_http_delete_wasip2(func, args, slots, ctx)?,
("Http", "post") => emit_http_post_wasip2(func, args, slots, ctx)?,
("Http", "put") => emit_http_put_wasip2(func, args, slots, ctx)?,
("Http", "patch") => emit_http_patch_wasip2(func, args, slots, ctx)?,
("Tcp", "connect") => emit_tcp_connect_wasip2(func, args, slots, ctx)?,
("Tcp", "close") => emit_tcp_close_wasip2(func, args, slots, ctx)?,
("Tcp", "writeLine") => emit_tcp_write_line_wasip2(func, args, slots, ctx)?,
("Tcp", "readLine") => emit_tcp_read_line_wasip2(func, args, slots, ctx)?,
("Tcp", "send") => emit_tcp_send_wasip2(func, args, slots, ctx)?,
("Tcp", "ping") => emit_tcp_ping_wasip2(func, args, slots, ctx)?,
_ => return Ok(MirBuiltinEmit::NotHandled),
}
Ok(MirBuiltinEmit::Produced(produces))
}
pub(crate) fn emit_mir_native_scalar_builtin(
func: &mut Function,
dotted: &str,
args: &[Spanned<MirExpr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, WasmGcError> {
macro_rules! arg {
($i:expr) => {
if emit_mir_expr(func, &args[$i], slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
macro_rules! round_ties_away {
($i:expr) => {{
arg!($i);
func.instruction(&Instruction::F64Trunc);
func.instruction(&Instruction::F64Const((1.0f64).into()));
arg!($i);
func.instruction(&Instruction::F64Copysign);
func.instruction(&Instruction::F64Add);
arg!($i);
func.instruction(&Instruction::F64Trunc);
arg!($i);
arg!($i);
func.instruction(&Instruction::F64Trunc);
func.instruction(&Instruction::F64Sub);
func.instruction(&Instruction::F64Abs);
func.instruction(&Instruction::F64Const((0.5f64).into()));
func.instruction(&Instruction::F64Ge);
func.instruction(&Instruction::Select);
}};
}
let i64_block = wasm_encoder::BlockType::Result(ValType::I64);
match dotted {
"Float.fromInt" if args.len() == 1 && ctx.registry.bignum => {
let to_f64 = aint_helper(ctx, "__aint_to_f64")?;
arg!(0);
func.instruction(&Instruction::Call(to_f64));
}
"Int.fromFloat" if args.len() == 1 && ctx.registry.bignum => {
let from_f64 = aint_helper(ctx, "__aint_from_f64")?;
arg!(0);
func.instruction(&Instruction::Call(from_f64));
}
"Float.floor" if args.len() == 1 && ctx.registry.bignum => {
let from_f64 = aint_helper(ctx, "__aint_from_f64")?;
arg!(0);
func.instruction(&Instruction::F64Floor);
func.instruction(&Instruction::Call(from_f64));
}
"Float.ceil" if args.len() == 1 && ctx.registry.bignum => {
let from_f64 = aint_helper(ctx, "__aint_from_f64")?;
arg!(0);
func.instruction(&Instruction::F64Ceil);
func.instruction(&Instruction::Call(from_f64));
}
"Float.round" if args.len() == 1 && ctx.registry.bignum => {
let from_f64 = aint_helper(ctx, "__aint_from_f64")?;
round_ties_away!(0);
func.instruction(&Instruction::Call(from_f64));
}
"Float.fromInt" if args.len() == 1 => {
arg!(0);
func.instruction(&Instruction::F64ConvertI64S);
}
"Int.fromFloat" if args.len() == 1 => {
arg!(0);
func.instruction(&Instruction::I64TruncF64S);
}
"Float.floor" if args.len() == 1 => {
arg!(0);
func.instruction(&Instruction::F64Floor);
func.instruction(&Instruction::I64TruncF64S);
}
"Float.ceil" if args.len() == 1 => {
arg!(0);
func.instruction(&Instruction::F64Ceil);
func.instruction(&Instruction::I64TruncF64S);
}
"Float.round" if args.len() == 1 => {
round_ties_away!(0);
func.instruction(&Instruction::I64TruncF64S);
}
"Float.abs" if args.len() == 1 => {
arg!(0);
func.instruction(&Instruction::F64Abs);
}
"Float.sqrt" if args.len() == 1 => {
arg!(0);
func.instruction(&Instruction::F64Sqrt);
}
"Float.min" if args.len() == 2 => {
arg!(0);
arg!(1);
func.instruction(&Instruction::F64Min);
}
"Float.max" if args.len() == 2 => {
arg!(0);
arg!(1);
func.instruction(&Instruction::F64Max);
}
"Float.pi" if args.is_empty() => {
func.instruction(&Instruction::F64Const(std::f64::consts::PI.into()));
}
"Int.abs" if args.len() == 1 && ctx.registry.bignum => {
let abs_idx = aint_helper(ctx, "__aint_abs")?;
arg!(0);
func.instruction(&Instruction::Call(abs_idx));
}
"Int.min" if args.len() == 2 && ctx.registry.bignum => {
let cmp_idx = aint_helper(ctx, "__aint_cmp")?;
let aint_block = aint_block_ty(ctx)?;
arg!(0);
arg!(1);
func.instruction(&Instruction::Call(cmp_idx));
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32LeS);
func.instruction(&Instruction::If(aint_block));
arg!(0);
func.instruction(&Instruction::Else);
arg!(1);
func.instruction(&Instruction::End);
}
"Int.max" if args.len() == 2 && ctx.registry.bignum => {
let cmp_idx = aint_helper(ctx, "__aint_cmp")?;
let aint_block = aint_block_ty(ctx)?;
arg!(0);
arg!(1);
func.instruction(&Instruction::Call(cmp_idx));
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32GeS);
func.instruction(&Instruction::If(aint_block));
arg!(0);
func.instruction(&Instruction::Else);
arg!(1);
func.instruction(&Instruction::End);
}
"Int.abs" if args.len() == 1 => {
arg!(0);
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64LtS);
func.instruction(&Instruction::If(i64_block));
func.instruction(&Instruction::I64Const(0));
arg!(0);
func.instruction(&Instruction::I64Sub);
func.instruction(&Instruction::Else);
arg!(0);
func.instruction(&Instruction::End);
}
"Int.min" if args.len() == 2 => {
arg!(0);
arg!(1);
func.instruction(&Instruction::I64LtS);
func.instruction(&Instruction::If(i64_block));
arg!(0);
func.instruction(&Instruction::Else);
arg!(1);
func.instruction(&Instruction::End);
}
"Int.max" if args.len() == 2 => {
arg!(0);
arg!(1);
func.instruction(&Instruction::I64GtS);
func.instruction(&Instruction::If(i64_block));
arg!(0);
func.instruction(&Instruction::Else);
arg!(1);
func.instruction(&Instruction::End);
}
"Bool.and" if args.len() == 2 => {
arg!(0);
arg!(1);
func.instruction(&Instruction::I32And);
}
"Bool.or" if args.len() == 2 => {
arg!(0);
arg!(1);
func.instruction(&Instruction::I32Or);
}
"Bool.not" if args.len() == 1 => {
arg!(0);
func.instruction(&Instruction::I32Eqz);
}
"String.length" if args.len() == 1 => {
let len_idx =
ctx.fn_map
.builtins
.get("String.len")
.copied()
.ok_or(WasmGcError::Validation(
"String.length requires the String.len builtin".into(),
))?;
arg!(0);
func.instruction(&Instruction::Call(len_idx));
lift_i64_result_to_aint(func, 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(),
))?;
arg!(0);
arg!(1);
func.instruction(&Instruction::Call(ops.split));
}
"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(),
))?;
arg!(0);
arg!(1);
func.instruction(&Instruction::Call(ops.join));
}
_ => return Ok(MirBuiltinEmit::NotHandled),
}
Ok(MirBuiltinEmit::Produced(true))
}
pub(crate) fn emit_mir_option_with_default(
func: &mut Function,
dotted: &str,
args: &[Spanned<MirExpr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, WasmGcError> {
if dotted != "Option.withDefault" || args.len() != 2 {
return Ok(MirBuiltinEmit::NotHandled);
}
let opt_arg = &args[0];
let default = &args[1];
macro_rules! e {
($x:expr) => {
if emit_mir_expr(func, $x, slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
if let MirExpr::Call(inner_sp) = &opt_arg.node
&& let MirCallee::Builtin(inner_id) = inner_sp.node.callee
&& let Some(inner_dotted) = ctx.mir_builtins.and_then(|n| n.get(inner_id.0 as usize))
{
let inner = &inner_sp.node;
if inner_dotted == "Vector.set"
&& inner.args.len() == 3
&& matches!(
(&default.node, &inner.args[0].node),
(MirExpr::Local(d), MirExpr::Local(v)) if d.node.slot == v.node.slot
)
{
return emit_mir_vector_set_or_default(
func,
&inner.args[0],
&inner.args[1],
&inner.args[2],
slots,
ctx,
);
}
if inner_dotted == "Vector.get"
&& inner.args.len() == 2
&& matches!(default.node, MirExpr::Literal(_))
{
let vector = &inner.args[0];
let index = &inner.args[1];
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 =
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);
macro_rules! idx_nonneg {
() => {
if !emit_index_nonneg(func, index, slots, ctx)? {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
macro_rules! idx_i32 {
() => {
if !emit_index_i32(func, index, slots, ctx)? {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
idx_nonneg!();
idx_i32!();
e!(vector);
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(block_ty));
e!(vector);
idx_i32!();
func.instruction(&Instruction::ArrayGet(vec_idx));
func.instruction(&Instruction::Else);
e!(default);
func.instruction(&Instruction::End);
return Ok(MirBuiltinEmit::Produced(true));
}
if inner_dotted == "Map.get" && inner.args.len() == 2 {
let map = &inner.args[0];
let key = &inner.args[1];
let map_aver = aver_type_str_of(map);
let canonical: String = map_aver.chars().filter(|c| !c.is_whitespace()).collect();
let get_or_default = 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"
)))?
.get_or_default;
e!(map);
e!(key);
e!(default);
func.instruction(&Instruction::Call(get_or_default));
return Ok(MirBuiltinEmit::Produced(true));
}
}
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 = 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(),
))?;
e!(opt_arg);
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);
e!(default);
func.instruction(&Instruction::End);
Ok(MirBuiltinEmit::Produced(true))
}
fn emit_mir_vector_set_or_default(
func: &mut Function,
vector: &Spanned<MirExpr>,
index: &Spanned<MirExpr>,
value: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, 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>"
)))?;
macro_rules! e {
($x:expr) => {
if emit_mir_expr(func, $x, slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
macro_rules! idx_nonneg {
() => {
if !emit_index_nonneg(func, index, slots, ctx)? {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
macro_rules! idx_i32 {
() => {
if !emit_index_i32(func, index, slots, ctx)? {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
if mir_arg_uniquely_owned(vector, ctx) {
idx_nonneg!();
idx_i32!();
e!(vector);
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
e!(vector);
idx_i32!();
e!(value);
func.instruction(&Instruction::ArraySet(vec_idx));
func.instruction(&Instruction::End);
e!(vector);
return Ok(MirBuiltinEmit::Produced(true));
}
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)"
))
})?;
e!(vector);
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));
e!(vector);
func.instruction(&Instruction::I32Const(0));
e!(vector);
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::ArrayCopy {
array_type_index_dst: vec_idx,
array_type_index_src: vec_idx,
});
idx_nonneg!();
idx_i32!();
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));
idx_i32!();
e!(value);
func.instruction(&Instruction::ArraySet(vec_idx));
func.instruction(&Instruction::End);
func.instruction(&Instruction::LocalGet(scratch));
Ok(MirBuiltinEmit::Produced(true))
}
pub(crate) fn emit_mir_int_div_mod_boxed(
func: &mut Function,
dotted: &str,
args: &[Spanned<MirExpr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, WasmGcError> {
let is_div = match dotted {
"Int.div" => true,
"Int.mod" => false,
_ => return Ok(MirBuiltinEmit::NotHandled),
};
if args.len() != 2 {
return Ok(MirBuiltinEmit::NotHandled);
}
let a = &args[0];
let b = &args[1];
macro_rules! e {
($x:expr) => {
if emit_mir_expr(func, $x, slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
let res_idx =
ctx.registry
.result_type_idx("Result<Int,String>")
.ok_or(WasmGcError::Validation(
"boxed Int.div/mod requires the `Result<Int,String>` slot to be registered".into(),
))?;
if ctx.registry.bignum {
return emit_mir_aint_div_mod_boxed(func, is_div, a, b, slots, ctx, res_idx);
}
let helper_name = if is_div {
"__int_div_euclid"
} else {
"__int_mod_euclid"
};
let helper_idx =
ctx.fn_map
.builtins
.get(helper_name)
.copied()
.ok_or(WasmGcError::Validation(format!(
"boxed Int.{} requires the {helper_name} helper to be registered",
if is_div { "div" } else { "mod" }
)))?;
let res_block = wasm_encoder::BlockType::Result(ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(res_idx),
}));
macro_rules! emit_err {
($msg:expr) => {{
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I64Const(0));
emit_string_literal_bytes(func, $msg, ctx)?;
func.instruction(&Instruction::StructNew(res_idx));
}};
}
macro_rules! emit_ok {
() => {{
func.instruction(&Instruction::I32Const(1));
e!(a);
e!(b);
func.instruction(&Instruction::Call(helper_idx));
emit_default_value(func, "String", ctx.registry)?;
func.instruction(&Instruction::StructNew(res_idx));
}};
}
e!(b);
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64Eq);
func.instruction(&Instruction::If(res_block));
emit_err!(b"division by zero");
func.instruction(&Instruction::Else);
if is_div {
e!(a);
func.instruction(&Instruction::I64Const(i64::MIN));
func.instruction(&Instruction::I64Eq);
e!(b);
func.instruction(&Instruction::I64Const(-1));
func.instruction(&Instruction::I64Eq);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(res_block));
emit_err!(b"division overflow");
func.instruction(&Instruction::Else);
emit_ok!();
func.instruction(&Instruction::End);
} else {
emit_ok!();
}
func.instruction(&Instruction::End);
Ok(MirBuiltinEmit::Produced(true))
}
fn emit_mir_aint_div_mod_boxed(
func: &mut Function,
is_div: bool,
a: &Spanned<MirExpr>,
b: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
res_idx: u32,
) -> Result<MirBuiltinEmit, WasmGcError> {
let aint_idx = ctx.registry.aint_struct_idx.ok_or(WasmGcError::Validation(
"bignum Int.div/mod requires the $AverInt struct slot to be registered".into(),
))?;
let divmod_idx =
ctx.fn_map
.builtins
.get("__aint_divmod")
.copied()
.ok_or(WasmGcError::Validation(
"bignum Int.div/mod requires the __aint_divmod helper to be registered".into(),
))?;
macro_rules! e {
($x:expr) => {
if emit_mir_expr(func, $x, slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
let res_block = wasm_encoder::BlockType::Result(ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(res_idx),
}));
macro_rules! emit_err {
() => {{
func.instruction(&Instruction::I32Const(0));
emit_default_value(func, "Int", ctx.registry)?;
emit_string_literal_bytes(func, b"division by zero", ctx)?;
func.instruction(&Instruction::StructNew(res_idx));
}};
}
macro_rules! emit_ok {
() => {{
func.instruction(&Instruction::I32Const(1));
e!(a);
e!(b);
func.instruction(&Instruction::I32Const(if is_div { 0 } else { 1 }));
func.instruction(&Instruction::Call(divmod_idx));
emit_default_value(func, "String", ctx.registry)?;
func.instruction(&Instruction::StructNew(res_idx));
}};
}
e!(b);
func.instruction(&Instruction::StructGet {
struct_type_index: aint_idx,
field_index: 1,
});
func.instruction(&Instruction::RefIsNull);
e!(b);
func.instruction(&Instruction::StructGet {
struct_type_index: aint_idx,
field_index: 0,
});
func.instruction(&Instruction::I64Eqz);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(res_block));
emit_err!();
func.instruction(&Instruction::Else);
emit_ok!();
func.instruction(&Instruction::End);
Ok(MirBuiltinEmit::Produced(true))
}
pub(crate) fn emit_mir_result_with_default(
func: &mut Function,
dotted: &str,
args: &[Spanned<MirExpr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, WasmGcError> {
if dotted != "Result.withDefault" || args.len() != 2 {
return Ok(MirBuiltinEmit::NotHandled);
}
let res_arg = &args[0];
let default = &args[1];
macro_rules! e {
($x:expr) => {
if emit_mir_expr(func, $x, slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
if let MirExpr::Call(inner) = &res_arg.node {
let inner = &inner.node;
if let MirCallee::Builtin(inner_id) = inner.callee
&& let Some(inner_dotted) = ctx.mir_builtins.and_then(|n| n.get(inner_id.0 as usize))
&& (inner_dotted == "Int.mod" || inner_dotted == "Int.div")
&& inner.args.len() == 2
{
let is_mod = inner_dotted == "Int.mod";
let a = &inner.args[0];
let b = &inner.args[1];
if ctx.registry.bignum {
let aint_idx = ctx.registry.aint_struct_idx.ok_or(WasmGcError::Validation(
"bignum Result.withDefault(Int.div/mod) needs the $AverInt slot".into(),
))?;
let divmod_idx = ctx.fn_map.builtins.get("__aint_divmod").copied().ok_or(
WasmGcError::Validation(
"bignum Result.withDefault(Int.div/mod) needs the __aint_divmod helper"
.into(),
),
)?;
let block_ty = wasm_encoder::BlockType::Result(
crate::codegen::wasm_gc::types::struct_ref(aint_idx),
);
e!(b);
func.instruction(&Instruction::StructGet {
struct_type_index: aint_idx,
field_index: 1,
});
func.instruction(&Instruction::RefIsNull);
e!(b);
func.instruction(&Instruction::StructGet {
struct_type_index: aint_idx,
field_index: 0,
});
func.instruction(&Instruction::I64Eqz);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(block_ty));
e!(default);
func.instruction(&Instruction::Else);
e!(a);
e!(b);
func.instruction(&Instruction::I32Const(if is_mod { 1 } else { 0 }));
func.instruction(&Instruction::Call(divmod_idx));
func.instruction(&Instruction::End);
return Ok(MirBuiltinEmit::Produced(true));
}
let helper_name = if is_mod {
"__int_mod_euclid"
} else {
"__int_div_euclid"
};
let helper_idx =
ctx.fn_map
.builtins
.get(helper_name)
.copied()
.ok_or(WasmGcError::Validation(format!(
"Int.{} requires {} helper to be registered",
if is_mod { "mod" } else { "div" },
helper_name
)))?;
let block_ty = wasm_encoder::BlockType::Result(ValType::I64);
e!(b);
func.instruction(&Instruction::I64Const(0));
func.instruction(&Instruction::I64Eq);
func.instruction(&Instruction::If(block_ty));
e!(default);
func.instruction(&Instruction::Else);
e!(a);
e!(b);
func.instruction(&Instruction::Call(helper_idx));
func.instruction(&Instruction::End);
return Ok(MirBuiltinEmit::Produced(true));
}
}
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(),
))?;
e!(res_arg);
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);
e!(default);
func.instruction(&Instruction::End);
Ok(MirBuiltinEmit::Produced(true))
}
pub(crate) fn emit_mir_map_builtin(
func: &mut Function,
dotted: &str,
args: &[Spanned<MirExpr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, WasmGcError> {
if dotted == "Map.fromList" && args.len() == 1 {
let list_arg = &args[0];
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 from_list = ctx
.fn_map
.map_helpers_lookup(&map_canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.fromList: helpers for `{map_canonical}` not registered"
)))?
.from_list;
if emit_mir_expr(func, list_arg, slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
func.instruction(&Instruction::Call(from_list));
return Ok(MirBuiltinEmit::Produced(true));
}
let method = match dotted {
"Map.set" => "set",
"Map.get" => "get",
"Map.has" => "has",
"Map.len" => "len",
"Map.keys" => "keys",
"Map.values" => "values",
"Map.remove" => "remove",
"Map.entries" => "entries",
_ => return Ok(MirBuiltinEmit::NotHandled),
};
let arity = match method {
"set" => 3,
"get" | "has" | "remove" => 2,
_ => 1,
};
if args.len() != arity {
return Ok(MirBuiltinEmit::NotHandled);
}
let map_aver = aver_type_str_of(&args[0]);
let canonical: String = map_aver.chars().filter(|c| !c.is_whitespace()).collect();
macro_rules! emit_args {
() => {
for a in args {
if emit_mir_expr(func, a, slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
}
};
}
if method == "has" {
let get_pair = ctx
.fn_map
.map_helpers_lookup(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.has: map argument has type `{map_aver}` but no helpers are registered"
)))?
.get_pair;
emit_args!();
func.instruction(&Instruction::Call(get_pair));
func.instruction(&Instruction::Drop);
return Ok(MirBuiltinEmit::Produced(true));
}
let target = {
let helpers = ctx
.fn_map
.map_helpers_lookup(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Map.{method}: map argument has type `{map_aver}` but no helpers are registered"
)))?;
match method {
"set" => {
if mir_arg_uniquely_owned(&args[0], ctx) {
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,
_ => unreachable!("outer match restricts method"),
}
};
emit_args!();
func.instruction(&Instruction::Call(target));
if method == "len" {
lift_i64_result_to_aint(func, ctx)?;
}
Ok(MirBuiltinEmit::Produced(true))
}
pub(crate) fn emit_mir_list_builtin(
func: &mut Function,
dotted: &str,
args: &[Spanned<MirExpr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, WasmGcError> {
macro_rules! emit_args {
() => {
for a in args {
if emit_mir_expr(func, a, slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
}
};
}
match dotted {
"List.reverse" | "List.len" | "List.length" | "List.concat" | "List.take" | "List.drop"
| "List.contains" => {
let arity_ok = match dotted {
"List.reverse" | "List.len" | "List.length" => args.len() == 1,
_ => args.len() == 2,
};
if !arity_ok {
return Ok(MirBuiltinEmit::NotHandled);
}
let list_aver = aver_type_str_of(&args[0]);
let canonical = normalize_compound(&list_aver);
let fn_idx = {
let ops = ctx
.fn_map
.list_ops_lookup(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List op called but `{canonical}` helper wasn't registered"
)))?;
match dotted {
"List.reverse" => ops.reverse,
"List.len" | "List.length" => ops.len,
"List.concat" => ops.concat,
"List.take" => ops.take,
"List.drop" => ops.drop,
"List.contains" => match ops.contains {
Some(idx) => idx,
None => return Ok(MirBuiltinEmit::Fallback),
},
_ => unreachable!("outer match restricts dotted"),
}
};
if matches!(dotted, "List.take" | "List.drop") {
if emit_mir_expr(func, &args[0], slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
if !emit_aint_arg_as_i64_sat(func, &args[1], slots, ctx)? {
return Ok(MirBuiltinEmit::Fallback);
}
} else {
emit_args!();
}
func.instruction(&Instruction::Call(fn_idx));
if matches!(dotted, "List.len" | "List.length") {
lift_i64_result_to_aint(func, ctx)?;
}
}
"List.zip" if args.len() == 2 => {
let la_aver = aver_type_str_of(&args[0]);
let lb_aver = aver_type_str_of(&args[1]);
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_args!();
func.instruction(&Instruction::Call(zip_fn));
}
"List.fromVector" if args.len() == 1 => {
let vec_aver = aver_type_str_of(&args[0]);
let vec_canonical: String = vec_aver.chars().filter(|c| !c.is_whitespace()).collect();
let elem = 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 to_list = ctx
.fn_map
.vfl_ops
.get(&list_canonical)
.ok_or(WasmGcError::Validation(format!(
"List.fromVector: helper for `{list_canonical}` wasn't registered"
)))?
.to_list;
emit_args!();
func.instruction(&Instruction::Call(to_list));
}
"List.prepend" if args.len() == 2 => {
let tail_ty = aver_type_str_of(&args[1]);
let canonical: String = tail_ty.chars().filter(|c| !c.is_whitespace()).collect();
let list_idx =
ctx.registry
.list_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List.prepend: tail type `{tail_ty}` is not a registered List<T>"
)))?;
emit_args!();
func.instruction(&Instruction::StructNew(list_idx));
}
"List.empty" if args.is_empty() => {
let canonical = if ctx.registry.list_order.len() == 1 {
ctx.registry.list_order[0].clone()
} else {
ctx.return_type
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>()
};
let list_idx =
ctx.registry
.list_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List.empty: cannot resolve list instantiation (got `{canonical}`)"
)))?;
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
list_idx,
)));
}
_ => return Ok(MirBuiltinEmit::NotHandled),
}
Ok(MirBuiltinEmit::Produced(true))
}
pub(crate) fn mir_arg_uniquely_owned(arg: &Spanned<MirExpr>, ctx: &EmitCtx<'_>) -> bool {
match &arg.node {
MirExpr::Local(local) => {
local.node.last_use && !ctx.is_aliased_slot(local.node.slot.0 as u16)
}
_ => true,
}
}
pub(crate) fn emit_mir_vector_builtin(
func: &mut Function,
dotted: &str,
args: &[Spanned<MirExpr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, WasmGcError> {
match dotted {
"Vector.len" if args.len() == 1 => {
if emit_mir_expr(func, &args[0], slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I64ExtendI32U);
lift_i64_result_to_aint(func, ctx)?;
}
"Vector.new" if args.len() == 2 => {
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"
)))?;
if emit_mir_expr(func, &args[1], slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
if !emit_index_i32(func, &args[0], slots, ctx)? {
return Ok(MirBuiltinEmit::Fallback);
}
func.instruction(&Instruction::ArrayNew(vec_idx));
}
"Vector.get" if args.len() == 2 => {
return emit_mir_vector_get_boxed(func, &args[0], &args[1], slots, ctx);
}
"Vector.set" if args.len() == 3 => {
return emit_mir_vector_set_boxed(func, &args[0], &args[1], &args[2], slots, ctx);
}
"Vector.fromList" if args.len() == 1 => {
let list_aver = aver_type_str_of(&args[0]);
let canonical = normalize_compound(&list_aver);
let from_list = 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)"
)))?
.from_list;
if emit_mir_expr(func, &args[0], slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
func.instruction(&Instruction::Call(from_list));
}
_ => return Ok(MirBuiltinEmit::NotHandled),
}
Ok(MirBuiltinEmit::Produced(true))
}
pub(crate) fn emit_mir_vector_get_boxed(
func: &mut Function,
vector: &Spanned<MirExpr>,
index: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, 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 = 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);
macro_rules! e {
($x:expr) => {
if emit_mir_expr(func, $x, slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
macro_rules! idx_nonneg {
() => {
if !emit_index_nonneg(func, index, slots, ctx)? {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
macro_rules! idx_i32 {
() => {
if !emit_index_i32(func, index, slots, ctx)? {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
idx_nonneg!();
idx_i32!();
e!(vector);
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(block_ty));
func.instruction(&Instruction::I32Const(1));
e!(vector);
idx_i32!();
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(MirBuiltinEmit::Produced(true))
}
pub(crate) fn emit_mir_vector_set_boxed(
func: &mut Function,
vector: &Spanned<MirExpr>,
index: &Spanned<MirExpr>,
value: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<MirBuiltinEmit, 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);
macro_rules! e {
($x:expr) => {
if emit_mir_expr(func, $x, slots, ctx)?.is_none() {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
macro_rules! idx_nonneg {
() => {
if !emit_index_nonneg(func, index, slots, ctx)? {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
macro_rules! idx_i32 {
() => {
if !emit_index_i32(func, index, slots, ctx)? {
return Ok(MirBuiltinEmit::Fallback);
}
};
}
if mir_arg_uniquely_owned(vector, ctx) {
idx_nonneg!();
idx_i32!();
e!(vector);
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(block_ty));
e!(vector);
idx_i32!();
e!(value);
func.instruction(&Instruction::ArraySet(vec_idx));
func.instruction(&Instruction::I32Const(1));
e!(vector);
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(MirBuiltinEmit::Produced(true));
}
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)"
))
})?;
idx_nonneg!();
idx_i32!();
e!(vector);
func.instruction(&Instruction::ArrayLen);
func.instruction(&Instruction::I32LtU);
func.instruction(&Instruction::I32And);
func.instruction(&Instruction::If(block_ty));
e!(vector);
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));
e!(vector);
func.instruction(&Instruction::I32Const(0));
e!(vector);
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));
idx_i32!();
e!(value);
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(MirBuiltinEmit::Produced(true))
}
pub(crate) fn emit_mir_numeric_binop(
func: &mut Function,
bop: &crate::ir::mir::MirBinOp,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let l = &bop.lhs;
let r = &bop.rhs;
let both_raw = super::mir_int_binop_is_raw(bop, ctx);
if both_raw {
if super::emit_mir_int_raw(func, l, slots, ctx)?.is_none() {
return Ok(None);
}
if super::emit_mir_int_raw(func, r, slots, ctx)?.is_none() {
return Ok(None);
}
let inst = match bop.op {
BinOp::Add => Instruction::I64Add,
BinOp::Sub => Instruction::I64Sub,
BinOp::Mul => Instruction::I64Mul,
BinOp::Eq => Instruction::I64Eq,
BinOp::Neq => Instruction::I64Ne,
BinOp::Lt => Instruction::I64LtS,
BinOp::Gt => Instruction::I64GtS,
BinOp::Lte => Instruction::I64LeS,
BinOp::Gte => Instruction::I64GeS,
_ => unreachable!("both_raw gated to Add/Sub/Mul + the 6 comparisons"),
};
func.instruction(&inst);
return Ok(Some(()));
}
let l_ty = wasm_type_of(l, ctx.registry)?;
let r_ty = wasm_type_of(r, ctx.registry)?;
let operand = if l_ty == Some(ValType::F64) || r_ty == Some(ValType::F64) {
Some(ValType::F64)
} else {
l_ty
};
if ctx.registry.bignum
&& ctx.registry.aint_struct_idx.is_some()
&& operand != Some(ValType::F64)
&& operand != Some(ValType::I32)
&& l_ty
== ctx
.registry
.aint_struct_idx
.map(crate::codegen::wasm_gc::types::struct_ref)
&& r_ty
== ctx
.registry
.aint_struct_idx
.map(crate::codegen::wasm_gc::types::struct_ref)
&& matches!(
bop.op,
BinOp::Eq | BinOp::Neq | BinOp::Lt | BinOp::Gt | BinOp::Lte | BinOp::Gte
)
{
let lit = mir_int_literal(l)
.map(|k| (k, true))
.or_else(|| mir_int_literal(r).map(|k| (k, false)));
if let Some((k, const_on_left)) = lit {
let non_lit = if const_on_left { r } else { l };
if emit_mir_expr(func, non_lit, slots, ctx)?.is_none() {
return Ok(None);
}
return emit_aint_cmp_const(func, bop.op, k, const_on_left, slots, ctx);
}
}
if emit_mir_expr(func, l, slots, ctx)?.is_none() {
return Ok(None);
}
if operand == Some(ValType::F64) && l_ty == Some(ValType::I64) {
func.instruction(&Instruction::F64ConvertI64S);
}
if emit_mir_expr(func, r, slots, ctx)?.is_none() {
return Ok(None);
}
if operand == Some(ValType::F64) && r_ty == Some(ValType::I64) {
func.instruction(&Instruction::F64ConvertI64S);
}
if ctx.registry.bignum
&& operand != Some(ValType::F64)
&& operand != Some(ValType::I32)
&& ctx.registry.aint_struct_idx.is_some()
&& l_ty
== ctx
.registry
.aint_struct_idx
.map(crate::codegen::wasm_gc::types::struct_ref)
{
return emit_aint_binop(func, bop.op, ctx);
}
let inst = match (operand, bop.op) {
(Some(ValType::F64), BinOp::Add) => Instruction::F64Add,
(Some(ValType::F64), BinOp::Sub) => Instruction::F64Sub,
(Some(ValType::F64), BinOp::Mul) => Instruction::F64Mul,
(Some(ValType::F64), BinOp::Div) => Instruction::F64Div,
(Some(ValType::F64), BinOp::Eq) => Instruction::F64Eq,
(Some(ValType::F64), BinOp::Neq) => Instruction::F64Ne,
(Some(ValType::F64), BinOp::Lt) => Instruction::F64Lt,
(Some(ValType::F64), BinOp::Gt) => Instruction::F64Gt,
(Some(ValType::F64), BinOp::Lte) => Instruction::F64Le,
(Some(ValType::F64), BinOp::Gte) => Instruction::F64Ge,
(Some(ValType::I32), BinOp::Eq) => Instruction::I32Eq,
(Some(ValType::I32), BinOp::Neq) => Instruction::I32Ne,
(_, BinOp::Add) => Instruction::I64Add,
(_, BinOp::Sub) => Instruction::I64Sub,
(_, BinOp::Mul) => Instruction::I64Mul,
(_, BinOp::Div) => Instruction::I64DivS,
(_, BinOp::Eq) => Instruction::I64Eq,
(_, BinOp::Neq) => Instruction::I64Ne,
(_, BinOp::Lt) => Instruction::I64LtS,
(_, BinOp::Gt) => Instruction::I64GtS,
(_, BinOp::Lte) => Instruction::I64LeS,
(_, BinOp::Gte) => Instruction::I64GeS,
};
func.instruction(&inst);
Ok(Some(()))
}
fn emit_aint_binop(
func: &mut Function,
op: BinOp,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let call = |name: &str| -> Result<u32, WasmGcError> {
ctx.fn_map
.builtins
.get(name)
.copied()
.ok_or(WasmGcError::Validation(format!(
"bignum active but {name} helper not registered"
)))
};
match op {
BinOp::Add => {
func.instruction(&Instruction::Call(call("__aint_add")?));
}
BinOp::Sub => {
func.instruction(&Instruction::Call(call("__aint_sub")?));
}
BinOp::Mul => {
func.instruction(&Instruction::Call(call("__aint_mul")?));
}
BinOp::Div => {
return Ok(None);
}
BinOp::Eq => {
func.instruction(&Instruction::Call(call("__aint_eq")?));
}
BinOp::Neq => {
func.instruction(&Instruction::Call(call("__aint_eq")?));
func.instruction(&Instruction::I32Eqz);
}
BinOp::Lt => {
func.instruction(&Instruction::Call(call("__aint_cmp")?));
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32LtS);
}
BinOp::Gt => {
func.instruction(&Instruction::Call(call("__aint_cmp")?));
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32GtS);
}
BinOp::Lte => {
func.instruction(&Instruction::Call(call("__aint_cmp")?));
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32LeS);
}
BinOp::Gte => {
func.instruction(&Instruction::Call(call("__aint_cmp")?));
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32GeS);
}
}
Ok(Some(()))
}
fn mir_int_literal(expr: &Spanned<MirExpr>) -> Option<i64> {
match &expr.node {
MirExpr::Literal(l) => match l.node {
crate::ast::Literal::Int(n) => Some(n),
_ => None,
},
_ => None,
}
}
fn emit_aint_cmp_const(
func: &mut Function,
op: BinOp,
k: i64,
const_on_left: bool,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let aint_idx = ctx.registry.aint_struct_idx.ok_or(WasmGcError::Validation(
"const-compare specialization requires the $AverInt struct slot".into(),
))?;
let scratch = slots.const_cmp_scratch.ok_or(WasmGcError::Validation(
"const-compare specialization needs a scratch slot but none was reserved".into(),
))?;
let eff = if const_on_left { flip_cmp(op) } else { op };
func.instruction(&Instruction::LocalSet(scratch));
let block_ty = wasm_encoder::BlockType::Result(ValType::I32);
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::StructGet {
struct_type_index: aint_idx,
field_index: 1,
});
func.instruction(&Instruction::RefIsNull);
func.instruction(&Instruction::If(block_ty));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::StructGet {
struct_type_index: aint_idx,
field_index: 0,
});
func.instruction(&Instruction::I64Const(k));
func.instruction(&match eff {
BinOp::Eq => Instruction::I64Eq,
BinOp::Neq => Instruction::I64Ne,
BinOp::Lt => Instruction::I64LtS,
BinOp::Gt => Instruction::I64GtS,
BinOp::Lte => Instruction::I64LeS,
BinOp::Gte => Instruction::I64GeS,
_ => unreachable!("emit_aint_cmp_const gated to the six comparisons"),
});
func.instruction(&Instruction::Else);
match eff {
BinOp::Lt | BinOp::Lte => {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::StructGet {
struct_type_index: aint_idx,
field_index: 2,
});
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32LtS);
}
BinOp::Gt | BinOp::Gte => {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::StructGet {
struct_type_index: aint_idx,
field_index: 2,
});
func.instruction(&Instruction::I32Const(0));
func.instruction(&Instruction::I32GtS);
}
BinOp::Eq => {
func.instruction(&Instruction::I32Const(0));
}
BinOp::Neq => {
func.instruction(&Instruction::I32Const(1));
}
_ => unreachable!("emit_aint_cmp_const gated to the six comparisons"),
}
func.instruction(&Instruction::End);
Ok(Some(()))
}
fn flip_cmp(op: BinOp) -> BinOp {
match op {
BinOp::Lt => BinOp::Gt,
BinOp::Gt => BinOp::Lt,
BinOp::Lte => BinOp::Gte,
BinOp::Gte => BinOp::Lte,
BinOp::Eq => BinOp::Eq,
BinOp::Neq => BinOp::Neq,
other => other,
}
}