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 ListContains,
425 StringCount,
426 StringEmpty,
427 StringContains,
428 DictCount,
429 DictHas,
430 RangeCount,
431 RangeLen,
432 RangeEmpty,
433 RangeFirst,
434 RangeLast,
435 SetCount,
436 SetLen,
437 SetEmpty,
438 SetContains,
439}
440
441#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
443pub struct LocalSlotInfo {
444 pub name: String,
445 pub mutable: bool,
446 pub scope_depth: usize,
447}
448
449impl fmt::Display for Constant {
450 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451 match self {
452 Constant::Int(n) => write!(f, "{n}"),
453 Constant::Float(n) => write!(f, "{n}"),
454 Constant::String(s) => write!(f, "\"{s}\""),
455 Constant::Bool(b) => write!(f, "{b}"),
456 Constant::Nil => write!(f, "nil"),
457 Constant::Duration(ms) => write!(f, "{ms}ms"),
458 }
459 }
460}
461
462#[derive(Debug, Clone)]
464pub struct Chunk {
465 pub code: Vec<u8>,
467 pub constants: Vec<Constant>,
469 pub lines: Vec<u32>,
471 pub columns: Vec<u32>,
474 pub source_file: Option<String>,
479 current_col: u32,
481 pub functions: Vec<CompiledFunctionRef>,
483 inline_cache_slots: BTreeMap<usize, usize>,
486 inline_caches: Rc<RefCell<Vec<InlineCacheEntry>>>,
489 constant_strings: Rc<RefCell<Vec<Option<Rc<str>>>>>,
497 pub(crate) local_slots: Vec<LocalSlotInfo>,
499}
500
501pub type ChunkRef = Rc<Chunk>;
502pub type CompiledFunctionRef = Rc<CompiledFunction>;
503
504#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct CachedChunk {
510 pub(crate) code: Vec<u8>,
511 pub(crate) constants: Vec<Constant>,
512 pub(crate) lines: Vec<u32>,
513 pub(crate) columns: Vec<u32>,
514 pub(crate) source_file: Option<String>,
515 pub(crate) current_col: u32,
516 pub(crate) functions: Vec<CachedCompiledFunction>,
517 pub(crate) inline_cache_slots: BTreeMap<usize, usize>,
518 pub(crate) local_slots: Vec<LocalSlotInfo>,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct CachedCompiledFunction {
523 pub(crate) name: String,
524 pub(crate) type_params: Vec<String>,
525 pub(crate) nominal_type_names: Vec<String>,
526 pub(crate) params: Vec<ParamSlot>,
527 pub(crate) default_start: Option<usize>,
528 pub(crate) chunk: CachedChunk,
529 pub(crate) is_generator: bool,
530 pub(crate) is_stream: bool,
531 pub(crate) has_rest_param: bool,
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize)]
540pub struct ParamSlot {
541 pub name: String,
542 pub type_expr: Option<TypeExpr>,
545 pub has_default: bool,
549}
550
551impl ParamSlot {
552 pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
555 Self {
556 name: param.name.clone(),
557 type_expr: param.type_expr.clone(),
558 has_default: param.default_value.is_some(),
559 }
560 }
561
562 pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
567 params.iter().map(Self::from_typed_param).collect()
568 }
569}
570
571#[derive(Debug, Clone)]
573pub struct CompiledFunction {
574 pub name: String,
575 pub type_params: Vec<String>,
579 pub nominal_type_names: Vec<String>,
583 pub params: Vec<ParamSlot>,
584 pub default_start: Option<usize>,
586 pub chunk: ChunkRef,
587 pub is_generator: bool,
589 pub is_stream: bool,
591 pub has_rest_param: bool,
593}
594
595impl CompiledFunction {
596 pub fn param_names(&self) -> impl Iterator<Item = &str> {
599 self.params.iter().map(|p| p.name.as_str())
600 }
601
602 pub fn required_param_count(&self) -> usize {
604 self.default_start.unwrap_or(self.params.len())
605 }
606
607 pub fn declares_type_param(&self, name: &str) -> bool {
608 self.type_params.iter().any(|param| param == name)
609 }
610
611 pub fn has_nominal_type(&self, name: &str) -> bool {
612 self.nominal_type_names.iter().any(|ty| ty == name)
613 }
614
615 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
616 CachedCompiledFunction {
617 name: self.name.clone(),
618 type_params: self.type_params.clone(),
619 nominal_type_names: self.nominal_type_names.clone(),
620 params: self.params.clone(),
621 default_start: self.default_start,
622 chunk: self.chunk.freeze_for_cache(),
623 is_generator: self.is_generator,
624 is_stream: self.is_stream,
625 has_rest_param: self.has_rest_param,
626 }
627 }
628
629 pub(crate) fn from_cached(cached: &CachedCompiledFunction) -> Self {
630 Self {
631 name: cached.name.clone(),
632 type_params: cached.type_params.clone(),
633 nominal_type_names: cached.nominal_type_names.clone(),
634 params: cached.params.clone(),
635 default_start: cached.default_start,
636 chunk: Rc::new(Chunk::from_cached(&cached.chunk)),
637 is_generator: cached.is_generator,
638 is_stream: cached.is_stream,
639 has_rest_param: cached.has_rest_param,
640 }
641 }
642}
643
644impl Chunk {
645 pub fn new() -> Self {
646 Self {
647 code: Vec::new(),
648 constants: Vec::new(),
649 lines: Vec::new(),
650 columns: Vec::new(),
651 source_file: None,
652 current_col: 0,
653 functions: Vec::new(),
654 inline_cache_slots: BTreeMap::new(),
655 inline_caches: Rc::new(RefCell::new(Vec::new())),
656 constant_strings: Rc::new(RefCell::new(Vec::new())),
657 local_slots: Vec::new(),
658 }
659 }
660
661 pub fn set_column(&mut self, col: u32) {
663 self.current_col = col;
664 }
665
666 pub fn add_constant(&mut self, constant: Constant) -> u16 {
668 for (i, c) in self.constants.iter().enumerate() {
669 if c == &constant {
670 return i as u16;
671 }
672 }
673 let idx = self.constants.len();
674 self.constants.push(constant);
675 idx as u16
676 }
677
678 pub fn emit(&mut self, op: Op, line: u32) {
680 let col = self.current_col;
681 self.code.push(op as u8);
682 self.lines.push(line);
683 self.columns.push(col);
684 }
685
686 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
688 let col = self.current_col;
689 let op_offset = self.code.len();
690 self.code.push(op as u8);
691 self.code.push((arg >> 8) as u8);
692 self.code.push((arg & 0xFF) as u8);
693 self.lines.push(line);
694 self.lines.push(line);
695 self.lines.push(line);
696 self.columns.push(col);
697 self.columns.push(col);
698 self.columns.push(col);
699 if matches!(
700 op,
701 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread
702 ) {
703 self.register_inline_cache(op_offset);
704 }
705 }
706
707 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
709 let col = self.current_col;
710 self.code.push(op as u8);
711 self.code.push(arg);
712 self.lines.push(line);
713 self.lines.push(line);
714 self.columns.push(col);
715 self.columns.push(col);
716 }
717
718 pub fn emit_call_builtin(
720 &mut self,
721 id: crate::BuiltinId,
722 name_idx: u16,
723 arg_count: u8,
724 line: u32,
725 ) {
726 let col = self.current_col;
727 self.code.push(Op::CallBuiltin as u8);
728 self.code.extend_from_slice(&id.raw().to_be_bytes());
729 self.code.push((name_idx >> 8) as u8);
730 self.code.push((name_idx & 0xFF) as u8);
731 self.code.push(arg_count);
732 for _ in 0..12 {
733 self.lines.push(line);
734 self.columns.push(col);
735 }
736 }
737
738 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
740 let col = self.current_col;
741 self.code.push(Op::CallBuiltinSpread as u8);
742 self.code.extend_from_slice(&id.raw().to_be_bytes());
743 self.code.push((name_idx >> 8) as u8);
744 self.code.push((name_idx & 0xFF) as u8);
745 for _ in 0..11 {
746 self.lines.push(line);
747 self.columns.push(col);
748 }
749 }
750
751 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
753 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
754 }
755
756 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
758 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
759 }
760
761 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
762 let col = self.current_col;
763 let op_offset = self.code.len();
764 self.code.push(op as u8);
765 self.code.push((name_idx >> 8) as u8);
766 self.code.push((name_idx & 0xFF) as u8);
767 self.code.push(arg_count);
768 self.lines.push(line);
769 self.lines.push(line);
770 self.lines.push(line);
771 self.lines.push(line);
772 self.columns.push(col);
773 self.columns.push(col);
774 self.columns.push(col);
775 self.columns.push(col);
776 self.register_inline_cache(op_offset);
777 }
778
779 pub fn current_offset(&self) -> usize {
781 self.code.len()
782 }
783
784 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
786 let col = self.current_col;
787 self.code.push(op as u8);
788 let patch_pos = self.code.len();
789 self.code.push(0xFF);
790 self.code.push(0xFF);
791 self.lines.push(line);
792 self.lines.push(line);
793 self.lines.push(line);
794 self.columns.push(col);
795 self.columns.push(col);
796 self.columns.push(col);
797 patch_pos
798 }
799
800 pub fn patch_jump(&mut self, patch_pos: usize) {
802 let target = self.code.len() as u16;
803 self.code[patch_pos] = (target >> 8) as u8;
804 self.code[patch_pos + 1] = (target & 0xFF) as u8;
805 }
806
807 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
809 let target = target as u16;
810 self.code[patch_pos] = (target >> 8) as u8;
811 self.code[patch_pos + 1] = (target & 0xFF) as u8;
812 }
813
814 pub fn read_u16(&self, pos: usize) -> u16 {
816 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
817 }
818
819 fn register_inline_cache(&mut self, op_offset: usize) {
820 if self.inline_cache_slots.contains_key(&op_offset) {
821 return;
822 }
823 let mut entries = self.inline_caches.borrow_mut();
824 let slot = entries.len();
825 entries.push(InlineCacheEntry::Empty);
826 self.inline_cache_slots.insert(op_offset, slot);
827 }
828
829 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
830 self.inline_cache_slots.get(&op_offset).copied()
831 }
832
833 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<Rc<str>> {
838 let mut entries = self.constant_strings.borrow_mut();
843 if entries.len() < self.constants.len() {
844 entries.resize(self.constants.len(), None);
845 }
846 if let Some(Some(existing)) = entries.get(idx) {
847 return Some(Rc::clone(existing));
848 }
849 let materialized = match self.constants.get(idx)? {
850 Constant::String(s) => Rc::<str>::from(s.as_str()),
851 _ => return None,
852 };
853 entries[idx] = Some(Rc::clone(&materialized));
854 Some(materialized)
855 }
856
857 pub(crate) fn inline_cache_entry(&self, slot: usize) -> InlineCacheEntry {
858 self.inline_caches
859 .borrow()
860 .get(slot)
861 .cloned()
862 .unwrap_or(InlineCacheEntry::Empty)
863 }
864
865 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
866 if let Some(existing) = self.inline_caches.borrow_mut().get_mut(slot) {
867 *existing = entry;
868 }
869 }
870
871 pub fn freeze_for_cache(&self) -> CachedChunk {
872 CachedChunk {
873 code: self.code.clone(),
874 constants: self.constants.clone(),
875 lines: self.lines.clone(),
876 columns: self.columns.clone(),
877 source_file: self.source_file.clone(),
878 current_col: self.current_col,
879 functions: self
880 .functions
881 .iter()
882 .map(|function| function.freeze_for_cache())
883 .collect(),
884 inline_cache_slots: self.inline_cache_slots.clone(),
885 local_slots: self.local_slots.clone(),
886 }
887 }
888
889 pub fn from_cached(cached: &CachedChunk) -> Self {
890 let inline_cache_count = cached.inline_cache_slots.len();
891 let constants_count = cached.constants.len();
892 Self {
893 code: cached.code.clone(),
894 constants: cached.constants.clone(),
895 lines: cached.lines.clone(),
896 columns: cached.columns.clone(),
897 source_file: cached.source_file.clone(),
898 current_col: cached.current_col,
899 functions: cached
900 .functions
901 .iter()
902 .map(|function| Rc::new(CompiledFunction::from_cached(function)))
903 .collect(),
904 inline_cache_slots: cached.inline_cache_slots.clone(),
905 inline_caches: Rc::new(RefCell::new(vec![
906 InlineCacheEntry::Empty;
907 inline_cache_count
908 ])),
909 constant_strings: Rc::new(RefCell::new(vec![None; constants_count])),
910 local_slots: cached.local_slots.clone(),
911 }
912 }
913
914 pub(crate) fn add_local_slot(
915 &mut self,
916 name: String,
917 mutable: bool,
918 scope_depth: usize,
919 ) -> u16 {
920 let idx = self.local_slots.len();
921 self.local_slots.push(LocalSlotInfo {
922 name,
923 mutable,
924 scope_depth,
925 });
926 idx as u16
927 }
928
929 #[cfg(test)]
930 pub(crate) fn inline_cache_entries(&self) -> Vec<InlineCacheEntry> {
931 self.inline_caches.borrow().clone()
932 }
933
934 pub fn read_u64(&self, pos: usize) -> u64 {
936 u64::from_be_bytes([
937 self.code[pos],
938 self.code[pos + 1],
939 self.code[pos + 2],
940 self.code[pos + 3],
941 self.code[pos + 4],
942 self.code[pos + 5],
943 self.code[pos + 6],
944 self.code[pos + 7],
945 ])
946 }
947
948 pub fn disassemble(&self, name: &str) -> String {
950 let mut out = format!("== {name} ==\n");
951 let mut ip = 0;
952 while ip < self.code.len() {
953 let op = self.code[ip];
954 let line = self.lines.get(ip).copied().unwrap_or(0);
955 out.push_str(&format!("{:04} [{:>4}] ", ip, line));
956 ip += 1;
957
958 match op {
959 x if x == Op::Constant as u8 => {
960 let idx = self.read_u16(ip);
961 ip += 2;
962 let val = &self.constants[idx as usize];
963 out.push_str(&format!("CONSTANT {:>4} ({})\n", idx, val));
964 }
965 x if x == Op::Nil as u8 => out.push_str("NIL\n"),
966 x if x == Op::True as u8 => out.push_str("TRUE\n"),
967 x if x == Op::False as u8 => out.push_str("FALSE\n"),
968 x if x == Op::GetVar as u8 => {
969 let idx = self.read_u16(ip);
970 ip += 2;
971 out.push_str(&format!(
972 "GET_VAR {:>4} ({})\n",
973 idx, self.constants[idx as usize]
974 ));
975 }
976 x if x == Op::DefLet as u8 => {
977 let idx = self.read_u16(ip);
978 ip += 2;
979 out.push_str(&format!(
980 "DEF_LET {:>4} ({})\n",
981 idx, self.constants[idx as usize]
982 ));
983 }
984 x if x == Op::DefVar as u8 => {
985 let idx = self.read_u16(ip);
986 ip += 2;
987 out.push_str(&format!(
988 "DEF_VAR {:>4} ({})\n",
989 idx, self.constants[idx as usize]
990 ));
991 }
992 x if x == Op::SetVar as u8 => {
993 let idx = self.read_u16(ip);
994 ip += 2;
995 out.push_str(&format!(
996 "SET_VAR {:>4} ({})\n",
997 idx, self.constants[idx as usize]
998 ));
999 }
1000 x if x == Op::GetLocalSlot as u8 => {
1001 let slot = self.read_u16(ip);
1002 ip += 2;
1003 out.push_str(&format!("GET_LOCAL_SLOT {:>4}", slot));
1004 if let Some(info) = self.local_slots.get(slot as usize) {
1005 out.push_str(&format!(" ({})", info.name));
1006 }
1007 out.push('\n');
1008 }
1009 x if x == Op::DefLocalSlot as u8 => {
1010 let slot = self.read_u16(ip);
1011 ip += 2;
1012 out.push_str(&format!("DEF_LOCAL_SLOT {:>4}", slot));
1013 if let Some(info) = self.local_slots.get(slot as usize) {
1014 out.push_str(&format!(" ({})", info.name));
1015 }
1016 out.push('\n');
1017 }
1018 x if x == Op::SetLocalSlot as u8 => {
1019 let slot = self.read_u16(ip);
1020 ip += 2;
1021 out.push_str(&format!("SET_LOCAL_SLOT {:>4}", slot));
1022 if let Some(info) = self.local_slots.get(slot as usize) {
1023 out.push_str(&format!(" ({})", info.name));
1024 }
1025 out.push('\n');
1026 }
1027 x if x == Op::PushScope as u8 => out.push_str("PUSH_SCOPE\n"),
1028 x if x == Op::PopScope as u8 => out.push_str("POP_SCOPE\n"),
1029 x if x == Op::Add as u8 => out.push_str("ADD\n"),
1030 x if x == Op::Sub as u8 => out.push_str("SUB\n"),
1031 x if x == Op::Mul as u8 => out.push_str("MUL\n"),
1032 x if x == Op::Div as u8 => out.push_str("DIV\n"),
1033 x if x == Op::Mod as u8 => out.push_str("MOD\n"),
1034 x if x == Op::Pow as u8 => out.push_str("POW\n"),
1035 x if x == Op::Negate as u8 => out.push_str("NEGATE\n"),
1036 x if x == Op::Equal as u8 => out.push_str("EQUAL\n"),
1037 x if x == Op::NotEqual as u8 => out.push_str("NOT_EQUAL\n"),
1038 x if x == Op::Less as u8 => out.push_str("LESS\n"),
1039 x if x == Op::Greater as u8 => out.push_str("GREATER\n"),
1040 x if x == Op::LessEqual as u8 => out.push_str("LESS_EQUAL\n"),
1041 x if x == Op::GreaterEqual as u8 => out.push_str("GREATER_EQUAL\n"),
1042 x if x == Op::Contains as u8 => out.push_str("CONTAINS\n"),
1043 x if x == Op::Not as u8 => out.push_str("NOT\n"),
1044 x if x == Op::Jump as u8 => {
1045 let target = self.read_u16(ip);
1046 ip += 2;
1047 out.push_str(&format!("JUMP {:>4}\n", target));
1048 }
1049 x if x == Op::JumpIfFalse as u8 => {
1050 let target = self.read_u16(ip);
1051 ip += 2;
1052 out.push_str(&format!("JUMP_IF_FALSE {:>4}\n", target));
1053 }
1054 x if x == Op::JumpIfTrue as u8 => {
1055 let target = self.read_u16(ip);
1056 ip += 2;
1057 out.push_str(&format!("JUMP_IF_TRUE {:>4}\n", target));
1058 }
1059 x if x == Op::Pop as u8 => out.push_str("POP\n"),
1060 x if x == Op::Call as u8 => {
1061 let argc = self.code[ip];
1062 ip += 1;
1063 out.push_str(&format!("CALL {:>4}\n", argc));
1064 }
1065 x if x == Op::TailCall as u8 => {
1066 let argc = self.code[ip];
1067 ip += 1;
1068 out.push_str(&format!("TAIL_CALL {:>4}\n", argc));
1069 }
1070 x if x == Op::Return as u8 => out.push_str("RETURN\n"),
1071 x if x == Op::Closure as u8 => {
1072 let idx = self.read_u16(ip);
1073 ip += 2;
1074 out.push_str(&format!("CLOSURE {:>4}\n", idx));
1075 }
1076 x if x == Op::BuildList as u8 => {
1077 let count = self.read_u16(ip);
1078 ip += 2;
1079 out.push_str(&format!("BUILD_LIST {:>4}\n", count));
1080 }
1081 x if x == Op::BuildDict as u8 => {
1082 let count = self.read_u16(ip);
1083 ip += 2;
1084 out.push_str(&format!("BUILD_DICT {:>4}\n", count));
1085 }
1086 x if x == Op::Subscript as u8 => out.push_str("SUBSCRIPT\n"),
1087 x if x == Op::SubscriptOpt as u8 => out.push_str("SUBSCRIPT_OPT\n"),
1088 x if x == Op::Slice as u8 => out.push_str("SLICE\n"),
1089 x if x == Op::GetProperty as u8 => {
1090 let idx = self.read_u16(ip);
1091 ip += 2;
1092 out.push_str(&format!(
1093 "GET_PROPERTY {:>4} ({})\n",
1094 idx, self.constants[idx as usize]
1095 ));
1096 }
1097 x if x == Op::GetPropertyOpt as u8 => {
1098 let idx = self.read_u16(ip);
1099 ip += 2;
1100 out.push_str(&format!(
1101 "GET_PROPERTY_OPT {:>4} ({})\n",
1102 idx, self.constants[idx as usize]
1103 ));
1104 }
1105 x if x == Op::SetProperty as u8 => {
1106 let idx = self.read_u16(ip);
1107 ip += 2;
1108 out.push_str(&format!(
1109 "SET_PROPERTY {:>4} ({})\n",
1110 idx, self.constants[idx as usize]
1111 ));
1112 }
1113 x if x == Op::SetSubscript as u8 => {
1114 let idx = self.read_u16(ip);
1115 ip += 2;
1116 out.push_str(&format!(
1117 "SET_SUBSCRIPT {:>4} ({})\n",
1118 idx, self.constants[idx as usize]
1119 ));
1120 }
1121 x if x == Op::MethodCall as u8 => {
1122 let idx = self.read_u16(ip);
1123 ip += 2;
1124 let argc = self.code[ip];
1125 ip += 1;
1126 out.push_str(&format!(
1127 "METHOD_CALL {:>4} ({}) argc={}\n",
1128 idx, self.constants[idx as usize], argc
1129 ));
1130 }
1131 x if x == Op::MethodCallOpt as u8 => {
1132 let idx = self.read_u16(ip);
1133 ip += 2;
1134 let argc = self.code[ip];
1135 ip += 1;
1136 out.push_str(&format!(
1137 "METHOD_CALL_OPT {:>4} ({}) argc={}\n",
1138 idx, self.constants[idx as usize], argc
1139 ));
1140 }
1141 x if x == Op::Concat as u8 => {
1142 let count = self.read_u16(ip);
1143 ip += 2;
1144 out.push_str(&format!("CONCAT {:>4}\n", count));
1145 }
1146 x if x == Op::IterInit as u8 => out.push_str("ITER_INIT\n"),
1147 x if x == Op::IterNext as u8 => {
1148 let target = self.read_u16(ip);
1149 ip += 2;
1150 out.push_str(&format!("ITER_NEXT {:>4}\n", target));
1151 }
1152 x if x == Op::Throw as u8 => out.push_str("THROW\n"),
1153 x if x == Op::TryCatchSetup as u8 => {
1154 let target = self.read_u16(ip);
1155 ip += 2;
1156 out.push_str(&format!("TRY_CATCH_SETUP {:>4}\n", target));
1157 }
1158 x if x == Op::PopHandler as u8 => out.push_str("POP_HANDLER\n"),
1159 x if x == Op::Pipe as u8 => out.push_str("PIPE\n"),
1160 x if x == Op::Parallel as u8 => out.push_str("PARALLEL\n"),
1161 x if x == Op::ParallelMap as u8 => out.push_str("PARALLEL_MAP\n"),
1162 x if x == Op::ParallelMapStream as u8 => out.push_str("PARALLEL_MAP_STREAM\n"),
1163 x if x == Op::ParallelSettle as u8 => out.push_str("PARALLEL_SETTLE\n"),
1164 x if x == Op::Spawn as u8 => out.push_str("SPAWN\n"),
1165 x if x == Op::Import as u8 => {
1166 let idx = self.read_u16(ip);
1167 ip += 2;
1168 out.push_str(&format!(
1169 "IMPORT {:>4} ({})\n",
1170 idx, self.constants[idx as usize]
1171 ));
1172 }
1173 x if x == Op::SelectiveImport as u8 => {
1174 let path_idx = self.read_u16(ip);
1175 ip += 2;
1176 let names_idx = self.read_u16(ip);
1177 ip += 2;
1178 out.push_str(&format!(
1179 "SELECTIVE_IMPORT {:>4} ({}) names: {:>4} ({})\n",
1180 path_idx,
1181 self.constants[path_idx as usize],
1182 names_idx,
1183 self.constants[names_idx as usize]
1184 ));
1185 }
1186 x if x == Op::SyncMutexEnter as u8 => {
1187 let idx = self.read_u16(ip);
1188 ip += 2;
1189 out.push_str(&format!(
1190 "SYNC_MUTEX_ENTER {:>4} ({})\n",
1191 idx, self.constants[idx as usize]
1192 ));
1193 }
1194 x if x == Op::DeadlineSetup as u8 => out.push_str("DEADLINE_SETUP\n"),
1195 x if x == Op::DeadlineEnd as u8 => out.push_str("DEADLINE_END\n"),
1196 x if x == Op::BuildEnum as u8 => {
1197 let enum_idx = self.read_u16(ip);
1198 ip += 2;
1199 let variant_idx = self.read_u16(ip);
1200 ip += 2;
1201 let field_count = self.read_u16(ip);
1202 ip += 2;
1203 out.push_str(&format!(
1204 "BUILD_ENUM {:>4} ({}) {:>4} ({}) fields={}\n",
1205 enum_idx,
1206 self.constants[enum_idx as usize],
1207 variant_idx,
1208 self.constants[variant_idx as usize],
1209 field_count
1210 ));
1211 }
1212 x if x == Op::MatchEnum as u8 => {
1213 let enum_idx = self.read_u16(ip);
1214 ip += 2;
1215 let variant_idx = self.read_u16(ip);
1216 ip += 2;
1217 out.push_str(&format!(
1218 "MATCH_ENUM {:>4} ({}) {:>4} ({})\n",
1219 enum_idx,
1220 self.constants[enum_idx as usize],
1221 variant_idx,
1222 self.constants[variant_idx as usize]
1223 ));
1224 }
1225 x if x == Op::PopIterator as u8 => out.push_str("POP_ITERATOR\n"),
1226 x if x == Op::TryUnwrap as u8 => out.push_str("TRY_UNWRAP\n"),
1227 x if x == Op::TryWrapOk as u8 => out.push_str("TRY_WRAP_OK\n"),
1228 x if x == Op::CallSpread as u8 => out.push_str("CALL_SPREAD\n"),
1229 x if x == Op::CallBuiltin as u8 => {
1230 let id = self.read_u64(ip);
1231 ip += 8;
1232 let idx = self.read_u16(ip);
1233 ip += 2;
1234 let argc = self.code[ip];
1235 ip += 1;
1236 out.push_str(&format!(
1237 "CALL_BUILTIN {id:#018x} {:>4} ({}) argc={}\n",
1238 idx, self.constants[idx as usize], argc
1239 ));
1240 }
1241 x if x == Op::CallBuiltinSpread as u8 => {
1242 let id = self.read_u64(ip);
1243 ip += 8;
1244 let idx = self.read_u16(ip);
1245 ip += 2;
1246 out.push_str(&format!(
1247 "CALL_BUILTIN_SPREAD {id:#018x} {:>4} ({})\n",
1248 idx, self.constants[idx as usize]
1249 ));
1250 }
1251 x if x == Op::MethodCallSpread as u8 => {
1252 let idx = self.read_u16(ip + 1);
1253 ip += 2;
1254 out.push_str(&format!("METHOD_CALL_SPREAD {idx}\n"));
1255 }
1256 x if x == Op::Dup as u8 => out.push_str("DUP\n"),
1257 x if x == Op::Swap as u8 => out.push_str("SWAP\n"),
1258 x if x == Op::AddInt as u8 => out.push_str("ADD_INT\n"),
1259 x if x == Op::SubInt as u8 => out.push_str("SUB_INT\n"),
1260 x if x == Op::MulInt as u8 => out.push_str("MUL_INT\n"),
1261 x if x == Op::DivInt as u8 => out.push_str("DIV_INT\n"),
1262 x if x == Op::ModInt as u8 => out.push_str("MOD_INT\n"),
1263 x if x == Op::AddFloat as u8 => out.push_str("ADD_FLOAT\n"),
1264 x if x == Op::SubFloat as u8 => out.push_str("SUB_FLOAT\n"),
1265 x if x == Op::MulFloat as u8 => out.push_str("MUL_FLOAT\n"),
1266 x if x == Op::DivFloat as u8 => out.push_str("DIV_FLOAT\n"),
1267 x if x == Op::ModFloat as u8 => out.push_str("MOD_FLOAT\n"),
1268 x if x == Op::EqualInt as u8 => out.push_str("EQUAL_INT\n"),
1269 x if x == Op::NotEqualInt as u8 => out.push_str("NOT_EQUAL_INT\n"),
1270 x if x == Op::LessInt as u8 => out.push_str("LESS_INT\n"),
1271 x if x == Op::GreaterInt as u8 => out.push_str("GREATER_INT\n"),
1272 x if x == Op::LessEqualInt as u8 => out.push_str("LESS_EQUAL_INT\n"),
1273 x if x == Op::GreaterEqualInt as u8 => out.push_str("GREATER_EQUAL_INT\n"),
1274 x if x == Op::EqualFloat as u8 => out.push_str("EQUAL_FLOAT\n"),
1275 x if x == Op::NotEqualFloat as u8 => out.push_str("NOT_EQUAL_FLOAT\n"),
1276 x if x == Op::LessFloat as u8 => out.push_str("LESS_FLOAT\n"),
1277 x if x == Op::GreaterFloat as u8 => out.push_str("GREATER_FLOAT\n"),
1278 x if x == Op::LessEqualFloat as u8 => out.push_str("LESS_EQUAL_FLOAT\n"),
1279 x if x == Op::GreaterEqualFloat as u8 => out.push_str("GREATER_EQUAL_FLOAT\n"),
1280 x if x == Op::EqualBool as u8 => out.push_str("EQUAL_BOOL\n"),
1281 x if x == Op::NotEqualBool as u8 => out.push_str("NOT_EQUAL_BOOL\n"),
1282 x if x == Op::EqualString as u8 => out.push_str("EQUAL_STRING\n"),
1283 x if x == Op::NotEqualString as u8 => out.push_str("NOT_EQUAL_STRING\n"),
1284 x if x == Op::Yield as u8 => out.push_str("YIELD\n"),
1285 _ => {
1286 out.push_str(&format!("UNKNOWN(0x{:02x})\n", op));
1287 }
1288 }
1289 }
1290 out
1291 }
1292}
1293
1294impl Default for Chunk {
1295 fn default() -> Self {
1296 Self::new()
1297 }
1298}
1299
1300#[cfg(test)]
1301mod tests {
1302 use super::Op;
1303
1304 #[test]
1305 fn op_from_byte_matches_repr_order() {
1306 for (byte, op) in Op::ALL.iter().copied().enumerate() {
1307 assert_eq!(byte as u8, op as u8);
1308 assert_eq!(Op::from_byte(byte as u8), Some(op));
1309 }
1310 assert_eq!(Op::from_byte(Op::ALL.len() as u8), None);
1311 }
1312}