1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::fmt;
4use std::rc::Rc;
5
6use harn_parser::TypeExpr;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[repr(u8)]
12pub enum Op {
13 Constant, Nil,
17 True,
19 False,
21
22 GetVar, DefLet, DefVar, SetVar, PushScope,
33 PopScope,
35
36 Add,
38 Sub,
39 Mul,
40 Div,
41 Mod,
42 Pow,
43 Negate,
44
45 Equal,
47 NotEqual,
48 Less,
49 Greater,
50 LessEqual,
51 GreaterEqual,
52
53 Not,
55
56 Jump,
59 JumpIfFalse,
61 JumpIfTrue,
63 Pop,
65
66 Call,
69 TailCall,
73 Return,
75 Closure,
77
78 BuildList,
81 BuildDict,
83 Subscript,
85 SubscriptOpt,
88 Slice,
90
91 GetProperty,
94 GetPropertyOpt,
97 SetProperty,
100 SetSubscript,
103 MethodCall,
105 MethodCallOpt,
108
109 Concat,
112
113 IterInit,
116 IterNext,
119
120 Pipe,
123
124 Throw,
127 TryCatchSetup,
129 PopHandler,
131
132 Parallel,
136 ParallelMap,
139 ParallelMapStream,
142 ParallelSettle,
145 Spawn,
148 SyncMutexEnter,
151
152 Import,
155 SelectiveImport,
157
158 DeadlineSetup,
161 DeadlineEnd,
163
164 BuildEnum,
169
170 MatchEnum,
175
176 PopIterator,
179
180 GetArgc,
183
184 CheckType,
190
191 TryUnwrap,
194 TryWrapOk,
196
197 CallSpread,
200 CallBuiltin,
203 CallBuiltinSpread,
206 MethodCallSpread,
209
210 Dup,
213 Swap,
215 Contains,
218
219 AddInt,
221 SubInt,
222 MulInt,
223 DivInt,
224 ModInt,
225 AddFloat,
226 SubFloat,
227 MulFloat,
228 DivFloat,
229 ModFloat,
230 EqualInt,
231 NotEqualInt,
232 LessInt,
233 GreaterInt,
234 LessEqualInt,
235 GreaterEqualInt,
236 EqualFloat,
237 NotEqualFloat,
238 LessFloat,
239 GreaterFloat,
240 LessEqualFloat,
241 GreaterEqualFloat,
242 EqualBool,
243 NotEqualBool,
244 EqualString,
245 NotEqualString,
246
247 Yield,
249
250 GetLocalSlot,
253 DefLocalSlot,
255 SetLocalSlot,
257}
258
259impl Op {
260 pub(crate) const ALL: &'static [Self] = &[
261 Op::Constant,
262 Op::Nil,
263 Op::True,
264 Op::False,
265 Op::GetVar,
266 Op::DefLet,
267 Op::DefVar,
268 Op::SetVar,
269 Op::PushScope,
270 Op::PopScope,
271 Op::Add,
272 Op::Sub,
273 Op::Mul,
274 Op::Div,
275 Op::Mod,
276 Op::Pow,
277 Op::Negate,
278 Op::Equal,
279 Op::NotEqual,
280 Op::Less,
281 Op::Greater,
282 Op::LessEqual,
283 Op::GreaterEqual,
284 Op::Not,
285 Op::Jump,
286 Op::JumpIfFalse,
287 Op::JumpIfTrue,
288 Op::Pop,
289 Op::Call,
290 Op::TailCall,
291 Op::Return,
292 Op::Closure,
293 Op::BuildList,
294 Op::BuildDict,
295 Op::Subscript,
296 Op::SubscriptOpt,
297 Op::Slice,
298 Op::GetProperty,
299 Op::GetPropertyOpt,
300 Op::SetProperty,
301 Op::SetSubscript,
302 Op::MethodCall,
303 Op::MethodCallOpt,
304 Op::Concat,
305 Op::IterInit,
306 Op::IterNext,
307 Op::Pipe,
308 Op::Throw,
309 Op::TryCatchSetup,
310 Op::PopHandler,
311 Op::Parallel,
312 Op::ParallelMap,
313 Op::ParallelMapStream,
314 Op::ParallelSettle,
315 Op::Spawn,
316 Op::SyncMutexEnter,
317 Op::Import,
318 Op::SelectiveImport,
319 Op::DeadlineSetup,
320 Op::DeadlineEnd,
321 Op::BuildEnum,
322 Op::MatchEnum,
323 Op::PopIterator,
324 Op::GetArgc,
325 Op::CheckType,
326 Op::TryUnwrap,
327 Op::TryWrapOk,
328 Op::CallSpread,
329 Op::CallBuiltin,
330 Op::CallBuiltinSpread,
331 Op::MethodCallSpread,
332 Op::Dup,
333 Op::Swap,
334 Op::Contains,
335 Op::AddInt,
336 Op::SubInt,
337 Op::MulInt,
338 Op::DivInt,
339 Op::ModInt,
340 Op::AddFloat,
341 Op::SubFloat,
342 Op::MulFloat,
343 Op::DivFloat,
344 Op::ModFloat,
345 Op::EqualInt,
346 Op::NotEqualInt,
347 Op::LessInt,
348 Op::GreaterInt,
349 Op::LessEqualInt,
350 Op::GreaterEqualInt,
351 Op::EqualFloat,
352 Op::NotEqualFloat,
353 Op::LessFloat,
354 Op::GreaterFloat,
355 Op::LessEqualFloat,
356 Op::GreaterEqualFloat,
357 Op::EqualBool,
358 Op::NotEqualBool,
359 Op::EqualString,
360 Op::NotEqualString,
361 Op::Yield,
362 Op::GetLocalSlot,
363 Op::DefLocalSlot,
364 Op::SetLocalSlot,
365 ];
366
367 pub(crate) fn from_byte(byte: u8) -> Option<Self> {
368 Self::ALL.get(byte as usize).copied()
369 }
370}
371
372#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
374pub enum Constant {
375 Int(i64),
376 Float(f64),
377 String(String),
378 Bool(bool),
379 Nil,
380 Duration(i64),
381}
382
383#[derive(Debug, Clone, PartialEq, Eq)]
391pub(crate) enum InlineCacheEntry {
392 Empty,
393 Property {
394 name_idx: u16,
395 target: PropertyCacheTarget,
396 },
397 Method {
398 name_idx: u16,
399 argc: usize,
400 target: MethodCacheTarget,
401 },
402}
403
404#[derive(Debug, Clone, PartialEq, Eq)]
405pub(crate) enum PropertyCacheTarget {
406 DictField(Rc<str>),
407 StructField { field_name: Rc<str>, index: usize },
408 ListCount,
409 ListEmpty,
410 ListFirst,
411 ListLast,
412 StringCount,
413 StringEmpty,
414 PairFirst,
415 PairSecond,
416 EnumVariant,
417 EnumFields,
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq)]
421pub(crate) enum MethodCacheTarget {
422 ListCount,
423 ListEmpty,
424 StringCount,
425 StringEmpty,
426 DictCount,
427 RangeCount,
428 RangeLen,
429 RangeEmpty,
430 RangeFirst,
431 RangeLast,
432 SetCount,
433 SetLen,
434 SetEmpty,
435}
436
437#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
439pub struct LocalSlotInfo {
440 pub name: String,
441 pub mutable: bool,
442 pub scope_depth: usize,
443}
444
445impl fmt::Display for Constant {
446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447 match self {
448 Constant::Int(n) => write!(f, "{n}"),
449 Constant::Float(n) => write!(f, "{n}"),
450 Constant::String(s) => write!(f, "\"{s}\""),
451 Constant::Bool(b) => write!(f, "{b}"),
452 Constant::Nil => write!(f, "nil"),
453 Constant::Duration(ms) => write!(f, "{ms}ms"),
454 }
455 }
456}
457
458#[derive(Debug, Clone)]
460pub struct Chunk {
461 pub code: Vec<u8>,
463 pub constants: Vec<Constant>,
465 pub lines: Vec<u32>,
467 pub columns: Vec<u32>,
470 pub source_file: Option<String>,
475 current_col: u32,
477 pub functions: Vec<CompiledFunctionRef>,
479 inline_cache_slots: BTreeMap<usize, usize>,
482 inline_caches: Rc<RefCell<Vec<InlineCacheEntry>>>,
485 constant_strings: Rc<RefCell<Vec<Option<Rc<str>>>>>,
493 pub(crate) local_slots: Vec<LocalSlotInfo>,
495}
496
497pub type ChunkRef = Rc<Chunk>;
498pub type CompiledFunctionRef = Rc<CompiledFunction>;
499
500#[derive(Debug, Clone, Serialize, Deserialize)]
505pub struct CachedChunk {
506 pub(crate) code: Vec<u8>,
507 pub(crate) constants: Vec<Constant>,
508 pub(crate) lines: Vec<u32>,
509 pub(crate) columns: Vec<u32>,
510 pub(crate) source_file: Option<String>,
511 pub(crate) current_col: u32,
512 pub(crate) functions: Vec<CachedCompiledFunction>,
513 pub(crate) inline_cache_slots: BTreeMap<usize, usize>,
514 pub(crate) local_slots: Vec<LocalSlotInfo>,
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct CachedCompiledFunction {
519 pub(crate) name: String,
520 pub(crate) type_params: Vec<String>,
521 pub(crate) nominal_type_names: Vec<String>,
522 pub(crate) params: Vec<ParamSlot>,
523 pub(crate) default_start: Option<usize>,
524 pub(crate) chunk: CachedChunk,
525 pub(crate) is_generator: bool,
526 pub(crate) is_stream: bool,
527 pub(crate) has_rest_param: bool,
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct ParamSlot {
537 pub name: String,
538 pub type_expr: Option<TypeExpr>,
541 pub has_default: bool,
545}
546
547impl ParamSlot {
548 pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
551 Self {
552 name: param.name.clone(),
553 type_expr: param.type_expr.clone(),
554 has_default: param.default_value.is_some(),
555 }
556 }
557
558 pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
563 params.iter().map(Self::from_typed_param).collect()
564 }
565}
566
567#[derive(Debug, Clone)]
569pub struct CompiledFunction {
570 pub name: String,
571 pub type_params: Vec<String>,
575 pub nominal_type_names: Vec<String>,
579 pub params: Vec<ParamSlot>,
580 pub default_start: Option<usize>,
582 pub chunk: ChunkRef,
583 pub is_generator: bool,
585 pub is_stream: bool,
587 pub has_rest_param: bool,
589}
590
591impl CompiledFunction {
592 pub fn param_names(&self) -> impl Iterator<Item = &str> {
595 self.params.iter().map(|p| p.name.as_str())
596 }
597
598 pub fn required_param_count(&self) -> usize {
600 self.default_start.unwrap_or(self.params.len())
601 }
602
603 pub fn declares_type_param(&self, name: &str) -> bool {
604 self.type_params.iter().any(|param| param == name)
605 }
606
607 pub fn has_nominal_type(&self, name: &str) -> bool {
608 self.nominal_type_names.iter().any(|ty| ty == name)
609 }
610
611 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
612 CachedCompiledFunction {
613 name: self.name.clone(),
614 type_params: self.type_params.clone(),
615 nominal_type_names: self.nominal_type_names.clone(),
616 params: self.params.clone(),
617 default_start: self.default_start,
618 chunk: self.chunk.freeze_for_cache(),
619 is_generator: self.is_generator,
620 is_stream: self.is_stream,
621 has_rest_param: self.has_rest_param,
622 }
623 }
624
625 pub(crate) fn from_cached(cached: &CachedCompiledFunction) -> Self {
626 Self {
627 name: cached.name.clone(),
628 type_params: cached.type_params.clone(),
629 nominal_type_names: cached.nominal_type_names.clone(),
630 params: cached.params.clone(),
631 default_start: cached.default_start,
632 chunk: Rc::new(Chunk::from_cached(&cached.chunk)),
633 is_generator: cached.is_generator,
634 is_stream: cached.is_stream,
635 has_rest_param: cached.has_rest_param,
636 }
637 }
638}
639
640impl Chunk {
641 pub fn new() -> Self {
642 Self {
643 code: Vec::new(),
644 constants: Vec::new(),
645 lines: Vec::new(),
646 columns: Vec::new(),
647 source_file: None,
648 current_col: 0,
649 functions: Vec::new(),
650 inline_cache_slots: BTreeMap::new(),
651 inline_caches: Rc::new(RefCell::new(Vec::new())),
652 constant_strings: Rc::new(RefCell::new(Vec::new())),
653 local_slots: Vec::new(),
654 }
655 }
656
657 pub fn set_column(&mut self, col: u32) {
659 self.current_col = col;
660 }
661
662 pub fn add_constant(&mut self, constant: Constant) -> u16 {
664 for (i, c) in self.constants.iter().enumerate() {
665 if c == &constant {
666 return i as u16;
667 }
668 }
669 let idx = self.constants.len();
670 self.constants.push(constant);
671 idx as u16
672 }
673
674 pub fn emit(&mut self, op: Op, line: u32) {
676 let col = self.current_col;
677 self.code.push(op as u8);
678 self.lines.push(line);
679 self.columns.push(col);
680 }
681
682 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
684 let col = self.current_col;
685 let op_offset = self.code.len();
686 self.code.push(op as u8);
687 self.code.push((arg >> 8) as u8);
688 self.code.push((arg & 0xFF) as u8);
689 self.lines.push(line);
690 self.lines.push(line);
691 self.lines.push(line);
692 self.columns.push(col);
693 self.columns.push(col);
694 self.columns.push(col);
695 if matches!(
696 op,
697 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread
698 ) {
699 self.register_inline_cache(op_offset);
700 }
701 }
702
703 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
705 let col = self.current_col;
706 self.code.push(op as u8);
707 self.code.push(arg);
708 self.lines.push(line);
709 self.lines.push(line);
710 self.columns.push(col);
711 self.columns.push(col);
712 }
713
714 pub fn emit_call_builtin(
716 &mut self,
717 id: crate::BuiltinId,
718 name_idx: u16,
719 arg_count: u8,
720 line: u32,
721 ) {
722 let col = self.current_col;
723 self.code.push(Op::CallBuiltin as u8);
724 self.code.extend_from_slice(&id.raw().to_be_bytes());
725 self.code.push((name_idx >> 8) as u8);
726 self.code.push((name_idx & 0xFF) as u8);
727 self.code.push(arg_count);
728 for _ in 0..12 {
729 self.lines.push(line);
730 self.columns.push(col);
731 }
732 }
733
734 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
736 let col = self.current_col;
737 self.code.push(Op::CallBuiltinSpread as u8);
738 self.code.extend_from_slice(&id.raw().to_be_bytes());
739 self.code.push((name_idx >> 8) as u8);
740 self.code.push((name_idx & 0xFF) as u8);
741 for _ in 0..11 {
742 self.lines.push(line);
743 self.columns.push(col);
744 }
745 }
746
747 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
749 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
750 }
751
752 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
754 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
755 }
756
757 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
758 let col = self.current_col;
759 let op_offset = self.code.len();
760 self.code.push(op as u8);
761 self.code.push((name_idx >> 8) as u8);
762 self.code.push((name_idx & 0xFF) as u8);
763 self.code.push(arg_count);
764 self.lines.push(line);
765 self.lines.push(line);
766 self.lines.push(line);
767 self.lines.push(line);
768 self.columns.push(col);
769 self.columns.push(col);
770 self.columns.push(col);
771 self.columns.push(col);
772 self.register_inline_cache(op_offset);
773 }
774
775 pub fn current_offset(&self) -> usize {
777 self.code.len()
778 }
779
780 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
782 let col = self.current_col;
783 self.code.push(op as u8);
784 let patch_pos = self.code.len();
785 self.code.push(0xFF);
786 self.code.push(0xFF);
787 self.lines.push(line);
788 self.lines.push(line);
789 self.lines.push(line);
790 self.columns.push(col);
791 self.columns.push(col);
792 self.columns.push(col);
793 patch_pos
794 }
795
796 pub fn patch_jump(&mut self, patch_pos: usize) {
798 let target = self.code.len() as u16;
799 self.code[patch_pos] = (target >> 8) as u8;
800 self.code[patch_pos + 1] = (target & 0xFF) as u8;
801 }
802
803 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
805 let target = target as u16;
806 self.code[patch_pos] = (target >> 8) as u8;
807 self.code[patch_pos + 1] = (target & 0xFF) as u8;
808 }
809
810 pub fn read_u16(&self, pos: usize) -> u16 {
812 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
813 }
814
815 fn register_inline_cache(&mut self, op_offset: usize) {
816 if self.inline_cache_slots.contains_key(&op_offset) {
817 return;
818 }
819 let mut entries = self.inline_caches.borrow_mut();
820 let slot = entries.len();
821 entries.push(InlineCacheEntry::Empty);
822 self.inline_cache_slots.insert(op_offset, slot);
823 }
824
825 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
826 self.inline_cache_slots.get(&op_offset).copied()
827 }
828
829 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<Rc<str>> {
834 let mut entries = self.constant_strings.borrow_mut();
839 if entries.len() < self.constants.len() {
840 entries.resize(self.constants.len(), None);
841 }
842 if let Some(Some(existing)) = entries.get(idx) {
843 return Some(Rc::clone(existing));
844 }
845 let materialized = match self.constants.get(idx)? {
846 Constant::String(s) => Rc::<str>::from(s.as_str()),
847 _ => return None,
848 };
849 entries[idx] = Some(Rc::clone(&materialized));
850 Some(materialized)
851 }
852
853 pub(crate) fn inline_cache_entry(&self, slot: usize) -> InlineCacheEntry {
854 self.inline_caches
855 .borrow()
856 .get(slot)
857 .cloned()
858 .unwrap_or(InlineCacheEntry::Empty)
859 }
860
861 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
862 if let Some(existing) = self.inline_caches.borrow_mut().get_mut(slot) {
863 *existing = entry;
864 }
865 }
866
867 pub fn freeze_for_cache(&self) -> CachedChunk {
868 CachedChunk {
869 code: self.code.clone(),
870 constants: self.constants.clone(),
871 lines: self.lines.clone(),
872 columns: self.columns.clone(),
873 source_file: self.source_file.clone(),
874 current_col: self.current_col,
875 functions: self
876 .functions
877 .iter()
878 .map(|function| function.freeze_for_cache())
879 .collect(),
880 inline_cache_slots: self.inline_cache_slots.clone(),
881 local_slots: self.local_slots.clone(),
882 }
883 }
884
885 pub fn from_cached(cached: &CachedChunk) -> Self {
886 let inline_cache_count = cached.inline_cache_slots.len();
887 let constants_count = cached.constants.len();
888 Self {
889 code: cached.code.clone(),
890 constants: cached.constants.clone(),
891 lines: cached.lines.clone(),
892 columns: cached.columns.clone(),
893 source_file: cached.source_file.clone(),
894 current_col: cached.current_col,
895 functions: cached
896 .functions
897 .iter()
898 .map(|function| Rc::new(CompiledFunction::from_cached(function)))
899 .collect(),
900 inline_cache_slots: cached.inline_cache_slots.clone(),
901 inline_caches: Rc::new(RefCell::new(vec![
902 InlineCacheEntry::Empty;
903 inline_cache_count
904 ])),
905 constant_strings: Rc::new(RefCell::new(vec![None; constants_count])),
906 local_slots: cached.local_slots.clone(),
907 }
908 }
909
910 pub(crate) fn add_local_slot(
911 &mut self,
912 name: String,
913 mutable: bool,
914 scope_depth: usize,
915 ) -> u16 {
916 let idx = self.local_slots.len();
917 self.local_slots.push(LocalSlotInfo {
918 name,
919 mutable,
920 scope_depth,
921 });
922 idx as u16
923 }
924
925 #[cfg(test)]
926 pub(crate) fn inline_cache_entries(&self) -> Vec<InlineCacheEntry> {
927 self.inline_caches.borrow().clone()
928 }
929
930 pub fn read_u64(&self, pos: usize) -> u64 {
932 u64::from_be_bytes([
933 self.code[pos],
934 self.code[pos + 1],
935 self.code[pos + 2],
936 self.code[pos + 3],
937 self.code[pos + 4],
938 self.code[pos + 5],
939 self.code[pos + 6],
940 self.code[pos + 7],
941 ])
942 }
943
944 pub fn disassemble(&self, name: &str) -> String {
946 let mut out = format!("== {name} ==\n");
947 let mut ip = 0;
948 while ip < self.code.len() {
949 let op = self.code[ip];
950 let line = self.lines.get(ip).copied().unwrap_or(0);
951 out.push_str(&format!("{:04} [{:>4}] ", ip, line));
952 ip += 1;
953
954 match op {
955 x if x == Op::Constant as u8 => {
956 let idx = self.read_u16(ip);
957 ip += 2;
958 let val = &self.constants[idx as usize];
959 out.push_str(&format!("CONSTANT {:>4} ({})\n", idx, val));
960 }
961 x if x == Op::Nil as u8 => out.push_str("NIL\n"),
962 x if x == Op::True as u8 => out.push_str("TRUE\n"),
963 x if x == Op::False as u8 => out.push_str("FALSE\n"),
964 x if x == Op::GetVar as u8 => {
965 let idx = self.read_u16(ip);
966 ip += 2;
967 out.push_str(&format!(
968 "GET_VAR {:>4} ({})\n",
969 idx, self.constants[idx as usize]
970 ));
971 }
972 x if x == Op::DefLet as u8 => {
973 let idx = self.read_u16(ip);
974 ip += 2;
975 out.push_str(&format!(
976 "DEF_LET {:>4} ({})\n",
977 idx, self.constants[idx as usize]
978 ));
979 }
980 x if x == Op::DefVar as u8 => {
981 let idx = self.read_u16(ip);
982 ip += 2;
983 out.push_str(&format!(
984 "DEF_VAR {:>4} ({})\n",
985 idx, self.constants[idx as usize]
986 ));
987 }
988 x if x == Op::SetVar as u8 => {
989 let idx = self.read_u16(ip);
990 ip += 2;
991 out.push_str(&format!(
992 "SET_VAR {:>4} ({})\n",
993 idx, self.constants[idx as usize]
994 ));
995 }
996 x if x == Op::GetLocalSlot as u8 => {
997 let slot = self.read_u16(ip);
998 ip += 2;
999 out.push_str(&format!("GET_LOCAL_SLOT {:>4}", slot));
1000 if let Some(info) = self.local_slots.get(slot as usize) {
1001 out.push_str(&format!(" ({})", info.name));
1002 }
1003 out.push('\n');
1004 }
1005 x if x == Op::DefLocalSlot as u8 => {
1006 let slot = self.read_u16(ip);
1007 ip += 2;
1008 out.push_str(&format!("DEF_LOCAL_SLOT {:>4}", slot));
1009 if let Some(info) = self.local_slots.get(slot as usize) {
1010 out.push_str(&format!(" ({})", info.name));
1011 }
1012 out.push('\n');
1013 }
1014 x if x == Op::SetLocalSlot as u8 => {
1015 let slot = self.read_u16(ip);
1016 ip += 2;
1017 out.push_str(&format!("SET_LOCAL_SLOT {:>4}", slot));
1018 if let Some(info) = self.local_slots.get(slot as usize) {
1019 out.push_str(&format!(" ({})", info.name));
1020 }
1021 out.push('\n');
1022 }
1023 x if x == Op::PushScope as u8 => out.push_str("PUSH_SCOPE\n"),
1024 x if x == Op::PopScope as u8 => out.push_str("POP_SCOPE\n"),
1025 x if x == Op::Add as u8 => out.push_str("ADD\n"),
1026 x if x == Op::Sub as u8 => out.push_str("SUB\n"),
1027 x if x == Op::Mul as u8 => out.push_str("MUL\n"),
1028 x if x == Op::Div as u8 => out.push_str("DIV\n"),
1029 x if x == Op::Mod as u8 => out.push_str("MOD\n"),
1030 x if x == Op::Pow as u8 => out.push_str("POW\n"),
1031 x if x == Op::Negate as u8 => out.push_str("NEGATE\n"),
1032 x if x == Op::Equal as u8 => out.push_str("EQUAL\n"),
1033 x if x == Op::NotEqual as u8 => out.push_str("NOT_EQUAL\n"),
1034 x if x == Op::Less as u8 => out.push_str("LESS\n"),
1035 x if x == Op::Greater as u8 => out.push_str("GREATER\n"),
1036 x if x == Op::LessEqual as u8 => out.push_str("LESS_EQUAL\n"),
1037 x if x == Op::GreaterEqual as u8 => out.push_str("GREATER_EQUAL\n"),
1038 x if x == Op::Contains as u8 => out.push_str("CONTAINS\n"),
1039 x if x == Op::Not as u8 => out.push_str("NOT\n"),
1040 x if x == Op::Jump as u8 => {
1041 let target = self.read_u16(ip);
1042 ip += 2;
1043 out.push_str(&format!("JUMP {:>4}\n", target));
1044 }
1045 x if x == Op::JumpIfFalse as u8 => {
1046 let target = self.read_u16(ip);
1047 ip += 2;
1048 out.push_str(&format!("JUMP_IF_FALSE {:>4}\n", target));
1049 }
1050 x if x == Op::JumpIfTrue as u8 => {
1051 let target = self.read_u16(ip);
1052 ip += 2;
1053 out.push_str(&format!("JUMP_IF_TRUE {:>4}\n", target));
1054 }
1055 x if x == Op::Pop as u8 => out.push_str("POP\n"),
1056 x if x == Op::Call as u8 => {
1057 let argc = self.code[ip];
1058 ip += 1;
1059 out.push_str(&format!("CALL {:>4}\n", argc));
1060 }
1061 x if x == Op::TailCall as u8 => {
1062 let argc = self.code[ip];
1063 ip += 1;
1064 out.push_str(&format!("TAIL_CALL {:>4}\n", argc));
1065 }
1066 x if x == Op::Return as u8 => out.push_str("RETURN\n"),
1067 x if x == Op::Closure as u8 => {
1068 let idx = self.read_u16(ip);
1069 ip += 2;
1070 out.push_str(&format!("CLOSURE {:>4}\n", idx));
1071 }
1072 x if x == Op::BuildList as u8 => {
1073 let count = self.read_u16(ip);
1074 ip += 2;
1075 out.push_str(&format!("BUILD_LIST {:>4}\n", count));
1076 }
1077 x if x == Op::BuildDict as u8 => {
1078 let count = self.read_u16(ip);
1079 ip += 2;
1080 out.push_str(&format!("BUILD_DICT {:>4}\n", count));
1081 }
1082 x if x == Op::Subscript as u8 => out.push_str("SUBSCRIPT\n"),
1083 x if x == Op::SubscriptOpt as u8 => out.push_str("SUBSCRIPT_OPT\n"),
1084 x if x == Op::Slice as u8 => out.push_str("SLICE\n"),
1085 x if x == Op::GetProperty as u8 => {
1086 let idx = self.read_u16(ip);
1087 ip += 2;
1088 out.push_str(&format!(
1089 "GET_PROPERTY {:>4} ({})\n",
1090 idx, self.constants[idx as usize]
1091 ));
1092 }
1093 x if x == Op::GetPropertyOpt as u8 => {
1094 let idx = self.read_u16(ip);
1095 ip += 2;
1096 out.push_str(&format!(
1097 "GET_PROPERTY_OPT {:>4} ({})\n",
1098 idx, self.constants[idx as usize]
1099 ));
1100 }
1101 x if x == Op::SetProperty as u8 => {
1102 let idx = self.read_u16(ip);
1103 ip += 2;
1104 out.push_str(&format!(
1105 "SET_PROPERTY {:>4} ({})\n",
1106 idx, self.constants[idx as usize]
1107 ));
1108 }
1109 x if x == Op::SetSubscript as u8 => {
1110 let idx = self.read_u16(ip);
1111 ip += 2;
1112 out.push_str(&format!(
1113 "SET_SUBSCRIPT {:>4} ({})\n",
1114 idx, self.constants[idx as usize]
1115 ));
1116 }
1117 x if x == Op::MethodCall as u8 => {
1118 let idx = self.read_u16(ip);
1119 ip += 2;
1120 let argc = self.code[ip];
1121 ip += 1;
1122 out.push_str(&format!(
1123 "METHOD_CALL {:>4} ({}) argc={}\n",
1124 idx, self.constants[idx as usize], argc
1125 ));
1126 }
1127 x if x == Op::MethodCallOpt as u8 => {
1128 let idx = self.read_u16(ip);
1129 ip += 2;
1130 let argc = self.code[ip];
1131 ip += 1;
1132 out.push_str(&format!(
1133 "METHOD_CALL_OPT {:>4} ({}) argc={}\n",
1134 idx, self.constants[idx as usize], argc
1135 ));
1136 }
1137 x if x == Op::Concat as u8 => {
1138 let count = self.read_u16(ip);
1139 ip += 2;
1140 out.push_str(&format!("CONCAT {:>4}\n", count));
1141 }
1142 x if x == Op::IterInit as u8 => out.push_str("ITER_INIT\n"),
1143 x if x == Op::IterNext as u8 => {
1144 let target = self.read_u16(ip);
1145 ip += 2;
1146 out.push_str(&format!("ITER_NEXT {:>4}\n", target));
1147 }
1148 x if x == Op::Throw as u8 => out.push_str("THROW\n"),
1149 x if x == Op::TryCatchSetup as u8 => {
1150 let target = self.read_u16(ip);
1151 ip += 2;
1152 out.push_str(&format!("TRY_CATCH_SETUP {:>4}\n", target));
1153 }
1154 x if x == Op::PopHandler as u8 => out.push_str("POP_HANDLER\n"),
1155 x if x == Op::Pipe as u8 => out.push_str("PIPE\n"),
1156 x if x == Op::Parallel as u8 => out.push_str("PARALLEL\n"),
1157 x if x == Op::ParallelMap as u8 => out.push_str("PARALLEL_MAP\n"),
1158 x if x == Op::ParallelMapStream as u8 => out.push_str("PARALLEL_MAP_STREAM\n"),
1159 x if x == Op::ParallelSettle as u8 => out.push_str("PARALLEL_SETTLE\n"),
1160 x if x == Op::Spawn as u8 => out.push_str("SPAWN\n"),
1161 x if x == Op::Import as u8 => {
1162 let idx = self.read_u16(ip);
1163 ip += 2;
1164 out.push_str(&format!(
1165 "IMPORT {:>4} ({})\n",
1166 idx, self.constants[idx as usize]
1167 ));
1168 }
1169 x if x == Op::SelectiveImport as u8 => {
1170 let path_idx = self.read_u16(ip);
1171 ip += 2;
1172 let names_idx = self.read_u16(ip);
1173 ip += 2;
1174 out.push_str(&format!(
1175 "SELECTIVE_IMPORT {:>4} ({}) names: {:>4} ({})\n",
1176 path_idx,
1177 self.constants[path_idx as usize],
1178 names_idx,
1179 self.constants[names_idx as usize]
1180 ));
1181 }
1182 x if x == Op::SyncMutexEnter as u8 => {
1183 let idx = self.read_u16(ip);
1184 ip += 2;
1185 out.push_str(&format!(
1186 "SYNC_MUTEX_ENTER {:>4} ({})\n",
1187 idx, self.constants[idx as usize]
1188 ));
1189 }
1190 x if x == Op::DeadlineSetup as u8 => out.push_str("DEADLINE_SETUP\n"),
1191 x if x == Op::DeadlineEnd as u8 => out.push_str("DEADLINE_END\n"),
1192 x if x == Op::BuildEnum as u8 => {
1193 let enum_idx = self.read_u16(ip);
1194 ip += 2;
1195 let variant_idx = self.read_u16(ip);
1196 ip += 2;
1197 let field_count = self.read_u16(ip);
1198 ip += 2;
1199 out.push_str(&format!(
1200 "BUILD_ENUM {:>4} ({}) {:>4} ({}) fields={}\n",
1201 enum_idx,
1202 self.constants[enum_idx as usize],
1203 variant_idx,
1204 self.constants[variant_idx as usize],
1205 field_count
1206 ));
1207 }
1208 x if x == Op::MatchEnum as u8 => {
1209 let enum_idx = self.read_u16(ip);
1210 ip += 2;
1211 let variant_idx = self.read_u16(ip);
1212 ip += 2;
1213 out.push_str(&format!(
1214 "MATCH_ENUM {:>4} ({}) {:>4} ({})\n",
1215 enum_idx,
1216 self.constants[enum_idx as usize],
1217 variant_idx,
1218 self.constants[variant_idx as usize]
1219 ));
1220 }
1221 x if x == Op::PopIterator as u8 => out.push_str("POP_ITERATOR\n"),
1222 x if x == Op::TryUnwrap as u8 => out.push_str("TRY_UNWRAP\n"),
1223 x if x == Op::TryWrapOk as u8 => out.push_str("TRY_WRAP_OK\n"),
1224 x if x == Op::CallSpread as u8 => out.push_str("CALL_SPREAD\n"),
1225 x if x == Op::CallBuiltin as u8 => {
1226 let id = self.read_u64(ip);
1227 ip += 8;
1228 let idx = self.read_u16(ip);
1229 ip += 2;
1230 let argc = self.code[ip];
1231 ip += 1;
1232 out.push_str(&format!(
1233 "CALL_BUILTIN {id:#018x} {:>4} ({}) argc={}\n",
1234 idx, self.constants[idx as usize], argc
1235 ));
1236 }
1237 x if x == Op::CallBuiltinSpread as u8 => {
1238 let id = self.read_u64(ip);
1239 ip += 8;
1240 let idx = self.read_u16(ip);
1241 ip += 2;
1242 out.push_str(&format!(
1243 "CALL_BUILTIN_SPREAD {id:#018x} {:>4} ({})\n",
1244 idx, self.constants[idx as usize]
1245 ));
1246 }
1247 x if x == Op::MethodCallSpread as u8 => {
1248 let idx = self.read_u16(ip + 1);
1249 ip += 2;
1250 out.push_str(&format!("METHOD_CALL_SPREAD {idx}\n"));
1251 }
1252 x if x == Op::Dup as u8 => out.push_str("DUP\n"),
1253 x if x == Op::Swap as u8 => out.push_str("SWAP\n"),
1254 x if x == Op::AddInt as u8 => out.push_str("ADD_INT\n"),
1255 x if x == Op::SubInt as u8 => out.push_str("SUB_INT\n"),
1256 x if x == Op::MulInt as u8 => out.push_str("MUL_INT\n"),
1257 x if x == Op::DivInt as u8 => out.push_str("DIV_INT\n"),
1258 x if x == Op::ModInt as u8 => out.push_str("MOD_INT\n"),
1259 x if x == Op::AddFloat as u8 => out.push_str("ADD_FLOAT\n"),
1260 x if x == Op::SubFloat as u8 => out.push_str("SUB_FLOAT\n"),
1261 x if x == Op::MulFloat as u8 => out.push_str("MUL_FLOAT\n"),
1262 x if x == Op::DivFloat as u8 => out.push_str("DIV_FLOAT\n"),
1263 x if x == Op::ModFloat as u8 => out.push_str("MOD_FLOAT\n"),
1264 x if x == Op::EqualInt as u8 => out.push_str("EQUAL_INT\n"),
1265 x if x == Op::NotEqualInt as u8 => out.push_str("NOT_EQUAL_INT\n"),
1266 x if x == Op::LessInt as u8 => out.push_str("LESS_INT\n"),
1267 x if x == Op::GreaterInt as u8 => out.push_str("GREATER_INT\n"),
1268 x if x == Op::LessEqualInt as u8 => out.push_str("LESS_EQUAL_INT\n"),
1269 x if x == Op::GreaterEqualInt as u8 => out.push_str("GREATER_EQUAL_INT\n"),
1270 x if x == Op::EqualFloat as u8 => out.push_str("EQUAL_FLOAT\n"),
1271 x if x == Op::NotEqualFloat as u8 => out.push_str("NOT_EQUAL_FLOAT\n"),
1272 x if x == Op::LessFloat as u8 => out.push_str("LESS_FLOAT\n"),
1273 x if x == Op::GreaterFloat as u8 => out.push_str("GREATER_FLOAT\n"),
1274 x if x == Op::LessEqualFloat as u8 => out.push_str("LESS_EQUAL_FLOAT\n"),
1275 x if x == Op::GreaterEqualFloat as u8 => out.push_str("GREATER_EQUAL_FLOAT\n"),
1276 x if x == Op::EqualBool as u8 => out.push_str("EQUAL_BOOL\n"),
1277 x if x == Op::NotEqualBool as u8 => out.push_str("NOT_EQUAL_BOOL\n"),
1278 x if x == Op::EqualString as u8 => out.push_str("EQUAL_STRING\n"),
1279 x if x == Op::NotEqualString as u8 => out.push_str("NOT_EQUAL_STRING\n"),
1280 x if x == Op::Yield as u8 => out.push_str("YIELD\n"),
1281 _ => {
1282 out.push_str(&format!("UNKNOWN(0x{:02x})\n", op));
1283 }
1284 }
1285 }
1286 out
1287 }
1288}
1289
1290impl Default for Chunk {
1291 fn default() -> Self {
1292 Self::new()
1293 }
1294}
1295
1296#[cfg(test)]
1297mod tests {
1298 use super::Op;
1299
1300 #[test]
1301 fn op_from_byte_matches_repr_order() {
1302 for (byte, op) in Op::ALL.iter().copied().enumerate() {
1303 assert_eq!(byte as u8, op as u8);
1304 assert_eq!(Op::from_byte(byte as u8), Some(op));
1305 }
1306 assert_eq!(Op::from_byte(Op::ALL.len() as u8), None);
1307 }
1308}