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