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