1use std::collections::{BTreeMap, HashMap};
2use std::fmt;
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::Arc;
5
6use harn_parser::TypeExpr;
7use parking_lot::Mutex;
8use serde::{Deserialize, Serialize};
9
10use crate::harness::HarnessKind;
11use crate::runtime_guards::RuntimeParamGuard;
12
13pub(crate) const NO_INLINE_CACHE_SLOT: u32 = u32::MAX;
21static NEXT_CHUNK_CACHE_ID: AtomicU64 = AtomicU64::new(1);
22
23fn next_chunk_cache_id() -> u64 {
24 NEXT_CHUNK_CACHE_ID.fetch_add(1, Ordering::Relaxed)
25}
26
27pub use crate::vm::ops::Op;
34pub(crate) use crate::vm::ops::{is_adaptive_binary_op, op_reads_outer_name};
35
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38pub enum Constant {
39 Int(i64),
40 Float(f64),
41 String(String),
42 Bool(bool),
43 Nil,
44 Duration(i64),
45}
46
47fn constants_identical(a: &Constant, b: &Constant) -> bool {
56 match (a, b) {
57 (Constant::Float(x), Constant::Float(y)) => x.to_bits() == y.to_bits(),
58 _ => a == b,
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Hash)]
68enum ConstantKey {
69 Int(i64),
70 Float(u64),
71 String(String),
72 Bool(bool),
73 Nil,
74 Duration(i64),
75}
76
77impl From<&Constant> for ConstantKey {
78 fn from(constant: &Constant) -> Self {
79 match constant {
80 Constant::Int(value) => Self::Int(*value),
81 Constant::Float(value) => Self::Float(value.to_bits()),
82 Constant::String(value) => Self::String(value.clone()),
83 Constant::Bool(value) => Self::Bool(*value),
84 Constant::Nil => Self::Nil,
85 Constant::Duration(value) => Self::Duration(*value),
86 }
87 }
88}
89
90fn build_constant_index(constants: &[Constant]) -> HashMap<ConstantKey, u16> {
91 let mut index = HashMap::with_capacity(constants.len());
92 for (slot, constant) in constants.iter().enumerate() {
93 if let Ok(slot) = u16::try_from(slot) {
94 index.entry(ConstantKey::from(constant)).or_insert(slot);
95 }
96 }
97 index
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
110pub(crate) enum InlineCacheEntry {
111 Empty,
112 Property {
113 name_idx: u16,
114 target: PropertyCacheTarget,
115 },
116 Method {
117 name_idx: u16,
118 argc: usize,
119 target: MethodCacheTarget,
120 },
121 AdaptiveBinary {
122 op: AdaptiveBinaryOp,
123 state: AdaptiveBinaryState,
124 },
125 DirectCall {
126 state: DirectCallState,
127 },
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub(crate) enum AdaptiveBinaryOp {
132 Add,
133 Sub,
134 Mul,
135 Div,
136 Mod,
137 Equal,
138 NotEqual,
139 Less,
140 Greater,
141 LessEqual,
142 GreaterEqual,
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub(crate) enum AdaptiveBinaryState {
152 Warmup {
153 shape: BinaryShape,
154 hits: u8,
155 },
156 Specialized {
157 shape: BinaryShape,
158 hits: u64,
159 misses: u64,
160 },
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub(crate) enum BinaryShape {
165 Int,
166 Float,
167 Bool,
168 String,
169}
170
171#[derive(Debug, Clone)]
172pub(crate) enum DirectCallState {
173 Warmup {
174 argc: usize,
175 target: DirectCallTarget,
176 hits: u8,
177 },
178 Specialized {
179 argc: usize,
180 target: DirectCallTarget,
181 hits: u64,
182 misses: u64,
183 },
184}
185
186#[derive(Debug, Clone)]
187pub(crate) enum DirectCallTarget {
188 Closure(Arc<crate::value::VmClosure>),
189}
190
191impl PartialEq for DirectCallTarget {
192 fn eq(&self, other: &Self) -> bool {
193 match (self, other) {
194 (Self::Closure(left), Self::Closure(right)) => Arc::ptr_eq(left, right),
195 }
196 }
197}
198
199impl Eq for DirectCallTarget {}
200
201impl PartialEq for DirectCallState {
202 fn eq(&self, other: &Self) -> bool {
203 match (self, other) {
204 (
205 Self::Warmup {
206 argc: left_argc,
207 target: left_target,
208 hits: left_hits,
209 },
210 Self::Warmup {
211 argc: right_argc,
212 target: right_target,
213 hits: right_hits,
214 },
215 ) => left_argc == right_argc && left_target == right_target && left_hits == right_hits,
216 (
217 Self::Specialized {
218 argc: left_argc,
219 target: left_target,
220 hits: left_hits,
221 misses: left_misses,
222 },
223 Self::Specialized {
224 argc: right_argc,
225 target: right_target,
226 hits: right_hits,
227 misses: right_misses,
228 },
229 ) => {
230 left_argc == right_argc
231 && left_target == right_target
232 && left_hits == right_hits
233 && left_misses == right_misses
234 }
235 _ => false,
236 }
237 }
238}
239
240impl Eq for DirectCallState {}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub(crate) enum PropertyCacheTarget {
244 DictField(Arc<str>),
245 StructField { field_name: Arc<str>, index: usize },
246 HarnessSubHandle(HarnessKind),
247 ListCount,
248 ListEmpty,
249 ListFirst,
250 ListLast,
251 StringCount,
252 StringEmpty,
253 PairFirst,
254 PairSecond,
255 EnumVariant,
256 EnumFields,
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub(crate) enum MethodCacheTarget {
261 Harness(HarnessKind),
262 ListCount,
263 ListEmpty,
264 ListContains,
265 StringCount,
266 StringEmpty,
267 StringContains,
268 DictCount,
269 DictHas,
270 RangeCount,
271 RangeLen,
272 RangeEmpty,
273 RangeFirst,
274 RangeLast,
275 SetCount,
276 SetLen,
277 SetEmpty,
278 SetContains,
279}
280
281#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283pub struct LocalSlotInfo {
284 pub name: String,
285 pub mutable: bool,
286 pub scope_depth: usize,
287}
288
289impl fmt::Display for Constant {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 match self {
292 Constant::Int(n) => write!(f, "{n}"),
293 Constant::Float(n) => write!(f, "{n}"),
294 Constant::String(s) => write!(f, "\"{s}\""),
295 Constant::Bool(b) => write!(f, "{b}"),
296 Constant::Nil => write!(f, "nil"),
297 Constant::Duration(ms) => write!(f, "{ms}ms"),
298 }
299 }
300}
301
302#[derive(Debug)]
304pub struct Chunk {
305 cache_id: u64,
309 pub code: Vec<u8>,
311 pub constants: Vec<Constant>,
313 constant_index: Option<HashMap<ConstantKey, u16>>,
322 pub lines: Vec<u32>,
324 pub columns: Vec<u32>,
327 pub source_file: Option<String>,
332 current_col: u32,
334 pub functions: Vec<CompiledFunctionRef>,
336 inline_cache_slots: BTreeMap<usize, usize>,
342 inline_cache_index: Vec<u32>,
350 inline_caches: Arc<Mutex<Vec<InlineCacheEntry>>>,
354 constant_strings: Arc<Mutex<Vec<Option<crate::value::HarnStr>>>>,
358 pub(crate) local_slots: Vec<LocalSlotInfo>,
360 pub(crate) references_outer_names: bool,
376 #[cfg(debug_assertions)]
390 balance_depth: i32,
391 #[cfg(debug_assertions)]
392 balance_nonlinear: u32,
393}
394
395pub type ChunkRef = Arc<Chunk>;
396pub type CompiledFunctionRef = Arc<CompiledFunction>;
397
398impl Clone for Chunk {
399 fn clone(&self) -> Self {
400 Self {
401 cache_id: self.cache_id,
402 code: self.code.clone(),
403 constants: self.constants.clone(),
404 constant_index: self.constant_index.clone(),
405 lines: self.lines.clone(),
406 columns: self.columns.clone(),
407 source_file: self.source_file.clone(),
408 current_col: self.current_col,
409 functions: self.functions.clone(),
410 inline_cache_slots: self.inline_cache_slots.clone(),
411 inline_cache_index: self.inline_cache_index.clone(),
412 inline_caches: Arc::new(Mutex::new(vec![
413 InlineCacheEntry::Empty;
414 self.inline_cache_slot_count()
415 ])),
416 constant_strings: Arc::new(Mutex::new(vec![None; self.constants.len()])),
417 local_slots: self.local_slots.clone(),
418 references_outer_names: self.references_outer_names,
419 #[cfg(debug_assertions)]
420 balance_depth: self.balance_depth,
421 #[cfg(debug_assertions)]
422 balance_nonlinear: self.balance_nonlinear,
423 }
424 }
425}
426
427#[derive(Debug, Serialize, Deserialize)]
432pub struct CachedChunk {
433 pub(crate) code: Vec<u8>,
434 pub(crate) constants: Vec<Constant>,
435 pub(crate) lines: Vec<u32>,
436 pub(crate) columns: Vec<u32>,
437 pub(crate) source_file: Option<String>,
438 pub(crate) current_col: u32,
439 pub(crate) functions: Vec<CachedCompiledFunction>,
440 pub(crate) inline_cache_slots: BTreeMap<usize, usize>,
441 pub(crate) local_slots: Vec<LocalSlotInfo>,
442 #[serde(default)]
443 pub(crate) references_outer_names: bool,
444}
445
446#[derive(Debug, Serialize, Deserialize)]
447pub struct CachedCompiledFunction {
448 pub(crate) name: String,
449 pub(crate) type_params: Vec<String>,
450 pub(crate) nominal_type_names: Vec<String>,
451 pub(crate) params: Vec<CachedParamSlot>,
452 pub(crate) default_start: Option<usize>,
453 pub(crate) chunk: CachedChunk,
454 pub(crate) is_generator: bool,
455 pub(crate) is_stream: bool,
456 pub(crate) has_rest_param: bool,
457 pub(crate) has_runtime_type_checks: bool,
458}
459
460#[derive(Debug, Serialize, Deserialize)]
461pub(crate) struct CachedParamSlot {
462 pub(crate) name: String,
463 pub(crate) type_expr: Option<TypeExpr>,
464 pub(crate) has_default: bool,
465}
466
467impl CachedParamSlot {
468 fn thaw(self) -> ParamSlot {
469 let runtime_guard = self
470 .type_expr
471 .as_ref()
472 .map(RuntimeParamGuard::from_type_expr);
473 ParamSlot {
474 name: self.name,
475 type_expr: self.type_expr,
476 runtime_guard,
477 has_default: self.has_default,
478 }
479 }
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct ParamSlot {
489 pub name: String,
490 pub type_expr: Option<TypeExpr>,
493 #[serde(skip)]
496 pub(crate) runtime_guard: Option<RuntimeParamGuard>,
497 pub has_default: bool,
501}
502
503impl ParamSlot {
504 pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
507 Self::from_typed_param_with_type(param, param.type_expr.clone())
508 }
509
510 pub(crate) fn from_typed_param_with_type(
511 param: &harn_parser::TypedParam,
512 type_expr: Option<TypeExpr>,
513 ) -> Self {
514 let runtime_guard = type_expr.as_ref().map(RuntimeParamGuard::from_type_expr);
515 Self {
516 name: param.name.clone(),
517 type_expr,
518 runtime_guard,
519 has_default: param.default_value.is_some(),
520 }
521 }
522
523 fn freeze_for_cache(&self) -> CachedParamSlot {
524 CachedParamSlot {
525 name: self.name.clone(),
526 type_expr: self.type_expr.clone(),
527 has_default: self.has_default,
528 }
529 }
530
531 pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
536 params.iter().map(Self::from_typed_param).collect()
537 }
538}
539
540#[derive(Debug, Clone)]
542pub struct CompiledFunction {
543 pub name: String,
544 pub type_params: Vec<String>,
548 pub nominal_type_names: Vec<String>,
552 pub params: Vec<ParamSlot>,
553 pub default_start: Option<usize>,
555 pub chunk: ChunkRef,
556 pub is_generator: bool,
558 pub is_stream: bool,
560 pub has_rest_param: bool,
562 pub has_runtime_type_checks: bool,
567}
568
569impl CompiledFunction {
570 pub(crate) fn has_runtime_type_checks_for_params(params: &[ParamSlot]) -> bool {
571 params.iter().any(|param| param.type_expr.is_some())
572 }
573
574 pub fn param_names(&self) -> impl Iterator<Item = &str> {
577 self.params.iter().map(|p| p.name.as_str())
578 }
579
580 pub fn required_param_count(&self) -> usize {
582 self.default_start.unwrap_or(self.params.len())
583 }
584
585 pub(crate) fn minimum_arg_count(&self) -> usize {
587 if self.has_rest_param {
588 self.required_param_count()
589 .min(self.params.len().saturating_sub(1))
590 } else {
591 self.required_param_count()
592 }
593 }
594
595 pub(crate) fn callee_arg_count(&self, supplied: usize) -> usize {
597 if self.has_rest_param {
598 supplied
599 } else {
600 supplied.min(self.params.len())
601 }
602 }
603
604 pub fn declares_type_param(&self, name: &str) -> bool {
605 self.type_params.iter().any(|param| param == name)
606 }
607
608 pub fn has_nominal_type(&self, name: &str) -> bool {
609 self.nominal_type_names.iter().any(|ty| ty == name)
610 }
611
612 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
613 CachedCompiledFunction {
614 name: self.name.clone(),
615 type_params: self.type_params.clone(),
616 nominal_type_names: self.nominal_type_names.clone(),
617 params: self
618 .params
619 .iter()
620 .map(ParamSlot::freeze_for_cache)
621 .collect(),
622 default_start: self.default_start,
623 chunk: self.chunk.freeze_for_cache(),
624 is_generator: self.is_generator,
625 is_stream: self.is_stream,
626 has_rest_param: self.has_rest_param,
627 has_runtime_type_checks: self.has_runtime_type_checks,
628 }
629 }
630
631 pub(crate) fn from_cached(cached: CachedCompiledFunction) -> Self {
632 Self {
633 name: cached.name,
634 type_params: cached.type_params,
635 nominal_type_names: cached.nominal_type_names,
636 params: cached
637 .params
638 .into_iter()
639 .map(CachedParamSlot::thaw)
640 .collect(),
641 default_start: cached.default_start,
642 chunk: Arc::new(Chunk::from_cached(cached.chunk)),
643 is_generator: cached.is_generator,
644 is_stream: cached.is_stream,
645 has_rest_param: cached.has_rest_param,
646 has_runtime_type_checks: cached.has_runtime_type_checks,
647 }
648 }
649}
650
651#[cfg(debug_assertions)]
654#[derive(Clone, Copy)]
655pub(crate) struct BalanceProbe {
656 depth: i32,
657 nonlinear: u32,
658}
659
660#[cfg(debug_assertions)]
677fn op_stack_delta(op: Op, count: u16) -> Option<i32> {
678 use Op::*;
679 let count = count as i32;
680 Some(match op {
681 Constant | Nil | True | False | GetVar | GetArgc | GetLocalSlot | Closure | Dup => 1,
683 DefLet | DefVar | DefCell | SetVar | DefLocalSlot | SetLocalSlot | SetProperty
687 | SetLocalSlotProperty | ConcatAssignLocal | Pop => -1,
688 Negate | Not | GetProperty | GetPropertyOpt | CheckType | TryUnwrap | TryWrapOk | Swap
692 | PushScope | PopScope | PopIterator | PopHandler => 0,
693 Add | Sub | Mul | Div | Mod | Pow | AddInt | SubInt | MulInt | DivInt | ModInt
695 | AddFloat | SubFloat | MulFloat | DivFloat | ModFloat | Equal | NotEqual | Less
696 | Greater | LessEqual | GreaterEqual | EqualInt | NotEqualInt | LessInt | GreaterInt
697 | LessEqualInt | GreaterEqualInt | EqualFloat | NotEqualFloat | LessFloat
698 | GreaterFloat | LessEqualFloat | GreaterEqualFloat | EqualBool | NotEqualBool
699 | EqualString | NotEqualString | Contains | Subscript | SubscriptOpt => -1,
700 IterInit => -1,
703 Slice | SetSubscript | SetLocalSlotSubscript => -2,
706 BuildList | Concat | CallBuiltin => 1 - count,
708 BuildDict => 1 - 2 * count,
709 Call | MethodCall | MethodCallOpt => -count,
711 Jump | JumpIfFalse | JumpIfTrue | IterNext | Return | TailCall | Throw | TryCatchSetup
714 | Spawn | Pipe | Parallel | ParallelMap | ParallelMapStream | ParallelSettle
715 | SyncMutexEnter | SyncMutexEnterKeyed | TaskScopeEnter | TaskScopeExit | Import
716 | SelectiveImport | NamespaceImport | DeadlineSetup | DeadlineEnd | BuildEnum
717 | MatchEnum | Yield | CallSpread | CallBuiltinSpread | MethodCallSpread => return None,
718 })
719}
720
721impl Chunk {
722 pub fn new() -> Self {
723 Self {
724 cache_id: next_chunk_cache_id(),
725 code: Vec::new(),
726 constants: Vec::new(),
727 constant_index: Some(HashMap::new()),
728 lines: Vec::new(),
729 columns: Vec::new(),
730 source_file: None,
731 current_col: 0,
732 functions: Vec::new(),
733 inline_cache_slots: BTreeMap::new(),
734 inline_cache_index: Vec::new(),
735 inline_caches: Arc::new(Mutex::new(Vec::new())),
736 constant_strings: Arc::new(Mutex::new(Vec::new())),
737 local_slots: Vec::new(),
738 references_outer_names: false,
739 #[cfg(debug_assertions)]
740 balance_depth: 0,
741 #[cfg(debug_assertions)]
742 balance_nonlinear: 0,
743 }
744 }
745
746 pub fn set_column(&mut self, col: u32) {
748 self.current_col = col;
749 }
750
751 pub fn add_constant(&mut self, constant: Constant) -> u16 {
753 if self.constant_index.is_none() {
754 self.constant_index = Some(build_constant_index(&self.constants));
755 }
756 let index_map = self
757 .constant_index
758 .as_mut()
759 .expect("constant side index was just derived");
760 debug_assert!(
761 index_map.len() <= self.constants.len(),
762 "constant side index cannot outgrow the constant pool"
763 );
764 let key = ConstantKey::from(&constant);
765 if let Some(index) = index_map.get(&key) {
766 debug_assert!(
767 self.constants
768 .get(*index as usize)
769 .is_some_and(|existing| constants_identical(existing, &constant)),
770 "constant side index drifted from the constant pool"
771 );
772 return *index;
773 }
774 let idx = self.constants.len();
775 let idx = u16::try_from(idx).expect("constant pool exceeded u16 operand space");
776 index_map.insert(key, idx);
777 self.constants.push(constant);
778 idx
779 }
780
781 pub fn emit(&mut self, op: Op, line: u32) {
783 #[cfg(debug_assertions)]
784 self.note_balance(op, 0);
785 let col = self.current_col;
786 let op_offset = self.code.len();
787 self.code.push(op as u8);
788 self.lines.push(line);
789 self.columns.push(col);
790 if is_adaptive_binary_op(op) {
791 self.register_inline_cache(op_offset);
792 }
793 if op_reads_outer_name(op) {
794 self.references_outer_names = true;
795 }
796 }
797
798 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
800 #[cfg(debug_assertions)]
801 self.note_balance(op, arg);
802 let col = self.current_col;
803 let op_offset = self.code.len();
804 self.code.push(op as u8);
805 self.code.push((arg >> 8) as u8);
806 self.code.push((arg & 0xFF) as u8);
807 self.lines.push(line);
808 self.lines.push(line);
809 self.lines.push(line);
810 self.columns.push(col);
811 self.columns.push(col);
812 self.columns.push(col);
813 if matches!(
814 op,
815 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread | Op::ConcatAssignLocal
816 ) {
817 self.register_inline_cache(op_offset);
818 }
819 if op_reads_outer_name(op) {
820 self.references_outer_names = true;
821 }
822 }
823
824 pub fn emit_set_local_slot_property(&mut self, prop_idx: u16, slot: u16, line: u32) {
827 #[cfg(debug_assertions)]
828 self.note_balance(Op::SetLocalSlotProperty, 0);
829 let col = self.current_col;
830 self.code.push(Op::SetLocalSlotProperty as u8);
831 self.code.push((prop_idx >> 8) as u8);
832 self.code.push((prop_idx & 0xFF) as u8);
833 self.code.push((slot >> 8) as u8);
834 self.code.push((slot & 0xFF) as u8);
835 for _ in 0..5 {
836 self.lines.push(line);
837 self.columns.push(col);
838 }
839 }
840
841 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
843 #[cfg(debug_assertions)]
844 self.note_balance(op, arg as u16);
845 let col = self.current_col;
846 let op_offset = self.code.len();
847 self.code.push(op as u8);
848 self.code.push(arg);
849 self.lines.push(line);
850 self.lines.push(line);
851 self.columns.push(col);
852 self.columns.push(col);
853 if matches!(op, Op::Call) {
854 self.register_inline_cache(op_offset);
855 }
856 if op_reads_outer_name(op) {
857 self.references_outer_names = true;
858 }
859 }
860
861 pub fn emit_call_builtin(
863 &mut self,
864 id: crate::BuiltinId,
865 name_idx: u16,
866 arg_count: u8,
867 line: u32,
868 ) {
869 #[cfg(debug_assertions)]
870 self.note_balance(Op::CallBuiltin, arg_count as u16);
871 let col = self.current_col;
872 let op_offset = self.code.len();
873 self.code.push(Op::CallBuiltin as u8);
874 self.code.extend_from_slice(&id.raw().to_be_bytes());
875 self.code.push((name_idx >> 8) as u8);
876 self.code.push((name_idx & 0xFF) as u8);
877 self.code.push(arg_count);
878 for _ in 0..12 {
879 self.lines.push(line);
880 self.columns.push(col);
881 }
882 self.register_inline_cache(op_offset);
883 self.references_outer_names = true;
884 }
885
886 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
888 #[cfg(debug_assertions)]
889 self.note_balance(Op::CallBuiltinSpread, 0);
890 let col = self.current_col;
891 self.code.push(Op::CallBuiltinSpread as u8);
892 self.code.extend_from_slice(&id.raw().to_be_bytes());
893 self.code.push((name_idx >> 8) as u8);
894 self.code.push((name_idx & 0xFF) as u8);
895 for _ in 0..11 {
896 self.lines.push(line);
897 self.columns.push(col);
898 }
899 self.references_outer_names = true;
900 }
901
902 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
904 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
905 }
906
907 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
909 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
910 }
911
912 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
913 #[cfg(debug_assertions)]
914 self.note_balance(op, arg_count as u16);
915 let col = self.current_col;
916 let op_offset = self.code.len();
917 self.code.push(op as u8);
918 self.code.push((name_idx >> 8) as u8);
919 self.code.push((name_idx & 0xFF) as u8);
920 self.code.push(arg_count);
921 self.lines.push(line);
922 self.lines.push(line);
923 self.lines.push(line);
924 self.lines.push(line);
925 self.columns.push(col);
926 self.columns.push(col);
927 self.columns.push(col);
928 self.columns.push(col);
929 self.register_inline_cache(op_offset);
930 }
931
932 pub fn current_offset(&self) -> usize {
934 self.code.len()
935 }
936
937 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
939 #[cfg(debug_assertions)]
940 self.note_balance(op, 0);
941 let col = self.current_col;
942 self.code.push(op as u8);
943 let patch_pos = self.code.len();
944 self.code.push(0xFF);
945 self.code.push(0xFF);
946 self.lines.push(line);
947 self.lines.push(line);
948 self.lines.push(line);
949 self.columns.push(col);
950 self.columns.push(col);
951 self.columns.push(col);
952 patch_pos
953 }
954
955 pub fn patch_jump(&mut self, patch_pos: usize) {
957 let target = self.code.len() as u16;
958 self.code[patch_pos] = (target >> 8) as u8;
959 self.code[patch_pos + 1] = (target & 0xFF) as u8;
960 }
961
962 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
964 let target = target as u16;
965 self.code[patch_pos] = (target >> 8) as u8;
966 self.code[patch_pos + 1] = (target & 0xFF) as u8;
967 }
968
969 pub fn read_u16(&self, pos: usize) -> u16 {
971 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
972 }
973
974 #[cfg(debug_assertions)]
978 fn note_balance(&mut self, op: Op, count: u16) {
979 match op_stack_delta(op, count) {
980 Some(delta) => self.balance_depth += delta,
981 None => self.balance_nonlinear += 1,
982 }
983 }
984
985 #[cfg(debug_assertions)]
988 pub(crate) fn balance_probe(&self) -> BalanceProbe {
989 BalanceProbe {
990 depth: self.balance_depth,
991 nonlinear: self.balance_nonlinear,
992 }
993 }
994
995 #[cfg(debug_assertions)]
1001 pub(crate) fn balance_delta_since(&self, probe: BalanceProbe) -> Option<i32> {
1002 if self.balance_nonlinear == probe.nonlinear {
1003 Some(self.balance_depth - probe.depth)
1004 } else {
1005 None
1006 }
1007 }
1008
1009 fn register_inline_cache(&mut self, op_offset: usize) {
1010 if self.inline_cache_slots.contains_key(&op_offset) {
1011 return;
1012 }
1013 let mut entries = self.inline_caches.lock();
1014 let slot = entries.len();
1015 entries.push(InlineCacheEntry::Empty);
1016 self.inline_cache_slots.insert(op_offset, slot);
1017 Self::write_inline_cache_index(&mut self.inline_cache_index, op_offset, slot);
1018 }
1019
1020 fn write_inline_cache_index(index: &mut Vec<u32>, op_offset: usize, slot: usize) {
1025 if op_offset >= index.len() {
1026 index.resize(op_offset + 1, NO_INLINE_CACHE_SLOT);
1027 }
1028 index[op_offset] = slot as u32;
1029 }
1030
1031 #[inline]
1040 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
1041 match self.inline_cache_index.get(op_offset).copied() {
1042 None | Some(NO_INLINE_CACHE_SLOT) => None,
1043 Some(slot) => Some(slot as usize),
1044 }
1045 }
1046
1047 pub(crate) fn inline_cache_slot_count(&self) -> usize {
1048 self.inline_cache_slots.len()
1049 }
1050
1051 pub(crate) fn cache_id(&self) -> u64 {
1052 self.cache_id
1053 }
1054
1055 #[cfg(feature = "vm-bench-internals")]
1062 pub fn inline_cache_slot_via_btreemap_for_bench(&self, op_offset: usize) -> Option<usize> {
1063 self.inline_cache_slots.get(&op_offset).copied()
1064 }
1065
1066 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<crate::value::HarnStr> {
1071 let mut entries = self.constant_strings.lock();
1076 if entries.len() < self.constants.len() {
1077 entries.resize(self.constants.len(), None);
1078 }
1079 if let Some(Some(existing)) = entries.get(idx) {
1080 return Some(existing.clone());
1081 }
1082 let materialized = match self.constants.get(idx)? {
1083 Constant::String(s) => crate::value::HarnStr::from(s.as_str()),
1084 _ => return None,
1085 };
1086 entries[idx] = Some(materialized.clone());
1087 Some(materialized)
1088 }
1089
1090 #[inline]
1093 #[cfg(test)]
1094 pub(crate) fn peek_adaptive_binary_cache(
1095 &self,
1096 slot: usize,
1097 ) -> Option<(AdaptiveBinaryOp, AdaptiveBinaryState)> {
1098 match self.inline_caches.lock().get(slot)? {
1099 &InlineCacheEntry::AdaptiveBinary { op, state } => Some((op, state)),
1100 _ => None,
1101 }
1102 }
1103
1104 #[inline]
1107 #[cfg(test)]
1108 pub(crate) fn peek_method_cache(&self, slot: usize) -> Option<(u16, usize, MethodCacheTarget)> {
1109 match self.inline_caches.lock().get(slot)? {
1110 &InlineCacheEntry::Method {
1111 name_idx,
1112 argc,
1113 target,
1114 } => Some((name_idx, argc, target)),
1115 _ => None,
1116 }
1117 }
1118
1119 #[inline]
1122 #[cfg(test)]
1123 pub(crate) fn peek_property_cache(&self, slot: usize) -> Option<(u16, PropertyCacheTarget)> {
1124 match self.inline_caches.lock().get(slot)? {
1125 InlineCacheEntry::Property { name_idx, target } => Some((*name_idx, target.clone())),
1126 _ => None,
1127 }
1128 }
1129
1130 #[inline]
1133 #[cfg(test)]
1134 pub(crate) fn peek_direct_call_state(&self, slot: usize) -> Option<DirectCallState> {
1135 match self.inline_caches.lock().get(slot)? {
1136 InlineCacheEntry::DirectCall { state } => Some(state.clone()),
1137 _ => None,
1138 }
1139 }
1140
1141 #[cfg(test)]
1142 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1143 if let Some(existing) = self.inline_caches.lock().get_mut(slot) {
1144 *existing = entry;
1145 }
1146 }
1147
1148 pub fn freeze_for_cache(&self) -> CachedChunk {
1149 CachedChunk {
1150 code: self.code.clone(),
1151 constants: self.constants.clone(),
1152 lines: self.lines.clone(),
1153 columns: self.columns.clone(),
1154 source_file: self.source_file.clone(),
1155 current_col: self.current_col,
1156 functions: self
1157 .functions
1158 .iter()
1159 .map(|function| function.freeze_for_cache())
1160 .collect(),
1161 inline_cache_slots: self.inline_cache_slots.clone(),
1162 local_slots: self.local_slots.clone(),
1163 references_outer_names: self.references_outer_names,
1164 }
1165 }
1166
1167 pub fn from_cached(cached: CachedChunk) -> Self {
1168 let CachedChunk {
1169 code,
1170 constants,
1171 lines,
1172 columns,
1173 source_file,
1174 current_col,
1175 functions,
1176 inline_cache_slots,
1177 local_slots,
1178 references_outer_names,
1179 } = cached;
1180 let inline_cache_count = inline_cache_slots.len();
1181 let constants_count = constants.len();
1182 let mut inline_cache_index = Vec::new();
1189 inline_cache_index.resize(code.len(), NO_INLINE_CACHE_SLOT);
1190 for (&op_offset, &slot) in &inline_cache_slots {
1191 if op_offset < inline_cache_index.len() {
1192 inline_cache_index[op_offset] = slot as u32;
1193 }
1194 }
1195 Self {
1196 cache_id: next_chunk_cache_id(),
1197 code,
1198 constants,
1199 constant_index: None,
1201 lines,
1202 columns,
1203 source_file,
1204 current_col,
1205 functions: functions
1206 .into_iter()
1207 .map(|function| Arc::new(CompiledFunction::from_cached(function)))
1208 .collect(),
1209 inline_cache_slots,
1210 inline_cache_index,
1211 inline_caches: Arc::new(Mutex::new(vec![
1212 InlineCacheEntry::Empty;
1213 inline_cache_count
1214 ])),
1215 constant_strings: Arc::new(Mutex::new(vec![None; constants_count])),
1216 local_slots,
1217 references_outer_names,
1218 #[cfg(debug_assertions)]
1219 balance_depth: 0,
1220 #[cfg(debug_assertions)]
1221 balance_nonlinear: 0,
1222 }
1223 }
1224
1225 pub(crate) fn add_local_slot(
1226 &mut self,
1227 name: String,
1228 mutable: bool,
1229 scope_depth: usize,
1230 ) -> u16 {
1231 let idx = self.local_slots.len();
1232 self.local_slots.push(LocalSlotInfo {
1233 name,
1234 mutable,
1235 scope_depth,
1236 });
1237 idx as u16
1238 }
1239
1240 pub fn read_u64(&self, pos: usize) -> u64 {
1242 u64::from_be_bytes([
1243 self.code[pos],
1244 self.code[pos + 1],
1245 self.code[pos + 2],
1246 self.code[pos + 3],
1247 self.code[pos + 4],
1248 self.code[pos + 5],
1249 self.code[pos + 6],
1250 self.code[pos + 7],
1251 ])
1252 }
1253
1254 pub fn disassemble(&self, name: &str) -> String {
1258 let mut out = format!("== {name} ==\n");
1259 let mut ip = 0;
1260 while ip < self.code.len() {
1261 let op_byte = self.code[ip];
1262 let line = self.lines.get(ip).copied().unwrap_or(0);
1263 out.push_str(&format!("{ip:04} [{line:>4}] "));
1264 ip += 1;
1265
1266 if let Some(op) = Op::from_byte(op_byte) {
1267 self.disassemble_op(op, &mut ip, &mut out);
1268 } else {
1269 out.push_str(&format!("UNKNOWN(0x{op_byte:02x})\n"));
1270 }
1271 }
1272 out
1273 }
1274}
1275
1276pub(crate) fn disasm_bare(_chunk: &Chunk, _ip: &mut usize, label: &str) -> String {
1287 label.to_string()
1288}
1289
1290pub(crate) fn disasm_u8(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1291 let arg = chunk.code[*ip];
1292 *ip += 1;
1293 format!("{label} {arg:>4}")
1294}
1295
1296pub(crate) fn disasm_u16(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1297 let arg = chunk.read_u16(*ip);
1298 *ip += 2;
1299 format!("{label} {arg:>4}")
1300}
1301
1302pub(crate) fn disasm_try_catch_setup(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1303 let catch_offset = chunk.read_u16(*ip);
1304 *ip += 2;
1305 let type_idx = chunk.read_u16(*ip);
1306 *ip += 2;
1307 if let Some(type_name) = chunk.constants.get(type_idx as usize) {
1308 format!("{label} {catch_offset:>4} type {type_idx:>4} ({type_name})")
1309 } else {
1310 format!("{label} {catch_offset:>4} type {type_idx:>4}")
1311 }
1312}
1313
1314pub(crate) fn disasm_const_pool_u16(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1315 let idx = chunk.read_u16(*ip);
1316 *ip += 2;
1317 format!("{label} {idx:>4} ({})", chunk.constants[idx as usize])
1318}
1319
1320pub(crate) fn disasm_local_slot_u16(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1321 let slot = chunk.read_u16(*ip);
1322 *ip += 2;
1323 let mut out = format!("{label} {slot:>4}");
1324 if let Some(info) = chunk.local_slots.get(slot as usize) {
1325 out.push_str(&format!(" ({})", info.name));
1326 }
1327 out
1328}
1329
1330pub(crate) fn disasm_const_pool_local_slot(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1331 let prop = chunk.read_u16(*ip);
1332 *ip += 2;
1333 let slot = chunk.read_u16(*ip);
1334 *ip += 2;
1335 let mut out = format!(
1336 "{label} prop {prop:>4} ({}) slot {slot:>4}",
1337 chunk.constants[prop as usize]
1338 );
1339 if let Some(info) = chunk.local_slots.get(slot as usize) {
1340 out.push_str(&format!(" ({})", info.name));
1341 }
1342 out
1343}
1344
1345pub(crate) fn disasm_method_call(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1346 let idx = chunk.read_u16(*ip);
1347 *ip += 2;
1348 let argc = chunk.code[*ip];
1349 *ip += 1;
1350 format!(
1351 "{label} {idx:>4} ({}) argc={argc}",
1352 chunk.constants[idx as usize]
1353 )
1354}
1355
1356pub(crate) fn disasm_match_enum(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1357 let enum_idx = chunk.read_u16(*ip);
1358 *ip += 2;
1359 let var_idx = chunk.read_u16(*ip);
1360 *ip += 2;
1361 format!(
1362 "{label} {enum_idx:>4} ({}) {var_idx:>4} ({})",
1363 chunk.constants[enum_idx as usize], chunk.constants[var_idx as usize],
1364 )
1365}
1366
1367pub(crate) fn disasm_build_enum(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1368 let enum_idx = chunk.read_u16(*ip);
1369 *ip += 2;
1370 let var_idx = chunk.read_u16(*ip);
1371 *ip += 2;
1372 let field_count = chunk.read_u16(*ip);
1373 *ip += 2;
1374 format!(
1375 "{label} {enum_idx:>4} ({}) {var_idx:>4} ({}) fields={field_count}",
1376 chunk.constants[enum_idx as usize], chunk.constants[var_idx as usize],
1377 )
1378}
1379
1380pub(crate) fn disasm_selective_import(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1381 let path_idx = chunk.read_u16(*ip);
1382 *ip += 2;
1383 let names_idx = chunk.read_u16(*ip);
1384 *ip += 2;
1385 format!(
1386 "{label} {path_idx:>4} ({}) names: {names_idx:>4} ({})",
1387 chunk.constants[path_idx as usize], chunk.constants[names_idx as usize],
1388 )
1389}
1390
1391pub(crate) fn disasm_check_type(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1392 let var_idx = chunk.read_u16(*ip);
1393 *ip += 2;
1394 let type_idx = chunk.read_u16(*ip);
1395 *ip += 2;
1396 format!(
1397 "{label} {var_idx:>4} ({}) -> {type_idx:>4} ({})",
1398 chunk.constants[var_idx as usize], chunk.constants[type_idx as usize],
1399 )
1400}
1401
1402pub(crate) fn disasm_call_builtin(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1403 let id = chunk.read_u64(*ip);
1404 *ip += 8;
1405 let idx = chunk.read_u16(*ip);
1406 *ip += 2;
1407 let argc = chunk.code[*ip];
1408 *ip += 1;
1409 format!(
1410 "{label} {id:#018x} {idx:>4} ({}) argc={argc}",
1411 chunk.constants[idx as usize],
1412 )
1413}
1414
1415pub(crate) fn disasm_call_builtin_spread(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1416 let id = chunk.read_u64(*ip);
1417 *ip += 8;
1418 let idx = chunk.read_u16(*ip);
1419 *ip += 2;
1420 format!(
1421 "{label} {id:#018x} {idx:>4} ({})",
1422 chunk.constants[idx as usize],
1423 )
1424}
1425
1426pub(crate) fn disasm_method_call_spread(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1427 let idx = chunk.read_u16(*ip);
1433 *ip += 2;
1434 format!("{label} {idx:>4} ({})", chunk.constants[idx as usize])
1435}
1436
1437impl Default for Chunk {
1438 fn default() -> Self {
1439 Self::new()
1440 }
1441}
1442
1443#[cfg(test)]
1444#[path = "chunk_tests.rs"]
1445mod tests;