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(
21 items: &[TopLevel],
22 arena: &mut Arena,
23) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
24 compile_program_with_modules(items, arena, None, "")
25}
26
27pub fn compile_program_with_modules(
29 items: &[TopLevel],
30 arena: &mut Arena,
31 module_root: Option<&str>,
32 source_file: &str,
33) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
34 compile_program_inner(items, arena, source_file, ModuleSource::Disk(module_root))
35}
36
37pub fn compile_program_with_loaded_modules(
41 items: &[TopLevel],
42 arena: &mut Arena,
43 loaded: Vec<crate::source::LoadedModule>,
44 source_file: &str,
45) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
46 compile_program_inner(items, arena, source_file, ModuleSource::Loaded(loaded))
47}
48
49enum ModuleSource<'a> {
50 Disk(Option<&'a str>),
51 Loaded(Vec<crate::source::LoadedModule>),
52}
53
54fn compile_program_inner(
55 items: &[TopLevel],
56 arena: &mut Arena,
57 source_file: &str,
58 module_source: ModuleSource<'_>,
59) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
60 let mut compiler = ProgramCompiler::new();
61 compiler.source_file = source_file.to_string();
62 compiler.sync_record_field_symbols(arena);
63 compiler.install_branch_path_root_constant(arena);
69
70 match module_source {
71 ModuleSource::Disk(Some(module_root)) => {
72 compiler.load_modules(items, module_root, arena)?;
73 }
74 ModuleSource::Disk(None) => {}
75 ModuleSource::Loaded(loaded) => {
76 for m in loaded {
77 compiler.integrate_module(&m.dep_name, m.items, arena)?;
78 }
79 }
80 }
81
82 for item in items {
83 if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
84 compiler.ensure_global(name);
85 }
86 }
87
88 for item in items {
89 match item {
90 TopLevel::FnDef(fndef) => {
91 compiler.ensure_global(&fndef.name);
92 let effect_ids: Vec<u32> = fndef
93 .effects
94 .iter()
95 .map(|effect| compiler.symbols.intern_name(&effect.node))
96 .collect();
97 let fn_id = compiler.code.add_function(FnChunk {
98 name: fndef.name.clone(),
99 arity: fndef.params.len() as u8,
100 local_count: 0,
101 code: Vec::new(),
102 constants: Vec::new(),
103 effects: effect_ids,
104 thin: false,
105 parent_thin: false,
106 leaf: false,
107 no_alloc: false,
108 source_file: String::new(),
109 line_table: Vec::new(),
110 });
111 let symbol_id = compiler.symbols.intern_function(
112 &fndef.name,
113 fn_id,
114 &fndef
115 .effects
116 .iter()
117 .map(|e| e.node.clone())
118 .collect::<Vec<_>>(),
119 );
120 let global_idx = compiler.global_names[&fndef.name];
121 compiler.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
122 }
123 TopLevel::TypeDef(td) => {
124 match td {
126 TypeDef::Product { name, fields, .. } => {
127 let field_names: Vec<String> =
128 fields.iter().map(|(n, _)| n.clone()).collect();
129 arena.register_record_type(name, field_names);
130 }
131 TypeDef::Sum { name, variants, .. } => {
132 let variant_names: Vec<String> =
133 variants.iter().map(|v| v.name.clone()).collect();
134 arena.register_sum_type(name, variant_names);
135 }
136 }
137 compiler.register_type_in_symbols(td, arena);
139 }
140 _ => {}
141 }
142 }
143
144 compiler.register_current_module_namespace(items);
145
146 for item in items {
147 if let TopLevel::FnDef(fndef) = item {
148 let fn_id = compiler.code.find(&fndef.name).unwrap();
149 let chunk = compiler.compile_fn(fndef, arena)?;
150 compiler.code.functions[fn_id as usize] = chunk;
151 }
152 }
153
154 compiler.compile_top_level(items, arena)?;
155 compiler.code.symbols = compiler.symbols.clone();
156 classify::classify_thin_functions(&mut compiler.code, arena)?;
157
158 let user_fn_defs: Vec<&crate::ast::FnDef> = items
165 .iter()
166 .filter_map(|item| {
167 if let TopLevel::FnDef(fd) = item {
168 Some(fd)
169 } else {
170 None
171 }
172 })
173 .collect();
174 if !user_fn_defs.is_empty() {
175 let policy = super::VmAllocPolicy;
176 let alloc_info = crate::ir::compute_alloc_info(&user_fn_defs, &policy);
177 for fd in &user_fn_defs {
178 let allocates = *alloc_info.get(&fd.name).unwrap_or(&true);
179 if !allocates && let Some(fn_id) = compiler.code.find(&fd.name) {
180 let chunk = &mut compiler.code.functions[fn_id as usize];
181 chunk.no_alloc = true;
182 chunk.thin = true;
189 }
190 }
191 }
192
193 Ok((compiler.code, compiler.globals))
194}
195
196#[derive(Debug)]
197pub struct CompileError {
198 pub msg: String,
199}
200
201impl std::fmt::Display for CompileError {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 write!(f, "Compile error: {}", self.msg)
204 }
205}
206
207struct ProgramCompiler {
208 code: CodeStore,
209 symbols: VmSymbolTable,
210 globals: Vec<NanValue>,
211 global_names: HashMap<String, u16>,
212 source_file: String,
214}
215
216impl ProgramCompiler {
217 fn new() -> Self {
218 let mut compiler = ProgramCompiler {
219 code: CodeStore::new(),
220 symbols: VmSymbolTable::default(),
221 globals: Vec::new(),
222 global_names: HashMap::new(),
223 source_file: String::new(),
224 };
225 compiler.bootstrap_core_symbols();
226 compiler
227 }
228
229 fn sync_record_field_symbols(&mut self, arena: &Arena) {
230 for type_id in 0..arena.type_count() {
231 let type_name = arena.get_type_name(type_id);
232 self.symbols.intern_namespace_path(type_name);
233 let field_names = arena.get_field_names(type_id);
234 if field_names.is_empty() {
235 continue;
236 }
237 let field_symbol_ids: Vec<u32> = field_names
238 .iter()
239 .map(|field_name| self.symbols.intern_name(field_name))
240 .collect();
241 self.code.register_record_fields(type_id, &field_symbol_ids);
242 }
243 }
244
245 fn load_modules(
248 &mut self,
249 items: &[TopLevel],
250 module_root: &str,
251 arena: &mut Arena,
252 ) -> Result<(), CompileError> {
253 let module = items.iter().find_map(|i| {
254 if let TopLevel::Module(m) = i {
255 Some(m)
256 } else {
257 None
258 }
259 });
260 let module = match module {
261 Some(m) => m,
262 None => return Ok(()),
263 };
264
265 let modules = crate::source::load_module_tree(&module.depends, module_root)
266 .map_err(|e| CompileError { msg: e })?;
267
268 for loaded in modules {
269 self.integrate_module(&loaded.dep_name, loaded.items, arena)?;
270 }
271 Ok(())
272 }
273
274 fn integrate_module(
277 &mut self,
278 dep_name: &str,
279 mut mod_items: Vec<TopLevel>,
280 arena: &mut Arena,
281 ) -> Result<(), CompileError> {
282 crate::tco::transform_program(&mut mod_items);
283 crate::resolver::resolve_program(&mut mod_items);
284
285 for mt in visibility::collect_module_types(&mod_items) {
287 let type_id = match &mt.kind {
288 visibility::ModuleTypeKind::Record { field_names } => {
289 arena.register_record_type(&mt.bare_name, field_names.clone())
290 }
291 visibility::ModuleTypeKind::Sum { variant_names } => {
292 arena.register_sum_type(&mt.bare_name, variant_names.clone())
293 }
294 };
295 arena.register_type_alias(
296 &visibility::qualified_name(dep_name, &mt.bare_name),
297 type_id,
298 );
299 }
300 for item in &mod_items {
301 if let TopLevel::TypeDef(td) = item {
302 self.register_type_in_symbols(td, arena);
303 }
304 }
305
306 let mut module_fn_ids: Vec<(String, u32)> = Vec::new();
308 for item in &mod_items {
309 if let TopLevel::FnDef(fndef) = item {
310 let qualified_name = visibility::qualified_name(dep_name, &fndef.name);
311 let effect_ids: Vec<u32> = fndef
312 .effects
313 .iter()
314 .map(|effect| self.symbols.intern_name(&effect.node))
315 .collect();
316 let fn_id = self.code.add_function(FnChunk {
317 name: qualified_name.clone(),
318 arity: fndef.params.len() as u8,
319 local_count: 0,
320 code: Vec::new(),
321 constants: Vec::new(),
322 effects: effect_ids,
323 thin: false,
324 parent_thin: false,
325 leaf: false,
326 no_alloc: false,
327 source_file: String::new(),
328 line_table: Vec::new(),
329 });
330 self.symbols.intern_function(
331 &qualified_name,
332 fn_id,
333 &fndef
334 .effects
335 .iter()
336 .map(|e| e.node.clone())
337 .collect::<Vec<_>>(),
338 );
339 module_fn_ids.push((fndef.name.clone(), fn_id));
340 }
341 }
342
343 let module_scope: HashMap<String, u32> = module_fn_ids.iter().cloned().collect();
344 let mut fn_idx = 0;
345 for item in &mod_items {
346 if let TopLevel::FnDef(fndef) = item {
347 let (fn_name, fn_id) = &module_fn_ids[fn_idx];
348 let mut chunk = self.compile_fn_with_scope(fndef, arena, &module_scope)?;
349 chunk.name = visibility::qualified_name(dep_name, fn_name);
350 self.code.functions[*fn_id as usize] = chunk;
351 fn_idx += 1;
352 }
353 }
354
355 let exports = visibility::collect_module_exports(&mod_items);
357
358 for fd in &exports.functions {
359 let qualified = visibility::qualified_name(dep_name, &fd.name);
360 let global_idx = self.ensure_global(&qualified);
361 let symbol_id = self.symbols.find(&qualified).ok_or_else(|| CompileError {
362 msg: format!("missing VM symbol for exposed function {}", qualified),
363 })?;
364 self.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
365 }
366
367 let module_symbol_id = self.symbols.intern_namespace_path(dep_name);
368 for et in &exports.types {
369 let type_name = match et.def {
370 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
371 };
372 if let Some(type_symbol_id) = self.symbols.find(type_name) {
373 let member_symbol_id = self.symbols.intern_name(type_name);
374 self.symbols.add_namespace_member_by_id(
375 module_symbol_id,
376 member_symbol_id,
377 VmSymbolTable::symbol_ref(type_symbol_id),
378 );
379 }
380 }
381 for fd in &exports.functions {
382 let qualified = visibility::qualified_name(dep_name, &fd.name);
383 if let Some(fn_symbol_id) = self.symbols.find(&qualified) {
384 let member_symbol_id = self.symbols.intern_name(&fd.name);
385 self.symbols.add_namespace_member_by_id(
386 module_symbol_id,
387 member_symbol_id,
388 VmSymbolTable::symbol_ref(fn_symbol_id),
389 );
390 }
391 }
392
393 Ok(())
394 }
395
396 fn install_branch_path_root_constant(&mut self, arena: &mut Arena) {
403 let Some(type_id) = arena.find_type_id(crate::types::branch_path::TYPE_NAME) else {
408 return;
409 };
410 let dewey = crate::nan_value::NanValue::new_string_value("", arena);
411 let record_idx = arena.push_record(type_id, vec![dewey]);
412 let root_value = crate::nan_value::NanValue::new_record(record_idx);
413 self.symbols.intern_constant("BranchPath.Root", root_value);
414 let namespace_symbol_id = self.symbols.intern_namespace_path("BranchPath");
415 let member_symbol_id = self.symbols.intern_name("Root");
416 self.symbols
417 .add_namespace_member_by_id(namespace_symbol_id, member_symbol_id, root_value);
418 }
419
420 fn ensure_global(&mut self, name: &str) -> u16 {
421 if let Some(&idx) = self.global_names.get(name) {
422 return idx;
423 }
424 let idx = self.globals.len() as u16;
425 self.global_names.insert(name.to_string(), idx);
426 self.globals.push(NanValue::UNIT);
427 idx
428 }
429
430 fn register_type_in_symbols(&mut self, td: &TypeDef, arena: &Arena) {
433 match td {
434 TypeDef::Product { name, fields, .. } => {
435 self.symbols.intern_namespace_path(name);
436 let type_id = arena
437 .find_type_id(name)
438 .expect("type already registered in Arena");
439 let field_symbol_ids: Vec<u32> = fields
440 .iter()
441 .map(|(field_name, _)| self.symbols.intern_name(field_name))
442 .collect();
443 self.code.register_record_fields(type_id, &field_symbol_ids);
444 }
445 TypeDef::Sum { name, variants, .. } => {
446 let type_symbol_id = self.symbols.intern_namespace_path(name);
447 let type_id = arena
448 .find_type_id(name)
449 .expect("type already registered in Arena");
450 for (variant_id, variant) in variants.iter().enumerate() {
451 let ctor_id = arena
452 .find_ctor_id(type_id, variant_id as u16)
453 .expect("ctor id");
454 let qualified_name = visibility::member_key(name, &variant.name);
455 let ctor_symbol_id = self.symbols.intern_variant_ctor(
456 &qualified_name,
457 VmVariantCtor {
458 type_id,
459 variant_id: variant_id as u16,
460 ctor_id,
461 field_count: variant.fields.len() as u8,
462 },
463 );
464 let member_symbol_id = self.symbols.intern_name(&variant.name);
465 self.symbols.add_namespace_member_by_id(
466 type_symbol_id,
467 member_symbol_id,
468 VmSymbolTable::symbol_ref(ctor_symbol_id),
469 );
470 }
471 }
472 }
473 }
474
475 fn bootstrap_core_symbols(&mut self) {
476 for builtin in VmBuiltin::ALL.iter().copied() {
477 let builtin_symbol_id = self.symbols.intern_builtin(builtin);
478 if let Some((namespace, member)) = builtin.name().split_once('.') {
479 let namespace_symbol_id = self.symbols.intern_namespace_path(namespace);
480 let member_symbol_id = self.symbols.intern_name(member);
481 self.symbols.add_namespace_member_by_id(
482 namespace_symbol_id,
483 member_symbol_id,
484 VmSymbolTable::symbol_ref(builtin_symbol_id),
485 );
486 }
487 }
488
489 let result_symbol_id = self.symbols.intern_namespace_path("Result");
490 let ok_symbol_id = self.symbols.intern_wrapper("Result.Ok", 0);
491 let err_symbol_id = self.symbols.intern_wrapper("Result.Err", 1);
492 let ok_member_symbol_id = self.symbols.intern_name("Ok");
493 self.symbols.add_namespace_member_by_id(
494 result_symbol_id,
495 ok_member_symbol_id,
496 VmSymbolTable::symbol_ref(ok_symbol_id),
497 );
498 let err_member_symbol_id = self.symbols.intern_name("Err");
499 self.symbols.add_namespace_member_by_id(
500 result_symbol_id,
501 err_member_symbol_id,
502 VmSymbolTable::symbol_ref(err_symbol_id),
503 );
504 for (member, builtin_name) in result::extra_members() {
505 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
506 let member_symbol_id = self.symbols.intern_name(member);
507 self.symbols.add_namespace_member_by_id(
508 result_symbol_id,
509 member_symbol_id,
510 VmSymbolTable::symbol_ref(symbol_id),
511 );
512 }
513 }
514
515 let option_symbol_id = self.symbols.intern_namespace_path("Option");
516 let some_symbol_id = self.symbols.intern_wrapper("Option.Some", 2);
517 self.symbols.intern_constant("Option.None", NanValue::NONE);
518 let some_member_symbol_id = self.symbols.intern_name("Some");
519 self.symbols.add_namespace_member_by_id(
520 option_symbol_id,
521 some_member_symbol_id,
522 VmSymbolTable::symbol_ref(some_symbol_id),
523 );
524 let none_member_symbol_id = self.symbols.intern_name("None");
525 self.symbols.add_namespace_member_by_id(
526 option_symbol_id,
527 none_member_symbol_id,
528 NanValue::NONE,
529 );
530 for (member, builtin_name) in option::extra_members() {
531 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
532 let member_symbol_id = self.symbols.intern_name(member);
533 self.symbols.add_namespace_member_by_id(
534 option_symbol_id,
535 member_symbol_id,
536 VmSymbolTable::symbol_ref(symbol_id),
537 );
538 }
539 }
540 }
541
542 fn compile_fn(&mut self, fndef: &FnDef, arena: &mut Arena) -> Result<FnChunk, CompileError> {
543 let empty_scope = HashMap::new();
544 self.compile_fn_with_scope(fndef, arena, &empty_scope)
545 }
546
547 fn compile_fn_with_scope(
548 &mut self,
549 fndef: &FnDef,
550 arena: &mut Arena,
551 module_scope: &HashMap<String, u32>,
552 ) -> Result<FnChunk, CompileError> {
553 let resolution = fndef.resolution.as_ref();
554 let local_count = resolution.map_or(fndef.params.len() as u16, |r| r.local_count);
555 let local_slots: HashMap<String, u16> = resolution
556 .map(|r| r.local_slots.as_ref().clone())
557 .unwrap_or_else(|| {
558 fndef
559 .params
560 .iter()
561 .enumerate()
562 .map(|(i, (name, _))| (name.clone(), i as u16))
563 .collect()
564 });
565
566 let mut fc = FnCompiler::new(
567 &fndef.name,
568 fndef.params.len() as u8,
569 local_count,
570 fndef
571 .effects
572 .iter()
573 .map(|effect| self.symbols.intern_name(&effect.node))
574 .collect(),
575 local_slots,
576 &self.global_names,
577 module_scope,
578 &self.code,
579 &mut self.symbols,
580 arena,
581 );
582 fc.source_file = self.source_file.clone();
583 fc.note_line(fndef.line);
584
585 match fndef.body.as_ref() {
586 FnBody::Block(stmts) => fc.compile_body(stmts)?,
587 }
588
589 Ok(fc.finish())
590 }
591
592 fn compile_top_level(
593 &mut self,
594 items: &[TopLevel],
595 arena: &mut Arena,
596 ) -> Result<(), CompileError> {
597 let has_stmts = items.iter().any(|i| matches!(i, TopLevel::Stmt(_)));
598 if !has_stmts {
599 return Ok(());
600 }
601
602 for item in items {
603 if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
604 self.ensure_global(name);
605 }
606 }
607
608 let empty_mod_scope = HashMap::new();
609 let mut fc = FnCompiler::new(
610 "__top_level__",
611 0,
612 0,
613 Vec::new(),
614 HashMap::new(),
615 &self.global_names,
616 &empty_mod_scope,
617 &self.code,
618 &mut self.symbols,
619 arena,
620 );
621
622 for item in items {
623 if let TopLevel::Stmt(stmt) = item {
624 match stmt {
625 Stmt::Binding(name, _type_ann, expr) => {
626 fc.compile_expr(expr)?;
627 let idx = self.global_names[name.as_str()];
628 fc.emit_op(STORE_GLOBAL);
629 fc.emit_u16(idx);
630 }
631 Stmt::Expr(expr) => {
632 fc.compile_expr(expr)?;
633 fc.emit_op(POP);
634 }
635 }
636 }
637 }
638
639 fc.emit_op(LOAD_UNIT);
640 fc.emit_op(RETURN);
641
642 let chunk = fc.finish();
643 self.code.add_function(chunk);
644 Ok(())
645 }
646
647 fn register_current_module_namespace(&mut self, items: &[TopLevel]) {
648 let Some(module) = items.iter().find_map(|item| {
649 if let TopLevel::Module(module) = item {
650 Some(module)
651 } else {
652 None
653 }
654 }) else {
655 return;
656 };
657
658 let module_symbol_id = self.symbols.intern_namespace_path(&module.name);
659 let exposes_ref = if module.exposes.is_empty() {
660 None
661 } else {
662 Some(module.exposes.as_slice())
663 };
664
665 for item in items {
666 match item {
667 TopLevel::FnDef(fndef) => {
668 if visibility::is_exposed(&fndef.name, exposes_ref)
669 && let Some(symbol_id) = self.symbols.find(&fndef.name)
670 {
671 let member_symbol_id = self.symbols.intern_name(&fndef.name);
672 self.symbols.add_namespace_member_by_id(
673 module_symbol_id,
674 member_symbol_id,
675 VmSymbolTable::symbol_ref(symbol_id),
676 );
677 }
678 }
679 TopLevel::TypeDef(TypeDef::Product { name, .. } | TypeDef::Sum { name, .. }) => {
680 if visibility::is_exposed(name, exposes_ref)
681 && let Some(symbol_id) = self.symbols.find(name)
682 {
683 let member_symbol_id = self.symbols.intern_name(name);
684 self.symbols.add_namespace_member_by_id(
685 module_symbol_id,
686 member_symbol_id,
687 VmSymbolTable::symbol_ref(symbol_id),
688 );
689 }
690 }
691 _ => {}
692 }
693 }
694 }
695}
696
697enum CallTarget {
699 KnownFn(u32),
701 Wrapper(u8),
703 None_,
705 Variant(u32, u16),
707 Builtin(VmBuiltin),
709 UnknownQualified(String),
711}
712
713struct FnCompiler<'a> {
714 name: String,
715 arity: u8,
716 local_count: u16,
717 effects: Vec<u32>,
718 local_slots: HashMap<String, u16>,
719 global_names: &'a HashMap<String, u16>,
720 module_scope: &'a HashMap<String, u32>,
723 code_store: &'a CodeStore,
724 symbols: &'a mut VmSymbolTable,
725 arena: &'a mut Arena,
726 code: Vec<u8>,
727 constants: Vec<NanValue>,
728 last_op_pos: usize,
730 source_file: String,
732 line_table: Vec<(u16, u16)>,
734 last_noted_line: u16,
736}
737
738impl<'a> FnCompiler<'a> {
739 #[allow(clippy::too_many_arguments)]
740 fn new(
741 name: &str,
742 arity: u8,
743 local_count: u16,
744 effects: Vec<u32>,
745 local_slots: HashMap<String, u16>,
746 global_names: &'a HashMap<String, u16>,
747 module_scope: &'a HashMap<String, u32>,
748 code_store: &'a CodeStore,
749 symbols: &'a mut VmSymbolTable,
750 arena: &'a mut Arena,
751 ) -> Self {
752 FnCompiler {
753 name: name.to_string(),
754 arity,
755 local_count,
756 effects,
757 local_slots,
758 global_names,
759 module_scope,
760 code_store,
761 symbols,
762 arena,
763 code: Vec::new(),
764 constants: Vec::new(),
765 last_op_pos: usize::MAX,
766 source_file: String::new(),
767 line_table: Vec::new(),
768 last_noted_line: 0,
769 }
770 }
771
772 fn finish(self) -> FnChunk {
773 FnChunk {
774 name: self.name,
775 arity: self.arity,
776 local_count: self.local_count,
777 code: self.code,
778 constants: self.constants,
779 effects: self.effects,
780 thin: false,
781 parent_thin: false,
782 leaf: false,
783 no_alloc: false,
784 source_file: self.source_file,
785 line_table: self.line_table,
786 }
787 }
788
789 fn note_line(&mut self, line: usize) {
793 if line == 0 {
794 return;
795 }
796 let line16 = line as u16;
797 if line16 == self.last_noted_line {
798 return; }
800 self.last_noted_line = line16;
801 self.line_table.push((self.code.len() as u16, line16));
802 }
803
804 fn emit_op(&mut self, op: u8) {
805 let prev_pos = self.last_op_pos;
806 let prev_op = if prev_pos < self.code.len() {
807 self.code[prev_pos]
808 } else {
809 0xFF
810 };
811
812 if op == LOAD_LOCAL && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
814 self.code[prev_pos] = LOAD_LOCAL_2;
815 return;
817 }
818 if op == LOAD_CONST && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
820 self.code[prev_pos] = LOAD_LOCAL_CONST;
821 return;
823 }
824 if op == UNWRAP_OR && self.code.len() >= 4 {
828 let len = self.code.len();
829 if self.code[len - 4] == VECTOR_GET && self.code[len - 3] == LOAD_CONST {
830 let hi = self.code[len - 2];
831 let lo = self.code[len - 1];
832 self.code[len - 4] = VECTOR_GET_OR;
833 self.code[len - 3] = hi;
834 self.code[len - 2] = lo;
835 self.code.pop(); self.last_op_pos = len - 4;
837 return;
838 }
839 }
840 self.last_op_pos = self.code.len();
841 self.code.push(op);
842 }
843
844 fn emit_u8(&mut self, val: u8) {
845 self.code.push(val);
846 }
847
848 fn emit_u16(&mut self, val: u16) {
849 self.code.push((val >> 8) as u8);
850 self.code.push((val & 0xFF) as u8);
851 }
852
853 fn emit_i16(&mut self, val: i16) {
854 self.emit_u16(val as u16);
855 }
856
857 fn emit_u32(&mut self, val: u32) {
858 self.code.push((val >> 24) as u8);
859 self.code.push(((val >> 16) & 0xFF) as u8);
860 self.code.push(((val >> 8) & 0xFF) as u8);
861 self.code.push((val & 0xFF) as u8);
862 }
863
864 fn emit_u64(&mut self, val: u64) {
865 self.code.extend_from_slice(&val.to_be_bytes());
866 }
867
868 fn add_constant(&mut self, val: NanValue) -> u16 {
869 for (i, c) in self.constants.iter().enumerate() {
870 if c.bits() == val.bits() {
871 return i as u16;
872 }
873 }
874 let idx = self.constants.len() as u16;
875 self.constants.push(val);
876 idx
877 }
878
879 fn offset(&self) -> usize {
880 self.code.len()
881 }
882
883 fn emit_jump(&mut self, op: u8) -> usize {
884 self.emit_op(op);
885 let patch_pos = self.code.len();
886 self.emit_i16(0);
887 patch_pos
888 }
889
890 fn patch_jump(&mut self, patch_pos: usize) {
891 let target = self.code.len();
892 let offset = (target as isize - patch_pos as isize - 2) as i16;
893 let bytes = (offset as u16).to_be_bytes();
894 self.code[patch_pos] = bytes[0];
895 self.code[patch_pos + 1] = bytes[1];
896 }
897
898 fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
899 let offset = (target as isize - patch_pos as isize - 2) as i16;
900 let bytes = (offset as u16).to_be_bytes();
901 self.code[patch_pos] = bytes[0];
902 self.code[patch_pos + 1] = bytes[1];
903 }
904
905 fn bind_top_to_local(&mut self, name: &str) {
906 if let Some(&slot) = self.local_slots.get(name) {
907 self.emit_op(STORE_LOCAL);
908 self.emit_u8(slot as u8);
909 } else {
910 self.emit_op(POP);
911 }
912 }
913
914 fn dup_and_bind_top_to_local(&mut self, name: &str) {
915 self.emit_op(DUP);
916 self.bind_top_to_local(name);
917 }
918}
919
920#[cfg(test)]
921mod tests {
922 use super::compile_program;
923 use crate::nan_value::Arena;
924 use crate::source::parse_source;
925 use crate::vm::opcode::{LT, NOT, VECTOR_GET_OR, VECTOR_SET_OR_KEEP};
926
927 #[test]
928 fn vector_get_with_literal_default_lowers_to_vector_get_or() {
929 let source = r#"
930module Demo
931
932fn cellAt(grid: Vector<Int>, idx: Int) -> Int
933 Option.withDefault(Vector.get(grid, idx), 0)
934"#;
935
936 let mut items = parse_source(source).expect("source should parse");
937 crate::tco::transform_program(&mut items);
938 crate::resolver::resolve_program(&mut items);
939
940 let mut arena = Arena::new();
941 let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
942 let fn_id = code.find("cellAt").expect("cellAt should exist");
943 let chunk = code.get(fn_id);
944
945 assert!(
946 chunk.code.contains(&VECTOR_GET_OR),
947 "expected VECTOR_GET_OR in bytecode, got {:?}",
948 chunk.code
949 );
950 }
951
952 #[test]
953 fn vector_set_with_same_default_lowers_to_vector_set_or_keep() {
954 let source = r#"
955module Demo
956
957fn updateOrKeep(vec: Vector<Int>, idx: Int, value: Int) -> Vector<Int>
958 Option.withDefault(Vector.set(vec, idx, value), vec)
959"#;
960
961 let mut items = parse_source(source).expect("source should parse");
962 crate::tco::transform_program(&mut items);
963 crate::resolver::resolve_program(&mut items);
964
965 let mut arena = Arena::new();
966 let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
967 let fn_id = code
968 .find("updateOrKeep")
969 .expect("updateOrKeep should exist");
970 let chunk = code.get(fn_id);
971
972 assert!(
973 chunk.code.contains(&VECTOR_SET_OR_KEEP),
974 "expected VECTOR_SET_OR_KEEP in bytecode, got {:?}",
975 chunk.code
976 );
977 }
978
979 #[test]
980 fn bool_match_on_gte_uses_base_compare_without_not() {
981 let source = r#"
982module Demo
983
984fn bucket(n: Int) -> Int
985 match n >= 10
986 true -> 7
987 false -> 3
988"#;
989
990 let mut items = parse_source(source).expect("source should parse");
991 crate::tco::transform_program(&mut items);
992 crate::resolver::resolve_program(&mut items);
993
994 let mut arena = Arena::new();
995 let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
996 let fn_id = code.find("bucket").expect("bucket should exist");
997 let chunk = code.get(fn_id);
998
999 assert!(
1000 chunk.code.contains(<),
1001 "expected LT in bytecode, got {:?}",
1002 chunk.code
1003 );
1004 assert!(
1005 !chunk.code.contains(&NOT),
1006 "did not expect NOT in normalized bool-match bytecode, got {:?}",
1007 chunk.code
1008 );
1009 }
1010
1011 #[test]
1012 fn self_host_runtime_http_server_aliases_compile_in_vm() {
1013 let source = r#"
1014module Demo
1015
1016fn listen(handler: Int) -> Unit
1017 SelfHostRuntime.httpServerListen(8080, handler)
1018
1019fn listenWith(context: Int, handler: Int) -> Unit
1020 SelfHostRuntime.httpServerListenWith(8081, context, handler)
1021"#;
1022
1023 let mut items = parse_source(source).expect("source should parse");
1024 crate::tco::transform_program(&mut items);
1025 crate::resolver::resolve_program(&mut items);
1026
1027 let mut arena = Arena::new();
1028 let (code, _globals) = compile_program(&items, &mut arena).expect("vm compile should pass");
1029 assert!(code.find("listen").is_some(), "listen should compile");
1030 assert!(
1031 code.find("listenWith").is_some(),
1032 "listenWith should compile"
1033 );
1034 }
1035}