1mod classify;
2mod expr;
3pub mod mir;
4mod resolve_helpers;
5
6use std::collections::HashMap;
7
8use crate::ast::{Stmt, TopLevel, TypeDef};
9use crate::ir::SymbolTable;
10use crate::ir::hir::{
11 ResolveCtx, ResolvedFnDef, ResolvedStmt, ResolvedTopLevel, resolve_top_level,
12};
13use crate::nan_value::{Arena, NanValue};
14use crate::types::{option, result};
15use crate::visibility;
16
17use super::builtin::VmBuiltin;
18use super::opcode::*;
19use super::symbol::{VmSymbolTable, VmVariantCtor};
20use super::types::{CodeStore, FnChunk};
21
22pub fn compile_program_with_modules(
36 items: &[ResolvedTopLevel],
37 symbols: &SymbolTable,
38 arena: &mut Arena,
39 module_root: Option<&str>,
40 source_file: &str,
41 analysis: Option<&crate::ir::AnalysisResult>,
42) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
43 compile_program_inner(
44 items,
45 symbols,
46 arena,
47 source_file,
48 ModuleSource::Disk(module_root),
49 analysis,
50 )
51}
52
53pub fn compile_program_with_loaded_modules(
57 items: &[ResolvedTopLevel],
58 symbols: &SymbolTable,
59 arena: &mut Arena,
60 loaded: Vec<crate::source::LoadedModule>,
61 source_file: &str,
62 analysis: Option<&crate::ir::AnalysisResult>,
63) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
64 compile_program_inner(
65 items,
66 symbols,
67 arena,
68 source_file,
69 ModuleSource::Loaded(loaded),
70 analysis,
71 )
72}
73
74pub fn compile_program_with_mir_fallback(
87 items: &[ResolvedTopLevel],
88 symbols: &SymbolTable,
89 arena: &mut Arena,
90 analysis: Option<&crate::ir::AnalysisResult>,
91) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
92 compile_program_inner(
98 items,
99 symbols,
100 arena,
101 "",
102 ModuleSource::Disk(None),
103 analysis,
104 )
105}
106
107enum ModuleSource<'a> {
108 Disk(Option<&'a str>),
109 Loaded(Vec<crate::source::LoadedModule>),
110}
111
112fn build_optimized_mir(
121 items: &[ResolvedTopLevel],
122 external_callers_possible: bool,
123) -> crate::ir::mir::MirProgram {
124 let mut lowered = crate::ir::mir::lower_program(items);
125 lowered.external_callers_possible = external_callers_possible;
131 crate::ir::mir::optimize(lowered)
132}
133
134fn compile_program_inner(
135 items: &[ResolvedTopLevel],
136 symbols: &SymbolTable,
137 arena: &mut Arena,
138 source_file: &str,
139 module_source: ModuleSource<'_>,
140 analysis: Option<&crate::ir::AnalysisResult>,
141) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
142 let mir_built = build_optimized_mir(items, false);
157 let mir_program = &mir_built;
158
159 let mut compiler = ProgramCompiler::new();
160 compiler.source_file = source_file.to_string();
161 compiler.sync_record_field_symbols(arena)?;
162 compiler.install_branch_path_root_constant(arena)?;
168
169 match module_source {
170 ModuleSource::Disk(Some(module_root)) => {
171 compiler.load_modules(items, module_root, symbols, arena)?;
172 }
173 ModuleSource::Disk(None) => {}
174 ModuleSource::Loaded(loaded) => {
175 for m in loaded {
176 compiler.integrate_module(&m.dep_name, m.items, symbols, arena)?;
177 }
178 }
179 }
180
181 for item in items {
182 if let ResolvedTopLevel::Passthrough(TopLevel::Stmt(Stmt::Binding(name, _, _))) = item {
183 compiler.ensure_global(name);
184 }
185 }
186
187 for item in items {
188 match item {
189 ResolvedTopLevel::FnDef(rfd) => {
190 compiler.ensure_global(&rfd.name);
191 let effect_ids: Vec<u32> = rfd
192 .effects
193 .iter()
194 .map(|effect| compiler.symbols.intern_name(&effect.node))
195 .collect();
196 let fn_id = compiler.code.add_function(FnChunk {
197 name: rfd.name.clone(),
198 arity: rfd.params.len() as u8,
199 local_count: 0,
200 code: Vec::new(),
201 constants: Vec::new(),
202 effects: effect_ids,
203 thin: false,
204 parent_thin: false,
205 leaf: false,
206 no_alloc: false,
207 source_file: String::new(),
208 line_table: Vec::new(),
209 });
210 let symbol_id = compiler.symbols.intern_function(
211 &rfd.name,
212 fn_id,
213 &rfd.effects
214 .iter()
215 .map(|e| e.node.clone())
216 .collect::<Vec<_>>(),
217 )?;
218 let global_idx = compiler.global_names[&rfd.name];
219 compiler.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
220 }
221 ResolvedTopLevel::Passthrough(TopLevel::TypeDef(td)) => {
222 match td {
224 TypeDef::Product { name, fields, .. } => {
225 let field_names: Vec<String> =
226 fields.iter().map(|(n, _)| n.clone()).collect();
227 arena.register_record_type(name, field_names);
228 }
229 TypeDef::Sum { name, variants, .. } => {
230 let variant_names: Vec<String> =
231 variants.iter().map(|v| v.name.clone()).collect();
232 arena.register_sum_type(name, variant_names);
233 }
234 }
235 compiler.register_type_in_symbols(td, arena)?;
237 }
238 _ => {}
239 }
240 }
241
242 compiler.register_current_module_namespace(items)?;
243
244 for item in items {
245 if let ResolvedTopLevel::FnDef(rfd) = item {
246 let fn_id = compiler.code.find(&rfd.name).unwrap();
247 let entry_scope = HashMap::new();
255 let mir_fn = mir_program.fn_by_id(rfd.fn_id).ok_or_else(|| CompileError {
256 msg: format!(
257 "internal error: fn `{}` did not lower to MIR (an unsupported shape reached the VM backend)",
258 rfd.name
259 ),
260 })?;
261 let chunk = compiler
262 .compile_fn_via_mir(rfd, mir_fn, symbols, arena, &entry_scope, mir_program)
263 .map_err(|e| e.into_compile_error(&format!("fn `{}`", rfd.name)))?;
264 compiler.code.functions[fn_id as usize] = chunk;
265 }
266 }
267
268 compiler.compile_top_level(items, symbols, arena, mir_program)?;
269 compiler.code.symbols = compiler.symbols.clone();
270 classify::classify_thin_functions(&mut compiler.code, arena)?;
271
272 let allocates = |name: &str| -> bool {
281 if let Some(a) = analysis
282 && let Some(fa) = a.fn_analyses.get(name)
283 && let Some(b) = fa.allocates
284 {
285 return b;
286 }
287 true
288 };
289 for item in items {
290 if let ResolvedTopLevel::FnDef(rfd) = item
291 && !allocates(&rfd.name)
292 && let Some(fn_id) = compiler.code.find(&rfd.name)
293 {
294 let chunk = &mut compiler.code.functions[fn_id as usize];
295 chunk.no_alloc = true;
296 chunk.thin = true;
303 }
304 }
305
306 Ok((compiler.code, compiler.globals))
307}
308
309#[derive(Debug)]
310pub struct CompileError {
311 pub msg: String,
312}
313
314impl std::fmt::Display for CompileError {
315 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316 write!(f, "Compile error: {}", self.msg)
317 }
318}
319
320impl From<crate::vm::symbol::SymbolError> for CompileError {
325 fn from(err: crate::vm::symbol::SymbolError) -> Self {
326 CompileError {
327 msg: err.to_string(),
328 }
329 }
330}
331
332struct ProgramCompiler {
333 code: CodeStore,
334 symbols: VmSymbolTable,
335 globals: Vec<NanValue>,
336 global_names: HashMap<String, u16>,
337 source_file: String,
339}
340
341impl ProgramCompiler {
342 fn new() -> Self {
343 let mut compiler = ProgramCompiler {
344 code: CodeStore::new(),
345 symbols: VmSymbolTable::default(),
346 globals: Vec::new(),
347 global_names: HashMap::new(),
348 source_file: String::new(),
349 };
350 compiler
355 .bootstrap_core_symbols()
356 .expect("bootstrap_core_symbols on empty VmSymbolTable cannot fail");
357 compiler
358 }
359
360 fn sync_record_field_symbols(&mut self, arena: &Arena) -> Result<(), CompileError> {
361 for type_id in 0..arena.type_count() {
362 let type_name = arena.get_type_name(type_id);
363 self.symbols.intern_namespace_path(type_name)?;
364 let field_names = arena.get_field_names(type_id);
365 if field_names.is_empty() {
366 continue;
367 }
368 let field_symbol_ids: Vec<u32> = field_names
369 .iter()
370 .map(|field_name| self.symbols.intern_name(field_name))
371 .collect();
372 self.code.register_record_fields(type_id, &field_symbol_ids);
373 }
374 Ok(())
375 }
376
377 fn load_modules(
380 &mut self,
381 items: &[ResolvedTopLevel],
382 module_root: &str,
383 entry_symbols: &SymbolTable,
384 arena: &mut Arena,
385 ) -> Result<(), CompileError> {
386 let module = items.iter().find_map(|i| match i {
387 ResolvedTopLevel::Module(m) => Some(m),
388 _ => None,
389 });
390 let module = match module {
391 Some(m) => m,
392 None => return Ok(()),
393 };
394
395 let modules = crate::source::load_module_tree(&module.depends, module_root)
396 .map_err(|e| CompileError { msg: e })?;
397
398 for loaded in modules {
399 self.integrate_module(&loaded.dep_name, loaded.items, entry_symbols, arena)?;
400 }
401 Ok(())
402 }
403
404 fn integrate_module(
415 &mut self,
416 dep_name: &str,
417 mut mod_items: Vec<TopLevel>,
418 entry_symbols: &SymbolTable,
419 arena: &mut Arena,
420 ) -> Result<(), CompileError> {
421 crate::ir::pipeline::tco(&mut mod_items);
428 crate::ir::pipeline::resolve(&mut mod_items);
429
430 for mt in visibility::collect_module_types(&mod_items) {
432 let type_id = match &mt.kind {
433 visibility::ModuleTypeKind::Record { field_names } => {
434 arena.register_record_type(&mt.bare_name, field_names.clone())
435 }
436 visibility::ModuleTypeKind::Sum { variant_names } => {
437 arena.register_sum_type(&mt.bare_name, variant_names.clone())
438 }
439 };
440 arena.register_type_alias(
441 &visibility::qualified_name(dep_name, &mt.bare_name),
442 type_id,
443 );
444 }
445 for item in &mod_items {
446 if let TopLevel::TypeDef(td) = item {
447 self.register_type_in_symbols(td, arena)?;
448 }
449 }
450
451 let mut ctx = ResolveCtx::new(entry_symbols);
460 ctx.current_module = Some(dep_name.to_string());
461 let dep_resolved: Vec<ResolvedTopLevel> = mod_items
462 .iter()
463 .map(|i| resolve_top_level(&ctx, i))
464 .collect();
465
466 let mut module_fn_ids: Vec<(String, u32)> = Vec::new();
468 for item in &dep_resolved {
469 if let ResolvedTopLevel::FnDef(rfd) = item {
470 let qualified_name = visibility::qualified_name(dep_name, &rfd.name);
471 let effect_ids: Vec<u32> = rfd
472 .effects
473 .iter()
474 .map(|effect| self.symbols.intern_name(&effect.node))
475 .collect();
476 let fn_id = self.code.add_function(FnChunk {
477 name: qualified_name.clone(),
478 arity: rfd.params.len() as u8,
479 local_count: 0,
480 code: Vec::new(),
481 constants: Vec::new(),
482 effects: effect_ids,
483 thin: false,
484 parent_thin: false,
485 leaf: false,
486 no_alloc: false,
487 source_file: String::new(),
488 line_table: Vec::new(),
489 });
490 self.symbols.intern_function(
491 &qualified_name,
492 fn_id,
493 &rfd.effects
494 .iter()
495 .map(|e| e.node.clone())
496 .collect::<Vec<_>>(),
497 )?;
498 module_fn_ids.push((rfd.name.clone(), fn_id));
499 }
500 }
501
502 let module_scope: HashMap<String, u32> = module_fn_ids.iter().cloned().collect();
503 let dep_mir = build_optimized_mir(&dep_resolved, true);
508 let mut fn_idx = 0;
509 for item in &dep_resolved {
510 if let ResolvedTopLevel::FnDef(rfd) = item {
511 let (fn_name, fn_id) = &module_fn_ids[fn_idx];
512 let mir_fn = dep_mir.fn_by_id(rfd.fn_id).ok_or_else(|| CompileError {
513 msg: format!(
514 "internal error: dep fn `{}` did not lower to MIR (an unsupported shape reached the VM backend)",
515 rfd.name
516 ),
517 })?;
518 let mut chunk = self
519 .compile_fn_via_mir(rfd, mir_fn, entry_symbols, arena, &module_scope, &dep_mir)
520 .map_err(|e| e.into_compile_error(&format!("dep fn `{}`", rfd.name)))?;
521 chunk.name = visibility::qualified_name(dep_name, fn_name);
522 self.code.functions[*fn_id as usize] = chunk;
523 fn_idx += 1;
524 }
525 }
526
527 let exports = visibility::collect_module_exports(&mod_items);
529
530 for fd in &exports.functions {
531 let qualified = visibility::qualified_name(dep_name, &fd.name);
532 let global_idx = self.ensure_global(&qualified);
533 let symbol_id = self.symbols.find(&qualified).ok_or_else(|| CompileError {
534 msg: format!("missing VM symbol for exposed function {}", qualified),
535 })?;
536 self.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
537 }
538
539 let module_symbol_id = self.symbols.intern_namespace_path(dep_name)?;
540 for et in &exports.types {
541 let type_name = match et.def {
542 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
543 };
544 if let Some(type_symbol_id) = self.symbols.find(type_name) {
545 let member_symbol_id = self.symbols.intern_name(type_name);
546 self.symbols.add_namespace_member_by_id(
547 module_symbol_id,
548 member_symbol_id,
549 VmSymbolTable::symbol_ref(type_symbol_id),
550 )?;
551 }
552 }
553 for fd in &exports.functions {
554 let qualified = visibility::qualified_name(dep_name, &fd.name);
555 if let Some(fn_symbol_id) = self.symbols.find(&qualified) {
556 let member_symbol_id = self.symbols.intern_name(&fd.name);
557 self.symbols.add_namespace_member_by_id(
558 module_symbol_id,
559 member_symbol_id,
560 VmSymbolTable::symbol_ref(fn_symbol_id),
561 )?;
562 }
563 }
564
565 Ok(())
566 }
567
568 fn install_branch_path_root_constant(&mut self, arena: &mut Arena) -> Result<(), CompileError> {
575 let Some(type_id) = arena.find_type_id(crate::types::branch_path::TYPE_NAME) else {
580 return Ok(());
581 };
582 let dewey = crate::nan_value::NanValue::new_string_value("", arena);
583 let record_idx = arena.push_record(type_id, vec![dewey]);
584 let root_value = crate::nan_value::NanValue::new_record(record_idx);
585 self.symbols
586 .intern_constant("BranchPath.Root", root_value)?;
587 let namespace_symbol_id = self.symbols.intern_namespace_path("BranchPath")?;
588 let member_symbol_id = self.symbols.intern_name("Root");
589 self.symbols.add_namespace_member_by_id(
590 namespace_symbol_id,
591 member_symbol_id,
592 root_value,
593 )?;
594 Ok(())
595 }
596
597 fn ensure_global(&mut self, name: &str) -> u16 {
598 if let Some(&idx) = self.global_names.get(name) {
599 return idx;
600 }
601 let idx = self.globals.len() as u16;
602 self.global_names.insert(name.to_string(), idx);
603 self.globals.push(NanValue::UNIT);
604 idx
605 }
606
607 fn register_type_in_symbols(
610 &mut self,
611 td: &TypeDef,
612 arena: &Arena,
613 ) -> Result<(), CompileError> {
614 match td {
615 TypeDef::Product { name, fields, .. } => {
616 self.symbols.intern_namespace_path(name)?;
617 let type_id = arena
618 .find_type_id(name)
619 .expect("type already registered in Arena");
620 let field_symbol_ids: Vec<u32> = fields
621 .iter()
622 .map(|(field_name, _)| self.symbols.intern_name(field_name))
623 .collect();
624 self.code.register_record_fields(type_id, &field_symbol_ids);
625 }
626 TypeDef::Sum { name, variants, .. } => {
627 let type_symbol_id = self.symbols.intern_namespace_path(name)?;
628 let type_id = arena
629 .find_type_id(name)
630 .expect("type already registered in Arena");
631 for (variant_id, variant) in variants.iter().enumerate() {
632 let ctor_id = arena
633 .find_ctor_id(type_id, variant_id as u16)
634 .expect("ctor id");
635 let qualified_name = visibility::member_key(name, &variant.name);
636 let ctor_symbol_id = self.symbols.intern_variant_ctor(
637 &qualified_name,
638 VmVariantCtor {
639 type_id,
640 variant_id: variant_id as u16,
641 ctor_id,
642 field_count: variant.fields.len() as u8,
643 },
644 )?;
645 let member_symbol_id = self.symbols.intern_name(&variant.name);
646 self.symbols.add_namespace_member_by_id(
647 type_symbol_id,
648 member_symbol_id,
649 VmSymbolTable::symbol_ref(ctor_symbol_id),
650 )?;
651 }
652 }
653 }
654 Ok(())
655 }
656
657 fn bootstrap_core_symbols(&mut self) -> Result<(), CompileError> {
658 for builtin in VmBuiltin::ALL.iter().copied() {
659 let builtin_symbol_id = self.symbols.intern_builtin(builtin)?;
660 if let Some((namespace, member)) = builtin.name().split_once('.') {
661 let namespace_symbol_id = self.symbols.intern_namespace_path(namespace)?;
662 let member_symbol_id = self.symbols.intern_name(member);
663 self.symbols.add_namespace_member_by_id(
664 namespace_symbol_id,
665 member_symbol_id,
666 VmSymbolTable::symbol_ref(builtin_symbol_id),
667 )?;
668 }
669 }
670
671 let result_symbol_id = self.symbols.intern_namespace_path("Result")?;
672 let ok_symbol_id = self.symbols.intern_wrapper("Result.Ok", 0)?;
673 let err_symbol_id = self.symbols.intern_wrapper("Result.Err", 1)?;
674 let ok_member_symbol_id = self.symbols.intern_name("Ok");
675 self.symbols.add_namespace_member_by_id(
676 result_symbol_id,
677 ok_member_symbol_id,
678 VmSymbolTable::symbol_ref(ok_symbol_id),
679 )?;
680 let err_member_symbol_id = self.symbols.intern_name("Err");
681 self.symbols.add_namespace_member_by_id(
682 result_symbol_id,
683 err_member_symbol_id,
684 VmSymbolTable::symbol_ref(err_symbol_id),
685 )?;
686 for (member, builtin_name) in result::extra_members() {
687 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
688 let member_symbol_id = self.symbols.intern_name(member);
689 self.symbols.add_namespace_member_by_id(
690 result_symbol_id,
691 member_symbol_id,
692 VmSymbolTable::symbol_ref(symbol_id),
693 )?;
694 }
695 }
696
697 let option_symbol_id = self.symbols.intern_namespace_path("Option")?;
698 let some_symbol_id = self.symbols.intern_wrapper("Option.Some", 2)?;
699 self.symbols
700 .intern_constant("Option.None", NanValue::NONE)?;
701 let some_member_symbol_id = self.symbols.intern_name("Some");
702 self.symbols.add_namespace_member_by_id(
703 option_symbol_id,
704 some_member_symbol_id,
705 VmSymbolTable::symbol_ref(some_symbol_id),
706 )?;
707 let none_member_symbol_id = self.symbols.intern_name("None");
708 self.symbols.add_namespace_member_by_id(
709 option_symbol_id,
710 none_member_symbol_id,
711 NanValue::NONE,
712 )?;
713 for (member, builtin_name) in option::extra_members() {
714 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
715 let member_symbol_id = self.symbols.intern_name(member);
716 self.symbols.add_namespace_member_by_id(
717 option_symbol_id,
718 member_symbol_id,
719 VmSymbolTable::symbol_ref(symbol_id),
720 )?;
721 }
722 }
723 Ok(())
724 }
725
726 fn compile_fn_via_mir(
733 &mut self,
734 rfd: &ResolvedFnDef,
735 mir_fn: &crate::ir::mir::MirFn,
736 symbols: &SymbolTable,
737 arena: &mut Arena,
738 module_scope: &HashMap<String, u32>,
739 mir_program: &crate::ir::mir::MirProgram,
740 ) -> Result<FnChunk, mir::MirVmUnsupported> {
741 let resolution = rfd.resolution.as_ref();
742 let local_count = mir_fn
747 .local_count
748 .max(resolution.map_or(rfd.params.len() as u32, |r| u32::from(r.local_count)))
749 as u16;
750 let local_slots: HashMap<String, u16> = resolution
751 .map(|r| r.local_slots.as_ref().clone())
752 .unwrap_or_else(|| {
753 rfd.params
754 .iter()
755 .enumerate()
756 .map(|(i, (name, _))| (name.clone(), i as u16))
757 .collect()
758 });
759
760 let mut fc = FnCompiler::new(
761 &rfd.name,
762 rfd.params.len() as u8,
763 local_count,
764 rfd.effects
765 .iter()
766 .map(|effect| self.symbols.intern_name(&effect.node))
767 .collect(),
768 local_slots,
769 &self.global_names,
770 module_scope,
771 &self.code,
772 &mut self.symbols,
773 arena,
774 symbols,
775 Some(mir_program),
776 );
777 fc.source_file = self.source_file.clone();
778 fc.note_line(rfd.line);
779 fc.set_aliased_slots(mir_fn.aliased_slots.clone());
785
786 mir::compile_mir_fn_body(&mut fc, mir_fn)?;
787 Ok(fc.finish())
788 }
789
790 fn compile_top_level(
791 &mut self,
792 items: &[ResolvedTopLevel],
793 symbols: &SymbolTable,
794 arena: &mut Arena,
795 mir_program: &crate::ir::mir::MirProgram,
796 ) -> Result<(), CompileError> {
797 let has_stmts = items
798 .iter()
799 .any(|i| matches!(i, ResolvedTopLevel::Passthrough(TopLevel::Stmt(_))));
800 if !has_stmts {
801 return Ok(());
802 }
803
804 for item in items {
805 if let ResolvedTopLevel::Passthrough(TopLevel::Stmt(Stmt::Binding(name, _, _))) = item {
806 self.ensure_global(name);
807 }
808 }
809
810 let resolver_ctx = crate::ir::hir::ResolveCtx::new(symbols);
815 let resolved: Vec<ResolvedStmt> = items
816 .iter()
817 .filter_map(|i| match i {
818 ResolvedTopLevel::Passthrough(TopLevel::Stmt(stmt)) => {
819 Some(resolve_stmt_for_top_level(&resolver_ctx, stmt))
820 }
821 _ => None,
822 })
823 .collect();
824
825 let store_targets: Vec<Option<u16>> = resolved
829 .iter()
830 .map(|rs| match rs {
831 ResolvedStmt::Binding { name, .. } => Some(self.global_names[name.as_str()]),
832 ResolvedStmt::Expr(_) => None,
833 })
834 .collect();
835
836 let mut prog = mir_program.clone();
845 let lowered: Vec<crate::ast::Spanned<crate::ir::mir::MirExpr>> = resolved
846 .iter()
847 .map(|rs| {
848 let value = match rs {
849 ResolvedStmt::Binding { value, .. } | ResolvedStmt::Expr(value) => value,
850 };
851 crate::ir::mir::lower_top_level_value(value, &mut prog).map_err(|reason| {
852 CompileError {
853 msg: format!(
854 "internal error: a top-level statement did not lower to MIR ({reason:?})"
855 ),
856 }
857 })
858 })
859 .collect::<Result<_, _>>()?;
860 if let Some(bad) = lowered.iter().find(|low| !mir::mir_expr_compilable(low)) {
861 return Err(CompileError {
862 msg: format!(
863 "internal error: a top-level statement is outside the VM backend subset: {:?}",
864 bad.node
865 ),
866 });
867 }
868
869 let empty_mod_scope = HashMap::new();
870 let mut fc = FnCompiler::new(
871 "__top_level__",
872 0,
873 0,
874 Vec::new(),
875 HashMap::new(),
876 &self.global_names,
877 &empty_mod_scope,
878 &self.code,
879 &mut self.symbols,
880 arena,
881 symbols,
882 Some(&prog),
883 );
884
885 for (idx, low) in lowered.iter().enumerate() {
886 mir::compile_mir_expr(&mut fc, low)
887 .map_err(|e| e.into_compile_error("a top-level statement"))?;
888 match store_targets[idx] {
889 Some(global_idx) => {
890 fc.emit_op(STORE_GLOBAL);
891 fc.emit_u16(global_idx);
892 }
893 None => fc.emit_op(POP),
894 }
895 }
896
897 fc.emit_op(LOAD_UNIT);
898 fc.emit_op(RETURN);
899
900 let chunk = fc.finish();
901 self.code.add_function(chunk);
902 Ok(())
903 }
904
905 fn register_current_module_namespace(
906 &mut self,
907 items: &[ResolvedTopLevel],
908 ) -> Result<(), CompileError> {
909 let Some(module) = items.iter().find_map(|item| match item {
910 ResolvedTopLevel::Module(module) => Some(module),
911 _ => None,
912 }) else {
913 return Ok(());
914 };
915
916 let module_symbol_id = self.symbols.intern_namespace_path(&module.name)?;
917 let exposes_ref = if module.exposes.is_empty() {
918 None
919 } else {
920 Some(module.exposes.as_slice())
921 };
922
923 for item in items {
924 match item {
925 ResolvedTopLevel::FnDef(rfd) => {
926 if visibility::is_exposed(&rfd.name, exposes_ref)
927 && let Some(symbol_id) = self.symbols.find(&rfd.name)
928 {
929 let member_symbol_id = self.symbols.intern_name(&rfd.name);
930 self.symbols.add_namespace_member_by_id(
931 module_symbol_id,
932 member_symbol_id,
933 VmSymbolTable::symbol_ref(symbol_id),
934 )?;
935 }
936 }
937 ResolvedTopLevel::Passthrough(TopLevel::TypeDef(
938 TypeDef::Product { name, .. } | TypeDef::Sum { name, .. },
939 )) => {
940 if visibility::is_exposed(name, exposes_ref)
941 && let Some(symbol_id) = self.symbols.find(name)
942 {
943 let member_symbol_id = self.symbols.intern_name(name);
944 self.symbols.add_namespace_member_by_id(
945 module_symbol_id,
946 member_symbol_id,
947 VmSymbolTable::symbol_ref(symbol_id),
948 )?;
949 }
950 }
951 _ => {}
952 }
953 }
954 Ok(())
955 }
956}
957
958fn resolve_stmt_for_top_level(ctx: &crate::ir::hir::ResolveCtx<'_>, stmt: &Stmt) -> ResolvedStmt {
959 crate::ir::hir::resolve::resolve_stmt_external(ctx, stmt)
960}
961
962pub(super) struct FnCompiler<'a> {
964 name: String,
965 arity: u8,
966 local_count: u16,
967 effects: Vec<u32>,
968 pub(super) local_slots: HashMap<String, u16>,
969 global_names: &'a HashMap<String, u16>,
970 module_scope: &'a HashMap<String, u32>,
973 pub(super) code_store: &'a CodeStore,
974 pub(super) symbols: &'a mut VmSymbolTable,
975 pub(super) arena: &'a mut Arena,
976 pub(super) symbol_table: &'a SymbolTable,
986 pub(super) mir_program: Option<&'a crate::ir::mir::MirProgram>,
993 code: Vec<u8>,
994 constants: Vec<NanValue>,
995 last_op_pos: usize,
997 source_file: String,
999 line_table: Vec<(u16, u16)>,
1001 last_noted_line: u16,
1003 aliased_slots: std::sync::Arc<Vec<bool>>,
1012}
1013
1014impl<'a> FnCompiler<'a> {
1015 #[allow(clippy::too_many_arguments)]
1016 fn new(
1017 name: &str,
1018 arity: u8,
1019 local_count: u16,
1020 effects: Vec<u32>,
1021 local_slots: HashMap<String, u16>,
1022 global_names: &'a HashMap<String, u16>,
1023 module_scope: &'a HashMap<String, u32>,
1024 code_store: &'a CodeStore,
1025 symbols: &'a mut VmSymbolTable,
1026 arena: &'a mut Arena,
1027 symbol_table: &'a SymbolTable,
1028 mir_program: Option<&'a crate::ir::mir::MirProgram>,
1029 ) -> Self {
1030 FnCompiler {
1031 name: name.to_string(),
1032 arity,
1033 local_count,
1034 effects,
1035 local_slots,
1036 global_names,
1037 module_scope,
1038 code_store,
1039 symbols,
1040 arena,
1041 symbol_table,
1042 mir_program,
1043 code: Vec::new(),
1044 constants: Vec::new(),
1045 last_op_pos: usize::MAX,
1046 source_file: String::new(),
1047 line_table: Vec::new(),
1048 last_noted_line: 0,
1049 aliased_slots: std::sync::Arc::new(Vec::new()),
1050 }
1051 }
1052
1053 fn set_aliased_slots(&mut self, aliased: std::sync::Arc<Vec<bool>>) {
1054 self.aliased_slots = aliased;
1055 }
1056
1057 pub(super) fn is_aliased_slot(&self, slot: u16) -> bool {
1058 self.aliased_slots
1059 .get(slot as usize)
1060 .copied()
1061 .unwrap_or(false)
1062 }
1063
1064 pub(super) fn name(&self) -> &str {
1065 &self.name
1066 }
1067
1068 pub(super) fn global_names(&self) -> &HashMap<String, u16> {
1069 self.global_names
1070 }
1071
1072 pub(super) fn module_scope(&self) -> &HashMap<String, u32> {
1073 self.module_scope
1074 }
1075
1076 fn finish(self) -> FnChunk {
1077 FnChunk {
1078 name: self.name,
1079 arity: self.arity,
1080 local_count: self.local_count,
1081 code: self.code,
1082 constants: self.constants,
1083 effects: self.effects,
1084 thin: false,
1085 parent_thin: false,
1086 leaf: false,
1087 no_alloc: false,
1088 source_file: self.source_file,
1089 line_table: self.line_table,
1090 }
1091 }
1092
1093 pub(super) fn note_line(&mut self, line: usize) {
1097 if line == 0 {
1098 return;
1099 }
1100 let line16 = line as u16;
1101 if line16 == self.last_noted_line {
1102 return; }
1104 self.last_noted_line = line16;
1105 self.line_table.push((self.code.len() as u16, line16));
1106 }
1107
1108 pub(super) fn emit_op(&mut self, op: u8) {
1109 let prev_pos = self.last_op_pos;
1110 let prev_op = if prev_pos < self.code.len() {
1111 self.code[prev_pos]
1112 } else {
1113 0xFF
1114 };
1115
1116 if op == LOAD_LOCAL && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
1118 self.code[prev_pos] = LOAD_LOCAL_2;
1119 return;
1121 }
1122 if op == LOAD_CONST && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
1124 self.code[prev_pos] = LOAD_LOCAL_CONST;
1125 return;
1127 }
1128 if op == UNWRAP_OR && self.code.len() >= 4 {
1132 let len = self.code.len();
1133 if self.code[len - 4] == VECTOR_GET && self.code[len - 3] == LOAD_CONST {
1134 let hi = self.code[len - 2];
1135 let lo = self.code[len - 1];
1136 self.code[len - 4] = VECTOR_GET_OR;
1137 self.code[len - 3] = hi;
1138 self.code[len - 2] = lo;
1139 self.code.pop(); self.last_op_pos = len - 4;
1141 return;
1142 }
1143 }
1144 self.last_op_pos = self.code.len();
1145 self.code.push(op);
1146 }
1147
1148 pub(super) fn emit_u8(&mut self, val: u8) {
1149 self.code.push(val);
1150 }
1151
1152 pub(super) fn emit_u16(&mut self, val: u16) {
1153 self.code.push((val >> 8) as u8);
1154 self.code.push((val & 0xFF) as u8);
1155 }
1156
1157 pub(super) fn emit_i16(&mut self, val: i16) {
1158 self.emit_u16(val as u16);
1159 }
1160
1161 pub(super) fn emit_u32(&mut self, val: u32) {
1162 self.code.push((val >> 24) as u8);
1163 self.code.push(((val >> 16) & 0xFF) as u8);
1164 self.code.push(((val >> 8) & 0xFF) as u8);
1165 self.code.push((val & 0xFF) as u8);
1166 }
1167
1168 pub(super) fn emit_u64(&mut self, val: u64) {
1169 self.code.extend_from_slice(&val.to_be_bytes());
1170 }
1171
1172 pub(super) fn emit_i64(&mut self, val: i64) {
1173 self.code.extend_from_slice(&val.to_be_bytes());
1174 }
1175
1176 pub(super) fn add_constant(&mut self, val: NanValue) -> u16 {
1177 for (i, c) in self.constants.iter().enumerate() {
1178 if c.bits() == val.bits() {
1179 return i as u16;
1180 }
1181 }
1182 let idx = self.constants.len() as u16;
1183 self.constants.push(val);
1184 idx
1185 }
1186
1187 pub(super) fn offset(&self) -> usize {
1188 self.code.len()
1189 }
1190
1191 pub(super) fn code_mut(&mut self) -> &mut Vec<u8> {
1192 &mut self.code
1193 }
1194
1195 pub(super) fn emit_jump(&mut self, op: u8) -> usize {
1196 self.emit_op(op);
1197 let patch_pos = self.code.len();
1198 self.emit_i16(0);
1199 patch_pos
1200 }
1201
1202 pub(super) fn patch_jump(&mut self, patch_pos: usize) {
1203 let target = self.code.len();
1204 let offset = (target as isize - patch_pos as isize - 2) as i16;
1205 let bytes = (offset as u16).to_be_bytes();
1206 self.code[patch_pos] = bytes[0];
1207 self.code[patch_pos + 1] = bytes[1];
1208 }
1209
1210 pub(super) fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
1211 let offset = (target as isize - patch_pos as isize - 2) as i16;
1212 let bytes = (offset as u16).to_be_bytes();
1213 self.code[patch_pos] = bytes[0];
1214 self.code[patch_pos + 1] = bytes[1];
1215 }
1216}
1217
1218#[cfg(test)]
1219mod tests {
1220 use super::compile_program_with_mir_fallback;
1221 use crate::ir::SymbolTable;
1222 use crate::ir::hir::resolve_program;
1223 use crate::nan_value::Arena;
1224 use crate::source::parse_source;
1225 use crate::vm::opcode::{LT, VECTOR_GET_OR, VECTOR_SET_OR_KEEP};
1226
1227 fn compile_via_pipeline(source: &str) -> crate::vm::CodeStore {
1234 let mut items = parse_source(source).expect("source should parse");
1235 crate::ir::pipeline::tco(&mut items);
1236 crate::ir::pipeline::resolve(&mut items);
1237 let symbols = SymbolTable::build(&items, &[]);
1238 let resolved = resolve_program(&symbols, &items);
1239 let mut arena = Arena::new();
1240 let (code, _globals) =
1241 compile_program_with_mir_fallback(&resolved, &symbols, &mut arena, None)
1242 .expect("vm compile should pass");
1243 code
1244 }
1245
1246 #[test]
1247 fn vector_get_with_literal_default_lowers_to_vector_get_or() {
1248 let source = r#"
1249module Demo
1250
1251fn cellAt(grid: Vector<Int>, idx: Int) -> Int
1252 Option.withDefault(Vector.get(grid, idx), 0)
1253"#;
1254
1255 let code = compile_via_pipeline(source);
1256 let fn_id = code.find("cellAt").expect("cellAt should exist");
1257 let chunk = code.get(fn_id);
1258
1259 assert!(
1260 chunk.code.contains(&VECTOR_GET_OR),
1261 "expected VECTOR_GET_OR in bytecode, got {:?}",
1262 chunk.code
1263 );
1264 }
1265
1266 #[test]
1267 fn vector_set_with_same_default_lowers_to_vector_set_or_keep() {
1268 let source = r#"
1269module Demo
1270
1271fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
1272 Option.withDefault(Vector.set(vec, idx, value), vec)
1273"#;
1274
1275 let code = compile_via_pipeline(source);
1276 let fn_id = code
1277 .find("updateOrKeep")
1278 .expect("updateOrKeep should exist");
1279 let chunk = code.get(fn_id);
1280
1281 assert!(
1282 chunk.code.contains(&VECTOR_SET_OR_KEEP),
1283 "expected VECTOR_SET_OR_KEEP in bytecode, got {:?}",
1284 chunk.code
1285 );
1286 }
1287
1288 #[test]
1289 fn bool_match_on_gte_uses_base_compare_without_not() {
1290 let source = r#"
1291module Demo
1292
1293fn bucket(n: Int) -> Int
1294 match n >= 10
1295 true -> 7
1296 false -> 3
1297"#;
1298
1299 let code = compile_via_pipeline(source);
1300 let fn_id = code.find("bucket").expect("bucket should exist");
1301 let chunk = code.get(fn_id);
1302
1303 assert!(
1304 chunk.code.contains(<),
1305 "expected the base integer compare LT in bytecode, got {:?}",
1306 chunk.code
1307 );
1308 }
1309
1310 #[test]
1311 fn self_host_runtime_http_server_aliases_compile_in_vm() {
1312 let source = r#"
1313module Demo
1314
1315fn listen(handler: Int) -> Unit
1316 SelfHostRuntime.httpServerListen(8080, handler)
1317
1318fn listenWith(context: Int, handler: Int) -> Unit
1319 SelfHostRuntime.httpServerListenWith(8081, context, handler)
1320"#;
1321
1322 let code = compile_via_pipeline(source);
1323 assert!(code.find("listen").is_some(), "listen should compile");
1324 assert!(
1325 code.find("listenWith").is_some(),
1326 "listenWith should compile"
1327 );
1328 }
1329}