pub(super) use std::collections::{HashMap, HashSet};
pub(super) use wasm_encoder::{Function, Instruction, ValType};
pub(super) use crate::ast::Spanned;
pub(super) use crate::ast::{BinOp, Literal};
pub(super) use crate::ir::CtorId;
pub(super) use crate::ir::SymbolTable;
pub(super) use crate::ir::hir::{ResolvedFnBody, ResolvedFnDef, ResolvedStmt};
pub(super) use crate::ir::mir::{
BuiltinCtor, MirCallee, MirCtor, MirExpr, MirFn, MirIndependentProduct, MirMatch, MirMatchArm,
MirPattern, MirProgram, MirRecordField, MirStrPart,
};
pub(super) use crate::types::Type;
pub(super) use super::super::WasmGcError;
pub(super) use super::super::types::{TypeRegistry, VariantInfo, aver_to_wasm, normalize_compound};
pub(super) use super::builtins::emit_args_get_inline;
pub(super) use super::emit::{
emit_branch_marker, emit_caller_fn_idx, emit_default_value, emit_group_call,
emit_return_call_insn, emit_string_literal_bytes, sum_or_record_eq_fn,
};
pub(super) use super::infer::{aver_type_canonical, aver_type_str_of, wasm_type_of};
pub(super) use super::slots::count_value_params;
pub(super) use super::{CallerFnCollector, EmitCtx, FnMap, SlotTable, Wasip2Lowering};
mod builtins;
mod collections;
mod constructors;
mod control;
mod coverage;
mod pattern_match;
mod records;
mod strings;
pub(super) use builtins::*;
pub(super) use collections::*;
pub(super) use constructors::*;
pub(super) use control::*;
pub(super) use pattern_match::*;
pub(super) use records::*;
pub(super) use strings::*;
pub use coverage::{CoverageReport, coverage_report};
pub(crate) fn emit_carrier_construct_bridge(
func: &mut Function,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if ctx.registry.bignum {
let to_checked = ctx
.fn_map
.builtins
.get("__aint_to_i64_checked")
.copied()
.ok_or(WasmGcError::Validation(
"carrier-i64 construct needs __aint_to_i64_checked but it is not registered".into(),
))?;
func.instruction(&Instruction::Call(to_checked));
}
Ok(())
}
pub(crate) fn emit_carrier_project_bridge(
func: &mut Function,
ctx: &EmitCtx<'_>,
) -> Result<(), WasmGcError> {
if ctx.registry.bignum {
let from_i64 =
ctx.fn_map
.builtins
.get("__aint_from_i64")
.copied()
.ok_or(WasmGcError::Validation(
"carrier-i64 project needs __aint_from_i64 but it is not registered".into(),
))?;
func.instruction(&Instruction::Call(from_i64));
}
Ok(())
}
fn emit_mir_int_stringify(
func: &mut Function,
arg: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
enum Raw<'a> {
Inner(&'a Spanned<MirExpr>),
CarrierProject(&'a Spanned<MirExpr>),
}
let raw: Option<Raw<'_>> = match &arg.node {
MirExpr::Box(inner) if mir_renders_raw_i64(&inner.node, ctx) => Some(Raw::Inner(inner)),
_ if mir_renders_raw_i64(&arg.node, ctx) => Some(Raw::Inner(arg)),
MirExpr::Project(_) if mir_is_eligible_carrier_value_project(&arg.node, ctx) => {
Some(Raw::CarrierProject(arg))
}
_ => None,
};
if ctx.registry.bignum
&& let Some(raw) = raw
{
let produced = match raw {
Raw::Inner(inner) => emit_mir_int_raw(func, inner, slots, ctx)?,
Raw::CarrierProject(p) => emit_mir_carrier_value_raw(func, p, slots, ctx)?,
};
if produced.is_none() {
return Ok(None);
}
let lean = ctx
.fn_map
.builtins
.get("__wasmgc_string_from_int_i64")
.copied()
.ok_or(WasmGcError::Validation(
"raw-i64 String.fromInt needs __wasmgc_string_from_int_i64 but it is not registered"
.into(),
))?;
func.instruction(&Instruction::Call(lean));
return Ok(Some(()));
}
if emit_mir_expr(func, arg, slots, ctx)?.is_none() {
return Ok(None);
}
let to_string_idx =
ctx.fn_map
.builtins
.get("String.fromInt")
.copied()
.ok_or(WasmGcError::Validation(
"stringify of Int requires String.fromInt builtin".into(),
))?;
func.instruction(&Instruction::Call(to_string_idx));
Ok(Some(()))
}
fn registered_builtin_returns_raw_int(dotted: &str) -> bool {
matches!(dotted, "String.len" | "String.byteLength" | "Char.toCode")
}
fn registered_builtin_int_arg_positions(dotted: &str) -> &'static [usize] {
match dotted {
"Char.fromCode" => &[0],
"String.charAt" => &[1],
"String.slice" => &[1, 2],
"Byte.toHex" => &[0],
_ => &[],
}
}
fn effect_int_arg_positions(dotted: &str) -> &'static [usize] {
match dotted {
"Random.int" => &[0, 1],
"Time.sleep" => &[0],
"Response.text" => &[0],
"Tcp.send" | "Tcp.ping" | "Tcp.connect" => &[1],
"HttpServer.listen" | "HttpServer.listenWith" => &[0],
"Terminal.moveTo" => &[0, 1],
_ => &[],
}
}
fn effect_returns_int(dotted: &str) -> bool {
matches!(dotted, "Random.int" | "Time.unixMs")
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn emit_fn_body_via_mir(
func: &mut Function,
rfd: &ResolvedFnDef,
mir_fn: &MirFn,
mir_program: &MirProgram,
fn_map: &FnMap,
self_wasm_idx: u32,
registry: &TypeRegistry,
symbol_table: &SymbolTable,
effect_idx_lookup: &HashMap<String, u32>,
caller_fn_collector: &std::cell::RefCell<CallerFnCollector>,
wasip2_lowering: Option<&Wasip2Lowering>,
) -> Result<Option<Vec<ValType>>, WasmGcError> {
let slots = SlotTable::build_for_fn(rfd, registry, fn_map, &mir_fn.repr)?;
let return_type_str = rfd.return_type.display();
let ResolvedFnBody::Block(stmts) = rfd.body.as_ref();
let mut binding_names: HashSet<String> = HashSet::new();
for s in stmts {
if let ResolvedStmt::Binding { name, .. } = s {
binding_names.insert(name.clone());
}
}
let ctx = EmitCtx {
fn_map,
self_wasm_idx,
self_fn_name: rfd.name.as_str(),
return_type: &return_type_str,
registry,
symbol_table,
resolution: rfd.resolution.as_ref(),
aliased_slots: mir_fn.aliased_slots.as_slice(),
repr: &mir_fn.repr,
params: &rfd.params,
binding_names: &binding_names,
effect_idx_lookup,
caller_fn_collector,
wasip2_lowering,
mir_builtins: Some(&mir_program.builtins),
mir_program: Some(mir_program),
int_result_raw: std::cell::Cell::new(false),
};
ctx.int_result_raw.set(mir_fn.repr.bare_return);
let Some(produces_value) = emit_mir_expr(func, &mir_fn.body, &slots, &ctx)? else {
return Ok(None);
};
if return_type_str.trim() == "Unit" && produces_value {
func.instruction(&Instruction::Drop);
} else if return_type_str.trim() != "Unit" && !produces_value {
return Err(WasmGcError::Validation(format!(
"fn `{}` returns {} but trailing expression yields no value",
rfd.name, return_type_str
)));
}
func.instruction(&Instruction::End);
Ok(Some(slots.extra_locals(count_value_params(&rfd.params))))
}
fn mir_nullary_variant_idx(operand: &Spanned<MirExpr>, ctx: &EmitCtx<'_>) -> Option<u32> {
let MirExpr::Construct(c) = &operand.node else {
return None;
};
let MirCtor::User(ctor_id) = c.node.ctor else {
return None;
};
if !c.node.args.is_empty() {
return None;
}
let info = mir_user_variant_info(ctor_id, ctx).ok()?;
if info.fields.is_empty() {
Some(info.type_idx)
} else {
None
}
}
pub(crate) fn emit_mir_expr(
func: &mut Function,
expr: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<bool>, WasmGcError> {
let result_raw = ctx.int_result_raw.replace(false);
match &expr.node {
MirExpr::Literal(lit) => match &lit.node {
Literal::Int(n) => {
if result_raw {
func.instruction(&Instruction::I64Const(*n));
} else if ctx.registry.bignum {
func.instruction(&Instruction::I64Const(*n));
let from_i64 = ctx.fn_map.builtins.get("__aint_from_i64").copied().ok_or(
WasmGcError::Validation(
"bignum active but __aint_from_i64 helper not registered".into(),
),
)?;
func.instruction(&Instruction::Call(from_i64));
} else {
func.instruction(&Instruction::I64Const(*n));
}
Ok(Some(true))
}
Literal::BigInt(s) => {
let bytes = s.as_bytes();
let seg_idx = ctx.registry.string_literal_segment(bytes).ok_or(
WasmGcError::Validation(format!(
"Big-int literal `{s:?}` digit string was not registered in the data segment table"
)),
)?;
let string_type_idx =
ctx.registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"Big-int literal reachable but no String type slot allocated".into(),
))?;
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,
});
let from_string =
ctx.fn_map
.builtins
.get("Int.fromString")
.copied()
.ok_or(WasmGcError::Validation(
"big-int literal needs the Int.fromString helper but it was not registered"
.into(),
))?;
func.instruction(&Instruction::Call(from_string));
let result_idx = ctx.registry.result_type_idx("Result<Int,String>").ok_or(
WasmGcError::Validation(
"big-int literal needs the Result<Int,String> type slot but it was not allocated"
.into(),
),
)?;
func.instruction(&Instruction::StructGet {
struct_type_index: result_idx,
field_index: 1,
});
Ok(Some(true))
}
Literal::Float(f) => {
func.instruction(&Instruction::F64Const((*f).into()));
Ok(Some(true))
}
Literal::Bool(b) => {
func.instruction(&Instruction::I32Const(if *b { 1 } else { 0 }));
Ok(Some(true))
}
Literal::Unit => Ok(Some(false)),
Literal::Str(s) => {
let bytes = s.as_bytes();
let seg_idx =
ctx.registry
.string_literal_segment(bytes)
.ok_or(WasmGcError::Validation(format!(
"String literal `{s:?}` was not registered in the data segment table"
)))?;
let string_type_idx =
ctx.registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"String literal reachable but no String type slot allocated".into(),
))?;
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,
});
Ok(Some(true))
}
},
MirExpr::Local(local) => {
func.instruction(&Instruction::LocalGet(local.node.slot.0));
Ok(Some(aver_type_str_of(expr).trim() != "Unit"))
}
MirExpr::BinOp(spanned_binop) => {
let bop = &spanned_binop.node;
if aver_type_str_of(&bop.lhs).trim() == "String" {
return match emit_mir_string_binop(func, bop, slots, ctx)? {
Some(()) => Ok(Some(true)),
None => Ok(None),
};
}
match bop.lhs.ty() {
Some(Type::Int) | Some(Type::Float) => {
if emit_mir_numeric_binop(func, bop, slots, ctx)?.is_none() {
return Ok(None);
}
Ok(Some(true))
}
Some(lty) if matches!(bop.op, BinOp::Eq | BinOp::Neq) => {
let neq = matches!(bop.op, BinOp::Neq);
if let Some(vidx) = mir_nullary_variant_idx(&bop.rhs, ctx) {
if emit_mir_expr(func, &bop.lhs, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::RefTestNonNull(
wasm_encoder::HeapType::Concrete(vidx),
));
if neq {
func.instruction(&Instruction::I32Eqz);
}
return Ok(Some(true));
}
if let Some(vidx) = mir_nullary_variant_idx(&bop.lhs, ctx) {
if emit_mir_expr(func, &bop.rhs, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::RefTestNonNull(
wasm_encoder::HeapType::Concrete(vidx),
));
if neq {
func.instruction(&Instruction::I32Eqz);
}
return Ok(Some(true));
}
let Some(eq_fn) = sum_or_record_eq_fn(lty, ctx) else {
if emit_mir_numeric_binop(func, bop, slots, ctx)?.is_none() {
return Ok(None);
}
return Ok(Some(true));
};
if emit_mir_expr(func, &bop.lhs, slots, ctx)?.is_none() {
return Ok(None);
}
if emit_mir_expr(func, &bop.rhs, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Call(eq_fn));
if neq {
func.instruction(&Instruction::I32Eqz);
}
Ok(Some(true))
}
_ => Ok(None),
}
}
MirExpr::Neg(inner) => {
let inner_ty = wasm_type_of(inner, ctx.registry)?;
if inner_ty == Some(ValType::F64) {
if emit_mir_expr(func, inner, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::F64Neg);
} else if mir_renders_raw_i64(&inner.node, ctx) {
func.instruction(&Instruction::I64Const(0));
if emit_mir_int_raw(func, inner, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::I64Sub);
} else if ctx.registry.bignum {
if emit_mir_expr(func, inner, slots, ctx)?.is_none() {
return Ok(None);
}
let neg = ctx.fn_map.builtins.get("__aint_neg").copied().ok_or(
WasmGcError::Validation(
"bignum active but __aint_neg helper not registered".into(),
),
)?;
func.instruction(&Instruction::Call(neg));
} else {
func.instruction(&Instruction::I64Const(0));
if emit_mir_expr(func, inner, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::I64Sub);
}
Ok(Some(true))
}
MirExpr::Return(inner) => {
ctx.int_result_raw.set(result_raw);
let Some(produces) = emit_mir_expr(func, inner, slots, ctx)? else {
return Ok(None);
};
func.instruction(&Instruction::Return);
Ok(Some(produces))
}
MirExpr::Let(spanned_let) => {
let l = &spanned_let.node;
if l.binding_name.is_empty() {
let Some(value_produces) = emit_mir_expr(func, &l.value, slots, ctx)? else {
return Ok(None);
};
if value_produces {
func.instruction(&Instruction::Drop);
}
ctx.int_result_raw.set(result_raw);
return emit_mir_expr(func, &l.body, slots, ctx);
}
let slot = ctx
.self_local_slot(&l.binding_name)
.ok_or(WasmGcError::Validation(format!(
"binding `{}` has no resolver slot",
l.binding_name
)))?;
let value_produces = if ctx.slot_is_bare(slot as u32) {
match emit_mir_int_raw(func, &l.value, slots, ctx)? {
Some(p) => p,
None => return Ok(None),
}
} else {
match emit_mir_expr(func, &l.value, slots, ctx)? {
Some(p) => p,
None => return Ok(None),
}
};
if value_produces && (slot as usize) < slots.by_slot.len() {
func.instruction(&Instruction::LocalSet(slot));
}
ctx.int_result_raw.set(result_raw);
emit_mir_expr(func, &l.body, slots, ctx)
}
MirExpr::Call(spanned_call) => {
let call = &spanned_call.node;
match call.callee {
MirCallee::Fn(fn_id) => {
match ctx.fn_map.by_id.get(&fn_id) {
None => {
let name = ctx.symbol_table.fn_entry(fn_id).key.name.clone();
if ctx.self_local_slot(&name).is_some() {
func.instruction(&Instruction::Unreachable);
Ok(Some(aver_type_str_of(expr).trim() != "Unit"))
} else {
Err(WasmGcError::Validation(format!(
"call to unknown fn `{name}` (FnId {fn_id:?})"
)))
}
}
Some(entry) => {
let wasm_idx = entry.wasm_idx;
if emit_mir_fn_args_then_call(
func, fn_id, &call.args, slots, ctx, wasm_idx,
)?
.is_none()
{
return Ok(None);
}
Ok(Some(aver_type_str_of(expr).trim() != "Unit"))
}
}
}
MirCallee::Builtin(id) => {
let Some(dotted) = ctx.mir_builtins.and_then(|names| names.get(id.0 as usize))
else {
return Ok(None);
};
let dotted = dotted.as_str();
match emit_mir_wasip2_effect(func, dotted, &call.args, expr, slots, ctx)? {
MirBuiltinEmit::Produced(produces) => return Ok(Some(produces)),
MirBuiltinEmit::Fallback => return Ok(None),
MirBuiltinEmit::NotHandled => {}
}
if dotted == "Args.get" {
emit_args_get_inline(func, slots, ctx)?;
return Ok(Some(aver_type_str_of(expr).trim() != "Unit"));
}
if let Some(&effect_idx) = ctx.fn_map.effects.get(dotted) {
let int_args = effect_int_arg_positions(dotted);
for (i, arg) in call.args.iter().enumerate() {
if emit_mir_expr(func, arg, slots, ctx)?.is_none() {
return Ok(None);
}
if ctx.registry.bignum && int_args.contains(&i) {
let to_checked = ctx
.fn_map
.builtins
.get("__aint_to_i64_checked")
.copied()
.ok_or(WasmGcError::Validation(
"bignum active but __aint_to_i64_checked helper not \
registered"
.into(),
))?;
func.instruction(&Instruction::Call(to_checked));
}
}
emit_caller_fn_idx(func, ctx)?;
func.instruction(&Instruction::Call(effect_idx));
if effect_returns_int(dotted) {
lift_i64_result_to_aint(func, ctx)?;
}
return Ok(Some(aver_type_str_of(expr).trim() != "Unit"));
}
match emit_mir_native_scalar_builtin(func, dotted, &call.args, slots, ctx)? {
MirBuiltinEmit::Produced(produces) => return Ok(Some(produces)),
MirBuiltinEmit::Fallback => return Ok(None),
MirBuiltinEmit::NotHandled => {}
}
match emit_mir_list_builtin(func, dotted, &call.args, slots, ctx)? {
MirBuiltinEmit::Produced(produces) => return Ok(Some(produces)),
MirBuiltinEmit::Fallback => return Ok(None),
MirBuiltinEmit::NotHandled => {}
}
match emit_mir_vector_builtin(func, dotted, &call.args, slots, ctx)? {
MirBuiltinEmit::Produced(produces) => return Ok(Some(produces)),
MirBuiltinEmit::Fallback => return Ok(None),
MirBuiltinEmit::NotHandled => {}
}
match emit_mir_map_builtin(func, dotted, &call.args, slots, ctx)? {
MirBuiltinEmit::Produced(produces) => return Ok(Some(produces)),
MirBuiltinEmit::Fallback => return Ok(None),
MirBuiltinEmit::NotHandled => {}
}
match emit_mir_option_with_default(func, dotted, &call.args, slots, ctx)? {
MirBuiltinEmit::Produced(produces) => return Ok(Some(produces)),
MirBuiltinEmit::Fallback => return Ok(None),
MirBuiltinEmit::NotHandled => {}
}
match emit_mir_result_with_default(func, dotted, &call.args, slots, ctx)? {
MirBuiltinEmit::Produced(produces) => return Ok(Some(produces)),
MirBuiltinEmit::Fallback => return Ok(None),
MirBuiltinEmit::NotHandled => {}
}
match emit_mir_int_div_mod_boxed(func, dotted, &call.args, slots, ctx)? {
MirBuiltinEmit::Produced(produces) => return Ok(Some(produces)),
MirBuiltinEmit::Fallback => return Ok(None),
MirBuiltinEmit::NotHandled => {}
}
if ctx.registry.bignum && dotted == "String.fromInt" && call.args.len() == 1 {
return match emit_mir_int_stringify(func, &call.args[0], slots, ctx)? {
Some(()) => Ok(Some(aver_type_str_of(expr).trim() != "Unit")),
None => Ok(None),
};
}
match ctx.fn_map.builtins.get(dotted) {
Some(&wasm_idx) => {
let int_args = registered_builtin_int_arg_positions(dotted);
if emit_mir_args_then_call_lowering_int(
func, &call.args, slots, ctx, wasm_idx, int_args,
)?
.is_none()
{
return Ok(None);
}
if registered_builtin_returns_raw_int(dotted) {
lift_i64_result_to_aint(func, ctx)?;
}
Ok(Some(aver_type_str_of(expr).trim() != "Unit"))
}
None => Ok(None),
}
}
MirCallee::Intrinsic(intr) => {
use crate::ir::hir::BuiltinIntrinsic;
if ctx.registry.bignum
&& matches!(
intr,
BuiltinIntrinsic::IntDivEuclid | BuiltinIntrinsic::IntModEuclid
)
&& call.args.len() == 2
{
let is_mod = matches!(intr, BuiltinIntrinsic::IntModEuclid);
let divmod = ctx.fn_map.builtins.get("__aint_divmod").copied().ok_or(
WasmGcError::Validation(
"bignum active but __aint_divmod helper not registered".into(),
),
)?;
if emit_mir_expr(func, &call.args[0], slots, ctx)?.is_none()
|| emit_mir_expr(func, &call.args[1], slots, ctx)?.is_none()
{
return Ok(None);
}
func.instruction(&Instruction::I32Const(if is_mod { 1 } else { 0 }));
func.instruction(&Instruction::Call(divmod));
return Ok(Some(true));
}
match ctx.fn_map.builtins.get(intr.name()) {
Some(&wasm_idx) => {
if emit_mir_args_then_call(func, &call.args, slots, ctx, wasm_idx)?
.is_none()
{
return Ok(None);
}
Ok(Some(aver_type_str_of(expr).trim() != "Unit"))
}
None => Ok(None),
}
}
MirCallee::LocalSlot { slot, ref name, .. } => {
let Some(key) = ctx.fn_param_fn_sig(name) else {
return Ok(None);
};
let Some(&type_index) = ctx.fn_map.call_indirect_types.get(&key) else {
return Ok(None);
};
for arg in &call.args {
if emit_mir_expr(func, arg, slots, ctx)?.is_none() {
return Ok(None);
}
}
func.instruction(&Instruction::LocalGet(slot as u32));
func.instruction(&Instruction::CallIndirect {
type_index,
table_index: 0,
});
Ok(Some(aver_type_str_of(expr).trim() != "Unit"))
}
}
}
MirExpr::TailCall(spanned_tc) => {
let tc = &spanned_tc.node;
let wasm_idx = match ctx.fn_map.by_id.get(&tc.target) {
Some(entry) => entry.wasm_idx,
None => {
let name = ctx.symbol_table.fn_entry(tc.target).key.canonical();
return Err(WasmGcError::Validation(format!(
"tail call to unknown fn `{name}` (FnId {:?})",
tc.target
)));
}
};
for (i, arg) in tc.args.iter().enumerate() {
if ctx.callee_param_is_bare(tc.target, i) {
if emit_mir_int_raw(func, arg, slots, ctx)?.is_none() {
return Ok(None);
}
} else if emit_mir_expr(func, arg, slots, ctx)?.is_none() {
return Ok(None);
}
}
emit_return_call_insn(func, wasm_idx, ctx.self_wasm_idx);
Ok(Some(aver_type_str_of(expr).trim() != "Unit"))
}
MirExpr::Match(spanned_match) => {
ctx.int_result_raw.set(result_raw);
emit_mir_match(func, &spanned_match.node, slots, ctx)
}
MirExpr::Construct(spanned_ctor) => {
let con = &spanned_ctor.node;
let covered = match con.ctor {
MirCtor::Builtin(BuiltinCtor::OptionSome) => {
if con.args.len() != 1 {
return Err(WasmGcError::Validation(format!(
"Option.Some constructor requires 1 arg, got {}",
con.args.len()
)));
}
emit_mir_option_constructor(func, Some(&con.args[0]), None, slots, ctx)?
}
MirCtor::Builtin(BuiltinCtor::OptionNone) => {
let stamped = aver_type_canonical(expr, ctx.return_type, ctx.registry);
let hint: String = stamped
.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
.map(|inner| inner.to_string())
.unwrap_or_else(|| {
let ret = normalize_compound(ctx.return_type);
ret.strip_prefix("Option<")
.and_then(|s| s.strip_suffix('>'))
.map(|inner| inner.to_string())
.unwrap_or(ret)
});
emit_mir_option_constructor(func, None, Some(&hint), slots, ctx)?
}
MirCtor::Builtin(BuiltinCtor::ResultOk) => emit_mir_result_constructor(
func,
"Ok",
con.args.first(),
expr.ty(),
slots,
ctx,
)?,
MirCtor::Builtin(BuiltinCtor::ResultErr) => emit_mir_result_constructor(
func,
"Err",
con.args.first(),
expr.ty(),
slots,
ctx,
)?,
MirCtor::User(ctor_id) => {
let info = mir_user_variant_info(ctor_id, ctx)?;
emit_mir_constructor_with_args(func, info, &con.args, slots, ctx)?
}
};
match covered {
Some(()) => Ok(Some(aver_type_str_of(expr).trim() != "Unit")),
None => Ok(None),
}
}
MirExpr::RecordCreate(spanned_rec) => {
let rec = &spanned_rec.node;
match emit_mir_record_create(func, &rec.type_name, &rec.fields, slots, ctx)? {
Some(()) => Ok(Some(aver_type_str_of(expr).trim() != "Unit")),
None => Ok(None),
}
}
MirExpr::RecordUpdate(spanned_upd) => {
let upd = &spanned_upd.node;
match emit_mir_record_update(func, &upd.type_name, &upd.base, &upd.updates, slots, ctx)?
{
Some(()) => Ok(Some(aver_type_str_of(expr).trim() != "Unit")),
None => Ok(None),
}
}
MirExpr::Project(spanned_proj) => {
let proj = &spanned_proj.node;
let record_name = aver_type_str_of(&proj.base);
if ctx.registry.newtype_underlying(&record_name).is_some() {
let produced = emit_mir_expr(func, &proj.base, slots, ctx)?;
let bare_carrier_read = mir_base_renders_carrier_i64_raw(&proj.base, ctx);
if produced.is_some()
&& ctx.registry.is_eligible_carrier(&record_name)
&& !bare_carrier_read
{
emit_carrier_project_bridge(func, ctx)?;
}
return Ok(produced.map(|_| aver_type_str_of(expr).trim() != "Unit"));
}
let (Some(type_idx), Some(field_idx)) = (
ctx.registry.record_type_idx(&record_name),
ctx.registry.record_field_index(&record_name, &proj.field),
) else {
return Ok(None);
};
if emit_mir_expr(func, &proj.base, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::StructGet {
struct_type_index: type_idx,
field_index: field_idx,
});
Ok(Some(aver_type_str_of(expr).trim() != "Unit"))
}
MirExpr::Tuple(items) => {
if items.len() < 2 {
return Err(WasmGcError::Validation(format!(
"Tuple literal needs at least 2 elements; got {}",
items.len()
)));
}
let elem_tys: Vec<String> = items.iter().map(aver_type_str_of).collect();
let canonical = format!("Tuple<{}>", elem_tys.join(","))
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>();
let tuple_idx =
ctx.registry
.tuple_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Tuple literal: `{canonical}` slot not registered"
)))?;
for item in items {
if emit_mir_expr(func, item, slots, ctx)?.is_none() {
return Ok(None);
}
}
func.instruction(&Instruction::StructNew(tuple_idx));
Ok(Some(aver_type_str_of(expr).trim() != "Unit"))
}
MirExpr::MapLiteral(entries) => {
let canonical: String = if entries.is_empty() {
let stamped = aver_type_canonical(expr, ctx.return_type, ctx.registry);
if ctx.registry.map_order.contains(&stamped) {
stamped
} else if ctx.registry.map_order.len() == 1 {
ctx.registry.map_order[0].clone()
} else {
return Err(WasmGcError::Validation(format!(
"empty MapLiteral: cannot resolve Map<K,V> instantiation \
(stamped `{stamped}` not registered; {} instantiations \
available)",
ctx.registry.map_order.len()
)));
}
} else {
let k_aver = aver_type_str_of(&entries[0].0);
let v_aver = aver_type_str_of(&entries[0].1);
format!("Map<{},{}>", k_aver.trim(), v_aver.trim())
.chars()
.filter(|c| !c.is_whitespace())
.collect()
};
let (empty_fn, set_fn) = {
let helpers =
ctx.fn_map
.map_helpers
.get(&canonical)
.ok_or(WasmGcError::Validation(format!(
"MapLiteral: helpers missing for `{canonical}`"
)))?;
(helpers.empty, helpers.set)
};
func.instruction(&Instruction::Call(empty_fn));
for (k_expr, v_expr) in entries {
if emit_mir_expr(func, k_expr, slots, ctx)?.is_none() {
return Ok(None);
}
if emit_mir_expr(func, v_expr, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Call(set_fn));
}
Ok(Some(aver_type_str_of(expr).trim() != "Unit"))
}
MirExpr::List(items) => match emit_mir_list_literal(func, expr, items, slots, ctx)? {
Some(()) => Ok(Some(aver_type_str_of(expr).trim() != "Unit")),
None => Ok(None),
},
MirExpr::Try(inner) => emit_mir_try(func, inner, slots, ctx),
MirExpr::InterpolatedStr(parts) => emit_mir_interpolated_str(func, parts, slots, ctx),
MirExpr::IndependentProduct(spanned_ip) => {
emit_mir_independent_product(func, &spanned_ip.node, slots, ctx)
}
MirExpr::IfThenElse(ite) => {
let ite = &ite.node;
let block_ty = if result_raw {
wasm_encoder::BlockType::Result(ValType::I64)
} else {
let result_ty =
aver_type_canonical(&ite.then_branch, ctx.return_type, ctx.registry);
match aver_to_wasm(&result_ty, Some(ctx.registry))? {
Some(v) => wasm_encoder::BlockType::Result(v),
None => wasm_encoder::BlockType::Empty,
}
};
let produces = !matches!(block_ty, wasm_encoder::BlockType::Empty);
if emit_mir_expr(func, &ite.cond, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::If(block_ty));
ctx.int_result_raw.set(result_raw);
if emit_mir_expr(func, &ite.then_branch, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Else);
ctx.int_result_raw.set(result_raw);
if emit_mir_expr(func, &ite.else_branch, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::End);
Ok(Some(produces))
}
MirExpr::FnValue(name) => match ctx.fn_map.funcref_table.get(name) {
Some(&idx) => {
func.instruction(&Instruction::I32Const(idx as i32));
Ok(Some(true))
}
None => Ok(None),
},
MirExpr::Box(inner) => {
if emit_mir_int_raw(func, inner, slots, ctx)?.is_none() {
return Ok(None);
}
lift_i64_result_to_aint(func, ctx)?;
Ok(Some(true))
}
MirExpr::Unbox(inner) => {
if emit_mir_expr(func, inner, slots, ctx)?.is_none() {
return Ok(None);
}
let to_checked = ctx
.fn_map
.builtins
.get("__aint_to_i64_checked")
.copied()
.ok_or(WasmGcError::Validation(
"bignum active but __aint_to_i64_checked helper not registered (Unbox)".into(),
))?;
func.instruction(&Instruction::Call(to_checked));
Ok(Some(true))
}
}
}
pub(crate) fn mir_renders_raw_i64(e: &MirExpr, ctx: &EmitCtx<'_>) -> bool {
match e {
MirExpr::Local(local) => ctx.slot_is_bare(local.node.slot.0),
MirExpr::Unbox(_) => true,
MirExpr::Project(_) => mir_is_bare_carrier_project(e, ctx),
MirExpr::Box(_) => false,
MirExpr::Literal(_) => false,
MirExpr::Neg(inner) => {
mir_arith_leaves_raw_compatible(&inner.node, ctx)
&& mir_has_raw_anchor(&inner.node, ctx)
}
MirExpr::BinOp(b) => {
matches!(b.node.op, BinOp::Add | BinOp::Sub | BinOp::Mul)
&& mir_arith_leaves_raw_compatible(e, ctx)
&& mir_has_raw_anchor(e, ctx)
}
_ => false,
}
}
fn mir_arith_leaves_raw_compatible(e: &MirExpr, ctx: &EmitCtx<'_>) -> bool {
match e {
MirExpr::Local(local) => ctx.slot_is_bare(local.node.slot.0),
MirExpr::Unbox(_) => true,
MirExpr::Project(_) => mir_is_bare_carrier_project(e, ctx),
MirExpr::Literal(l) => matches!(l.node, Literal::Int(_)),
MirExpr::Neg(inner) => mir_arith_leaves_raw_compatible(&inner.node, ctx),
MirExpr::BinOp(b) => {
matches!(b.node.op, BinOp::Add | BinOp::Sub | BinOp::Mul)
&& mir_arith_leaves_raw_compatible(&b.node.lhs.node, ctx)
&& mir_arith_leaves_raw_compatible(&b.node.rhs.node, ctx)
}
_ => false,
}
}
fn mir_is_bare_carrier_project(e: &MirExpr, ctx: &EmitCtx<'_>) -> bool {
let MirExpr::Project(p) = e else {
return false;
};
mir_base_renders_carrier_i64_raw(&p.node.base, ctx)
|| mir_is_field_carrier_read(&p.node.base, &p.node.field, ctx)
}
fn mir_is_field_carrier_read(base: &Spanned<MirExpr>, field: &str, ctx: &EmitCtx<'_>) -> bool {
let record = aver_type_str_of(base);
ctx.registry.is_eligible_carrier_field(&record, field)
}
fn mir_base_renders_carrier_i64_raw(base: &Spanned<MirExpr>, ctx: &EmitCtx<'_>) -> bool {
match &base.node {
MirExpr::Local(local) => ctx.slot_is_bare_carrier(local.node.slot.0),
MirExpr::Project(_) => ctx.registry.is_eligible_carrier(&aver_type_str_of(base)),
_ => false,
}
}
fn mir_is_eligible_carrier_value_project(e: &MirExpr, ctx: &EmitCtx<'_>) -> bool {
let MirExpr::Project(p) = e else {
return false;
};
let base_renders_i64 = matches!(
&p.node.base.node,
MirExpr::Local(_) | MirExpr::Project(_) | MirExpr::Call(_) | MirExpr::TailCall(_)
);
let record = aver_type_str_of(&p.node.base);
base_renders_i64
&& ctx.registry.is_eligible_carrier(&record)
&& ctx.registry.newtype_underlying(&record).is_some()
}
fn emit_mir_carrier_value_raw(
func: &mut Function,
e: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<bool>, WasmGcError> {
let MirExpr::Project(p) = &e.node else {
return Ok(None);
};
emit_mir_expr(func, &p.node.base, slots, ctx)
}
fn mir_has_raw_anchor(e: &MirExpr, ctx: &EmitCtx<'_>) -> bool {
match e {
MirExpr::Local(local) => ctx.slot_is_bare(local.node.slot.0),
MirExpr::Unbox(_) => true,
MirExpr::Project(_) => mir_is_bare_carrier_project(e, ctx),
MirExpr::Neg(inner) => mir_has_raw_anchor(&inner.node, ctx),
MirExpr::BinOp(b) => {
mir_has_raw_anchor(&b.node.lhs.node, ctx) || mir_has_raw_anchor(&b.node.rhs.node, ctx)
}
_ => false,
}
}
pub(crate) fn mir_int_binop_is_raw(bop: &crate::ir::mir::MirBinOp, ctx: &EmitCtx<'_>) -> bool {
if matches!(bop.op, BinOp::Div) {
return false;
}
let leaves_ok = mir_arith_leaves_raw_compatible(&bop.lhs.node, ctx)
&& mir_arith_leaves_raw_compatible(&bop.rhs.node, ctx);
let anchored = mir_has_raw_anchor(&bop.lhs.node, ctx) || mir_has_raw_anchor(&bop.rhs.node, ctx);
leaves_ok && anchored
}
pub(crate) fn emit_mir_int_raw(
func: &mut Function,
e: &Spanned<MirExpr>,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<bool>, WasmGcError> {
match &e.node {
MirExpr::Literal(l) => match &l.node {
Literal::Int(n) => {
func.instruction(&Instruction::I64Const(*n));
Ok(Some(true))
}
_ => Ok(None),
},
MirExpr::Local(local) => {
if !ctx.slot_is_bare(local.node.slot.0) {
return Ok(None);
}
func.instruction(&Instruction::LocalGet(local.node.slot.0));
Ok(Some(true))
}
MirExpr::Neg(inner) => {
func.instruction(&Instruction::I64Const(0));
if emit_mir_int_raw(func, inner, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::I64Sub);
Ok(Some(true))
}
MirExpr::BinOp(b) => {
let op = b.node.op;
let inst = match op {
BinOp::Add => Instruction::I64Add,
BinOp::Sub => Instruction::I64Sub,
BinOp::Mul => Instruction::I64Mul,
_ => return Ok(None),
};
if emit_mir_int_raw(func, &b.node.lhs, slots, ctx)?.is_none() {
return Ok(None);
}
if emit_mir_int_raw(func, &b.node.rhs, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&inst);
Ok(Some(true))
}
MirExpr::Unbox(_) | MirExpr::Call(_) | MirExpr::TailCall(_) => {
emit_mir_expr(func, e, slots, ctx)
}
MirExpr::Project(_) => {
if mir_is_bare_carrier_project(&e.node, ctx) {
emit_mir_expr(func, e, slots, ctx)
} else {
Ok(None)
}
}
MirExpr::Match(_) | MirExpr::IfThenElse(_) | MirExpr::Let(_) | MirExpr::Return(_) => {
ctx.int_result_raw.set(true);
emit_mir_expr(func, e, slots, ctx)
}
_ => Ok(None),
}
}
pub(crate) fn emit_mir_args_then_call(
func: &mut Function,
args: &[Spanned<MirExpr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
wasm_idx: u32,
) -> Result<Option<()>, WasmGcError> {
emit_mir_args_then_call_lowering_int(func, args, slots, ctx, wasm_idx, &[])
}
fn emit_mir_fn_args_then_call(
func: &mut Function,
target: crate::ir::FnId,
args: &[Spanned<MirExpr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
wasm_idx: u32,
) -> Result<Option<()>, WasmGcError> {
for (i, arg) in args.iter().enumerate() {
if ctx.callee_param_is_bare(target, i) {
if emit_mir_int_raw(func, arg, slots, ctx)?.is_none() {
return Ok(None);
}
} else if emit_mir_expr(func, arg, slots, ctx)?.is_none() {
return Ok(None);
}
}
func.instruction(&Instruction::Call(wasm_idx));
Ok(Some(()))
}
pub(crate) fn emit_mir_args_then_call_lowering_int(
func: &mut Function,
args: &[Spanned<MirExpr>],
slots: &SlotTable,
ctx: &EmitCtx<'_>,
wasm_idx: u32,
int_arg_positions: &[usize],
) -> Result<Option<()>, WasmGcError> {
for (i, arg) in args.iter().enumerate() {
if emit_mir_expr(func, arg, slots, ctx)?.is_none() {
return Ok(None);
}
if ctx.registry.bignum && int_arg_positions.contains(&i) {
let to_sat = ctx
.fn_map
.builtins
.get("__aint_to_i64_sat")
.copied()
.ok_or(WasmGcError::Validation(
"bignum active but __aint_to_i64_sat helper not registered".into(),
))?;
func.instruction(&Instruction::Call(to_sat));
}
}
func.instruction(&Instruction::Call(wasm_idx));
Ok(Some(()))
}