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
9use crate::runtime_guards::RuntimeParamGuard;
10
11pub(crate) const NO_INLINE_CACHE_SLOT: u32 = u32::MAX;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[repr(u8)]
23pub enum Op {
24 Constant, Nil,
28 True,
30 False,
32
33 GetVar, DefLet, DefVar, SetVar, PushScope,
44 PopScope,
46
47 Add,
49 Sub,
50 Mul,
51 Div,
52 Mod,
53 Pow,
54 Negate,
55
56 Equal,
58 NotEqual,
59 Less,
60 Greater,
61 LessEqual,
62 GreaterEqual,
63
64 Not,
66
67 Jump,
70 JumpIfFalse,
72 JumpIfTrue,
74 Pop,
76
77 Call,
80 TailCall,
84 Return,
86 Closure,
88
89 BuildList,
92 BuildDict,
94 Subscript,
96 SubscriptOpt,
99 Slice,
101
102 GetProperty,
105 GetPropertyOpt,
108 SetProperty,
111 SetSubscript,
114 MethodCall,
116 MethodCallOpt,
119
120 Concat,
123
124 IterInit,
127 IterNext,
130
131 Pipe,
134
135 Throw,
138 TryCatchSetup,
140 PopHandler,
142
143 Parallel,
147 ParallelMap,
150 ParallelMapStream,
153 ParallelSettle,
156 Spawn,
159 SyncMutexEnter,
162
163 Import,
166 SelectiveImport,
168
169 DeadlineSetup,
172 DeadlineEnd,
174
175 BuildEnum,
180
181 MatchEnum,
186
187 PopIterator,
190
191 GetArgc,
194
195 CheckType,
201
202 TryUnwrap,
205 TryWrapOk,
207
208 CallSpread,
211 CallBuiltin,
214 CallBuiltinSpread,
217 MethodCallSpread,
220
221 Dup,
224 Swap,
226 Contains,
229
230 AddInt,
232 SubInt,
233 MulInt,
234 DivInt,
235 ModInt,
236 AddFloat,
237 SubFloat,
238 MulFloat,
239 DivFloat,
240 ModFloat,
241 EqualInt,
242 NotEqualInt,
243 LessInt,
244 GreaterInt,
245 LessEqualInt,
246 GreaterEqualInt,
247 EqualFloat,
248 NotEqualFloat,
249 LessFloat,
250 GreaterFloat,
251 LessEqualFloat,
252 GreaterEqualFloat,
253 EqualBool,
254 NotEqualBool,
255 EqualString,
256 NotEqualString,
257
258 Yield,
260
261 GetLocalSlot,
264 DefLocalSlot,
266 SetLocalSlot,
268}
269
270impl Op {
271 pub(crate) const ALL: &'static [Self] = &[
272 Op::Constant,
273 Op::Nil,
274 Op::True,
275 Op::False,
276 Op::GetVar,
277 Op::DefLet,
278 Op::DefVar,
279 Op::SetVar,
280 Op::PushScope,
281 Op::PopScope,
282 Op::Add,
283 Op::Sub,
284 Op::Mul,
285 Op::Div,
286 Op::Mod,
287 Op::Pow,
288 Op::Negate,
289 Op::Equal,
290 Op::NotEqual,
291 Op::Less,
292 Op::Greater,
293 Op::LessEqual,
294 Op::GreaterEqual,
295 Op::Not,
296 Op::Jump,
297 Op::JumpIfFalse,
298 Op::JumpIfTrue,
299 Op::Pop,
300 Op::Call,
301 Op::TailCall,
302 Op::Return,
303 Op::Closure,
304 Op::BuildList,
305 Op::BuildDict,
306 Op::Subscript,
307 Op::SubscriptOpt,
308 Op::Slice,
309 Op::GetProperty,
310 Op::GetPropertyOpt,
311 Op::SetProperty,
312 Op::SetSubscript,
313 Op::MethodCall,
314 Op::MethodCallOpt,
315 Op::Concat,
316 Op::IterInit,
317 Op::IterNext,
318 Op::Pipe,
319 Op::Throw,
320 Op::TryCatchSetup,
321 Op::PopHandler,
322 Op::Parallel,
323 Op::ParallelMap,
324 Op::ParallelMapStream,
325 Op::ParallelSettle,
326 Op::Spawn,
327 Op::SyncMutexEnter,
328 Op::Import,
329 Op::SelectiveImport,
330 Op::DeadlineSetup,
331 Op::DeadlineEnd,
332 Op::BuildEnum,
333 Op::MatchEnum,
334 Op::PopIterator,
335 Op::GetArgc,
336 Op::CheckType,
337 Op::TryUnwrap,
338 Op::TryWrapOk,
339 Op::CallSpread,
340 Op::CallBuiltin,
341 Op::CallBuiltinSpread,
342 Op::MethodCallSpread,
343 Op::Dup,
344 Op::Swap,
345 Op::Contains,
346 Op::AddInt,
347 Op::SubInt,
348 Op::MulInt,
349 Op::DivInt,
350 Op::ModInt,
351 Op::AddFloat,
352 Op::SubFloat,
353 Op::MulFloat,
354 Op::DivFloat,
355 Op::ModFloat,
356 Op::EqualInt,
357 Op::NotEqualInt,
358 Op::LessInt,
359 Op::GreaterInt,
360 Op::LessEqualInt,
361 Op::GreaterEqualInt,
362 Op::EqualFloat,
363 Op::NotEqualFloat,
364 Op::LessFloat,
365 Op::GreaterFloat,
366 Op::LessEqualFloat,
367 Op::GreaterEqualFloat,
368 Op::EqualBool,
369 Op::NotEqualBool,
370 Op::EqualString,
371 Op::NotEqualString,
372 Op::Yield,
373 Op::GetLocalSlot,
374 Op::DefLocalSlot,
375 Op::SetLocalSlot,
376 ];
377
378 pub(crate) fn from_byte(byte: u8) -> Option<Self> {
379 Self::ALL.get(byte as usize).copied()
380 }
381}
382
383#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
385pub enum Constant {
386 Int(i64),
387 Float(f64),
388 String(String),
389 Bool(bool),
390 Nil,
391 Duration(i64),
392}
393
394#[derive(Debug, Clone, PartialEq, Eq)]
404pub(crate) enum InlineCacheEntry {
405 Empty,
406 Property {
407 name_idx: u16,
408 target: PropertyCacheTarget,
409 },
410 Method {
411 name_idx: u16,
412 argc: usize,
413 target: MethodCacheTarget,
414 },
415 AdaptiveBinary {
416 op: AdaptiveBinaryOp,
417 state: AdaptiveBinaryState,
418 },
419 DirectCall {
420 state: DirectCallState,
421 },
422}
423
424#[derive(Debug, Clone, Copy, PartialEq, Eq)]
425pub(crate) enum AdaptiveBinaryOp {
426 Add,
427 Sub,
428 Mul,
429 Div,
430 Mod,
431 Equal,
432 NotEqual,
433 Less,
434 Greater,
435 LessEqual,
436 GreaterEqual,
437}
438
439#[derive(Debug, Clone, Copy, PartialEq, Eq)]
448pub(crate) enum AdaptiveBinaryState {
449 Warmup {
450 shape: BinaryShape,
451 hits: u8,
452 },
453 Specialized {
454 shape: BinaryShape,
455 hits: u64,
456 misses: u64,
457 },
458}
459
460#[derive(Debug, Clone, Copy, PartialEq, Eq)]
461pub(crate) enum BinaryShape {
462 Int,
463 Float,
464 Bool,
465 String,
466}
467
468#[derive(Debug, Clone)]
469pub(crate) enum DirectCallState {
470 Warmup {
471 argc: usize,
472 target: DirectCallTarget,
473 hits: u8,
474 },
475 Specialized {
476 argc: usize,
477 target: DirectCallTarget,
478 hits: u64,
479 misses: u64,
480 },
481}
482
483#[derive(Debug, Clone)]
484pub(crate) enum DirectCallTarget {
485 Closure(Rc<crate::value::VmClosure>),
486}
487
488impl PartialEq for DirectCallTarget {
489 fn eq(&self, other: &Self) -> bool {
490 match (self, other) {
491 (Self::Closure(left), Self::Closure(right)) => Rc::ptr_eq(left, right),
492 }
493 }
494}
495
496impl Eq for DirectCallTarget {}
497
498impl PartialEq for DirectCallState {
499 fn eq(&self, other: &Self) -> bool {
500 match (self, other) {
501 (
502 Self::Warmup {
503 argc: left_argc,
504 target: left_target,
505 hits: left_hits,
506 },
507 Self::Warmup {
508 argc: right_argc,
509 target: right_target,
510 hits: right_hits,
511 },
512 ) => left_argc == right_argc && left_target == right_target && left_hits == right_hits,
513 (
514 Self::Specialized {
515 argc: left_argc,
516 target: left_target,
517 hits: left_hits,
518 misses: left_misses,
519 },
520 Self::Specialized {
521 argc: right_argc,
522 target: right_target,
523 hits: right_hits,
524 misses: right_misses,
525 },
526 ) => {
527 left_argc == right_argc
528 && left_target == right_target
529 && left_hits == right_hits
530 && left_misses == right_misses
531 }
532 _ => false,
533 }
534 }
535}
536
537impl Eq for DirectCallState {}
538
539#[derive(Debug, Clone, PartialEq, Eq)]
540pub(crate) enum PropertyCacheTarget {
541 DictField(Rc<str>),
542 StructField { field_name: Rc<str>, index: usize },
543 ListCount,
544 ListEmpty,
545 ListFirst,
546 ListLast,
547 StringCount,
548 StringEmpty,
549 PairFirst,
550 PairSecond,
551 EnumVariant,
552 EnumFields,
553}
554
555#[derive(Debug, Clone, Copy, PartialEq, Eq)]
556pub(crate) enum MethodCacheTarget {
557 ListCount,
558 ListEmpty,
559 ListContains,
560 StringCount,
561 StringEmpty,
562 StringContains,
563 DictCount,
564 DictHas,
565 RangeCount,
566 RangeLen,
567 RangeEmpty,
568 RangeFirst,
569 RangeLast,
570 SetCount,
571 SetLen,
572 SetEmpty,
573 SetContains,
574}
575
576#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
578pub struct LocalSlotInfo {
579 pub name: String,
580 pub mutable: bool,
581 pub scope_depth: usize,
582}
583
584impl fmt::Display for Constant {
585 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
586 match self {
587 Constant::Int(n) => write!(f, "{n}"),
588 Constant::Float(n) => write!(f, "{n}"),
589 Constant::String(s) => write!(f, "\"{s}\""),
590 Constant::Bool(b) => write!(f, "{b}"),
591 Constant::Nil => write!(f, "nil"),
592 Constant::Duration(ms) => write!(f, "{ms}ms"),
593 }
594 }
595}
596
597#[derive(Debug, Clone)]
599pub struct Chunk {
600 pub code: Vec<u8>,
602 pub constants: Vec<Constant>,
604 pub lines: Vec<u32>,
606 pub columns: Vec<u32>,
609 pub source_file: Option<String>,
614 current_col: u32,
616 pub functions: Vec<CompiledFunctionRef>,
618 inline_cache_slots: BTreeMap<usize, usize>,
624 inline_cache_index: Vec<u32>,
632 inline_caches: Rc<RefCell<Vec<InlineCacheEntry>>>,
635 constant_strings: Rc<RefCell<Vec<Option<Rc<str>>>>>,
643 pub(crate) local_slots: Vec<LocalSlotInfo>,
645 pub(crate) references_outer_names: bool,
661}
662
663pub type ChunkRef = Rc<Chunk>;
664pub type CompiledFunctionRef = Rc<CompiledFunction>;
665
666#[derive(Debug, Clone, Serialize, Deserialize)]
671pub struct CachedChunk {
672 pub(crate) code: Vec<u8>,
673 pub(crate) constants: Vec<Constant>,
674 pub(crate) lines: Vec<u32>,
675 pub(crate) columns: Vec<u32>,
676 pub(crate) source_file: Option<String>,
677 pub(crate) current_col: u32,
678 pub(crate) functions: Vec<CachedCompiledFunction>,
679 pub(crate) inline_cache_slots: BTreeMap<usize, usize>,
680 pub(crate) local_slots: Vec<LocalSlotInfo>,
681 #[serde(default)]
682 pub(crate) references_outer_names: bool,
683}
684
685#[derive(Debug, Clone, Serialize, Deserialize)]
686pub struct CachedCompiledFunction {
687 pub(crate) name: String,
688 pub(crate) type_params: Vec<String>,
689 pub(crate) nominal_type_names: Vec<String>,
690 pub(crate) params: Vec<CachedParamSlot>,
691 pub(crate) default_start: Option<usize>,
692 pub(crate) chunk: CachedChunk,
693 pub(crate) is_generator: bool,
694 pub(crate) is_stream: bool,
695 pub(crate) has_rest_param: bool,
696 pub(crate) has_runtime_type_checks: bool,
697}
698
699#[derive(Debug, Clone, Serialize, Deserialize)]
700pub(crate) struct CachedParamSlot {
701 pub(crate) name: String,
702 pub(crate) type_expr: Option<TypeExpr>,
703 pub(crate) has_default: bool,
704}
705
706impl CachedParamSlot {
707 fn thaw(&self) -> ParamSlot {
708 ParamSlot {
709 name: self.name.clone(),
710 type_expr: self.type_expr.clone(),
711 runtime_guard: self
712 .type_expr
713 .as_ref()
714 .map(RuntimeParamGuard::from_type_expr),
715 has_default: self.has_default,
716 }
717 }
718}
719
720#[derive(Debug, Clone, Serialize, Deserialize)]
726pub struct ParamSlot {
727 pub name: String,
728 pub type_expr: Option<TypeExpr>,
731 #[serde(skip)]
734 pub(crate) runtime_guard: Option<RuntimeParamGuard>,
735 pub has_default: bool,
739}
740
741impl ParamSlot {
742 pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
745 Self {
746 name: param.name.clone(),
747 type_expr: param.type_expr.clone(),
748 runtime_guard: param
749 .type_expr
750 .as_ref()
751 .map(RuntimeParamGuard::from_type_expr),
752 has_default: param.default_value.is_some(),
753 }
754 }
755
756 fn freeze_for_cache(&self) -> CachedParamSlot {
757 CachedParamSlot {
758 name: self.name.clone(),
759 type_expr: self.type_expr.clone(),
760 has_default: self.has_default,
761 }
762 }
763
764 pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
769 params.iter().map(Self::from_typed_param).collect()
770 }
771}
772
773#[derive(Debug, Clone)]
775pub struct CompiledFunction {
776 pub name: String,
777 pub type_params: Vec<String>,
781 pub nominal_type_names: Vec<String>,
785 pub params: Vec<ParamSlot>,
786 pub default_start: Option<usize>,
788 pub chunk: ChunkRef,
789 pub is_generator: bool,
791 pub is_stream: bool,
793 pub has_rest_param: bool,
795 pub has_runtime_type_checks: bool,
800}
801
802impl CompiledFunction {
803 pub(crate) fn has_runtime_type_checks_for_params(params: &[ParamSlot]) -> bool {
804 params.iter().any(|param| param.type_expr.is_some())
805 }
806
807 pub fn param_names(&self) -> impl Iterator<Item = &str> {
810 self.params.iter().map(|p| p.name.as_str())
811 }
812
813 pub fn required_param_count(&self) -> usize {
815 self.default_start.unwrap_or(self.params.len())
816 }
817
818 pub fn declares_type_param(&self, name: &str) -> bool {
819 self.type_params.iter().any(|param| param == name)
820 }
821
822 pub fn has_nominal_type(&self, name: &str) -> bool {
823 self.nominal_type_names.iter().any(|ty| ty == name)
824 }
825
826 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
827 CachedCompiledFunction {
828 name: self.name.clone(),
829 type_params: self.type_params.clone(),
830 nominal_type_names: self.nominal_type_names.clone(),
831 params: self
832 .params
833 .iter()
834 .map(ParamSlot::freeze_for_cache)
835 .collect(),
836 default_start: self.default_start,
837 chunk: self.chunk.freeze_for_cache(),
838 is_generator: self.is_generator,
839 is_stream: self.is_stream,
840 has_rest_param: self.has_rest_param,
841 has_runtime_type_checks: self.has_runtime_type_checks,
842 }
843 }
844
845 pub(crate) fn from_cached(cached: &CachedCompiledFunction) -> Self {
846 Self {
847 name: cached.name.clone(),
848 type_params: cached.type_params.clone(),
849 nominal_type_names: cached.nominal_type_names.clone(),
850 params: cached.params.iter().map(CachedParamSlot::thaw).collect(),
851 default_start: cached.default_start,
852 chunk: Rc::new(Chunk::from_cached(&cached.chunk)),
853 is_generator: cached.is_generator,
854 is_stream: cached.is_stream,
855 has_rest_param: cached.has_rest_param,
856 has_runtime_type_checks: cached.has_runtime_type_checks,
857 }
858 }
859}
860
861impl Chunk {
862 pub fn new() -> Self {
863 Self {
864 code: Vec::new(),
865 constants: Vec::new(),
866 lines: Vec::new(),
867 columns: Vec::new(),
868 source_file: None,
869 current_col: 0,
870 functions: Vec::new(),
871 inline_cache_slots: BTreeMap::new(),
872 inline_cache_index: Vec::new(),
873 inline_caches: Rc::new(RefCell::new(Vec::new())),
874 constant_strings: Rc::new(RefCell::new(Vec::new())),
875 local_slots: Vec::new(),
876 references_outer_names: false,
877 }
878 }
879
880 #[inline]
893 pub(crate) fn op_reads_outer_name(op: Op) -> bool {
894 matches!(
895 op,
896 Op::GetVar
897 | Op::SetVar
898 | Op::CallBuiltin
899 | Op::CallBuiltinSpread
900 | Op::CallSpread
901 | Op::Call
902 | Op::TailCall
903 | Op::Pipe
904 | Op::CheckType
905 )
906 }
907
908 pub fn set_column(&mut self, col: u32) {
910 self.current_col = col;
911 }
912
913 pub fn add_constant(&mut self, constant: Constant) -> u16 {
915 for (i, c) in self.constants.iter().enumerate() {
916 if c == &constant {
917 return i as u16;
918 }
919 }
920 let idx = self.constants.len();
921 self.constants.push(constant);
922 idx as u16
923 }
924
925 pub fn emit(&mut self, op: Op, line: u32) {
927 let col = self.current_col;
928 let op_offset = self.code.len();
929 self.code.push(op as u8);
930 self.lines.push(line);
931 self.columns.push(col);
932 if is_adaptive_binary_op(op) {
933 self.register_inline_cache(op_offset);
934 }
935 if Self::op_reads_outer_name(op) {
936 self.references_outer_names = true;
937 }
938 }
939
940 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
942 let col = self.current_col;
943 let op_offset = self.code.len();
944 self.code.push(op as u8);
945 self.code.push((arg >> 8) as u8);
946 self.code.push((arg & 0xFF) as u8);
947 self.lines.push(line);
948 self.lines.push(line);
949 self.lines.push(line);
950 self.columns.push(col);
951 self.columns.push(col);
952 self.columns.push(col);
953 if matches!(
954 op,
955 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread
956 ) {
957 self.register_inline_cache(op_offset);
958 }
959 if Self::op_reads_outer_name(op) {
960 self.references_outer_names = true;
961 }
962 }
963
964 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
966 let col = self.current_col;
967 let op_offset = self.code.len();
968 self.code.push(op as u8);
969 self.code.push(arg);
970 self.lines.push(line);
971 self.lines.push(line);
972 self.columns.push(col);
973 self.columns.push(col);
974 if matches!(op, Op::Call) {
975 self.register_inline_cache(op_offset);
976 }
977 if Self::op_reads_outer_name(op) {
978 self.references_outer_names = true;
979 }
980 }
981
982 pub fn emit_call_builtin(
984 &mut self,
985 id: crate::BuiltinId,
986 name_idx: u16,
987 arg_count: u8,
988 line: u32,
989 ) {
990 let col = self.current_col;
991 let op_offset = self.code.len();
992 self.code.push(Op::CallBuiltin as u8);
993 self.code.extend_from_slice(&id.raw().to_be_bytes());
994 self.code.push((name_idx >> 8) as u8);
995 self.code.push((name_idx & 0xFF) as u8);
996 self.code.push(arg_count);
997 for _ in 0..12 {
998 self.lines.push(line);
999 self.columns.push(col);
1000 }
1001 self.register_inline_cache(op_offset);
1002 self.references_outer_names = true;
1003 }
1004
1005 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
1007 let col = self.current_col;
1008 self.code.push(Op::CallBuiltinSpread as u8);
1009 self.code.extend_from_slice(&id.raw().to_be_bytes());
1010 self.code.push((name_idx >> 8) as u8);
1011 self.code.push((name_idx & 0xFF) as u8);
1012 for _ in 0..11 {
1013 self.lines.push(line);
1014 self.columns.push(col);
1015 }
1016 self.references_outer_names = true;
1017 }
1018
1019 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
1021 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
1022 }
1023
1024 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
1026 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
1027 }
1028
1029 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
1030 let col = self.current_col;
1031 let op_offset = self.code.len();
1032 self.code.push(op as u8);
1033 self.code.push((name_idx >> 8) as u8);
1034 self.code.push((name_idx & 0xFF) as u8);
1035 self.code.push(arg_count);
1036 self.lines.push(line);
1037 self.lines.push(line);
1038 self.lines.push(line);
1039 self.lines.push(line);
1040 self.columns.push(col);
1041 self.columns.push(col);
1042 self.columns.push(col);
1043 self.columns.push(col);
1044 self.register_inline_cache(op_offset);
1045 }
1046
1047 pub fn current_offset(&self) -> usize {
1049 self.code.len()
1050 }
1051
1052 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
1054 let col = self.current_col;
1055 self.code.push(op as u8);
1056 let patch_pos = self.code.len();
1057 self.code.push(0xFF);
1058 self.code.push(0xFF);
1059 self.lines.push(line);
1060 self.lines.push(line);
1061 self.lines.push(line);
1062 self.columns.push(col);
1063 self.columns.push(col);
1064 self.columns.push(col);
1065 patch_pos
1066 }
1067
1068 pub fn patch_jump(&mut self, patch_pos: usize) {
1070 let target = self.code.len() as u16;
1071 self.code[patch_pos] = (target >> 8) as u8;
1072 self.code[patch_pos + 1] = (target & 0xFF) as u8;
1073 }
1074
1075 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
1077 let target = target as u16;
1078 self.code[patch_pos] = (target >> 8) as u8;
1079 self.code[patch_pos + 1] = (target & 0xFF) as u8;
1080 }
1081
1082 pub fn read_u16(&self, pos: usize) -> u16 {
1084 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
1085 }
1086
1087 fn register_inline_cache(&mut self, op_offset: usize) {
1088 if self.inline_cache_slots.contains_key(&op_offset) {
1089 return;
1090 }
1091 let mut entries = self.inline_caches.borrow_mut();
1092 let slot = entries.len();
1093 entries.push(InlineCacheEntry::Empty);
1094 self.inline_cache_slots.insert(op_offset, slot);
1095 Self::write_inline_cache_index(&mut self.inline_cache_index, op_offset, slot);
1096 }
1097
1098 fn write_inline_cache_index(index: &mut Vec<u32>, op_offset: usize, slot: usize) {
1103 if op_offset >= index.len() {
1104 index.resize(op_offset + 1, NO_INLINE_CACHE_SLOT);
1105 }
1106 index[op_offset] = slot as u32;
1107 }
1108
1109 #[inline]
1118 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
1119 match self.inline_cache_index.get(op_offset).copied() {
1120 None | Some(NO_INLINE_CACHE_SLOT) => None,
1121 Some(slot) => Some(slot as usize),
1122 }
1123 }
1124
1125 #[cfg(feature = "vm-bench-internals")]
1132 pub fn inline_cache_slot_via_btreemap_for_bench(&self, op_offset: usize) -> Option<usize> {
1133 self.inline_cache_slots.get(&op_offset).copied()
1134 }
1135
1136 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<Rc<str>> {
1141 let mut entries = self.constant_strings.borrow_mut();
1146 if entries.len() < self.constants.len() {
1147 entries.resize(self.constants.len(), None);
1148 }
1149 if let Some(Some(existing)) = entries.get(idx) {
1150 return Some(Rc::clone(existing));
1151 }
1152 let materialized = match self.constants.get(idx)? {
1153 Constant::String(s) => Rc::<str>::from(s.as_str()),
1154 _ => return None,
1155 };
1156 entries[idx] = Some(Rc::clone(&materialized));
1157 Some(materialized)
1158 }
1159
1160 #[cfg(feature = "vm-bench-internals")]
1161 pub(crate) fn inline_cache_entry(&self, slot: usize) -> InlineCacheEntry {
1162 self.inline_caches
1163 .borrow()
1164 .get(slot)
1165 .cloned()
1166 .unwrap_or(InlineCacheEntry::Empty)
1167 }
1168
1169 #[inline]
1181 pub(crate) fn peek_adaptive_binary_cache(
1182 &self,
1183 slot: usize,
1184 ) -> Option<(AdaptiveBinaryOp, AdaptiveBinaryState)> {
1185 match self.inline_caches.borrow().get(slot)? {
1186 &InlineCacheEntry::AdaptiveBinary { op, state } => Some((op, state)),
1187 _ => None,
1188 }
1189 }
1190
1191 #[inline]
1203 pub(crate) fn peek_method_cache(&self, slot: usize) -> Option<(u16, usize, MethodCacheTarget)> {
1204 match self.inline_caches.borrow().get(slot)? {
1205 &InlineCacheEntry::Method {
1206 name_idx,
1207 argc,
1208 target,
1209 } => Some((name_idx, argc, target)),
1210 _ => None,
1211 }
1212 }
1213
1214 #[inline]
1224 pub(crate) fn peek_property_cache(&self, slot: usize) -> Option<(u16, PropertyCacheTarget)> {
1225 match self.inline_caches.borrow().get(slot)? {
1226 InlineCacheEntry::Property { name_idx, target } => Some((*name_idx, target.clone())),
1227 _ => None,
1228 }
1229 }
1230
1231 #[inline]
1241 pub(crate) fn peek_direct_call_state(&self, slot: usize) -> Option<DirectCallState> {
1242 match self.inline_caches.borrow().get(slot)? {
1243 InlineCacheEntry::DirectCall { state } => Some(state.clone()),
1244 _ => None,
1245 }
1246 }
1247
1248 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1249 if let Some(existing) = self.inline_caches.borrow_mut().get_mut(slot) {
1250 *existing = entry;
1251 }
1252 }
1253
1254 pub fn freeze_for_cache(&self) -> CachedChunk {
1255 CachedChunk {
1256 code: self.code.clone(),
1257 constants: self.constants.clone(),
1258 lines: self.lines.clone(),
1259 columns: self.columns.clone(),
1260 source_file: self.source_file.clone(),
1261 current_col: self.current_col,
1262 functions: self
1263 .functions
1264 .iter()
1265 .map(|function| function.freeze_for_cache())
1266 .collect(),
1267 inline_cache_slots: self.inline_cache_slots.clone(),
1268 local_slots: self.local_slots.clone(),
1269 references_outer_names: self.references_outer_names,
1270 }
1271 }
1272
1273 pub fn from_cached(cached: &CachedChunk) -> Self {
1274 let inline_cache_count = cached.inline_cache_slots.len();
1275 let constants_count = cached.constants.len();
1276 let mut inline_cache_index = Vec::new();
1283 inline_cache_index.resize(cached.code.len(), NO_INLINE_CACHE_SLOT);
1284 for (&op_offset, &slot) in cached.inline_cache_slots.iter() {
1285 if op_offset < inline_cache_index.len() {
1286 inline_cache_index[op_offset] = slot as u32;
1287 }
1288 }
1289 Self {
1290 code: cached.code.clone(),
1291 constants: cached.constants.clone(),
1292 lines: cached.lines.clone(),
1293 columns: cached.columns.clone(),
1294 source_file: cached.source_file.clone(),
1295 current_col: cached.current_col,
1296 functions: cached
1297 .functions
1298 .iter()
1299 .map(|function| Rc::new(CompiledFunction::from_cached(function)))
1300 .collect(),
1301 inline_cache_slots: cached.inline_cache_slots.clone(),
1302 inline_cache_index,
1303 inline_caches: Rc::new(RefCell::new(vec![
1304 InlineCacheEntry::Empty;
1305 inline_cache_count
1306 ])),
1307 constant_strings: Rc::new(RefCell::new(vec![None; constants_count])),
1308 local_slots: cached.local_slots.clone(),
1309 references_outer_names: cached.references_outer_names,
1310 }
1311 }
1312
1313 pub(crate) fn add_local_slot(
1314 &mut self,
1315 name: String,
1316 mutable: bool,
1317 scope_depth: usize,
1318 ) -> u16 {
1319 let idx = self.local_slots.len();
1320 self.local_slots.push(LocalSlotInfo {
1321 name,
1322 mutable,
1323 scope_depth,
1324 });
1325 idx as u16
1326 }
1327
1328 #[cfg(test)]
1329 pub(crate) fn inline_cache_entries(&self) -> Vec<InlineCacheEntry> {
1330 self.inline_caches.borrow().clone()
1331 }
1332
1333 pub fn read_u64(&self, pos: usize) -> u64 {
1335 u64::from_be_bytes([
1336 self.code[pos],
1337 self.code[pos + 1],
1338 self.code[pos + 2],
1339 self.code[pos + 3],
1340 self.code[pos + 4],
1341 self.code[pos + 5],
1342 self.code[pos + 6],
1343 self.code[pos + 7],
1344 ])
1345 }
1346
1347 pub fn disassemble(&self, name: &str) -> String {
1349 let mut out = format!("== {name} ==\n");
1350 let mut ip = 0;
1351 while ip < self.code.len() {
1352 let op = self.code[ip];
1353 let line = self.lines.get(ip).copied().unwrap_or(0);
1354 out.push_str(&format!("{ip:04} [{line:>4}] "));
1355 ip += 1;
1356
1357 match op {
1358 x if x == Op::Constant as u8 => {
1359 let idx = self.read_u16(ip);
1360 ip += 2;
1361 let val = &self.constants[idx as usize];
1362 out.push_str(&format!("CONSTANT {idx:>4} ({val})\n"));
1363 }
1364 x if x == Op::Nil as u8 => out.push_str("NIL\n"),
1365 x if x == Op::True as u8 => out.push_str("TRUE\n"),
1366 x if x == Op::False as u8 => out.push_str("FALSE\n"),
1367 x if x == Op::GetVar as u8 => {
1368 let idx = self.read_u16(ip);
1369 ip += 2;
1370 out.push_str(&format!(
1371 "GET_VAR {:>4} ({})\n",
1372 idx, self.constants[idx as usize]
1373 ));
1374 }
1375 x if x == Op::DefLet as u8 => {
1376 let idx = self.read_u16(ip);
1377 ip += 2;
1378 out.push_str(&format!(
1379 "DEF_LET {:>4} ({})\n",
1380 idx, self.constants[idx as usize]
1381 ));
1382 }
1383 x if x == Op::DefVar as u8 => {
1384 let idx = self.read_u16(ip);
1385 ip += 2;
1386 out.push_str(&format!(
1387 "DEF_VAR {:>4} ({})\n",
1388 idx, self.constants[idx as usize]
1389 ));
1390 }
1391 x if x == Op::SetVar as u8 => {
1392 let idx = self.read_u16(ip);
1393 ip += 2;
1394 out.push_str(&format!(
1395 "SET_VAR {:>4} ({})\n",
1396 idx, self.constants[idx as usize]
1397 ));
1398 }
1399 x if x == Op::GetLocalSlot as u8 => {
1400 let slot = self.read_u16(ip);
1401 ip += 2;
1402 out.push_str(&format!("GET_LOCAL_SLOT {slot:>4}"));
1403 if let Some(info) = self.local_slots.get(slot as usize) {
1404 out.push_str(&format!(" ({})", info.name));
1405 }
1406 out.push('\n');
1407 }
1408 x if x == Op::DefLocalSlot as u8 => {
1409 let slot = self.read_u16(ip);
1410 ip += 2;
1411 out.push_str(&format!("DEF_LOCAL_SLOT {slot:>4}"));
1412 if let Some(info) = self.local_slots.get(slot as usize) {
1413 out.push_str(&format!(" ({})", info.name));
1414 }
1415 out.push('\n');
1416 }
1417 x if x == Op::SetLocalSlot as u8 => {
1418 let slot = self.read_u16(ip);
1419 ip += 2;
1420 out.push_str(&format!("SET_LOCAL_SLOT {slot:>4}"));
1421 if let Some(info) = self.local_slots.get(slot as usize) {
1422 out.push_str(&format!(" ({})", info.name));
1423 }
1424 out.push('\n');
1425 }
1426 x if x == Op::PushScope as u8 => out.push_str("PUSH_SCOPE\n"),
1427 x if x == Op::PopScope as u8 => out.push_str("POP_SCOPE\n"),
1428 x if x == Op::Add as u8 => out.push_str("ADD\n"),
1429 x if x == Op::Sub as u8 => out.push_str("SUB\n"),
1430 x if x == Op::Mul as u8 => out.push_str("MUL\n"),
1431 x if x == Op::Div as u8 => out.push_str("DIV\n"),
1432 x if x == Op::Mod as u8 => out.push_str("MOD\n"),
1433 x if x == Op::Pow as u8 => out.push_str("POW\n"),
1434 x if x == Op::Negate as u8 => out.push_str("NEGATE\n"),
1435 x if x == Op::Equal as u8 => out.push_str("EQUAL\n"),
1436 x if x == Op::NotEqual as u8 => out.push_str("NOT_EQUAL\n"),
1437 x if x == Op::Less as u8 => out.push_str("LESS\n"),
1438 x if x == Op::Greater as u8 => out.push_str("GREATER\n"),
1439 x if x == Op::LessEqual as u8 => out.push_str("LESS_EQUAL\n"),
1440 x if x == Op::GreaterEqual as u8 => out.push_str("GREATER_EQUAL\n"),
1441 x if x == Op::Contains as u8 => out.push_str("CONTAINS\n"),
1442 x if x == Op::Not as u8 => out.push_str("NOT\n"),
1443 x if x == Op::Jump as u8 => {
1444 let target = self.read_u16(ip);
1445 ip += 2;
1446 out.push_str(&format!("JUMP {target:>4}\n"));
1447 }
1448 x if x == Op::JumpIfFalse as u8 => {
1449 let target = self.read_u16(ip);
1450 ip += 2;
1451 out.push_str(&format!("JUMP_IF_FALSE {target:>4}\n"));
1452 }
1453 x if x == Op::JumpIfTrue as u8 => {
1454 let target = self.read_u16(ip);
1455 ip += 2;
1456 out.push_str(&format!("JUMP_IF_TRUE {target:>4}\n"));
1457 }
1458 x if x == Op::Pop as u8 => out.push_str("POP\n"),
1459 x if x == Op::Call as u8 => {
1460 let argc = self.code[ip];
1461 ip += 1;
1462 out.push_str(&format!("CALL {argc:>4}\n"));
1463 }
1464 x if x == Op::TailCall as u8 => {
1465 let argc = self.code[ip];
1466 ip += 1;
1467 out.push_str(&format!("TAIL_CALL {argc:>4}\n"));
1468 }
1469 x if x == Op::Return as u8 => out.push_str("RETURN\n"),
1470 x if x == Op::Closure as u8 => {
1471 let idx = self.read_u16(ip);
1472 ip += 2;
1473 out.push_str(&format!("CLOSURE {idx:>4}\n"));
1474 }
1475 x if x == Op::BuildList as u8 => {
1476 let count = self.read_u16(ip);
1477 ip += 2;
1478 out.push_str(&format!("BUILD_LIST {count:>4}\n"));
1479 }
1480 x if x == Op::BuildDict as u8 => {
1481 let count = self.read_u16(ip);
1482 ip += 2;
1483 out.push_str(&format!("BUILD_DICT {count:>4}\n"));
1484 }
1485 x if x == Op::Subscript as u8 => out.push_str("SUBSCRIPT\n"),
1486 x if x == Op::SubscriptOpt as u8 => out.push_str("SUBSCRIPT_OPT\n"),
1487 x if x == Op::Slice as u8 => out.push_str("SLICE\n"),
1488 x if x == Op::GetProperty as u8 => {
1489 let idx = self.read_u16(ip);
1490 ip += 2;
1491 out.push_str(&format!(
1492 "GET_PROPERTY {:>4} ({})\n",
1493 idx, self.constants[idx as usize]
1494 ));
1495 }
1496 x if x == Op::GetPropertyOpt as u8 => {
1497 let idx = self.read_u16(ip);
1498 ip += 2;
1499 out.push_str(&format!(
1500 "GET_PROPERTY_OPT {:>4} ({})\n",
1501 idx, self.constants[idx as usize]
1502 ));
1503 }
1504 x if x == Op::SetProperty as u8 => {
1505 let idx = self.read_u16(ip);
1506 ip += 2;
1507 out.push_str(&format!(
1508 "SET_PROPERTY {:>4} ({})\n",
1509 idx, self.constants[idx as usize]
1510 ));
1511 }
1512 x if x == Op::SetSubscript as u8 => {
1513 let idx = self.read_u16(ip);
1514 ip += 2;
1515 out.push_str(&format!(
1516 "SET_SUBSCRIPT {:>4} ({})\n",
1517 idx, self.constants[idx as usize]
1518 ));
1519 }
1520 x if x == Op::MethodCall as u8 => {
1521 let idx = self.read_u16(ip);
1522 ip += 2;
1523 let argc = self.code[ip];
1524 ip += 1;
1525 out.push_str(&format!(
1526 "METHOD_CALL {:>4} ({}) argc={}\n",
1527 idx, self.constants[idx as usize], argc
1528 ));
1529 }
1530 x if x == Op::MethodCallOpt as u8 => {
1531 let idx = self.read_u16(ip);
1532 ip += 2;
1533 let argc = self.code[ip];
1534 ip += 1;
1535 out.push_str(&format!(
1536 "METHOD_CALL_OPT {:>4} ({}) argc={}\n",
1537 idx, self.constants[idx as usize], argc
1538 ));
1539 }
1540 x if x == Op::Concat as u8 => {
1541 let count = self.read_u16(ip);
1542 ip += 2;
1543 out.push_str(&format!("CONCAT {count:>4}\n"));
1544 }
1545 x if x == Op::IterInit as u8 => out.push_str("ITER_INIT\n"),
1546 x if x == Op::IterNext as u8 => {
1547 let target = self.read_u16(ip);
1548 ip += 2;
1549 out.push_str(&format!("ITER_NEXT {target:>4}\n"));
1550 }
1551 x if x == Op::Throw as u8 => out.push_str("THROW\n"),
1552 x if x == Op::TryCatchSetup as u8 => {
1553 let target = self.read_u16(ip);
1554 ip += 2;
1555 out.push_str(&format!("TRY_CATCH_SETUP {target:>4}\n"));
1556 }
1557 x if x == Op::PopHandler as u8 => out.push_str("POP_HANDLER\n"),
1558 x if x == Op::Pipe as u8 => out.push_str("PIPE\n"),
1559 x if x == Op::Parallel as u8 => out.push_str("PARALLEL\n"),
1560 x if x == Op::ParallelMap as u8 => out.push_str("PARALLEL_MAP\n"),
1561 x if x == Op::ParallelMapStream as u8 => out.push_str("PARALLEL_MAP_STREAM\n"),
1562 x if x == Op::ParallelSettle as u8 => out.push_str("PARALLEL_SETTLE\n"),
1563 x if x == Op::Spawn as u8 => out.push_str("SPAWN\n"),
1564 x if x == Op::Import as u8 => {
1565 let idx = self.read_u16(ip);
1566 ip += 2;
1567 out.push_str(&format!(
1568 "IMPORT {:>4} ({})\n",
1569 idx, self.constants[idx as usize]
1570 ));
1571 }
1572 x if x == Op::SelectiveImport as u8 => {
1573 let path_idx = self.read_u16(ip);
1574 ip += 2;
1575 let names_idx = self.read_u16(ip);
1576 ip += 2;
1577 out.push_str(&format!(
1578 "SELECTIVE_IMPORT {:>4} ({}) names: {:>4} ({})\n",
1579 path_idx,
1580 self.constants[path_idx as usize],
1581 names_idx,
1582 self.constants[names_idx as usize]
1583 ));
1584 }
1585 x if x == Op::SyncMutexEnter as u8 => {
1586 let idx = self.read_u16(ip);
1587 ip += 2;
1588 out.push_str(&format!(
1589 "SYNC_MUTEX_ENTER {:>4} ({})\n",
1590 idx, self.constants[idx as usize]
1591 ));
1592 }
1593 x if x == Op::DeadlineSetup as u8 => out.push_str("DEADLINE_SETUP\n"),
1594 x if x == Op::DeadlineEnd as u8 => out.push_str("DEADLINE_END\n"),
1595 x if x == Op::BuildEnum as u8 => {
1596 let enum_idx = self.read_u16(ip);
1597 ip += 2;
1598 let variant_idx = self.read_u16(ip);
1599 ip += 2;
1600 let field_count = self.read_u16(ip);
1601 ip += 2;
1602 out.push_str(&format!(
1603 "BUILD_ENUM {:>4} ({}) {:>4} ({}) fields={}\n",
1604 enum_idx,
1605 self.constants[enum_idx as usize],
1606 variant_idx,
1607 self.constants[variant_idx as usize],
1608 field_count
1609 ));
1610 }
1611 x if x == Op::MatchEnum as u8 => {
1612 let enum_idx = self.read_u16(ip);
1613 ip += 2;
1614 let variant_idx = self.read_u16(ip);
1615 ip += 2;
1616 out.push_str(&format!(
1617 "MATCH_ENUM {:>4} ({}) {:>4} ({})\n",
1618 enum_idx,
1619 self.constants[enum_idx as usize],
1620 variant_idx,
1621 self.constants[variant_idx as usize]
1622 ));
1623 }
1624 x if x == Op::PopIterator as u8 => out.push_str("POP_ITERATOR\n"),
1625 x if x == Op::TryUnwrap as u8 => out.push_str("TRY_UNWRAP\n"),
1626 x if x == Op::TryWrapOk as u8 => out.push_str("TRY_WRAP_OK\n"),
1627 x if x == Op::CallSpread as u8 => out.push_str("CALL_SPREAD\n"),
1628 x if x == Op::CallBuiltin as u8 => {
1629 let id = self.read_u64(ip);
1630 ip += 8;
1631 let idx = self.read_u16(ip);
1632 ip += 2;
1633 let argc = self.code[ip];
1634 ip += 1;
1635 out.push_str(&format!(
1636 "CALL_BUILTIN {id:#018x} {:>4} ({}) argc={}\n",
1637 idx, self.constants[idx as usize], argc
1638 ));
1639 }
1640 x if x == Op::CallBuiltinSpread as u8 => {
1641 let id = self.read_u64(ip);
1642 ip += 8;
1643 let idx = self.read_u16(ip);
1644 ip += 2;
1645 out.push_str(&format!(
1646 "CALL_BUILTIN_SPREAD {id:#018x} {:>4} ({})\n",
1647 idx, self.constants[idx as usize]
1648 ));
1649 }
1650 x if x == Op::MethodCallSpread as u8 => {
1651 let idx = self.read_u16(ip + 1);
1652 ip += 2;
1653 out.push_str(&format!("METHOD_CALL_SPREAD {idx}\n"));
1654 }
1655 x if x == Op::Dup as u8 => out.push_str("DUP\n"),
1656 x if x == Op::Swap as u8 => out.push_str("SWAP\n"),
1657 x if x == Op::AddInt as u8 => out.push_str("ADD_INT\n"),
1658 x if x == Op::SubInt as u8 => out.push_str("SUB_INT\n"),
1659 x if x == Op::MulInt as u8 => out.push_str("MUL_INT\n"),
1660 x if x == Op::DivInt as u8 => out.push_str("DIV_INT\n"),
1661 x if x == Op::ModInt as u8 => out.push_str("MOD_INT\n"),
1662 x if x == Op::AddFloat as u8 => out.push_str("ADD_FLOAT\n"),
1663 x if x == Op::SubFloat as u8 => out.push_str("SUB_FLOAT\n"),
1664 x if x == Op::MulFloat as u8 => out.push_str("MUL_FLOAT\n"),
1665 x if x == Op::DivFloat as u8 => out.push_str("DIV_FLOAT\n"),
1666 x if x == Op::ModFloat as u8 => out.push_str("MOD_FLOAT\n"),
1667 x if x == Op::EqualInt as u8 => out.push_str("EQUAL_INT\n"),
1668 x if x == Op::NotEqualInt as u8 => out.push_str("NOT_EQUAL_INT\n"),
1669 x if x == Op::LessInt as u8 => out.push_str("LESS_INT\n"),
1670 x if x == Op::GreaterInt as u8 => out.push_str("GREATER_INT\n"),
1671 x if x == Op::LessEqualInt as u8 => out.push_str("LESS_EQUAL_INT\n"),
1672 x if x == Op::GreaterEqualInt as u8 => out.push_str("GREATER_EQUAL_INT\n"),
1673 x if x == Op::EqualFloat as u8 => out.push_str("EQUAL_FLOAT\n"),
1674 x if x == Op::NotEqualFloat as u8 => out.push_str("NOT_EQUAL_FLOAT\n"),
1675 x if x == Op::LessFloat as u8 => out.push_str("LESS_FLOAT\n"),
1676 x if x == Op::GreaterFloat as u8 => out.push_str("GREATER_FLOAT\n"),
1677 x if x == Op::LessEqualFloat as u8 => out.push_str("LESS_EQUAL_FLOAT\n"),
1678 x if x == Op::GreaterEqualFloat as u8 => out.push_str("GREATER_EQUAL_FLOAT\n"),
1679 x if x == Op::EqualBool as u8 => out.push_str("EQUAL_BOOL\n"),
1680 x if x == Op::NotEqualBool as u8 => out.push_str("NOT_EQUAL_BOOL\n"),
1681 x if x == Op::EqualString as u8 => out.push_str("EQUAL_STRING\n"),
1682 x if x == Op::NotEqualString as u8 => out.push_str("NOT_EQUAL_STRING\n"),
1683 x if x == Op::Yield as u8 => out.push_str("YIELD\n"),
1684 _ => {
1685 out.push_str(&format!("UNKNOWN(0x{op:02x})\n"));
1686 }
1687 }
1688 }
1689 out
1690 }
1691}
1692
1693fn is_adaptive_binary_op(op: Op) -> bool {
1694 matches!(
1695 op,
1696 Op::Add
1697 | Op::Sub
1698 | Op::Mul
1699 | Op::Div
1700 | Op::Mod
1701 | Op::Equal
1702 | Op::NotEqual
1703 | Op::Less
1704 | Op::Greater
1705 | Op::LessEqual
1706 | Op::GreaterEqual
1707 )
1708}
1709
1710impl Default for Chunk {
1711 fn default() -> Self {
1712 Self::new()
1713 }
1714}
1715
1716#[cfg(test)]
1717mod tests {
1718 use std::rc::Rc;
1719
1720 use super::{
1721 Chunk, DirectCallState, DirectCallTarget, InlineCacheEntry, MethodCacheTarget, Op,
1722 PropertyCacheTarget,
1723 };
1724 use crate::BuiltinId;
1725
1726 #[test]
1727 fn op_from_byte_matches_repr_order() {
1728 for (byte, op) in Op::ALL.iter().copied().enumerate() {
1729 assert_eq!(byte as u8, op as u8);
1730 assert_eq!(Op::from_byte(byte as u8), Some(op));
1731 }
1732 assert_eq!(Op::from_byte(Op::ALL.len() as u8), None);
1733 }
1734
1735 #[test]
1744 fn empty_chunk_does_not_reference_outer_names() {
1745 let chunk = Chunk::new();
1746 assert!(!chunk.references_outer_names);
1747 }
1748
1749 #[test]
1750 fn arithmetic_only_chunk_does_not_reference_outer_names() {
1751 let mut chunk = Chunk::new();
1756 chunk.emit_u16(Op::GetLocalSlot, 0, 1);
1757 chunk.emit_u16(Op::Constant, 0, 1);
1758 chunk.emit(Op::MulInt, 1);
1759 chunk.emit(Op::Pop, 1);
1760 chunk.emit(Op::Return, 1);
1761 assert!(!chunk.references_outer_names);
1762 }
1763
1764 #[test]
1765 fn slot_only_chunk_does_not_reference_outer_names() {
1766 let mut chunk = Chunk::new();
1768 chunk.emit_u16(Op::DefLocalSlot, 0, 1);
1769 chunk.emit_u16(Op::GetLocalSlot, 0, 1);
1770 chunk.emit_u16(Op::SetLocalSlot, 0, 1);
1771 assert!(!chunk.references_outer_names);
1772 }
1773
1774 #[test]
1775 fn get_var_flags_outer_name_reference() {
1776 let mut chunk = Chunk::new();
1777 chunk.emit_u16(Op::GetVar, 0, 1);
1778 assert!(chunk.references_outer_names);
1779 }
1780
1781 #[test]
1782 fn set_var_flags_outer_name_reference() {
1783 let mut chunk = Chunk::new();
1784 chunk.emit_u16(Op::SetVar, 0, 1);
1785 assert!(chunk.references_outer_names);
1786 }
1787
1788 #[test]
1789 fn check_type_flags_outer_name_reference() {
1790 let mut chunk = Chunk::new();
1791 chunk.emit_u16(Op::CheckType, 0, 1);
1792 assert!(chunk.references_outer_names);
1793 }
1794
1795 #[test]
1796 fn call_builtin_flags_outer_name_reference() {
1797 let mut chunk = Chunk::new();
1798 chunk.emit_call_builtin(BuiltinId::from_name("any_name"), 0, 1, 1);
1799 assert!(chunk.references_outer_names);
1800 }
1801
1802 #[test]
1803 fn call_builtin_spread_flags_outer_name_reference() {
1804 let mut chunk = Chunk::new();
1805 chunk.emit_call_builtin_spread(BuiltinId::from_name("any_name"), 0, 1);
1806 assert!(chunk.references_outer_names);
1807 }
1808
1809 #[test]
1810 fn tail_call_flags_outer_name_reference() {
1811 let mut chunk = Chunk::new();
1814 chunk.emit_u8(Op::TailCall, 1, 1);
1815 assert!(chunk.references_outer_names);
1816 }
1817
1818 #[test]
1819 fn call_flags_outer_name_reference() {
1820 let mut chunk = Chunk::new();
1823 chunk.emit_u8(Op::Call, 1, 1);
1824 assert!(chunk.references_outer_names);
1825 }
1826
1827 #[test]
1828 fn pipe_flags_outer_name_reference() {
1829 let mut chunk = Chunk::new();
1832 chunk.emit(Op::Pipe, 1);
1833 assert!(chunk.references_outer_names);
1834 }
1835
1836 #[test]
1837 fn method_call_does_not_flag_outer_name_reference() {
1838 let mut chunk = Chunk::new();
1841 chunk.emit_method_call(0, 1, 1);
1842 chunk.emit_method_call_opt(0, 1, 1);
1843 assert!(!chunk.references_outer_names);
1844 }
1845
1846 #[test]
1847 fn jump_and_control_flow_do_not_flag_outer_name_reference() {
1848 let mut chunk = Chunk::new();
1851 chunk.emit_u16(Op::Constant, 0, 1);
1852 chunk.emit(Op::JumpIfFalse, 1);
1853 chunk.emit(Op::Jump, 1);
1854 chunk.emit(Op::Return, 1);
1855 chunk.emit(Op::Pop, 1);
1856 assert!(!chunk.references_outer_names);
1857 }
1858
1859 #[test]
1860 fn references_outer_names_is_monotonic() {
1861 let mut chunk = Chunk::new();
1864 chunk.emit_u16(Op::GetVar, 0, 1);
1865 assert!(chunk.references_outer_names);
1866 chunk.emit_u16(Op::GetLocalSlot, 0, 1);
1867 chunk.emit(Op::MulInt, 1);
1868 assert!(chunk.references_outer_names);
1869 }
1870
1871 #[test]
1872 fn freeze_thaw_round_trips_references_outer_names() {
1873 let mut chunk = Chunk::new();
1877 chunk.emit_u16(Op::GetVar, 0, 1);
1878 assert!(chunk.references_outer_names);
1879 let frozen = chunk.freeze_for_cache();
1880 let thawed = Chunk::from_cached(&frozen);
1881 assert!(thawed.references_outer_names);
1882
1883 let plain = Chunk::new();
1884 assert!(!plain.references_outer_names);
1885 let frozen_plain = plain.freeze_for_cache();
1886 let thawed_plain = Chunk::from_cached(&frozen_plain);
1887 assert!(!thawed_plain.references_outer_names);
1888 }
1889
1890 #[test]
1902 fn inline_cache_slot_returns_none_for_non_cacheable_offsets() {
1903 let mut chunk = Chunk::new();
1906 chunk.emit_u16(Op::GetLocalSlot, 0, 1);
1907 chunk.emit(Op::Pop, 1);
1908 chunk.emit(Op::Return, 1);
1909 assert!(chunk.inline_cache_slot(0).is_none());
1910 assert!(chunk.inline_cache_slot(3).is_none());
1911 assert!(chunk.inline_cache_slot(4).is_none());
1912 }
1913
1914 #[test]
1915 fn inline_cache_slot_registered_for_adaptive_binary_op() {
1916 let mut chunk = Chunk::new();
1920 chunk.emit(Op::Add, 1);
1921 assert_eq!(chunk.inline_cache_slot(0), Some(0));
1922 }
1923
1924 #[test]
1925 fn inline_cache_slot_distinct_for_sequential_adaptive_binary_ops() {
1926 let mut chunk = Chunk::new();
1931 chunk.emit(Op::Add, 1);
1932 chunk.emit(Op::Sub, 1);
1933 chunk.emit(Op::Mul, 1);
1934 let s0 = chunk.inline_cache_slot(0).expect("Add slot");
1935 let s1 = chunk.inline_cache_slot(1).expect("Sub slot");
1936 let s2 = chunk.inline_cache_slot(2).expect("Mul slot");
1937 assert_ne!(s0, s1);
1938 assert_ne!(s1, s2);
1939 assert_ne!(s0, s2);
1940 }
1941
1942 #[test]
1943 fn inline_cache_slot_returns_none_for_out_of_bounds_offset() {
1944 let mut chunk = Chunk::new();
1947 chunk.emit(Op::Add, 1);
1948 assert!(chunk.inline_cache_slot(usize::MAX).is_none());
1949 assert!(chunk.inline_cache_slot(chunk.code.len()).is_none());
1950 assert!(chunk.inline_cache_slot(chunk.code.len() + 16).is_none());
1951 }
1952
1953 #[test]
1954 fn inline_cache_slot_for_get_property_and_method_call() {
1955 let mut chunk = Chunk::new();
1959 chunk.emit_u16(Op::GetProperty, 0, 1); chunk.emit_method_call(0, 1, 1); chunk.emit_method_call_opt(0, 1, 1); chunk.emit_u16(Op::GetPropertyOpt, 0, 1); assert!(chunk.inline_cache_slot(0).is_some(), "GetProperty");
1964 assert!(chunk.inline_cache_slot(3).is_some(), "MethodCall");
1965 assert!(chunk.inline_cache_slot(7).is_some(), "MethodCallOpt");
1966 assert!(chunk.inline_cache_slot(11).is_some(), "GetPropertyOpt");
1967 }
1968
1969 #[test]
1970 fn inline_cache_slot_for_call_and_call_builtin() {
1971 let mut chunk = Chunk::new();
1976 chunk.emit_u8(Op::Call, 1, 1); let call_builtin_offset = chunk.code.len();
1978 chunk.emit_call_builtin(BuiltinId::from_name("any"), 0, 1, 1);
1979 assert!(chunk.inline_cache_slot(0).is_some(), "Op::Call IC slot");
1980 assert!(
1981 chunk.inline_cache_slot(call_builtin_offset).is_some(),
1982 "Op::CallBuiltin IC slot"
1983 );
1984 }
1985
1986 #[test]
1987 fn inline_cache_slot_register_is_idempotent_for_same_offset() {
1988 let mut chunk = Chunk::new();
1994 chunk.emit(Op::Add, 1);
1995 let slot_before = chunk.inline_cache_slot(0).expect("first registration");
1996 chunk.register_inline_cache(0);
1998 let slot_after = chunk.inline_cache_slot(0).expect("re-registration");
1999 assert_eq!(slot_before, slot_after);
2000 }
2001
2002 #[test]
2003 fn inline_cache_index_round_trips_through_cached_chunk() {
2004 let mut chunk = Chunk::new();
2011 chunk.emit_u16(Op::GetLocalSlot, 0, 1);
2012 chunk.emit_u16(Op::Constant, 0, 1);
2013 chunk.emit(Op::Add, 1);
2014 chunk.emit(Op::Sub, 1);
2015 chunk.emit_method_call(0, 1, 1);
2016 chunk.emit_u8(Op::Call, 1, 1);
2017 let live_slots: Vec<(usize, Option<usize>)> = (0..chunk.code.len())
2018 .map(|o| (o, chunk.inline_cache_slot(o)))
2019 .collect();
2020 let frozen = chunk.freeze_for_cache();
2021 let thawed = Chunk::from_cached(&frozen);
2022 let thawed_slots: Vec<(usize, Option<usize>)> = (0..thawed.code.len())
2023 .map(|o| (o, thawed.inline_cache_slot(o)))
2024 .collect();
2025 assert_eq!(live_slots, thawed_slots);
2026 }
2027
2028 #[test]
2029 fn inline_cache_index_agrees_with_btreemap_view() {
2030 let mut chunk = Chunk::new();
2036 chunk.emit(Op::Add, 1);
2037 chunk.emit_u16(Op::GetVar, 0, 1);
2038 chunk.emit(Op::LessInt, 1);
2039 chunk.emit_u8(Op::Call, 2, 1);
2040 chunk.emit(Op::Equal, 1);
2041 chunk.emit_u16(Op::GetProperty, 0, 1);
2042 chunk.emit_method_call_opt(0, 0, 1);
2043 for offset in 0..chunk.code.len() {
2044 let from_map = chunk.inline_cache_slots.get(&offset).copied();
2045 let from_index = chunk.inline_cache_slot(offset);
2046 assert_eq!(from_index, from_map, "parity broken at offset {offset}");
2047 }
2048 }
2049
2050 #[test]
2061 fn peek_adaptive_binary_returns_none_for_empty_slot() {
2062 let mut chunk = Chunk::new();
2063 chunk.emit(Op::Add, 1);
2064 let slot = chunk.inline_cache_slot(0).expect("Add registers a slot");
2065 assert!(chunk.peek_adaptive_binary_cache(slot).is_none());
2067 }
2068
2069 #[test]
2070 fn peek_adaptive_binary_returns_op_and_state_after_warmup() {
2071 use super::{AdaptiveBinaryOp, AdaptiveBinaryState, BinaryShape, InlineCacheEntry};
2072 let mut chunk = Chunk::new();
2073 chunk.emit(Op::Add, 1);
2074 let slot = chunk.inline_cache_slot(0).expect("Add registers a slot");
2075 chunk.set_inline_cache_entry(
2076 slot,
2077 InlineCacheEntry::AdaptiveBinary {
2078 op: AdaptiveBinaryOp::Add,
2079 state: AdaptiveBinaryState::Warmup {
2080 shape: BinaryShape::Int,
2081 hits: 2,
2082 },
2083 },
2084 );
2085 let (op, state) = chunk
2086 .peek_adaptive_binary_cache(slot)
2087 .expect("warmed slot peek");
2088 assert_eq!(op, AdaptiveBinaryOp::Add);
2089 assert!(matches!(
2090 state,
2091 AdaptiveBinaryState::Warmup {
2092 shape: BinaryShape::Int,
2093 hits: 2
2094 }
2095 ));
2096 }
2097
2098 #[test]
2099 fn peek_adaptive_binary_returns_none_for_non_binary_variants() {
2100 use super::{InlineCacheEntry, PropertyCacheTarget};
2106 let mut chunk = Chunk::new();
2107 chunk.emit(Op::Add, 1);
2108 let slot = chunk.inline_cache_slot(0).expect("Add registers a slot");
2109 chunk.set_inline_cache_entry(
2110 slot,
2111 InlineCacheEntry::Property {
2112 name_idx: 0,
2113 target: PropertyCacheTarget::ListCount,
2114 },
2115 );
2116 assert!(chunk.peek_adaptive_binary_cache(slot).is_none());
2117 }
2118
2119 #[test]
2120 fn peek_adaptive_binary_returns_none_for_out_of_bounds_slot() {
2121 let chunk = Chunk::new();
2126 assert!(chunk.peek_adaptive_binary_cache(0).is_none());
2127 assert!(chunk.peek_adaptive_binary_cache(usize::MAX).is_none());
2128 }
2129
2130 #[test]
2131 fn peek_adaptive_binary_state_is_copy() {
2132 fn assert_copy<T: Copy>() {}
2138 assert_copy::<super::AdaptiveBinaryState>();
2139 assert_copy::<super::AdaptiveBinaryOp>();
2140 assert_copy::<super::BinaryShape>();
2141 }
2142
2143 #[test]
2154 fn peek_method_cache_returns_none_for_empty_slot() {
2155 let mut chunk = Chunk::new();
2156 chunk.emit_method_call(0, 0, 1);
2157 let slot = chunk
2158 .inline_cache_slot(0)
2159 .expect("MethodCall registers a slot");
2160 assert!(chunk.peek_method_cache(slot).is_none());
2161 }
2162
2163 #[test]
2164 fn peek_method_cache_returns_triple_after_warmup() {
2165 let mut chunk = Chunk::new();
2166 chunk.emit_method_call(7, 2, 1);
2167 let slot = chunk
2168 .inline_cache_slot(0)
2169 .expect("MethodCall registers a slot");
2170 chunk.set_inline_cache_entry(
2171 slot,
2172 InlineCacheEntry::Method {
2173 name_idx: 7,
2174 argc: 2,
2175 target: MethodCacheTarget::ListContains,
2176 },
2177 );
2178 let (name_idx, argc, target) = chunk.peek_method_cache(slot).expect("warmed slot peek");
2179 assert_eq!(name_idx, 7);
2180 assert_eq!(argc, 2);
2181 assert_eq!(target, MethodCacheTarget::ListContains);
2182 }
2183
2184 #[test]
2185 fn peek_method_cache_returns_none_for_non_method_variants() {
2186 let mut chunk = Chunk::new();
2190 chunk.emit_method_call(0, 0, 1);
2191 let slot = chunk
2192 .inline_cache_slot(0)
2193 .expect("MethodCall registers a slot");
2194
2195 chunk.set_inline_cache_entry(
2196 slot,
2197 InlineCacheEntry::Property {
2198 name_idx: 0,
2199 target: PropertyCacheTarget::ListCount,
2200 },
2201 );
2202 assert!(chunk.peek_method_cache(slot).is_none());
2203 }
2204
2205 #[test]
2206 fn peek_method_cache_returns_none_for_out_of_bounds_slot() {
2207 let chunk = Chunk::new();
2208 assert!(chunk.peek_method_cache(0).is_none());
2209 assert!(chunk.peek_method_cache(usize::MAX).is_none());
2210 }
2211
2212 #[test]
2213 fn peek_method_cache_target_is_copy() {
2214 fn assert_copy<T: Copy>() {}
2220 assert_copy::<super::MethodCacheTarget>();
2221 }
2222
2223 #[test]
2233 fn peek_property_cache_returns_none_for_empty_slot() {
2234 let mut chunk = Chunk::new();
2235 chunk.emit_u16(Op::GetProperty, 0, 1);
2236 let slot = chunk
2237 .inline_cache_slot(0)
2238 .expect("GetProperty registers a slot");
2239 assert!(chunk.peek_property_cache(slot).is_none());
2240 }
2241
2242 #[test]
2243 fn peek_property_cache_returns_pair_after_warmup_for_dict_field() {
2244 let mut chunk = Chunk::new();
2245 chunk.emit_u16(Op::GetProperty, 0, 1);
2246 let slot = chunk
2247 .inline_cache_slot(0)
2248 .expect("GetProperty registers a slot");
2249 chunk.set_inline_cache_entry(
2250 slot,
2251 InlineCacheEntry::Property {
2252 name_idx: 11,
2253 target: PropertyCacheTarget::DictField(Rc::from("count")),
2254 },
2255 );
2256 let (name_idx, target) = chunk
2257 .peek_property_cache(slot)
2258 .expect("warmed property slot peek");
2259 assert_eq!(name_idx, 11);
2260 match target {
2261 PropertyCacheTarget::DictField(field) => assert_eq!(field.as_ref(), "count"),
2262 other => panic!("expected DictField, got {other:?}"),
2263 }
2264 }
2265
2266 #[test]
2267 fn peek_property_cache_returns_pair_for_unit_target() {
2268 let mut chunk = Chunk::new();
2272 chunk.emit_u16(Op::GetProperty, 0, 1);
2273 let slot = chunk
2274 .inline_cache_slot(0)
2275 .expect("GetProperty registers a slot");
2276 chunk.set_inline_cache_entry(
2277 slot,
2278 InlineCacheEntry::Property {
2279 name_idx: 3,
2280 target: PropertyCacheTarget::ListCount,
2281 },
2282 );
2283 let (name_idx, target) = chunk
2284 .peek_property_cache(slot)
2285 .expect("warmed property slot peek");
2286 assert_eq!(name_idx, 3);
2287 assert_eq!(target, PropertyCacheTarget::ListCount);
2288 }
2289
2290 #[test]
2291 fn peek_property_cache_returns_none_for_non_property_variants() {
2292 let mut chunk = Chunk::new();
2293 chunk.emit_u16(Op::GetProperty, 0, 1);
2294 let slot = chunk
2295 .inline_cache_slot(0)
2296 .expect("GetProperty registers a slot");
2297 chunk.set_inline_cache_entry(
2298 slot,
2299 InlineCacheEntry::Method {
2300 name_idx: 0,
2301 argc: 0,
2302 target: MethodCacheTarget::ListCount,
2303 },
2304 );
2305 assert!(chunk.peek_property_cache(slot).is_none());
2306 }
2307
2308 #[test]
2309 fn peek_property_cache_returns_none_for_out_of_bounds_slot() {
2310 let chunk = Chunk::new();
2311 assert!(chunk.peek_property_cache(0).is_none());
2312 assert!(chunk.peek_property_cache(usize::MAX).is_none());
2313 }
2314
2315 #[test]
2325 fn peek_direct_call_state_returns_none_for_empty_slot() {
2326 let mut chunk = Chunk::new();
2327 chunk.emit_u8(Op::Call, 0, 1);
2328 let slot = chunk
2329 .inline_cache_slot(0)
2330 .expect("Op::Call registers a slot");
2331 assert!(chunk.peek_direct_call_state(slot).is_none());
2332 }
2333
2334 #[test]
2335 fn peek_direct_call_state_returns_warmup_state() {
2336 let mut chunk = Chunk::new();
2337 chunk.emit_u8(Op::Call, 0, 1);
2338 let slot = chunk
2339 .inline_cache_slot(0)
2340 .expect("Op::Call registers a slot");
2341 let target = synthetic_direct_call_target();
2342 chunk.set_inline_cache_entry(
2343 slot,
2344 InlineCacheEntry::DirectCall {
2345 state: DirectCallState::Warmup {
2346 argc: 2,
2347 target: target.clone(),
2348 hits: 1,
2349 },
2350 },
2351 );
2352 let state = chunk
2353 .peek_direct_call_state(slot)
2354 .expect("warmed direct-call slot peek");
2355 match state {
2356 DirectCallState::Warmup {
2357 argc,
2358 target: peeked_target,
2359 hits,
2360 } => {
2361 assert_eq!(argc, 2);
2362 assert_eq!(hits, 1);
2363 assert_eq!(peeked_target, target);
2364 }
2365 other => panic!("expected Warmup, got {other:?}"),
2366 }
2367 }
2368
2369 #[test]
2370 fn peek_direct_call_state_returns_specialized_state() {
2371 let mut chunk = Chunk::new();
2372 chunk.emit_u8(Op::Call, 0, 1);
2373 let slot = chunk
2374 .inline_cache_slot(0)
2375 .expect("Op::Call registers a slot");
2376 let target = synthetic_direct_call_target();
2377 chunk.set_inline_cache_entry(
2378 slot,
2379 InlineCacheEntry::DirectCall {
2380 state: DirectCallState::Specialized {
2381 argc: 3,
2382 target: target.clone(),
2383 hits: 100,
2384 misses: 0,
2385 },
2386 },
2387 );
2388 let state = chunk
2389 .peek_direct_call_state(slot)
2390 .expect("warmed direct-call slot peek");
2391 match state {
2392 DirectCallState::Specialized {
2393 argc,
2394 target: peeked_target,
2395 hits,
2396 misses,
2397 } => {
2398 assert_eq!(argc, 3);
2399 assert_eq!(hits, 100);
2400 assert_eq!(misses, 0);
2401 assert_eq!(peeked_target, target);
2402 }
2403 other => panic!("expected Specialized, got {other:?}"),
2404 }
2405 }
2406
2407 #[test]
2408 fn peek_direct_call_state_returns_none_for_non_direct_call_variants() {
2409 let mut chunk = Chunk::new();
2410 chunk.emit_u8(Op::Call, 0, 1);
2411 let slot = chunk
2412 .inline_cache_slot(0)
2413 .expect("Op::Call registers a slot");
2414
2415 chunk.set_inline_cache_entry(
2416 slot,
2417 InlineCacheEntry::Property {
2418 name_idx: 0,
2419 target: PropertyCacheTarget::ListCount,
2420 },
2421 );
2422 assert!(chunk.peek_direct_call_state(slot).is_none());
2423 }
2424
2425 #[test]
2426 fn peek_direct_call_state_returns_none_for_out_of_bounds_slot() {
2427 let chunk = Chunk::new();
2428 assert!(chunk.peek_direct_call_state(0).is_none());
2429 assert!(chunk.peek_direct_call_state(usize::MAX).is_none());
2430 }
2431
2432 fn synthetic_direct_call_target() -> DirectCallTarget {
2436 use crate::value::VmClosure;
2437 use crate::{CompiledFunction, VmEnv};
2438 let func = CompiledFunction {
2439 name: "synthetic".to_string(),
2440 type_params: Vec::new(),
2441 nominal_type_names: Vec::new(),
2442 params: Vec::new(),
2443 default_start: None,
2444 chunk: Rc::new(Chunk::new()),
2445 is_generator: false,
2446 is_stream: false,
2447 has_rest_param: false,
2448 has_runtime_type_checks: false,
2449 };
2450 DirectCallTarget::Closure(Rc::new(VmClosure {
2451 func: Rc::new(func),
2452 env: VmEnv::new(),
2453 source_dir: None,
2454 module_functions: None,
2455 module_state: None,
2456 }))
2457 }
2458}