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