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: HashMap<ConstantKey, u16>,
317 pub lines: Vec<u32>,
319 pub columns: Vec<u32>,
322 pub source_file: Option<String>,
327 current_col: u32,
329 pub functions: Vec<CompiledFunctionRef>,
331 inline_cache_slots: BTreeMap<usize, usize>,
337 inline_cache_index: Vec<u32>,
345 inline_caches: Arc<Mutex<Vec<InlineCacheEntry>>>,
349 constant_strings: Arc<Mutex<Vec<Option<crate::value::HarnStr>>>>,
353 pub(crate) local_slots: Vec<LocalSlotInfo>,
355 pub(crate) references_outer_names: bool,
371 #[cfg(debug_assertions)]
385 balance_depth: i32,
386 #[cfg(debug_assertions)]
387 balance_nonlinear: u32,
388}
389
390pub type ChunkRef = Arc<Chunk>;
391pub type CompiledFunctionRef = Arc<CompiledFunction>;
392
393impl Clone for Chunk {
394 fn clone(&self) -> Self {
395 Self {
396 cache_id: self.cache_id,
397 code: self.code.clone(),
398 constants: self.constants.clone(),
399 constant_index: self.constant_index.clone(),
400 lines: self.lines.clone(),
401 columns: self.columns.clone(),
402 source_file: self.source_file.clone(),
403 current_col: self.current_col,
404 functions: self.functions.clone(),
405 inline_cache_slots: self.inline_cache_slots.clone(),
406 inline_cache_index: self.inline_cache_index.clone(),
407 inline_caches: Arc::new(Mutex::new(vec![
408 InlineCacheEntry::Empty;
409 self.inline_cache_slot_count()
410 ])),
411 constant_strings: Arc::new(Mutex::new(vec![None; self.constants.len()])),
412 local_slots: self.local_slots.clone(),
413 references_outer_names: self.references_outer_names,
414 #[cfg(debug_assertions)]
415 balance_depth: self.balance_depth,
416 #[cfg(debug_assertions)]
417 balance_nonlinear: self.balance_nonlinear,
418 }
419 }
420}
421
422#[derive(Debug, Serialize, Deserialize)]
427pub struct CachedChunk {
428 pub(crate) code: Vec<u8>,
429 pub(crate) constants: Vec<Constant>,
430 pub(crate) lines: Vec<u32>,
431 pub(crate) columns: Vec<u32>,
432 pub(crate) source_file: Option<String>,
433 pub(crate) current_col: u32,
434 pub(crate) functions: Vec<CachedCompiledFunction>,
435 pub(crate) inline_cache_slots: BTreeMap<usize, usize>,
436 pub(crate) local_slots: Vec<LocalSlotInfo>,
437 #[serde(default)]
438 pub(crate) references_outer_names: bool,
439}
440
441#[derive(Debug, Serialize, Deserialize)]
442pub struct CachedCompiledFunction {
443 pub(crate) name: String,
444 pub(crate) type_params: Vec<String>,
445 pub(crate) nominal_type_names: Vec<String>,
446 pub(crate) params: Vec<CachedParamSlot>,
447 pub(crate) default_start: Option<usize>,
448 pub(crate) chunk: CachedChunk,
449 pub(crate) is_generator: bool,
450 pub(crate) is_stream: bool,
451 pub(crate) has_rest_param: bool,
452 pub(crate) has_runtime_type_checks: bool,
453}
454
455#[derive(Debug, Serialize, Deserialize)]
456pub(crate) struct CachedParamSlot {
457 pub(crate) name: String,
458 pub(crate) type_expr: Option<TypeExpr>,
459 pub(crate) has_default: bool,
460}
461
462impl CachedParamSlot {
463 fn thaw(self) -> ParamSlot {
464 let runtime_guard = self
465 .type_expr
466 .as_ref()
467 .map(RuntimeParamGuard::from_type_expr);
468 ParamSlot {
469 name: self.name,
470 type_expr: self.type_expr,
471 runtime_guard,
472 has_default: self.has_default,
473 }
474 }
475}
476
477#[derive(Debug, Clone, Serialize, Deserialize)]
483pub struct ParamSlot {
484 pub name: String,
485 pub type_expr: Option<TypeExpr>,
488 #[serde(skip)]
491 pub(crate) runtime_guard: Option<RuntimeParamGuard>,
492 pub has_default: bool,
496}
497
498impl ParamSlot {
499 pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
502 Self::from_typed_param_with_type(param, param.type_expr.clone())
503 }
504
505 pub(crate) fn from_typed_param_with_type(
506 param: &harn_parser::TypedParam,
507 type_expr: Option<TypeExpr>,
508 ) -> Self {
509 let runtime_guard = type_expr.as_ref().map(RuntimeParamGuard::from_type_expr);
510 Self {
511 name: param.name.clone(),
512 type_expr,
513 runtime_guard,
514 has_default: param.default_value.is_some(),
515 }
516 }
517
518 fn freeze_for_cache(&self) -> CachedParamSlot {
519 CachedParamSlot {
520 name: self.name.clone(),
521 type_expr: self.type_expr.clone(),
522 has_default: self.has_default,
523 }
524 }
525
526 pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
531 params.iter().map(Self::from_typed_param).collect()
532 }
533}
534
535#[derive(Debug, Clone)]
537pub struct CompiledFunction {
538 pub name: String,
539 pub type_params: Vec<String>,
543 pub nominal_type_names: Vec<String>,
547 pub params: Vec<ParamSlot>,
548 pub default_start: Option<usize>,
550 pub chunk: ChunkRef,
551 pub is_generator: bool,
553 pub is_stream: bool,
555 pub has_rest_param: bool,
557 pub has_runtime_type_checks: bool,
562}
563
564impl CompiledFunction {
565 pub(crate) fn has_runtime_type_checks_for_params(params: &[ParamSlot]) -> bool {
566 params.iter().any(|param| param.type_expr.is_some())
567 }
568
569 pub fn param_names(&self) -> impl Iterator<Item = &str> {
572 self.params.iter().map(|p| p.name.as_str())
573 }
574
575 pub fn required_param_count(&self) -> usize {
577 self.default_start.unwrap_or(self.params.len())
578 }
579
580 pub(crate) fn minimum_arg_count(&self) -> usize {
582 if self.has_rest_param {
583 self.required_param_count()
584 .min(self.params.len().saturating_sub(1))
585 } else {
586 self.required_param_count()
587 }
588 }
589
590 pub(crate) fn callee_arg_count(&self, supplied: usize) -> usize {
592 if self.has_rest_param {
593 supplied
594 } else {
595 supplied.min(self.params.len())
596 }
597 }
598
599 pub fn declares_type_param(&self, name: &str) -> bool {
600 self.type_params.iter().any(|param| param == name)
601 }
602
603 pub fn has_nominal_type(&self, name: &str) -> bool {
604 self.nominal_type_names.iter().any(|ty| ty == name)
605 }
606
607 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
608 CachedCompiledFunction {
609 name: self.name.clone(),
610 type_params: self.type_params.clone(),
611 nominal_type_names: self.nominal_type_names.clone(),
612 params: self
613 .params
614 .iter()
615 .map(ParamSlot::freeze_for_cache)
616 .collect(),
617 default_start: self.default_start,
618 chunk: self.chunk.freeze_for_cache(),
619 is_generator: self.is_generator,
620 is_stream: self.is_stream,
621 has_rest_param: self.has_rest_param,
622 has_runtime_type_checks: self.has_runtime_type_checks,
623 }
624 }
625
626 pub(crate) fn from_cached(cached: CachedCompiledFunction) -> Self {
627 Self {
628 name: cached.name,
629 type_params: cached.type_params,
630 nominal_type_names: cached.nominal_type_names,
631 params: cached
632 .params
633 .into_iter()
634 .map(CachedParamSlot::thaw)
635 .collect(),
636 default_start: cached.default_start,
637 chunk: Arc::new(Chunk::from_cached(cached.chunk)),
638 is_generator: cached.is_generator,
639 is_stream: cached.is_stream,
640 has_rest_param: cached.has_rest_param,
641 has_runtime_type_checks: cached.has_runtime_type_checks,
642 }
643 }
644}
645
646#[cfg(debug_assertions)]
649#[derive(Clone, Copy)]
650pub(crate) struct BalanceProbe {
651 depth: i32,
652 nonlinear: u32,
653}
654
655#[cfg(debug_assertions)]
672fn op_stack_delta(op: Op, count: u16) -> Option<i32> {
673 use Op::*;
674 let count = count as i32;
675 Some(match op {
676 Constant | Nil | True | False | GetVar | GetArgc | GetLocalSlot | Closure | Dup => 1,
678 DefLet | DefVar | DefCell | SetVar | DefLocalSlot | SetLocalSlot | SetProperty
682 | SetLocalSlotProperty | ConcatAssignLocal | Pop => -1,
683 Negate | Not | GetProperty | GetPropertyOpt | CheckType | TryUnwrap | TryWrapOk | Swap
687 | PushScope | PopScope | PopIterator | PopHandler => 0,
688 Add | Sub | Mul | Div | Mod | Pow | AddInt | SubInt | MulInt | DivInt | ModInt
690 | AddFloat | SubFloat | MulFloat | DivFloat | ModFloat | Equal | NotEqual | Less
691 | Greater | LessEqual | GreaterEqual | EqualInt | NotEqualInt | LessInt | GreaterInt
692 | LessEqualInt | GreaterEqualInt | EqualFloat | NotEqualFloat | LessFloat
693 | GreaterFloat | LessEqualFloat | GreaterEqualFloat | EqualBool | NotEqualBool
694 | EqualString | NotEqualString | Contains | Subscript | SubscriptOpt => -1,
695 IterInit => -1,
698 Slice | SetSubscript | SetLocalSlotSubscript => -2,
701 BuildList | Concat | CallBuiltin => 1 - count,
703 BuildDict => 1 - 2 * count,
704 Call | MethodCall | MethodCallOpt => -count,
706 Jump | JumpIfFalse | JumpIfTrue | IterNext | Return | TailCall | Throw | TryCatchSetup
709 | Spawn | Pipe | Parallel | ParallelMap | ParallelMapStream | ParallelSettle
710 | SyncMutexEnter | SyncMutexEnterKeyed | TaskScopeEnter | TaskScopeExit | Import
711 | SelectiveImport | DeadlineSetup | DeadlineEnd | BuildEnum | MatchEnum | Yield
712 | CallSpread | CallBuiltinSpread | MethodCallSpread => return None,
713 })
714}
715
716impl Chunk {
717 pub fn new() -> Self {
718 Self {
719 cache_id: next_chunk_cache_id(),
720 code: Vec::new(),
721 constants: Vec::new(),
722 constant_index: HashMap::new(),
723 lines: Vec::new(),
724 columns: Vec::new(),
725 source_file: None,
726 current_col: 0,
727 functions: Vec::new(),
728 inline_cache_slots: BTreeMap::new(),
729 inline_cache_index: Vec::new(),
730 inline_caches: Arc::new(Mutex::new(Vec::new())),
731 constant_strings: Arc::new(Mutex::new(Vec::new())),
732 local_slots: Vec::new(),
733 references_outer_names: false,
734 #[cfg(debug_assertions)]
735 balance_depth: 0,
736 #[cfg(debug_assertions)]
737 balance_nonlinear: 0,
738 }
739 }
740
741 pub fn set_column(&mut self, col: u32) {
743 self.current_col = col;
744 }
745
746 pub fn add_constant(&mut self, constant: Constant) -> u16 {
748 debug_assert!(
749 self.constant_index.len() <= self.constants.len(),
750 "constant side index cannot outgrow the constant pool"
751 );
752 let key = ConstantKey::from(&constant);
753 if let Some(index) = self.constant_index.get(&key) {
754 debug_assert!(
755 self.constants
756 .get(*index as usize)
757 .is_some_and(|existing| constants_identical(existing, &constant)),
758 "constant side index drifted from the constant pool"
759 );
760 return *index;
761 }
762 let idx = self.constants.len();
763 let idx = u16::try_from(idx).expect("constant pool exceeded u16 operand space");
764 self.constants.push(constant);
765 self.constant_index.insert(key, idx);
766 idx
767 }
768
769 pub fn emit(&mut self, op: Op, line: u32) {
771 #[cfg(debug_assertions)]
772 self.note_balance(op, 0);
773 let col = self.current_col;
774 let op_offset = self.code.len();
775 self.code.push(op as u8);
776 self.lines.push(line);
777 self.columns.push(col);
778 if is_adaptive_binary_op(op) {
779 self.register_inline_cache(op_offset);
780 }
781 if op_reads_outer_name(op) {
782 self.references_outer_names = true;
783 }
784 }
785
786 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
788 #[cfg(debug_assertions)]
789 self.note_balance(op, arg);
790 let col = self.current_col;
791 let op_offset = self.code.len();
792 self.code.push(op as u8);
793 self.code.push((arg >> 8) as u8);
794 self.code.push((arg & 0xFF) as u8);
795 self.lines.push(line);
796 self.lines.push(line);
797 self.lines.push(line);
798 self.columns.push(col);
799 self.columns.push(col);
800 self.columns.push(col);
801 if matches!(
802 op,
803 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread | Op::ConcatAssignLocal
804 ) {
805 self.register_inline_cache(op_offset);
806 }
807 if op_reads_outer_name(op) {
808 self.references_outer_names = true;
809 }
810 }
811
812 pub fn emit_set_local_slot_property(&mut self, prop_idx: u16, slot: u16, line: u32) {
815 #[cfg(debug_assertions)]
816 self.note_balance(Op::SetLocalSlotProperty, 0);
817 let col = self.current_col;
818 self.code.push(Op::SetLocalSlotProperty as u8);
819 self.code.push((prop_idx >> 8) as u8);
820 self.code.push((prop_idx & 0xFF) as u8);
821 self.code.push((slot >> 8) as u8);
822 self.code.push((slot & 0xFF) as u8);
823 for _ in 0..5 {
824 self.lines.push(line);
825 self.columns.push(col);
826 }
827 }
828
829 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
831 #[cfg(debug_assertions)]
832 self.note_balance(op, arg as u16);
833 let col = self.current_col;
834 let op_offset = self.code.len();
835 self.code.push(op as u8);
836 self.code.push(arg);
837 self.lines.push(line);
838 self.lines.push(line);
839 self.columns.push(col);
840 self.columns.push(col);
841 if matches!(op, Op::Call) {
842 self.register_inline_cache(op_offset);
843 }
844 if op_reads_outer_name(op) {
845 self.references_outer_names = true;
846 }
847 }
848
849 pub fn emit_call_builtin(
851 &mut self,
852 id: crate::BuiltinId,
853 name_idx: u16,
854 arg_count: u8,
855 line: u32,
856 ) {
857 #[cfg(debug_assertions)]
858 self.note_balance(Op::CallBuiltin, arg_count as u16);
859 let col = self.current_col;
860 let op_offset = self.code.len();
861 self.code.push(Op::CallBuiltin as u8);
862 self.code.extend_from_slice(&id.raw().to_be_bytes());
863 self.code.push((name_idx >> 8) as u8);
864 self.code.push((name_idx & 0xFF) as u8);
865 self.code.push(arg_count);
866 for _ in 0..12 {
867 self.lines.push(line);
868 self.columns.push(col);
869 }
870 self.register_inline_cache(op_offset);
871 self.references_outer_names = true;
872 }
873
874 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
876 #[cfg(debug_assertions)]
877 self.note_balance(Op::CallBuiltinSpread, 0);
878 let col = self.current_col;
879 self.code.push(Op::CallBuiltinSpread as u8);
880 self.code.extend_from_slice(&id.raw().to_be_bytes());
881 self.code.push((name_idx >> 8) as u8);
882 self.code.push((name_idx & 0xFF) as u8);
883 for _ in 0..11 {
884 self.lines.push(line);
885 self.columns.push(col);
886 }
887 self.references_outer_names = true;
888 }
889
890 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
892 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
893 }
894
895 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
897 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
898 }
899
900 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
901 #[cfg(debug_assertions)]
902 self.note_balance(op, arg_count as u16);
903 let col = self.current_col;
904 let op_offset = self.code.len();
905 self.code.push(op as u8);
906 self.code.push((name_idx >> 8) as u8);
907 self.code.push((name_idx & 0xFF) as u8);
908 self.code.push(arg_count);
909 self.lines.push(line);
910 self.lines.push(line);
911 self.lines.push(line);
912 self.lines.push(line);
913 self.columns.push(col);
914 self.columns.push(col);
915 self.columns.push(col);
916 self.columns.push(col);
917 self.register_inline_cache(op_offset);
918 }
919
920 pub fn current_offset(&self) -> usize {
922 self.code.len()
923 }
924
925 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
927 #[cfg(debug_assertions)]
928 self.note_balance(op, 0);
929 let col = self.current_col;
930 self.code.push(op as u8);
931 let patch_pos = self.code.len();
932 self.code.push(0xFF);
933 self.code.push(0xFF);
934 self.lines.push(line);
935 self.lines.push(line);
936 self.lines.push(line);
937 self.columns.push(col);
938 self.columns.push(col);
939 self.columns.push(col);
940 patch_pos
941 }
942
943 pub fn patch_jump(&mut self, patch_pos: usize) {
945 let target = self.code.len() as u16;
946 self.code[patch_pos] = (target >> 8) as u8;
947 self.code[patch_pos + 1] = (target & 0xFF) as u8;
948 }
949
950 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
952 let target = target as u16;
953 self.code[patch_pos] = (target >> 8) as u8;
954 self.code[patch_pos + 1] = (target & 0xFF) as u8;
955 }
956
957 pub fn read_u16(&self, pos: usize) -> u16 {
959 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
960 }
961
962 #[cfg(debug_assertions)]
966 fn note_balance(&mut self, op: Op, count: u16) {
967 match op_stack_delta(op, count) {
968 Some(delta) => self.balance_depth += delta,
969 None => self.balance_nonlinear += 1,
970 }
971 }
972
973 #[cfg(debug_assertions)]
976 pub(crate) fn balance_probe(&self) -> BalanceProbe {
977 BalanceProbe {
978 depth: self.balance_depth,
979 nonlinear: self.balance_nonlinear,
980 }
981 }
982
983 #[cfg(debug_assertions)]
989 pub(crate) fn balance_delta_since(&self, probe: BalanceProbe) -> Option<i32> {
990 if self.balance_nonlinear == probe.nonlinear {
991 Some(self.balance_depth - probe.depth)
992 } else {
993 None
994 }
995 }
996
997 fn register_inline_cache(&mut self, op_offset: usize) {
998 if self.inline_cache_slots.contains_key(&op_offset) {
999 return;
1000 }
1001 let mut entries = self.inline_caches.lock();
1002 let slot = entries.len();
1003 entries.push(InlineCacheEntry::Empty);
1004 self.inline_cache_slots.insert(op_offset, slot);
1005 Self::write_inline_cache_index(&mut self.inline_cache_index, op_offset, slot);
1006 }
1007
1008 fn write_inline_cache_index(index: &mut Vec<u32>, op_offset: usize, slot: usize) {
1013 if op_offset >= index.len() {
1014 index.resize(op_offset + 1, NO_INLINE_CACHE_SLOT);
1015 }
1016 index[op_offset] = slot as u32;
1017 }
1018
1019 #[inline]
1028 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
1029 match self.inline_cache_index.get(op_offset).copied() {
1030 None | Some(NO_INLINE_CACHE_SLOT) => None,
1031 Some(slot) => Some(slot as usize),
1032 }
1033 }
1034
1035 pub(crate) fn inline_cache_slot_count(&self) -> usize {
1036 self.inline_cache_slots.len()
1037 }
1038
1039 pub(crate) fn cache_id(&self) -> u64 {
1040 self.cache_id
1041 }
1042
1043 #[cfg(feature = "vm-bench-internals")]
1050 pub fn inline_cache_slot_via_btreemap_for_bench(&self, op_offset: usize) -> Option<usize> {
1051 self.inline_cache_slots.get(&op_offset).copied()
1052 }
1053
1054 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<crate::value::HarnStr> {
1059 let mut entries = self.constant_strings.lock();
1064 if entries.len() < self.constants.len() {
1065 entries.resize(self.constants.len(), None);
1066 }
1067 if let Some(Some(existing)) = entries.get(idx) {
1068 return Some(existing.clone());
1069 }
1070 let materialized = match self.constants.get(idx)? {
1071 Constant::String(s) => crate::value::HarnStr::from(s.as_str()),
1072 _ => return None,
1073 };
1074 entries[idx] = Some(materialized.clone());
1075 Some(materialized)
1076 }
1077
1078 #[inline]
1081 #[cfg(test)]
1082 pub(crate) fn peek_adaptive_binary_cache(
1083 &self,
1084 slot: usize,
1085 ) -> Option<(AdaptiveBinaryOp, AdaptiveBinaryState)> {
1086 match self.inline_caches.lock().get(slot)? {
1087 &InlineCacheEntry::AdaptiveBinary { op, state } => Some((op, state)),
1088 _ => None,
1089 }
1090 }
1091
1092 #[inline]
1095 #[cfg(test)]
1096 pub(crate) fn peek_method_cache(&self, slot: usize) -> Option<(u16, usize, MethodCacheTarget)> {
1097 match self.inline_caches.lock().get(slot)? {
1098 &InlineCacheEntry::Method {
1099 name_idx,
1100 argc,
1101 target,
1102 } => Some((name_idx, argc, target)),
1103 _ => None,
1104 }
1105 }
1106
1107 #[inline]
1110 #[cfg(test)]
1111 pub(crate) fn peek_property_cache(&self, slot: usize) -> Option<(u16, PropertyCacheTarget)> {
1112 match self.inline_caches.lock().get(slot)? {
1113 InlineCacheEntry::Property { name_idx, target } => Some((*name_idx, target.clone())),
1114 _ => None,
1115 }
1116 }
1117
1118 #[inline]
1121 #[cfg(test)]
1122 pub(crate) fn peek_direct_call_state(&self, slot: usize) -> Option<DirectCallState> {
1123 match self.inline_caches.lock().get(slot)? {
1124 InlineCacheEntry::DirectCall { state } => Some(state.clone()),
1125 _ => None,
1126 }
1127 }
1128
1129 #[cfg(test)]
1130 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1131 if let Some(existing) = self.inline_caches.lock().get_mut(slot) {
1132 *existing = entry;
1133 }
1134 }
1135
1136 pub fn freeze_for_cache(&self) -> CachedChunk {
1137 CachedChunk {
1138 code: self.code.clone(),
1139 constants: self.constants.clone(),
1140 lines: self.lines.clone(),
1141 columns: self.columns.clone(),
1142 source_file: self.source_file.clone(),
1143 current_col: self.current_col,
1144 functions: self
1145 .functions
1146 .iter()
1147 .map(|function| function.freeze_for_cache())
1148 .collect(),
1149 inline_cache_slots: self.inline_cache_slots.clone(),
1150 local_slots: self.local_slots.clone(),
1151 references_outer_names: self.references_outer_names,
1152 }
1153 }
1154
1155 pub fn from_cached(cached: CachedChunk) -> Self {
1156 let CachedChunk {
1157 code,
1158 constants,
1159 lines,
1160 columns,
1161 source_file,
1162 current_col,
1163 functions,
1164 inline_cache_slots,
1165 local_slots,
1166 references_outer_names,
1167 } = cached;
1168 let inline_cache_count = inline_cache_slots.len();
1169 let constants_count = constants.len();
1170 let mut inline_cache_index = Vec::new();
1177 inline_cache_index.resize(code.len(), NO_INLINE_CACHE_SLOT);
1178 for (&op_offset, &slot) in &inline_cache_slots {
1179 if op_offset < inline_cache_index.len() {
1180 inline_cache_index[op_offset] = slot as u32;
1181 }
1182 }
1183 let constant_index = build_constant_index(&constants);
1184 Self {
1185 cache_id: next_chunk_cache_id(),
1186 code,
1187 constants,
1188 constant_index,
1189 lines,
1190 columns,
1191 source_file,
1192 current_col,
1193 functions: functions
1194 .into_iter()
1195 .map(|function| Arc::new(CompiledFunction::from_cached(function)))
1196 .collect(),
1197 inline_cache_slots,
1198 inline_cache_index,
1199 inline_caches: Arc::new(Mutex::new(vec![
1200 InlineCacheEntry::Empty;
1201 inline_cache_count
1202 ])),
1203 constant_strings: Arc::new(Mutex::new(vec![None; constants_count])),
1204 local_slots,
1205 references_outer_names,
1206 #[cfg(debug_assertions)]
1207 balance_depth: 0,
1208 #[cfg(debug_assertions)]
1209 balance_nonlinear: 0,
1210 }
1211 }
1212
1213 pub(crate) fn add_local_slot(
1214 &mut self,
1215 name: String,
1216 mutable: bool,
1217 scope_depth: usize,
1218 ) -> u16 {
1219 let idx = self.local_slots.len();
1220 self.local_slots.push(LocalSlotInfo {
1221 name,
1222 mutable,
1223 scope_depth,
1224 });
1225 idx as u16
1226 }
1227
1228 pub fn read_u64(&self, pos: usize) -> u64 {
1230 u64::from_be_bytes([
1231 self.code[pos],
1232 self.code[pos + 1],
1233 self.code[pos + 2],
1234 self.code[pos + 3],
1235 self.code[pos + 4],
1236 self.code[pos + 5],
1237 self.code[pos + 6],
1238 self.code[pos + 7],
1239 ])
1240 }
1241
1242 pub fn disassemble(&self, name: &str) -> String {
1246 let mut out = format!("== {name} ==\n");
1247 let mut ip = 0;
1248 while ip < self.code.len() {
1249 let op_byte = self.code[ip];
1250 let line = self.lines.get(ip).copied().unwrap_or(0);
1251 out.push_str(&format!("{ip:04} [{line:>4}] "));
1252 ip += 1;
1253
1254 if let Some(op) = Op::from_byte(op_byte) {
1255 self.disassemble_op(op, &mut ip, &mut out);
1256 } else {
1257 out.push_str(&format!("UNKNOWN(0x{op_byte:02x})\n"));
1258 }
1259 }
1260 out
1261 }
1262}
1263
1264pub(crate) fn disasm_bare(_chunk: &Chunk, _ip: &mut usize, label: &str) -> String {
1275 label.to_string()
1276}
1277
1278pub(crate) fn disasm_u8(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1279 let arg = chunk.code[*ip];
1280 *ip += 1;
1281 format!("{label} {arg:>4}")
1282}
1283
1284pub(crate) fn disasm_u16(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1285 let arg = chunk.read_u16(*ip);
1286 *ip += 2;
1287 format!("{label} {arg:>4}")
1288}
1289
1290pub(crate) fn disasm_try_catch_setup(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1291 let catch_offset = chunk.read_u16(*ip);
1292 *ip += 2;
1293 let type_idx = chunk.read_u16(*ip);
1294 *ip += 2;
1295 if let Some(type_name) = chunk.constants.get(type_idx as usize) {
1296 format!("{label} {catch_offset:>4} type {type_idx:>4} ({type_name})")
1297 } else {
1298 format!("{label} {catch_offset:>4} type {type_idx:>4}")
1299 }
1300}
1301
1302pub(crate) fn disasm_const_pool_u16(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1303 let idx = chunk.read_u16(*ip);
1304 *ip += 2;
1305 format!("{label} {idx:>4} ({})", chunk.constants[idx as usize])
1306}
1307
1308pub(crate) fn disasm_local_slot_u16(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1309 let slot = chunk.read_u16(*ip);
1310 *ip += 2;
1311 let mut out = format!("{label} {slot:>4}");
1312 if let Some(info) = chunk.local_slots.get(slot as usize) {
1313 out.push_str(&format!(" ({})", info.name));
1314 }
1315 out
1316}
1317
1318pub(crate) fn disasm_const_pool_local_slot(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1319 let prop = chunk.read_u16(*ip);
1320 *ip += 2;
1321 let slot = chunk.read_u16(*ip);
1322 *ip += 2;
1323 let mut out = format!(
1324 "{label} prop {prop:>4} ({}) slot {slot:>4}",
1325 chunk.constants[prop as usize]
1326 );
1327 if let Some(info) = chunk.local_slots.get(slot as usize) {
1328 out.push_str(&format!(" ({})", info.name));
1329 }
1330 out
1331}
1332
1333pub(crate) fn disasm_method_call(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1334 let idx = chunk.read_u16(*ip);
1335 *ip += 2;
1336 let argc = chunk.code[*ip];
1337 *ip += 1;
1338 format!(
1339 "{label} {idx:>4} ({}) argc={argc}",
1340 chunk.constants[idx as usize]
1341 )
1342}
1343
1344pub(crate) fn disasm_match_enum(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1345 let enum_idx = chunk.read_u16(*ip);
1346 *ip += 2;
1347 let var_idx = chunk.read_u16(*ip);
1348 *ip += 2;
1349 format!(
1350 "{label} {enum_idx:>4} ({}) {var_idx:>4} ({})",
1351 chunk.constants[enum_idx as usize], chunk.constants[var_idx as usize],
1352 )
1353}
1354
1355pub(crate) fn disasm_build_enum(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1356 let enum_idx = chunk.read_u16(*ip);
1357 *ip += 2;
1358 let var_idx = chunk.read_u16(*ip);
1359 *ip += 2;
1360 let field_count = chunk.read_u16(*ip);
1361 *ip += 2;
1362 format!(
1363 "{label} {enum_idx:>4} ({}) {var_idx:>4} ({}) fields={field_count}",
1364 chunk.constants[enum_idx as usize], chunk.constants[var_idx as usize],
1365 )
1366}
1367
1368pub(crate) fn disasm_selective_import(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1369 let path_idx = chunk.read_u16(*ip);
1370 *ip += 2;
1371 let names_idx = chunk.read_u16(*ip);
1372 *ip += 2;
1373 format!(
1374 "{label} {path_idx:>4} ({}) names: {names_idx:>4} ({})",
1375 chunk.constants[path_idx as usize], chunk.constants[names_idx as usize],
1376 )
1377}
1378
1379pub(crate) fn disasm_check_type(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1380 let var_idx = chunk.read_u16(*ip);
1381 *ip += 2;
1382 let type_idx = chunk.read_u16(*ip);
1383 *ip += 2;
1384 format!(
1385 "{label} {var_idx:>4} ({}) -> {type_idx:>4} ({})",
1386 chunk.constants[var_idx as usize], chunk.constants[type_idx as usize],
1387 )
1388}
1389
1390pub(crate) fn disasm_call_builtin(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1391 let id = chunk.read_u64(*ip);
1392 *ip += 8;
1393 let idx = chunk.read_u16(*ip);
1394 *ip += 2;
1395 let argc = chunk.code[*ip];
1396 *ip += 1;
1397 format!(
1398 "{label} {id:#018x} {idx:>4} ({}) argc={argc}",
1399 chunk.constants[idx as usize],
1400 )
1401}
1402
1403pub(crate) fn disasm_call_builtin_spread(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1404 let id = chunk.read_u64(*ip);
1405 *ip += 8;
1406 let idx = chunk.read_u16(*ip);
1407 *ip += 2;
1408 format!(
1409 "{label} {id:#018x} {idx:>4} ({})",
1410 chunk.constants[idx as usize],
1411 )
1412}
1413
1414pub(crate) fn disasm_method_call_spread(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1415 let idx = chunk.read_u16(*ip);
1421 *ip += 2;
1422 format!("{label} {idx:>4} ({})", chunk.constants[idx as usize])
1423}
1424
1425impl Default for Chunk {
1426 fn default() -> Self {
1427 Self::new()
1428 }
1429}
1430
1431#[cfg(test)]
1432#[path = "chunk_tests.rs"]
1433mod tests;