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::runtime_guards::RuntimeParamGuard;
11
12pub(crate) const NO_INLINE_CACHE_SLOT: u32 = u32::MAX;
20static NEXT_CHUNK_CACHE_ID: AtomicU64 = AtomicU64::new(1);
21
22fn next_chunk_cache_id() -> u64 {
23 NEXT_CHUNK_CACHE_ID.fetch_add(1, Ordering::Relaxed)
24}
25
26pub use crate::vm::ops::Op;
33pub(crate) use crate::vm::ops::{is_adaptive_binary_op, op_reads_outer_name};
34
35mod disassembly;
36pub(crate) use disassembly::*;
37mod inline_cache;
38pub(crate) use inline_cache::*;
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct BindingTypeSlot {
49 pub name: String,
50 pub type_expr: TypeExpr,
51 pub nominal_type_names: Vec<String>,
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58pub enum Constant {
59 Int(i64),
60 Float(f64),
61 String(String),
62 Bool(bool),
63 Nil,
64 Duration(i64),
65}
66
67fn constants_identical(a: &Constant, b: &Constant) -> bool {
76 match (a, b) {
77 (Constant::Float(x), Constant::Float(y)) => x.to_bits() == y.to_bits(),
78 _ => a == b,
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Hash)]
88enum ConstantKey {
89 Int(i64),
90 Float(u64),
91 String(String),
92 Bool(bool),
93 Nil,
94 Duration(i64),
95}
96
97impl From<&Constant> for ConstantKey {
98 fn from(constant: &Constant) -> Self {
99 match constant {
100 Constant::Int(value) => Self::Int(*value),
101 Constant::Float(value) => Self::Float(value.to_bits()),
102 Constant::String(value) => Self::String(value.clone()),
103 Constant::Bool(value) => Self::Bool(*value),
104 Constant::Nil => Self::Nil,
105 Constant::Duration(value) => Self::Duration(*value),
106 }
107 }
108}
109
110fn build_constant_index(constants: &[Constant]) -> HashMap<ConstantKey, u16> {
111 let mut index = HashMap::with_capacity(constants.len());
112 for (slot, constant) in constants.iter().enumerate() {
113 if let Ok(slot) = u16::try_from(slot) {
114 index.entry(ConstantKey::from(constant)).or_insert(slot);
115 }
116 }
117 index
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122pub struct LocalSlotInfo {
123 pub name: String,
124 pub mutable: bool,
125 pub scope_depth: usize,
126}
127
128impl fmt::Display for Constant {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 Constant::Int(n) => write!(f, "{n}"),
132 Constant::Float(n) => write!(f, "{n}"),
133 Constant::String(s) => write!(f, "\"{s}\""),
134 Constant::Bool(b) => write!(f, "{b}"),
135 Constant::Nil => write!(f, "nil"),
136 Constant::Duration(ms) => write!(f, "{ms}ms"),
137 }
138 }
139}
140
141#[derive(Debug)]
143pub struct Chunk {
144 cache_id: u64,
148 pub code: Vec<u8>,
150 pub constants: Vec<Constant>,
152 constant_index: Option<HashMap<ConstantKey, u16>>,
161 pub lines: Vec<u32>,
163 pub columns: Vec<u32>,
166 pub source_file: Option<String>,
171 current_col: u32,
173 pub functions: Vec<CompiledFunctionRef>,
175 inline_cache_slots: BTreeMap<usize, usize>,
181 inline_cache_index: Vec<u32>,
189 inline_caches: Arc<Mutex<Vec<InlineCacheEntry>>>,
193 constant_strings: Arc<Mutex<Vec<Option<crate::value::HarnStr>>>>,
197 pub(crate) local_slots: Vec<LocalSlotInfo>,
199 pub(crate) binding_types: Vec<BindingTypeSlot>,
202 pub(crate) references_outer_names: bool,
218 #[cfg(debug_assertions)]
232 balance_depth: i32,
233 #[cfg(debug_assertions)]
234 balance_nonlinear: u32,
235}
236
237pub type ChunkRef = Arc<Chunk>;
238pub type CompiledFunctionRef = Arc<CompiledFunction>;
239
240impl Clone for Chunk {
241 fn clone(&self) -> Self {
242 Self {
243 cache_id: self.cache_id,
244 code: self.code.clone(),
245 constants: self.constants.clone(),
246 constant_index: self.constant_index.clone(),
247 lines: self.lines.clone(),
248 columns: self.columns.clone(),
249 source_file: self.source_file.clone(),
250 current_col: self.current_col,
251 functions: self.functions.clone(),
252 inline_cache_slots: self.inline_cache_slots.clone(),
253 inline_cache_index: self.inline_cache_index.clone(),
254 inline_caches: Arc::new(Mutex::new(vec![
255 InlineCacheEntry::Empty;
256 self.inline_cache_slot_count()
257 ])),
258 constant_strings: Arc::new(Mutex::new(vec![None; self.constants.len()])),
259 local_slots: self.local_slots.clone(),
260 binding_types: self.binding_types.clone(),
261 references_outer_names: self.references_outer_names,
262 #[cfg(debug_assertions)]
263 balance_depth: self.balance_depth,
264 #[cfg(debug_assertions)]
265 balance_nonlinear: self.balance_nonlinear,
266 }
267 }
268}
269
270#[derive(Clone, Debug, Serialize, Deserialize)]
275pub struct CachedChunk {
276 pub(crate) code: Vec<u8>,
277 pub(crate) constants: Vec<Constant>,
278 pub(crate) lines: Vec<u32>,
279 pub(crate) columns: Vec<u32>,
280 pub(crate) source_file: Option<String>,
281 pub(crate) current_col: u32,
282 pub(crate) functions: Vec<CachedCompiledFunction>,
283 pub(crate) inline_cache_slots: BTreeMap<usize, usize>,
284 pub(crate) local_slots: Vec<LocalSlotInfo>,
285 #[serde(default)]
286 pub(crate) binding_types: Vec<BindingTypeSlot>,
287 #[serde(default)]
288 pub(crate) references_outer_names: bool,
289}
290
291#[derive(Clone, Debug, Serialize, Deserialize)]
292pub struct CachedCompiledFunction {
293 pub(crate) name: String,
294 pub(crate) type_params: Vec<String>,
295 pub(crate) nominal_type_names: Vec<String>,
296 pub(crate) params: Vec<CachedParamSlot>,
297 pub(crate) default_start: Option<usize>,
298 pub(crate) chunk: CachedChunk,
299 pub(crate) is_generator: bool,
300 pub(crate) is_stream: bool,
301 pub(crate) has_rest_param: bool,
302 pub(crate) has_runtime_type_checks: bool,
303}
304
305#[derive(Clone, Debug, Serialize, Deserialize)]
306pub(crate) struct CachedParamSlot {
307 pub(crate) name: String,
308 pub(crate) type_expr: Option<TypeExpr>,
309 pub(crate) has_default: bool,
310}
311
312impl CachedParamSlot {
313 fn thaw(self) -> ParamSlot {
314 let runtime_guard = self
315 .type_expr
316 .as_ref()
317 .map(RuntimeParamGuard::from_type_expr);
318 ParamSlot {
319 name: self.name,
320 type_expr: self.type_expr,
321 runtime_guard,
322 has_default: self.has_default,
323 }
324 }
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct ParamSlot {
334 pub name: String,
335 pub type_expr: Option<TypeExpr>,
338 #[serde(skip)]
341 pub(crate) runtime_guard: Option<RuntimeParamGuard>,
342 pub has_default: bool,
346}
347
348impl ParamSlot {
349 pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
352 Self::from_typed_param_with_type(param, param.type_expr.clone())
353 }
354
355 pub(crate) fn from_typed_param_with_type(
356 param: &harn_parser::TypedParam,
357 type_expr: Option<TypeExpr>,
358 ) -> Self {
359 let runtime_guard = type_expr.as_ref().map(RuntimeParamGuard::from_type_expr);
360 Self {
361 name: param.name.clone(),
362 type_expr,
363 runtime_guard,
364 has_default: param.default_value.is_some(),
365 }
366 }
367
368 fn freeze_for_cache(&self) -> CachedParamSlot {
369 CachedParamSlot {
370 name: self.name.clone(),
371 type_expr: self.type_expr.clone(),
372 has_default: self.has_default,
373 }
374 }
375
376 pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
381 params.iter().map(Self::from_typed_param).collect()
382 }
383}
384
385#[derive(Debug, Clone)]
387pub struct CompiledFunction {
388 pub name: String,
389 pub type_params: Vec<String>,
393 pub nominal_type_names: Vec<String>,
397 pub params: Vec<ParamSlot>,
398 pub default_start: Option<usize>,
400 pub chunk: ChunkRef,
401 pub is_generator: bool,
403 pub is_stream: bool,
405 pub has_rest_param: bool,
407 pub has_runtime_type_checks: bool,
412}
413
414impl CompiledFunction {
415 pub(crate) fn from_portable(portable: harn_kernel::CompiledFunction) -> Self {
416 Self {
417 name: portable.name,
418 type_params: portable.type_params,
419 nominal_type_names: portable.nominal_type_names,
420 params: portable
421 .params
422 .into_iter()
423 .map(|param| {
424 let runtime_guard = param
425 .type_expr
426 .as_ref()
427 .map(RuntimeParamGuard::from_type_expr);
428 ParamSlot {
429 name: param.name,
430 type_expr: param.type_expr,
431 runtime_guard,
432 has_default: param.has_default,
433 }
434 })
435 .collect(),
436 default_start: portable.default_start,
437 chunk: Arc::new(Chunk::from_portable((*portable.chunk).clone())),
438 is_generator: portable.is_generator,
439 is_stream: portable.is_stream,
440 has_rest_param: portable.has_rest_param,
441 has_runtime_type_checks: portable.has_runtime_type_checks,
442 }
443 }
444
445 #[cfg(test)]
446 pub(crate) fn has_runtime_type_checks_for_params(params: &[ParamSlot]) -> bool {
447 params.iter().any(|param| param.type_expr.is_some())
448 }
449
450 pub fn param_names(&self) -> impl Iterator<Item = &str> {
453 self.params.iter().map(|p| p.name.as_str())
454 }
455
456 pub fn required_param_count(&self) -> usize {
458 self.default_start.unwrap_or(self.params.len())
459 }
460
461 pub(crate) fn minimum_arg_count(&self) -> usize {
463 if self.has_rest_param {
464 self.required_param_count()
465 .min(self.params.len().saturating_sub(1))
466 } else {
467 self.required_param_count()
468 }
469 }
470
471 pub(crate) fn callee_arg_count(&self, supplied: usize) -> usize {
473 if self.has_rest_param {
474 supplied
475 } else {
476 supplied.min(self.params.len())
477 }
478 }
479
480 pub fn declares_type_param(&self, name: &str) -> bool {
481 self.type_params.iter().any(|param| param == name)
482 }
483
484 pub fn has_nominal_type(&self, name: &str) -> bool {
485 self.nominal_type_names.iter().any(|ty| ty == name)
486 }
487
488 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
489 CachedCompiledFunction {
490 name: self.name.clone(),
491 type_params: self.type_params.clone(),
492 nominal_type_names: self.nominal_type_names.clone(),
493 params: self
494 .params
495 .iter()
496 .map(ParamSlot::freeze_for_cache)
497 .collect(),
498 default_start: self.default_start,
499 chunk: self.chunk.freeze_for_cache(),
500 is_generator: self.is_generator,
501 is_stream: self.is_stream,
502 has_rest_param: self.has_rest_param,
503 has_runtime_type_checks: self.has_runtime_type_checks,
504 }
505 }
506
507 pub(crate) fn from_cached(cached: CachedCompiledFunction) -> Self {
508 Self {
509 name: cached.name,
510 type_params: cached.type_params,
511 nominal_type_names: cached.nominal_type_names,
512 params: cached
513 .params
514 .into_iter()
515 .map(CachedParamSlot::thaw)
516 .collect(),
517 default_start: cached.default_start,
518 chunk: Arc::new(Chunk::from_cached(cached.chunk)),
519 is_generator: cached.is_generator,
520 is_stream: cached.is_stream,
521 has_rest_param: cached.has_rest_param,
522 has_runtime_type_checks: cached.has_runtime_type_checks,
523 }
524 }
525}
526
527#[cfg(debug_assertions)]
544fn op_stack_delta(op: Op, count: u16) -> Option<i32> {
545 use Op::*;
546 let count = count as i32;
547 Some(match op {
548 Constant | Nil | True | False | RootHarness | GetVar | GetArgc | GetLocalSlot | Closure
550 | Dup => 1,
551 DefLet | DefVar | DefCell | SetVar | DefLocalSlot | SetLocalSlot | SetProperty
555 | SetLocalSlotProperty | ConcatAssignLocal | Pop => -1,
556 AssertBindingType => 0,
560 Negate | Not | GetProperty | GetPropertyOpt | CheckType | TryUnwrap | TryWrapOk | Swap
561 | PushScope | PopScope | PopIterator | PopHandler => 0,
562 Add | Sub | Mul | Div | Mod | Pow | AddInt | SubInt | MulInt | DivInt | ModInt
564 | AddFloat | SubFloat | MulFloat | DivFloat | ModFloat | Equal | NotEqual | Less
565 | Greater | LessEqual | GreaterEqual | EqualInt | NotEqualInt | LessInt | GreaterInt
566 | LessEqualInt | GreaterEqualInt | EqualFloat | NotEqualFloat | LessFloat
567 | GreaterFloat | LessEqualFloat | GreaterEqualFloat | EqualBool | NotEqualBool
568 | EqualString | NotEqualString | Contains | Subscript | SubscriptOpt => -1,
569 IterInit => -1,
572 Slice | SetSubscript | SetLocalSlotSubscript => -2,
575 BuildList | Concat | CallBuiltin => 1 - count,
577 BuildDict => 1 - 2 * count,
578 Call | MethodCall | MethodCallOpt => -count,
580 Jump
583 | JumpIfFalse
584 | JumpIfTrue
585 | IterNext
586 | Return
587 | TailCall
588 | Throw
589 | TryCatchSetup
590 | Spawn
591 | Pipe
592 | Parallel
593 | ParallelMap
594 | ParallelMapStream
595 | ParallelSettle
596 | SyncMutexEnter
597 | SyncMutexEnterKeyed
598 | TaskScopeEnter
599 | TaskScopeExit
600 | Import
601 | SelectiveImport
602 | NamespaceImport
603 | NamespaceImportMembers
604 | DeadlineSetup
605 | DeadlineEnd
606 | BuildEnum
607 | MatchEnum
608 | Yield
609 | CallSpread
610 | CallBuiltinSpread
611 | MethodCallSpread => return None,
612 })
613}
614
615impl Chunk {
616 pub fn from_portable(portable: harn_kernel::Chunk) -> Self {
619 let mut inline_cache_slots = BTreeMap::new();
620 let mut offset = 0usize;
621 while offset < portable.code.len() {
622 let Some(op) = Op::from_byte(portable.code[offset]) else {
623 break;
624 };
625 if is_adaptive_binary_op(op)
626 || matches!(
627 op,
628 Op::GetProperty
629 | Op::GetPropertyOpt
630 | Op::MethodCall
631 | Op::MethodCallOpt
632 | Op::MethodCallSpread
633 | Op::ConcatAssignLocal
634 | Op::Call
635 | Op::CallBuiltin
636 )
637 {
638 let slot = inline_cache_slots.len();
639 inline_cache_slots.insert(offset, slot);
640 }
641 let Some(width) = harn_kernel::program::instruction_len(op, &portable.code[offset..])
642 else {
643 break;
644 };
645 offset = offset.saturating_add(width);
646 }
647
648 let code = portable.code;
649 let constants = portable
650 .constants
651 .into_iter()
652 .map(|constant| match constant {
653 harn_kernel::Constant::Int(value) => Constant::Int(value),
654 harn_kernel::Constant::Float(value) => Constant::Float(value),
655 harn_kernel::Constant::String(value) => Constant::String(value),
656 harn_kernel::Constant::Bool(value) => Constant::Bool(value),
657 harn_kernel::Constant::Nil => Constant::Nil,
658 harn_kernel::Constant::Duration(value) => Constant::Duration(value),
659 })
660 .collect::<Vec<_>>();
661 let constant_count = constants.len();
662 let inline_cache_count = inline_cache_slots.len();
663 let mut inline_cache_index = vec![NO_INLINE_CACHE_SLOT; code.len()];
664 for (&op_offset, &slot) in &inline_cache_slots {
665 inline_cache_index[op_offset] = slot as u32;
666 }
667
668 Self {
669 cache_id: next_chunk_cache_id(),
670 code,
671 constants,
672 constant_index: None,
673 lines: portable.lines,
674 columns: portable.columns,
675 source_file: portable.source_file,
676 current_col: portable.current_col,
677 functions: portable
678 .functions
679 .into_iter()
680 .map(|function| {
681 Arc::new(CompiledFunction::from_portable(function.as_ref().clone()))
682 })
683 .collect(),
684 inline_cache_slots,
685 inline_cache_index,
686 inline_caches: Arc::new(Mutex::new(vec![
687 InlineCacheEntry::Empty;
688 inline_cache_count
689 ])),
690 constant_strings: Arc::new(Mutex::new(vec![None; constant_count])),
691 local_slots: portable
692 .local_slots
693 .into_iter()
694 .map(|slot| LocalSlotInfo {
695 name: slot.name,
696 mutable: slot.mutable,
697 scope_depth: slot.scope_depth,
698 })
699 .collect(),
700 binding_types: portable
701 .binding_types
702 .into_iter()
703 .map(|slot| BindingTypeSlot {
704 name: slot.name,
705 type_expr: slot.type_expr,
706 nominal_type_names: slot.nominal_type_names,
707 })
708 .collect(),
709 references_outer_names: portable.references_outer_names,
710 #[cfg(debug_assertions)]
711 balance_depth: 0,
712 #[cfg(debug_assertions)]
713 balance_nonlinear: 0,
714 }
715 }
716
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: Some(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 binding_types: Vec::new(),
734 references_outer_names: false,
735 #[cfg(debug_assertions)]
736 balance_depth: 0,
737 #[cfg(debug_assertions)]
738 balance_nonlinear: 0,
739 }
740 }
741
742 pub fn set_column(&mut self, col: u32) {
744 self.current_col = col;
745 }
746
747 pub fn add_constant(&mut self, constant: Constant) -> u16 {
749 if self.constant_index.is_none() {
750 self.constant_index = Some(build_constant_index(&self.constants));
751 }
752 let index_map = self
753 .constant_index
754 .as_mut()
755 .expect("constant side index was just derived");
756 debug_assert!(
757 index_map.len() <= self.constants.len(),
758 "constant side index cannot outgrow the constant pool"
759 );
760 let key = ConstantKey::from(&constant);
761 if let Some(index) = index_map.get(&key) {
762 debug_assert!(
763 self.constants
764 .get(*index as usize)
765 .is_some_and(|existing| constants_identical(existing, &constant)),
766 "constant side index drifted from the constant pool"
767 );
768 return *index;
769 }
770 let idx = self.constants.len();
771 let idx = u16::try_from(idx).expect("constant pool exceeded u16 operand space");
772 index_map.insert(key, idx);
773 self.constants.push(constant);
774 idx
775 }
776
777 pub fn emit(&mut self, op: Op, line: u32) {
779 #[cfg(debug_assertions)]
780 self.note_balance(op, 0);
781 let col = self.current_col;
782 let op_offset = self.code.len();
783 self.code.push(op as u8);
784 self.lines.push(line);
785 self.columns.push(col);
786 if is_adaptive_binary_op(op) {
787 self.register_inline_cache(op_offset);
788 }
789 if op_reads_outer_name(op) {
790 self.references_outer_names = true;
791 }
792 }
793
794 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
796 #[cfg(debug_assertions)]
797 self.note_balance(op, arg);
798 let col = self.current_col;
799 let op_offset = self.code.len();
800 self.code.push(op as u8);
801 self.code.push((arg >> 8) as u8);
802 self.code.push((arg & 0xFF) as u8);
803 self.lines.push(line);
804 self.lines.push(line);
805 self.lines.push(line);
806 self.columns.push(col);
807 self.columns.push(col);
808 self.columns.push(col);
809 if matches!(
810 op,
811 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread | Op::ConcatAssignLocal
812 ) {
813 self.register_inline_cache(op_offset);
814 }
815 if op_reads_outer_name(op) {
816 self.references_outer_names = true;
817 }
818 }
819
820 pub fn emit_set_local_slot_property(&mut self, prop_idx: u16, slot: u16, line: u32) {
823 #[cfg(debug_assertions)]
824 self.note_balance(Op::SetLocalSlotProperty, 0);
825 let col = self.current_col;
826 self.code.push(Op::SetLocalSlotProperty as u8);
827 self.code.push((prop_idx >> 8) as u8);
828 self.code.push((prop_idx & 0xFF) as u8);
829 self.code.push((slot >> 8) as u8);
830 self.code.push((slot & 0xFF) as u8);
831 for _ in 0..5 {
832 self.lines.push(line);
833 self.columns.push(col);
834 }
835 }
836
837 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
839 #[cfg(debug_assertions)]
840 self.note_balance(op, arg as u16);
841 let col = self.current_col;
842 let op_offset = self.code.len();
843 self.code.push(op as u8);
844 self.code.push(arg);
845 self.lines.push(line);
846 self.lines.push(line);
847 self.columns.push(col);
848 self.columns.push(col);
849 if matches!(op, Op::Call) {
850 self.register_inline_cache(op_offset);
851 }
852 if op_reads_outer_name(op) {
853 self.references_outer_names = true;
854 }
855 }
856
857 pub fn emit_call_builtin(
859 &mut self,
860 id: crate::BuiltinId,
861 name_idx: u16,
862 arg_count: u8,
863 line: u32,
864 ) {
865 #[cfg(debug_assertions)]
866 self.note_balance(Op::CallBuiltin, arg_count as u16);
867 let col = self.current_col;
868 let op_offset = self.code.len();
869 self.code.push(Op::CallBuiltin as u8);
870 self.code.extend_from_slice(&id.raw().to_be_bytes());
871 self.code.push((name_idx >> 8) as u8);
872 self.code.push((name_idx & 0xFF) as u8);
873 self.code.push(arg_count);
874 for _ in 0..12 {
875 self.lines.push(line);
876 self.columns.push(col);
877 }
878 self.register_inline_cache(op_offset);
879 self.references_outer_names = true;
880 }
881
882 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
884 #[cfg(debug_assertions)]
885 self.note_balance(Op::CallBuiltinSpread, 0);
886 let col = self.current_col;
887 self.code.push(Op::CallBuiltinSpread as u8);
888 self.code.extend_from_slice(&id.raw().to_be_bytes());
889 self.code.push((name_idx >> 8) as u8);
890 self.code.push((name_idx & 0xFF) as u8);
891 for _ in 0..11 {
892 self.lines.push(line);
893 self.columns.push(col);
894 }
895 self.references_outer_names = true;
896 }
897
898 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
900 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
901 }
902
903 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
905 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
906 }
907
908 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
909 #[cfg(debug_assertions)]
910 self.note_balance(op, arg_count as u16);
911 let col = self.current_col;
912 let op_offset = self.code.len();
913 self.code.push(op as u8);
914 self.code.push((name_idx >> 8) as u8);
915 self.code.push((name_idx & 0xFF) as u8);
916 self.code.push(arg_count);
917 self.lines.push(line);
918 self.lines.push(line);
919 self.lines.push(line);
920 self.lines.push(line);
921 self.columns.push(col);
922 self.columns.push(col);
923 self.columns.push(col);
924 self.columns.push(col);
925 self.register_inline_cache(op_offset);
926 }
927
928 pub fn current_offset(&self) -> usize {
930 self.code.len()
931 }
932
933 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
935 #[cfg(debug_assertions)]
936 self.note_balance(op, 0);
937 let col = self.current_col;
938 self.code.push(op as u8);
939 let patch_pos = self.code.len();
940 self.code.push(0xFF);
941 self.code.push(0xFF);
942 self.lines.push(line);
943 self.lines.push(line);
944 self.lines.push(line);
945 self.columns.push(col);
946 self.columns.push(col);
947 self.columns.push(col);
948 patch_pos
949 }
950
951 pub fn patch_jump(&mut self, patch_pos: usize) {
953 let target = self.code.len() as u16;
954 self.code[patch_pos] = (target >> 8) as u8;
955 self.code[patch_pos + 1] = (target & 0xFF) as u8;
956 }
957
958 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
960 let target = target as u16;
961 self.code[patch_pos] = (target >> 8) as u8;
962 self.code[patch_pos + 1] = (target & 0xFF) as u8;
963 }
964
965 pub fn read_u16(&self, pos: usize) -> u16 {
967 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
968 }
969
970 #[cfg(debug_assertions)]
974 fn note_balance(&mut self, op: Op, count: u16) {
975 match op_stack_delta(op, count) {
976 Some(delta) => self.balance_depth += delta,
977 None => self.balance_nonlinear += 1,
978 }
979 }
980
981 fn register_inline_cache(&mut self, op_offset: usize) {
982 if self.inline_cache_slots.contains_key(&op_offset) {
983 return;
984 }
985 let mut entries = self.inline_caches.lock();
986 let slot = entries.len();
987 entries.push(InlineCacheEntry::Empty);
988 self.inline_cache_slots.insert(op_offset, slot);
989 Self::write_inline_cache_index(&mut self.inline_cache_index, op_offset, slot);
990 }
991
992 fn write_inline_cache_index(index: &mut Vec<u32>, op_offset: usize, slot: usize) {
997 if op_offset >= index.len() {
998 index.resize(op_offset + 1, NO_INLINE_CACHE_SLOT);
999 }
1000 index[op_offset] = slot as u32;
1001 }
1002
1003 #[inline]
1012 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
1013 match self.inline_cache_index.get(op_offset).copied() {
1014 None | Some(NO_INLINE_CACHE_SLOT) => None,
1015 Some(slot) => Some(slot as usize),
1016 }
1017 }
1018
1019 pub(crate) fn inline_cache_slot_count(&self) -> usize {
1020 self.inline_cache_slots.len()
1021 }
1022
1023 pub(crate) fn cache_id(&self) -> u64 {
1024 self.cache_id
1025 }
1026
1027 #[cfg(feature = "vm-bench-internals")]
1034 pub fn inline_cache_slot_via_btreemap_for_bench(&self, op_offset: usize) -> Option<usize> {
1035 self.inline_cache_slots.get(&op_offset).copied()
1036 }
1037
1038 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<crate::value::HarnStr> {
1043 let mut entries = self.constant_strings.lock();
1048 if entries.len() < self.constants.len() {
1049 entries.resize(self.constants.len(), None);
1050 }
1051 if let Some(Some(existing)) = entries.get(idx) {
1052 return Some(existing.clone());
1053 }
1054 let materialized = match self.constants.get(idx)? {
1055 Constant::String(s) => crate::value::HarnStr::from(s.as_str()),
1056 _ => return None,
1057 };
1058 entries[idx] = Some(materialized.clone());
1059 Some(materialized)
1060 }
1061
1062 #[inline]
1065 #[cfg(test)]
1066 pub(crate) fn peek_adaptive_binary_cache(
1067 &self,
1068 slot: usize,
1069 ) -> Option<(AdaptiveBinaryOp, AdaptiveBinaryState)> {
1070 match self.inline_caches.lock().get(slot)? {
1071 &InlineCacheEntry::AdaptiveBinary { op, state } => Some((op, state)),
1072 _ => None,
1073 }
1074 }
1075
1076 #[inline]
1079 #[cfg(test)]
1080 pub(crate) fn peek_method_cache(&self, slot: usize) -> Option<(u16, usize, MethodCacheTarget)> {
1081 match self.inline_caches.lock().get(slot)? {
1082 &InlineCacheEntry::Method {
1083 name_idx,
1084 argc,
1085 target,
1086 } => Some((name_idx, argc, target)),
1087 _ => None,
1088 }
1089 }
1090
1091 #[inline]
1094 #[cfg(test)]
1095 pub(crate) fn peek_property_cache(&self, slot: usize) -> Option<(u16, PropertyCacheTarget)> {
1096 match self.inline_caches.lock().get(slot)? {
1097 InlineCacheEntry::Property { name_idx, target } => Some((*name_idx, target.clone())),
1098 _ => None,
1099 }
1100 }
1101
1102 #[inline]
1105 #[cfg(test)]
1106 pub(crate) fn peek_direct_call_state(&self, slot: usize) -> Option<DirectCallState> {
1107 match self.inline_caches.lock().get(slot)? {
1108 InlineCacheEntry::DirectCall { state } => Some(state.clone()),
1109 _ => None,
1110 }
1111 }
1112
1113 #[cfg(test)]
1114 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1115 if let Some(existing) = self.inline_caches.lock().get_mut(slot) {
1116 *existing = entry;
1117 }
1118 }
1119
1120 pub fn freeze_for_cache(&self) -> CachedChunk {
1121 CachedChunk {
1122 code: self.code.clone(),
1123 constants: self.constants.clone(),
1124 lines: self.lines.clone(),
1125 columns: self.columns.clone(),
1126 source_file: self.source_file.clone(),
1127 current_col: self.current_col,
1128 functions: self
1129 .functions
1130 .iter()
1131 .map(|function| function.freeze_for_cache())
1132 .collect(),
1133 inline_cache_slots: self.inline_cache_slots.clone(),
1134 local_slots: self.local_slots.clone(),
1135 binding_types: self.binding_types.clone(),
1136 references_outer_names: self.references_outer_names,
1137 }
1138 }
1139
1140 pub fn from_cached(cached: CachedChunk) -> Self {
1141 let CachedChunk {
1142 code,
1143 constants,
1144 lines,
1145 columns,
1146 source_file,
1147 current_col,
1148 functions,
1149 inline_cache_slots,
1150 local_slots,
1151 binding_types,
1152 references_outer_names,
1153 } = cached;
1154 let inline_cache_count = inline_cache_slots.len();
1155 let constants_count = constants.len();
1156 let mut inline_cache_index = Vec::new();
1163 inline_cache_index.resize(code.len(), NO_INLINE_CACHE_SLOT);
1164 for (&op_offset, &slot) in &inline_cache_slots {
1165 if op_offset < inline_cache_index.len() {
1166 inline_cache_index[op_offset] = slot as u32;
1167 }
1168 }
1169 Self {
1170 cache_id: next_chunk_cache_id(),
1171 code,
1172 constants,
1173 constant_index: None,
1175 lines,
1176 columns,
1177 source_file,
1178 current_col,
1179 functions: functions
1180 .into_iter()
1181 .map(|function| Arc::new(CompiledFunction::from_cached(function)))
1182 .collect(),
1183 inline_cache_slots,
1184 inline_cache_index,
1185 inline_caches: Arc::new(Mutex::new(vec![
1186 InlineCacheEntry::Empty;
1187 inline_cache_count
1188 ])),
1189 constant_strings: Arc::new(Mutex::new(vec![None; constants_count])),
1190 local_slots,
1191 binding_types,
1192 references_outer_names,
1193 #[cfg(debug_assertions)]
1194 balance_depth: 0,
1195 #[cfg(debug_assertions)]
1196 balance_nonlinear: 0,
1197 }
1198 }
1199
1200 #[cfg(test)]
1201 pub(crate) fn add_local_slot(
1202 &mut self,
1203 name: String,
1204 mutable: bool,
1205 scope_depth: usize,
1206 ) -> u16 {
1207 let idx = self.local_slots.len();
1208 self.local_slots.push(LocalSlotInfo {
1209 name,
1210 mutable,
1211 scope_depth,
1212 });
1213 idx as u16
1214 }
1215
1216 pub fn read_u64(&self, pos: usize) -> u64 {
1218 u64::from_be_bytes([
1219 self.code[pos],
1220 self.code[pos + 1],
1221 self.code[pos + 2],
1222 self.code[pos + 3],
1223 self.code[pos + 4],
1224 self.code[pos + 5],
1225 self.code[pos + 6],
1226 self.code[pos + 7],
1227 ])
1228 }
1229
1230 pub fn disassemble(&self, name: &str) -> String {
1234 let mut out = format!("== {name} ==\n");
1235 let mut ip = 0;
1236 while ip < self.code.len() {
1237 let op_start = ip;
1238 let op_byte = self.code[ip];
1239 let line = self.lines.get(ip).copied().unwrap_or(0);
1240 out.push_str(&format!("{ip:04} [{line:>4}] "));
1241 ip += 1;
1242
1243 if let Some(op) = Op::from_byte(op_byte) {
1244 self.disassemble_op(op, &mut ip, &mut out);
1245 debug_assert_eq!(
1246 ip,
1247 op_start + op.instruction_len(),
1248 "disassembler operand width drifted for {}",
1249 op.name(),
1250 );
1251 } else {
1252 out.push_str(&format!("UNKNOWN(0x{op_byte:02x})\n"));
1253 }
1254 }
1255 out
1256 }
1257}
1258
1259impl Default for Chunk {
1260 fn default() -> Self {
1261 Self::new()
1262 }
1263}
1264
1265#[cfg(test)]
1266#[path = "chunk_tests.rs"]
1267mod tests;