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<Vec<std::sync::OnceLock<crate::value::HarnStr>>>,
202 pub(crate) local_slots: Vec<LocalSlotInfo>,
204 pub(crate) binding_types: Vec<BindingTypeSlot>,
207 pub(crate) references_outer_names: bool,
223 #[cfg(debug_assertions)]
237 balance_depth: i32,
238 #[cfg(debug_assertions)]
239 balance_nonlinear: u32,
240}
241
242pub type ChunkRef = Arc<Chunk>;
243pub type CompiledFunctionRef = Arc<CompiledFunction>;
244
245impl Clone for Chunk {
246 fn clone(&self) -> Self {
247 Self {
248 cache_id: self.cache_id,
249 code: self.code.clone(),
250 constants: self.constants.clone(),
251 constant_index: self.constant_index.clone(),
252 lines: self.lines.clone(),
253 columns: self.columns.clone(),
254 source_file: self.source_file.clone(),
255 current_col: self.current_col,
256 functions: self.functions.clone(),
257 inline_cache_slots: self.inline_cache_slots.clone(),
258 inline_cache_index: self.inline_cache_index.clone(),
259 inline_caches: Arc::new(Mutex::new(vec![
260 InlineCacheEntry::Empty;
261 self.inline_cache_slot_count()
262 ])),
263 constant_strings: Arc::clone(&self.constant_strings),
265 local_slots: self.local_slots.clone(),
266 binding_types: self.binding_types.clone(),
267 references_outer_names: self.references_outer_names,
268 #[cfg(debug_assertions)]
269 balance_depth: self.balance_depth,
270 #[cfg(debug_assertions)]
271 balance_nonlinear: self.balance_nonlinear,
272 }
273 }
274}
275
276#[derive(Clone, Debug, Serialize, Deserialize)]
281pub struct CachedChunk {
282 pub(crate) code: Vec<u8>,
283 pub(crate) constants: Vec<Constant>,
284 pub(crate) lines: Vec<u32>,
285 pub(crate) columns: Vec<u32>,
286 pub(crate) source_file: Option<String>,
287 pub(crate) current_col: u32,
288 pub(crate) functions: Vec<CachedCompiledFunction>,
289 pub(crate) inline_cache_slots: BTreeMap<usize, usize>,
290 pub(crate) local_slots: Vec<LocalSlotInfo>,
291 #[serde(default)]
292 pub(crate) binding_types: Vec<BindingTypeSlot>,
293 #[serde(default)]
294 pub(crate) references_outer_names: bool,
295}
296
297#[derive(Clone, Debug, Serialize, Deserialize)]
298pub struct CachedCompiledFunction {
299 pub(crate) name: String,
300 pub(crate) type_params: Vec<String>,
301 pub(crate) nominal_type_names: Vec<String>,
302 pub(crate) params: Vec<CachedParamSlot>,
303 pub(crate) default_start: Option<usize>,
304 pub(crate) chunk: CachedChunk,
305 pub(crate) is_generator: bool,
306 pub(crate) is_stream: bool,
307 pub(crate) has_rest_param: bool,
308 pub(crate) has_runtime_type_checks: bool,
309}
310
311#[derive(Clone, Debug, Serialize, Deserialize)]
312pub(crate) struct CachedParamSlot {
313 pub(crate) name: String,
314 pub(crate) type_expr: Option<TypeExpr>,
315 pub(crate) has_default: bool,
316}
317
318impl CachedParamSlot {
319 fn thaw(self) -> ParamSlot {
320 let runtime_guard = self
321 .type_expr
322 .as_ref()
323 .map(RuntimeParamGuard::from_type_expr);
324 ParamSlot {
325 name: self.name,
326 type_expr: self.type_expr,
327 runtime_guard,
328 has_default: self.has_default,
329 }
330 }
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct ParamSlot {
340 pub name: String,
341 pub type_expr: Option<TypeExpr>,
344 #[serde(skip)]
347 pub(crate) runtime_guard: Option<RuntimeParamGuard>,
348 pub has_default: bool,
352}
353
354impl ParamSlot {
355 pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
358 Self::from_typed_param_with_type(param, param.type_expr.clone())
359 }
360
361 pub(crate) fn from_typed_param_with_type(
362 param: &harn_parser::TypedParam,
363 type_expr: Option<TypeExpr>,
364 ) -> Self {
365 let runtime_guard = type_expr.as_ref().map(RuntimeParamGuard::from_type_expr);
366 Self {
367 name: param.name.clone(),
368 type_expr,
369 runtime_guard,
370 has_default: param.default_value.is_some(),
371 }
372 }
373
374 fn freeze_for_cache(&self) -> CachedParamSlot {
375 CachedParamSlot {
376 name: self.name.clone(),
377 type_expr: self.type_expr.clone(),
378 has_default: self.has_default,
379 }
380 }
381
382 pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
387 params.iter().map(Self::from_typed_param).collect()
388 }
389}
390
391#[derive(Debug, Clone)]
393pub struct CompiledFunction {
394 pub name: crate::value::HarnStr,
397 pub type_params: Vec<String>,
401 pub nominal_type_names: Vec<String>,
405 pub params: Vec<ParamSlot>,
406 pub default_start: Option<usize>,
408 pub chunk: ChunkRef,
409 pub is_generator: bool,
411 pub is_stream: bool,
413 pub has_rest_param: bool,
415 pub has_runtime_type_checks: bool,
420}
421
422impl CompiledFunction {
423 pub(crate) fn from_portable(portable: harn_kernel::CompiledFunction) -> Self {
424 Self {
425 name: crate::value::HarnStr::from(portable.name),
426 type_params: portable.type_params,
427 nominal_type_names: portable.nominal_type_names,
428 params: portable
429 .params
430 .into_iter()
431 .map(|param| {
432 let runtime_guard = param
433 .type_expr
434 .as_ref()
435 .map(RuntimeParamGuard::from_type_expr);
436 ParamSlot {
437 name: param.name,
438 type_expr: param.type_expr,
439 runtime_guard,
440 has_default: param.has_default,
441 }
442 })
443 .collect(),
444 default_start: portable.default_start,
445 chunk: Arc::new(Chunk::from_portable((*portable.chunk).clone())),
446 is_generator: portable.is_generator,
447 is_stream: portable.is_stream,
448 has_rest_param: portable.has_rest_param,
449 has_runtime_type_checks: portable.has_runtime_type_checks,
450 }
451 }
452
453 #[cfg(test)]
454 pub(crate) fn has_runtime_type_checks_for_params(params: &[ParamSlot]) -> bool {
455 params.iter().any(|param| param.type_expr.is_some())
456 }
457
458 pub fn param_names(&self) -> impl Iterator<Item = &str> {
461 self.params.iter().map(|p| p.name.as_str())
462 }
463
464 pub fn required_param_count(&self) -> usize {
466 self.default_start.unwrap_or(self.params.len())
467 }
468
469 pub(crate) fn minimum_arg_count(&self) -> usize {
471 if self.has_rest_param {
472 self.required_param_count()
473 .min(self.params.len().saturating_sub(1))
474 } else {
475 self.required_param_count()
476 }
477 }
478
479 pub(crate) fn callee_arg_count(&self, supplied: usize) -> usize {
481 if self.has_rest_param {
482 supplied
483 } else {
484 supplied.min(self.params.len())
485 }
486 }
487
488 pub fn declares_type_param(&self, name: &str) -> bool {
489 self.type_params.iter().any(|param| param == name)
490 }
491
492 pub fn has_nominal_type(&self, name: &str) -> bool {
493 self.nominal_type_names.iter().any(|ty| ty == name)
494 }
495
496 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
497 CachedCompiledFunction {
498 name: self.name.to_string(),
499 type_params: self.type_params.clone(),
500 nominal_type_names: self.nominal_type_names.clone(),
501 params: self
502 .params
503 .iter()
504 .map(ParamSlot::freeze_for_cache)
505 .collect(),
506 default_start: self.default_start,
507 chunk: self.chunk.freeze_for_cache(),
508 is_generator: self.is_generator,
509 is_stream: self.is_stream,
510 has_rest_param: self.has_rest_param,
511 has_runtime_type_checks: self.has_runtime_type_checks,
512 }
513 }
514
515 pub(crate) fn from_cached(cached: CachedCompiledFunction) -> Self {
516 Self {
517 name: crate::value::HarnStr::from(cached.name),
518 type_params: cached.type_params,
519 nominal_type_names: cached.nominal_type_names,
520 params: cached
521 .params
522 .into_iter()
523 .map(CachedParamSlot::thaw)
524 .collect(),
525 default_start: cached.default_start,
526 chunk: Arc::new(Chunk::from_cached(cached.chunk)),
527 is_generator: cached.is_generator,
528 is_stream: cached.is_stream,
529 has_rest_param: cached.has_rest_param,
530 has_runtime_type_checks: cached.has_runtime_type_checks,
531 }
532 }
533}
534
535#[cfg(debug_assertions)]
552fn op_stack_delta(op: Op, count: u16) -> Option<i32> {
553 use Op::*;
554 let count = count as i32;
555 Some(match op {
556 Constant | Nil | True | False | RootHarness | GetVar | GetArgc | GetLocalSlot | Closure
558 | Dup => 1,
559 DefLet | DefVar | DefCell | SetVar | DefLocalSlot | SetLocalSlot | SetProperty
563 | SetLocalSlotProperty | ConcatAssignLocal | Pop => -1,
564 AssertBindingType => 0,
568 Negate | Not | GetProperty | GetPropertyOpt | CheckType | TryUnwrap | TryWrapOk | Swap
569 | PushScope | PopScope | PopIterator | PopHandler => 0,
570 Add | Sub | Mul | Div | Mod | Pow | AddInt | SubInt | MulInt | DivInt | ModInt
572 | AddFloat | SubFloat | MulFloat | DivFloat | ModFloat | Equal | NotEqual | Less
573 | Greater | LessEqual | GreaterEqual | EqualInt | NotEqualInt | LessInt | GreaterInt
574 | LessEqualInt | GreaterEqualInt | EqualFloat | NotEqualFloat | LessFloat
575 | GreaterFloat | LessEqualFloat | GreaterEqualFloat | EqualBool | NotEqualBool
576 | EqualString | NotEqualString | Contains | Subscript | SubscriptOpt => -1,
577 IterInit => -1,
580 Slice | SetSubscript | SetLocalSlotSubscript => -2,
583 BuildList | Concat | CallBuiltin => 1 - count,
585 BuildDict => 1 - 2 * count,
586 Call | MethodCall | MethodCallOpt => -count,
588 Jump
591 | JumpIfFalse
592 | JumpIfTrue
593 | IterNext
594 | Return
595 | TailCall
596 | Throw
597 | TryCatchSetup
598 | Spawn
599 | Pipe
600 | Parallel
601 | ParallelMap
602 | ParallelMapStream
603 | ParallelSettle
604 | SyncMutexEnter
605 | SyncMutexEnterKeyed
606 | TaskScopeEnter
607 | TaskScopeExit
608 | Import
609 | SelectiveImport
610 | NamespaceImport
611 | NamespaceImportMembers
612 | DeadlineSetup
613 | DeadlineEnd
614 | BuildEnum
615 | MatchEnum
616 | Yield
617 | CallSpread
618 | CallBuiltinSpread
619 | MethodCallSpread => return None,
620 })
621}
622
623impl Chunk {
624 pub fn from_portable(portable: harn_kernel::Chunk) -> Self {
627 let mut inline_cache_slots = BTreeMap::new();
628 let mut offset = 0usize;
629 while offset < portable.code.len() {
630 let Some(op) = Op::from_byte(portable.code[offset]) else {
631 break;
632 };
633 if is_adaptive_binary_op(op)
634 || matches!(
635 op,
636 Op::GetProperty
637 | Op::GetPropertyOpt
638 | Op::MethodCall
639 | Op::MethodCallOpt
640 | Op::MethodCallSpread
641 | Op::ConcatAssignLocal
642 | Op::Call
643 | Op::CallBuiltin
644 )
645 {
646 let slot = inline_cache_slots.len();
647 inline_cache_slots.insert(offset, slot);
648 }
649 let Some(width) = harn_kernel::program::instruction_len(op, &portable.code[offset..])
650 else {
651 break;
652 };
653 offset = offset.saturating_add(width);
654 }
655
656 let code = portable.code;
657 let constants = portable
658 .constants
659 .into_iter()
660 .map(|constant| match constant {
661 harn_kernel::Constant::Int(value) => Constant::Int(value),
662 harn_kernel::Constant::Float(value) => Constant::Float(value),
663 harn_kernel::Constant::String(value) => Constant::String(value),
664 harn_kernel::Constant::Bool(value) => Constant::Bool(value),
665 harn_kernel::Constant::Nil => Constant::Nil,
666 harn_kernel::Constant::Duration(value) => Constant::Duration(value),
667 })
668 .collect::<Vec<_>>();
669 let constant_count = constants.len();
670 let inline_cache_count = inline_cache_slots.len();
671 let mut inline_cache_index = vec![NO_INLINE_CACHE_SLOT; code.len()];
672 for (&op_offset, &slot) in &inline_cache_slots {
673 inline_cache_index[op_offset] = slot as u32;
674 }
675
676 Self {
677 cache_id: next_chunk_cache_id(),
678 code,
679 constants,
680 constant_index: None,
681 lines: portable.lines,
682 columns: portable.columns,
683 source_file: portable.source_file,
684 current_col: portable.current_col,
685 functions: portable
686 .functions
687 .into_iter()
688 .map(|function| {
689 Arc::new(CompiledFunction::from_portable(function.as_ref().clone()))
690 })
691 .collect(),
692 inline_cache_slots,
693 inline_cache_index,
694 inline_caches: Arc::new(Mutex::new(vec![
695 InlineCacheEntry::Empty;
696 inline_cache_count
697 ])),
698 constant_strings: Arc::new(
699 (0..constant_count)
700 .map(|_| std::sync::OnceLock::new())
701 .collect(),
702 ),
703 local_slots: portable
704 .local_slots
705 .into_iter()
706 .map(|slot| LocalSlotInfo {
707 name: slot.name,
708 mutable: slot.mutable,
709 scope_depth: slot.scope_depth,
710 })
711 .collect(),
712 binding_types: portable
713 .binding_types
714 .into_iter()
715 .map(|slot| BindingTypeSlot {
716 name: slot.name,
717 type_expr: slot.type_expr,
718 nominal_type_names: slot.nominal_type_names,
719 })
720 .collect(),
721 references_outer_names: portable.references_outer_names,
722 #[cfg(debug_assertions)]
723 balance_depth: 0,
724 #[cfg(debug_assertions)]
725 balance_nonlinear: 0,
726 }
727 }
728
729 pub fn new() -> Self {
730 Self {
731 cache_id: next_chunk_cache_id(),
732 code: Vec::new(),
733 constants: Vec::new(),
734 constant_index: Some(HashMap::new()),
735 lines: Vec::new(),
736 columns: Vec::new(),
737 source_file: None,
738 current_col: 0,
739 functions: Vec::new(),
740 inline_cache_slots: BTreeMap::new(),
741 inline_cache_index: Vec::new(),
742 inline_caches: Arc::new(Mutex::new(Vec::new())),
743 constant_strings: Arc::new(Vec::new()),
744 local_slots: Vec::new(),
745 binding_types: Vec::new(),
746 references_outer_names: false,
747 #[cfg(debug_assertions)]
748 balance_depth: 0,
749 #[cfg(debug_assertions)]
750 balance_nonlinear: 0,
751 }
752 }
753
754 pub fn set_column(&mut self, col: u32) {
756 self.current_col = col;
757 }
758
759 pub fn add_constant(&mut self, constant: Constant) -> u16 {
761 if self.constant_index.is_none() {
762 self.constant_index = Some(build_constant_index(&self.constants));
763 }
764 let index_map = self
765 .constant_index
766 .as_mut()
767 .expect("constant side index was just derived");
768 debug_assert!(
769 index_map.len() <= self.constants.len(),
770 "constant side index cannot outgrow the constant pool"
771 );
772 let key = ConstantKey::from(&constant);
773 if let Some(index) = index_map.get(&key) {
774 debug_assert!(
775 self.constants
776 .get(*index as usize)
777 .is_some_and(|existing| constants_identical(existing, &constant)),
778 "constant side index drifted from the constant pool"
779 );
780 return *index;
781 }
782 let idx = self.constants.len();
783 let idx = u16::try_from(idx).expect("constant pool exceeded u16 operand space");
784 index_map.insert(key, idx);
785 self.constants.push(constant);
786 idx
787 }
788
789 pub fn emit(&mut self, op: Op, line: u32) {
791 #[cfg(debug_assertions)]
792 self.note_balance(op, 0);
793 let col = self.current_col;
794 let op_offset = self.code.len();
795 self.code.push(op as u8);
796 self.lines.push(line);
797 self.columns.push(col);
798 if is_adaptive_binary_op(op) {
799 self.register_inline_cache(op_offset);
800 }
801 if op_reads_outer_name(op) {
802 self.references_outer_names = true;
803 }
804 }
805
806 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
808 #[cfg(debug_assertions)]
809 self.note_balance(op, arg);
810 let col = self.current_col;
811 let op_offset = self.code.len();
812 self.code.push(op as u8);
813 self.code.push((arg >> 8) as u8);
814 self.code.push((arg & 0xFF) as u8);
815 self.lines.push(line);
816 self.lines.push(line);
817 self.lines.push(line);
818 self.columns.push(col);
819 self.columns.push(col);
820 self.columns.push(col);
821 if matches!(
822 op,
823 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread | Op::ConcatAssignLocal
824 ) {
825 self.register_inline_cache(op_offset);
826 }
827 if op_reads_outer_name(op) {
828 self.references_outer_names = true;
829 }
830 }
831
832 pub fn emit_set_local_slot_property(&mut self, prop_idx: u16, slot: u16, line: u32) {
835 #[cfg(debug_assertions)]
836 self.note_balance(Op::SetLocalSlotProperty, 0);
837 let col = self.current_col;
838 self.code.push(Op::SetLocalSlotProperty as u8);
839 self.code.push((prop_idx >> 8) as u8);
840 self.code.push((prop_idx & 0xFF) as u8);
841 self.code.push((slot >> 8) as u8);
842 self.code.push((slot & 0xFF) as u8);
843 for _ in 0..5 {
844 self.lines.push(line);
845 self.columns.push(col);
846 }
847 }
848
849 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
851 #[cfg(debug_assertions)]
852 self.note_balance(op, arg as u16);
853 let col = self.current_col;
854 let op_offset = self.code.len();
855 self.code.push(op as u8);
856 self.code.push(arg);
857 self.lines.push(line);
858 self.lines.push(line);
859 self.columns.push(col);
860 self.columns.push(col);
861 if matches!(op, Op::Call) {
862 self.register_inline_cache(op_offset);
863 }
864 if op_reads_outer_name(op) {
865 self.references_outer_names = true;
866 }
867 }
868
869 pub fn emit_call_builtin(
871 &mut self,
872 id: crate::BuiltinId,
873 name_idx: u16,
874 arg_count: u8,
875 line: u32,
876 ) {
877 #[cfg(debug_assertions)]
878 self.note_balance(Op::CallBuiltin, arg_count as u16);
879 let col = self.current_col;
880 let op_offset = self.code.len();
881 self.code.push(Op::CallBuiltin as u8);
882 self.code.extend_from_slice(&id.raw().to_be_bytes());
883 self.code.push((name_idx >> 8) as u8);
884 self.code.push((name_idx & 0xFF) as u8);
885 self.code.push(arg_count);
886 for _ in 0..12 {
887 self.lines.push(line);
888 self.columns.push(col);
889 }
890 self.register_inline_cache(op_offset);
891 self.references_outer_names = true;
892 }
893
894 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
896 #[cfg(debug_assertions)]
897 self.note_balance(Op::CallBuiltinSpread, 0);
898 let col = self.current_col;
899 self.code.push(Op::CallBuiltinSpread as u8);
900 self.code.extend_from_slice(&id.raw().to_be_bytes());
901 self.code.push((name_idx >> 8) as u8);
902 self.code.push((name_idx & 0xFF) as u8);
903 for _ in 0..11 {
904 self.lines.push(line);
905 self.columns.push(col);
906 }
907 self.references_outer_names = true;
908 }
909
910 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
912 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
913 }
914
915 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
917 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
918 }
919
920 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
921 #[cfg(debug_assertions)]
922 self.note_balance(op, arg_count as u16);
923 let col = self.current_col;
924 let op_offset = self.code.len();
925 self.code.push(op as u8);
926 self.code.push((name_idx >> 8) as u8);
927 self.code.push((name_idx & 0xFF) as u8);
928 self.code.push(arg_count);
929 self.lines.push(line);
930 self.lines.push(line);
931 self.lines.push(line);
932 self.lines.push(line);
933 self.columns.push(col);
934 self.columns.push(col);
935 self.columns.push(col);
936 self.columns.push(col);
937 self.register_inline_cache(op_offset);
938 }
939
940 pub fn current_offset(&self) -> usize {
942 self.code.len()
943 }
944
945 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
947 #[cfg(debug_assertions)]
948 self.note_balance(op, 0);
949 let col = self.current_col;
950 self.code.push(op as u8);
951 let patch_pos = self.code.len();
952 self.code.push(0xFF);
953 self.code.push(0xFF);
954 self.lines.push(line);
955 self.lines.push(line);
956 self.lines.push(line);
957 self.columns.push(col);
958 self.columns.push(col);
959 self.columns.push(col);
960 patch_pos
961 }
962
963 pub fn patch_jump(&mut self, patch_pos: usize) {
965 let target = self.code.len() 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 patch_jump_to(&mut self, patch_pos: usize, target: usize) {
972 let target = target as u16;
973 self.code[patch_pos] = (target >> 8) as u8;
974 self.code[patch_pos + 1] = (target & 0xFF) as u8;
975 }
976
977 pub fn read_u16(&self, pos: usize) -> u16 {
979 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
980 }
981
982 #[cfg(debug_assertions)]
986 fn note_balance(&mut self, op: Op, count: u16) {
987 match op_stack_delta(op, count) {
988 Some(delta) => self.balance_depth += delta,
989 None => self.balance_nonlinear += 1,
990 }
991 }
992
993 fn register_inline_cache(&mut self, op_offset: usize) {
994 if self.inline_cache_slots.contains_key(&op_offset) {
995 return;
996 }
997 let mut entries = self.inline_caches.lock();
998 let slot = entries.len();
999 entries.push(InlineCacheEntry::Empty);
1000 self.inline_cache_slots.insert(op_offset, slot);
1001 Self::write_inline_cache_index(&mut self.inline_cache_index, op_offset, slot);
1002 }
1003
1004 fn write_inline_cache_index(index: &mut Vec<u32>, op_offset: usize, slot: usize) {
1009 if op_offset >= index.len() {
1010 index.resize(op_offset + 1, NO_INLINE_CACHE_SLOT);
1011 }
1012 index[op_offset] = slot as u32;
1013 }
1014
1015 #[inline]
1024 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
1025 match self.inline_cache_index.get(op_offset).copied() {
1026 None | Some(NO_INLINE_CACHE_SLOT) => None,
1027 Some(slot) => Some(slot as usize),
1028 }
1029 }
1030
1031 pub(crate) fn inline_cache_slot_count(&self) -> usize {
1032 self.inline_cache_slots.len()
1033 }
1034
1035 pub(crate) fn cache_id(&self) -> u64 {
1036 self.cache_id
1037 }
1038
1039 #[cfg(feature = "vm-bench-internals")]
1046 pub fn inline_cache_slot_via_btreemap_for_bench(&self, op_offset: usize) -> Option<usize> {
1047 self.inline_cache_slots.get(&op_offset).copied()
1048 }
1049
1050 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<crate::value::HarnStr> {
1055 let slot = match self.constant_strings.get(idx) {
1056 Some(slot) => slot,
1057 None => {
1061 return match self.constants.get(idx)? {
1062 Constant::String(s) => Some(crate::value::HarnStr::from(s.as_str())),
1063 _ => None,
1064 }
1065 }
1066 };
1067 if let Some(existing) = slot.get() {
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 let _ = slot.set(materialized.clone());
1076 Some(materialized)
1077 }
1078
1079 #[inline]
1082 #[cfg(test)]
1083 pub(crate) fn peek_adaptive_binary_cache(
1084 &self,
1085 slot: usize,
1086 ) -> Option<(AdaptiveBinaryOp, AdaptiveBinaryState)> {
1087 match self.inline_caches.lock().get(slot)? {
1088 &InlineCacheEntry::AdaptiveBinary { op, state } => Some((op, state)),
1089 _ => None,
1090 }
1091 }
1092
1093 #[inline]
1096 #[cfg(test)]
1097 pub(crate) fn peek_method_cache(&self, slot: usize) -> Option<(u16, usize, MethodCacheTarget)> {
1098 match self.inline_caches.lock().get(slot)? {
1099 &InlineCacheEntry::Method {
1100 name_idx,
1101 argc,
1102 target,
1103 } => Some((name_idx, argc, target)),
1104 _ => None,
1105 }
1106 }
1107
1108 #[inline]
1111 #[cfg(test)]
1112 pub(crate) fn peek_property_cache(&self, slot: usize) -> Option<(u16, PropertyCacheTarget)> {
1113 match self.inline_caches.lock().get(slot)? {
1114 InlineCacheEntry::Property { name_idx, target } => Some((*name_idx, target.clone())),
1115 _ => None,
1116 }
1117 }
1118
1119 #[inline]
1122 #[cfg(test)]
1123 pub(crate) fn peek_direct_call_state(&self, slot: usize) -> Option<DirectCallState> {
1124 match self.inline_caches.lock().get(slot)? {
1125 InlineCacheEntry::DirectCall { state } => Some(state.clone()),
1126 _ => None,
1127 }
1128 }
1129
1130 #[cfg(test)]
1131 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1132 if let Some(existing) = self.inline_caches.lock().get_mut(slot) {
1133 *existing = entry;
1134 }
1135 }
1136
1137 pub fn freeze_for_cache(&self) -> CachedChunk {
1138 CachedChunk {
1139 code: self.code.clone(),
1140 constants: self.constants.clone(),
1141 lines: self.lines.clone(),
1142 columns: self.columns.clone(),
1143 source_file: self.source_file.clone(),
1144 current_col: self.current_col,
1145 functions: self
1146 .functions
1147 .iter()
1148 .map(|function| function.freeze_for_cache())
1149 .collect(),
1150 inline_cache_slots: self.inline_cache_slots.clone(),
1151 local_slots: self.local_slots.clone(),
1152 binding_types: self.binding_types.clone(),
1153 references_outer_names: self.references_outer_names,
1154 }
1155 }
1156
1157 pub fn from_cached(cached: CachedChunk) -> Self {
1158 let CachedChunk {
1159 code,
1160 constants,
1161 lines,
1162 columns,
1163 source_file,
1164 current_col,
1165 functions,
1166 inline_cache_slots,
1167 local_slots,
1168 binding_types,
1169 references_outer_names,
1170 } = cached;
1171 let inline_cache_count = inline_cache_slots.len();
1172 let constants_count = constants.len();
1173 let mut inline_cache_index = Vec::new();
1180 inline_cache_index.resize(code.len(), NO_INLINE_CACHE_SLOT);
1181 for (&op_offset, &slot) in &inline_cache_slots {
1182 if op_offset < inline_cache_index.len() {
1183 inline_cache_index[op_offset] = slot as u32;
1184 }
1185 }
1186 Self {
1187 cache_id: next_chunk_cache_id(),
1188 code,
1189 constants,
1190 constant_index: None,
1192 lines,
1193 columns,
1194 source_file,
1195 current_col,
1196 functions: functions
1197 .into_iter()
1198 .map(|function| Arc::new(CompiledFunction::from_cached(function)))
1199 .collect(),
1200 inline_cache_slots,
1201 inline_cache_index,
1202 inline_caches: Arc::new(Mutex::new(vec![
1203 InlineCacheEntry::Empty;
1204 inline_cache_count
1205 ])),
1206 constant_strings: Arc::new(
1207 (0..constants_count)
1208 .map(|_| std::sync::OnceLock::new())
1209 .collect(),
1210 ),
1211 local_slots,
1212 binding_types,
1213 references_outer_names,
1214 #[cfg(debug_assertions)]
1215 balance_depth: 0,
1216 #[cfg(debug_assertions)]
1217 balance_nonlinear: 0,
1218 }
1219 }
1220
1221 #[cfg(test)]
1222 pub(crate) fn add_local_slot(
1223 &mut self,
1224 name: String,
1225 mutable: bool,
1226 scope_depth: usize,
1227 ) -> u16 {
1228 let idx = self.local_slots.len();
1229 self.local_slots.push(LocalSlotInfo {
1230 name,
1231 mutable,
1232 scope_depth,
1233 });
1234 idx as u16
1235 }
1236
1237 pub fn read_u64(&self, pos: usize) -> u64 {
1239 u64::from_be_bytes([
1240 self.code[pos],
1241 self.code[pos + 1],
1242 self.code[pos + 2],
1243 self.code[pos + 3],
1244 self.code[pos + 4],
1245 self.code[pos + 5],
1246 self.code[pos + 6],
1247 self.code[pos + 7],
1248 ])
1249 }
1250
1251 pub fn disassemble(&self, name: &str) -> String {
1255 let mut out = format!("== {name} ==\n");
1256 let mut ip = 0;
1257 while ip < self.code.len() {
1258 let op_start = ip;
1259 let op_byte = self.code[ip];
1260 let line = self.lines.get(ip).copied().unwrap_or(0);
1261 out.push_str(&format!("{ip:04} [{line:>4}] "));
1262 ip += 1;
1263
1264 if let Some(op) = Op::from_byte(op_byte) {
1265 self.disassemble_op(op, &mut ip, &mut out);
1266 debug_assert_eq!(
1267 ip,
1268 op_start + op.instruction_len(),
1269 "disassembler operand width drifted for {}",
1270 op.name(),
1271 );
1272 } else {
1273 out.push_str(&format!("UNKNOWN(0x{op_byte:02x})\n"));
1274 }
1275 }
1276 out
1277 }
1278}
1279
1280impl Default for Chunk {
1281 fn default() -> Self {
1282 Self::new()
1283 }
1284}
1285
1286#[cfg(test)]
1287#[path = "chunk_tests.rs"]
1288mod tests;