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