use super::*;
fn block_ty_is_raw_i64(block_ty: wasm_encoder::BlockType) -> bool {
matches!(block_ty, wasm_encoder::BlockType::Result(ValType::I64))
}
pub(crate) fn emit_mir_match(
func: &mut Function,
m: &MirMatch,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<bool>, WasmGcError> {
if m.arms.is_empty() {
return Err(WasmGcError::Validation("match has no arms".into()));
}
let result_raw = ctx.int_result_raw.replace(false);
let block_ty = if result_raw {
wasm_encoder::BlockType::Result(ValType::I64)
} else {
let result_ty_str = aver_type_canonical(&m.arms[0].body, ctx.return_type, ctx.registry);
match aver_to_wasm(&result_ty_str, 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 m.arms
.iter()
.any(|a| matches!(a.pattern, MirPattern::Tuple(_)))
{
if m.arms.len() == 1
&& let MirPattern::Tuple(items) = &m.arms[0].pattern
&& items.len() >= 2
&& items
.iter()
.all(|p| matches!(p, MirPattern::Bind(..) | MirPattern::Wildcard))
{
return emit_mir_tuple_match(func, &m.subject, &m.arms[0], result_raw, slots, ctx);
}
return Ok(
emit_mir_tuple_constructor_match(func, m, block_ty, slots, ctx)?.map(|()| produces),
);
}
if m.arms
.iter()
.any(|a| matches!(a.pattern, MirPattern::EmptyList | MirPattern::Cons { .. }))
{
return Ok(emit_mir_list_match(func, m, block_ty, slots, ctx)?.map(|()| produces));
}
if m.arms.iter().any(arm_is_mir_result_ctor) {
return Ok(emit_mir_result_match(func, m, block_ty, slots, ctx)?.map(|()| produces));
}
if m.arms.iter().any(arm_is_mir_option_ctor) {
if subject_is_map_get(&m.subject, ctx) {
return Ok(
emit_mir_map_get_match_fused(func, m, block_ty, slots, ctx)?.map(|()| produces)
);
}
return Ok(emit_mir_option_match(func, m, block_ty, slots, ctx)?.map(|()| produces));
}
if m.arms.iter().any(|a| {
matches!(
a.pattern,
MirPattern::Ctor {
ctor: MirCtor::User(_),
..
}
)
}) {
if m.arms.len() == 1 {
return Ok(emit_mir_single_variant_match(
func, &m.subject, &m.arms[0], result_raw, slots, ctx,
)?
.map(|()| produces));
}
return Ok(emit_mir_variant_dispatch(func, m, block_ty, slots, ctx)?.map(|()| produces));
}
match aver_type_str_of(&m.subject).trim() {
"Bool" => {
if m.arms.len() != 2 {
return Ok(None);
}
let mut true_body: Option<&Spanned<MirExpr>> = None;
let mut false_body: Option<&Spanned<MirExpr>> = None;
for arm in &m.arms {
match &arm.pattern {
MirPattern::Literal(Literal::Bool(true)) => true_body = Some(&arm.body),
MirPattern::Literal(Literal::Bool(false)) => false_body = Some(&arm.body),
MirPattern::Wildcard => {
if true_body.is_none() {
true_body = Some(&arm.body);
} else {
false_body = Some(&arm.body);
}
}
_ => return Ok(None),
}
}
let (Some(t), Some(f)) = (true_body, false_body) else {
return Ok(None);
};
if emit_mir_expr(func, &m.subject, 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, t, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Else);
ctx.int_result_raw.set(result_raw);
if emit_mir_expr(func, f, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::End);
Ok(Some(produces))
}
"Int" => {
let mut wildcard_body: Option<&Spanned<MirExpr>> = None;
let mut typed_arms: Vec<(i64, &Spanned<MirExpr>)> = Vec::new();
for arm in &m.arms {
match &arm.pattern {
MirPattern::Literal(Literal::Int(n)) => typed_arms.push((*n, &arm.body)),
MirPattern::Wildcard => {
if wildcard_body.is_none() {
wildcard_body = Some(&arm.body);
}
}
_ => return Ok(None),
}
}
let Some(wildcard) = wildcard_body else {
return Ok(None);
};
if emit_mir_int_cascade(
func,
&m.subject,
&typed_arms,
wildcard,
block_ty,
result_raw,
slots,
ctx,
)?
.is_none()
{
return Ok(None);
}
Ok(Some(produces))
}
"String" => {
if emit_mir_string_match(func, m, block_ty, slots, ctx)?.is_none() {
return Ok(None);
}
Ok(Some(produces))
}
"Unit" => {
if matches!(
m.subject.node,
MirExpr::Local(_) | MirExpr::Project(_) | MirExpr::Literal(_)
) {
ctx.int_result_raw.set(result_raw);
if emit_mir_expr(func, &m.arms[0].body, slots, ctx)?.is_none() {
return Ok(None);
}
Ok(Some(produces))
} else {
Ok(None)
}
}
_ => Ok(None),
}
}
fn emit_mir_tuple_match(
func: &mut Function,
subject: &Spanned<MirExpr>,
arm: &MirMatchArm,
result_raw: bool,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<bool>, WasmGcError> {
let MirPattern::Tuple(items) = &arm.pattern else {
return Ok(None);
};
let subj_ty = aver_type_str_of(subject);
let canonical: String = subj_ty.chars().filter(|c| !c.is_whitespace()).collect();
let tuple_idx = ctx
.registry
.tuple_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Tuple match: subject type `{subj_ty}` is not a registered Tuple<A,B>"
)))?;
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"Tuple match needs a subject scratch slot but none was reserved".into(),
))?;
if emit_mir_expr(func, subject, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::LocalSet(scratch));
for (field_idx, pat) in items.iter().enumerate() {
if let MirPattern::Bind(slot, _) = pat
&& slot.0 != u32::from(u16::MAX)
{
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(tuple_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: tuple_idx,
field_index: field_idx as u32,
});
func.instruction(&Instruction::LocalSet(slot.0));
}
}
ctx.int_result_raw.set(result_raw);
let Some(produces) = emit_mir_expr(func, &arm.body, slots, ctx)? else {
return Ok(None);
};
Ok(Some(produces))
}
fn emit_mir_tuple_constructor_match(
func: &mut Function,
m: &MirMatch,
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let subject_ty = aver_type_str_of(&m.subject);
let canonical: String = subject_ty.chars().filter(|c| !c.is_whitespace()).collect();
let Some(tuple_idx) = ctx.registry.tuple_type_idx(&canonical) else {
return Ok(None);
};
let Some(elems) = TypeRegistry::tuple_elements(&canonical) else {
return Ok(None);
};
let elems: Vec<String> = elems.into_iter().map(|s| s.to_string()).collect();
let mut any_result_ctor = false;
for arm in &m.arms {
match &arm.pattern {
MirPattern::Wildcard | MirPattern::Bind(..) => {}
MirPattern::Tuple(items) => {
if items.len() != elems.len() {
return Ok(None);
}
for (i, pat) in items.iter().enumerate() {
match pat {
MirPattern::Bind(..) | MirPattern::Wildcard => {}
MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::ResultOk | BuiltinCtor::ResultErr),
..
} => {
any_result_ctor = true;
let elem_canonical: String =
elems[i].chars().filter(|c| !c.is_whitespace()).collect();
if ctx.registry.result_type_idx(&elem_canonical).is_none() {
return Ok(None);
}
}
_ => return Ok(None),
}
}
}
_ => return Ok(None),
}
}
if !any_result_ctor {
return Ok(None);
}
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"multi-arm tuple match needs a subject scratch slot but none was reserved".into(),
))?;
if emit_mir_expr(func, &m.subject, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::LocalSet(scratch));
emit_mir_tuple_constructor_arm_cascade(
func, scratch, tuple_idx, &elems, &m.arms, block_ty, slots, ctx,
)
}
#[allow(clippy::too_many_arguments)]
fn emit_mir_tuple_constructor_arm_cascade(
func: &mut Function,
scratch: u32,
tuple_idx: u32,
elems: &[String],
arms: &[MirMatchArm],
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let Some(arm) = arms.first() else {
func.instruction(&Instruction::Unreachable);
return Ok(Some(()));
};
match &arm.pattern {
MirPattern::Wildcard => {
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
}
MirPattern::Bind(slot, _) => {
if slot.0 != u32::from(u16::MAX) {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::LocalSet(slot.0));
}
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
}
MirPattern::Tuple(items) => {
let mut tests_emitted = 0u32;
for (i, pat) in items.iter().enumerate() {
if let MirPattern::Ctor {
ctor: MirCtor::Builtin(bc),
..
} = pat
&& matches!(bc, BuiltinCtor::ResultOk | BuiltinCtor::ResultErr)
{
let elem_canonical: String =
elems[i].chars().filter(|c| !c.is_whitespace()).collect();
let res_idx = ctx.registry.result_type_idx(&elem_canonical).ok_or(
WasmGcError::Validation(
"tuple-of-constructors match: element is not a registered Result"
.into(),
),
)?;
let expected_tag: i32 = if matches!(bc, BuiltinCtor::ResultOk) {
1
} else {
0
};
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(tuple_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: tuple_idx,
field_index: i as u32,
});
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(expected_tag));
func.instruction(&Instruction::I32Eq);
if tests_emitted > 0 {
func.instruction(&Instruction::I32And);
}
tests_emitted += 1;
}
}
if tests_emitted == 0 {
func.instruction(&Instruction::I32Const(1));
}
func.instruction(&Instruction::If(block_ty));
for (i, pat) in items.iter().enumerate() {
match pat {
MirPattern::Bind(slot, _) if slot.0 != u32::from(u16::MAX) => {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(tuple_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: tuple_idx,
field_index: i as u32,
});
func.instruction(&Instruction::LocalSet(slot.0));
}
MirPattern::Ctor {
ctor: MirCtor::Builtin(bc),
bindings,
..
} if matches!(bc, BuiltinCtor::ResultOk | BuiltinCtor::ResultErr) => {
let elem_canonical: String =
elems[i].chars().filter(|c| !c.is_whitespace()).collect();
let res_idx =
ctx.registry
.result_type_idx(&elem_canonical)
.ok_or(WasmGcError::Validation(
"tuple-of-constructors match: element is not a registered Result"
.into(),
))?;
let payload_field: u32 = if matches!(bc, BuiltinCtor::ResultOk) {
1
} else {
2
};
for binding in bindings {
if binding.0 == u32::from(u16::MAX) {
continue;
}
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(tuple_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: tuple_idx,
field_index: i as u32,
});
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(res_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: res_idx,
field_index: payload_field,
});
func.instruction(&Instruction::LocalSet(binding.0));
}
}
_ => {}
}
}
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Else);
if emit_mir_tuple_constructor_arm_cascade(
func,
scratch,
tuple_idx,
elems,
&arms[1..],
block_ty,
slots,
ctx,
)?
.is_none()
{
return Ok(None);
}
func.instruction(&Instruction::End);
}
_ => return Ok(None),
}
Ok(Some(()))
}
fn emit_mir_map_get_match_fused(
func: &mut Function,
m: &MirMatch,
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let MirExpr::Call(call) = &m.subject.node else {
return Ok(None);
};
let call = &call.node;
if call.args.len() != 2 {
return Ok(None);
}
let map = &call.args[0];
let key = &call.args[1];
let map_aver = aver_type_str_of(map);
let canonical: String = map_aver.chars().filter(|c| !c.is_whitespace()).collect();
let Some(helpers) = ctx.fn_map.map_helpers_lookup(&canonical) else {
return Ok(None);
};
let get_pair = helpers.get_pair;
let mut some_arm: Option<&MirMatchArm> = None;
let mut none_arm: Option<&MirMatchArm> = None;
for arm in &m.arms {
match &arm.pattern {
MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::OptionSome),
..
} => some_arm = Some(arm),
MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::OptionNone),
..
} => none_arm = Some(arm),
MirPattern::Wildcard => {
if none_arm.is_none() {
none_arm = Some(arm);
} else if some_arm.is_none() {
some_arm = Some(arm);
}
}
_ => {}
}
}
let (Some(some_arm), Some(none_arm)) = (some_arm, none_arm) else {
return Ok(None);
};
if emit_mir_expr(func, map, slots, ctx)?.is_none() {
return Ok(None);
}
if emit_mir_expr(func, key, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Call(get_pair));
match ctor_arm_binding_slot(some_arm) {
Some(slot) => {
func.instruction(&Instruction::LocalSet(slot));
}
None => {
func.instruction(&Instruction::Drop);
}
}
func.instruction(&Instruction::If(block_ty));
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &some_arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Else);
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &none_arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::End);
Ok(Some(()))
}
pub(crate) fn emit_mir_string_match(
func: &mut Function,
m: &MirMatch,
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"String match needs a subject scratch slot but none was reserved".into(),
))?;
let eq_idx = ctx
.fn_map
.builtins
.get("__wasmgc_string_eq")
.copied()
.ok_or(WasmGcError::Validation(
"String match: __wasmgc_string_eq builtin wasn't registered".into(),
))?;
let s_idx = ctx
.registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"String match needs the String type slot allocated".into(),
))?;
if emit_mir_expr(func, &m.subject, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::LocalSet(scratch));
let mut literal_arms: Vec<(&str, &Spanned<MirExpr>)> = Vec::new();
let mut default_body: Option<&Spanned<MirExpr>> = None;
for arm in &m.arms {
if let MirPattern::Literal(Literal::Str(s)) = &arm.pattern {
literal_arms.push((s.as_str(), &arm.body));
} else if default_body.is_none() {
default_body = Some(&arm.body);
}
}
let Some(default_body) = default_body else {
return Ok(None);
};
let mut ends_to_close = 0usize;
for (lit, body) in &literal_arms {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNullable(
wasm_encoder::HeapType::Concrete(s_idx),
));
emit_string_literal_bytes(func, lit.as_bytes(), ctx)?;
func.instruction(&Instruction::Call(eq_idx));
func.instruction(&Instruction::If(block_ty));
if emit_mir_expr(func, body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Else);
ends_to_close += 1;
}
if emit_mir_expr(func, default_body, slots, ctx)?.is_none() {
return Ok(None);
}
for _ in 0..ends_to_close {
func.instruction(&Instruction::End);
}
Ok(Some(()))
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn emit_mir_int_cascade(
func: &mut Function,
subject: &Spanned<MirExpr>,
typed_arms: &[(i64, &Spanned<MirExpr>)],
wildcard: &Spanned<MirExpr>,
block_ty: wasm_encoder::BlockType,
result_raw: bool,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let Some(((pat_lit, body), rest)) = typed_arms.split_first() else {
ctx.int_result_raw.set(result_raw);
if emit_mir_expr(func, wildcard, slots, ctx)?.is_none() {
return Ok(None);
}
return Ok(Some(()));
};
let subject_raw = super::mir_renders_raw_i64(&subject.node, ctx);
ctx.int_result_raw.set(false);
if emit_mir_expr(func, subject, slots, ctx)?.is_none() {
return Ok(None);
}
if ctx.registry.bignum && !subject_raw {
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(),
))?;
let eq = ctx
.fn_map
.builtins
.get("__aint_eq")
.copied()
.ok_or(WasmGcError::Validation(
"bignum active but __aint_eq helper not registered".into(),
))?;
func.instruction(&Instruction::I64Const(*pat_lit));
func.instruction(&Instruction::Call(from_i64));
func.instruction(&Instruction::Call(eq));
} else {
func.instruction(&Instruction::I64Const(*pat_lit));
func.instruction(&Instruction::I64Eq);
}
func.instruction(&Instruction::If(block_ty));
ctx.int_result_raw.set(result_raw);
if emit_mir_expr(func, body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Else);
if emit_mir_int_cascade(
func, subject, rest, wildcard, block_ty, result_raw, slots, ctx,
)?
.is_none()
{
return Ok(None);
}
func.instruction(&Instruction::End);
Ok(Some(()))
}
pub(crate) fn arm_is_mir_result_ctor(arm: &MirMatchArm) -> bool {
matches!(
&arm.pattern,
MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::ResultOk | BuiltinCtor::ResultErr),
..
}
)
}
pub(crate) fn arm_is_mir_option_ctor(arm: &MirMatchArm) -> bool {
matches!(
&arm.pattern,
MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::OptionSome | BuiltinCtor::OptionNone),
..
}
)
}
pub(crate) fn subject_is_map_get(subject: &Spanned<MirExpr>, ctx: &EmitCtx<'_>) -> bool {
if let MirExpr::Call(call) = &subject.node
&& let MirCallee::Builtin(id) = call.node.callee
&& let Some(name) = ctx.mir_builtins.and_then(|names| names.get(id.0 as usize))
{
return name == "Map.get" && call.node.args.len() == 2;
}
false
}
pub(crate) fn ctor_arm_binding_slot(arm: &MirMatchArm) -> Option<u32> {
if let MirPattern::Ctor { bindings, .. } = &arm.pattern
&& let Some(slot) = bindings.first()
&& slot.0 != u32::from(u16::MAX)
{
return Some(slot.0);
}
None
}
pub(crate) fn emit_mir_option_match(
func: &mut Function,
m: &MirMatch,
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"Option match needs a subject scratch slot but none was reserved".into(),
))?;
let subject_ty = aver_type_str_of(&m.subject);
let canonical: String = subject_ty.chars().filter(|c| !c.is_whitespace()).collect();
let opt_idx = ctx
.registry
.option_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Option match: subject type `{subject_ty}` is not a registered Option<T>"
)))?;
let mut some_arm: Option<&MirMatchArm> = None;
let mut none_arm: Option<&MirMatchArm> = None;
for arm in &m.arms {
match &arm.pattern {
MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::OptionSome),
..
} => some_arm = Some(arm),
MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::OptionNone),
..
} => none_arm = Some(arm),
MirPattern::Wildcard => {
if none_arm.is_none() {
none_arm = Some(arm);
} else if some_arm.is_none() {
some_arm = Some(arm);
}
}
_ => {}
}
}
let (Some(some_arm), Some(none_arm)) = (some_arm, none_arm) else {
return Ok(None);
};
if emit_mir_expr(func, &m.subject, slots, ctx)?.is_none() {
return Ok(None);
}
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));
if let Some(slot) = ctor_arm_binding_slot(some_arm) {
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::LocalSet(slot));
}
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &some_arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Else);
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &none_arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::End);
Ok(Some(()))
}
pub(crate) fn emit_mir_result_match(
func: &mut Function,
m: &MirMatch,
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"Result match needs a subject scratch slot but none was reserved".into(),
))?;
let subject_ty = aver_type_str_of(&m.subject);
let canonical: String = subject_ty.chars().filter(|c| !c.is_whitespace()).collect();
let res_idx = ctx
.registry
.result_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"Result match: subject type `{subject_ty}` is not a registered Result<T,E>"
)))?;
let mut ok_arm: Option<&MirMatchArm> = None;
let mut err_arm: Option<&MirMatchArm> = None;
for arm in &m.arms {
match &arm.pattern {
MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::ResultOk),
..
} => ok_arm = Some(arm),
MirPattern::Ctor {
ctor: MirCtor::Builtin(BuiltinCtor::ResultErr),
..
} => err_arm = Some(arm),
MirPattern::Wildcard => {
if err_arm.is_none() {
err_arm = Some(arm);
} else if ok_arm.is_none() {
ok_arm = Some(arm);
}
}
_ => {}
}
}
let (Some(ok_arm), Some(err_arm)) = (ok_arm, err_arm) else {
return Ok(None);
};
if emit_mir_expr(func, &m.subject, slots, ctx)?.is_none() {
return Ok(None);
}
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));
if let Some(slot) = ctor_arm_binding_slot(ok_arm) {
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::LocalSet(slot));
}
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &ok_arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Else);
if let Some(slot) = ctor_arm_binding_slot(err_arm) {
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: 2,
});
func.instruction(&Instruction::LocalSet(slot));
}
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &err_arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::End);
Ok(Some(()))
}
pub(crate) fn emit_mir_list_match(
func: &mut Function,
m: &MirMatch,
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"List match needs a subject scratch slot but none was reserved".into(),
))?;
let subject_ty = aver_type_str_of(&m.subject);
let canonical: String = subject_ty.chars().filter(|c| !c.is_whitespace()).collect();
let list_idx = ctx
.registry
.list_type_idx(&canonical)
.ok_or(WasmGcError::Validation(format!(
"List match: subject type `{subject_ty}` is not a registered List<T>"
)))?;
let mut empty_arm: Option<&MirMatchArm> = None;
let mut cons_arm: Option<&MirMatchArm> = None;
for arm in &m.arms {
match &arm.pattern {
MirPattern::EmptyList => empty_arm = Some(arm),
MirPattern::Cons { .. } => cons_arm = Some(arm),
MirPattern::Wildcard => {
if empty_arm.is_none() {
empty_arm = Some(arm);
} else if cons_arm.is_none() {
cons_arm = Some(arm);
}
}
_ => {}
}
}
let (Some(empty_arm), Some(cons_arm)) = (empty_arm, cons_arm) else {
return Ok(None);
};
if emit_mir_expr(func, &m.subject, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::LocalSet(scratch));
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefIsNull);
func.instruction(&Instruction::If(block_ty));
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &empty_arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Else);
if let MirPattern::Cons { head, tail, .. } = &cons_arm.pattern {
if head.0 != u32::from(u16::MAX) {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(list_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 0,
});
func.instruction(&Instruction::LocalSet(head.0));
}
if tail.0 != u32::from(u16::MAX) {
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(list_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: list_idx,
field_index: 1,
});
func.instruction(&Instruction::LocalSet(tail.0));
}
}
ctx.int_result_raw.set(block_ty_is_raw_i64(block_ty));
if emit_mir_expr(func, &cons_arm.body, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::End);
Ok(Some(()))
}
pub(crate) fn mir_user_variant_info<'a>(
ctor_id: CtorId,
ctx: &'a EmitCtx<'_>,
) -> Result<&'a VariantInfo, WasmGcError> {
let ctor_entry = ctx.symbol_table.ctor_entry(ctor_id);
let bare = ctor_entry.name.as_str();
let parent = ctx
.symbol_table
.type_entry(ctor_entry.owning_type)
.key
.name
.clone();
ctx.registry
.variant_in(&parent, bare)
.or_else(|| ctx.registry.variant(bare))
.ok_or(WasmGcError::Validation(format!(
"unknown variant `{parent}.{bare}` in match"
)))
}
pub(crate) fn emit_mir_arm_body_value(
func: &mut Function,
body: &Spanned<MirExpr>,
result_raw: bool,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
ctx.int_result_raw.set(result_raw);
Ok(emit_mir_expr(func, body, slots, ctx)?.map(|_| ()))
}
pub(crate) fn emit_mir_single_variant_match(
func: &mut Function,
subject: &Spanned<MirExpr>,
arm: &MirMatchArm,
result_raw: bool,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let MirPattern::Ctor {
ctor: MirCtor::User(ctor_id),
bindings,
..
} = &arm.pattern
else {
return Ok(None);
};
let info = mir_user_variant_info(*ctor_id, ctx)?;
const NO_SLOT: u32 = u16::MAX as u32;
if ctx.registry.newtype_underlying(&info.parent).is_some() && bindings.len() == 1 {
let slot = bindings[0].0;
if emit_mir_expr(func, subject, slots, ctx)?.is_none() {
return Ok(None);
}
if slot != NO_SLOT && ctx.registry.is_eligible_carrier(&info.parent) {
super::emit_carrier_project_bridge(func, ctx)?;
}
if slot != NO_SLOT {
func.instruction(&Instruction::LocalSet(slot));
} else {
func.instruction(&Instruction::Drop);
}
return emit_mir_arm_body_value(func, &arm.body, result_raw, slots, ctx);
}
let variant_idx = info.type_idx;
let cast_ty = wasm_encoder::HeapType::Concrete(variant_idx);
if bindings.is_empty() {
if emit_mir_expr(func, subject, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Drop);
return emit_mir_arm_body_value(func, &arm.body, result_raw, slots, ctx);
}
if emit_mir_expr(func, subject, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::RefCastNonNull(cast_ty));
if bindings.len() == 1 {
let slot = bindings[0].0;
func.instruction(&Instruction::StructGet {
struct_type_index: variant_idx,
field_index: 0,
});
if slot != NO_SLOT {
func.instruction(&Instruction::LocalSet(slot));
} else {
func.instruction(&Instruction::Drop);
}
return emit_mir_arm_body_value(func, &arm.body, result_raw, slots, ctx);
}
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"multi-binding variant pattern needs subject_scratch but none was reserved".into(),
))?;
func.instruction(&Instruction::LocalSet(scratch));
for (i, slot) in bindings.iter().enumerate() {
if slot.0 == NO_SLOT {
continue;
}
func.instruction(&Instruction::LocalGet(scratch));
func.instruction(&Instruction::RefCastNonNull(cast_ty));
func.instruction(&Instruction::StructGet {
struct_type_index: variant_idx,
field_index: i as u32,
});
func.instruction(&Instruction::LocalSet(slot.0));
}
emit_mir_arm_body_value(func, &arm.body, result_raw, slots, ctx)
}
pub(crate) fn emit_mir_variant_dispatch(
func: &mut Function,
m: &MirMatch,
block_ty: wasm_encoder::BlockType,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
let scratch = slots.subject_scratch.ok_or(WasmGcError::Validation(
"multi-arm variant match needs a subject scratch slot but none was reserved".into(),
))?;
if emit_mir_expr(func, &m.subject, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::LocalSet(scratch));
let result_raw = block_ty_is_raw_i64(block_ty);
emit_mir_variant_arm_cascade(func, &m.arms, block_ty, scratch, result_raw, slots, ctx)
}
pub(crate) fn emit_mir_variant_arm_cascade(
func: &mut Function,
arms: &[MirMatchArm],
block_ty: wasm_encoder::BlockType,
subject_scratch: u32,
result_raw: bool,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
if arms.is_empty() {
func.instruction(&Instruction::Unreachable);
return Ok(Some(()));
}
if arms.len() == 1 {
return emit_mir_arm_body(func, &arms[0], subject_scratch, result_raw, slots, ctx);
}
let arm = &arms[0];
match &arm.pattern {
MirPattern::Ctor {
ctor: MirCtor::User(ctor_id),
..
} => {
let info = mir_user_variant_info(*ctor_id, ctx)?;
func.instruction(&Instruction::LocalGet(subject_scratch));
func.instruction(&Instruction::RefTestNonNull(
wasm_encoder::HeapType::Concrete(info.type_idx),
));
func.instruction(&Instruction::If(block_ty));
if emit_mir_arm_body(func, arm, subject_scratch, result_raw, slots, ctx)?.is_none() {
return Ok(None);
}
func.instruction(&Instruction::Else);
if emit_mir_variant_arm_cascade(
func,
&arms[1..],
block_ty,
subject_scratch,
result_raw,
slots,
ctx,
)?
.is_none()
{
return Ok(None);
}
func.instruction(&Instruction::End);
Ok(Some(()))
}
MirPattern::Wildcard => {
emit_mir_arm_body(func, arm, subject_scratch, result_raw, slots, ctx)
}
_ => Ok(None),
}
}
pub(crate) fn emit_mir_arm_body(
func: &mut Function,
arm: &MirMatchArm,
subject_scratch: u32,
result_raw: bool,
slots: &SlotTable,
ctx: &EmitCtx<'_>,
) -> Result<Option<()>, WasmGcError> {
if let MirPattern::Ctor {
ctor: MirCtor::User(ctor_id),
bindings,
..
} = &arm.pattern
{
let info = mir_user_variant_info(*ctor_id, ctx)?;
const NO_SLOT: u32 = u16::MAX as u32;
if ctx.registry.newtype_underlying(&info.parent).is_some() && bindings.len() == 1 {
let slot = bindings[0].0;
if slot != NO_SLOT {
func.instruction(&Instruction::LocalGet(subject_scratch));
if ctx.registry.is_eligible_carrier(&info.parent) {
super::emit_carrier_project_bridge(func, ctx)?;
}
func.instruction(&Instruction::LocalSet(slot));
}
return emit_mir_arm_body_value(func, &arm.body, result_raw, slots, ctx);
}
for (i, slot) in bindings.iter().enumerate() {
if slot.0 == NO_SLOT {
continue;
}
func.instruction(&Instruction::LocalGet(subject_scratch));
func.instruction(&Instruction::RefCastNonNull(
wasm_encoder::HeapType::Concrete(info.type_idx),
));
func.instruction(&Instruction::StructGet {
struct_type_index: info.type_idx,
field_index: i as u32,
});
func.instruction(&Instruction::LocalSet(slot.0));
}
return emit_mir_arm_body_value(func, &arm.body, result_raw, slots, ctx);
}
emit_mir_arm_body_value(func, &arm.body, result_raw, slots, ctx)
}