Skip to main content

aver/vm/compiler/
mod.rs

1mod calls;
2mod classify;
3mod expr;
4mod patterns;
5
6use std::collections::HashMap;
7
8use crate::ast::{FnBody, FnDef, Stmt, TopLevel, TypeDef};
9use crate::nan_value::{Arena, NanValue};
10use crate::types::{option, result};
11use crate::visibility;
12
13use super::builtin::VmBuiltin;
14use super::opcode::*;
15use super::symbol::{VmSymbolTable, VmVariantCtor};
16use super::types::{CodeStore, FnChunk};
17
18/// Compile a parsed + TCO-transformed + resolved program into bytecode.
19/// Also loads dependent modules if a `module` declaration with `depends` is present.
20pub fn compile_program(
21    items: &[TopLevel],
22    arena: &mut Arena,
23) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
24    compile_program_with_modules(items, arena, None, "")
25}
26
27/// Compile with explicit module root for `depends` resolution.
28pub fn compile_program_with_modules(
29    items: &[TopLevel],
30    arena: &mut Arena,
31    module_root: Option<&str>,
32    source_file: &str,
33) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
34    compile_program_inner(items, arena, source_file, ModuleSource::Disk(module_root))
35}
36
37/// Compile using dependency modules that were already parsed off-disk
38/// (or out of a virtual filesystem). The browser playground uses this
39/// to run multi-file programs without any real fs access.
40pub fn compile_program_with_loaded_modules(
41    items: &[TopLevel],
42    arena: &mut Arena,
43    loaded: Vec<crate::source::LoadedModule>,
44    source_file: &str,
45) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
46    compile_program_inner(items, arena, source_file, ModuleSource::Loaded(loaded))
47}
48
49enum ModuleSource<'a> {
50    Disk(Option<&'a str>),
51    Loaded(Vec<crate::source::LoadedModule>),
52}
53
54fn compile_program_inner(
55    items: &[TopLevel],
56    arena: &mut Arena,
57    source_file: &str,
58    module_source: ModuleSource<'_>,
59) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
60    let mut compiler = ProgramCompiler::new();
61    compiler.source_file = source_file.to_string();
62    compiler.sync_record_field_symbols(arena);
63
64    match module_source {
65        ModuleSource::Disk(Some(module_root)) => {
66            compiler.load_modules(items, module_root, arena)?;
67        }
68        ModuleSource::Disk(None) => {}
69        ModuleSource::Loaded(loaded) => {
70            for m in loaded {
71                compiler.integrate_module(&m.dep_name, m.items, arena)?;
72            }
73        }
74    }
75
76    for item in items {
77        if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
78            compiler.ensure_global(name);
79        }
80    }
81
82    for item in items {
83        match item {
84            TopLevel::FnDef(fndef) => {
85                compiler.ensure_global(&fndef.name);
86                let effect_ids: Vec<u32> = fndef
87                    .effects
88                    .iter()
89                    .map(|effect| compiler.symbols.intern_name(&effect.node))
90                    .collect();
91                let fn_id = compiler.code.add_function(FnChunk {
92                    name: fndef.name.clone(),
93                    arity: fndef.params.len() as u8,
94                    local_count: 0,
95                    code: Vec::new(),
96                    constants: Vec::new(),
97                    effects: effect_ids,
98                    thin: false,
99                    parent_thin: false,
100                    leaf: false,
101                    source_file: String::new(),
102                    line_table: Vec::new(),
103                });
104                let symbol_id = compiler.symbols.intern_function(
105                    &fndef.name,
106                    fn_id,
107                    &fndef
108                        .effects
109                        .iter()
110                        .map(|e| e.node.clone())
111                        .collect::<Vec<_>>(),
112                );
113                let global_idx = compiler.global_names[&fndef.name];
114                compiler.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
115            }
116            TopLevel::TypeDef(td) => {
117                // Current module: register in Arena (no qualified alias needed)
118                match td {
119                    TypeDef::Product { name, fields, .. } => {
120                        let field_names: Vec<String> =
121                            fields.iter().map(|(n, _)| n.clone()).collect();
122                        arena.register_record_type(name, field_names);
123                    }
124                    TypeDef::Sum { name, variants, .. } => {
125                        let variant_names: Vec<String> =
126                            variants.iter().map(|v| v.name.clone()).collect();
127                        arena.register_sum_type(name, variant_names);
128                    }
129                }
130                // VM-specific: register type symbols
131                compiler.register_type_in_symbols(td, arena);
132            }
133            _ => {}
134        }
135    }
136
137    compiler.register_current_module_namespace(items);
138
139    for item in items {
140        if let TopLevel::FnDef(fndef) = item {
141            let fn_id = compiler.code.find(&fndef.name).unwrap();
142            let chunk = compiler.compile_fn(fndef, arena)?;
143            compiler.code.functions[fn_id as usize] = chunk;
144        }
145    }
146
147    compiler.compile_top_level(items, arena)?;
148    compiler.code.symbols = compiler.symbols.clone();
149    classify::classify_thin_functions(&mut compiler.code, arena)?;
150
151    Ok((compiler.code, compiler.globals))
152}
153
154#[derive(Debug)]
155pub struct CompileError {
156    pub msg: String,
157}
158
159impl std::fmt::Display for CompileError {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        write!(f, "Compile error: {}", self.msg)
162    }
163}
164
165struct ProgramCompiler {
166    code: CodeStore,
167    symbols: VmSymbolTable,
168    globals: Vec<NanValue>,
169    global_names: HashMap<String, u16>,
170    /// Source file path for the main program (propagated to FnChunks).
171    source_file: String,
172}
173
174impl ProgramCompiler {
175    fn new() -> Self {
176        let mut compiler = ProgramCompiler {
177            code: CodeStore::new(),
178            symbols: VmSymbolTable::default(),
179            globals: Vec::new(),
180            global_names: HashMap::new(),
181            source_file: String::new(),
182        };
183        compiler.bootstrap_core_symbols();
184        compiler
185    }
186
187    fn sync_record_field_symbols(&mut self, arena: &Arena) {
188        for type_id in 0..arena.type_count() {
189            let type_name = arena.get_type_name(type_id);
190            self.symbols.intern_namespace_path(type_name);
191            let field_names = arena.get_field_names(type_id);
192            if field_names.is_empty() {
193                continue;
194            }
195            let field_symbol_ids: Vec<u32> = field_names
196                .iter()
197                .map(|field_name| self.symbols.intern_name(field_name))
198                .collect();
199            self.code.register_record_fields(type_id, &field_symbol_ids);
200        }
201    }
202
203    /// Load all modules from `depends [...]` declarations using the shared loader,
204    /// then compile each module's functions and register symbols.
205    fn load_modules(
206        &mut self,
207        items: &[TopLevel],
208        module_root: &str,
209        arena: &mut Arena,
210    ) -> Result<(), CompileError> {
211        let module = items.iter().find_map(|i| {
212            if let TopLevel::Module(m) = i {
213                Some(m)
214            } else {
215                None
216            }
217        });
218        let module = match module {
219            Some(m) => m,
220            None => return Ok(()),
221        };
222
223        let modules = crate::source::load_module_tree(&module.depends, module_root)
224            .map_err(|e| CompileError { msg: e })?;
225
226        for loaded in modules {
227            self.integrate_module(&loaded.dep_name, loaded.items, arena)?;
228        }
229        Ok(())
230    }
231
232    /// Integrate a loaded module into the VM: register types, compile functions,
233    /// expose symbols.
234    fn integrate_module(
235        &mut self,
236        dep_name: &str,
237        mut mod_items: Vec<TopLevel>,
238        arena: &mut Arena,
239    ) -> Result<(), CompileError> {
240        crate::tco::transform_program(&mut mod_items);
241        crate::resolver::resolve_program(&mut mod_items);
242
243        // Register types in Arena with qualified aliases.
244        for mt in visibility::collect_module_types(&mod_items) {
245            let type_id = match &mt.kind {
246                visibility::ModuleTypeKind::Record { field_names } => {
247                    arena.register_record_type(&mt.bare_name, field_names.clone())
248                }
249                visibility::ModuleTypeKind::Sum { variant_names } => {
250                    arena.register_sum_type(&mt.bare_name, variant_names.clone())
251                }
252            };
253            arena.register_type_alias(
254                &visibility::qualified_name(dep_name, &mt.bare_name),
255                type_id,
256            );
257        }
258        for item in &mod_items {
259            if let TopLevel::TypeDef(td) = item {
260                self.register_type_in_symbols(td, arena);
261            }
262        }
263
264        // Compile ALL functions (not just exposed).
265        let mut module_fn_ids: Vec<(String, u32)> = Vec::new();
266        for item in &mod_items {
267            if let TopLevel::FnDef(fndef) = item {
268                let qualified_name = visibility::qualified_name(dep_name, &fndef.name);
269                let effect_ids: Vec<u32> = fndef
270                    .effects
271                    .iter()
272                    .map(|effect| self.symbols.intern_name(&effect.node))
273                    .collect();
274                let fn_id = self.code.add_function(FnChunk {
275                    name: qualified_name.clone(),
276                    arity: fndef.params.len() as u8,
277                    local_count: 0,
278                    code: Vec::new(),
279                    constants: Vec::new(),
280                    effects: effect_ids,
281                    thin: false,
282                    parent_thin: false,
283                    leaf: false,
284                    source_file: String::new(),
285                    line_table: Vec::new(),
286                });
287                self.symbols.intern_function(
288                    &qualified_name,
289                    fn_id,
290                    &fndef
291                        .effects
292                        .iter()
293                        .map(|e| e.node.clone())
294                        .collect::<Vec<_>>(),
295                );
296                module_fn_ids.push((fndef.name.clone(), fn_id));
297            }
298        }
299
300        let module_scope: HashMap<String, u32> = module_fn_ids.iter().cloned().collect();
301        let mut fn_idx = 0;
302        for item in &mod_items {
303            if let TopLevel::FnDef(fndef) = item {
304                let (fn_name, fn_id) = &module_fn_ids[fn_idx];
305                let mut chunk = self.compile_fn_with_scope(fndef, arena, &module_scope)?;
306                chunk.name = visibility::qualified_name(dep_name, fn_name);
307                self.code.functions[*fn_id as usize] = chunk;
308                fn_idx += 1;
309            }
310        }
311
312        // Expose exported functions and types via globals and namespace members.
313        let exports = visibility::collect_module_exports(&mod_items);
314
315        for fd in &exports.functions {
316            let qualified = visibility::qualified_name(dep_name, &fd.name);
317            let global_idx = self.ensure_global(&qualified);
318            let symbol_id = self.symbols.find(&qualified).ok_or_else(|| CompileError {
319                msg: format!("missing VM symbol for exposed function {}", qualified),
320            })?;
321            self.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
322        }
323
324        let module_symbol_id = self.symbols.intern_namespace_path(dep_name);
325        for et in &exports.types {
326            let type_name = match et.def {
327                TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
328            };
329            if let Some(type_symbol_id) = self.symbols.find(type_name) {
330                let member_symbol_id = self.symbols.intern_name(type_name);
331                self.symbols.add_namespace_member_by_id(
332                    module_symbol_id,
333                    member_symbol_id,
334                    VmSymbolTable::symbol_ref(type_symbol_id),
335                );
336            }
337        }
338        for fd in &exports.functions {
339            let qualified = visibility::qualified_name(dep_name, &fd.name);
340            if let Some(fn_symbol_id) = self.symbols.find(&qualified) {
341                let member_symbol_id = self.symbols.intern_name(&fd.name);
342                self.symbols.add_namespace_member_by_id(
343                    module_symbol_id,
344                    member_symbol_id,
345                    VmSymbolTable::symbol_ref(fn_symbol_id),
346                );
347            }
348        }
349
350        Ok(())
351    }
352
353    fn ensure_global(&mut self, name: &str) -> u16 {
354        if let Some(&idx) = self.global_names.get(name) {
355            return idx;
356        }
357        let idx = self.globals.len() as u16;
358        self.global_names.insert(name.to_string(), idx);
359        self.globals.push(NanValue::UNIT);
360        idx
361    }
362
363    /// Register type symbols in VmSymbolTable for namespace resolution.
364    /// Arena registration is handled separately via shared `collect_module_types`.
365    fn register_type_in_symbols(&mut self, td: &TypeDef, arena: &Arena) {
366        match td {
367            TypeDef::Product { name, fields, .. } => {
368                self.symbols.intern_namespace_path(name);
369                let type_id = arena
370                    .find_type_id(name)
371                    .expect("type already registered in Arena");
372                let field_symbol_ids: Vec<u32> = fields
373                    .iter()
374                    .map(|(field_name, _)| self.symbols.intern_name(field_name))
375                    .collect();
376                self.code.register_record_fields(type_id, &field_symbol_ids);
377            }
378            TypeDef::Sum { name, variants, .. } => {
379                let type_symbol_id = self.symbols.intern_namespace_path(name);
380                let type_id = arena
381                    .find_type_id(name)
382                    .expect("type already registered in Arena");
383                for (variant_id, variant) in variants.iter().enumerate() {
384                    let ctor_id = arena
385                        .find_ctor_id(type_id, variant_id as u16)
386                        .expect("ctor id");
387                    let qualified_name = visibility::member_key(name, &variant.name);
388                    let ctor_symbol_id = self.symbols.intern_variant_ctor(
389                        &qualified_name,
390                        VmVariantCtor {
391                            type_id,
392                            variant_id: variant_id as u16,
393                            ctor_id,
394                            field_count: variant.fields.len() as u8,
395                        },
396                    );
397                    let member_symbol_id = self.symbols.intern_name(&variant.name);
398                    self.symbols.add_namespace_member_by_id(
399                        type_symbol_id,
400                        member_symbol_id,
401                        VmSymbolTable::symbol_ref(ctor_symbol_id),
402                    );
403                }
404            }
405        }
406    }
407
408    fn bootstrap_core_symbols(&mut self) {
409        for builtin in VmBuiltin::ALL.iter().copied() {
410            let builtin_symbol_id = self.symbols.intern_builtin(builtin);
411            if let Some((namespace, member)) = builtin.name().split_once('.') {
412                let namespace_symbol_id = self.symbols.intern_namespace_path(namespace);
413                let member_symbol_id = self.symbols.intern_name(member);
414                self.symbols.add_namespace_member_by_id(
415                    namespace_symbol_id,
416                    member_symbol_id,
417                    VmSymbolTable::symbol_ref(builtin_symbol_id),
418                );
419            }
420        }
421
422        let result_symbol_id = self.symbols.intern_namespace_path("Result");
423        let ok_symbol_id = self.symbols.intern_wrapper("Result.Ok", 0);
424        let err_symbol_id = self.symbols.intern_wrapper("Result.Err", 1);
425        let ok_member_symbol_id = self.symbols.intern_name("Ok");
426        self.symbols.add_namespace_member_by_id(
427            result_symbol_id,
428            ok_member_symbol_id,
429            VmSymbolTable::symbol_ref(ok_symbol_id),
430        );
431        let err_member_symbol_id = self.symbols.intern_name("Err");
432        self.symbols.add_namespace_member_by_id(
433            result_symbol_id,
434            err_member_symbol_id,
435            VmSymbolTable::symbol_ref(err_symbol_id),
436        );
437        for (member, builtin_name) in result::extra_members() {
438            if let Some(symbol_id) = self.symbols.find(&builtin_name) {
439                let member_symbol_id = self.symbols.intern_name(member);
440                self.symbols.add_namespace_member_by_id(
441                    result_symbol_id,
442                    member_symbol_id,
443                    VmSymbolTable::symbol_ref(symbol_id),
444                );
445            }
446        }
447
448        let option_symbol_id = self.symbols.intern_namespace_path("Option");
449        let some_symbol_id = self.symbols.intern_wrapper("Option.Some", 2);
450        self.symbols.intern_constant("Option.None", NanValue::NONE);
451        let some_member_symbol_id = self.symbols.intern_name("Some");
452        self.symbols.add_namespace_member_by_id(
453            option_symbol_id,
454            some_member_symbol_id,
455            VmSymbolTable::symbol_ref(some_symbol_id),
456        );
457        let none_member_symbol_id = self.symbols.intern_name("None");
458        self.symbols.add_namespace_member_by_id(
459            option_symbol_id,
460            none_member_symbol_id,
461            NanValue::NONE,
462        );
463        for (member, builtin_name) in option::extra_members() {
464            if let Some(symbol_id) = self.symbols.find(&builtin_name) {
465                let member_symbol_id = self.symbols.intern_name(member);
466                self.symbols.add_namespace_member_by_id(
467                    option_symbol_id,
468                    member_symbol_id,
469                    VmSymbolTable::symbol_ref(symbol_id),
470                );
471            }
472        }
473    }
474
475    fn compile_fn(&mut self, fndef: &FnDef, arena: &mut Arena) -> Result<FnChunk, CompileError> {
476        let empty_scope = HashMap::new();
477        self.compile_fn_with_scope(fndef, arena, &empty_scope)
478    }
479
480    fn compile_fn_with_scope(
481        &mut self,
482        fndef: &FnDef,
483        arena: &mut Arena,
484        module_scope: &HashMap<String, u32>,
485    ) -> Result<FnChunk, CompileError> {
486        let resolution = fndef.resolution.as_ref();
487        let local_count = resolution.map_or(fndef.params.len() as u16, |r| r.local_count);
488        let local_slots: HashMap<String, u16> = resolution
489            .map(|r| r.local_slots.as_ref().clone())
490            .unwrap_or_else(|| {
491                fndef
492                    .params
493                    .iter()
494                    .enumerate()
495                    .map(|(i, (name, _))| (name.clone(), i as u16))
496                    .collect()
497            });
498
499        let mut fc = FnCompiler::new(
500            &fndef.name,
501            fndef.params.len() as u8,
502            local_count,
503            fndef
504                .effects
505                .iter()
506                .map(|effect| self.symbols.intern_name(&effect.node))
507                .collect(),
508            local_slots,
509            &self.global_names,
510            module_scope,
511            &self.code,
512            &mut self.symbols,
513            arena,
514        );
515        fc.source_file = self.source_file.clone();
516        fc.note_line(fndef.line);
517
518        match fndef.body.as_ref() {
519            FnBody::Block(stmts) => fc.compile_body(stmts)?,
520        }
521
522        Ok(fc.finish())
523    }
524
525    fn compile_top_level(
526        &mut self,
527        items: &[TopLevel],
528        arena: &mut Arena,
529    ) -> Result<(), CompileError> {
530        let has_stmts = items.iter().any(|i| matches!(i, TopLevel::Stmt(_)));
531        if !has_stmts {
532            return Ok(());
533        }
534
535        for item in items {
536            if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
537                self.ensure_global(name);
538            }
539        }
540
541        let empty_mod_scope = HashMap::new();
542        let mut fc = FnCompiler::new(
543            "__top_level__",
544            0,
545            0,
546            Vec::new(),
547            HashMap::new(),
548            &self.global_names,
549            &empty_mod_scope,
550            &self.code,
551            &mut self.symbols,
552            arena,
553        );
554
555        for item in items {
556            if let TopLevel::Stmt(stmt) = item {
557                match stmt {
558                    Stmt::Binding(name, _type_ann, expr) => {
559                        fc.compile_expr(expr)?;
560                        let idx = self.global_names[name.as_str()];
561                        fc.emit_op(STORE_GLOBAL);
562                        fc.emit_u16(idx);
563                    }
564                    Stmt::Expr(expr) => {
565                        fc.compile_expr(expr)?;
566                        fc.emit_op(POP);
567                    }
568                }
569            }
570        }
571
572        fc.emit_op(LOAD_UNIT);
573        fc.emit_op(RETURN);
574
575        let chunk = fc.finish();
576        self.code.add_function(chunk);
577        Ok(())
578    }
579
580    fn register_current_module_namespace(&mut self, items: &[TopLevel]) {
581        let Some(module) = items.iter().find_map(|item| {
582            if let TopLevel::Module(module) = item {
583                Some(module)
584            } else {
585                None
586            }
587        }) else {
588            return;
589        };
590
591        let module_symbol_id = self.symbols.intern_namespace_path(&module.name);
592        let exposes_ref = if module.exposes.is_empty() {
593            None
594        } else {
595            Some(module.exposes.as_slice())
596        };
597
598        for item in items {
599            match item {
600                TopLevel::FnDef(fndef) => {
601                    if visibility::is_exposed(&fndef.name, exposes_ref)
602                        && let Some(symbol_id) = self.symbols.find(&fndef.name)
603                    {
604                        let member_symbol_id = self.symbols.intern_name(&fndef.name);
605                        self.symbols.add_namespace_member_by_id(
606                            module_symbol_id,
607                            member_symbol_id,
608                            VmSymbolTable::symbol_ref(symbol_id),
609                        );
610                    }
611                }
612                TopLevel::TypeDef(TypeDef::Product { name, .. } | TypeDef::Sum { name, .. }) => {
613                    if visibility::is_exposed(name, exposes_ref)
614                        && let Some(symbol_id) = self.symbols.find(name)
615                    {
616                        let member_symbol_id = self.symbols.intern_name(name);
617                        self.symbols.add_namespace_member_by_id(
618                            module_symbol_id,
619                            member_symbol_id,
620                            VmSymbolTable::symbol_ref(symbol_id),
621                        );
622                    }
623                }
624                _ => {}
625            }
626        }
627    }
628}
629
630/// What a function expression resolves to at compile time.
631enum CallTarget {
632    /// Known function id (local or qualified module function).
633    KnownFn(u32),
634    /// Result.Ok / Result.Err / Option.Some → WRAP opcode. kind: 0=Ok, 1=Err, 2=Some.
635    Wrapper(u8),
636    /// Option.None → load constant.
637    None_,
638    /// User-defined variant constructor: Shape.Circle → VARIANT_NEW (or inline nullary at runtime).
639    Variant(u32, u16),
640    /// Known VM builtin/service resolved by name and interned into the VM symbol table.
641    Builtin(VmBuiltin),
642    /// Unknown capitalized dotted path that did not resolve to a function, variant, or builtin.
643    UnknownQualified(String),
644}
645
646struct FnCompiler<'a> {
647    name: String,
648    arity: u8,
649    local_count: u16,
650    effects: Vec<u32>,
651    local_slots: HashMap<String, u16>,
652    global_names: &'a HashMap<String, u16>,
653    /// Module-local function scope: simple_name → fn_id.
654    /// Used for intra-module calls (e.g. `placeStairs` inside map.av).
655    module_scope: &'a HashMap<String, u32>,
656    code_store: &'a CodeStore,
657    symbols: &'a mut VmSymbolTable,
658    arena: &'a mut Arena,
659    code: Vec<u8>,
660    constants: Vec<NanValue>,
661    /// Byte offset of the last emitted opcode (for superinstruction fusion).
662    last_op_pos: usize,
663    /// Source file path for this function.
664    source_file: String,
665    /// Run-length encoded line table being built: (bytecode_offset, source_line).
666    line_table: Vec<(u16, u16)>,
667    /// Last emitted line (for RLE dedup).
668    last_noted_line: u16,
669}
670
671impl<'a> FnCompiler<'a> {
672    #[allow(clippy::too_many_arguments)]
673    fn new(
674        name: &str,
675        arity: u8,
676        local_count: u16,
677        effects: Vec<u32>,
678        local_slots: HashMap<String, u16>,
679        global_names: &'a HashMap<String, u16>,
680        module_scope: &'a HashMap<String, u32>,
681        code_store: &'a CodeStore,
682        symbols: &'a mut VmSymbolTable,
683        arena: &'a mut Arena,
684    ) -> Self {
685        FnCompiler {
686            name: name.to_string(),
687            arity,
688            local_count,
689            effects,
690            local_slots,
691            global_names,
692            module_scope,
693            code_store,
694            symbols,
695            arena,
696            code: Vec::new(),
697            constants: Vec::new(),
698            last_op_pos: usize::MAX,
699            source_file: String::new(),
700            line_table: Vec::new(),
701            last_noted_line: 0,
702        }
703    }
704
705    fn finish(self) -> FnChunk {
706        FnChunk {
707            name: self.name,
708            arity: self.arity,
709            local_count: self.local_count,
710            code: self.code,
711            constants: self.constants,
712            effects: self.effects,
713            thin: false,
714            parent_thin: false,
715            leaf: false,
716            source_file: self.source_file,
717            line_table: self.line_table,
718        }
719    }
720
721    /// Record that bytecode emitted from this point forward corresponds to
722    /// the given source line. RLE-deduplicated: consecutive calls with the
723    /// same line produce only one entry.
724    fn note_line(&mut self, line: usize) {
725        if line == 0 {
726            return;
727        }
728        let line16 = line as u16;
729        if line16 == self.last_noted_line {
730            return; // RLE dedup
731        }
732        self.last_noted_line = line16;
733        self.line_table.push((self.code.len() as u16, line16));
734    }
735
736    fn emit_op(&mut self, op: u8) {
737        let prev_pos = self.last_op_pos;
738        let prev_op = if prev_pos < self.code.len() {
739            self.code[prev_pos]
740        } else {
741            0xFF
742        };
743
744        // LOAD_LOCAL + LOAD_LOCAL → LOAD_LOCAL_2
745        if op == LOAD_LOCAL && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
746            self.code[prev_pos] = LOAD_LOCAL_2;
747            // slot_a already at prev_pos+1, slot_b emitted next via emit_u8
748            return;
749        }
750        // LOAD_LOCAL + LOAD_CONST → LOAD_LOCAL_CONST
751        if op == LOAD_CONST && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
752            self.code[prev_pos] = LOAD_LOCAL_CONST;
753            // slot at prev_pos+1, const_idx (u16) emitted next via emit_u16
754            return;
755        }
756        // VECTOR_GET + LOAD_CONST(hi,lo) + UNWRAP_OR → VECTOR_GET_OR(hi,lo)
757        // Before: [..., VECTOR_GET, LOAD_CONST, hi, lo] + about to emit UNWRAP_OR
758        // After:  [..., VECTOR_GET_OR, hi, lo]
759        if op == UNWRAP_OR && self.code.len() >= 4 {
760            let len = self.code.len();
761            if self.code[len - 4] == VECTOR_GET && self.code[len - 3] == LOAD_CONST {
762                let hi = self.code[len - 2];
763                let lo = self.code[len - 1];
764                self.code[len - 4] = VECTOR_GET_OR;
765                self.code[len - 3] = hi;
766                self.code[len - 2] = lo;
767                self.code.pop(); // remove extra byte
768                self.last_op_pos = len - 4;
769                return;
770            }
771        }
772        self.last_op_pos = self.code.len();
773        self.code.push(op);
774    }
775
776    fn emit_u8(&mut self, val: u8) {
777        self.code.push(val);
778    }
779
780    fn emit_u16(&mut self, val: u16) {
781        self.code.push((val >> 8) as u8);
782        self.code.push((val & 0xFF) as u8);
783    }
784
785    fn emit_i16(&mut self, val: i16) {
786        self.emit_u16(val as u16);
787    }
788
789    fn emit_u32(&mut self, val: u32) {
790        self.code.push((val >> 24) as u8);
791        self.code.push(((val >> 16) & 0xFF) as u8);
792        self.code.push(((val >> 8) & 0xFF) as u8);
793        self.code.push((val & 0xFF) as u8);
794    }
795
796    fn emit_u64(&mut self, val: u64) {
797        self.code.extend_from_slice(&val.to_be_bytes());
798    }
799
800    fn add_constant(&mut self, val: NanValue) -> u16 {
801        for (i, c) in self.constants.iter().enumerate() {
802            if c.bits() == val.bits() {
803                return i as u16;
804            }
805        }
806        let idx = self.constants.len() as u16;
807        self.constants.push(val);
808        idx
809    }
810
811    fn offset(&self) -> usize {
812        self.code.len()
813    }
814
815    fn emit_jump(&mut self, op: u8) -> usize {
816        self.emit_op(op);
817        let patch_pos = self.code.len();
818        self.emit_i16(0);
819        patch_pos
820    }
821
822    fn patch_jump(&mut self, patch_pos: usize) {
823        let target = self.code.len();
824        let offset = (target as isize - patch_pos as isize - 2) as i16;
825        let bytes = (offset as u16).to_be_bytes();
826        self.code[patch_pos] = bytes[0];
827        self.code[patch_pos + 1] = bytes[1];
828    }
829
830    fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
831        let offset = (target as isize - patch_pos as isize - 2) as i16;
832        let bytes = (offset as u16).to_be_bytes();
833        self.code[patch_pos] = bytes[0];
834        self.code[patch_pos + 1] = bytes[1];
835    }
836
837    fn bind_top_to_local(&mut self, name: &str) {
838        if let Some(&slot) = self.local_slots.get(name) {
839            self.emit_op(STORE_LOCAL);
840            self.emit_u8(slot as u8);
841        } else {
842            self.emit_op(POP);
843        }
844    }
845
846    fn dup_and_bind_top_to_local(&mut self, name: &str) {
847        self.emit_op(DUP);
848        self.bind_top_to_local(name);
849    }
850}
851
852#[cfg(test)]
853mod tests {
854    use super::compile_program;
855    use crate::nan_value::Arena;
856    use crate::source::parse_source;
857    use crate::vm::opcode::{LT, NOT, VECTOR_GET_OR, VECTOR_SET_OR_KEEP};
858
859    #[test]
860    fn vector_get_with_literal_default_lowers_to_vector_get_or() {
861        let source = r#"
862module Demo
863
864fn cellAt(grid: Vector<Int>, idx: Int) -> Int
865    Option.withDefault(Vector.get(grid, idx), 0)
866"#;
867
868        let mut items = parse_source(source).expect("source should parse");
869        crate::tco::transform_program(&mut items);
870        crate::resolver::resolve_program(&mut items);
871
872        let mut arena = Arena::new();
873        let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
874        let fn_id = code.find("cellAt").expect("cellAt should exist");
875        let chunk = code.get(fn_id);
876
877        assert!(
878            chunk.code.contains(&VECTOR_GET_OR),
879            "expected VECTOR_GET_OR in bytecode, got {:?}",
880            chunk.code
881        );
882    }
883
884    #[test]
885    fn vector_set_with_same_default_lowers_to_vector_set_or_keep() {
886        let source = r#"
887module Demo
888
889fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
890    Option.withDefault(Vector.set(vec, idx, value), vec)
891"#;
892
893        let mut items = parse_source(source).expect("source should parse");
894        crate::tco::transform_program(&mut items);
895        crate::resolver::resolve_program(&mut items);
896
897        let mut arena = Arena::new();
898        let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
899        let fn_id = code
900            .find("updateOrKeep")
901            .expect("updateOrKeep should exist");
902        let chunk = code.get(fn_id);
903
904        assert!(
905            chunk.code.contains(&VECTOR_SET_OR_KEEP),
906            "expected VECTOR_SET_OR_KEEP in bytecode, got {:?}",
907            chunk.code
908        );
909    }
910
911    #[test]
912    fn bool_match_on_gte_uses_base_compare_without_not() {
913        let source = r#"
914module Demo
915
916fn bucket(n: Int) -> Int
917    match n >= 10
918        true -> 7
919        false -> 3
920"#;
921
922        let mut items = parse_source(source).expect("source should parse");
923        crate::tco::transform_program(&mut items);
924        crate::resolver::resolve_program(&mut items);
925
926        let mut arena = Arena::new();
927        let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
928        let fn_id = code.find("bucket").expect("bucket should exist");
929        let chunk = code.get(fn_id);
930
931        assert!(
932            chunk.code.contains(&LT),
933            "expected LT in bytecode, got {:?}",
934            chunk.code
935        );
936        assert!(
937            !chunk.code.contains(&NOT),
938            "did not expect NOT in normalized bool-match bytecode, got {:?}",
939            chunk.code
940        );
941    }
942
943    #[test]
944    fn self_host_runtime_http_server_aliases_compile_in_vm() {
945        let source = r#"
946module Demo
947
948fn listen(handler: Int) -> Unit
949    SelfHostRuntime.httpServerListen(8080, handler)
950
951fn listenWith(context: Int, handler: Int) -> Unit
952    SelfHostRuntime.httpServerListenWith(8081, context, handler)
953"#;
954
955        let mut items = parse_source(source).expect("source should parse");
956        crate::tco::transform_program(&mut items);
957        crate::resolver::resolve_program(&mut items);
958
959        let mut arena = Arena::new();
960        let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
961        assert!(code.find("listen").is_some(), "listen should compile");
962        assert!(
963            code.find("listenWith").is_some(),
964            "listenWith should compile"
965        );
966    }
967}