mod calls;
mod classify;
mod expr;
mod patterns;
use std::collections::HashMap;
use crate::ast::{FnBody, FnDef, Stmt, TopLevel, TypeDef};
use crate::nan_value::{Arena, NanValue};
use crate::types::{option, result};
use crate::visibility;
use super::builtin::VmBuiltin;
use super::opcode::*;
use super::symbol::{VmSymbolTable, VmVariantCtor};
use super::types::{CodeStore, FnChunk};
pub fn compile_program(
items: &[TopLevel],
arena: &mut Arena,
analysis: Option<&crate::ir::AnalysisResult>,
) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
compile_program_with_modules(items, arena, None, "", analysis)
}
pub fn compile_program_with_modules(
items: &[TopLevel],
arena: &mut Arena,
module_root: Option<&str>,
source_file: &str,
analysis: Option<&crate::ir::AnalysisResult>,
) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
compile_program_inner(
items,
arena,
source_file,
ModuleSource::Disk(module_root),
analysis,
)
}
pub fn compile_program_with_loaded_modules(
items: &[TopLevel],
arena: &mut Arena,
loaded: Vec<crate::source::LoadedModule>,
source_file: &str,
analysis: Option<&crate::ir::AnalysisResult>,
) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
compile_program_inner(
items,
arena,
source_file,
ModuleSource::Loaded(loaded),
analysis,
)
}
enum ModuleSource<'a> {
Disk(Option<&'a str>),
Loaded(Vec<crate::source::LoadedModule>),
}
fn compile_program_inner(
items: &[TopLevel],
arena: &mut Arena,
source_file: &str,
module_source: ModuleSource<'_>,
analysis: Option<&crate::ir::AnalysisResult>,
) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
let mut compiler = ProgramCompiler::new();
compiler.source_file = source_file.to_string();
compiler.sync_record_field_symbols(arena);
compiler.install_branch_path_root_constant(arena);
match module_source {
ModuleSource::Disk(Some(module_root)) => {
compiler.load_modules(items, module_root, arena)?;
}
ModuleSource::Disk(None) => {}
ModuleSource::Loaded(loaded) => {
for m in loaded {
compiler.integrate_module(&m.dep_name, m.items, arena)?;
}
}
}
for item in items {
if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
compiler.ensure_global(name);
}
}
for item in items {
match item {
TopLevel::FnDef(fndef) => {
compiler.ensure_global(&fndef.name);
let effect_ids: Vec<u32> = fndef
.effects
.iter()
.map(|effect| compiler.symbols.intern_name(&effect.node))
.collect();
let fn_id = compiler.code.add_function(FnChunk {
name: fndef.name.clone(),
arity: fndef.params.len() as u8,
local_count: 0,
code: Vec::new(),
constants: Vec::new(),
effects: effect_ids,
thin: false,
parent_thin: false,
leaf: false,
no_alloc: false,
source_file: String::new(),
line_table: Vec::new(),
});
let symbol_id = compiler.symbols.intern_function(
&fndef.name,
fn_id,
&fndef
.effects
.iter()
.map(|e| e.node.clone())
.collect::<Vec<_>>(),
);
let global_idx = compiler.global_names[&fndef.name];
compiler.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
}
TopLevel::TypeDef(td) => {
match td {
TypeDef::Product { name, fields, .. } => {
let field_names: Vec<String> =
fields.iter().map(|(n, _)| n.clone()).collect();
arena.register_record_type(name, field_names);
}
TypeDef::Sum { name, variants, .. } => {
let variant_names: Vec<String> =
variants.iter().map(|v| v.name.clone()).collect();
arena.register_sum_type(name, variant_names);
}
}
compiler.register_type_in_symbols(td, arena);
}
_ => {}
}
}
compiler.register_current_module_namespace(items);
for item in items {
if let TopLevel::FnDef(fndef) = item {
let fn_id = compiler.code.find(&fndef.name).unwrap();
let chunk = compiler.compile_fn(fndef, arena)?;
compiler.code.functions[fn_id as usize] = chunk;
}
}
compiler.compile_top_level(items, arena)?;
compiler.code.symbols = compiler.symbols.clone();
classify::classify_thin_functions(&mut compiler.code, arena)?;
let user_fn_defs: Vec<&crate::ast::FnDef> = items
.iter()
.filter_map(|item| {
if let TopLevel::FnDef(fd) = item {
Some(fd)
} else {
None
}
})
.collect();
if !user_fn_defs.is_empty() {
let fallback_info = if analysis.is_none() {
let policy = super::VmAllocPolicy;
Some(crate::ir::compute_alloc_info(&user_fn_defs, &policy))
} else {
None
};
let allocates = |name: &str| -> bool {
if let Some(a) = analysis
&& let Some(fa) = a.fn_analyses.get(name)
&& let Some(b) = fa.allocates
{
return b;
}
if let Some(info) = fallback_info.as_ref() {
return *info.get(name).unwrap_or(&true);
}
true
};
for fd in &user_fn_defs {
if !allocates(&fd.name)
&& let Some(fn_id) = compiler.code.find(&fd.name)
{
let chunk = &mut compiler.code.functions[fn_id as usize];
chunk.no_alloc = true;
chunk.thin = true;
}
}
}
Ok((compiler.code, compiler.globals))
}
#[derive(Debug)]
pub struct CompileError {
pub msg: String,
}
impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Compile error: {}", self.msg)
}
}
struct ProgramCompiler {
code: CodeStore,
symbols: VmSymbolTable,
globals: Vec<NanValue>,
global_names: HashMap<String, u16>,
source_file: String,
}
impl ProgramCompiler {
fn new() -> Self {
let mut compiler = ProgramCompiler {
code: CodeStore::new(),
symbols: VmSymbolTable::default(),
globals: Vec::new(),
global_names: HashMap::new(),
source_file: String::new(),
};
compiler.bootstrap_core_symbols();
compiler
}
fn sync_record_field_symbols(&mut self, arena: &Arena) {
for type_id in 0..arena.type_count() {
let type_name = arena.get_type_name(type_id);
self.symbols.intern_namespace_path(type_name);
let field_names = arena.get_field_names(type_id);
if field_names.is_empty() {
continue;
}
let field_symbol_ids: Vec<u32> = field_names
.iter()
.map(|field_name| self.symbols.intern_name(field_name))
.collect();
self.code.register_record_fields(type_id, &field_symbol_ids);
}
}
fn load_modules(
&mut self,
items: &[TopLevel],
module_root: &str,
arena: &mut Arena,
) -> Result<(), CompileError> {
let module = items.iter().find_map(|i| {
if let TopLevel::Module(m) = i {
Some(m)
} else {
None
}
});
let module = match module {
Some(m) => m,
None => return Ok(()),
};
let modules = crate::source::load_module_tree(&module.depends, module_root)
.map_err(|e| CompileError { msg: e })?;
for loaded in modules {
self.integrate_module(&loaded.dep_name, loaded.items, arena)?;
}
Ok(())
}
fn integrate_module(
&mut self,
dep_name: &str,
mut mod_items: Vec<TopLevel>,
arena: &mut Arena,
) -> Result<(), CompileError> {
crate::ir::pipeline::tco(&mut mod_items);
crate::ir::pipeline::resolve(&mut mod_items);
for mt in visibility::collect_module_types(&mod_items) {
let type_id = match &mt.kind {
visibility::ModuleTypeKind::Record { field_names } => {
arena.register_record_type(&mt.bare_name, field_names.clone())
}
visibility::ModuleTypeKind::Sum { variant_names } => {
arena.register_sum_type(&mt.bare_name, variant_names.clone())
}
};
arena.register_type_alias(
&visibility::qualified_name(dep_name, &mt.bare_name),
type_id,
);
}
for item in &mod_items {
if let TopLevel::TypeDef(td) = item {
self.register_type_in_symbols(td, arena);
}
}
let mut module_fn_ids: Vec<(String, u32)> = Vec::new();
for item in &mod_items {
if let TopLevel::FnDef(fndef) = item {
let qualified_name = visibility::qualified_name(dep_name, &fndef.name);
let effect_ids: Vec<u32> = fndef
.effects
.iter()
.map(|effect| self.symbols.intern_name(&effect.node))
.collect();
let fn_id = self.code.add_function(FnChunk {
name: qualified_name.clone(),
arity: fndef.params.len() as u8,
local_count: 0,
code: Vec::new(),
constants: Vec::new(),
effects: effect_ids,
thin: false,
parent_thin: false,
leaf: false,
no_alloc: false,
source_file: String::new(),
line_table: Vec::new(),
});
self.symbols.intern_function(
&qualified_name,
fn_id,
&fndef
.effects
.iter()
.map(|e| e.node.clone())
.collect::<Vec<_>>(),
);
module_fn_ids.push((fndef.name.clone(), fn_id));
}
}
let module_scope: HashMap<String, u32> = module_fn_ids.iter().cloned().collect();
let mut fn_idx = 0;
for item in &mod_items {
if let TopLevel::FnDef(fndef) = item {
let (fn_name, fn_id) = &module_fn_ids[fn_idx];
let mut chunk = self.compile_fn_with_scope(fndef, arena, &module_scope)?;
chunk.name = visibility::qualified_name(dep_name, fn_name);
self.code.functions[*fn_id as usize] = chunk;
fn_idx += 1;
}
}
let exports = visibility::collect_module_exports(&mod_items);
for fd in &exports.functions {
let qualified = visibility::qualified_name(dep_name, &fd.name);
let global_idx = self.ensure_global(&qualified);
let symbol_id = self.symbols.find(&qualified).ok_or_else(|| CompileError {
msg: format!("missing VM symbol for exposed function {}", qualified),
})?;
self.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
}
let module_symbol_id = self.symbols.intern_namespace_path(dep_name);
for et in &exports.types {
let type_name = match et.def {
TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
};
if let Some(type_symbol_id) = self.symbols.find(type_name) {
let member_symbol_id = self.symbols.intern_name(type_name);
self.symbols.add_namespace_member_by_id(
module_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(type_symbol_id),
);
}
}
for fd in &exports.functions {
let qualified = visibility::qualified_name(dep_name, &fd.name);
if let Some(fn_symbol_id) = self.symbols.find(&qualified) {
let member_symbol_id = self.symbols.intern_name(&fd.name);
self.symbols.add_namespace_member_by_id(
module_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(fn_symbol_id),
);
}
}
Ok(())
}
fn install_branch_path_root_constant(&mut self, arena: &mut Arena) {
let Some(type_id) = arena.find_type_id(crate::types::branch_path::TYPE_NAME) else {
return;
};
let dewey = crate::nan_value::NanValue::new_string_value("", arena);
let record_idx = arena.push_record(type_id, vec![dewey]);
let root_value = crate::nan_value::NanValue::new_record(record_idx);
self.symbols.intern_constant("BranchPath.Root", root_value);
let namespace_symbol_id = self.symbols.intern_namespace_path("BranchPath");
let member_symbol_id = self.symbols.intern_name("Root");
self.symbols
.add_namespace_member_by_id(namespace_symbol_id, member_symbol_id, root_value);
}
fn ensure_global(&mut self, name: &str) -> u16 {
if let Some(&idx) = self.global_names.get(name) {
return idx;
}
let idx = self.globals.len() as u16;
self.global_names.insert(name.to_string(), idx);
self.globals.push(NanValue::UNIT);
idx
}
fn register_type_in_symbols(&mut self, td: &TypeDef, arena: &Arena) {
match td {
TypeDef::Product { name, fields, .. } => {
self.symbols.intern_namespace_path(name);
let type_id = arena
.find_type_id(name)
.expect("type already registered in Arena");
let field_symbol_ids: Vec<u32> = fields
.iter()
.map(|(field_name, _)| self.symbols.intern_name(field_name))
.collect();
self.code.register_record_fields(type_id, &field_symbol_ids);
}
TypeDef::Sum { name, variants, .. } => {
let type_symbol_id = self.symbols.intern_namespace_path(name);
let type_id = arena
.find_type_id(name)
.expect("type already registered in Arena");
for (variant_id, variant) in variants.iter().enumerate() {
let ctor_id = arena
.find_ctor_id(type_id, variant_id as u16)
.expect("ctor id");
let qualified_name = visibility::member_key(name, &variant.name);
let ctor_symbol_id = self.symbols.intern_variant_ctor(
&qualified_name,
VmVariantCtor {
type_id,
variant_id: variant_id as u16,
ctor_id,
field_count: variant.fields.len() as u8,
},
);
let member_symbol_id = self.symbols.intern_name(&variant.name);
self.symbols.add_namespace_member_by_id(
type_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(ctor_symbol_id),
);
}
}
}
}
fn bootstrap_core_symbols(&mut self) {
for builtin in VmBuiltin::ALL.iter().copied() {
let builtin_symbol_id = self.symbols.intern_builtin(builtin);
if let Some((namespace, member)) = builtin.name().split_once('.') {
let namespace_symbol_id = self.symbols.intern_namespace_path(namespace);
let member_symbol_id = self.symbols.intern_name(member);
self.symbols.add_namespace_member_by_id(
namespace_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(builtin_symbol_id),
);
}
}
let result_symbol_id = self.symbols.intern_namespace_path("Result");
let ok_symbol_id = self.symbols.intern_wrapper("Result.Ok", 0);
let err_symbol_id = self.symbols.intern_wrapper("Result.Err", 1);
let ok_member_symbol_id = self.symbols.intern_name("Ok");
self.symbols.add_namespace_member_by_id(
result_symbol_id,
ok_member_symbol_id,
VmSymbolTable::symbol_ref(ok_symbol_id),
);
let err_member_symbol_id = self.symbols.intern_name("Err");
self.symbols.add_namespace_member_by_id(
result_symbol_id,
err_member_symbol_id,
VmSymbolTable::symbol_ref(err_symbol_id),
);
for (member, builtin_name) in result::extra_members() {
if let Some(symbol_id) = self.symbols.find(&builtin_name) {
let member_symbol_id = self.symbols.intern_name(member);
self.symbols.add_namespace_member_by_id(
result_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(symbol_id),
);
}
}
let option_symbol_id = self.symbols.intern_namespace_path("Option");
let some_symbol_id = self.symbols.intern_wrapper("Option.Some", 2);
self.symbols.intern_constant("Option.None", NanValue::NONE);
let some_member_symbol_id = self.symbols.intern_name("Some");
self.symbols.add_namespace_member_by_id(
option_symbol_id,
some_member_symbol_id,
VmSymbolTable::symbol_ref(some_symbol_id),
);
let none_member_symbol_id = self.symbols.intern_name("None");
self.symbols.add_namespace_member_by_id(
option_symbol_id,
none_member_symbol_id,
NanValue::NONE,
);
for (member, builtin_name) in option::extra_members() {
if let Some(symbol_id) = self.symbols.find(&builtin_name) {
let member_symbol_id = self.symbols.intern_name(member);
self.symbols.add_namespace_member_by_id(
option_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(symbol_id),
);
}
}
}
fn compile_fn(&mut self, fndef: &FnDef, arena: &mut Arena) -> Result<FnChunk, CompileError> {
let empty_scope = HashMap::new();
self.compile_fn_with_scope(fndef, arena, &empty_scope)
}
fn compile_fn_with_scope(
&mut self,
fndef: &FnDef,
arena: &mut Arena,
module_scope: &HashMap<String, u32>,
) -> Result<FnChunk, CompileError> {
let resolution = fndef.resolution.as_ref();
let local_count = resolution.map_or(fndef.params.len() as u16, |r| r.local_count);
let local_slots: HashMap<String, u16> = resolution
.map(|r| r.local_slots.as_ref().clone())
.unwrap_or_else(|| {
fndef
.params
.iter()
.enumerate()
.map(|(i, (name, _))| (name.clone(), i as u16))
.collect()
});
let mut fc = FnCompiler::new(
&fndef.name,
fndef.params.len() as u8,
local_count,
fndef
.effects
.iter()
.map(|effect| self.symbols.intern_name(&effect.node))
.collect(),
local_slots,
&self.global_names,
module_scope,
&self.code,
&mut self.symbols,
arena,
);
fc.source_file = self.source_file.clone();
fc.note_line(fndef.line);
if let Some(res) = resolution {
fc.set_aliased_slots(res.aliased_slots.clone());
}
match fndef.body.as_ref() {
FnBody::Block(stmts) => fc.compile_body(stmts)?,
}
Ok(fc.finish())
}
fn compile_top_level(
&mut self,
items: &[TopLevel],
arena: &mut Arena,
) -> Result<(), CompileError> {
let has_stmts = items.iter().any(|i| matches!(i, TopLevel::Stmt(_)));
if !has_stmts {
return Ok(());
}
for item in items {
if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
self.ensure_global(name);
}
}
let empty_mod_scope = HashMap::new();
let mut fc = FnCompiler::new(
"__top_level__",
0,
0,
Vec::new(),
HashMap::new(),
&self.global_names,
&empty_mod_scope,
&self.code,
&mut self.symbols,
arena,
);
for item in items {
if let TopLevel::Stmt(stmt) = item {
match stmt {
Stmt::Binding(name, _type_ann, expr) => {
fc.compile_expr(expr)?;
let idx = self.global_names[name.as_str()];
fc.emit_op(STORE_GLOBAL);
fc.emit_u16(idx);
}
Stmt::Expr(expr) => {
fc.compile_expr(expr)?;
fc.emit_op(POP);
}
}
}
}
fc.emit_op(LOAD_UNIT);
fc.emit_op(RETURN);
let chunk = fc.finish();
self.code.add_function(chunk);
Ok(())
}
fn register_current_module_namespace(&mut self, items: &[TopLevel]) {
let Some(module) = items.iter().find_map(|item| {
if let TopLevel::Module(module) = item {
Some(module)
} else {
None
}
}) else {
return;
};
let module_symbol_id = self.symbols.intern_namespace_path(&module.name);
let exposes_ref = if module.exposes.is_empty() {
None
} else {
Some(module.exposes.as_slice())
};
for item in items {
match item {
TopLevel::FnDef(fndef) => {
if visibility::is_exposed(&fndef.name, exposes_ref)
&& let Some(symbol_id) = self.symbols.find(&fndef.name)
{
let member_symbol_id = self.symbols.intern_name(&fndef.name);
self.symbols.add_namespace_member_by_id(
module_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(symbol_id),
);
}
}
TopLevel::TypeDef(TypeDef::Product { name, .. } | TypeDef::Sum { name, .. }) => {
if visibility::is_exposed(name, exposes_ref)
&& let Some(symbol_id) = self.symbols.find(name)
{
let member_symbol_id = self.symbols.intern_name(name);
self.symbols.add_namespace_member_by_id(
module_symbol_id,
member_symbol_id,
VmSymbolTable::symbol_ref(symbol_id),
);
}
}
_ => {}
}
}
}
}
enum CallTarget {
KnownFn(u32),
Wrapper(u8),
None_,
Variant(u32, u16),
Builtin(VmBuiltin),
UnknownQualified(String),
}
struct FnCompiler<'a> {
name: String,
arity: u8,
local_count: u16,
effects: Vec<u32>,
local_slots: HashMap<String, u16>,
global_names: &'a HashMap<String, u16>,
module_scope: &'a HashMap<String, u32>,
code_store: &'a CodeStore,
symbols: &'a mut VmSymbolTable,
arena: &'a mut Arena,
code: Vec<u8>,
constants: Vec<NanValue>,
last_op_pos: usize,
source_file: String,
line_table: Vec<(u16, u16)>,
last_noted_line: u16,
aliased_slots: std::sync::Arc<Vec<bool>>,
}
impl<'a> FnCompiler<'a> {
#[allow(clippy::too_many_arguments)]
fn new(
name: &str,
arity: u8,
local_count: u16,
effects: Vec<u32>,
local_slots: HashMap<String, u16>,
global_names: &'a HashMap<String, u16>,
module_scope: &'a HashMap<String, u32>,
code_store: &'a CodeStore,
symbols: &'a mut VmSymbolTable,
arena: &'a mut Arena,
) -> Self {
FnCompiler {
name: name.to_string(),
arity,
local_count,
effects,
local_slots,
global_names,
module_scope,
code_store,
symbols,
arena,
code: Vec::new(),
constants: Vec::new(),
last_op_pos: usize::MAX,
source_file: String::new(),
line_table: Vec::new(),
last_noted_line: 0,
aliased_slots: std::sync::Arc::new(Vec::new()),
}
}
fn set_aliased_slots(&mut self, aliased: std::sync::Arc<Vec<bool>>) {
self.aliased_slots = aliased;
}
pub(super) fn is_aliased_slot(&self, slot: u16) -> bool {
self.aliased_slots
.get(slot as usize)
.copied()
.unwrap_or(false)
}
fn finish(self) -> FnChunk {
FnChunk {
name: self.name,
arity: self.arity,
local_count: self.local_count,
code: self.code,
constants: self.constants,
effects: self.effects,
thin: false,
parent_thin: false,
leaf: false,
no_alloc: false,
source_file: self.source_file,
line_table: self.line_table,
}
}
fn note_line(&mut self, line: usize) {
if line == 0 {
return;
}
let line16 = line as u16;
if line16 == self.last_noted_line {
return; }
self.last_noted_line = line16;
self.line_table.push((self.code.len() as u16, line16));
}
fn emit_op(&mut self, op: u8) {
let prev_pos = self.last_op_pos;
let prev_op = if prev_pos < self.code.len() {
self.code[prev_pos]
} else {
0xFF
};
if op == LOAD_LOCAL && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
self.code[prev_pos] = LOAD_LOCAL_2;
return;
}
if op == LOAD_CONST && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
self.code[prev_pos] = LOAD_LOCAL_CONST;
return;
}
if op == UNWRAP_OR && self.code.len() >= 4 {
let len = self.code.len();
if self.code[len - 4] == VECTOR_GET && self.code[len - 3] == LOAD_CONST {
let hi = self.code[len - 2];
let lo = self.code[len - 1];
self.code[len - 4] = VECTOR_GET_OR;
self.code[len - 3] = hi;
self.code[len - 2] = lo;
self.code.pop(); self.last_op_pos = len - 4;
return;
}
}
self.last_op_pos = self.code.len();
self.code.push(op);
}
fn emit_u8(&mut self, val: u8) {
self.code.push(val);
}
fn emit_u16(&mut self, val: u16) {
self.code.push((val >> 8) as u8);
self.code.push((val & 0xFF) as u8);
}
fn emit_i16(&mut self, val: i16) {
self.emit_u16(val as u16);
}
fn emit_u32(&mut self, val: u32) {
self.code.push((val >> 24) as u8);
self.code.push(((val >> 16) & 0xFF) as u8);
self.code.push(((val >> 8) & 0xFF) as u8);
self.code.push((val & 0xFF) as u8);
}
fn emit_u64(&mut self, val: u64) {
self.code.extend_from_slice(&val.to_be_bytes());
}
fn emit_i64(&mut self, val: i64) {
self.code.extend_from_slice(&val.to_be_bytes());
}
fn add_constant(&mut self, val: NanValue) -> u16 {
for (i, c) in self.constants.iter().enumerate() {
if c.bits() == val.bits() {
return i as u16;
}
}
let idx = self.constants.len() as u16;
self.constants.push(val);
idx
}
fn offset(&self) -> usize {
self.code.len()
}
fn emit_jump(&mut self, op: u8) -> usize {
self.emit_op(op);
let patch_pos = self.code.len();
self.emit_i16(0);
patch_pos
}
fn patch_jump(&mut self, patch_pos: usize) {
let target = self.code.len();
let offset = (target as isize - patch_pos as isize - 2) as i16;
let bytes = (offset as u16).to_be_bytes();
self.code[patch_pos] = bytes[0];
self.code[patch_pos + 1] = bytes[1];
}
fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
let offset = (target as isize - patch_pos as isize - 2) as i16;
let bytes = (offset as u16).to_be_bytes();
self.code[patch_pos] = bytes[0];
self.code[patch_pos + 1] = bytes[1];
}
fn bind_top_to_local(&mut self, name: &str) {
if let Some(&slot) = self.local_slots.get(name) {
self.emit_op(STORE_LOCAL);
self.emit_u8(slot as u8);
} else {
self.emit_op(POP);
}
}
fn dup_and_bind_top_to_local(&mut self, name: &str) {
self.emit_op(DUP);
self.bind_top_to_local(name);
}
pub(super) fn install_arm_slots(
&mut self,
arm: &crate::ast::MatchArm,
) -> Vec<(String, Option<u16>)> {
let names = collect_pattern_binding_names(&arm.pattern);
let slots = arm.binding_slots.get().cloned().unwrap_or_default();
let mut saved = Vec::new();
for (i, name) in names.iter().enumerate() {
if name == "_" {
continue;
}
let Some(&slot) = slots.get(i) else { continue };
if slot == u16::MAX {
continue;
}
saved.push((name.clone(), self.local_slots.get(name).copied()));
self.local_slots.insert(name.clone(), slot);
}
saved
}
pub(super) fn restore_local_slots(&mut self, saved: Vec<(String, Option<u16>)>) {
for (name, prior) in saved.into_iter().rev() {
match prior {
Some(slot) => {
self.local_slots.insert(name, slot);
}
None => {
self.local_slots.remove(&name);
}
}
}
}
}
fn collect_pattern_binding_names(pattern: &crate::ast::Pattern) -> Vec<String> {
use crate::ast::Pattern;
match pattern {
Pattern::Ident(name) => vec![name.clone()],
Pattern::Cons(head, tail) => vec![head.clone(), tail.clone()],
Pattern::Constructor(_, bindings) => bindings.clone(),
Pattern::Tuple(items) => items
.iter()
.flat_map(collect_pattern_binding_names)
.collect(),
Pattern::Wildcard | Pattern::Literal(_) | Pattern::EmptyList => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::compile_program;
use crate::nan_value::Arena;
use crate::source::parse_source;
use crate::vm::opcode::{LT, NOT, VECTOR_GET_OR, VECTOR_SET_OR_KEEP};
#[test]
fn vector_get_with_literal_default_lowers_to_vector_get_or() {
let source = r#"
module Demo
fn cellAt(grid: Vector<Int>, idx: Int) -> Int
Option.withDefault(Vector.get(grid, idx), 0)
"#;
let mut items = parse_source(source).expect("source should parse");
crate::ir::pipeline::tco(&mut items);
crate::ir::pipeline::resolve(&mut items);
let mut arena = Arena::new();
let (code, _globals) =
compile_program(&items, &mut arena, None).expect("vm compile should pass");
let fn_id = code.find("cellAt").expect("cellAt should exist");
let chunk = code.get(fn_id);
assert!(
chunk.code.contains(&VECTOR_GET_OR),
"expected VECTOR_GET_OR in bytecode, got {:?}",
chunk.code
);
}
#[test]
fn vector_set_with_same_default_lowers_to_vector_set_or_keep() {
let source = r#"
module Demo
fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
Option.withDefault(Vector.set(vec, idx, value), vec)
"#;
let mut items = parse_source(source).expect("source should parse");
crate::ir::pipeline::tco(&mut items);
crate::ir::pipeline::resolve(&mut items);
let mut arena = Arena::new();
let (code, _globals) =
compile_program(&items, &mut arena, None).expect("vm compile should pass");
let fn_id = code
.find("updateOrKeep")
.expect("updateOrKeep should exist");
let chunk = code.get(fn_id);
assert!(
chunk.code.contains(&VECTOR_SET_OR_KEEP),
"expected VECTOR_SET_OR_KEEP in bytecode, got {:?}",
chunk.code
);
}
#[test]
fn bool_match_on_gte_uses_base_compare_without_not() {
let source = r#"
module Demo
fn bucket(n: Int) -> Int
match n >= 10
true -> 7
false -> 3
"#;
let mut items = parse_source(source).expect("source should parse");
crate::ir::pipeline::tco(&mut items);
crate::ir::pipeline::resolve(&mut items);
let mut arena = Arena::new();
let (code, _globals) =
compile_program(&items, &mut arena, None).expect("vm compile should pass");
let fn_id = code.find("bucket").expect("bucket should exist");
let chunk = code.get(fn_id);
assert!(
chunk.code.contains(<),
"expected LT in bytecode, got {:?}",
chunk.code
);
assert!(
!chunk.code.contains(&NOT),
"did not expect NOT in normalized bool-match bytecode, got {:?}",
chunk.code
);
}
#[test]
fn self_host_runtime_http_server_aliases_compile_in_vm() {
let source = r#"
module Demo
fn listen(handler: Int) -> Unit
SelfHostRuntime.httpServerListen(8080, handler)
fn listenWith(context: Int, handler: Int) -> Unit
SelfHostRuntime.httpServerListenWith(8081, context, handler)
"#;
let mut items = parse_source(source).expect("source should parse");
crate::ir::pipeline::tco(&mut items);
crate::ir::pipeline::resolve(&mut items);
let mut arena = Arena::new();
let (code, _globals) =
compile_program(&items, &mut arena, None).expect("vm compile should pass");
assert!(code.find("listen").is_some(), "listen should compile");
assert!(
code.find("listenWith").is_some(),
"listenWith should compile"
);
}
}