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) -> Result<(CodeStore, Vec<NanValue>), CompileError> {
33 let mut compiler = ProgramCompiler::new();
34 compiler.sync_record_field_symbols(arena);
35
36 if let Some(module_root) = module_root {
37 compiler.load_modules(items, module_root, arena)?;
38 }
39
40 for item in items {
41 match item {
42 TopLevel::FnDef(fndef) => {
43 compiler.ensure_global(&fndef.name);
44 let effect_ids: Vec<u32> = fndef
45 .effects
46 .iter()
47 .map(|effect| compiler.symbols.intern_name(effect))
48 .collect();
49 let fn_id = compiler.code.add_function(FnChunk {
50 name: fndef.name.clone(),
51 arity: fndef.params.len() as u8,
52 local_count: 0,
53 code: Vec::new(),
54 constants: Vec::new(),
55 effects: effect_ids,
56 thin: false,
57 parent_thin: false,
58 leaf: false,
59 });
60 let symbol_id =
61 compiler
62 .symbols
63 .intern_function(&fndef.name, fn_id, &fndef.effects);
64 let global_idx = compiler.global_names[&fndef.name];
65 compiler.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
66 }
67 TopLevel::TypeDef(td) => {
68 compiler.register_type_def(td, arena);
69 }
70 _ => {}
71 }
72 }
73
74 compiler.register_current_module_namespace(items);
75
76 for item in items {
77 if let TopLevel::FnDef(fndef) = item {
78 let fn_id = compiler.code.find(&fndef.name).unwrap();
79 let chunk = compiler.compile_fn(fndef, arena)?;
80 compiler.code.functions[fn_id as usize] = chunk;
81 }
82 }
83
84 compiler.compile_top_level(items, arena)?;
85 compiler.code.symbols = compiler.symbols.clone();
86 classify::classify_thin_functions(&mut compiler.code, arena)?;
87
88 Ok((compiler.code, compiler.globals))
89}
90
91#[derive(Debug)]
92pub struct CompileError {
93 pub msg: String,
94}
95
96impl std::fmt::Display for CompileError {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 write!(f, "Compile error: {}", self.msg)
99 }
100}
101
102struct ProgramCompiler {
103 code: CodeStore,
104 symbols: VmSymbolTable,
105 globals: Vec<NanValue>,
106 global_names: HashMap<String, u16>,
107}
108
109impl ProgramCompiler {
110 fn new() -> Self {
111 let mut compiler = ProgramCompiler {
112 code: CodeStore::new(),
113 symbols: VmSymbolTable::default(),
114 globals: Vec::new(),
115 global_names: HashMap::new(),
116 };
117 compiler.bootstrap_core_symbols();
118 compiler
119 }
120
121 fn sync_record_field_symbols(&mut self, arena: &Arena) {
122 for type_id in 0..arena.type_count() {
123 let type_name = arena.get_type_name(type_id);
124 self.symbols.intern_namespace_path(type_name);
125 let field_names = arena.get_field_names(type_id);
126 if field_names.is_empty() {
127 continue;
128 }
129 let field_symbol_ids: Vec<u32> = field_names
130 .iter()
131 .map(|field_name| self.symbols.intern_name(field_name))
132 .collect();
133 self.code.register_record_fields(type_id, &field_symbol_ids);
134 }
135 }
136
137 fn load_modules(
142 &mut self,
143 items: &[TopLevel],
144 module_root: &str,
145 arena: &mut Arena,
146 ) -> Result<(), CompileError> {
147 let module = items.iter().find_map(|i| {
148 if let TopLevel::Module(m) = i {
149 Some(m)
150 } else {
151 None
152 }
153 });
154 let module = match module {
155 Some(m) => m,
156 None => return Ok(()),
157 };
158
159 let mut loaded = HashSet::new();
160 for dep_name in &module.depends {
161 self.load_module_recursive(dep_name, module_root, arena, &mut loaded)?;
162 }
163 Ok(())
164 }
165
166 fn load_module_recursive(
167 &mut self,
168 dep_name: &str,
169 module_root: &str,
170 arena: &mut Arena,
171 loaded: &mut HashSet<String>,
172 ) -> Result<(), CompileError> {
173 if loaded.contains(dep_name) {
174 return Ok(());
175 }
176 loaded.insert(dep_name.to_string());
177
178 let file_path = find_module_file(dep_name, module_root).ok_or_else(|| CompileError {
179 msg: format!("module '{}' not found (root: {})", dep_name, module_root),
180 })?;
181
182 let source = std::fs::read_to_string(&file_path).map_err(|e| CompileError {
183 msg: format!("cannot read module '{}': {}", dep_name, e),
184 })?;
185
186 let mut mod_items = crate::source::parse_source(&source).map_err(|e| CompileError {
187 msg: format!("parse error in module '{}': {}", dep_name, e),
188 })?;
189
190 crate::tco::transform_program(&mut mod_items);
191 crate::resolver::resolve_program(&mut mod_items);
192
193 if let Some(sub_module) = mod_items.iter().find_map(|i| {
194 if let TopLevel::Module(m) = i {
195 Some(m)
196 } else {
197 None
198 }
199 }) {
200 for sub_dep in &sub_module.depends {
201 self.load_module_recursive(sub_dep, module_root, arena, loaded)?;
202 }
203 }
204
205 for item in &mod_items {
206 if let TopLevel::TypeDef(td) = item {
207 self.register_type_def(td, arena);
208 }
209 }
210
211 let exposes: Option<Vec<String>> = mod_items.iter().find_map(|i| {
212 if let TopLevel::Module(m) = i {
213 if m.exposes.is_empty() {
214 None
215 } else {
216 Some(m.exposes.clone())
217 }
218 } else {
219 None
220 }
221 });
222
223 let mut module_fn_ids: Vec<(String, u32)> = Vec::new();
224 for item in &mod_items {
225 if let TopLevel::FnDef(fndef) = item {
226 let qualified_name = format!("{}.{}", dep_name, fndef.name);
227 let effect_ids: Vec<u32> = fndef
228 .effects
229 .iter()
230 .map(|effect| self.symbols.intern_name(effect))
231 .collect();
232 let fn_id = self.code.add_function(FnChunk {
233 name: qualified_name.clone(),
234 arity: fndef.params.len() as u8,
235 local_count: 0,
236 code: Vec::new(),
237 constants: Vec::new(),
238 effects: effect_ids,
239 thin: false,
240 parent_thin: false,
241 leaf: false,
242 });
243 self.symbols
244 .intern_function(&qualified_name, fn_id, &fndef.effects);
245 module_fn_ids.push((fndef.name.clone(), fn_id));
246 }
247 }
248
249 let module_scope: HashMap<String, u32> = module_fn_ids.iter().cloned().collect();
250
251 let mut fn_idx = 0;
252 for item in &mod_items {
253 if let TopLevel::FnDef(fndef) = item {
254 let (_, fn_id) = module_fn_ids[fn_idx];
255 let chunk = self.compile_fn_with_scope(fndef, arena, &module_scope)?;
256 self.code.functions[fn_id as usize] = chunk;
257 fn_idx += 1;
258 }
259 }
260
261 for (fn_name, _fn_id) in &module_fn_ids {
262 let exposed = match &exposes {
263 Some(list) => list.iter().any(|e| e == fn_name),
264 None => !fn_name.starts_with('_'),
265 };
266 if exposed {
267 let qualified = format!("{}.{}", dep_name, fn_name);
268 let global_idx = self.ensure_global(&qualified);
269 let symbol_id = self.symbols.find(&qualified).ok_or_else(|| CompileError {
270 msg: format!("missing VM symbol for exposed function {}", qualified),
271 })?;
272 self.globals[global_idx as usize] = VmSymbolTable::symbol_ref(symbol_id);
273 }
274 }
275
276 let module_symbol_id = self.symbols.intern_namespace_path(dep_name);
277 for item in &mod_items {
278 if let TopLevel::TypeDef(td) = item {
279 let type_name = match td {
280 TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name,
281 };
282 let exposed = match &exposes {
283 Some(list) => list.iter().any(|e| e == type_name),
284 None => !type_name.starts_with('_'),
285 };
286 if exposed && let Some(type_symbol_id) = self.symbols.find(type_name) {
287 let member_symbol_id = self.symbols.intern_name(type_name);
288 self.symbols.add_namespace_member_by_id(
289 module_symbol_id,
290 member_symbol_id,
291 VmSymbolTable::symbol_ref(type_symbol_id),
292 );
293 }
294 }
295 }
296
297 for (fn_name, _fn_id) in &module_fn_ids {
298 let exposed = match &exposes {
299 Some(list) => list.iter().any(|e| e == fn_name),
300 None => !fn_name.starts_with('_'),
301 };
302 if exposed {
303 let qualified = format!("{}.{}", dep_name, fn_name);
304 if let Some(fn_symbol_id) = self.symbols.find(&qualified) {
305 let member_symbol_id = self.symbols.intern_name(fn_name);
306 self.symbols.add_namespace_member_by_id(
307 module_symbol_id,
308 member_symbol_id,
309 VmSymbolTable::symbol_ref(fn_symbol_id),
310 );
311 }
312 }
313 }
314
315 Ok(())
316 }
317
318 fn ensure_global(&mut self, name: &str) -> u16 {
319 if let Some(&idx) = self.global_names.get(name) {
320 return idx;
321 }
322 let idx = self.globals.len() as u16;
323 self.global_names.insert(name.to_string(), idx);
324 self.globals.push(NanValue::UNIT);
325 idx
326 }
327
328 fn register_type_def(&mut self, td: &TypeDef, arena: &mut Arena) {
329 match td {
330 TypeDef::Product { name, fields, .. } => {
331 self.symbols.intern_namespace_path(name);
332 let field_names: Vec<String> = fields.iter().map(|(n, _)| n.clone()).collect();
333 let field_symbol_ids: Vec<u32> = field_names
334 .iter()
335 .map(|field_name| self.symbols.intern_name(field_name))
336 .collect();
337 let type_id = arena.register_record_type(name, field_names);
338 self.code.register_record_fields(type_id, &field_symbol_ids);
339 }
340 TypeDef::Sum { name, variants, .. } => {
341 let type_symbol_id = self.symbols.intern_namespace_path(name);
342 let variant_names: Vec<String> = variants.iter().map(|v| v.name.clone()).collect();
343 let type_id = arena.register_sum_type(name, variant_names);
344 for (variant_id, variant) in variants.iter().enumerate() {
345 let ctor_id = arena
346 .find_ctor_id(type_id, variant_id as u16)
347 .expect("ctor id");
348 let qualified_name = format!("{}.{}", name, variant.name);
349 let ctor_symbol_id = self.symbols.intern_variant_ctor(
350 &qualified_name,
351 VmVariantCtor {
352 type_id,
353 variant_id: variant_id as u16,
354 ctor_id,
355 field_count: variant.fields.len() as u8,
356 },
357 );
358 let member_symbol_id = self.symbols.intern_name(&variant.name);
359 self.symbols.add_namespace_member_by_id(
360 type_symbol_id,
361 member_symbol_id,
362 VmSymbolTable::symbol_ref(ctor_symbol_id),
363 );
364 }
365 }
366 }
367 }
368
369 fn bootstrap_core_symbols(&mut self) {
370 for builtin in VmBuiltin::ALL.iter().copied() {
371 let builtin_symbol_id = self.symbols.intern_builtin(builtin);
372 if let Some((namespace, member)) = builtin.name().split_once('.') {
373 let namespace_symbol_id = self.symbols.intern_namespace_path(namespace);
374 let member_symbol_id = self.symbols.intern_name(member);
375 self.symbols.add_namespace_member_by_id(
376 namespace_symbol_id,
377 member_symbol_id,
378 VmSymbolTable::symbol_ref(builtin_symbol_id),
379 );
380 }
381 }
382
383 let result_symbol_id = self.symbols.intern_namespace_path("Result");
384 let ok_symbol_id = self.symbols.intern_wrapper("Result.Ok", 0);
385 let err_symbol_id = self.symbols.intern_wrapper("Result.Err", 1);
386 let ok_member_symbol_id = self.symbols.intern_name("Ok");
387 self.symbols.add_namespace_member_by_id(
388 result_symbol_id,
389 ok_member_symbol_id,
390 VmSymbolTable::symbol_ref(ok_symbol_id),
391 );
392 let err_member_symbol_id = self.symbols.intern_name("Err");
393 self.symbols.add_namespace_member_by_id(
394 result_symbol_id,
395 err_member_symbol_id,
396 VmSymbolTable::symbol_ref(err_symbol_id),
397 );
398 for (member, builtin_name) in result::extra_members() {
399 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
400 let member_symbol_id = self.symbols.intern_name(member);
401 self.symbols.add_namespace_member_by_id(
402 result_symbol_id,
403 member_symbol_id,
404 VmSymbolTable::symbol_ref(symbol_id),
405 );
406 }
407 }
408
409 let option_symbol_id = self.symbols.intern_namespace_path("Option");
410 let some_symbol_id = self.symbols.intern_wrapper("Option.Some", 2);
411 self.symbols.intern_constant("Option.None", NanValue::NONE);
412 let some_member_symbol_id = self.symbols.intern_name("Some");
413 self.symbols.add_namespace_member_by_id(
414 option_symbol_id,
415 some_member_symbol_id,
416 VmSymbolTable::symbol_ref(some_symbol_id),
417 );
418 let none_member_symbol_id = self.symbols.intern_name("None");
419 self.symbols.add_namespace_member_by_id(
420 option_symbol_id,
421 none_member_symbol_id,
422 NanValue::NONE,
423 );
424 for (member, builtin_name) in option::extra_members() {
425 if let Some(symbol_id) = self.symbols.find(&builtin_name) {
426 let member_symbol_id = self.symbols.intern_name(member);
427 self.symbols.add_namespace_member_by_id(
428 option_symbol_id,
429 member_symbol_id,
430 VmSymbolTable::symbol_ref(symbol_id),
431 );
432 }
433 }
434 }
435
436 fn compile_fn(&mut self, fndef: &FnDef, arena: &mut Arena) -> Result<FnChunk, CompileError> {
437 let empty_scope = HashMap::new();
438 self.compile_fn_with_scope(fndef, arena, &empty_scope)
439 }
440
441 fn compile_fn_with_scope(
442 &mut self,
443 fndef: &FnDef,
444 arena: &mut Arena,
445 module_scope: &HashMap<String, u32>,
446 ) -> Result<FnChunk, CompileError> {
447 let resolution = fndef.resolution.as_ref();
448 let local_count = resolution.map_or(fndef.params.len() as u16, |r| r.local_count);
449 let local_slots: HashMap<String, u16> = resolution
450 .map(|r| r.local_slots.as_ref().clone())
451 .unwrap_or_else(|| {
452 fndef
453 .params
454 .iter()
455 .enumerate()
456 .map(|(i, (name, _))| (name.clone(), i as u16))
457 .collect()
458 });
459
460 let mut fc = FnCompiler::new(
461 &fndef.name,
462 fndef.params.len() as u8,
463 local_count,
464 fndef
465 .effects
466 .iter()
467 .map(|effect| self.symbols.intern_name(effect))
468 .collect(),
469 local_slots,
470 &self.global_names,
471 module_scope,
472 &self.code,
473 &mut self.symbols,
474 arena,
475 );
476
477 match fndef.body.as_ref() {
478 FnBody::Block(stmts) => fc.compile_body(stmts)?,
479 }
480
481 Ok(fc.finish())
482 }
483
484 fn compile_top_level(
485 &mut self,
486 items: &[TopLevel],
487 arena: &mut Arena,
488 ) -> Result<(), CompileError> {
489 let has_stmts = items.iter().any(|i| matches!(i, TopLevel::Stmt(_)));
490 if !has_stmts {
491 return Ok(());
492 }
493
494 for item in items {
495 if let TopLevel::Stmt(Stmt::Binding(name, _, _)) = item {
496 self.ensure_global(name);
497 }
498 }
499
500 let empty_mod_scope = HashMap::new();
501 let mut fc = FnCompiler::new(
502 "__top_level__",
503 0,
504 0,
505 Vec::new(),
506 HashMap::new(),
507 &self.global_names,
508 &empty_mod_scope,
509 &self.code,
510 &mut self.symbols,
511 arena,
512 );
513
514 for item in items {
515 if let TopLevel::Stmt(stmt) = item {
516 match stmt {
517 Stmt::Binding(name, _type_ann, expr) => {
518 fc.compile_expr(expr)?;
519 let idx = self.global_names[name.as_str()];
520 fc.emit_op(STORE_GLOBAL);
521 fc.emit_u16(idx);
522 }
523 Stmt::Expr(expr) => {
524 fc.compile_expr(expr)?;
525 fc.emit_op(POP);
526 }
527 }
528 }
529 }
530
531 fc.emit_op(LOAD_UNIT);
532 fc.emit_op(RETURN);
533
534 let chunk = fc.finish();
535 self.code.add_function(chunk);
536 Ok(())
537 }
538
539 fn register_current_module_namespace(&mut self, items: &[TopLevel]) {
540 let Some(module) = items.iter().find_map(|item| {
541 if let TopLevel::Module(module) = item {
542 Some(module)
543 } else {
544 None
545 }
546 }) else {
547 return;
548 };
549
550 let module_symbol_id = self.symbols.intern_namespace_path(&module.name);
551
552 for item in items {
553 match item {
554 TopLevel::FnDef(fndef) => {
555 let exposed = if module.exposes.is_empty() {
556 !fndef.name.starts_with('_')
557 } else {
558 module.exposes.iter().any(|name| name == &fndef.name)
559 };
560 if exposed && let Some(symbol_id) = self.symbols.find(&fndef.name) {
561 let member_symbol_id = self.symbols.intern_name(&fndef.name);
562 self.symbols.add_namespace_member_by_id(
563 module_symbol_id,
564 member_symbol_id,
565 VmSymbolTable::symbol_ref(symbol_id),
566 );
567 }
568 }
569 TopLevel::TypeDef(TypeDef::Product { name, .. } | TypeDef::Sum { name, .. }) => {
570 let exposed = if module.exposes.is_empty() {
571 !name.starts_with('_')
572 } else {
573 module.exposes.iter().any(|member| member == name)
574 };
575 if exposed && let Some(symbol_id) = self.symbols.find(name) {
576 let member_symbol_id = self.symbols.intern_name(name);
577 self.symbols.add_namespace_member_by_id(
578 module_symbol_id,
579 member_symbol_id,
580 VmSymbolTable::symbol_ref(symbol_id),
581 );
582 }
583 }
584 _ => {}
585 }
586 }
587 }
588}
589
590enum CallTarget {
592 KnownFn(u32),
594 Wrapper(u8),
596 None_,
598 Variant(u32, u16),
600 Builtin(VmBuiltin),
602 UnknownQualified(String),
604}
605
606struct FnCompiler<'a> {
607 name: String,
608 arity: u8,
609 local_count: u16,
610 effects: Vec<u32>,
611 local_slots: HashMap<String, u16>,
612 global_names: &'a HashMap<String, u16>,
613 module_scope: &'a HashMap<String, u32>,
616 code_store: &'a CodeStore,
617 symbols: &'a mut VmSymbolTable,
618 arena: &'a mut Arena,
619 code: Vec<u8>,
620 constants: Vec<NanValue>,
621 last_op_pos: usize,
623}
624
625impl<'a> FnCompiler<'a> {
626 #[allow(clippy::too_many_arguments)]
627 fn new(
628 name: &str,
629 arity: u8,
630 local_count: u16,
631 effects: Vec<u32>,
632 local_slots: HashMap<String, u16>,
633 global_names: &'a HashMap<String, u16>,
634 module_scope: &'a HashMap<String, u32>,
635 code_store: &'a CodeStore,
636 symbols: &'a mut VmSymbolTable,
637 arena: &'a mut Arena,
638 ) -> Self {
639 FnCompiler {
640 name: name.to_string(),
641 arity,
642 local_count,
643 effects,
644 local_slots,
645 global_names,
646 module_scope,
647 code_store,
648 symbols,
649 arena,
650 code: Vec::new(),
651 constants: Vec::new(),
652 last_op_pos: usize::MAX,
653 }
654 }
655
656 fn finish(self) -> FnChunk {
657 FnChunk {
658 name: self.name,
659 arity: self.arity,
660 local_count: self.local_count,
661 code: self.code,
662 constants: self.constants,
663 effects: self.effects,
664 thin: false,
665 parent_thin: false,
666 leaf: false,
667 }
668 }
669
670 fn emit_op(&mut self, op: u8) {
671 let prev_pos = self.last_op_pos;
672 let prev_op = if prev_pos < self.code.len() {
673 self.code[prev_pos]
674 } else {
675 0xFF
676 };
677
678 if op == LOAD_LOCAL && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
680 self.code[prev_pos] = LOAD_LOCAL_2;
681 return;
683 }
684 if op == LOAD_CONST && prev_op == LOAD_LOCAL && prev_pos + 2 == self.code.len() {
686 self.code[prev_pos] = LOAD_LOCAL_CONST;
687 return;
689 }
690 if op == UNWRAP_OR && self.code.len() >= 4 {
694 let len = self.code.len();
695 if self.code[len - 4] == VECTOR_GET && self.code[len - 3] == LOAD_CONST {
696 let hi = self.code[len - 2];
697 let lo = self.code[len - 1];
698 self.code[len - 4] = VECTOR_GET_OR;
699 self.code[len - 3] = hi;
700 self.code[len - 2] = lo;
701 self.code.pop(); self.last_op_pos = len - 4;
703 return;
704 }
705 }
706 self.last_op_pos = self.code.len();
707 self.code.push(op);
708 }
709
710 fn emit_u8(&mut self, val: u8) {
711 self.code.push(val);
712 }
713
714 fn emit_u16(&mut self, val: u16) {
715 self.code.push((val >> 8) as u8);
716 self.code.push((val & 0xFF) as u8);
717 }
718
719 fn emit_i16(&mut self, val: i16) {
720 self.emit_u16(val as u16);
721 }
722
723 fn emit_u32(&mut self, val: u32) {
724 self.code.push((val >> 24) as u8);
725 self.code.push(((val >> 16) & 0xFF) as u8);
726 self.code.push(((val >> 8) & 0xFF) as u8);
727 self.code.push((val & 0xFF) as u8);
728 }
729
730 fn emit_u64(&mut self, val: u64) {
731 self.code.extend_from_slice(&val.to_be_bytes());
732 }
733
734 fn add_constant(&mut self, val: NanValue) -> u16 {
735 for (i, c) in self.constants.iter().enumerate() {
736 if c.bits() == val.bits() {
737 return i as u16;
738 }
739 }
740 let idx = self.constants.len() as u16;
741 self.constants.push(val);
742 idx
743 }
744
745 fn offset(&self) -> usize {
746 self.code.len()
747 }
748
749 fn emit_jump(&mut self, op: u8) -> usize {
750 self.emit_op(op);
751 let patch_pos = self.code.len();
752 self.emit_i16(0);
753 patch_pos
754 }
755
756 fn patch_jump(&mut self, patch_pos: usize) {
757 let target = self.code.len();
758 let offset = (target as isize - patch_pos as isize - 2) as i16;
759 let bytes = (offset as u16).to_be_bytes();
760 self.code[patch_pos] = bytes[0];
761 self.code[patch_pos + 1] = bytes[1];
762 }
763
764 fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
765 let offset = (target as isize - patch_pos as isize - 2) as i16;
766 let bytes = (offset as u16).to_be_bytes();
767 self.code[patch_pos] = bytes[0];
768 self.code[patch_pos + 1] = bytes[1];
769 }
770
771 fn bind_top_to_local(&mut self, name: &str) {
772 if let Some(&slot) = self.local_slots.get(name) {
773 self.emit_op(STORE_LOCAL);
774 self.emit_u8(slot as u8);
775 } else {
776 self.emit_op(POP);
777 }
778 }
779
780 fn dup_and_bind_top_to_local(&mut self, name: &str) {
781 self.emit_op(DUP);
782 self.bind_top_to_local(name);
783 }
784}