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