1mod calls;
2mod classify;
3mod expr;
4mod patterns;
5
6use std::collections::HashMap;
7
8use crate::ast::{Stmt, TopLevel, TypeDef};
9use crate::ir::SymbolTable;
10use crate::ir::hir::{
11 ResolveCtx, ResolvedFnBody, 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(
34 items: &[ResolvedTopLevel],
35 symbols: &SymbolTable,
36 arena: &mut Arena,
37 analysis: Option<&crate::ir::AnalysisResult>,
38) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
39 compile_program_with_modules(items, symbols, arena, None, "", analysis)
40}
41
42pub fn compile_program_with_modules(
44 items: &[ResolvedTopLevel],
45 symbols: &SymbolTable,
46 arena: &mut Arena,
47 module_root: Option<&str>,
48 source_file: &str,
49 analysis: Option<&crate::ir::AnalysisResult>,
50) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
51 compile_program_inner(
52 items,
53 symbols,
54 arena,
55 source_file,
56 ModuleSource::Disk(module_root),
57 analysis,
58 )
59}
60
61pub fn compile_program_with_loaded_modules(
65 items: &[ResolvedTopLevel],
66 symbols: &SymbolTable,
67 arena: &mut Arena,
68 loaded: Vec<crate::source::LoadedModule>,
69 source_file: &str,
70 analysis: Option<&crate::ir::AnalysisResult>,
71) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
72 compile_program_inner(
73 items,
74 symbols,
75 arena,
76 source_file,
77 ModuleSource::Loaded(loaded),
78 analysis,
79 )
80}
81
82enum ModuleSource<'a> {
83 Disk(Option<&'a str>),
84 Loaded(Vec<crate::source::LoadedModule>),
85}
86
87fn compile_program_inner(
88 items: &[ResolvedTopLevel],
89 symbols: &SymbolTable,
90 arena: &mut Arena,
91 source_file: &str,
92 module_source: ModuleSource<'_>,
93 analysis: Option<&crate::ir::AnalysisResult>,
94) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
95 let mut compiler = ProgramCompiler::new();
96 compiler.source_file = source_file.to_string();
97 compiler.sync_record_field_symbols(arena)?;
98 compiler.install_branch_path_root_constant(arena)?;
104
105 match module_source {
106 ModuleSource::Disk(Some(module_root)) => {
107 compiler.load_modules(items, module_root, symbols, arena)?;
108 }
109 ModuleSource::Disk(None) => {}
110 ModuleSource::Loaded(loaded) => {
111 for m in loaded {
112 compiler.integrate_module(&m.dep_name, m.items, symbols, arena)?;
113 }
114 }
115 }
116
117 for item in items {
118 if let ResolvedTopLevel::Passthrough(TopLevel::Stmt(Stmt::Binding(name, _, _))) = item {
119 compiler.ensure_global(name);
120 }
121 }
122
123 for item in items {
124 match item {
125 ResolvedTopLevel::FnDef(rfd) => {
126 compiler.ensure_global(&rfd.name);
127 let effect_ids: Vec<u32> = rfd
128 .effects
129 .iter()
130 .map(|effect| compiler.symbols.intern_name(&effect.node))
131 .collect();
132 let fn_id = compiler.code.add_function(FnChunk {
133 name: rfd.name.clone(),
134 arity: rfd.params.len() as u8,
135 local_count: 0,
136 code: Vec::new(),
137 constants: Vec::new(),
138 effects: effect_ids,
139 thin: false,
140 parent_thin: false,
141 leaf: false,
142 no_alloc: false,
143 source_file: String::new(),
144 line_table: Vec::new(),
145 });
146 let symbol_id = compiler.symbols.intern_function(
147 &rfd.name,
148 fn_id,
149 &rfd.effects
150 .iter()
151 .map(|e| e.node.clone())
152 .collect::<Vec<_>>(),
153 )?;
154 let global_idx = compiler.global_names[&rfd.name];
155 compiler.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
156 }
157 ResolvedTopLevel::Passthrough(TopLevel::TypeDef(td)) => {
158 match td {
160 TypeDef::Product { name, fields, .. } => {
161 let field_names: Vec<String> =
162 fields.iter().map(|(n, _)| n.clone()).collect();
163 arena.register_record_type(name, field_names);
164 }
165 TypeDef::Sum { name, variants, .. } => {
166 let variant_names: Vec<String> =
167 variants.iter().map(|v| v.name.clone()).collect();
168 arena.register_sum_type(name, variant_names);
169 }
170 }
171 compiler.register_type_in_symbols(td, arena)?;
173 }
174 _ => {}
175 }
176 }
177
178 compiler.register_current_module_namespace(items)?;
179
180 for item in items {
181 if let ResolvedTopLevel::FnDef(rfd) = item {
182 let fn_id = compiler.code.find(&rfd.name).unwrap();
183 let chunk = compiler.compile_fn(rfd, symbols, arena)?;
184 compiler.code.functions[fn_id as usize] = chunk;
185 }
186 }
187
188 compiler.compile_top_level(items, symbols, arena)?;
189 compiler.code.symbols = compiler.symbols.clone();
190 classify::classify_thin_functions(&mut compiler.code, arena)?;
191
192 let allocates = |name: &str| -> bool {
201 if let Some(a) = analysis
202 && let Some(fa) = a.fn_analyses.get(name)
203 && let Some(b) = fa.allocates
204 {
205 return b;
206 }
207 true
208 };
209 for item in items {
210 if let ResolvedTopLevel::FnDef(rfd) = item
211 && !allocates(&rfd.name)
212 && let Some(fn_id) = compiler.code.find(&rfd.name)
213 {
214 let chunk = &mut compiler.code.functions[fn_id as usize];
215 chunk.no_alloc = true;
216 chunk.thin = true;
223 }
224 }
225
226 Ok((compiler.code, compiler.globals))
227}
228
229#[derive(Debug)]
230pub struct CompileError {
231 pub msg: String,
232}
233
234impl std::fmt::Display for CompileError {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 write!(f, "Compile error: {}", self.msg)
237 }
238}
239
240impl From<crate::vm::symbol::SymbolError> for CompileError {
245 fn from(err: crate::vm::symbol::SymbolError) -> Self {
246 CompileError {
247 msg: err.to_string(),
248 }
249 }
250}
251
252struct ProgramCompiler {
253 code: CodeStore,
254 symbols: VmSymbolTable,
255 globals: Vec<NanValue>,
256 global_names: HashMap<String, u16>,
257 source_file: String,
259}
260
261impl ProgramCompiler {
262 fn new() -> Self {
263 let mut compiler = ProgramCompiler {
264 code: CodeStore::new(),
265 symbols: VmSymbolTable::default(),
266 globals: Vec::new(),
267 global_names: HashMap::new(),
268 source_file: String::new(),
269 };
270 compiler
275 .bootstrap_core_symbols()
276 .expect("bootstrap_core_symbols on empty VmSymbolTable cannot fail");
277 compiler
278 }
279
280 fn sync_record_field_symbols(&mut self, arena: &Arena) -> Result<(), CompileError> {
281 for type_id in 0..arena.type_count() {
282 let type_name = arena.get_type_name(type_id);
283 self.symbols.intern_namespace_path(type_name)?;
284 let field_names = arena.get_field_names(type_id);
285 if field_names.is_empty() {
286 continue;
287 }
288 let field_symbol_ids: Vec<u32> = field_names
289 .iter()
290 .map(|field_name| self.symbols.intern_name(field_name))
291 .collect();
292 self.code.register_record_fields(type_id, &field_symbol_ids);
293 }
294 Ok(())
295 }
296
297 fn load_modules(
300 &mut self,
301 items: &[ResolvedTopLevel],
302 module_root: &str,
303 entry_symbols: &SymbolTable,
304 arena: &mut Arena,
305 ) -> Result<(), CompileError> {
306 let module = items.iter().find_map(|i| match i {
307 ResolvedTopLevel::Module(m) => Some(m),
308 _ => None,
309 });
310 let module = match module {
311 Some(m) => m,
312 None => return Ok(()),
313 };
314
315 let modules = crate::source::load_module_tree(&module.depends, module_root)
316 .map_err(|e| CompileError { msg: e })?;
317
318 for loaded in modules {
319 self.integrate_module(&loaded.dep_name, loaded.items, entry_symbols, arena)?;
320 }
321 Ok(())
322 }
323
324 fn integrate_module(
335 &mut self,
336 dep_name: &str,
337 mut mod_items: Vec<TopLevel>,
338 entry_symbols: &SymbolTable,
339 arena: &mut Arena,
340 ) -> Result<(), CompileError> {
341 crate::ir::pipeline::tco(&mut mod_items);
348 crate::ir::pipeline::resolve(&mut mod_items);
349
350 for mt in visibility::collect_module_types(&mod_items) {
352 let type_id = match &mt.kind {
353 visibility::ModuleTypeKind::Record { field_names } => {
354 arena.register_record_type(&mt.bare_name, field_names.clone())
355 }
356 visibility::ModuleTypeKind::Sum { variant_names } => {
357 arena.register_sum_type(&mt.bare_name, variant_names.clone())
358 }
359 };
360 arena.register_type_alias(
361 &visibility::qualified_name(dep_name, &mt.bare_name),
362 type_id,
363 );
364 }
365 for item in &mod_items {
366 if let TopLevel::TypeDef(td) = item {
367 self.register_type_in_symbols(td, arena)?;
368 }
369 }
370
371 let mut ctx = ResolveCtx::new(entry_symbols);
380 ctx.current_module = Some(dep_name.to_string());
381 let dep_resolved: Vec<ResolvedTopLevel> = mod_items
382 .iter()
383 .map(|i| resolve_top_level(&ctx, i))
384 .collect();
385
386 let mut module_fn_ids: Vec<(String, u32)> = Vec::new();
388 for item in &dep_resolved {
389 if let ResolvedTopLevel::FnDef(rfd) = item {
390 let qualified_name = visibility::qualified_name(dep_name, &rfd.name);
391 let effect_ids: Vec<u32> = rfd
392 .effects
393 .iter()
394 .map(|effect| self.symbols.intern_name(&effect.node))
395 .collect();
396 let fn_id = self.code.add_function(FnChunk {
397 name: qualified_name.clone(),
398 arity: rfd.params.len() as u8,
399 local_count: 0,
400 code: Vec::new(),
401 constants: Vec::new(),
402 effects: effect_ids,
403 thin: false,
404 parent_thin: false,
405 leaf: false,
406 no_alloc: false,
407 source_file: String::new(),
408 line_table: Vec::new(),
409 });
410 self.symbols.intern_function(
411 &qualified_name,
412 fn_id,
413 &rfd.effects
414 .iter()
415 .map(|e| e.node.clone())
416 .collect::<Vec<_>>(),
417 )?;
418 module_fn_ids.push((rfd.name.clone(), fn_id));
419 }
420 }
421
422 let module_scope: HashMap<String, u32> = module_fn_ids.iter().cloned().collect();
423 let mut fn_idx = 0;
424 for item in &dep_resolved {
425 if let ResolvedTopLevel::FnDef(rfd) = item {
426 let (fn_name, fn_id) = &module_fn_ids[fn_idx];
427 let mut chunk =
428 self.compile_fn_with_scope(rfd, entry_symbols, arena, &module_scope)?;
429 chunk.name = visibility::qualified_name(dep_name, fn_name);
430 self.code.functions[*fn_id as usize] = chunk;
431 fn_idx += 1;
432 }
433 }
434
435 let exports = visibility::collect_module_exports(&mod_items);
437
438 for fd in &exports.functions {
439 let qualified = visibility::qualified_name(dep_name, &fd.name);
440 let global_idx = self.ensure_global(&qualified);
441 let symbol_id = self.symbols.find(&qualified).ok_or_else(|| CompileError {
442 msg: format!("missing VM symbol for exposed function {}", qualified),
443 })?;
444 self.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
445 }
446
447 let module_symbol_id = self.symbols.intern_namespace_path(dep_name)?;
448 for et in &exports.types {
449 let type_name = match et.def {
450 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
451 };
452 if let Some(type_symbol_id) = self.symbols.find(type_name) {
453 let member_symbol_id = self.symbols.intern_name(type_name);
454 self.symbols.add_namespace_member_by_id(
455 module_symbol_id,
456 member_symbol_id,
457 VmSymbolTable::symbol_ref(type_symbol_id),
458 )?;
459 }
460 }
461 for fd in &exports.functions {
462 let qualified = visibility::qualified_name(dep_name, &fd.name);
463 if let Some(fn_symbol_id) = self.symbols.find(&qualified) {
464 let member_symbol_id = self.symbols.intern_name(&fd.name);
465 self.symbols.add_namespace_member_by_id(
466 module_symbol_id,
467 member_symbol_id,
468 VmSymbolTable::symbol_ref(fn_symbol_id),
469 )?;
470 }
471 }
472
473 Ok(())
474 }
475
476 fn install_branch_path_root_constant(&mut self, arena: &mut Arena) -> Result<(), CompileError> {
483 let Some(type_id) = arena.find_type_id(crate::types::branch_path::TYPE_NAME) else {
488 return Ok(());
489 };
490 let dewey = crate::nan_value::NanValue::new_string_value("", arena);
491 let record_idx = arena.push_record(type_id, vec![dewey]);
492 let root_value = crate::nan_value::NanValue::new_record(record_idx);
493 self.symbols
494 .intern_constant("BranchPath.Root", root_value)?;
495 let namespace_symbol_id = self.symbols.intern_namespace_path("BranchPath")?;
496 let member_symbol_id = self.symbols.intern_name("Root");
497 self.symbols.add_namespace_member_by_id(
498 namespace_symbol_id,
499 member_symbol_id,
500 root_value,
501 )?;
502 Ok(())
503 }
504
505 fn ensure_global(&mut self, name: &str) -> u16 {
506 if let Some(&idx) = self.global_names.get(name) {
507 return idx;
508 }
509 let idx = self.globals.len() as u16;
510 self.global_names.insert(name.to_string(), idx);
511 self.globals.push(NanValue::UNIT);
512 idx
513 }
514
515 fn register_type_in_symbols(
518 &mut self,
519 td: &TypeDef,
520 arena: &Arena,
521 ) -> Result<(), CompileError> {
522 match td {
523 TypeDef::Product { name, fields, .. } => {
524 self.symbols.intern_namespace_path(name)?;
525 let type_id = arena
526 .find_type_id(name)
527 .expect("type already registered in Arena");
528 let field_symbol_ids: Vec<u32> = fields
529 .iter()
530 .map(|(field_name, _)| self.symbols.intern_name(field_name))
531 .collect();
532 self.code.register_record_fields(type_id, &field_symbol_ids);
533 }
534 TypeDef::Sum { name, variants, .. } => {
535 let type_symbol_id = self.symbols.intern_namespace_path(name)?;
536 let type_id = arena
537 .find_type_id(name)
538 .expect("type already registered in Arena");
539 for (variant_id, variant) in variants.iter().enumerate() {
540 let ctor_id = arena
541 .find_ctor_id(type_id, variant_id as u16)
542 .expect("ctor id");
543 let qualified_name = visibility::member_key(name, &variant.name);
544 let ctor_symbol_id = self.symbols.intern_variant_ctor(
545 &qualified_name,
546 VmVariantCtor {
547 type_id,
548 variant_id: variant_id as u16,
549 ctor_id,
550 field_count: variant.fields.len() as u8,
551 },
552 )?;
553 let member_symbol_id = self.symbols.intern_name(&variant.name);
554 self.symbols.add_namespace_member_by_id(
555 type_symbol_id,
556 member_symbol_id,
557 VmSymbolTable::symbol_ref(ctor_symbol_id),
558 )?;
559 }
560 }
561 }
562 Ok(())
563 }
564
565 fn bootstrap_core_symbols(&mut self) -> Result<(), CompileError> {
566 for builtin in VmBuiltin::ALL.iter().copied() {
567 let builtin_symbol_id = self.symbols.intern_builtin(builtin)?;
568 if let Some((namespace, member)) = builtin.name().split_once('.') {
569 let namespace_symbol_id = self.symbols.intern_namespace_path(namespace)?;
570 let member_symbol_id = self.symbols.intern_name(member);
571 self.symbols.add_namespace_member_by_id(
572 namespace_symbol_id,
573 member_symbol_id,
574 VmSymbolTable::symbol_ref(builtin_symbol_id),
575 )?;
576 }
577 }
578
579 let result_symbol_id = self.symbols.intern_namespace_path("Result")?;
580 let ok_symbol_id = self.symbols.intern_wrapper("Result.Ok", 0)?;
581 let err_symbol_id = self.symbols.intern_wrapper("Result.Err", 1)?;
582 let ok_member_symbol_id = self.symbols.intern_name("Ok");
583 self.symbols.add_namespace_member_by_id(
584 result_symbol_id,
585 ok_member_symbol_id,
586 VmSymbolTable::symbol_ref(ok_symbol_id),
587 )?;
588 let err_member_symbol_id = self.symbols.intern_name("Err");
589 self.symbols.add_namespace_member_by_id(
590 result_symbol_id,
591 err_member_symbol_id,
592 VmSymbolTable::symbol_ref(err_symbol_id),
593 )?;
594 for (member, builtin_name) in result::extra_members() {
595 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
596 let member_symbol_id = self.symbols.intern_name(member);
597 self.symbols.add_namespace_member_by_id(
598 result_symbol_id,
599 member_symbol_id,
600 VmSymbolTable::symbol_ref(symbol_id),
601 )?;
602 }
603 }
604
605 let option_symbol_id = self.symbols.intern_namespace_path("Option")?;
606 let some_symbol_id = self.symbols.intern_wrapper("Option.Some", 2)?;
607 self.symbols
608 .intern_constant("Option.None", NanValue::NONE)?;
609 let some_member_symbol_id = self.symbols.intern_name("Some");
610 self.symbols.add_namespace_member_by_id(
611 option_symbol_id,
612 some_member_symbol_id,
613 VmSymbolTable::symbol_ref(some_symbol_id),
614 )?;
615 let none_member_symbol_id = self.symbols.intern_name("None");
616 self.symbols.add_namespace_member_by_id(
617 option_symbol_id,
618 none_member_symbol_id,
619 NanValue::NONE,
620 )?;
621 for (member, builtin_name) in option::extra_members() {
622 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
623 let member_symbol_id = self.symbols.intern_name(member);
624 self.symbols.add_namespace_member_by_id(
625 option_symbol_id,
626 member_symbol_id,
627 VmSymbolTable::symbol_ref(symbol_id),
628 )?;
629 }
630 }
631 Ok(())
632 }
633
634 fn compile_fn(
635 &mut self,
636 rfd: &ResolvedFnDef,
637 symbols: &SymbolTable,
638 arena: &mut Arena,
639 ) -> Result<FnChunk, CompileError> {
640 let empty_scope = HashMap::new();
641 self.compile_fn_with_scope(rfd, symbols, arena, &empty_scope)
642 }
643
644 fn compile_fn_with_scope(
645 &mut self,
646 rfd: &ResolvedFnDef,
647 symbols: &SymbolTable,
648 arena: &mut Arena,
649 module_scope: &HashMap<String, u32>,
650 ) -> Result<FnChunk, CompileError> {
651 let resolution = rfd.resolution.as_ref();
652 let local_count = resolution.map_or(rfd.params.len() as u16, |r| r.local_count);
653 let local_slots: HashMap<String, u16> = resolution
654 .map(|r| r.local_slots.as_ref().clone())
655 .unwrap_or_else(|| {
656 rfd.params
657 .iter()
658 .enumerate()
659 .map(|(i, (name, _))| (name.clone(), i as u16))
660 .collect()
661 });
662
663 let mut fc = FnCompiler::new(
664 &rfd.name,
665 rfd.params.len() as u8,
666 local_count,
667 rfd.effects
668 .iter()
669 .map(|effect| self.symbols.intern_name(&effect.node))
670 .collect(),
671 local_slots,
672 &self.global_names,
673 module_scope,
674 &self.code,
675 &mut self.symbols,
676 arena,
677 symbols,
678 );
679 fc.source_file = self.source_file.clone();
680 fc.note_line(rfd.line);
681 if let Some(res) = resolution {
682 fc.set_aliased_slots(res.aliased_slots.clone());
683 }
684
685 match rfd.body.as_ref() {
686 ResolvedFnBody::Block(stmts) => fc.compile_body(stmts)?,
687 }
688
689 Ok(fc.finish())
690 }
691
692 fn compile_top_level(
693 &mut self,
694 items: &[ResolvedTopLevel],
695 symbols: &SymbolTable,
696 arena: &mut Arena,
697 ) -> Result<(), CompileError> {
698 let has_stmts = items
699 .iter()
700 .any(|i| matches!(i, ResolvedTopLevel::Passthrough(TopLevel::Stmt(_))));
701 if !has_stmts {
702 return Ok(());
703 }
704
705 for item in items {
706 if let ResolvedTopLevel::Passthrough(TopLevel::Stmt(Stmt::Binding(name, _, _))) = item {
707 self.ensure_global(name);
708 }
709 }
710
711 let resolver_ctx = crate::ir::hir::ResolveCtx::new(symbols);
717
718 let empty_mod_scope = HashMap::new();
719 let mut fc = FnCompiler::new(
720 "__top_level__",
721 0,
722 0,
723 Vec::new(),
724 HashMap::new(),
725 &self.global_names,
726 &empty_mod_scope,
727 &self.code,
728 &mut self.symbols,
729 arena,
730 symbols,
731 );
732
733 for item in items {
734 if let ResolvedTopLevel::Passthrough(TopLevel::Stmt(stmt)) = item {
735 let resolved_stmt = resolve_stmt_for_top_level(&resolver_ctx, stmt);
736 match &resolved_stmt {
737 ResolvedStmt::Binding { name, value, .. } => {
738 fc.compile_expr(value)?;
739 let idx = self.global_names[name.as_str()];
740 fc.emit_op(STORE_GLOBAL);
741 fc.emit_u16(idx);
742 }
743 ResolvedStmt::Expr(value) => {
744 fc.compile_expr(value)?;
745 fc.emit_op(POP);
746 }
747 }
748 }
749 }
750
751 fc.emit_op(LOAD_UNIT);
752 fc.emit_op(RETURN);
753
754 let chunk = fc.finish();
755 self.code.add_function(chunk);
756 Ok(())
757 }
758
759 fn register_current_module_namespace(
760 &mut self,
761 items: &[ResolvedTopLevel],
762 ) -> Result<(), CompileError> {
763 let Some(module) = items.iter().find_map(|item| match item {
764 ResolvedTopLevel::Module(module) => Some(module),
765 _ => None,
766 }) else {
767 return Ok(());
768 };
769
770 let module_symbol_id = self.symbols.intern_namespace_path(&module.name)?;
771 let exposes_ref = if module.exposes.is_empty() {
772 None
773 } else {
774 Some(module.exposes.as_slice())
775 };
776
777 for item in items {
778 match item {
779 ResolvedTopLevel::FnDef(rfd) => {
780 if visibility::is_exposed(&rfd.name, exposes_ref)
781 && let Some(symbol_id) = self.symbols.find(&rfd.name)
782 {
783 let member_symbol_id = self.symbols.intern_name(&rfd.name);
784 self.symbols.add_namespace_member_by_id(
785 module_symbol_id,
786 member_symbol_id,
787 VmSymbolTable::symbol_ref(symbol_id),
788 )?;
789 }
790 }
791 ResolvedTopLevel::Passthrough(TopLevel::TypeDef(
792 TypeDef::Product { name, .. } | TypeDef::Sum { name, .. },
793 )) => {
794 if visibility::is_exposed(name, exposes_ref)
795 && let Some(symbol_id) = self.symbols.find(name)
796 {
797 let member_symbol_id = self.symbols.intern_name(name);
798 self.symbols.add_namespace_member_by_id(
799 module_symbol_id,
800 member_symbol_id,
801 VmSymbolTable::symbol_ref(symbol_id),
802 )?;
803 }
804 }
805 _ => {}
806 }
807 }
808 Ok(())
809 }
810}
811
812fn resolve_stmt_for_top_level(ctx: &crate::ir::hir::ResolveCtx<'_>, stmt: &Stmt) -> ResolvedStmt {
813 crate::ir::hir::resolve::resolve_stmt_external(ctx, stmt)
814}
815
816pub(super) enum CallTarget {
818 KnownFn(u32),
820 Wrapper(u8),
822 None_,
824 Variant(u32, u16),
826 Builtin(VmBuiltin),
828 UnknownQualified(String),
830}
831
832pub(super) struct FnCompiler<'a> {
833 name: String,
834 arity: u8,
835 local_count: u16,
836 effects: Vec<u32>,
837 pub(super) local_slots: HashMap<String, u16>,
838 global_names: &'a HashMap<String, u16>,
839 module_scope: &'a HashMap<String, u32>,
842 pub(super) code_store: &'a CodeStore,
843 pub(super) symbols: &'a mut VmSymbolTable,
844 pub(super) arena: &'a mut Arena,
845 pub(super) symbol_table: &'a SymbolTable,
855 code: Vec<u8>,
856 constants: Vec<NanValue>,
857 last_op_pos: usize,
859 source_file: String,
861 line_table: Vec<(u16, u16)>,
863 last_noted_line: u16,
865 aliased_slots: std::sync::Arc<Vec<bool>>,
874}
875
876impl<'a> FnCompiler<'a> {
877 #[allow(clippy::too_many_arguments)]
878 fn new(
879 name: &str,
880 arity: u8,
881 local_count: u16,
882 effects: Vec<u32>,
883 local_slots: HashMap<String, u16>,
884 global_names: &'a HashMap<String, u16>,
885 module_scope: &'a HashMap<String, u32>,
886 code_store: &'a CodeStore,
887 symbols: &'a mut VmSymbolTable,
888 arena: &'a mut Arena,
889 symbol_table: &'a SymbolTable,
890 ) -> Self {
891 FnCompiler {
892 name: name.to_string(),
893 arity,
894 local_count,
895 effects,
896 local_slots,
897 global_names,
898 module_scope,
899 code_store,
900 symbols,
901 arena,
902 symbol_table,
903 code: Vec::new(),
904 constants: Vec::new(),
905 last_op_pos: usize::MAX,
906 source_file: String::new(),
907 line_table: Vec::new(),
908 last_noted_line: 0,
909 aliased_slots: std::sync::Arc::new(Vec::new()),
910 }
911 }
912
913 fn set_aliased_slots(&mut self, aliased: std::sync::Arc<Vec<bool>>) {
914 self.aliased_slots = aliased;
915 }
916
917 pub(super) fn is_aliased_slot(&self, slot: u16) -> bool {
918 self.aliased_slots
919 .get(slot as usize)
920 .copied()
921 .unwrap_or(false)
922 }
923
924 pub(super) fn name(&self) -> &str {
925 &self.name
926 }
927
928 pub(super) fn global_names(&self) -> &HashMap<String, u16> {
929 self.global_names
930 }
931
932 pub(super) fn module_scope(&self) -> &HashMap<String, u32> {
933 self.module_scope
934 }
935
936 fn finish(self) -> FnChunk {
937 FnChunk {
938 name: self.name,
939 arity: self.arity,
940 local_count: self.local_count,
941 code: self.code,
942 constants: self.constants,
943 effects: self.effects,
944 thin: false,
945 parent_thin: false,
946 leaf: false,
947 no_alloc: false,
948 source_file: self.source_file,
949 line_table: self.line_table,
950 }
951 }
952
953 pub(super) fn note_line(&mut self, line: usize) {
957 if line == 0 {
958 return;
959 }
960 let line16 = line as u16;
961 if line16 == self.last_noted_line {
962 return; }
964 self.last_noted_line = line16;
965 self.line_table.push((self.code.len() as u16, line16));
966 }
967
968 pub(super) fn emit_op(&mut self, op: u8) {
969 let prev_pos = self.last_op_pos;
970 let prev_op = if prev_pos < self.code.len() {
971 self.code[prev_pos]
972 } else {
973 0xFF
974 };
975
976 if op == LOAD_LOCAL && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
978 self.code[prev_pos] = LOAD_LOCAL_2;
979 return;
981 }
982 if op == LOAD_CONST && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
984 self.code[prev_pos] = LOAD_LOCAL_CONST;
985 return;
987 }
988 if op == UNWRAP_OR && self.code.len() >= 4 {
992 let len = self.code.len();
993 if self.code[len - 4] == VECTOR_GET && self.code[len - 3] == LOAD_CONST {
994 let hi = self.code[len - 2];
995 let lo = self.code[len - 1];
996 self.code[len - 4] = VECTOR_GET_OR;
997 self.code[len - 3] = hi;
998 self.code[len - 2] = lo;
999 self.code.pop(); self.last_op_pos = len - 4;
1001 return;
1002 }
1003 }
1004 self.last_op_pos = self.code.len();
1005 self.code.push(op);
1006 }
1007
1008 pub(super) fn emit_u8(&mut self, val: u8) {
1009 self.code.push(val);
1010 }
1011
1012 pub(super) fn emit_u16(&mut self, val: u16) {
1013 self.code.push((val >> 8) as u8);
1014 self.code.push((val & 0xFF) as u8);
1015 }
1016
1017 pub(super) fn emit_i16(&mut self, val: i16) {
1018 self.emit_u16(val as u16);
1019 }
1020
1021 pub(super) fn emit_u32(&mut self, val: u32) {
1022 self.code.push((val >> 24) as u8);
1023 self.code.push(((val >> 16) & 0xFF) as u8);
1024 self.code.push(((val >> 8) & 0xFF) as u8);
1025 self.code.push((val & 0xFF) as u8);
1026 }
1027
1028 pub(super) fn emit_u64(&mut self, val: u64) {
1029 self.code.extend_from_slice(&val.to_be_bytes());
1030 }
1031
1032 pub(super) fn emit_i64(&mut self, val: i64) {
1033 self.code.extend_from_slice(&val.to_be_bytes());
1034 }
1035
1036 pub(super) fn add_constant(&mut self, val: NanValue) -> u16 {
1037 for (i, c) in self.constants.iter().enumerate() {
1038 if c.bits() == val.bits() {
1039 return i as u16;
1040 }
1041 }
1042 let idx = self.constants.len() as u16;
1043 self.constants.push(val);
1044 idx
1045 }
1046
1047 pub(super) fn offset(&self) -> usize {
1048 self.code.len()
1049 }
1050
1051 pub(super) fn code(&self) -> &Vec<u8> {
1052 &self.code
1053 }
1054
1055 pub(super) fn code_mut(&mut self) -> &mut Vec<u8> {
1056 &mut self.code
1057 }
1058
1059 pub(super) fn emit_jump(&mut self, op: u8) -> usize {
1060 self.emit_op(op);
1061 let patch_pos = self.code.len();
1062 self.emit_i16(0);
1063 patch_pos
1064 }
1065
1066 pub(super) fn patch_jump(&mut self, patch_pos: usize) {
1067 let target = self.code.len();
1068 let offset = (target as isize - patch_pos as isize - 2) as i16;
1069 let bytes = (offset as u16).to_be_bytes();
1070 self.code[patch_pos] = bytes[0];
1071 self.code[patch_pos + 1] = bytes[1];
1072 }
1073
1074 pub(super) fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
1075 let offset = (target as isize - patch_pos as isize - 2) as i16;
1076 let bytes = (offset as u16).to_be_bytes();
1077 self.code[patch_pos] = bytes[0];
1078 self.code[patch_pos + 1] = bytes[1];
1079 }
1080
1081 pub(super) fn bind_top_to_local(&mut self, name: &str) {
1082 if let Some(&slot) = self.local_slots.get(name) {
1083 self.emit_op(STORE_LOCAL);
1084 self.emit_u8(slot as u8);
1085 } else {
1086 self.emit_op(POP);
1087 }
1088 }
1089
1090 pub(super) fn dup_and_bind_top_to_local(&mut self, name: &str) {
1091 self.emit_op(DUP);
1092 self.bind_top_to_local(name);
1093 }
1094
1095 pub(super) fn install_arm_slots(
1101 &mut self,
1102 arm: &crate::ir::hir::ResolvedMatchArm,
1103 ) -> Vec<(String, Option<u16>)> {
1104 let names = collect_pattern_binding_names(&arm.pattern);
1105 let slots = arm.binding_slots.get().cloned().unwrap_or_default();
1106 let mut saved = Vec::new();
1107 for (i, name) in names.iter().enumerate() {
1108 if name == "_" {
1109 continue;
1110 }
1111 let Some(&slot) = slots.get(i) else { continue };
1112 if slot == u16::MAX {
1113 continue;
1114 }
1115 saved.push((name.clone(), self.local_slots.get(name).copied()));
1116 self.local_slots.insert(name.clone(), slot);
1117 }
1118 saved
1119 }
1120
1121 pub(super) fn restore_local_slots(&mut self, saved: Vec<(String, Option<u16>)>) {
1122 for (name, prior) in saved.into_iter().rev() {
1123 match prior {
1124 Some(slot) => {
1125 self.local_slots.insert(name, slot);
1126 }
1127 None => {
1128 self.local_slots.remove(&name);
1129 }
1130 }
1131 }
1132 }
1133}
1134
1135fn collect_pattern_binding_names(pattern: &crate::ir::hir::ResolvedPattern) -> Vec<String> {
1139 use crate::ir::hir::ResolvedPattern;
1140 match pattern {
1141 ResolvedPattern::Ident(name) => vec![name.clone()],
1142 ResolvedPattern::Cons(head, tail) => vec![head.clone(), tail.clone()],
1143 ResolvedPattern::Ctor(_, bindings) => bindings.clone(),
1144 ResolvedPattern::Tuple(items) => items
1145 .iter()
1146 .flat_map(collect_pattern_binding_names)
1147 .collect(),
1148 ResolvedPattern::Wildcard | ResolvedPattern::Literal(_) | ResolvedPattern::EmptyList => {
1149 Vec::new()
1150 }
1151 }
1152}
1153
1154#[cfg(test)]
1155mod tests {
1156 use super::compile_program;
1157 use crate::ir::SymbolTable;
1158 use crate::ir::hir::resolve_program;
1159 use crate::nan_value::Arena;
1160 use crate::source::parse_source;
1161 use crate::vm::opcode::{LT, NOT, VECTOR_GET_OR, VECTOR_SET_OR_KEEP};
1162
1163 fn compile_via_pipeline(source: &str) -> crate::vm::CodeStore {
1170 let mut items = parse_source(source).expect("source should parse");
1171 crate::ir::pipeline::tco(&mut items);
1172 crate::ir::pipeline::resolve(&mut items);
1173 let symbols = SymbolTable::build(&items, &[]);
1174 let resolved = resolve_program(&symbols, &items);
1175 let mut arena = Arena::new();
1176 let (code, _globals) =
1177 compile_program(&resolved, &symbols, &mut arena, None).expect("vm compile should pass");
1178 code
1179 }
1180
1181 #[test]
1182 fn vector_get_with_literal_default_lowers_to_vector_get_or() {
1183 let source = r#"
1184module Demo
1185
1186fn cellAt(grid: Vector<Int>, idx: Int) -> Int
1187 Option.withDefault(Vector.get(grid, idx), 0)
1188"#;
1189
1190 let code = compile_via_pipeline(source);
1191 let fn_id = code.find("cellAt").expect("cellAt should exist");
1192 let chunk = code.get(fn_id);
1193
1194 assert!(
1195 chunk.code.contains(&VECTOR_GET_OR),
1196 "expected VECTOR_GET_OR in bytecode, got {:?}",
1197 chunk.code
1198 );
1199 }
1200
1201 #[test]
1202 fn vector_set_with_same_default_lowers_to_vector_set_or_keep() {
1203 let source = r#"
1204module Demo
1205
1206fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
1207 Option.withDefault(Vector.set(vec, idx, value), vec)
1208"#;
1209
1210 let code = compile_via_pipeline(source);
1211 let fn_id = code
1212 .find("updateOrKeep")
1213 .expect("updateOrKeep should exist");
1214 let chunk = code.get(fn_id);
1215
1216 assert!(
1217 chunk.code.contains(&VECTOR_SET_OR_KEEP),
1218 "expected VECTOR_SET_OR_KEEP in bytecode, got {:?}",
1219 chunk.code
1220 );
1221 }
1222
1223 #[test]
1224 fn bool_match_on_gte_uses_base_compare_without_not() {
1225 let source = r#"
1226module Demo
1227
1228fn bucket(n: Int) -> Int
1229 match n >= 10
1230 true -> 7
1231 false -> 3
1232"#;
1233
1234 let code = compile_via_pipeline(source);
1235 let fn_id = code.find("bucket").expect("bucket should exist");
1236 let chunk = code.get(fn_id);
1237
1238 assert!(
1239 chunk.code.contains(<),
1240 "expected LT in bytecode, got {:?}",
1241 chunk.code
1242 );
1243 assert!(
1244 !chunk.code.contains(&NOT),
1245 "did not expect NOT in normalized bool-match bytecode, got {:?}",
1246 chunk.code
1247 );
1248 }
1249
1250 #[test]
1251 fn self_host_runtime_http_server_aliases_compile_in_vm() {
1252 let source = r#"
1253module Demo
1254
1255fn listen(handler: Int) -> Unit
1256 SelfHostRuntime.httpServerListen(8080, handler)
1257
1258fn listenWith(context: Int, handler: Int) -> Unit
1259 SelfHostRuntime.httpServerListenWith(8081, context, handler)
1260"#;
1261
1262 let code = compile_via_pipeline(source);
1263 assert!(code.find("listen").is_some(), "listen should compile");
1264 assert!(
1265 code.find("listenWith").is_some(),
1266 "listenWith should compile"
1267 );
1268 }
1269}