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)]
393pub(crate) enum InlineCacheEntry {
394 Empty,
395 Property {
396 name_idx: u16,
397 target: PropertyCacheTarget,
398 },
399 Method {
400 name_idx: u16,
401 argc: usize,
402 target: MethodCacheTarget,
403 },
404 AdaptiveBinary {
405 op: AdaptiveBinaryOp,
406 state: AdaptiveBinaryState,
407 },
408 DirectCall {
409 state: DirectCallState,
410 },
411}
412
413#[derive(Debug, Clone, Copy, PartialEq, Eq)]
414pub(crate) enum AdaptiveBinaryOp {
415 Add,
416 Sub,
417 Mul,
418 Div,
419 Mod,
420 Equal,
421 NotEqual,
422 Less,
423 Greater,
424 LessEqual,
425 GreaterEqual,
426}
427
428#[derive(Debug, Clone, PartialEq, Eq)]
429pub(crate) enum AdaptiveBinaryState {
430 Warmup {
431 shape: BinaryShape,
432 hits: u8,
433 },
434 Specialized {
435 shape: BinaryShape,
436 hits: u64,
437 misses: u64,
438 },
439}
440
441#[derive(Debug, Clone, Copy, PartialEq, Eq)]
442pub(crate) enum BinaryShape {
443 Int,
444 Float,
445 Bool,
446 String,
447}
448
449#[derive(Debug, Clone)]
450pub(crate) enum DirectCallState {
451 Warmup {
452 argc: usize,
453 target: DirectCallTarget,
454 hits: u8,
455 },
456 Specialized {
457 argc: usize,
458 target: DirectCallTarget,
459 hits: u64,
460 misses: u64,
461 },
462}
463
464#[derive(Debug, Clone)]
465pub(crate) enum DirectCallTarget {
466 Closure(Rc<crate::value::VmClosure>),
467}
468
469impl PartialEq for DirectCallTarget {
470 fn eq(&self, other: &Self) -> bool {
471 match (self, other) {
472 (Self::Closure(left), Self::Closure(right)) => Rc::ptr_eq(left, right),
473 }
474 }
475}
476
477impl Eq for DirectCallTarget {}
478
479impl PartialEq for DirectCallState {
480 fn eq(&self, other: &Self) -> bool {
481 match (self, other) {
482 (
483 Self::Warmup {
484 argc: left_argc,
485 target: left_target,
486 hits: left_hits,
487 },
488 Self::Warmup {
489 argc: right_argc,
490 target: right_target,
491 hits: right_hits,
492 },
493 ) => left_argc == right_argc && left_target == right_target && left_hits == right_hits,
494 (
495 Self::Specialized {
496 argc: left_argc,
497 target: left_target,
498 hits: left_hits,
499 misses: left_misses,
500 },
501 Self::Specialized {
502 argc: right_argc,
503 target: right_target,
504 hits: right_hits,
505 misses: right_misses,
506 },
507 ) => {
508 left_argc == right_argc
509 && left_target == right_target
510 && left_hits == right_hits
511 && left_misses == right_misses
512 }
513 _ => false,
514 }
515 }
516}
517
518impl Eq for DirectCallState {}
519
520#[derive(Debug, Clone, PartialEq, Eq)]
521pub(crate) enum PropertyCacheTarget {
522 DictField(Rc<str>),
523 StructField { field_name: Rc<str>, index: usize },
524 ListCount,
525 ListEmpty,
526 ListFirst,
527 ListLast,
528 StringCount,
529 StringEmpty,
530 PairFirst,
531 PairSecond,
532 EnumVariant,
533 EnumFields,
534}
535
536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
537pub(crate) enum MethodCacheTarget {
538 ListCount,
539 ListEmpty,
540 ListContains,
541 StringCount,
542 StringEmpty,
543 StringContains,
544 DictCount,
545 DictHas,
546 RangeCount,
547 RangeLen,
548 RangeEmpty,
549 RangeFirst,
550 RangeLast,
551 SetCount,
552 SetLen,
553 SetEmpty,
554 SetContains,
555}
556
557#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
559pub struct LocalSlotInfo {
560 pub name: String,
561 pub mutable: bool,
562 pub scope_depth: usize,
563}
564
565impl fmt::Display for Constant {
566 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567 match self {
568 Constant::Int(n) => write!(f, "{n}"),
569 Constant::Float(n) => write!(f, "{n}"),
570 Constant::String(s) => write!(f, "\"{s}\""),
571 Constant::Bool(b) => write!(f, "{b}"),
572 Constant::Nil => write!(f, "nil"),
573 Constant::Duration(ms) => write!(f, "{ms}ms"),
574 }
575 }
576}
577
578#[derive(Debug, Clone)]
580pub struct Chunk {
581 pub code: Vec<u8>,
583 pub constants: Vec<Constant>,
585 pub lines: Vec<u32>,
587 pub columns: Vec<u32>,
590 pub source_file: Option<String>,
595 current_col: u32,
597 pub functions: Vec<CompiledFunctionRef>,
599 inline_cache_slots: BTreeMap<usize, usize>,
602 inline_caches: Rc<RefCell<Vec<InlineCacheEntry>>>,
605 constant_strings: Rc<RefCell<Vec<Option<Rc<str>>>>>,
613 pub(crate) local_slots: Vec<LocalSlotInfo>,
615}
616
617pub type ChunkRef = Rc<Chunk>;
618pub type CompiledFunctionRef = Rc<CompiledFunction>;
619
620#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct CachedChunk {
626 pub(crate) code: Vec<u8>,
627 pub(crate) constants: Vec<Constant>,
628 pub(crate) lines: Vec<u32>,
629 pub(crate) columns: Vec<u32>,
630 pub(crate) source_file: Option<String>,
631 pub(crate) current_col: u32,
632 pub(crate) functions: Vec<CachedCompiledFunction>,
633 pub(crate) inline_cache_slots: BTreeMap<usize, usize>,
634 pub(crate) local_slots: Vec<LocalSlotInfo>,
635}
636
637#[derive(Debug, Clone, Serialize, Deserialize)]
638pub struct CachedCompiledFunction {
639 pub(crate) name: String,
640 pub(crate) type_params: Vec<String>,
641 pub(crate) nominal_type_names: Vec<String>,
642 pub(crate) params: Vec<ParamSlot>,
643 pub(crate) default_start: Option<usize>,
644 pub(crate) chunk: CachedChunk,
645 pub(crate) is_generator: bool,
646 pub(crate) is_stream: bool,
647 pub(crate) has_rest_param: bool,
648 pub(crate) has_runtime_type_checks: bool,
649}
650
651#[derive(Debug, Clone, Serialize, Deserialize)]
657pub struct ParamSlot {
658 pub name: String,
659 pub type_expr: Option<TypeExpr>,
662 pub has_default: bool,
666}
667
668impl ParamSlot {
669 pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
672 Self {
673 name: param.name.clone(),
674 type_expr: param.type_expr.clone(),
675 has_default: param.default_value.is_some(),
676 }
677 }
678
679 pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
684 params.iter().map(Self::from_typed_param).collect()
685 }
686}
687
688#[derive(Debug, Clone)]
690pub struct CompiledFunction {
691 pub name: String,
692 pub type_params: Vec<String>,
696 pub nominal_type_names: Vec<String>,
700 pub params: Vec<ParamSlot>,
701 pub default_start: Option<usize>,
703 pub chunk: ChunkRef,
704 pub is_generator: bool,
706 pub is_stream: bool,
708 pub has_rest_param: bool,
710 pub has_runtime_type_checks: bool,
715}
716
717impl CompiledFunction {
718 pub(crate) fn has_runtime_type_checks_for_params(params: &[ParamSlot]) -> bool {
719 params.iter().any(|param| param.type_expr.is_some())
720 }
721
722 pub fn param_names(&self) -> impl Iterator<Item = &str> {
725 self.params.iter().map(|p| p.name.as_str())
726 }
727
728 pub fn required_param_count(&self) -> usize {
730 self.default_start.unwrap_or(self.params.len())
731 }
732
733 pub fn declares_type_param(&self, name: &str) -> bool {
734 self.type_params.iter().any(|param| param == name)
735 }
736
737 pub fn has_nominal_type(&self, name: &str) -> bool {
738 self.nominal_type_names.iter().any(|ty| ty == name)
739 }
740
741 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
742 CachedCompiledFunction {
743 name: self.name.clone(),
744 type_params: self.type_params.clone(),
745 nominal_type_names: self.nominal_type_names.clone(),
746 params: self.params.clone(),
747 default_start: self.default_start,
748 chunk: self.chunk.freeze_for_cache(),
749 is_generator: self.is_generator,
750 is_stream: self.is_stream,
751 has_rest_param: self.has_rest_param,
752 has_runtime_type_checks: self.has_runtime_type_checks,
753 }
754 }
755
756 pub(crate) fn from_cached(cached: &CachedCompiledFunction) -> Self {
757 Self {
758 name: cached.name.clone(),
759 type_params: cached.type_params.clone(),
760 nominal_type_names: cached.nominal_type_names.clone(),
761 params: cached.params.clone(),
762 default_start: cached.default_start,
763 chunk: Rc::new(Chunk::from_cached(&cached.chunk)),
764 is_generator: cached.is_generator,
765 is_stream: cached.is_stream,
766 has_rest_param: cached.has_rest_param,
767 has_runtime_type_checks: cached.has_runtime_type_checks,
768 }
769 }
770}
771
772impl Chunk {
773 pub fn new() -> Self {
774 Self {
775 code: Vec::new(),
776 constants: Vec::new(),
777 lines: Vec::new(),
778 columns: Vec::new(),
779 source_file: None,
780 current_col: 0,
781 functions: Vec::new(),
782 inline_cache_slots: BTreeMap::new(),
783 inline_caches: Rc::new(RefCell::new(Vec::new())),
784 constant_strings: Rc::new(RefCell::new(Vec::new())),
785 local_slots: Vec::new(),
786 }
787 }
788
789 pub fn set_column(&mut self, col: u32) {
791 self.current_col = col;
792 }
793
794 pub fn add_constant(&mut self, constant: Constant) -> u16 {
796 for (i, c) in self.constants.iter().enumerate() {
797 if c == &constant {
798 return i as u16;
799 }
800 }
801 let idx = self.constants.len();
802 self.constants.push(constant);
803 idx as u16
804 }
805
806 pub fn emit(&mut self, op: Op, line: u32) {
808 let col = self.current_col;
809 let op_offset = self.code.len();
810 self.code.push(op as u8);
811 self.lines.push(line);
812 self.columns.push(col);
813 if is_adaptive_binary_op(op) {
814 self.register_inline_cache(op_offset);
815 }
816 }
817
818 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
820 let col = self.current_col;
821 let op_offset = self.code.len();
822 self.code.push(op as u8);
823 self.code.push((arg >> 8) as u8);
824 self.code.push((arg & 0xFF) as u8);
825 self.lines.push(line);
826 self.lines.push(line);
827 self.lines.push(line);
828 self.columns.push(col);
829 self.columns.push(col);
830 self.columns.push(col);
831 if matches!(
832 op,
833 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread
834 ) {
835 self.register_inline_cache(op_offset);
836 }
837 }
838
839 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
841 let col = self.current_col;
842 let op_offset = self.code.len();
843 self.code.push(op as u8);
844 self.code.push(arg);
845 self.lines.push(line);
846 self.lines.push(line);
847 self.columns.push(col);
848 self.columns.push(col);
849 if matches!(op, Op::Call) {
850 self.register_inline_cache(op_offset);
851 }
852 }
853
854 pub fn emit_call_builtin(
856 &mut self,
857 id: crate::BuiltinId,
858 name_idx: u16,
859 arg_count: u8,
860 line: u32,
861 ) {
862 let col = self.current_col;
863 let op_offset = self.code.len();
864 self.code.push(Op::CallBuiltin as u8);
865 self.code.extend_from_slice(&id.raw().to_be_bytes());
866 self.code.push((name_idx >> 8) as u8);
867 self.code.push((name_idx & 0xFF) as u8);
868 self.code.push(arg_count);
869 for _ in 0..12 {
870 self.lines.push(line);
871 self.columns.push(col);
872 }
873 self.register_inline_cache(op_offset);
874 }
875
876 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
878 let col = self.current_col;
879 self.code.push(Op::CallBuiltinSpread as u8);
880 self.code.extend_from_slice(&id.raw().to_be_bytes());
881 self.code.push((name_idx >> 8) as u8);
882 self.code.push((name_idx & 0xFF) as u8);
883 for _ in 0..11 {
884 self.lines.push(line);
885 self.columns.push(col);
886 }
887 }
888
889 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
891 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
892 }
893
894 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
896 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
897 }
898
899 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
900 let col = self.current_col;
901 let op_offset = self.code.len();
902 self.code.push(op as u8);
903 self.code.push((name_idx >> 8) as u8);
904 self.code.push((name_idx & 0xFF) as u8);
905 self.code.push(arg_count);
906 self.lines.push(line);
907 self.lines.push(line);
908 self.lines.push(line);
909 self.lines.push(line);
910 self.columns.push(col);
911 self.columns.push(col);
912 self.columns.push(col);
913 self.columns.push(col);
914 self.register_inline_cache(op_offset);
915 }
916
917 pub fn current_offset(&self) -> usize {
919 self.code.len()
920 }
921
922 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
924 let col = self.current_col;
925 self.code.push(op as u8);
926 let patch_pos = self.code.len();
927 self.code.push(0xFF);
928 self.code.push(0xFF);
929 self.lines.push(line);
930 self.lines.push(line);
931 self.lines.push(line);
932 self.columns.push(col);
933 self.columns.push(col);
934 self.columns.push(col);
935 patch_pos
936 }
937
938 pub fn patch_jump(&mut self, patch_pos: usize) {
940 let target = self.code.len() as u16;
941 self.code[patch_pos] = (target >> 8) as u8;
942 self.code[patch_pos + 1] = (target & 0xFF) as u8;
943 }
944
945 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
947 let target = target as u16;
948 self.code[patch_pos] = (target >> 8) as u8;
949 self.code[patch_pos + 1] = (target & 0xFF) as u8;
950 }
951
952 pub fn read_u16(&self, pos: usize) -> u16 {
954 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
955 }
956
957 fn register_inline_cache(&mut self, op_offset: usize) {
958 if self.inline_cache_slots.contains_key(&op_offset) {
959 return;
960 }
961 let mut entries = self.inline_caches.borrow_mut();
962 let slot = entries.len();
963 entries.push(InlineCacheEntry::Empty);
964 self.inline_cache_slots.insert(op_offset, slot);
965 }
966
967 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
968 self.inline_cache_slots.get(&op_offset).copied()
969 }
970
971 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<Rc<str>> {
976 let mut entries = self.constant_strings.borrow_mut();
981 if entries.len() < self.constants.len() {
982 entries.resize(self.constants.len(), None);
983 }
984 if let Some(Some(existing)) = entries.get(idx) {
985 return Some(Rc::clone(existing));
986 }
987 let materialized = match self.constants.get(idx)? {
988 Constant::String(s) => Rc::<str>::from(s.as_str()),
989 _ => return None,
990 };
991 entries[idx] = Some(Rc::clone(&materialized));
992 Some(materialized)
993 }
994
995 pub(crate) fn inline_cache_entry(&self, slot: usize) -> InlineCacheEntry {
996 self.inline_caches
997 .borrow()
998 .get(slot)
999 .cloned()
1000 .unwrap_or(InlineCacheEntry::Empty)
1001 }
1002
1003 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1004 if let Some(existing) = self.inline_caches.borrow_mut().get_mut(slot) {
1005 *existing = entry;
1006 }
1007 }
1008
1009 pub fn freeze_for_cache(&self) -> CachedChunk {
1010 CachedChunk {
1011 code: self.code.clone(),
1012 constants: self.constants.clone(),
1013 lines: self.lines.clone(),
1014 columns: self.columns.clone(),
1015 source_file: self.source_file.clone(),
1016 current_col: self.current_col,
1017 functions: self
1018 .functions
1019 .iter()
1020 .map(|function| function.freeze_for_cache())
1021 .collect(),
1022 inline_cache_slots: self.inline_cache_slots.clone(),
1023 local_slots: self.local_slots.clone(),
1024 }
1025 }
1026
1027 pub fn from_cached(cached: &CachedChunk) -> Self {
1028 let inline_cache_count = cached.inline_cache_slots.len();
1029 let constants_count = cached.constants.len();
1030 Self {
1031 code: cached.code.clone(),
1032 constants: cached.constants.clone(),
1033 lines: cached.lines.clone(),
1034 columns: cached.columns.clone(),
1035 source_file: cached.source_file.clone(),
1036 current_col: cached.current_col,
1037 functions: cached
1038 .functions
1039 .iter()
1040 .map(|function| Rc::new(CompiledFunction::from_cached(function)))
1041 .collect(),
1042 inline_cache_slots: cached.inline_cache_slots.clone(),
1043 inline_caches: Rc::new(RefCell::new(vec![
1044 InlineCacheEntry::Empty;
1045 inline_cache_count
1046 ])),
1047 constant_strings: Rc::new(RefCell::new(vec![None; constants_count])),
1048 local_slots: cached.local_slots.clone(),
1049 }
1050 }
1051
1052 pub(crate) fn add_local_slot(
1053 &mut self,
1054 name: String,
1055 mutable: bool,
1056 scope_depth: usize,
1057 ) -> u16 {
1058 let idx = self.local_slots.len();
1059 self.local_slots.push(LocalSlotInfo {
1060 name,
1061 mutable,
1062 scope_depth,
1063 });
1064 idx as u16
1065 }
1066
1067 #[cfg(test)]
1068 pub(crate) fn inline_cache_entries(&self) -> Vec<InlineCacheEntry> {
1069 self.inline_caches.borrow().clone()
1070 }
1071
1072 pub fn read_u64(&self, pos: usize) -> u64 {
1074 u64::from_be_bytes([
1075 self.code[pos],
1076 self.code[pos + 1],
1077 self.code[pos + 2],
1078 self.code[pos + 3],
1079 self.code[pos + 4],
1080 self.code[pos + 5],
1081 self.code[pos + 6],
1082 self.code[pos + 7],
1083 ])
1084 }
1085
1086 pub fn disassemble(&self, name: &str) -> String {
1088 let mut out = format!("== {name} ==\n");
1089 let mut ip = 0;
1090 while ip < self.code.len() {
1091 let op = self.code[ip];
1092 let line = self.lines.get(ip).copied().unwrap_or(0);
1093 out.push_str(&format!("{:04} [{:>4}] ", ip, line));
1094 ip += 1;
1095
1096 match op {
1097 x if x == Op::Constant as u8 => {
1098 let idx = self.read_u16(ip);
1099 ip += 2;
1100 let val = &self.constants[idx as usize];
1101 out.push_str(&format!("CONSTANT {:>4} ({})\n", idx, val));
1102 }
1103 x if x == Op::Nil as u8 => out.push_str("NIL\n"),
1104 x if x == Op::True as u8 => out.push_str("TRUE\n"),
1105 x if x == Op::False as u8 => out.push_str("FALSE\n"),
1106 x if x == Op::GetVar as u8 => {
1107 let idx = self.read_u16(ip);
1108 ip += 2;
1109 out.push_str(&format!(
1110 "GET_VAR {:>4} ({})\n",
1111 idx, self.constants[idx as usize]
1112 ));
1113 }
1114 x if x == Op::DefLet as u8 => {
1115 let idx = self.read_u16(ip);
1116 ip += 2;
1117 out.push_str(&format!(
1118 "DEF_LET {:>4} ({})\n",
1119 idx, self.constants[idx as usize]
1120 ));
1121 }
1122 x if x == Op::DefVar as u8 => {
1123 let idx = self.read_u16(ip);
1124 ip += 2;
1125 out.push_str(&format!(
1126 "DEF_VAR {:>4} ({})\n",
1127 idx, self.constants[idx as usize]
1128 ));
1129 }
1130 x if x == Op::SetVar as u8 => {
1131 let idx = self.read_u16(ip);
1132 ip += 2;
1133 out.push_str(&format!(
1134 "SET_VAR {:>4} ({})\n",
1135 idx, self.constants[idx as usize]
1136 ));
1137 }
1138 x if x == Op::GetLocalSlot as u8 => {
1139 let slot = self.read_u16(ip);
1140 ip += 2;
1141 out.push_str(&format!("GET_LOCAL_SLOT {:>4}", slot));
1142 if let Some(info) = self.local_slots.get(slot as usize) {
1143 out.push_str(&format!(" ({})", info.name));
1144 }
1145 out.push('\n');
1146 }
1147 x if x == Op::DefLocalSlot as u8 => {
1148 let slot = self.read_u16(ip);
1149 ip += 2;
1150 out.push_str(&format!("DEF_LOCAL_SLOT {:>4}", slot));
1151 if let Some(info) = self.local_slots.get(slot as usize) {
1152 out.push_str(&format!(" ({})", info.name));
1153 }
1154 out.push('\n');
1155 }
1156 x if x == Op::SetLocalSlot as u8 => {
1157 let slot = self.read_u16(ip);
1158 ip += 2;
1159 out.push_str(&format!("SET_LOCAL_SLOT {:>4}", slot));
1160 if let Some(info) = self.local_slots.get(slot as usize) {
1161 out.push_str(&format!(" ({})", info.name));
1162 }
1163 out.push('\n');
1164 }
1165 x if x == Op::PushScope as u8 => out.push_str("PUSH_SCOPE\n"),
1166 x if x == Op::PopScope as u8 => out.push_str("POP_SCOPE\n"),
1167 x if x == Op::Add as u8 => out.push_str("ADD\n"),
1168 x if x == Op::Sub as u8 => out.push_str("SUB\n"),
1169 x if x == Op::Mul as u8 => out.push_str("MUL\n"),
1170 x if x == Op::Div as u8 => out.push_str("DIV\n"),
1171 x if x == Op::Mod as u8 => out.push_str("MOD\n"),
1172 x if x == Op::Pow as u8 => out.push_str("POW\n"),
1173 x if x == Op::Negate as u8 => out.push_str("NEGATE\n"),
1174 x if x == Op::Equal as u8 => out.push_str("EQUAL\n"),
1175 x if x == Op::NotEqual as u8 => out.push_str("NOT_EQUAL\n"),
1176 x if x == Op::Less as u8 => out.push_str("LESS\n"),
1177 x if x == Op::Greater as u8 => out.push_str("GREATER\n"),
1178 x if x == Op::LessEqual as u8 => out.push_str("LESS_EQUAL\n"),
1179 x if x == Op::GreaterEqual as u8 => out.push_str("GREATER_EQUAL\n"),
1180 x if x == Op::Contains as u8 => out.push_str("CONTAINS\n"),
1181 x if x == Op::Not as u8 => out.push_str("NOT\n"),
1182 x if x == Op::Jump as u8 => {
1183 let target = self.read_u16(ip);
1184 ip += 2;
1185 out.push_str(&format!("JUMP {:>4}\n", target));
1186 }
1187 x if x == Op::JumpIfFalse as u8 => {
1188 let target = self.read_u16(ip);
1189 ip += 2;
1190 out.push_str(&format!("JUMP_IF_FALSE {:>4}\n", target));
1191 }
1192 x if x == Op::JumpIfTrue as u8 => {
1193 let target = self.read_u16(ip);
1194 ip += 2;
1195 out.push_str(&format!("JUMP_IF_TRUE {:>4}\n", target));
1196 }
1197 x if x == Op::Pop as u8 => out.push_str("POP\n"),
1198 x if x == Op::Call as u8 => {
1199 let argc = self.code[ip];
1200 ip += 1;
1201 out.push_str(&format!("CALL {:>4}\n", argc));
1202 }
1203 x if x == Op::TailCall as u8 => {
1204 let argc = self.code[ip];
1205 ip += 1;
1206 out.push_str(&format!("TAIL_CALL {:>4}\n", argc));
1207 }
1208 x if x == Op::Return as u8 => out.push_str("RETURN\n"),
1209 x if x == Op::Closure as u8 => {
1210 let idx = self.read_u16(ip);
1211 ip += 2;
1212 out.push_str(&format!("CLOSURE {:>4}\n", idx));
1213 }
1214 x if x == Op::BuildList as u8 => {
1215 let count = self.read_u16(ip);
1216 ip += 2;
1217 out.push_str(&format!("BUILD_LIST {:>4}\n", count));
1218 }
1219 x if x == Op::BuildDict as u8 => {
1220 let count = self.read_u16(ip);
1221 ip += 2;
1222 out.push_str(&format!("BUILD_DICT {:>4}\n", count));
1223 }
1224 x if x == Op::Subscript as u8 => out.push_str("SUBSCRIPT\n"),
1225 x if x == Op::SubscriptOpt as u8 => out.push_str("SUBSCRIPT_OPT\n"),
1226 x if x == Op::Slice as u8 => out.push_str("SLICE\n"),
1227 x if x == Op::GetProperty as u8 => {
1228 let idx = self.read_u16(ip);
1229 ip += 2;
1230 out.push_str(&format!(
1231 "GET_PROPERTY {:>4} ({})\n",
1232 idx, self.constants[idx as usize]
1233 ));
1234 }
1235 x if x == Op::GetPropertyOpt as u8 => {
1236 let idx = self.read_u16(ip);
1237 ip += 2;
1238 out.push_str(&format!(
1239 "GET_PROPERTY_OPT {:>4} ({})\n",
1240 idx, self.constants[idx as usize]
1241 ));
1242 }
1243 x if x == Op::SetProperty as u8 => {
1244 let idx = self.read_u16(ip);
1245 ip += 2;
1246 out.push_str(&format!(
1247 "SET_PROPERTY {:>4} ({})\n",
1248 idx, self.constants[idx as usize]
1249 ));
1250 }
1251 x if x == Op::SetSubscript as u8 => {
1252 let idx = self.read_u16(ip);
1253 ip += 2;
1254 out.push_str(&format!(
1255 "SET_SUBSCRIPT {:>4} ({})\n",
1256 idx, self.constants[idx as usize]
1257 ));
1258 }
1259 x if x == Op::MethodCall as u8 => {
1260 let idx = self.read_u16(ip);
1261 ip += 2;
1262 let argc = self.code[ip];
1263 ip += 1;
1264 out.push_str(&format!(
1265 "METHOD_CALL {:>4} ({}) argc={}\n",
1266 idx, self.constants[idx as usize], argc
1267 ));
1268 }
1269 x if x == Op::MethodCallOpt as u8 => {
1270 let idx = self.read_u16(ip);
1271 ip += 2;
1272 let argc = self.code[ip];
1273 ip += 1;
1274 out.push_str(&format!(
1275 "METHOD_CALL_OPT {:>4} ({}) argc={}\n",
1276 idx, self.constants[idx as usize], argc
1277 ));
1278 }
1279 x if x == Op::Concat as u8 => {
1280 let count = self.read_u16(ip);
1281 ip += 2;
1282 out.push_str(&format!("CONCAT {:>4}\n", count));
1283 }
1284 x if x == Op::IterInit as u8 => out.push_str("ITER_INIT\n"),
1285 x if x == Op::IterNext as u8 => {
1286 let target = self.read_u16(ip);
1287 ip += 2;
1288 out.push_str(&format!("ITER_NEXT {:>4}\n", target));
1289 }
1290 x if x == Op::Throw as u8 => out.push_str("THROW\n"),
1291 x if x == Op::TryCatchSetup as u8 => {
1292 let target = self.read_u16(ip);
1293 ip += 2;
1294 out.push_str(&format!("TRY_CATCH_SETUP {:>4}\n", target));
1295 }
1296 x if x == Op::PopHandler as u8 => out.push_str("POP_HANDLER\n"),
1297 x if x == Op::Pipe as u8 => out.push_str("PIPE\n"),
1298 x if x == Op::Parallel as u8 => out.push_str("PARALLEL\n"),
1299 x if x == Op::ParallelMap as u8 => out.push_str("PARALLEL_MAP\n"),
1300 x if x == Op::ParallelMapStream as u8 => out.push_str("PARALLEL_MAP_STREAM\n"),
1301 x if x == Op::ParallelSettle as u8 => out.push_str("PARALLEL_SETTLE\n"),
1302 x if x == Op::Spawn as u8 => out.push_str("SPAWN\n"),
1303 x if x == Op::Import as u8 => {
1304 let idx = self.read_u16(ip);
1305 ip += 2;
1306 out.push_str(&format!(
1307 "IMPORT {:>4} ({})\n",
1308 idx, self.constants[idx as usize]
1309 ));
1310 }
1311 x if x == Op::SelectiveImport as u8 => {
1312 let path_idx = self.read_u16(ip);
1313 ip += 2;
1314 let names_idx = self.read_u16(ip);
1315 ip += 2;
1316 out.push_str(&format!(
1317 "SELECTIVE_IMPORT {:>4} ({}) names: {:>4} ({})\n",
1318 path_idx,
1319 self.constants[path_idx as usize],
1320 names_idx,
1321 self.constants[names_idx as usize]
1322 ));
1323 }
1324 x if x == Op::SyncMutexEnter as u8 => {
1325 let idx = self.read_u16(ip);
1326 ip += 2;
1327 out.push_str(&format!(
1328 "SYNC_MUTEX_ENTER {:>4} ({})\n",
1329 idx, self.constants[idx as usize]
1330 ));
1331 }
1332 x if x == Op::DeadlineSetup as u8 => out.push_str("DEADLINE_SETUP\n"),
1333 x if x == Op::DeadlineEnd as u8 => out.push_str("DEADLINE_END\n"),
1334 x if x == Op::BuildEnum as u8 => {
1335 let enum_idx = self.read_u16(ip);
1336 ip += 2;
1337 let variant_idx = self.read_u16(ip);
1338 ip += 2;
1339 let field_count = self.read_u16(ip);
1340 ip += 2;
1341 out.push_str(&format!(
1342 "BUILD_ENUM {:>4} ({}) {:>4} ({}) fields={}\n",
1343 enum_idx,
1344 self.constants[enum_idx as usize],
1345 variant_idx,
1346 self.constants[variant_idx as usize],
1347 field_count
1348 ));
1349 }
1350 x if x == Op::MatchEnum as u8 => {
1351 let enum_idx = self.read_u16(ip);
1352 ip += 2;
1353 let variant_idx = self.read_u16(ip);
1354 ip += 2;
1355 out.push_str(&format!(
1356 "MATCH_ENUM {:>4} ({}) {:>4} ({})\n",
1357 enum_idx,
1358 self.constants[enum_idx as usize],
1359 variant_idx,
1360 self.constants[variant_idx as usize]
1361 ));
1362 }
1363 x if x == Op::PopIterator as u8 => out.push_str("POP_ITERATOR\n"),
1364 x if x == Op::TryUnwrap as u8 => out.push_str("TRY_UNWRAP\n"),
1365 x if x == Op::TryWrapOk as u8 => out.push_str("TRY_WRAP_OK\n"),
1366 x if x == Op::CallSpread as u8 => out.push_str("CALL_SPREAD\n"),
1367 x if x == Op::CallBuiltin as u8 => {
1368 let id = self.read_u64(ip);
1369 ip += 8;
1370 let idx = self.read_u16(ip);
1371 ip += 2;
1372 let argc = self.code[ip];
1373 ip += 1;
1374 out.push_str(&format!(
1375 "CALL_BUILTIN {id:#018x} {:>4} ({}) argc={}\n",
1376 idx, self.constants[idx as usize], argc
1377 ));
1378 }
1379 x if x == Op::CallBuiltinSpread as u8 => {
1380 let id = self.read_u64(ip);
1381 ip += 8;
1382 let idx = self.read_u16(ip);
1383 ip += 2;
1384 out.push_str(&format!(
1385 "CALL_BUILTIN_SPREAD {id:#018x} {:>4} ({})\n",
1386 idx, self.constants[idx as usize]
1387 ));
1388 }
1389 x if x == Op::MethodCallSpread as u8 => {
1390 let idx = self.read_u16(ip + 1);
1391 ip += 2;
1392 out.push_str(&format!("METHOD_CALL_SPREAD {idx}\n"));
1393 }
1394 x if x == Op::Dup as u8 => out.push_str("DUP\n"),
1395 x if x == Op::Swap as u8 => out.push_str("SWAP\n"),
1396 x if x == Op::AddInt as u8 => out.push_str("ADD_INT\n"),
1397 x if x == Op::SubInt as u8 => out.push_str("SUB_INT\n"),
1398 x if x == Op::MulInt as u8 => out.push_str("MUL_INT\n"),
1399 x if x == Op::DivInt as u8 => out.push_str("DIV_INT\n"),
1400 x if x == Op::ModInt as u8 => out.push_str("MOD_INT\n"),
1401 x if x == Op::AddFloat as u8 => out.push_str("ADD_FLOAT\n"),
1402 x if x == Op::SubFloat as u8 => out.push_str("SUB_FLOAT\n"),
1403 x if x == Op::MulFloat as u8 => out.push_str("MUL_FLOAT\n"),
1404 x if x == Op::DivFloat as u8 => out.push_str("DIV_FLOAT\n"),
1405 x if x == Op::ModFloat as u8 => out.push_str("MOD_FLOAT\n"),
1406 x if x == Op::EqualInt as u8 => out.push_str("EQUAL_INT\n"),
1407 x if x == Op::NotEqualInt as u8 => out.push_str("NOT_EQUAL_INT\n"),
1408 x if x == Op::LessInt as u8 => out.push_str("LESS_INT\n"),
1409 x if x == Op::GreaterInt as u8 => out.push_str("GREATER_INT\n"),
1410 x if x == Op::LessEqualInt as u8 => out.push_str("LESS_EQUAL_INT\n"),
1411 x if x == Op::GreaterEqualInt as u8 => out.push_str("GREATER_EQUAL_INT\n"),
1412 x if x == Op::EqualFloat as u8 => out.push_str("EQUAL_FLOAT\n"),
1413 x if x == Op::NotEqualFloat as u8 => out.push_str("NOT_EQUAL_FLOAT\n"),
1414 x if x == Op::LessFloat as u8 => out.push_str("LESS_FLOAT\n"),
1415 x if x == Op::GreaterFloat as u8 => out.push_str("GREATER_FLOAT\n"),
1416 x if x == Op::LessEqualFloat as u8 => out.push_str("LESS_EQUAL_FLOAT\n"),
1417 x if x == Op::GreaterEqualFloat as u8 => out.push_str("GREATER_EQUAL_FLOAT\n"),
1418 x if x == Op::EqualBool as u8 => out.push_str("EQUAL_BOOL\n"),
1419 x if x == Op::NotEqualBool as u8 => out.push_str("NOT_EQUAL_BOOL\n"),
1420 x if x == Op::EqualString as u8 => out.push_str("EQUAL_STRING\n"),
1421 x if x == Op::NotEqualString as u8 => out.push_str("NOT_EQUAL_STRING\n"),
1422 x if x == Op::Yield as u8 => out.push_str("YIELD\n"),
1423 _ => {
1424 out.push_str(&format!("UNKNOWN(0x{:02x})\n", op));
1425 }
1426 }
1427 }
1428 out
1429 }
1430}
1431
1432fn is_adaptive_binary_op(op: Op) -> bool {
1433 matches!(
1434 op,
1435 Op::Add
1436 | Op::Sub
1437 | Op::Mul
1438 | Op::Div
1439 | Op::Mod
1440 | Op::Equal
1441 | Op::NotEqual
1442 | Op::Less
1443 | Op::Greater
1444 | Op::LessEqual
1445 | Op::GreaterEqual
1446 )
1447}
1448
1449impl Default for Chunk {
1450 fn default() -> Self {
1451 Self::new()
1452 }
1453}
1454
1455#[cfg(test)]
1456mod tests {
1457 use super::Op;
1458
1459 #[test]
1460 fn op_from_byte_matches_repr_order() {
1461 for (byte, op) in Op::ALL.iter().copied().enumerate() {
1462 assert_eq!(byte as u8, op as u8);
1463 assert_eq!(Op::from_byte(byte as u8), Some(op));
1464 }
1465 assert_eq!(Op::from_byte(Op::ALL.len() as u8), None);
1466 }
1467}