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 | RootHarness | GetVar | GetArgc | GetLocalSlot | Closure
683 | Dup => 1,
684 DefLet | DefVar | DefCell | SetVar | DefLocalSlot | SetLocalSlot | SetProperty
688 | SetLocalSlotProperty | ConcatAssignLocal | Pop => -1,
689 Negate | Not | GetProperty | GetPropertyOpt | CheckType | TryUnwrap | TryWrapOk | Swap
693 | PushScope | PopScope | PopIterator | PopHandler => 0,
694 Add | Sub | Mul | Div | Mod | Pow | AddInt | SubInt | MulInt | DivInt | ModInt
696 | AddFloat | SubFloat | MulFloat | DivFloat | ModFloat | Equal | NotEqual | Less
697 | Greater | LessEqual | GreaterEqual | EqualInt | NotEqualInt | LessInt | GreaterInt
698 | LessEqualInt | GreaterEqualInt | EqualFloat | NotEqualFloat | LessFloat
699 | GreaterFloat | LessEqualFloat | GreaterEqualFloat | EqualBool | NotEqualBool
700 | EqualString | NotEqualString | Contains | Subscript | SubscriptOpt => -1,
701 IterInit => -1,
704 Slice | SetSubscript | SetLocalSlotSubscript => -2,
707 BuildList | Concat | CallBuiltin => 1 - count,
709 BuildDict => 1 - 2 * count,
710 Call | MethodCall | MethodCallOpt => -count,
712 Jump | JumpIfFalse | JumpIfTrue | IterNext | Return | TailCall | Throw | TryCatchSetup
715 | Spawn | Pipe | Parallel | ParallelMap | ParallelMapStream | ParallelSettle
716 | SyncMutexEnter | SyncMutexEnterKeyed | TaskScopeEnter | TaskScopeExit | Import
717 | SelectiveImport | NamespaceImport | DeadlineSetup | DeadlineEnd | BuildEnum
718 | MatchEnum | Yield | CallSpread | CallBuiltinSpread | MethodCallSpread => return None,
719 })
720}
721
722impl Chunk {
723 pub fn new() -> Self {
724 Self {
725 cache_id: next_chunk_cache_id(),
726 code: Vec::new(),
727 constants: Vec::new(),
728 constant_index: Some(HashMap::new()),
729 lines: Vec::new(),
730 columns: Vec::new(),
731 source_file: None,
732 current_col: 0,
733 functions: Vec::new(),
734 inline_cache_slots: BTreeMap::new(),
735 inline_cache_index: Vec::new(),
736 inline_caches: Arc::new(Mutex::new(Vec::new())),
737 constant_strings: Arc::new(Mutex::new(Vec::new())),
738 local_slots: Vec::new(),
739 references_outer_names: false,
740 #[cfg(debug_assertions)]
741 balance_depth: 0,
742 #[cfg(debug_assertions)]
743 balance_nonlinear: 0,
744 }
745 }
746
747 pub fn set_column(&mut self, col: u32) {
749 self.current_col = col;
750 }
751
752 pub fn add_constant(&mut self, constant: Constant) -> u16 {
754 if self.constant_index.is_none() {
755 self.constant_index = Some(build_constant_index(&self.constants));
756 }
757 let index_map = self
758 .constant_index
759 .as_mut()
760 .expect("constant side index was just derived");
761 debug_assert!(
762 index_map.len() <= self.constants.len(),
763 "constant side index cannot outgrow the constant pool"
764 );
765 let key = ConstantKey::from(&constant);
766 if let Some(index) = index_map.get(&key) {
767 debug_assert!(
768 self.constants
769 .get(*index as usize)
770 .is_some_and(|existing| constants_identical(existing, &constant)),
771 "constant side index drifted from the constant pool"
772 );
773 return *index;
774 }
775 let idx = self.constants.len();
776 let idx = u16::try_from(idx).expect("constant pool exceeded u16 operand space");
777 index_map.insert(key, idx);
778 self.constants.push(constant);
779 idx
780 }
781
782 pub fn emit(&mut self, op: Op, line: u32) {
784 #[cfg(debug_assertions)]
785 self.note_balance(op, 0);
786 let col = self.current_col;
787 let op_offset = self.code.len();
788 self.code.push(op as u8);
789 self.lines.push(line);
790 self.columns.push(col);
791 if is_adaptive_binary_op(op) {
792 self.register_inline_cache(op_offset);
793 }
794 if op_reads_outer_name(op) {
795 self.references_outer_names = true;
796 }
797 }
798
799 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
801 #[cfg(debug_assertions)]
802 self.note_balance(op, arg);
803 let col = self.current_col;
804 let op_offset = self.code.len();
805 self.code.push(op as u8);
806 self.code.push((arg >> 8) as u8);
807 self.code.push((arg & 0xFF) as u8);
808 self.lines.push(line);
809 self.lines.push(line);
810 self.lines.push(line);
811 self.columns.push(col);
812 self.columns.push(col);
813 self.columns.push(col);
814 if matches!(
815 op,
816 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread | Op::ConcatAssignLocal
817 ) {
818 self.register_inline_cache(op_offset);
819 }
820 if op_reads_outer_name(op) {
821 self.references_outer_names = true;
822 }
823 }
824
825 pub fn emit_set_local_slot_property(&mut self, prop_idx: u16, slot: u16, line: u32) {
828 #[cfg(debug_assertions)]
829 self.note_balance(Op::SetLocalSlotProperty, 0);
830 let col = self.current_col;
831 self.code.push(Op::SetLocalSlotProperty as u8);
832 self.code.push((prop_idx >> 8) as u8);
833 self.code.push((prop_idx & 0xFF) as u8);
834 self.code.push((slot >> 8) as u8);
835 self.code.push((slot & 0xFF) as u8);
836 for _ in 0..5 {
837 self.lines.push(line);
838 self.columns.push(col);
839 }
840 }
841
842 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
844 #[cfg(debug_assertions)]
845 self.note_balance(op, arg as u16);
846 let col = self.current_col;
847 let op_offset = self.code.len();
848 self.code.push(op as u8);
849 self.code.push(arg);
850 self.lines.push(line);
851 self.lines.push(line);
852 self.columns.push(col);
853 self.columns.push(col);
854 if matches!(op, Op::Call) {
855 self.register_inline_cache(op_offset);
856 }
857 if op_reads_outer_name(op) {
858 self.references_outer_names = true;
859 }
860 }
861
862 pub fn emit_call_builtin(
864 &mut self,
865 id: crate::BuiltinId,
866 name_idx: u16,
867 arg_count: u8,
868 line: u32,
869 ) {
870 #[cfg(debug_assertions)]
871 self.note_balance(Op::CallBuiltin, arg_count as u16);
872 let col = self.current_col;
873 let op_offset = self.code.len();
874 self.code.push(Op::CallBuiltin as u8);
875 self.code.extend_from_slice(&id.raw().to_be_bytes());
876 self.code.push((name_idx >> 8) as u8);
877 self.code.push((name_idx & 0xFF) as u8);
878 self.code.push(arg_count);
879 for _ in 0..12 {
880 self.lines.push(line);
881 self.columns.push(col);
882 }
883 self.register_inline_cache(op_offset);
884 self.references_outer_names = true;
885 }
886
887 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
889 #[cfg(debug_assertions)]
890 self.note_balance(Op::CallBuiltinSpread, 0);
891 let col = self.current_col;
892 self.code.push(Op::CallBuiltinSpread as u8);
893 self.code.extend_from_slice(&id.raw().to_be_bytes());
894 self.code.push((name_idx >> 8) as u8);
895 self.code.push((name_idx & 0xFF) as u8);
896 for _ in 0..11 {
897 self.lines.push(line);
898 self.columns.push(col);
899 }
900 self.references_outer_names = true;
901 }
902
903 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
905 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
906 }
907
908 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
910 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
911 }
912
913 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
914 #[cfg(debug_assertions)]
915 self.note_balance(op, arg_count as u16);
916 let col = self.current_col;
917 let op_offset = self.code.len();
918 self.code.push(op as u8);
919 self.code.push((name_idx >> 8) as u8);
920 self.code.push((name_idx & 0xFF) as u8);
921 self.code.push(arg_count);
922 self.lines.push(line);
923 self.lines.push(line);
924 self.lines.push(line);
925 self.lines.push(line);
926 self.columns.push(col);
927 self.columns.push(col);
928 self.columns.push(col);
929 self.columns.push(col);
930 self.register_inline_cache(op_offset);
931 }
932
933 pub fn current_offset(&self) -> usize {
935 self.code.len()
936 }
937
938 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
940 #[cfg(debug_assertions)]
941 self.note_balance(op, 0);
942 let col = self.current_col;
943 self.code.push(op as u8);
944 let patch_pos = self.code.len();
945 self.code.push(0xFF);
946 self.code.push(0xFF);
947 self.lines.push(line);
948 self.lines.push(line);
949 self.lines.push(line);
950 self.columns.push(col);
951 self.columns.push(col);
952 self.columns.push(col);
953 patch_pos
954 }
955
956 pub fn patch_jump(&mut self, patch_pos: usize) {
958 let target = self.code.len() as u16;
959 self.code[patch_pos] = (target >> 8) as u8;
960 self.code[patch_pos + 1] = (target & 0xFF) as u8;
961 }
962
963 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
965 let target = target as u16;
966 self.code[patch_pos] = (target >> 8) as u8;
967 self.code[patch_pos + 1] = (target & 0xFF) as u8;
968 }
969
970 pub fn read_u16(&self, pos: usize) -> u16 {
972 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
973 }
974
975 #[cfg(debug_assertions)]
979 fn note_balance(&mut self, op: Op, count: u16) {
980 match op_stack_delta(op, count) {
981 Some(delta) => self.balance_depth += delta,
982 None => self.balance_nonlinear += 1,
983 }
984 }
985
986 #[cfg(debug_assertions)]
989 pub(crate) fn balance_probe(&self) -> BalanceProbe {
990 BalanceProbe {
991 depth: self.balance_depth,
992 nonlinear: self.balance_nonlinear,
993 }
994 }
995
996 #[cfg(debug_assertions)]
1002 pub(crate) fn balance_delta_since(&self, probe: BalanceProbe) -> Option<i32> {
1003 if self.balance_nonlinear == probe.nonlinear {
1004 Some(self.balance_depth - probe.depth)
1005 } else {
1006 None
1007 }
1008 }
1009
1010 fn register_inline_cache(&mut self, op_offset: usize) {
1011 if self.inline_cache_slots.contains_key(&op_offset) {
1012 return;
1013 }
1014 let mut entries = self.inline_caches.lock();
1015 let slot = entries.len();
1016 entries.push(InlineCacheEntry::Empty);
1017 self.inline_cache_slots.insert(op_offset, slot);
1018 Self::write_inline_cache_index(&mut self.inline_cache_index, op_offset, slot);
1019 }
1020
1021 fn write_inline_cache_index(index: &mut Vec<u32>, op_offset: usize, slot: usize) {
1026 if op_offset >= index.len() {
1027 index.resize(op_offset + 1, NO_INLINE_CACHE_SLOT);
1028 }
1029 index[op_offset] = slot as u32;
1030 }
1031
1032 #[inline]
1041 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
1042 match self.inline_cache_index.get(op_offset).copied() {
1043 None | Some(NO_INLINE_CACHE_SLOT) => None,
1044 Some(slot) => Some(slot as usize),
1045 }
1046 }
1047
1048 pub(crate) fn inline_cache_slot_count(&self) -> usize {
1049 self.inline_cache_slots.len()
1050 }
1051
1052 pub(crate) fn cache_id(&self) -> u64 {
1053 self.cache_id
1054 }
1055
1056 #[cfg(feature = "vm-bench-internals")]
1063 pub fn inline_cache_slot_via_btreemap_for_bench(&self, op_offset: usize) -> Option<usize> {
1064 self.inline_cache_slots.get(&op_offset).copied()
1065 }
1066
1067 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<crate::value::HarnStr> {
1072 let mut entries = self.constant_strings.lock();
1077 if entries.len() < self.constants.len() {
1078 entries.resize(self.constants.len(), None);
1079 }
1080 if let Some(Some(existing)) = entries.get(idx) {
1081 return Some(existing.clone());
1082 }
1083 let materialized = match self.constants.get(idx)? {
1084 Constant::String(s) => crate::value::HarnStr::from(s.as_str()),
1085 _ => return None,
1086 };
1087 entries[idx] = Some(materialized.clone());
1088 Some(materialized)
1089 }
1090
1091 #[inline]
1094 #[cfg(test)]
1095 pub(crate) fn peek_adaptive_binary_cache(
1096 &self,
1097 slot: usize,
1098 ) -> Option<(AdaptiveBinaryOp, AdaptiveBinaryState)> {
1099 match self.inline_caches.lock().get(slot)? {
1100 &InlineCacheEntry::AdaptiveBinary { op, state } => Some((op, state)),
1101 _ => None,
1102 }
1103 }
1104
1105 #[inline]
1108 #[cfg(test)]
1109 pub(crate) fn peek_method_cache(&self, slot: usize) -> Option<(u16, usize, MethodCacheTarget)> {
1110 match self.inline_caches.lock().get(slot)? {
1111 &InlineCacheEntry::Method {
1112 name_idx,
1113 argc,
1114 target,
1115 } => Some((name_idx, argc, target)),
1116 _ => None,
1117 }
1118 }
1119
1120 #[inline]
1123 #[cfg(test)]
1124 pub(crate) fn peek_property_cache(&self, slot: usize) -> Option<(u16, PropertyCacheTarget)> {
1125 match self.inline_caches.lock().get(slot)? {
1126 InlineCacheEntry::Property { name_idx, target } => Some((*name_idx, target.clone())),
1127 _ => None,
1128 }
1129 }
1130
1131 #[inline]
1134 #[cfg(test)]
1135 pub(crate) fn peek_direct_call_state(&self, slot: usize) -> Option<DirectCallState> {
1136 match self.inline_caches.lock().get(slot)? {
1137 InlineCacheEntry::DirectCall { state } => Some(state.clone()),
1138 _ => None,
1139 }
1140 }
1141
1142 #[cfg(test)]
1143 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1144 if let Some(existing) = self.inline_caches.lock().get_mut(slot) {
1145 *existing = entry;
1146 }
1147 }
1148
1149 pub fn freeze_for_cache(&self) -> CachedChunk {
1150 CachedChunk {
1151 code: self.code.clone(),
1152 constants: self.constants.clone(),
1153 lines: self.lines.clone(),
1154 columns: self.columns.clone(),
1155 source_file: self.source_file.clone(),
1156 current_col: self.current_col,
1157 functions: self
1158 .functions
1159 .iter()
1160 .map(|function| function.freeze_for_cache())
1161 .collect(),
1162 inline_cache_slots: self.inline_cache_slots.clone(),
1163 local_slots: self.local_slots.clone(),
1164 references_outer_names: self.references_outer_names,
1165 }
1166 }
1167
1168 pub fn from_cached(cached: CachedChunk) -> Self {
1169 let CachedChunk {
1170 code,
1171 constants,
1172 lines,
1173 columns,
1174 source_file,
1175 current_col,
1176 functions,
1177 inline_cache_slots,
1178 local_slots,
1179 references_outer_names,
1180 } = cached;
1181 let inline_cache_count = inline_cache_slots.len();
1182 let constants_count = constants.len();
1183 let mut inline_cache_index = Vec::new();
1190 inline_cache_index.resize(code.len(), NO_INLINE_CACHE_SLOT);
1191 for (&op_offset, &slot) in &inline_cache_slots {
1192 if op_offset < inline_cache_index.len() {
1193 inline_cache_index[op_offset] = slot as u32;
1194 }
1195 }
1196 Self {
1197 cache_id: next_chunk_cache_id(),
1198 code,
1199 constants,
1200 constant_index: None,
1202 lines,
1203 columns,
1204 source_file,
1205 current_col,
1206 functions: functions
1207 .into_iter()
1208 .map(|function| Arc::new(CompiledFunction::from_cached(function)))
1209 .collect(),
1210 inline_cache_slots,
1211 inline_cache_index,
1212 inline_caches: Arc::new(Mutex::new(vec![
1213 InlineCacheEntry::Empty;
1214 inline_cache_count
1215 ])),
1216 constant_strings: Arc::new(Mutex::new(vec![None; constants_count])),
1217 local_slots,
1218 references_outer_names,
1219 #[cfg(debug_assertions)]
1220 balance_depth: 0,
1221 #[cfg(debug_assertions)]
1222 balance_nonlinear: 0,
1223 }
1224 }
1225
1226 pub(crate) fn add_local_slot(
1227 &mut self,
1228 name: String,
1229 mutable: bool,
1230 scope_depth: usize,
1231 ) -> u16 {
1232 let idx = self.local_slots.len();
1233 self.local_slots.push(LocalSlotInfo {
1234 name,
1235 mutable,
1236 scope_depth,
1237 });
1238 idx as u16
1239 }
1240
1241 pub fn read_u64(&self, pos: usize) -> u64 {
1243 u64::from_be_bytes([
1244 self.code[pos],
1245 self.code[pos + 1],
1246 self.code[pos + 2],
1247 self.code[pos + 3],
1248 self.code[pos + 4],
1249 self.code[pos + 5],
1250 self.code[pos + 6],
1251 self.code[pos + 7],
1252 ])
1253 }
1254
1255 pub fn disassemble(&self, name: &str) -> String {
1259 let mut out = format!("== {name} ==\n");
1260 let mut ip = 0;
1261 while ip < self.code.len() {
1262 let op_byte = self.code[ip];
1263 let line = self.lines.get(ip).copied().unwrap_or(0);
1264 out.push_str(&format!("{ip:04} [{line:>4}] "));
1265 ip += 1;
1266
1267 if let Some(op) = Op::from_byte(op_byte) {
1268 self.disassemble_op(op, &mut ip, &mut out);
1269 } else {
1270 out.push_str(&format!("UNKNOWN(0x{op_byte:02x})\n"));
1271 }
1272 }
1273 out
1274 }
1275}
1276
1277pub(crate) fn disasm_bare(_chunk: &Chunk, _ip: &mut usize, label: &str) -> String {
1288 label.to_string()
1289}
1290
1291pub(crate) fn disasm_u8(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1292 let arg = chunk.code[*ip];
1293 *ip += 1;
1294 format!("{label} {arg:>4}")
1295}
1296
1297pub(crate) fn disasm_u16(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1298 let arg = chunk.read_u16(*ip);
1299 *ip += 2;
1300 format!("{label} {arg:>4}")
1301}
1302
1303pub(crate) fn disasm_try_catch_setup(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1304 let catch_offset = chunk.read_u16(*ip);
1305 *ip += 2;
1306 let type_idx = chunk.read_u16(*ip);
1307 *ip += 2;
1308 if let Some(type_name) = chunk.constants.get(type_idx as usize) {
1309 format!("{label} {catch_offset:>4} type {type_idx:>4} ({type_name})")
1310 } else {
1311 format!("{label} {catch_offset:>4} type {type_idx:>4}")
1312 }
1313}
1314
1315pub(crate) fn disasm_const_pool_u16(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1316 let idx = chunk.read_u16(*ip);
1317 *ip += 2;
1318 format!("{label} {idx:>4} ({})", chunk.constants[idx as usize])
1319}
1320
1321pub(crate) fn disasm_local_slot_u16(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1322 let slot = chunk.read_u16(*ip);
1323 *ip += 2;
1324 let mut out = format!("{label} {slot:>4}");
1325 if let Some(info) = chunk.local_slots.get(slot as usize) {
1326 out.push_str(&format!(" ({})", info.name));
1327 }
1328 out
1329}
1330
1331pub(crate) fn disasm_const_pool_local_slot(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1332 let prop = chunk.read_u16(*ip);
1333 *ip += 2;
1334 let slot = chunk.read_u16(*ip);
1335 *ip += 2;
1336 let mut out = format!(
1337 "{label} prop {prop:>4} ({}) slot {slot:>4}",
1338 chunk.constants[prop as usize]
1339 );
1340 if let Some(info) = chunk.local_slots.get(slot as usize) {
1341 out.push_str(&format!(" ({})", info.name));
1342 }
1343 out
1344}
1345
1346pub(crate) fn disasm_method_call(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1347 let idx = chunk.read_u16(*ip);
1348 *ip += 2;
1349 let argc = chunk.code[*ip];
1350 *ip += 1;
1351 format!(
1352 "{label} {idx:>4} ({}) argc={argc}",
1353 chunk.constants[idx as usize]
1354 )
1355}
1356
1357pub(crate) fn disasm_match_enum(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1358 let enum_idx = chunk.read_u16(*ip);
1359 *ip += 2;
1360 let var_idx = chunk.read_u16(*ip);
1361 *ip += 2;
1362 format!(
1363 "{label} {enum_idx:>4} ({}) {var_idx:>4} ({})",
1364 chunk.constants[enum_idx as usize], chunk.constants[var_idx as usize],
1365 )
1366}
1367
1368pub(crate) fn disasm_build_enum(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1369 let enum_idx = chunk.read_u16(*ip);
1370 *ip += 2;
1371 let var_idx = chunk.read_u16(*ip);
1372 *ip += 2;
1373 let field_count = chunk.read_u16(*ip);
1374 *ip += 2;
1375 format!(
1376 "{label} {enum_idx:>4} ({}) {var_idx:>4} ({}) fields={field_count}",
1377 chunk.constants[enum_idx as usize], chunk.constants[var_idx as usize],
1378 )
1379}
1380
1381pub(crate) fn disasm_selective_import(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1382 let path_idx = chunk.read_u16(*ip);
1383 *ip += 2;
1384 let names_idx = chunk.read_u16(*ip);
1385 *ip += 2;
1386 format!(
1387 "{label} {path_idx:>4} ({}) names: {names_idx:>4} ({})",
1388 chunk.constants[path_idx as usize], chunk.constants[names_idx as usize],
1389 )
1390}
1391
1392pub(crate) fn disasm_check_type(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1393 let var_idx = chunk.read_u16(*ip);
1394 *ip += 2;
1395 let type_idx = chunk.read_u16(*ip);
1396 *ip += 2;
1397 format!(
1398 "{label} {var_idx:>4} ({}) -> {type_idx:>4} ({})",
1399 chunk.constants[var_idx as usize], chunk.constants[type_idx as usize],
1400 )
1401}
1402
1403pub(crate) fn disasm_call_builtin(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 let argc = chunk.code[*ip];
1409 *ip += 1;
1410 format!(
1411 "{label} {id:#018x} {idx:>4} ({}) argc={argc}",
1412 chunk.constants[idx as usize],
1413 )
1414}
1415
1416pub(crate) fn disasm_call_builtin_spread(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1417 let id = chunk.read_u64(*ip);
1418 *ip += 8;
1419 let idx = chunk.read_u16(*ip);
1420 *ip += 2;
1421 format!(
1422 "{label} {id:#018x} {idx:>4} ({})",
1423 chunk.constants[idx as usize],
1424 )
1425}
1426
1427pub(crate) fn disasm_method_call_spread(chunk: &Chunk, ip: &mut usize, label: &str) -> String {
1428 let idx = chunk.read_u16(*ip);
1434 *ip += 2;
1435 format!("{label} {idx:>4} ({})", chunk.constants[idx as usize])
1436}
1437
1438impl Default for Chunk {
1439 fn default() -> Self {
1440 Self::new()
1441 }
1442}
1443
1444#[cfg(test)]
1445#[path = "chunk_tests.rs"]
1446mod tests;