Skip to main content

aver/vm/compiler/
mod.rs

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