use crate::ast::Literal;
use crate::ast::Spanned;
use crate::ir::hir::BuiltinCtor;
use crate::ir::mir::{
LocalId, MirCall, MirCallee, MirCtor, MirExpr, MirFn, MirLet, MirPattern, MirProgram,
MirStrPart,
};
use crate::nan_value::NanValue;
use crate::vm::builtin::VmBuiltin;
use crate::vm::opcode::*;
use super::VmSymbolTable;
use super::resolve_helpers::buffer_intrinsic_opcode;
use super::{CompileError, FnCompiler};
#[derive(Debug)]
pub enum MirVmUnsupported {
UnsupportedExpr(&'static str),
UnsupportedCallee,
InnerError(CompileError),
}
impl From<CompileError> for MirVmUnsupported {
fn from(e: CompileError) -> Self {
MirVmUnsupported::InnerError(e)
}
}
impl MirVmUnsupported {
pub(super) fn into_compile_error(self, context: &str) -> CompileError {
match self {
MirVmUnsupported::InnerError(e) => e,
MirVmUnsupported::UnsupportedExpr(what) => CompileError {
msg: format!("internal error: VM backend cannot lower `{what}` in {context}"),
},
MirVmUnsupported::UnsupportedCallee => CompileError {
msg: format!(
"internal error: VM backend hit an unsupported callee shape in {context}"
),
},
}
}
}
pub(super) fn compile_mir_expr(
fc: &mut FnCompiler<'_>,
expr: &Spanned<MirExpr>,
) -> Result<(), MirVmUnsupported> {
fc.note_line(expr.line);
match &expr.node {
MirExpr::Literal(lit) => {
fc.compile_literal(&lit.node)?;
Ok(())
}
MirExpr::Local(spanned_local) => {
let local = &spanned_local.node;
fc.emit_op(if local.last_use {
MOVE_LOCAL
} else {
LOAD_LOCAL
});
fc.emit_u8(local.slot.0 as u8);
Ok(())
}
MirExpr::BinOp(spanned_binop) => {
let bop = &spanned_binop.node;
compile_mir_expr(fc, &bop.lhs)?;
compile_mir_expr(fc, &bop.rhs)?;
emit_binop_typed(fc, bop.op, bop.lhs.ty(), bop.rhs.ty());
Ok(())
}
MirExpr::Neg(inner) => {
compile_mir_expr(fc, inner)?;
let op = match inner.ty() {
Some(crate::ast::Type::Int) => NEG_INT,
Some(crate::ast::Type::Float) => NEG_FLOAT,
_ => NEG,
};
fc.emit_op(op);
Ok(())
}
MirExpr::Let(spanned_let) => {
let MirLet {
binding,
binding_name: _,
value,
body,
} = &spanned_let.node;
compile_mir_expr(fc, value)?;
fc.emit_op(STORE_LOCAL);
fc.emit_u8(binding.0 as u8);
compile_mir_expr(fc, body)
}
MirExpr::Call(spanned_call) => {
let MirCall { callee, args } = &spanned_call.node;
match callee {
MirCallee::Fn(fn_id) => {
for arg in args {
compile_mir_expr(fc, arg)?;
}
let name = fc.canonical_fn_name(*fn_id)?;
let vm_fn_id = fc.resolve_fn_id_by_name(&name).ok_or_else(|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: unresolved fn `{name}` (FnId={fn_id:?}) — \
module not loaded?"
),
})
})?;
fc.emit_op(CALL_KNOWN);
fc.emit_u16(vm_fn_id as u16);
fc.emit_u8(args.len() as u8);
Ok(())
}
MirCallee::Builtin(id) => {
let name = fc.mir_program.map(|p| p.builtin_name(*id)).unwrap_or("");
let builtin =
lookup_vm_builtin(name).ok_or(MirVmUnsupported::UnsupportedCallee)?;
if builtin == VmBuiltin::OptionWithDefault
&& try_emit_vector_compound(fc, args)?
{
return Ok(());
}
for arg in args {
compile_mir_expr(fc, arg)?;
}
let owned_mask = compute_builtin_owned_mask(args, fc);
match builtin {
VmBuiltin::ListLen => fc.emit_op(LIST_LEN),
VmBuiltin::ListPrepend => fc.emit_op(LIST_PREPEND),
VmBuiltin::VectorGet => fc.emit_op(VECTOR_GET),
VmBuiltin::VectorSet if owned_mask != 0 => {
let symbol_id = fc.symbols.intern_builtin(builtin).map_err(|e| {
MirVmUnsupported::InnerError(CompileError {
msg: format!("MIR-VM: intern_builtin failed: {e:?}"),
})
})?;
fc.emit_op(CALL_BUILTIN_OWNED);
fc.emit_u32(symbol_id);
fc.emit_u8(args.len() as u8);
fc.emit_u8(owned_mask);
}
VmBuiltin::VectorSet => fc.emit_op(VECTOR_SET),
VmBuiltin::OptionWithDefault => fc.emit_op(UNWRAP_OR),
VmBuiltin::ResultWithDefault => fc.emit_op(UNWRAP_RESULT_OR),
_ => {
let symbol_id = fc.symbols.intern_builtin(builtin).map_err(|e| {
MirVmUnsupported::InnerError(CompileError {
msg: format!("MIR-VM: intern_builtin failed: {e:?}"),
})
})?;
if owned_mask != 0 {
fc.emit_op(CALL_BUILTIN_OWNED);
fc.emit_u32(symbol_id);
fc.emit_u8(args.len() as u8);
fc.emit_u8(owned_mask);
} else {
fc.emit_op(CALL_BUILTIN);
fc.emit_u32(symbol_id);
fc.emit_u8(args.len() as u8);
}
}
}
Ok(())
}
MirCallee::Intrinsic(intrinsic) => {
match buffer_intrinsic_opcode(*intrinsic) {
Some((opcode, arity)) => {
if args.len() != arity {
return Err(MirVmUnsupported::InnerError(CompileError {
msg: format!(
"intrinsic {} expects {arity} arg(s), got {}",
intrinsic.name(),
args.len()
),
}));
}
for arg in args {
compile_mir_expr(fc, arg)?;
}
fc.emit_op(opcode);
}
None => {
if args.len() != 1 {
return Err(MirVmUnsupported::InnerError(CompileError {
msg: format!(
"intrinsic {} expects 1 arg, got {}",
intrinsic.name(),
args.len()
),
}));
}
compile_mir_expr(fc, &args[0])?;
let empty = fc.nan_literal(&Literal::Str(String::new()));
let idx = fc.add_constant(empty);
fc.emit_op(LOAD_CONST);
fc.emit_u16(idx);
fc.emit_op(CONCAT);
}
}
Ok(())
}
MirCallee::LocalSlot {
slot,
last_use,
name: _,
} => {
fc.emit_op(if *last_use { MOVE_LOCAL } else { LOAD_LOCAL });
fc.emit_u8(*slot as u8);
for arg in args {
compile_mir_expr(fc, arg)?;
}
fc.emit_op(CALL_VALUE);
fc.emit_u8(args.len() as u8);
Ok(())
}
}
}
MirExpr::Return(inner) => {
compile_mir_expr(fc, inner)?;
fc.emit_op(RETURN);
Ok(())
}
MirExpr::FnValue(name) => {
fc.compile_ident(name)?;
Ok(())
}
MirExpr::Construct(spanned_construct) => {
let c = &spanned_construct.node;
match c.ctor {
MirCtor::Builtin(BuiltinCtor::ResultOk) => {
emit_constructor_arg(fc, c.args.first())?;
fc.emit_op(WRAP);
fc.emit_u8(0);
Ok(())
}
MirCtor::Builtin(BuiltinCtor::ResultErr) => {
emit_constructor_arg(fc, c.args.first())?;
fc.emit_op(WRAP);
fc.emit_u8(1);
Ok(())
}
MirCtor::Builtin(BuiltinCtor::OptionSome) => {
emit_constructor_arg(fc, c.args.first())?;
fc.emit_op(WRAP);
fc.emit_u8(2);
Ok(())
}
MirCtor::Builtin(BuiltinCtor::OptionNone) => {
let idx = fc.add_constant(NanValue::NONE);
fc.emit_op(LOAD_CONST);
fc.emit_u16(idx);
Ok(())
}
MirCtor::User(ctor_id) => {
let entry = fc.symbol_table.ctor_entry(ctor_id);
let owning_type = entry.owning_type;
let variant_name = entry.name.clone();
let qualified_type_name = fc.canonical_type_name(owning_type)?;
let arena_type_id =
fc.resolve_type_id(&qualified_type_name).ok_or_else(|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: unknown arena type for `{qualified_type_name}` \
(CtorId={ctor_id:?})"
),
})
})?;
let variant_id =
fc.arena.find_variant_id(arena_type_id, &variant_name).ok_or_else(
|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: unknown variant `{variant_name}` on `{qualified_type_name}`"
),
})
},
)?;
for arg in &c.args {
compile_mir_expr(fc, arg)?;
}
fc.emit_op(VARIANT_NEW);
fc.emit_u16(arena_type_id as u16);
fc.emit_u16(variant_id);
fc.emit_u8(c.args.len() as u8);
Ok(())
}
}
}
MirExpr::Project(spanned_proj) => {
let p = &spanned_proj.node;
compile_mir_expr(fc, &p.base)?;
let field_symbol_id = fc.symbols.intern_name(&p.field);
fc.emit_op(RECORD_GET_NAMED);
fc.emit_u32(field_symbol_id);
Ok(())
}
MirExpr::Try(inner) => {
compile_mir_expr(fc, inner)?;
fc.emit_op(PROPAGATE_ERR);
Ok(())
}
MirExpr::Box(inner) | MirExpr::Unbox(inner) => compile_mir_expr(fc, inner),
MirExpr::TailCall(spanned_tail) => {
let tc = &spanned_tail.node;
let owned_mask = compute_owned_mask(&tc.args, fc);
for arg in &tc.args {
compile_mir_expr(fc, arg)?;
}
let target_name = fc.canonical_fn_name(tc.target)?;
if target_name == fc.name() {
fc.emit_op(TAIL_CALL_SELF);
fc.emit_u8(tc.args.len() as u8);
fc.emit_u8(owned_mask);
} else {
let vm_fn_id = fc.resolve_fn_id_by_name(&target_name).ok_or_else(|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: unresolved tail-call target `{target_name}` \
(FnId={:?})",
tc.target
),
})
})?;
fc.emit_op(TAIL_CALL_KNOWN);
fc.emit_u16(vm_fn_id as u16);
fc.emit_u8(tc.args.len() as u8);
fc.emit_u8(owned_mask);
}
Ok(())
}
MirExpr::List(items) => {
if items.is_empty() {
fc.emit_op(LIST_NIL);
return Ok(());
}
for item in items {
compile_mir_expr(fc, item)?;
}
fc.emit_op(LIST_NEW);
fc.emit_u8(items.len() as u8);
Ok(())
}
MirExpr::Tuple(items) => {
for item in items {
compile_mir_expr(fc, item)?;
}
fc.emit_op(TUPLE_NEW);
fc.emit_u8(items.len() as u8);
Ok(())
}
MirExpr::RecordCreate(spanned_rc) => {
let rc = &spanned_rc.node;
let qualified_type_name = match rc.type_id {
Some(id) => fc.canonical_type_name(id)?,
None => rc.type_name.clone(),
};
let arena_type_id = fc.resolve_type_id(&qualified_type_name).ok_or_else(|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: unknown arena type `{qualified_type_name}` for \
RecordCreate (TypeId={:?})",
rc.type_id
),
})
})?;
let field_names = fc.arena.get_field_names(arena_type_id).to_vec();
for expected_name in &field_names {
let field = rc.fields.iter().find(|f| f.name == *expected_name).ok_or_else(
|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: missing field `{expected_name}` in record `{qualified_type_name}`"
),
})
},
)?;
compile_mir_expr(fc, &field.value)?;
}
fc.emit_op(RECORD_NEW);
fc.emit_u16(arena_type_id as u16);
fc.emit_u8(field_names.len() as u8);
Ok(())
}
MirExpr::RecordUpdate(spanned_ru) => {
let ru = &spanned_ru.node;
let qualified_type_name = match ru.type_id {
Some(id) => fc.canonical_type_name(id)?,
None => ru.type_name.clone(),
};
let arena_type_id = fc.resolve_type_id(&qualified_type_name).ok_or_else(|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: unknown arena type `{qualified_type_name}` for \
RecordUpdate (TypeId={:?})",
ru.type_id
),
})
})?;
let field_names = fc.arena.get_field_names(arena_type_id).to_vec();
let mut updated_indices = Vec::with_capacity(ru.updates.len());
compile_mir_expr(fc, &ru.base)?;
for (field_idx, field_name) in field_names.iter().enumerate() {
if let Some(field) = ru.updates.iter().find(|f| f.name == *field_name) {
compile_mir_expr(fc, &field.value)?;
updated_indices.push(field_idx as u8);
}
}
fc.emit_op(RECORD_UPDATE);
fc.emit_u16(arena_type_id as u16);
fc.emit_u8(updated_indices.len() as u8);
for idx in updated_indices {
fc.emit_u8(idx);
}
Ok(())
}
MirExpr::IfThenElse(spanned_ite) => {
let ite = &spanned_ite.node;
compile_mir_expr(fc, &ite.cond)?;
let else_patch = fc.emit_jump(JUMP_IF_FALSE);
compile_mir_expr(fc, &ite.then_branch)?;
let end_patch = fc.emit_jump(JUMP);
fc.patch_jump(else_patch);
compile_mir_expr(fc, &ite.else_branch)?;
fc.patch_jump(end_patch);
Ok(())
}
MirExpr::Match(spanned_match) => {
let m = &spanned_match.node;
if m.arms.is_empty() {
return Err(CompileError {
msg: "match expression has no arms (non-exhaustive match reached codegen)"
.to_string(),
}
.into());
}
if try_emit_match_dispatch_const(fc, &m.subject, &m.arms)?.is_some() {
return Ok(());
}
compile_mir_expr(fc, &m.subject)?;
let mut end_jumps: Vec<usize> = Vec::new();
let last_idx = m.arms.len() - 1;
for (i, arm) in m.arms.iter().enumerate() {
let is_last = i == last_idx;
let fail_patches: Vec<usize> = if is_last {
emit_last_arm_bindings(fc, &arm.pattern)?;
Vec::new()
} else {
emit_pattern_check(fc, &arm.pattern)?
};
fc.emit_op(POP);
compile_mir_expr(fc, &arm.body)?;
if !is_last {
end_jumps.push(fc.emit_jump(JUMP));
let next_arm_start = fc.offset();
for patch in fail_patches {
fc.patch_jump_to(patch, next_arm_start);
}
}
}
for patch in end_jumps {
fc.patch_jump(patch);
}
Ok(())
}
MirExpr::MapLiteral(entries) => {
let empty_map = fc.arena.push_map(crate::nan_value::PersistentMap::new());
let nv = NanValue::new_map(empty_map);
let idx = fc.add_constant(nv);
fc.emit_op(LOAD_CONST);
fc.emit_u16(idx);
for (k, v) in entries {
compile_mir_expr(fc, k)?;
compile_mir_expr(fc, v)?;
let symbol_id = fc.symbols.intern_builtin(VmBuiltin::MapSet).map_err(|e| {
MirVmUnsupported::InnerError(CompileError {
msg: format!("MIR-VM: intern_builtin(MapSet) failed: {e:?}"),
})
})?;
fc.emit_op(CALL_BUILTIN);
fc.emit_u32(symbol_id);
fc.emit_u8(3);
}
Ok(())
}
MirExpr::InterpolatedStr(parts) => {
if parts.is_empty() {
let nv = NanValue::new_string_value("", fc.arena);
let cidx = fc.add_constant(nv);
fc.emit_op(LOAD_CONST);
fc.emit_u16(cidx);
return Ok(());
}
let mut first = true;
for part in parts {
match part {
MirStrPart::Literal(s) => {
let nv = NanValue::new_string_value(s, fc.arena);
let cidx = fc.add_constant(nv);
fc.emit_op(LOAD_CONST);
fc.emit_u16(cidx);
}
MirStrPart::Expr(e) => {
compile_mir_expr(fc, e)?;
let empty_nv = NanValue::new_string_value("", fc.arena);
let empty_const = fc.add_constant(empty_nv);
fc.emit_op(LOAD_CONST);
fc.emit_u16(empty_const);
fc.emit_op(CONCAT);
}
}
if !first {
fc.emit_op(CONCAT);
}
first = false;
}
Ok(())
}
MirExpr::IndependentProduct(spanned_ip) => {
let ip = &spanned_ip.node;
let call_ready = ip.items.iter().all(|item| {
let inner = match &item.node {
MirExpr::Try(boxed) => &boxed.node,
other => other,
};
matches!(inner, MirExpr::Call(_))
});
if !call_ready {
for item in &ip.items {
compile_mir_expr(fc, item)?;
}
fc.emit_op(TUPLE_NEW);
fc.emit_u8(ip.items.len() as u8);
return Ok(());
}
let mut arg_counts: Vec<u8> = Vec::with_capacity(ip.items.len());
for item in &ip.items {
let inner_call = match &item.node {
MirExpr::Try(boxed) => &boxed.node,
other => other,
};
let MirExpr::Call(spanned_call) = inner_call else {
unreachable!("call_ready preflight just confirmed");
};
let call = &spanned_call.node;
match &call.callee {
MirCallee::Fn(fn_id) => {
let name = fc.canonical_fn_name(*fn_id)?;
let symbol_id = fc.symbols.find(&name).ok_or_else(|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!("MIR-VM: missing VM symbol for fn `{name}`"),
})
})?;
let idx = fc.add_constant(VmSymbolTable::symbol_ref(symbol_id));
fc.emit_op(LOAD_CONST);
fc.emit_u16(idx);
}
MirCallee::Builtin(id) => {
let name = fc.mir_program.map(|p| p.builtin_name(*id)).unwrap_or("");
let symbol_id = fc.symbols.find(name).ok_or_else(|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!("MIR-VM: missing VM symbol for builtin `{name}`"),
})
})?;
let idx = fc.add_constant(VmSymbolTable::symbol_ref(symbol_id));
fc.emit_op(LOAD_CONST);
fc.emit_u16(idx);
}
MirCallee::Intrinsic(_) | MirCallee::LocalSlot { .. } => {
return Err(MirVmUnsupported::UnsupportedCallee);
}
}
for arg in &call.args {
compile_mir_expr(fc, arg)?;
}
arg_counts.push(call.args.len() as u8);
}
fc.emit_op(CALL_PAR);
fc.emit_u8(ip.items.len() as u8);
fc.emit_u8(if ip.unwrap_results { 1 } else { 0 });
for argc in arg_counts {
fc.emit_u8(argc);
}
Ok(())
}
}
}
pub(super) fn compile_mir_fn_body(
fc: &mut FnCompiler<'_>,
mir_fn: &MirFn,
) -> Result<(), MirVmUnsupported> {
compile_mir_expr(fc, &mir_fn.body)?;
fc.emit_op(RETURN);
Ok(())
}
pub fn classify_mir_program_coverage(mir: &MirProgram) -> MirVmCoverage {
let mut covered = 0u32;
let mut needs_hir_fallback = 0u32;
for mir_fn in mir.fns.values() {
if can_compile(&mir_fn.body) {
covered += 1;
} else {
needs_hir_fallback += 1;
}
}
MirVmCoverage {
covered,
needs_hir_fallback,
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MirVmCoverage {
pub covered: u32,
pub needs_hir_fallback: u32,
}
pub(super) fn mir_expr_compilable(expr: &Spanned<MirExpr>) -> bool {
can_compile(expr)
}
fn can_compile(expr: &Spanned<MirExpr>) -> bool {
match &expr.node {
MirExpr::Literal(_) => true,
MirExpr::Local(_) => true,
MirExpr::BinOp(b) => can_compile(&b.node.lhs) && can_compile(&b.node.rhs),
MirExpr::Neg(inner) => can_compile(inner),
MirExpr::Let(l) => can_compile(&l.node.value) && can_compile(&l.node.body),
MirExpr::Call(c) => {
let callee_ok = match &c.node.callee {
MirCallee::Fn(_) => true,
MirCallee::Builtin(_) => true,
MirCallee::Intrinsic(_) => true,
MirCallee::LocalSlot { .. } => true,
};
callee_ok && c.node.args.iter().all(can_compile)
}
MirExpr::Return(inner) => can_compile(inner),
MirExpr::FnValue(_) => true,
MirExpr::Construct(c) => c.node.args.iter().all(can_compile),
MirExpr::Project(p) => can_compile(&p.node.base),
MirExpr::Try(inner) => can_compile(inner),
MirExpr::Box(inner) | MirExpr::Unbox(inner) => can_compile(inner),
MirExpr::TailCall(t) => t.node.args.iter().all(can_compile),
MirExpr::List(items) => items.iter().all(can_compile),
MirExpr::Tuple(items) => items.iter().all(can_compile),
MirExpr::RecordCreate(rc) => rc.node.fields.iter().all(|f| can_compile(&f.value)),
MirExpr::RecordUpdate(ru) => {
can_compile(&ru.node.base) && ru.node.updates.iter().all(|f| can_compile(&f.value))
}
MirExpr::MapLiteral(entries) => entries
.iter()
.all(|(k, v)| can_compile(k) && can_compile(v)),
MirExpr::InterpolatedStr(parts) => parts.iter().all(|p| match p {
MirStrPart::Literal(_) => true,
MirStrPart::Expr(e) => can_compile(e),
}),
MirExpr::IndependentProduct(ip) => ip.node.items.iter().all(can_compile),
MirExpr::IfThenElse(ite) => {
can_compile(&ite.node.cond)
&& can_compile(&ite.node.then_branch)
&& can_compile(&ite.node.else_branch)
}
MirExpr::Match(m) => {
can_compile(&m.node.subject)
&& m.node
.arms
.iter()
.all(|arm| pattern_supported(&arm.pattern) && can_compile(&arm.body))
}
}
}
const WILDCARD_SLOT_SENTINEL: u32 = u16::MAX as u32;
fn emit_store_or_pop(fc: &mut FnCompiler<'_>, local: LocalId) {
if local.0 == WILDCARD_SLOT_SENTINEL {
fc.emit_op(POP);
} else {
fc.emit_op(STORE_LOCAL);
fc.emit_u8(local.0 as u8);
}
}
fn emit_dup_and_bind(fc: &mut FnCompiler<'_>, local: LocalId) {
if local.0 == WILDCARD_SLOT_SENTINEL {
return;
}
fc.emit_op(DUP);
fc.emit_op(STORE_LOCAL);
fc.emit_u8(local.0 as u8);
}
fn pattern_supported(p: &MirPattern) -> bool {
match p {
MirPattern::Wildcard
| MirPattern::Bind(_, _)
| MirPattern::Literal(_)
| MirPattern::EmptyList
| MirPattern::Cons { .. }
| MirPattern::Ctor { .. } => true,
MirPattern::Tuple(items) => items.iter().all(pattern_supported),
}
}
fn emit_pattern_check(
fc: &mut FnCompiler<'_>,
pattern: &MirPattern,
) -> Result<Vec<usize>, MirVmUnsupported> {
match pattern {
MirPattern::Wildcard => Ok(Vec::new()),
MirPattern::Bind(local, _name) => {
emit_dup_and_bind(fc, *local);
Ok(Vec::new())
}
MirPattern::Literal(Literal::Int(v)) => {
fc.emit_op(MATCH_INT_LITERAL);
fc.emit_i64(*v);
let patch = fc.offset();
fc.emit_i16(0);
Ok(vec![patch])
}
MirPattern::Literal(lit) => {
fc.emit_op(DUP);
fc.compile_literal(lit)?;
fc.emit_op(EQ);
let patch = fc.emit_jump(JUMP_IF_FALSE);
Ok(vec![patch])
}
MirPattern::EmptyList => {
fc.emit_op(MATCH_NIL);
let patch = fc.offset();
fc.emit_i16(0);
Ok(vec![patch])
}
MirPattern::Cons { head, tail, .. } => {
fc.emit_op(MATCH_CONS);
let patch = fc.offset();
fc.emit_i16(0);
fc.emit_op(DUP);
fc.emit_op(LIST_HEAD_TAIL);
emit_store_or_pop(fc, *head);
emit_store_or_pop(fc, *tail);
Ok(vec![patch])
}
MirPattern::Ctor {
ctor: MirCtor::User(ctor_id),
bindings,
..
} => {
let entry = fc.symbol_table.ctor_entry(*ctor_id);
let owning_type = entry.owning_type;
let variant_name = entry.name.clone();
let qualified_type_name = fc.canonical_type_name(owning_type)?;
let arena_type_id = fc.resolve_type_id(&qualified_type_name).ok_or_else(|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: unknown arena type `{qualified_type_name}` for ctor pattern"
),
})
})?;
let variant_id = fc
.arena
.find_variant_id(arena_type_id, &variant_name)
.ok_or_else(|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: unknown variant `{variant_name}` on `{qualified_type_name}`"
),
})
})?;
let arena_ctor_id =
fc.arena.find_ctor_id(arena_type_id, variant_id).ok_or_else(|| {
MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: unknown arena ctor id for `{qualified_type_name}.{variant_name}`"
),
})
})?;
if arena_ctor_id > u16::MAX as u32 {
return Err(MirVmUnsupported::InnerError(CompileError {
msg: format!(
"MIR-VM: ctor id too large for MATCH_VARIANT: {qualified_type_name}.{variant_name}"
),
}));
}
fc.emit_op(MATCH_VARIANT);
fc.emit_u16(arena_ctor_id as u16);
let patch = fc.offset();
fc.emit_i16(0);
for (i, b) in bindings.iter().enumerate() {
fc.emit_op(EXTRACT_FIELD);
fc.emit_u8(i as u8);
emit_store_or_pop(fc, *b);
}
Ok(vec![patch])
}
MirPattern::Tuple(items) => {
fc.emit_op(MATCH_TUPLE);
fc.emit_u8(items.len() as u8);
let tuple_fail = fc.offset();
fc.emit_i16(0);
let mut all_patches = vec![tuple_fail];
for (i, sub) in items.iter().enumerate() {
let nested = emit_nested_subpattern(
fc,
|fc| {
fc.emit_op(EXTRACT_TUPLE_ITEM);
fc.emit_u8(i as u8);
},
sub,
)?;
all_patches.extend(nested);
}
Ok(all_patches)
}
MirPattern::Ctor {
ctor: MirCtor::Builtin(bc),
bindings,
..
} => {
match bc {
BuiltinCtor::ResultOk | BuiltinCtor::ResultErr | BuiltinCtor::OptionSome => {
let kind: u8 = match bc {
BuiltinCtor::ResultOk => 0,
BuiltinCtor::ResultErr => 1,
BuiltinCtor::OptionSome => 2,
BuiltinCtor::OptionNone => unreachable!(),
};
fc.emit_op(MATCH_UNWRAP);
fc.emit_u8(kind);
let patch = fc.offset();
fc.emit_i16(0);
if let Some(b) = bindings.first() {
emit_dup_and_bind(fc, *b);
}
Ok(vec![patch])
}
BuiltinCtor::OptionNone => {
fc.emit_op(DUP);
let none_const = fc.add_constant(NanValue::NONE);
fc.emit_op(LOAD_CONST);
fc.emit_u16(none_const);
fc.emit_op(EQ);
let patch = fc.emit_jump(JUMP_IF_FALSE);
Ok(vec![patch])
}
}
}
}
}
fn try_emit_match_dispatch_const(
fc: &mut FnCompiler<'_>,
subject: &Spanned<MirExpr>,
arms: &[crate::ir::mir::MirMatchArm],
) -> Result<Option<()>, MirVmUnsupported> {
if arms.len() < 2 {
return Ok(None);
}
let last_idx = arms.len() - 1;
let default_arm = &arms[last_idx];
let default_local = match &default_arm.pattern {
MirPattern::Wildcard => None,
MirPattern::Bind(local, _name) => Some(*local),
_ => return Ok(None),
};
let mut entries: Vec<(u8, u64, u64)> = Vec::with_capacity(last_idx);
for arm in &arms[..last_idx] {
let pattern_lit = match &arm.pattern {
MirPattern::Literal(lit) => lit,
_ => return Ok(None),
};
let body_lit = match &arm.body.node {
MirExpr::Literal(spanned_lit) => &spanned_lit.node,
_ => return Ok(None),
};
if !(pattern_is_dispatchable_bits(pattern_lit) && body_is_dispatchable_bits(body_lit)) {
return Ok(None);
}
let expected = literal_dispatch_bits(fc, pattern_lit);
let result = literal_dispatch_bits(fc, body_lit);
entries.push((0u8, expected, result)); }
if entries.is_empty() {
return Ok(None);
}
compile_mir_expr(fc, subject)?;
fc.emit_op(MATCH_DISPATCH_CONST);
fc.emit_u8(entries.len() as u8);
let default_offset_patch = fc.offset();
fc.emit_i16(0);
for (kind, expected, result) in &entries {
fc.emit_u8(*kind);
fc.emit_u64(*expected);
fc.emit_u64(*result);
}
let table_end = fc.offset();
let hit_skip = fc.emit_jump(JUMP);
let default_start = fc.offset();
let default_rel = (default_start as isize - table_end as isize) as i16;
let bytes = (default_rel as u16).to_be_bytes();
fc.code_mut()[default_offset_patch] = bytes[0];
fc.code_mut()[default_offset_patch + 1] = bytes[1];
if let Some(local) = default_local {
emit_dup_and_bind(fc, local);
}
fc.emit_op(POP);
compile_mir_expr(fc, &default_arm.body)?;
fc.patch_jump(hit_skip);
Ok(Some(()))
}
fn pattern_is_dispatchable_bits(lit: &Literal) -> bool {
match lit {
Literal::Int(i) => is_inline_int(*i),
Literal::Bool(_) | Literal::Unit | Literal::Float(_) => true,
Literal::Str(_) | Literal::BigInt(_) => false,
}
}
#[inline]
fn is_inline_int(i: i64) -> bool {
(crate::nan_value::INT_INLINE_MIN..=crate::nan_value::INT_INLINE_MAX).contains(&i)
}
fn body_is_dispatchable_bits(lit: &Literal) -> bool {
pattern_is_dispatchable_bits(lit)
}
fn literal_dispatch_bits(fc: &mut FnCompiler<'_>, lit: &Literal) -> u64 {
let nv = match lit {
Literal::Int(i) => NanValue::new_int(*i, fc.arena),
Literal::Float(f) => NanValue::new_float(*f),
Literal::Bool(b) => NanValue::new_bool(*b),
Literal::Unit => NanValue::UNIT,
Literal::Str(s) => NanValue::new_string_value(s, fc.arena),
Literal::BigInt(_) => {
unreachable!("BigInt is not bit-dispatchable; excluded by pattern_is_dispatchable_bits")
}
};
nv.bits()
}
fn emit_last_arm_bindings(
fc: &mut FnCompiler<'_>,
pattern: &MirPattern,
) -> Result<(), MirVmUnsupported> {
match pattern {
MirPattern::Wildcard | MirPattern::Literal(_) | MirPattern::EmptyList => Ok(()),
MirPattern::Bind(local, _name) => {
emit_dup_and_bind(fc, *local);
Ok(())
}
MirPattern::Cons { head, tail, .. } => {
fc.emit_op(DUP);
fc.emit_op(LIST_HEAD_TAIL);
emit_store_or_pop(fc, *head);
emit_store_or_pop(fc, *tail);
Ok(())
}
MirPattern::Ctor {
ctor: MirCtor::User(_),
bindings,
..
} => {
for (i, b) in bindings.iter().enumerate() {
fc.emit_op(EXTRACT_FIELD);
fc.emit_u8(i as u8);
emit_store_or_pop(fc, *b);
}
Ok(())
}
MirPattern::Ctor {
ctor: MirCtor::Builtin(bc),
bindings,
..
} => {
if let Some(b) = bindings.first() {
let kind: u8 = match bc {
BuiltinCtor::ResultOk => 0,
BuiltinCtor::ResultErr => 1,
BuiltinCtor::OptionSome => 2,
BuiltinCtor::OptionNone => {
return Ok(());
}
};
fc.emit_op(MATCH_UNWRAP);
fc.emit_u8(kind);
fc.emit_i16(0); emit_dup_and_bind(fc, *b);
}
Ok(())
}
MirPattern::Tuple(items) => {
for (i, sub) in items.iter().enumerate() {
fc.emit_op(EXTRACT_TUPLE_ITEM);
fc.emit_u8(i as u8);
emit_last_arm_bindings(fc, sub)?;
fc.emit_op(POP);
}
Ok(())
}
}
}
fn emit_nested_subpattern<F>(
fc: &mut FnCompiler<'_>,
emit_subject: F,
pattern: &MirPattern,
) -> Result<Vec<usize>, MirVmUnsupported>
where
F: FnOnce(&mut FnCompiler<'_>),
{
emit_subject(fc);
let inner_fail_patches = emit_pattern_check(fc, pattern)?;
fc.emit_op(POP);
if inner_fail_patches.is_empty() {
return Ok(Vec::new());
}
let success_skip = fc.emit_jump(JUMP);
let cleanup_target = fc.offset();
for patch in inner_fail_patches {
fc.patch_jump_to(patch, cleanup_target);
}
fc.emit_op(POP);
let outer_fail = fc.emit_jump(JUMP);
fc.patch_jump(success_skip);
Ok(vec![outer_fail])
}
fn mir_local_slot_last_use(expr: &MirExpr) -> Option<(u32, bool)> {
match expr {
MirExpr::Local(l) => Some((l.node.slot.0, l.node.last_use)),
_ => None,
}
}
fn try_emit_vector_compound(
fc: &mut FnCompiler<'_>,
args: &[Spanned<MirExpr>],
) -> Result<bool, MirVmUnsupported> {
if args.len() != 2 {
return Ok(false);
}
let MirExpr::Call(inner_call) = &args[0].node else {
return Ok(false);
};
let MirCall {
callee: MirCallee::Builtin(inner_id),
args: inner_args,
} = &inner_call.node
else {
return Ok(false);
};
let inner_name = fc
.mir_program
.map(|p| p.builtin_name(*inner_id))
.unwrap_or("");
match lookup_vm_builtin(inner_name) {
Some(VmBuiltin::VectorSet) if inner_args.len() == 3 => {
let vec = mir_local_slot_last_use(&inner_args[0].node);
let def = mir_local_slot_last_use(&args[1].node);
let (Some((vec_slot, vec_last_use)), Some((def_slot, def_last_use))) = (vec, def)
else {
return Ok(false);
};
if vec_slot != def_slot {
return Ok(false);
}
let owned = (vec_last_use || def_last_use) && !fc.is_aliased_slot(vec_slot as u16);
compile_mir_expr(fc, &inner_args[0])?;
compile_mir_expr(fc, &inner_args[1])?;
compile_mir_expr(fc, &inner_args[2])?;
fc.emit_op(VECTOR_SET_OR_KEEP);
fc.emit_u8(u8::from(owned));
Ok(true)
}
Some(VmBuiltin::VectorGet) if inner_args.len() == 2 => {
let MirExpr::Literal(lit) = &args[1].node else {
return Ok(false);
};
compile_mir_expr(fc, &inner_args[0])?;
compile_mir_expr(fc, &inner_args[1])?;
let default_value = fc.nan_literal(&lit.node);
let const_idx = fc.add_constant(default_value);
fc.emit_op(VECTOR_GET_OR);
fc.emit_u16(const_idx);
Ok(true)
}
_ => Ok(false),
}
}
fn compute_owned_mask(args: &[Spanned<MirExpr>], fc: &FnCompiler<'_>) -> u8 {
let mut mask = 0u8;
for (i, arg) in args.iter().enumerate().take(8) {
if contains_last_use_slot_mir(&arg.node, i as u32) && !fc.is_aliased_slot(i as u16) {
mask |= 1 << i;
}
}
mask
}
fn compute_builtin_owned_mask(args: &[Spanned<MirExpr>], fc: &FnCompiler<'_>) -> u8 {
let mut mask = 0u8;
for (i, arg) in args.iter().enumerate().take(8) {
if let Some((slot, last_use)) = mir_local_slot_last_use(&arg.node)
&& last_use
&& !fc.is_aliased_slot(slot as u16)
{
mask |= 1 << i;
}
}
mask
}
fn contains_last_use_slot_mir(expr: &MirExpr, target: u32) -> bool {
match expr {
MirExpr::Local(spanned_local) => {
spanned_local.node.slot.0 == target && spanned_local.node.last_use
}
MirExpr::Call(c) => c
.node
.args
.iter()
.any(|a| contains_last_use_slot_mir(&a.node, target)),
MirExpr::TailCall(t) => t
.node
.args
.iter()
.any(|a| contains_last_use_slot_mir(&a.node, target)),
MirExpr::BinOp(b) => {
contains_last_use_slot_mir(&b.node.lhs.node, target)
|| contains_last_use_slot_mir(&b.node.rhs.node, target)
}
MirExpr::Neg(inner) => contains_last_use_slot_mir(&inner.node, target),
MirExpr::Project(p) => contains_last_use_slot_mir(&p.node.base.node, target),
MirExpr::Try(inner) => contains_last_use_slot_mir(&inner.node, target),
MirExpr::Box(inner) | MirExpr::Unbox(inner) => {
contains_last_use_slot_mir(&inner.node, target)
}
MirExpr::Construct(c) => c
.node
.args
.iter()
.any(|a| contains_last_use_slot_mir(&a.node, target)),
MirExpr::RecordCreate(rc) => rc
.node
.fields
.iter()
.any(|f| contains_last_use_slot_mir(&f.value.node, target)),
MirExpr::RecordUpdate(ru) => {
contains_last_use_slot_mir(&ru.node.base.node, target)
|| ru
.node
.updates
.iter()
.any(|f| contains_last_use_slot_mir(&f.value.node, target))
}
MirExpr::List(items) | MirExpr::Tuple(items) => items
.iter()
.any(|i| contains_last_use_slot_mir(&i.node, target)),
MirExpr::MapLiteral(entries) => entries.iter().any(|(k, v)| {
contains_last_use_slot_mir(&k.node, target)
|| contains_last_use_slot_mir(&v.node, target)
}),
MirExpr::IndependentProduct(ip) => ip
.node
.items
.iter()
.any(|i| contains_last_use_slot_mir(&i.node, target)),
MirExpr::InterpolatedStr(parts) => parts.iter().any(|p| match p {
MirStrPart::Expr(e) => contains_last_use_slot_mir(&e.node, target),
MirStrPart::Literal(_) => false,
}),
MirExpr::Match(_)
| MirExpr::IfThenElse(_)
| MirExpr::Let(_)
| MirExpr::Return(_)
| MirExpr::FnValue(_)
| MirExpr::Literal(_) => false,
}
}
fn lookup_vm_builtin(name: &str) -> Option<VmBuiltin> {
VmBuiltin::ALL.iter().copied().find(|b| b.name() == name)
}
fn emit_constructor_arg(
fc: &mut FnCompiler<'_>,
arg: Option<&Spanned<MirExpr>>,
) -> Result<(), MirVmUnsupported> {
match arg {
Some(a) => compile_mir_expr(fc, a),
None => {
fc.emit_op(LOAD_UNIT);
Ok(())
}
}
}
fn emit_binop_typed(
fc: &mut FnCompiler<'_>,
op: crate::ast::BinOp,
lhs_ty: Option<&crate::ast::Type>,
rhs_ty: Option<&crate::ast::Type>,
) {
use crate::ast::BinOp::*;
use crate::ast::Type;
let both_int = matches!((lhs_ty, rhs_ty), (Some(Type::Int), Some(Type::Int)));
let both_float = matches!((lhs_ty, rhs_ty), (Some(Type::Float), Some(Type::Float)));
let lt_op = if both_int {
LT_INT
} else if both_float {
LT_FLOAT
} else {
LT
};
let gt_op = if both_int {
GT_INT
} else if both_float {
GT_FLOAT
} else {
GT
};
let add_op = if both_int {
ADD_INT
} else if both_float {
ADD_FLOAT
} else {
ADD
};
let sub_op = if both_int {
SUB_INT
} else if both_float {
SUB_FLOAT
} else {
SUB
};
let mul_op = if both_int {
MUL_INT
} else if both_float {
MUL_FLOAT
} else {
MUL
};
let div_op = if both_float { DIV_FLOAT } else { DIV };
match op {
Add => fc.emit_op(add_op),
Sub => fc.emit_op(sub_op),
Mul => fc.emit_op(mul_op),
Div => fc.emit_op(div_op),
Eq => fc.emit_op(if both_int { EQ_INT } else { EQ }),
Lt => fc.emit_op(lt_op),
Gt => fc.emit_op(gt_op),
Neq => {
fc.emit_op(if both_int { EQ_INT } else { EQ });
fc.emit_op(NOT);
}
Lte => {
fc.emit_op(gt_op);
fc.emit_op(NOT);
}
Gte => {
fc.emit_op(lt_op);
fc.emit_op(NOT);
}
}
}