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
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.bootstrap_core_symbols();
271 compiler
272 }
273
274 fn sync_record_field_symbols(&mut self, arena: &Arena) {
275 for type_id in 0..arena.type_count() {
276 let type_name = arena.get_type_name(type_id);
277 self.symbols.intern_namespace_path(type_name);
278 let field_names = arena.get_field_names(type_id);
279 if field_names.is_empty() {
280 continue;
281 }
282 let field_symbol_ids: Vec<u32> = field_names
283 .iter()
284 .map(|field_name| self.symbols.intern_name(field_name))
285 .collect();
286 self.code.register_record_fields(type_id, &field_symbol_ids);
287 }
288 }
289
290 fn load_modules(
293 &mut self,
294 items: &[TopLevel],
295 module_root: &str,
296 arena: &mut Arena,
297 ) -> Result<(), CompileError> {
298 let module = items.iter().find_map(|i| {
299 if let TopLevel::Module(m) = i {
300 Some(m)
301 } else {
302 None
303 }
304 });
305 let module = match module {
306 Some(m) => m,
307 None => return Ok(()),
308 };
309
310 let modules = crate::source::load_module_tree(&module.depends, module_root)
311 .map_err(|e| CompileError { msg: e })?;
312
313 for loaded in modules {
314 self.integrate_module(&loaded.dep_name, loaded.items, arena)?;
315 }
316 Ok(())
317 }
318
319 fn integrate_module(
322 &mut self,
323 dep_name: &str,
324 mut mod_items: Vec<TopLevel>,
325 arena: &mut Arena,
326 ) -> Result<(), CompileError> {
327 crate::ir::pipeline::tco(&mut mod_items);
333 crate::ir::pipeline::resolve(&mut mod_items);
334
335 for mt in visibility::collect_module_types(&mod_items) {
337 let type_id = match &mt.kind {
338 visibility::ModuleTypeKind::Record { field_names } => {
339 arena.register_record_type(&mt.bare_name, field_names.clone())
340 }
341 visibility::ModuleTypeKind::Sum { variant_names } => {
342 arena.register_sum_type(&mt.bare_name, variant_names.clone())
343 }
344 };
345 arena.register_type_alias(
346 &visibility::qualified_name(dep_name, &mt.bare_name),
347 type_id,
348 );
349 }
350 for item in &mod_items {
351 if let TopLevel::TypeDef(td) = item {
352 self.register_type_in_symbols(td, arena);
353 }
354 }
355
356 let mut module_fn_ids: Vec<(String, u32)> = Vec::new();
358 for item in &mod_items {
359 if let TopLevel::FnDef(fndef) = item {
360 let qualified_name = visibility::qualified_name(dep_name, &fndef.name);
361 let effect_ids: Vec<u32> = fndef
362 .effects
363 .iter()
364 .map(|effect| self.symbols.intern_name(&effect.node))
365 .collect();
366 let fn_id = self.code.add_function(FnChunk {
367 name: qualified_name.clone(),
368 arity: fndef.params.len() as u8,
369 local_count: 0,
370 code: Vec::new(),
371 constants: Vec::new(),
372 effects: effect_ids,
373 thin: false,
374 parent_thin: false,
375 leaf: false,
376 no_alloc: false,
377 source_file: String::new(),
378 line_table: Vec::new(),
379 });
380 self.symbols.intern_function(
381 &qualified_name,
382 fn_id,
383 &fndef
384 .effects
385 .iter()
386 .map(|e| e.node.clone())
387 .collect::<Vec<_>>(),
388 );
389 module_fn_ids.push((fndef.name.clone(), fn_id));
390 }
391 }
392
393 let module_scope: HashMap<String, u32> = module_fn_ids.iter().cloned().collect();
394 let mut fn_idx = 0;
395 for item in &mod_items {
396 if let TopLevel::FnDef(fndef) = item {
397 let (fn_name, fn_id) = &module_fn_ids[fn_idx];
398 let mut chunk = self.compile_fn_with_scope(fndef, arena, &module_scope)?;
399 chunk.name = visibility::qualified_name(dep_name, fn_name);
400 self.code.functions[*fn_id as usize] = chunk;
401 fn_idx += 1;
402 }
403 }
404
405 let exports = visibility::collect_module_exports(&mod_items);
407
408 for fd in &exports.functions {
409 let qualified = visibility::qualified_name(dep_name, &fd.name);
410 let global_idx = self.ensure_global(&qualified);
411 let symbol_id = self.symbols.find(&qualified).ok_or_else(|| CompileError {
412 msg: format!("missing VM symbol for exposed function {}", qualified),
413 })?;
414 self.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
415 }
416
417 let module_symbol_id = self.symbols.intern_namespace_path(dep_name);
418 for et in &exports.types {
419 let type_name = match et.def {
420 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
421 };
422 if let Some(type_symbol_id) = self.symbols.find(type_name) {
423 let member_symbol_id = self.symbols.intern_name(type_name);
424 self.symbols.add_namespace_member_by_id(
425 module_symbol_id,
426 member_symbol_id,
427 VmSymbolTable::symbol_ref(type_symbol_id),
428 );
429 }
430 }
431 for fd in &exports.functions {
432 let qualified = visibility::qualified_name(dep_name, &fd.name);
433 if let Some(fn_symbol_id) = self.symbols.find(&qualified) {
434 let member_symbol_id = self.symbols.intern_name(&fd.name);
435 self.symbols.add_namespace_member_by_id(
436 module_symbol_id,
437 member_symbol_id,
438 VmSymbolTable::symbol_ref(fn_symbol_id),
439 );
440 }
441 }
442
443 Ok(())
444 }
445
446 fn install_branch_path_root_constant(&mut self, arena: &mut Arena) {
453 let Some(type_id) = arena.find_type_id(crate::types::branch_path::TYPE_NAME) else {
458 return;
459 };
460 let dewey = crate::nan_value::NanValue::new_string_value("", arena);
461 let record_idx = arena.push_record(type_id, vec![dewey]);
462 let root_value = crate::nan_value::NanValue::new_record(record_idx);
463 self.symbols.intern_constant("BranchPath.Root", root_value);
464 let namespace_symbol_id = self.symbols.intern_namespace_path("BranchPath");
465 let member_symbol_id = self.symbols.intern_name("Root");
466 self.symbols
467 .add_namespace_member_by_id(namespace_symbol_id, member_symbol_id, root_value);
468 }
469
470 fn ensure_global(&mut self, name: &str) -> u16 {
471 if let Some(&idx) = self.global_names.get(name) {
472 return idx;
473 }
474 let idx = self.globals.len() as u16;
475 self.global_names.insert(name.to_string(), idx);
476 self.globals.push(NanValue::UNIT);
477 idx
478 }
479
480 fn register_type_in_symbols(&mut self, td: &TypeDef, arena: &Arena) {
483 match td {
484 TypeDef::Product { name, fields, .. } => {
485 self.symbols.intern_namespace_path(name);
486 let type_id = arena
487 .find_type_id(name)
488 .expect("type already registered in Arena");
489 let field_symbol_ids: Vec<u32> = fields
490 .iter()
491 .map(|(field_name, _)| self.symbols.intern_name(field_name))
492 .collect();
493 self.code.register_record_fields(type_id, &field_symbol_ids);
494 }
495 TypeDef::Sum { name, variants, .. } => {
496 let type_symbol_id = self.symbols.intern_namespace_path(name);
497 let type_id = arena
498 .find_type_id(name)
499 .expect("type already registered in Arena");
500 for (variant_id, variant) in variants.iter().enumerate() {
501 let ctor_id = arena
502 .find_ctor_id(type_id, variant_id as u16)
503 .expect("ctor id");
504 let qualified_name = visibility::member_key(name, &variant.name);
505 let ctor_symbol_id = self.symbols.intern_variant_ctor(
506 &qualified_name,
507 VmVariantCtor {
508 type_id,
509 variant_id: variant_id as u16,
510 ctor_id,
511 field_count: variant.fields.len() as u8,
512 },
513 );
514 let member_symbol_id = self.symbols.intern_name(&variant.name);
515 self.symbols.add_namespace_member_by_id(
516 type_symbol_id,
517 member_symbol_id,
518 VmSymbolTable::symbol_ref(ctor_symbol_id),
519 );
520 }
521 }
522 }
523 }
524
525 fn bootstrap_core_symbols(&mut self) {
526 for builtin in VmBuiltin::ALL.iter().copied() {
527 let builtin_symbol_id = self.symbols.intern_builtin(builtin);
528 if let Some((namespace, member)) = builtin.name().split_once('.') {
529 let namespace_symbol_id = self.symbols.intern_namespace_path(namespace);
530 let member_symbol_id = self.symbols.intern_name(member);
531 self.symbols.add_namespace_member_by_id(
532 namespace_symbol_id,
533 member_symbol_id,
534 VmSymbolTable::symbol_ref(builtin_symbol_id),
535 );
536 }
537 }
538
539 let result_symbol_id = self.symbols.intern_namespace_path("Result");
540 let ok_symbol_id = self.symbols.intern_wrapper("Result.Ok", 0);
541 let err_symbol_id = self.symbols.intern_wrapper("Result.Err", 1);
542 let ok_member_symbol_id = self.symbols.intern_name("Ok");
543 self.symbols.add_namespace_member_by_id(
544 result_symbol_id,
545 ok_member_symbol_id,
546 VmSymbolTable::symbol_ref(ok_symbol_id),
547 );
548 let err_member_symbol_id = self.symbols.intern_name("Err");
549 self.symbols.add_namespace_member_by_id(
550 result_symbol_id,
551 err_member_symbol_id,
552 VmSymbolTable::symbol_ref(err_symbol_id),
553 );
554 for (member, builtin_name) in result::extra_members() {
555 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
556 let member_symbol_id = self.symbols.intern_name(member);
557 self.symbols.add_namespace_member_by_id(
558 result_symbol_id,
559 member_symbol_id,
560 VmSymbolTable::symbol_ref(symbol_id),
561 );
562 }
563 }
564
565 let option_symbol_id = self.symbols.intern_namespace_path("Option");
566 let some_symbol_id = self.symbols.intern_wrapper("Option.Some", 2);
567 self.symbols.intern_constant("Option.None", NanValue::NONE);
568 let some_member_symbol_id = self.symbols.intern_name("Some");
569 self.symbols.add_namespace_member_by_id(
570 option_symbol_id,
571 some_member_symbol_id,
572 VmSymbolTable::symbol_ref(some_symbol_id),
573 );
574 let none_member_symbol_id = self.symbols.intern_name("None");
575 self.symbols.add_namespace_member_by_id(
576 option_symbol_id,
577 none_member_symbol_id,
578 NanValue::NONE,
579 );
580 for (member, builtin_name) in option::extra_members() {
581 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
582 let member_symbol_id = self.symbols.intern_name(member);
583 self.symbols.add_namespace_member_by_id(
584 option_symbol_id,
585 member_symbol_id,
586 VmSymbolTable::symbol_ref(symbol_id),
587 );
588 }
589 }
590 }
591
592 fn compile_fn(&mut self, fndef: &FnDef, arena: &mut Arena) -> Result<FnChunk, CompileError> {
593 let empty_scope = HashMap::new();
594 self.compile_fn_with_scope(fndef, arena, &empty_scope)
595 }
596
597 fn compile_fn_with_scope(
598 &mut self,
599 fndef: &FnDef,
600 arena: &mut Arena,
601 module_scope: &HashMap<String, u32>,
602 ) -> Result<FnChunk, CompileError> {
603 let resolution = fndef.resolution.as_ref();
604 let local_count = resolution.map_or(fndef.params.len() as u16, |r| r.local_count);
605 let local_slots: HashMap<String, u16> = resolution
606 .map(|r| r.local_slots.as_ref().clone())
607 .unwrap_or_else(|| {
608 fndef
609 .params
610 .iter()
611 .enumerate()
612 .map(|(i, (name, _))| (name.clone(), i as u16))
613 .collect()
614 });
615
616 let mut fc = FnCompiler::new(
617 &fndef.name,
618 fndef.params.len() as u8,
619 local_count,
620 fndef
621 .effects
622 .iter()
623 .map(|effect| self.symbols.intern_name(&effect.node))
624 .collect(),
625 local_slots,
626 &self.global_names,
627 module_scope,
628 &self.code,
629 &mut self.symbols,
630 arena,
631 );
632 fc.source_file = self.source_file.clone();
633 fc.note_line(fndef.line);
634 if let Some(res) = resolution {
635 fc.set_aliased_slots(res.aliased_slots.clone());
636 }
637
638 match fndef.body.as_ref() {
639 FnBody::Block(stmts) => fc.compile_body(stmts)?,
640 }
641
642 Ok(fc.finish())
643 }
644
645 fn compile_top_level(
646 &mut self,
647 items: &[TopLevel],
648 arena: &mut Arena,
649 ) -> Result<(), CompileError> {
650 let has_stmts = items.iter().any(|i| matches!(i, TopLevel::Stmt(_)));
651 if !has_stmts {
652 return Ok(());
653 }
654
655 for item in items {
656 if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
657 self.ensure_global(name);
658 }
659 }
660
661 let empty_mod_scope = HashMap::new();
662 let mut fc = FnCompiler::new(
663 "__top_level__",
664 0,
665 0,
666 Vec::new(),
667 HashMap::new(),
668 &self.global_names,
669 &empty_mod_scope,
670 &self.code,
671 &mut self.symbols,
672 arena,
673 );
674
675 for item in items {
676 if let TopLevel::Stmt(stmt) = item {
677 match stmt {
678 Stmt::Binding(name, _type_ann, expr) => {
679 fc.compile_expr(expr)?;
680 let idx = self.global_names[name.as_str()];
681 fc.emit_op(STORE_GLOBAL);
682 fc.emit_u16(idx);
683 }
684 Stmt::Expr(expr) => {
685 fc.compile_expr(expr)?;
686 fc.emit_op(POP);
687 }
688 }
689 }
690 }
691
692 fc.emit_op(LOAD_UNIT);
693 fc.emit_op(RETURN);
694
695 let chunk = fc.finish();
696 self.code.add_function(chunk);
697 Ok(())
698 }
699
700 fn register_current_module_namespace(&mut self, items: &[TopLevel]) {
701 let Some(module) = items.iter().find_map(|item| {
702 if let TopLevel::Module(module) = item {
703 Some(module)
704 } else {
705 None
706 }
707 }) else {
708 return;
709 };
710
711 let module_symbol_id = self.symbols.intern_namespace_path(&module.name);
712 let exposes_ref = if module.exposes.is_empty() {
713 None
714 } else {
715 Some(module.exposes.as_slice())
716 };
717
718 for item in items {
719 match item {
720 TopLevel::FnDef(fndef) => {
721 if visibility::is_exposed(&fndef.name, exposes_ref)
722 && let Some(symbol_id) = self.symbols.find(&fndef.name)
723 {
724 let member_symbol_id = self.symbols.intern_name(&fndef.name);
725 self.symbols.add_namespace_member_by_id(
726 module_symbol_id,
727 member_symbol_id,
728 VmSymbolTable::symbol_ref(symbol_id),
729 );
730 }
731 }
732 TopLevel::TypeDef(TypeDef::Product { name, .. } | TypeDef::Sum { name, .. }) => {
733 if visibility::is_exposed(name, exposes_ref)
734 && let Some(symbol_id) = self.symbols.find(name)
735 {
736 let member_symbol_id = self.symbols.intern_name(name);
737 self.symbols.add_namespace_member_by_id(
738 module_symbol_id,
739 member_symbol_id,
740 VmSymbolTable::symbol_ref(symbol_id),
741 );
742 }
743 }
744 _ => {}
745 }
746 }
747 }
748}
749
750enum CallTarget {
752 KnownFn(u32),
754 Wrapper(u8),
756 None_,
758 Variant(u32, u16),
760 Builtin(VmBuiltin),
762 UnknownQualified(String),
764}
765
766struct FnCompiler<'a> {
767 name: String,
768 arity: u8,
769 local_count: u16,
770 effects: Vec<u32>,
771 local_slots: HashMap<String, u16>,
772 global_names: &'a HashMap<String, u16>,
773 module_scope: &'a HashMap<String, u32>,
776 code_store: &'a CodeStore,
777 symbols: &'a mut VmSymbolTable,
778 arena: &'a mut Arena,
779 code: Vec<u8>,
780 constants: Vec<NanValue>,
781 last_op_pos: usize,
783 source_file: String,
785 line_table: Vec<(u16, u16)>,
787 last_noted_line: u16,
789 aliased_slots: std::sync::Arc<Vec<bool>>,
798}
799
800impl<'a> FnCompiler<'a> {
801 #[allow(clippy::too_many_arguments)]
802 fn new(
803 name: &str,
804 arity: u8,
805 local_count: u16,
806 effects: Vec<u32>,
807 local_slots: HashMap<String, u16>,
808 global_names: &'a HashMap<String, u16>,
809 module_scope: &'a HashMap<String, u32>,
810 code_store: &'a CodeStore,
811 symbols: &'a mut VmSymbolTable,
812 arena: &'a mut Arena,
813 ) -> Self {
814 FnCompiler {
815 name: name.to_string(),
816 arity,
817 local_count,
818 effects,
819 local_slots,
820 global_names,
821 module_scope,
822 code_store,
823 symbols,
824 arena,
825 code: Vec::new(),
826 constants: Vec::new(),
827 last_op_pos: usize::MAX,
828 source_file: String::new(),
829 line_table: Vec::new(),
830 last_noted_line: 0,
831 aliased_slots: std::sync::Arc::new(Vec::new()),
832 }
833 }
834
835 fn set_aliased_slots(&mut self, aliased: std::sync::Arc<Vec<bool>>) {
836 self.aliased_slots = aliased;
837 }
838
839 pub(super) fn is_aliased_slot(&self, slot: u16) -> bool {
840 self.aliased_slots
841 .get(slot as usize)
842 .copied()
843 .unwrap_or(false)
844 }
845
846 fn finish(self) -> FnChunk {
847 FnChunk {
848 name: self.name,
849 arity: self.arity,
850 local_count: self.local_count,
851 code: self.code,
852 constants: self.constants,
853 effects: self.effects,
854 thin: false,
855 parent_thin: false,
856 leaf: false,
857 no_alloc: false,
858 source_file: self.source_file,
859 line_table: self.line_table,
860 }
861 }
862
863 fn note_line(&mut self, line: usize) {
867 if line == 0 {
868 return;
869 }
870 let line16 = line as u16;
871 if line16 == self.last_noted_line {
872 return; }
874 self.last_noted_line = line16;
875 self.line_table.push((self.code.len() as u16, line16));
876 }
877
878 fn emit_op(&mut self, op: u8) {
879 let prev_pos = self.last_op_pos;
880 let prev_op = if prev_pos < self.code.len() {
881 self.code[prev_pos]
882 } else {
883 0xFF
884 };
885
886 if op == LOAD_LOCAL && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
888 self.code[prev_pos] = LOAD_LOCAL_2;
889 return;
891 }
892 if op == LOAD_CONST && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
894 self.code[prev_pos] = LOAD_LOCAL_CONST;
895 return;
897 }
898 if op == UNWRAP_OR && self.code.len() >= 4 {
902 let len = self.code.len();
903 if self.code[len - 4] == VECTOR_GET && self.code[len - 3] == LOAD_CONST {
904 let hi = self.code[len - 2];
905 let lo = self.code[len - 1];
906 self.code[len - 4] = VECTOR_GET_OR;
907 self.code[len - 3] = hi;
908 self.code[len - 2] = lo;
909 self.code.pop(); self.last_op_pos = len - 4;
911 return;
912 }
913 }
914 self.last_op_pos = self.code.len();
915 self.code.push(op);
916 }
917
918 fn emit_u8(&mut self, val: u8) {
919 self.code.push(val);
920 }
921
922 fn emit_u16(&mut self, val: u16) {
923 self.code.push((val >> 8) as u8);
924 self.code.push((val & 0xFF) as u8);
925 }
926
927 fn emit_i16(&mut self, val: i16) {
928 self.emit_u16(val as u16);
929 }
930
931 fn emit_u32(&mut self, val: u32) {
932 self.code.push((val >> 24) as u8);
933 self.code.push(((val >> 16) & 0xFF) as u8);
934 self.code.push(((val >> 8) & 0xFF) as u8);
935 self.code.push((val & 0xFF) as u8);
936 }
937
938 fn emit_u64(&mut self, val: u64) {
939 self.code.extend_from_slice(&val.to_be_bytes());
940 }
941
942 fn emit_i64(&mut self, val: i64) {
943 self.code.extend_from_slice(&val.to_be_bytes());
944 }
945
946 fn add_constant(&mut self, val: NanValue) -> u16 {
947 for (i, c) in self.constants.iter().enumerate() {
948 if c.bits() == val.bits() {
949 return i as u16;
950 }
951 }
952 let idx = self.constants.len() as u16;
953 self.constants.push(val);
954 idx
955 }
956
957 fn offset(&self) -> usize {
958 self.code.len()
959 }
960
961 fn emit_jump(&mut self, op: u8) -> usize {
962 self.emit_op(op);
963 let patch_pos = self.code.len();
964 self.emit_i16(0);
965 patch_pos
966 }
967
968 fn patch_jump(&mut self, patch_pos: usize) {
969 let target = self.code.len();
970 let offset = (target as isize - patch_pos as isize - 2) as i16;
971 let bytes = (offset as u16).to_be_bytes();
972 self.code[patch_pos] = bytes[0];
973 self.code[patch_pos + 1] = bytes[1];
974 }
975
976 fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
977 let offset = (target as isize - patch_pos as isize - 2) as i16;
978 let bytes = (offset as u16).to_be_bytes();
979 self.code[patch_pos] = bytes[0];
980 self.code[patch_pos + 1] = bytes[1];
981 }
982
983 fn bind_top_to_local(&mut self, name: &str) {
984 if let Some(&slot) = self.local_slots.get(name) {
985 self.emit_op(STORE_LOCAL);
986 self.emit_u8(slot as u8);
987 } else {
988 self.emit_op(POP);
989 }
990 }
991
992 fn dup_and_bind_top_to_local(&mut self, name: &str) {
993 self.emit_op(DUP);
994 self.bind_top_to_local(name);
995 }
996
997 pub(super) fn install_arm_slots(
1003 &mut self,
1004 arm: &crate::ast::MatchArm,
1005 ) -> Vec<(String, Option<u16>)> {
1006 let names = collect_pattern_binding_names(&arm.pattern);
1007 let slots = arm.binding_slots.get().cloned().unwrap_or_default();
1008 let mut saved = Vec::new();
1009 for (i, name) in names.iter().enumerate() {
1010 if name == "_" {
1011 continue;
1012 }
1013 let Some(&slot) = slots.get(i) else { continue };
1014 if slot == u16::MAX {
1015 continue;
1016 }
1017 saved.push((name.clone(), self.local_slots.get(name).copied()));
1018 self.local_slots.insert(name.clone(), slot);
1019 }
1020 saved
1021 }
1022
1023 pub(super) fn restore_local_slots(&mut self, saved: Vec<(String, Option<u16>)>) {
1024 for (name, prior) in saved.into_iter().rev() {
1025 match prior {
1026 Some(slot) => {
1027 self.local_slots.insert(name, slot);
1028 }
1029 None => {
1030 self.local_slots.remove(&name);
1031 }
1032 }
1033 }
1034 }
1035}
1036
1037fn collect_pattern_binding_names(pattern: &crate::ast::Pattern) -> Vec<String> {
1041 use crate::ast::Pattern;
1042 match pattern {
1043 Pattern::Ident(name) => vec![name.clone()],
1044 Pattern::Cons(head, tail) => vec![head.clone(), tail.clone()],
1045 Pattern::Constructor(_, bindings) => bindings.clone(),
1046 Pattern::Tuple(items) => items
1047 .iter()
1048 .flat_map(collect_pattern_binding_names)
1049 .collect(),
1050 Pattern::Wildcard | Pattern::Literal(_) | Pattern::EmptyList => Vec::new(),
1051 }
1052}
1053
1054#[cfg(test)]
1055mod tests {
1056 use super::compile_program;
1057 use crate::nan_value::Arena;
1058 use crate::source::parse_source;
1059 use crate::vm::opcode::{LT, NOT, VECTOR_GET_OR, VECTOR_SET_OR_KEEP};
1060
1061 #[test]
1062 fn vector_get_with_literal_default_lowers_to_vector_get_or() {
1063 let source = r#"
1064module Demo
1065
1066fn cellAt(grid: Vector<Int>, idx: Int) -> Int
1067 Option.withDefault(Vector.get(grid, idx), 0)
1068"#;
1069
1070 let mut items = parse_source(source).expect("source should parse");
1071 crate::ir::pipeline::tco(&mut items);
1072 crate::ir::pipeline::resolve(&mut items);
1073
1074 let mut arena = Arena::new();
1075 let (code, _globals) =
1076 compile_program(&items, &mut arena, None).expect("vm compile should pass");
1077 let fn_id = code.find("cellAt").expect("cellAt should exist");
1078 let chunk = code.get(fn_id);
1079
1080 assert!(
1081 chunk.code.contains(&VECTOR_GET_OR),
1082 "expected VECTOR_GET_OR in bytecode, got {:?}",
1083 chunk.code
1084 );
1085 }
1086
1087 #[test]
1088 fn vector_set_with_same_default_lowers_to_vector_set_or_keep() {
1089 let source = r#"
1090module Demo
1091
1092fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
1093 Option.withDefault(Vector.set(vec, idx, value), vec)
1094"#;
1095
1096 let mut items = parse_source(source).expect("source should parse");
1097 crate::ir::pipeline::tco(&mut items);
1098 crate::ir::pipeline::resolve(&mut items);
1099
1100 let mut arena = Arena::new();
1101 let (code, _globals) =
1102 compile_program(&items, &mut arena, None).expect("vm compile should pass");
1103 let fn_id = code
1104 .find("updateOrKeep")
1105 .expect("updateOrKeep should exist");
1106 let chunk = code.get(fn_id);
1107
1108 assert!(
1109 chunk.code.contains(&VECTOR_SET_OR_KEEP),
1110 "expected VECTOR_SET_OR_KEEP in bytecode, got {:?}",
1111 chunk.code
1112 );
1113 }
1114
1115 #[test]
1116 fn bool_match_on_gte_uses_base_compare_without_not() {
1117 let source = r#"
1118module Demo
1119
1120fn bucket(n: Int) -> Int
1121 match n >= 10
1122 true -> 7
1123 false -> 3
1124"#;
1125
1126 let mut items = parse_source(source).expect("source should parse");
1127 crate::ir::pipeline::tco(&mut items);
1128 crate::ir::pipeline::resolve(&mut items);
1129
1130 let mut arena = Arena::new();
1131 let (code, _globals) =
1132 compile_program(&items, &mut arena, None).expect("vm compile should pass");
1133 let fn_id = code.find("bucket").expect("bucket should exist");
1134 let chunk = code.get(fn_id);
1135
1136 assert!(
1137 chunk.code.contains(<),
1138 "expected LT in bytecode, got {:?}",
1139 chunk.code
1140 );
1141 assert!(
1142 !chunk.code.contains(&NOT),
1143 "did not expect NOT in normalized bool-match bytecode, got {:?}",
1144 chunk.code
1145 );
1146 }
1147
1148 #[test]
1149 fn self_host_runtime_http_server_aliases_compile_in_vm() {
1150 let source = r#"
1151module Demo
1152
1153fn listen(handler: Int) -> Unit
1154 SelfHostRuntime.httpServerListen(8080, handler)
1155
1156fn listenWith(context: Int, handler: Int) -> Unit
1157 SelfHostRuntime.httpServerListenWith(8081, context, handler)
1158"#;
1159
1160 let mut items = parse_source(source).expect("source should parse");
1161 crate::ir::pipeline::tco(&mut items);
1162 crate::ir::pipeline::resolve(&mut items);
1163
1164 let mut arena = Arena::new();
1165 let (code, _globals) =
1166 compile_program(&items, &mut arena, None).expect("vm compile should pass");
1167 assert!(code.find("listen").is_some(), "listen should compile");
1168 assert!(
1169 code.find("listenWith").is_some(),
1170 "listenWith should compile"
1171 );
1172 }
1173}