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