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
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[repr(u8)]
14pub enum Op {
15 Constant, Nil,
19 True,
21 False,
23
24 GetVar, DefLet, DefVar, SetVar, PushScope,
35 PopScope,
37
38 Add,
40 Sub,
41 Mul,
42 Div,
43 Mod,
44 Pow,
45 Negate,
46
47 Equal,
49 NotEqual,
50 Less,
51 Greater,
52 LessEqual,
53 GreaterEqual,
54
55 Not,
57
58 Jump,
61 JumpIfFalse,
63 JumpIfTrue,
65 Pop,
67
68 Call,
71 TailCall,
75 Return,
77 Closure,
79
80 BuildList,
83 BuildDict,
85 Subscript,
87 SubscriptOpt,
90 Slice,
92
93 GetProperty,
96 GetPropertyOpt,
99 SetProperty,
102 SetSubscript,
105 MethodCall,
107 MethodCallOpt,
110
111 Concat,
114
115 IterInit,
118 IterNext,
121
122 Pipe,
125
126 Throw,
129 TryCatchSetup,
131 PopHandler,
133
134 Parallel,
138 ParallelMap,
141 ParallelMapStream,
144 ParallelSettle,
147 Spawn,
150 SyncMutexEnter,
153
154 Import,
157 SelectiveImport,
159
160 DeadlineSetup,
163 DeadlineEnd,
165
166 BuildEnum,
171
172 MatchEnum,
177
178 PopIterator,
181
182 GetArgc,
185
186 CheckType,
192
193 TryUnwrap,
196 TryWrapOk,
198
199 CallSpread,
202 CallBuiltin,
205 CallBuiltinSpread,
208 MethodCallSpread,
211
212 Dup,
215 Swap,
217 Contains,
220
221 AddInt,
223 SubInt,
224 MulInt,
225 DivInt,
226 ModInt,
227 AddFloat,
228 SubFloat,
229 MulFloat,
230 DivFloat,
231 ModFloat,
232 EqualInt,
233 NotEqualInt,
234 LessInt,
235 GreaterInt,
236 LessEqualInt,
237 GreaterEqualInt,
238 EqualFloat,
239 NotEqualFloat,
240 LessFloat,
241 GreaterFloat,
242 LessEqualFloat,
243 GreaterEqualFloat,
244 EqualBool,
245 NotEqualBool,
246 EqualString,
247 NotEqualString,
248
249 Yield,
251
252 GetLocalSlot,
255 DefLocalSlot,
257 SetLocalSlot,
259}
260
261impl Op {
262 pub(crate) const ALL: &'static [Self] = &[
263 Op::Constant,
264 Op::Nil,
265 Op::True,
266 Op::False,
267 Op::GetVar,
268 Op::DefLet,
269 Op::DefVar,
270 Op::SetVar,
271 Op::PushScope,
272 Op::PopScope,
273 Op::Add,
274 Op::Sub,
275 Op::Mul,
276 Op::Div,
277 Op::Mod,
278 Op::Pow,
279 Op::Negate,
280 Op::Equal,
281 Op::NotEqual,
282 Op::Less,
283 Op::Greater,
284 Op::LessEqual,
285 Op::GreaterEqual,
286 Op::Not,
287 Op::Jump,
288 Op::JumpIfFalse,
289 Op::JumpIfTrue,
290 Op::Pop,
291 Op::Call,
292 Op::TailCall,
293 Op::Return,
294 Op::Closure,
295 Op::BuildList,
296 Op::BuildDict,
297 Op::Subscript,
298 Op::SubscriptOpt,
299 Op::Slice,
300 Op::GetProperty,
301 Op::GetPropertyOpt,
302 Op::SetProperty,
303 Op::SetSubscript,
304 Op::MethodCall,
305 Op::MethodCallOpt,
306 Op::Concat,
307 Op::IterInit,
308 Op::IterNext,
309 Op::Pipe,
310 Op::Throw,
311 Op::TryCatchSetup,
312 Op::PopHandler,
313 Op::Parallel,
314 Op::ParallelMap,
315 Op::ParallelMapStream,
316 Op::ParallelSettle,
317 Op::Spawn,
318 Op::SyncMutexEnter,
319 Op::Import,
320 Op::SelectiveImport,
321 Op::DeadlineSetup,
322 Op::DeadlineEnd,
323 Op::BuildEnum,
324 Op::MatchEnum,
325 Op::PopIterator,
326 Op::GetArgc,
327 Op::CheckType,
328 Op::TryUnwrap,
329 Op::TryWrapOk,
330 Op::CallSpread,
331 Op::CallBuiltin,
332 Op::CallBuiltinSpread,
333 Op::MethodCallSpread,
334 Op::Dup,
335 Op::Swap,
336 Op::Contains,
337 Op::AddInt,
338 Op::SubInt,
339 Op::MulInt,
340 Op::DivInt,
341 Op::ModInt,
342 Op::AddFloat,
343 Op::SubFloat,
344 Op::MulFloat,
345 Op::DivFloat,
346 Op::ModFloat,
347 Op::EqualInt,
348 Op::NotEqualInt,
349 Op::LessInt,
350 Op::GreaterInt,
351 Op::LessEqualInt,
352 Op::GreaterEqualInt,
353 Op::EqualFloat,
354 Op::NotEqualFloat,
355 Op::LessFloat,
356 Op::GreaterFloat,
357 Op::LessEqualFloat,
358 Op::GreaterEqualFloat,
359 Op::EqualBool,
360 Op::NotEqualBool,
361 Op::EqualString,
362 Op::NotEqualString,
363 Op::Yield,
364 Op::GetLocalSlot,
365 Op::DefLocalSlot,
366 Op::SetLocalSlot,
367 ];
368
369 pub(crate) fn from_byte(byte: u8) -> Option<Self> {
370 Self::ALL.get(byte as usize).copied()
371 }
372}
373
374#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
376pub enum Constant {
377 Int(i64),
378 Float(f64),
379 String(String),
380 Bool(bool),
381 Nil,
382 Duration(i64),
383}
384
385#[derive(Debug, Clone, PartialEq, Eq)]
395pub(crate) enum InlineCacheEntry {
396 Empty,
397 Property {
398 name_idx: u16,
399 target: PropertyCacheTarget,
400 },
401 Method {
402 name_idx: u16,
403 argc: usize,
404 target: MethodCacheTarget,
405 },
406 AdaptiveBinary {
407 op: AdaptiveBinaryOp,
408 state: AdaptiveBinaryState,
409 },
410 DirectCall {
411 state: DirectCallState,
412 },
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
416pub(crate) enum AdaptiveBinaryOp {
417 Add,
418 Sub,
419 Mul,
420 Div,
421 Mod,
422 Equal,
423 NotEqual,
424 Less,
425 Greater,
426 LessEqual,
427 GreaterEqual,
428}
429
430#[derive(Debug, Clone, PartialEq, Eq)]
431pub(crate) enum AdaptiveBinaryState {
432 Warmup {
433 shape: BinaryShape,
434 hits: u8,
435 },
436 Specialized {
437 shape: BinaryShape,
438 hits: u64,
439 misses: u64,
440 },
441}
442
443#[derive(Debug, Clone, Copy, PartialEq, Eq)]
444pub(crate) enum BinaryShape {
445 Int,
446 Float,
447 Bool,
448 String,
449}
450
451#[derive(Debug, Clone)]
452pub(crate) enum DirectCallState {
453 Warmup {
454 argc: usize,
455 target: DirectCallTarget,
456 hits: u8,
457 },
458 Specialized {
459 argc: usize,
460 target: DirectCallTarget,
461 hits: u64,
462 misses: u64,
463 },
464}
465
466#[derive(Debug, Clone)]
467pub(crate) enum DirectCallTarget {
468 Closure(Rc<crate::value::VmClosure>),
469}
470
471impl PartialEq for DirectCallTarget {
472 fn eq(&self, other: &Self) -> bool {
473 match (self, other) {
474 (Self::Closure(left), Self::Closure(right)) => Rc::ptr_eq(left, right),
475 }
476 }
477}
478
479impl Eq for DirectCallTarget {}
480
481impl PartialEq for DirectCallState {
482 fn eq(&self, other: &Self) -> bool {
483 match (self, other) {
484 (
485 Self::Warmup {
486 argc: left_argc,
487 target: left_target,
488 hits: left_hits,
489 },
490 Self::Warmup {
491 argc: right_argc,
492 target: right_target,
493 hits: right_hits,
494 },
495 ) => left_argc == right_argc && left_target == right_target && left_hits == right_hits,
496 (
497 Self::Specialized {
498 argc: left_argc,
499 target: left_target,
500 hits: left_hits,
501 misses: left_misses,
502 },
503 Self::Specialized {
504 argc: right_argc,
505 target: right_target,
506 hits: right_hits,
507 misses: right_misses,
508 },
509 ) => {
510 left_argc == right_argc
511 && left_target == right_target
512 && left_hits == right_hits
513 && left_misses == right_misses
514 }
515 _ => false,
516 }
517 }
518}
519
520impl Eq for DirectCallState {}
521
522#[derive(Debug, Clone, PartialEq, Eq)]
523pub(crate) enum PropertyCacheTarget {
524 DictField(Rc<str>),
525 StructField { field_name: Rc<str>, index: usize },
526 ListCount,
527 ListEmpty,
528 ListFirst,
529 ListLast,
530 StringCount,
531 StringEmpty,
532 PairFirst,
533 PairSecond,
534 EnumVariant,
535 EnumFields,
536}
537
538#[derive(Debug, Clone, Copy, PartialEq, Eq)]
539pub(crate) enum MethodCacheTarget {
540 ListCount,
541 ListEmpty,
542 ListContains,
543 StringCount,
544 StringEmpty,
545 StringContains,
546 DictCount,
547 DictHas,
548 RangeCount,
549 RangeLen,
550 RangeEmpty,
551 RangeFirst,
552 RangeLast,
553 SetCount,
554 SetLen,
555 SetEmpty,
556 SetContains,
557}
558
559#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
561pub struct LocalSlotInfo {
562 pub name: String,
563 pub mutable: bool,
564 pub scope_depth: usize,
565}
566
567impl fmt::Display for Constant {
568 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569 match self {
570 Constant::Int(n) => write!(f, "{n}"),
571 Constant::Float(n) => write!(f, "{n}"),
572 Constant::String(s) => write!(f, "\"{s}\""),
573 Constant::Bool(b) => write!(f, "{b}"),
574 Constant::Nil => write!(f, "nil"),
575 Constant::Duration(ms) => write!(f, "{ms}ms"),
576 }
577 }
578}
579
580#[derive(Debug, Clone)]
582pub struct Chunk {
583 pub code: Vec<u8>,
585 pub constants: Vec<Constant>,
587 pub lines: Vec<u32>,
589 pub columns: Vec<u32>,
592 pub source_file: Option<String>,
597 current_col: u32,
599 pub functions: Vec<CompiledFunctionRef>,
601 inline_cache_slots: BTreeMap<usize, usize>,
604 inline_caches: Rc<RefCell<Vec<InlineCacheEntry>>>,
607 constant_strings: Rc<RefCell<Vec<Option<Rc<str>>>>>,
615 pub(crate) local_slots: Vec<LocalSlotInfo>,
617}
618
619pub type ChunkRef = Rc<Chunk>;
620pub type CompiledFunctionRef = Rc<CompiledFunction>;
621
622#[derive(Debug, Clone, Serialize, Deserialize)]
627pub struct CachedChunk {
628 pub(crate) code: Vec<u8>,
629 pub(crate) constants: Vec<Constant>,
630 pub(crate) lines: Vec<u32>,
631 pub(crate) columns: Vec<u32>,
632 pub(crate) source_file: Option<String>,
633 pub(crate) current_col: u32,
634 pub(crate) functions: Vec<CachedCompiledFunction>,
635 pub(crate) inline_cache_slots: BTreeMap<usize, usize>,
636 pub(crate) local_slots: Vec<LocalSlotInfo>,
637}
638
639#[derive(Debug, Clone, Serialize, Deserialize)]
640pub struct CachedCompiledFunction {
641 pub(crate) name: String,
642 pub(crate) type_params: Vec<String>,
643 pub(crate) nominal_type_names: Vec<String>,
644 pub(crate) params: Vec<CachedParamSlot>,
645 pub(crate) default_start: Option<usize>,
646 pub(crate) chunk: CachedChunk,
647 pub(crate) is_generator: bool,
648 pub(crate) is_stream: bool,
649 pub(crate) has_rest_param: bool,
650 pub(crate) has_runtime_type_checks: bool,
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize)]
654pub(crate) struct CachedParamSlot {
655 pub(crate) name: String,
656 pub(crate) type_expr: Option<TypeExpr>,
657 pub(crate) has_default: bool,
658}
659
660impl CachedParamSlot {
661 fn thaw(&self) -> ParamSlot {
662 ParamSlot {
663 name: self.name.clone(),
664 type_expr: self.type_expr.clone(),
665 runtime_guard: self
666 .type_expr
667 .as_ref()
668 .map(RuntimeParamGuard::from_type_expr),
669 has_default: self.has_default,
670 }
671 }
672}
673
674#[derive(Debug, Clone, Serialize, Deserialize)]
680pub struct ParamSlot {
681 pub name: String,
682 pub type_expr: Option<TypeExpr>,
685 #[serde(skip)]
688 pub(crate) runtime_guard: Option<RuntimeParamGuard>,
689 pub has_default: bool,
693}
694
695impl ParamSlot {
696 pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
699 Self {
700 name: param.name.clone(),
701 type_expr: param.type_expr.clone(),
702 runtime_guard: param
703 .type_expr
704 .as_ref()
705 .map(RuntimeParamGuard::from_type_expr),
706 has_default: param.default_value.is_some(),
707 }
708 }
709
710 fn freeze_for_cache(&self) -> CachedParamSlot {
711 CachedParamSlot {
712 name: self.name.clone(),
713 type_expr: self.type_expr.clone(),
714 has_default: self.has_default,
715 }
716 }
717
718 pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
723 params.iter().map(Self::from_typed_param).collect()
724 }
725}
726
727#[derive(Debug, Clone)]
729pub struct CompiledFunction {
730 pub name: String,
731 pub type_params: Vec<String>,
735 pub nominal_type_names: Vec<String>,
739 pub params: Vec<ParamSlot>,
740 pub default_start: Option<usize>,
742 pub chunk: ChunkRef,
743 pub is_generator: bool,
745 pub is_stream: bool,
747 pub has_rest_param: bool,
749 pub has_runtime_type_checks: bool,
754}
755
756impl CompiledFunction {
757 pub(crate) fn has_runtime_type_checks_for_params(params: &[ParamSlot]) -> bool {
758 params.iter().any(|param| param.type_expr.is_some())
759 }
760
761 pub fn param_names(&self) -> impl Iterator<Item = &str> {
764 self.params.iter().map(|p| p.name.as_str())
765 }
766
767 pub fn required_param_count(&self) -> usize {
769 self.default_start.unwrap_or(self.params.len())
770 }
771
772 pub fn declares_type_param(&self, name: &str) -> bool {
773 self.type_params.iter().any(|param| param == name)
774 }
775
776 pub fn has_nominal_type(&self, name: &str) -> bool {
777 self.nominal_type_names.iter().any(|ty| ty == name)
778 }
779
780 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
781 CachedCompiledFunction {
782 name: self.name.clone(),
783 type_params: self.type_params.clone(),
784 nominal_type_names: self.nominal_type_names.clone(),
785 params: self
786 .params
787 .iter()
788 .map(ParamSlot::freeze_for_cache)
789 .collect(),
790 default_start: self.default_start,
791 chunk: self.chunk.freeze_for_cache(),
792 is_generator: self.is_generator,
793 is_stream: self.is_stream,
794 has_rest_param: self.has_rest_param,
795 has_runtime_type_checks: self.has_runtime_type_checks,
796 }
797 }
798
799 pub(crate) fn from_cached(cached: &CachedCompiledFunction) -> Self {
800 Self {
801 name: cached.name.clone(),
802 type_params: cached.type_params.clone(),
803 nominal_type_names: cached.nominal_type_names.clone(),
804 params: cached.params.iter().map(CachedParamSlot::thaw).collect(),
805 default_start: cached.default_start,
806 chunk: Rc::new(Chunk::from_cached(&cached.chunk)),
807 is_generator: cached.is_generator,
808 is_stream: cached.is_stream,
809 has_rest_param: cached.has_rest_param,
810 has_runtime_type_checks: cached.has_runtime_type_checks,
811 }
812 }
813}
814
815impl Chunk {
816 pub fn new() -> Self {
817 Self {
818 code: Vec::new(),
819 constants: Vec::new(),
820 lines: Vec::new(),
821 columns: Vec::new(),
822 source_file: None,
823 current_col: 0,
824 functions: Vec::new(),
825 inline_cache_slots: BTreeMap::new(),
826 inline_caches: Rc::new(RefCell::new(Vec::new())),
827 constant_strings: Rc::new(RefCell::new(Vec::new())),
828 local_slots: Vec::new(),
829 }
830 }
831
832 pub fn set_column(&mut self, col: u32) {
834 self.current_col = col;
835 }
836
837 pub fn add_constant(&mut self, constant: Constant) -> u16 {
839 for (i, c) in self.constants.iter().enumerate() {
840 if c == &constant {
841 return i as u16;
842 }
843 }
844 let idx = self.constants.len();
845 self.constants.push(constant);
846 idx as u16
847 }
848
849 pub fn emit(&mut self, op: Op, line: u32) {
851 let col = self.current_col;
852 let op_offset = self.code.len();
853 self.code.push(op as u8);
854 self.lines.push(line);
855 self.columns.push(col);
856 if is_adaptive_binary_op(op) {
857 self.register_inline_cache(op_offset);
858 }
859 }
860
861 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
863 let col = self.current_col;
864 let op_offset = self.code.len();
865 self.code.push(op as u8);
866 self.code.push((arg >> 8) as u8);
867 self.code.push((arg & 0xFF) as u8);
868 self.lines.push(line);
869 self.lines.push(line);
870 self.lines.push(line);
871 self.columns.push(col);
872 self.columns.push(col);
873 self.columns.push(col);
874 if matches!(
875 op,
876 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread
877 ) {
878 self.register_inline_cache(op_offset);
879 }
880 }
881
882 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
884 let col = self.current_col;
885 let op_offset = self.code.len();
886 self.code.push(op as u8);
887 self.code.push(arg);
888 self.lines.push(line);
889 self.lines.push(line);
890 self.columns.push(col);
891 self.columns.push(col);
892 if matches!(op, Op::Call) {
893 self.register_inline_cache(op_offset);
894 }
895 }
896
897 pub fn emit_call_builtin(
899 &mut self,
900 id: crate::BuiltinId,
901 name_idx: u16,
902 arg_count: u8,
903 line: u32,
904 ) {
905 let col = self.current_col;
906 let op_offset = self.code.len();
907 self.code.push(Op::CallBuiltin as u8);
908 self.code.extend_from_slice(&id.raw().to_be_bytes());
909 self.code.push((name_idx >> 8) as u8);
910 self.code.push((name_idx & 0xFF) as u8);
911 self.code.push(arg_count);
912 for _ in 0..12 {
913 self.lines.push(line);
914 self.columns.push(col);
915 }
916 self.register_inline_cache(op_offset);
917 }
918
919 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
921 let col = self.current_col;
922 self.code.push(Op::CallBuiltinSpread as u8);
923 self.code.extend_from_slice(&id.raw().to_be_bytes());
924 self.code.push((name_idx >> 8) as u8);
925 self.code.push((name_idx & 0xFF) as u8);
926 for _ in 0..11 {
927 self.lines.push(line);
928 self.columns.push(col);
929 }
930 }
931
932 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
934 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
935 }
936
937 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
939 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
940 }
941
942 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
943 let col = self.current_col;
944 let op_offset = self.code.len();
945 self.code.push(op as u8);
946 self.code.push((name_idx >> 8) as u8);
947 self.code.push((name_idx & 0xFF) as u8);
948 self.code.push(arg_count);
949 self.lines.push(line);
950 self.lines.push(line);
951 self.lines.push(line);
952 self.lines.push(line);
953 self.columns.push(col);
954 self.columns.push(col);
955 self.columns.push(col);
956 self.columns.push(col);
957 self.register_inline_cache(op_offset);
958 }
959
960 pub fn current_offset(&self) -> usize {
962 self.code.len()
963 }
964
965 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
967 let col = self.current_col;
968 self.code.push(op as u8);
969 let patch_pos = self.code.len();
970 self.code.push(0xFF);
971 self.code.push(0xFF);
972 self.lines.push(line);
973 self.lines.push(line);
974 self.lines.push(line);
975 self.columns.push(col);
976 self.columns.push(col);
977 self.columns.push(col);
978 patch_pos
979 }
980
981 pub fn patch_jump(&mut self, patch_pos: usize) {
983 let target = self.code.len() as u16;
984 self.code[patch_pos] = (target >> 8) as u8;
985 self.code[patch_pos + 1] = (target & 0xFF) as u8;
986 }
987
988 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
990 let target = target as u16;
991 self.code[patch_pos] = (target >> 8) as u8;
992 self.code[patch_pos + 1] = (target & 0xFF) as u8;
993 }
994
995 pub fn read_u16(&self, pos: usize) -> u16 {
997 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
998 }
999
1000 fn register_inline_cache(&mut self, op_offset: usize) {
1001 if self.inline_cache_slots.contains_key(&op_offset) {
1002 return;
1003 }
1004 let mut entries = self.inline_caches.borrow_mut();
1005 let slot = entries.len();
1006 entries.push(InlineCacheEntry::Empty);
1007 self.inline_cache_slots.insert(op_offset, slot);
1008 }
1009
1010 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
1011 self.inline_cache_slots.get(&op_offset).copied()
1012 }
1013
1014 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<Rc<str>> {
1019 let mut entries = self.constant_strings.borrow_mut();
1024 if entries.len() < self.constants.len() {
1025 entries.resize(self.constants.len(), None);
1026 }
1027 if let Some(Some(existing)) = entries.get(idx) {
1028 return Some(Rc::clone(existing));
1029 }
1030 let materialized = match self.constants.get(idx)? {
1031 Constant::String(s) => Rc::<str>::from(s.as_str()),
1032 _ => return None,
1033 };
1034 entries[idx] = Some(Rc::clone(&materialized));
1035 Some(materialized)
1036 }
1037
1038 pub(crate) fn inline_cache_entry(&self, slot: usize) -> InlineCacheEntry {
1039 self.inline_caches
1040 .borrow()
1041 .get(slot)
1042 .cloned()
1043 .unwrap_or(InlineCacheEntry::Empty)
1044 }
1045
1046 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1047 if let Some(existing) = self.inline_caches.borrow_mut().get_mut(slot) {
1048 *existing = entry;
1049 }
1050 }
1051
1052 pub fn freeze_for_cache(&self) -> CachedChunk {
1053 CachedChunk {
1054 code: self.code.clone(),
1055 constants: self.constants.clone(),
1056 lines: self.lines.clone(),
1057 columns: self.columns.clone(),
1058 source_file: self.source_file.clone(),
1059 current_col: self.current_col,
1060 functions: self
1061 .functions
1062 .iter()
1063 .map(|function| function.freeze_for_cache())
1064 .collect(),
1065 inline_cache_slots: self.inline_cache_slots.clone(),
1066 local_slots: self.local_slots.clone(),
1067 }
1068 }
1069
1070 pub fn from_cached(cached: &CachedChunk) -> Self {
1071 let inline_cache_count = cached.inline_cache_slots.len();
1072 let constants_count = cached.constants.len();
1073 Self {
1074 code: cached.code.clone(),
1075 constants: cached.constants.clone(),
1076 lines: cached.lines.clone(),
1077 columns: cached.columns.clone(),
1078 source_file: cached.source_file.clone(),
1079 current_col: cached.current_col,
1080 functions: cached
1081 .functions
1082 .iter()
1083 .map(|function| Rc::new(CompiledFunction::from_cached(function)))
1084 .collect(),
1085 inline_cache_slots: cached.inline_cache_slots.clone(),
1086 inline_caches: Rc::new(RefCell::new(vec![
1087 InlineCacheEntry::Empty;
1088 inline_cache_count
1089 ])),
1090 constant_strings: Rc::new(RefCell::new(vec![None; constants_count])),
1091 local_slots: cached.local_slots.clone(),
1092 }
1093 }
1094
1095 pub(crate) fn add_local_slot(
1096 &mut self,
1097 name: String,
1098 mutable: bool,
1099 scope_depth: usize,
1100 ) -> u16 {
1101 let idx = self.local_slots.len();
1102 self.local_slots.push(LocalSlotInfo {
1103 name,
1104 mutable,
1105 scope_depth,
1106 });
1107 idx as u16
1108 }
1109
1110 #[cfg(test)]
1111 pub(crate) fn inline_cache_entries(&self) -> Vec<InlineCacheEntry> {
1112 self.inline_caches.borrow().clone()
1113 }
1114
1115 pub fn read_u64(&self, pos: usize) -> u64 {
1117 u64::from_be_bytes([
1118 self.code[pos],
1119 self.code[pos + 1],
1120 self.code[pos + 2],
1121 self.code[pos + 3],
1122 self.code[pos + 4],
1123 self.code[pos + 5],
1124 self.code[pos + 6],
1125 self.code[pos + 7],
1126 ])
1127 }
1128
1129 pub fn disassemble(&self, name: &str) -> String {
1131 let mut out = format!("== {name} ==\n");
1132 let mut ip = 0;
1133 while ip < self.code.len() {
1134 let op = self.code[ip];
1135 let line = self.lines.get(ip).copied().unwrap_or(0);
1136 out.push_str(&format!("{ip:04} [{line:>4}] "));
1137 ip += 1;
1138
1139 match op {
1140 x if x == Op::Constant as u8 => {
1141 let idx = self.read_u16(ip);
1142 ip += 2;
1143 let val = &self.constants[idx as usize];
1144 out.push_str(&format!("CONSTANT {idx:>4} ({val})\n"));
1145 }
1146 x if x == Op::Nil as u8 => out.push_str("NIL\n"),
1147 x if x == Op::True as u8 => out.push_str("TRUE\n"),
1148 x if x == Op::False as u8 => out.push_str("FALSE\n"),
1149 x if x == Op::GetVar as u8 => {
1150 let idx = self.read_u16(ip);
1151 ip += 2;
1152 out.push_str(&format!(
1153 "GET_VAR {:>4} ({})\n",
1154 idx, self.constants[idx as usize]
1155 ));
1156 }
1157 x if x == Op::DefLet as u8 => {
1158 let idx = self.read_u16(ip);
1159 ip += 2;
1160 out.push_str(&format!(
1161 "DEF_LET {:>4} ({})\n",
1162 idx, self.constants[idx as usize]
1163 ));
1164 }
1165 x if x == Op::DefVar as u8 => {
1166 let idx = self.read_u16(ip);
1167 ip += 2;
1168 out.push_str(&format!(
1169 "DEF_VAR {:>4} ({})\n",
1170 idx, self.constants[idx as usize]
1171 ));
1172 }
1173 x if x == Op::SetVar as u8 => {
1174 let idx = self.read_u16(ip);
1175 ip += 2;
1176 out.push_str(&format!(
1177 "SET_VAR {:>4} ({})\n",
1178 idx, self.constants[idx as usize]
1179 ));
1180 }
1181 x if x == Op::GetLocalSlot as u8 => {
1182 let slot = self.read_u16(ip);
1183 ip += 2;
1184 out.push_str(&format!("GET_LOCAL_SLOT {slot:>4}"));
1185 if let Some(info) = self.local_slots.get(slot as usize) {
1186 out.push_str(&format!(" ({})", info.name));
1187 }
1188 out.push('\n');
1189 }
1190 x if x == Op::DefLocalSlot as u8 => {
1191 let slot = self.read_u16(ip);
1192 ip += 2;
1193 out.push_str(&format!("DEF_LOCAL_SLOT {slot:>4}"));
1194 if let Some(info) = self.local_slots.get(slot as usize) {
1195 out.push_str(&format!(" ({})", info.name));
1196 }
1197 out.push('\n');
1198 }
1199 x if x == Op::SetLocalSlot as u8 => {
1200 let slot = self.read_u16(ip);
1201 ip += 2;
1202 out.push_str(&format!("SET_LOCAL_SLOT {slot:>4}"));
1203 if let Some(info) = self.local_slots.get(slot as usize) {
1204 out.push_str(&format!(" ({})", info.name));
1205 }
1206 out.push('\n');
1207 }
1208 x if x == Op::PushScope as u8 => out.push_str("PUSH_SCOPE\n"),
1209 x if x == Op::PopScope as u8 => out.push_str("POP_SCOPE\n"),
1210 x if x == Op::Add as u8 => out.push_str("ADD\n"),
1211 x if x == Op::Sub as u8 => out.push_str("SUB\n"),
1212 x if x == Op::Mul as u8 => out.push_str("MUL\n"),
1213 x if x == Op::Div as u8 => out.push_str("DIV\n"),
1214 x if x == Op::Mod as u8 => out.push_str("MOD\n"),
1215 x if x == Op::Pow as u8 => out.push_str("POW\n"),
1216 x if x == Op::Negate as u8 => out.push_str("NEGATE\n"),
1217 x if x == Op::Equal as u8 => out.push_str("EQUAL\n"),
1218 x if x == Op::NotEqual as u8 => out.push_str("NOT_EQUAL\n"),
1219 x if x == Op::Less as u8 => out.push_str("LESS\n"),
1220 x if x == Op::Greater as u8 => out.push_str("GREATER\n"),
1221 x if x == Op::LessEqual as u8 => out.push_str("LESS_EQUAL\n"),
1222 x if x == Op::GreaterEqual as u8 => out.push_str("GREATER_EQUAL\n"),
1223 x if x == Op::Contains as u8 => out.push_str("CONTAINS\n"),
1224 x if x == Op::Not as u8 => out.push_str("NOT\n"),
1225 x if x == Op::Jump as u8 => {
1226 let target = self.read_u16(ip);
1227 ip += 2;
1228 out.push_str(&format!("JUMP {target:>4}\n"));
1229 }
1230 x if x == Op::JumpIfFalse as u8 => {
1231 let target = self.read_u16(ip);
1232 ip += 2;
1233 out.push_str(&format!("JUMP_IF_FALSE {target:>4}\n"));
1234 }
1235 x if x == Op::JumpIfTrue as u8 => {
1236 let target = self.read_u16(ip);
1237 ip += 2;
1238 out.push_str(&format!("JUMP_IF_TRUE {target:>4}\n"));
1239 }
1240 x if x == Op::Pop as u8 => out.push_str("POP\n"),
1241 x if x == Op::Call as u8 => {
1242 let argc = self.code[ip];
1243 ip += 1;
1244 out.push_str(&format!("CALL {argc:>4}\n"));
1245 }
1246 x if x == Op::TailCall as u8 => {
1247 let argc = self.code[ip];
1248 ip += 1;
1249 out.push_str(&format!("TAIL_CALL {argc:>4}\n"));
1250 }
1251 x if x == Op::Return as u8 => out.push_str("RETURN\n"),
1252 x if x == Op::Closure as u8 => {
1253 let idx = self.read_u16(ip);
1254 ip += 2;
1255 out.push_str(&format!("CLOSURE {idx:>4}\n"));
1256 }
1257 x if x == Op::BuildList as u8 => {
1258 let count = self.read_u16(ip);
1259 ip += 2;
1260 out.push_str(&format!("BUILD_LIST {count:>4}\n"));
1261 }
1262 x if x == Op::BuildDict as u8 => {
1263 let count = self.read_u16(ip);
1264 ip += 2;
1265 out.push_str(&format!("BUILD_DICT {count:>4}\n"));
1266 }
1267 x if x == Op::Subscript as u8 => out.push_str("SUBSCRIPT\n"),
1268 x if x == Op::SubscriptOpt as u8 => out.push_str("SUBSCRIPT_OPT\n"),
1269 x if x == Op::Slice as u8 => out.push_str("SLICE\n"),
1270 x if x == Op::GetProperty as u8 => {
1271 let idx = self.read_u16(ip);
1272 ip += 2;
1273 out.push_str(&format!(
1274 "GET_PROPERTY {:>4} ({})\n",
1275 idx, self.constants[idx as usize]
1276 ));
1277 }
1278 x if x == Op::GetPropertyOpt as u8 => {
1279 let idx = self.read_u16(ip);
1280 ip += 2;
1281 out.push_str(&format!(
1282 "GET_PROPERTY_OPT {:>4} ({})\n",
1283 idx, self.constants[idx as usize]
1284 ));
1285 }
1286 x if x == Op::SetProperty as u8 => {
1287 let idx = self.read_u16(ip);
1288 ip += 2;
1289 out.push_str(&format!(
1290 "SET_PROPERTY {:>4} ({})\n",
1291 idx, self.constants[idx as usize]
1292 ));
1293 }
1294 x if x == Op::SetSubscript as u8 => {
1295 let idx = self.read_u16(ip);
1296 ip += 2;
1297 out.push_str(&format!(
1298 "SET_SUBSCRIPT {:>4} ({})\n",
1299 idx, self.constants[idx as usize]
1300 ));
1301 }
1302 x if x == Op::MethodCall as u8 => {
1303 let idx = self.read_u16(ip);
1304 ip += 2;
1305 let argc = self.code[ip];
1306 ip += 1;
1307 out.push_str(&format!(
1308 "METHOD_CALL {:>4} ({}) argc={}\n",
1309 idx, self.constants[idx as usize], argc
1310 ));
1311 }
1312 x if x == Op::MethodCallOpt as u8 => {
1313 let idx = self.read_u16(ip);
1314 ip += 2;
1315 let argc = self.code[ip];
1316 ip += 1;
1317 out.push_str(&format!(
1318 "METHOD_CALL_OPT {:>4} ({}) argc={}\n",
1319 idx, self.constants[idx as usize], argc
1320 ));
1321 }
1322 x if x == Op::Concat as u8 => {
1323 let count = self.read_u16(ip);
1324 ip += 2;
1325 out.push_str(&format!("CONCAT {count:>4}\n"));
1326 }
1327 x if x == Op::IterInit as u8 => out.push_str("ITER_INIT\n"),
1328 x if x == Op::IterNext as u8 => {
1329 let target = self.read_u16(ip);
1330 ip += 2;
1331 out.push_str(&format!("ITER_NEXT {target:>4}\n"));
1332 }
1333 x if x == Op::Throw as u8 => out.push_str("THROW\n"),
1334 x if x == Op::TryCatchSetup as u8 => {
1335 let target = self.read_u16(ip);
1336 ip += 2;
1337 out.push_str(&format!("TRY_CATCH_SETUP {target:>4}\n"));
1338 }
1339 x if x == Op::PopHandler as u8 => out.push_str("POP_HANDLER\n"),
1340 x if x == Op::Pipe as u8 => out.push_str("PIPE\n"),
1341 x if x == Op::Parallel as u8 => out.push_str("PARALLEL\n"),
1342 x if x == Op::ParallelMap as u8 => out.push_str("PARALLEL_MAP\n"),
1343 x if x == Op::ParallelMapStream as u8 => out.push_str("PARALLEL_MAP_STREAM\n"),
1344 x if x == Op::ParallelSettle as u8 => out.push_str("PARALLEL_SETTLE\n"),
1345 x if x == Op::Spawn as u8 => out.push_str("SPAWN\n"),
1346 x if x == Op::Import as u8 => {
1347 let idx = self.read_u16(ip);
1348 ip += 2;
1349 out.push_str(&format!(
1350 "IMPORT {:>4} ({})\n",
1351 idx, self.constants[idx as usize]
1352 ));
1353 }
1354 x if x == Op::SelectiveImport as u8 => {
1355 let path_idx = self.read_u16(ip);
1356 ip += 2;
1357 let names_idx = self.read_u16(ip);
1358 ip += 2;
1359 out.push_str(&format!(
1360 "SELECTIVE_IMPORT {:>4} ({}) names: {:>4} ({})\n",
1361 path_idx,
1362 self.constants[path_idx as usize],
1363 names_idx,
1364 self.constants[names_idx as usize]
1365 ));
1366 }
1367 x if x == Op::SyncMutexEnter as u8 => {
1368 let idx = self.read_u16(ip);
1369 ip += 2;
1370 out.push_str(&format!(
1371 "SYNC_MUTEX_ENTER {:>4} ({})\n",
1372 idx, self.constants[idx as usize]
1373 ));
1374 }
1375 x if x == Op::DeadlineSetup as u8 => out.push_str("DEADLINE_SETUP\n"),
1376 x if x == Op::DeadlineEnd as u8 => out.push_str("DEADLINE_END\n"),
1377 x if x == Op::BuildEnum as u8 => {
1378 let enum_idx = self.read_u16(ip);
1379 ip += 2;
1380 let variant_idx = self.read_u16(ip);
1381 ip += 2;
1382 let field_count = self.read_u16(ip);
1383 ip += 2;
1384 out.push_str(&format!(
1385 "BUILD_ENUM {:>4} ({}) {:>4} ({}) fields={}\n",
1386 enum_idx,
1387 self.constants[enum_idx as usize],
1388 variant_idx,
1389 self.constants[variant_idx as usize],
1390 field_count
1391 ));
1392 }
1393 x if x == Op::MatchEnum as u8 => {
1394 let enum_idx = self.read_u16(ip);
1395 ip += 2;
1396 let variant_idx = self.read_u16(ip);
1397 ip += 2;
1398 out.push_str(&format!(
1399 "MATCH_ENUM {:>4} ({}) {:>4} ({})\n",
1400 enum_idx,
1401 self.constants[enum_idx as usize],
1402 variant_idx,
1403 self.constants[variant_idx as usize]
1404 ));
1405 }
1406 x if x == Op::PopIterator as u8 => out.push_str("POP_ITERATOR\n"),
1407 x if x == Op::TryUnwrap as u8 => out.push_str("TRY_UNWRAP\n"),
1408 x if x == Op::TryWrapOk as u8 => out.push_str("TRY_WRAP_OK\n"),
1409 x if x == Op::CallSpread as u8 => out.push_str("CALL_SPREAD\n"),
1410 x if x == Op::CallBuiltin as u8 => {
1411 let id = self.read_u64(ip);
1412 ip += 8;
1413 let idx = self.read_u16(ip);
1414 ip += 2;
1415 let argc = self.code[ip];
1416 ip += 1;
1417 out.push_str(&format!(
1418 "CALL_BUILTIN {id:#018x} {:>4} ({}) argc={}\n",
1419 idx, self.constants[idx as usize], argc
1420 ));
1421 }
1422 x if x == Op::CallBuiltinSpread as u8 => {
1423 let id = self.read_u64(ip);
1424 ip += 8;
1425 let idx = self.read_u16(ip);
1426 ip += 2;
1427 out.push_str(&format!(
1428 "CALL_BUILTIN_SPREAD {id:#018x} {:>4} ({})\n",
1429 idx, self.constants[idx as usize]
1430 ));
1431 }
1432 x if x == Op::MethodCallSpread as u8 => {
1433 let idx = self.read_u16(ip + 1);
1434 ip += 2;
1435 out.push_str(&format!("METHOD_CALL_SPREAD {idx}\n"));
1436 }
1437 x if x == Op::Dup as u8 => out.push_str("DUP\n"),
1438 x if x == Op::Swap as u8 => out.push_str("SWAP\n"),
1439 x if x == Op::AddInt as u8 => out.push_str("ADD_INT\n"),
1440 x if x == Op::SubInt as u8 => out.push_str("SUB_INT\n"),
1441 x if x == Op::MulInt as u8 => out.push_str("MUL_INT\n"),
1442 x if x == Op::DivInt as u8 => out.push_str("DIV_INT\n"),
1443 x if x == Op::ModInt as u8 => out.push_str("MOD_INT\n"),
1444 x if x == Op::AddFloat as u8 => out.push_str("ADD_FLOAT\n"),
1445 x if x == Op::SubFloat as u8 => out.push_str("SUB_FLOAT\n"),
1446 x if x == Op::MulFloat as u8 => out.push_str("MUL_FLOAT\n"),
1447 x if x == Op::DivFloat as u8 => out.push_str("DIV_FLOAT\n"),
1448 x if x == Op::ModFloat as u8 => out.push_str("MOD_FLOAT\n"),
1449 x if x == Op::EqualInt as u8 => out.push_str("EQUAL_INT\n"),
1450 x if x == Op::NotEqualInt as u8 => out.push_str("NOT_EQUAL_INT\n"),
1451 x if x == Op::LessInt as u8 => out.push_str("LESS_INT\n"),
1452 x if x == Op::GreaterInt as u8 => out.push_str("GREATER_INT\n"),
1453 x if x == Op::LessEqualInt as u8 => out.push_str("LESS_EQUAL_INT\n"),
1454 x if x == Op::GreaterEqualInt as u8 => out.push_str("GREATER_EQUAL_INT\n"),
1455 x if x == Op::EqualFloat as u8 => out.push_str("EQUAL_FLOAT\n"),
1456 x if x == Op::NotEqualFloat as u8 => out.push_str("NOT_EQUAL_FLOAT\n"),
1457 x if x == Op::LessFloat as u8 => out.push_str("LESS_FLOAT\n"),
1458 x if x == Op::GreaterFloat as u8 => out.push_str("GREATER_FLOAT\n"),
1459 x if x == Op::LessEqualFloat as u8 => out.push_str("LESS_EQUAL_FLOAT\n"),
1460 x if x == Op::GreaterEqualFloat as u8 => out.push_str("GREATER_EQUAL_FLOAT\n"),
1461 x if x == Op::EqualBool as u8 => out.push_str("EQUAL_BOOL\n"),
1462 x if x == Op::NotEqualBool as u8 => out.push_str("NOT_EQUAL_BOOL\n"),
1463 x if x == Op::EqualString as u8 => out.push_str("EQUAL_STRING\n"),
1464 x if x == Op::NotEqualString as u8 => out.push_str("NOT_EQUAL_STRING\n"),
1465 x if x == Op::Yield as u8 => out.push_str("YIELD\n"),
1466 _ => {
1467 out.push_str(&format!("UNKNOWN(0x{op:02x})\n"));
1468 }
1469 }
1470 }
1471 out
1472 }
1473}
1474
1475fn is_adaptive_binary_op(op: Op) -> bool {
1476 matches!(
1477 op,
1478 Op::Add
1479 | Op::Sub
1480 | Op::Mul
1481 | Op::Div
1482 | Op::Mod
1483 | Op::Equal
1484 | Op::NotEqual
1485 | Op::Less
1486 | Op::Greater
1487 | Op::LessEqual
1488 | Op::GreaterEqual
1489 )
1490}
1491
1492impl Default for Chunk {
1493 fn default() -> Self {
1494 Self::new()
1495 }
1496}
1497
1498#[cfg(test)]
1499mod tests {
1500 use super::Op;
1501
1502 #[test]
1503 fn op_from_byte_matches_repr_order() {
1504 for (byte, op) in Op::ALL.iter().copied().enumerate() {
1505 assert_eq!(byte as u8, op as u8);
1506 assert_eq!(Op::from_byte(byte as u8), Some(op));
1507 }
1508 assert_eq!(Op::from_byte(Op::ALL.len() as u8), None);
1509 }
1510}