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| {
456 param
457 .type_expr
458 .as_ref()
459 .is_some_and(harn_kernel::type_contract::requires_runtime_type_check)
460 })
461 }
462
463 pub fn param_names(&self) -> impl Iterator<Item = &str> {
466 self.params.iter().map(|p| p.name.as_str())
467 }
468
469 pub fn required_param_count(&self) -> usize {
471 self.default_start.unwrap_or(self.params.len())
472 }
473
474 pub(crate) fn minimum_arg_count(&self) -> usize {
476 if self.has_rest_param {
477 self.required_param_count()
478 .min(self.params.len().saturating_sub(1))
479 } else {
480 self.required_param_count()
481 }
482 }
483
484 pub(crate) fn callee_arg_count(&self, supplied: usize) -> usize {
486 if self.has_rest_param {
487 supplied
488 } else {
489 supplied.min(self.params.len())
490 }
491 }
492
493 pub fn declares_type_param(&self, name: &str) -> bool {
494 self.type_params.iter().any(|param| param == name)
495 }
496
497 pub fn has_nominal_type(&self, name: &str) -> bool {
498 self.nominal_type_names.iter().any(|ty| ty == name)
499 }
500
501 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
502 CachedCompiledFunction {
503 name: self.name.to_string(),
504 type_params: self.type_params.clone(),
505 nominal_type_names: self.nominal_type_names.clone(),
506 params: self
507 .params
508 .iter()
509 .map(ParamSlot::freeze_for_cache)
510 .collect(),
511 default_start: self.default_start,
512 chunk: self.chunk.freeze_for_cache(),
513 is_generator: self.is_generator,
514 is_stream: self.is_stream,
515 has_rest_param: self.has_rest_param,
516 has_runtime_type_checks: self.has_runtime_type_checks,
517 }
518 }
519
520 pub(crate) fn from_cached(cached: CachedCompiledFunction) -> Self {
521 Self {
522 name: crate::value::HarnStr::from(cached.name),
523 type_params: cached.type_params,
524 nominal_type_names: cached.nominal_type_names,
525 params: cached
526 .params
527 .into_iter()
528 .map(CachedParamSlot::thaw)
529 .collect(),
530 default_start: cached.default_start,
531 chunk: Arc::new(Chunk::from_cached(cached.chunk)),
532 is_generator: cached.is_generator,
533 is_stream: cached.is_stream,
534 has_rest_param: cached.has_rest_param,
535 has_runtime_type_checks: cached.has_runtime_type_checks,
536 }
537 }
538}
539
540#[cfg(debug_assertions)]
557fn op_stack_delta(op: Op, count: u16) -> Option<i32> {
558 use Op::*;
559 let count = count as i32;
560 Some(match op {
561 Constant | Nil | True | False | RootHarness | GetVar | GetArgc | GetLocalSlot | Closure
563 | Dup => 1,
564 DefLet | DefVar | DefCell | SetVar | DefLocalSlot | SetLocalSlot | SetProperty
568 | SetLocalSlotProperty | ConcatAssignLocal | Pop => -1,
569 AssertBindingType => 0,
573 Negate | Not | GetProperty | GetPropertyOpt | CheckType | TryUnwrap | TryWrapOk | Swap
574 | PushScope | PopScope | PopIterator | PopHandler => 0,
575 Add | Sub | Mul | Div | Mod | Pow | AddInt | SubInt | MulInt | DivInt | ModInt
577 | AddFloat | SubFloat | MulFloat | DivFloat | ModFloat | Equal | NotEqual | Less
578 | Greater | LessEqual | GreaterEqual | EqualInt | NotEqualInt | LessInt | GreaterInt
579 | LessEqualInt | GreaterEqualInt | EqualFloat | NotEqualFloat | LessFloat
580 | GreaterFloat | LessEqualFloat | GreaterEqualFloat | EqualBool | NotEqualBool
581 | EqualString | NotEqualString | Contains | Subscript | SubscriptOpt => -1,
582 IterInit => -1,
585 Slice | SetSubscript | SetLocalSlotSubscript => -2,
588 BuildList | Concat | CallBuiltin => 1 - count,
590 BuildDict => 1 - 2 * count,
591 Call | MethodCall | MethodCallOpt => -count,
593 Jump
596 | JumpIfFalse
597 | JumpIfTrue
598 | IterNext
599 | Return
600 | TailCall
601 | Throw
602 | TryCatchSetup
603 | Spawn
604 | Pipe
605 | Parallel
606 | ParallelMap
607 | ParallelMapStream
608 | ParallelSettle
609 | SyncMutexEnter
610 | SyncMutexEnterKeyed
611 | TaskScopeEnter
612 | TaskScopeExit
613 | Import
614 | SelectiveImport
615 | NamespaceImport
616 | NamespaceImportMembers
617 | DeadlineSetup
618 | DeadlineEnd
619 | BuildEnum
620 | MatchEnum
621 | Yield
622 | CallSpread
623 | CallBuiltinSpread
624 | MethodCallSpread => return None,
625 })
626}
627
628impl Chunk {
629 pub fn from_portable(portable: harn_kernel::Chunk) -> Self {
632 let mut inline_cache_slots = BTreeMap::new();
633 let mut offset = 0usize;
634 while offset < portable.code.len() {
635 let Some(op) = Op::from_byte(portable.code[offset]) else {
636 break;
637 };
638 if is_adaptive_binary_op(op)
639 || matches!(
640 op,
641 Op::GetProperty
642 | Op::GetPropertyOpt
643 | Op::MethodCall
644 | Op::MethodCallOpt
645 | Op::MethodCallSpread
646 | Op::ConcatAssignLocal
647 | Op::Call
648 | Op::CallBuiltin
649 )
650 {
651 let slot = inline_cache_slots.len();
652 inline_cache_slots.insert(offset, slot);
653 }
654 let Some(width) = harn_kernel::program::instruction_len(op, &portable.code[offset..])
655 else {
656 break;
657 };
658 offset = offset.saturating_add(width);
659 }
660
661 let code = portable.code;
662 let constants = portable
663 .constants
664 .into_iter()
665 .map(|constant| match constant {
666 harn_kernel::Constant::Int(value) => Constant::Int(value),
667 harn_kernel::Constant::Float(value) => Constant::Float(value),
668 harn_kernel::Constant::String(value) => Constant::String(value),
669 harn_kernel::Constant::Bool(value) => Constant::Bool(value),
670 harn_kernel::Constant::Nil => Constant::Nil,
671 harn_kernel::Constant::Duration(value) => Constant::Duration(value),
672 })
673 .collect::<Vec<_>>();
674 let constant_count = constants.len();
675 let inline_cache_count = inline_cache_slots.len();
676 let mut inline_cache_index = vec![NO_INLINE_CACHE_SLOT; code.len()];
677 for (&op_offset, &slot) in &inline_cache_slots {
678 inline_cache_index[op_offset] = slot as u32;
679 }
680
681 Self {
682 cache_id: next_chunk_cache_id(),
683 code,
684 constants,
685 constant_index: None,
686 lines: portable.lines,
687 columns: portable.columns,
688 source_file: portable.source_file,
689 current_col: portable.current_col,
690 functions: portable
691 .functions
692 .into_iter()
693 .map(|function| {
694 Arc::new(CompiledFunction::from_portable(function.as_ref().clone()))
695 })
696 .collect(),
697 inline_cache_slots,
698 inline_cache_index,
699 inline_caches: Arc::new(Mutex::new(vec![
700 InlineCacheEntry::Empty;
701 inline_cache_count
702 ])),
703 constant_strings: Arc::new(
704 (0..constant_count)
705 .map(|_| std::sync::OnceLock::new())
706 .collect(),
707 ),
708 local_slots: portable
709 .local_slots
710 .into_iter()
711 .map(|slot| LocalSlotInfo {
712 name: slot.name,
713 mutable: slot.mutable,
714 scope_depth: slot.scope_depth,
715 })
716 .collect(),
717 binding_types: portable
718 .binding_types
719 .into_iter()
720 .map(|slot| BindingTypeSlot {
721 name: slot.name,
722 type_expr: slot.type_expr,
723 nominal_type_names: slot.nominal_type_names,
724 })
725 .collect(),
726 references_outer_names: portable.references_outer_names,
727 #[cfg(debug_assertions)]
728 balance_depth: 0,
729 #[cfg(debug_assertions)]
730 balance_nonlinear: 0,
731 }
732 }
733
734 pub fn new() -> Self {
735 Self {
736 cache_id: next_chunk_cache_id(),
737 code: Vec::new(),
738 constants: Vec::new(),
739 constant_index: Some(HashMap::new()),
740 lines: Vec::new(),
741 columns: Vec::new(),
742 source_file: None,
743 current_col: 0,
744 functions: Vec::new(),
745 inline_cache_slots: BTreeMap::new(),
746 inline_cache_index: Vec::new(),
747 inline_caches: Arc::new(Mutex::new(Vec::new())),
748 constant_strings: Arc::new(Vec::new()),
749 local_slots: Vec::new(),
750 binding_types: Vec::new(),
751 references_outer_names: false,
752 #[cfg(debug_assertions)]
753 balance_depth: 0,
754 #[cfg(debug_assertions)]
755 balance_nonlinear: 0,
756 }
757 }
758
759 pub fn set_column(&mut self, col: u32) {
761 self.current_col = col;
762 }
763
764 pub fn add_constant(&mut self, constant: Constant) -> u16 {
766 if self.constant_index.is_none() {
767 self.constant_index = Some(build_constant_index(&self.constants));
768 }
769 let index_map = self
770 .constant_index
771 .as_mut()
772 .expect("constant side index was just derived");
773 debug_assert!(
774 index_map.len() <= self.constants.len(),
775 "constant side index cannot outgrow the constant pool"
776 );
777 let key = ConstantKey::from(&constant);
778 if let Some(index) = index_map.get(&key) {
779 debug_assert!(
780 self.constants
781 .get(*index as usize)
782 .is_some_and(|existing| constants_identical(existing, &constant)),
783 "constant side index drifted from the constant pool"
784 );
785 return *index;
786 }
787 let idx = self.constants.len();
788 let idx = u16::try_from(idx).expect("constant pool exceeded u16 operand space");
789 index_map.insert(key, idx);
790 self.constants.push(constant);
791 idx
792 }
793
794 pub fn emit(&mut self, op: Op, line: u32) {
796 #[cfg(debug_assertions)]
797 self.note_balance(op, 0);
798 let col = self.current_col;
799 let op_offset = self.code.len();
800 self.code.push(op as u8);
801 self.lines.push(line);
802 self.columns.push(col);
803 if is_adaptive_binary_op(op) {
804 self.register_inline_cache(op_offset);
805 }
806 if op_reads_outer_name(op) {
807 self.references_outer_names = true;
808 }
809 }
810
811 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
813 #[cfg(debug_assertions)]
814 self.note_balance(op, arg);
815 let col = self.current_col;
816 let op_offset = self.code.len();
817 self.code.push(op as u8);
818 self.code.push((arg >> 8) as u8);
819 self.code.push((arg & 0xFF) as u8);
820 self.lines.push(line);
821 self.lines.push(line);
822 self.lines.push(line);
823 self.columns.push(col);
824 self.columns.push(col);
825 self.columns.push(col);
826 if matches!(
827 op,
828 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread | Op::ConcatAssignLocal
829 ) {
830 self.register_inline_cache(op_offset);
831 }
832 if op_reads_outer_name(op) {
833 self.references_outer_names = true;
834 }
835 }
836
837 pub fn emit_set_local_slot_property(&mut self, prop_idx: u16, slot: u16, line: u32) {
840 #[cfg(debug_assertions)]
841 self.note_balance(Op::SetLocalSlotProperty, 0);
842 let col = self.current_col;
843 self.code.push(Op::SetLocalSlotProperty as u8);
844 self.code.push((prop_idx >> 8) as u8);
845 self.code.push((prop_idx & 0xFF) as u8);
846 self.code.push((slot >> 8) as u8);
847 self.code.push((slot & 0xFF) as u8);
848 for _ in 0..5 {
849 self.lines.push(line);
850 self.columns.push(col);
851 }
852 }
853
854 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
856 #[cfg(debug_assertions)]
857 self.note_balance(op, arg as u16);
858 let col = self.current_col;
859 let op_offset = self.code.len();
860 self.code.push(op as u8);
861 self.code.push(arg);
862 self.lines.push(line);
863 self.lines.push(line);
864 self.columns.push(col);
865 self.columns.push(col);
866 if matches!(op, Op::Call) {
867 self.register_inline_cache(op_offset);
868 }
869 if op_reads_outer_name(op) {
870 self.references_outer_names = true;
871 }
872 }
873
874 pub fn emit_call_builtin(
876 &mut self,
877 id: crate::BuiltinId,
878 name_idx: u16,
879 arg_count: u8,
880 line: u32,
881 ) {
882 #[cfg(debug_assertions)]
883 self.note_balance(Op::CallBuiltin, arg_count as u16);
884 let col = self.current_col;
885 let op_offset = self.code.len();
886 self.code.push(Op::CallBuiltin as u8);
887 self.code.extend_from_slice(&id.raw().to_be_bytes());
888 self.code.push((name_idx >> 8) as u8);
889 self.code.push((name_idx & 0xFF) as u8);
890 self.code.push(arg_count);
891 for _ in 0..12 {
892 self.lines.push(line);
893 self.columns.push(col);
894 }
895 self.register_inline_cache(op_offset);
896 self.references_outer_names = true;
897 }
898
899 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
901 #[cfg(debug_assertions)]
902 self.note_balance(Op::CallBuiltinSpread, 0);
903 let col = self.current_col;
904 self.code.push(Op::CallBuiltinSpread as u8);
905 self.code.extend_from_slice(&id.raw().to_be_bytes());
906 self.code.push((name_idx >> 8) as u8);
907 self.code.push((name_idx & 0xFF) as u8);
908 for _ in 0..11 {
909 self.lines.push(line);
910 self.columns.push(col);
911 }
912 self.references_outer_names = true;
913 }
914
915 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
917 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
918 }
919
920 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
922 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
923 }
924
925 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
926 #[cfg(debug_assertions)]
927 self.note_balance(op, arg_count as u16);
928 let col = self.current_col;
929 let op_offset = self.code.len();
930 self.code.push(op as u8);
931 self.code.push((name_idx >> 8) as u8);
932 self.code.push((name_idx & 0xFF) as u8);
933 self.code.push(arg_count);
934 self.lines.push(line);
935 self.lines.push(line);
936 self.lines.push(line);
937 self.lines.push(line);
938 self.columns.push(col);
939 self.columns.push(col);
940 self.columns.push(col);
941 self.columns.push(col);
942 self.register_inline_cache(op_offset);
943 }
944
945 pub fn current_offset(&self) -> usize {
947 self.code.len()
948 }
949
950 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
952 #[cfg(debug_assertions)]
953 self.note_balance(op, 0);
954 let col = self.current_col;
955 self.code.push(op as u8);
956 let patch_pos = self.code.len();
957 self.code.push(0xFF);
958 self.code.push(0xFF);
959 self.lines.push(line);
960 self.lines.push(line);
961 self.lines.push(line);
962 self.columns.push(col);
963 self.columns.push(col);
964 self.columns.push(col);
965 patch_pos
966 }
967
968 pub fn patch_jump(&mut self, patch_pos: usize) {
970 let target = self.code.len() as u16;
971 self.code[patch_pos] = (target >> 8) as u8;
972 self.code[patch_pos + 1] = (target & 0xFF) as u8;
973 }
974
975 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
977 let target = target as u16;
978 self.code[patch_pos] = (target >> 8) as u8;
979 self.code[patch_pos + 1] = (target & 0xFF) as u8;
980 }
981
982 pub fn read_u16(&self, pos: usize) -> u16 {
984 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
985 }
986
987 #[cfg(debug_assertions)]
991 fn note_balance(&mut self, op: Op, count: u16) {
992 match op_stack_delta(op, count) {
993 Some(delta) => self.balance_depth += delta,
994 None => self.balance_nonlinear += 1,
995 }
996 }
997
998 fn register_inline_cache(&mut self, op_offset: usize) {
999 if self.inline_cache_slots.contains_key(&op_offset) {
1000 return;
1001 }
1002 let mut entries = self.inline_caches.lock();
1003 let slot = entries.len();
1004 entries.push(InlineCacheEntry::Empty);
1005 self.inline_cache_slots.insert(op_offset, slot);
1006 Self::write_inline_cache_index(&mut self.inline_cache_index, op_offset, slot);
1007 }
1008
1009 fn write_inline_cache_index(index: &mut Vec<u32>, op_offset: usize, slot: usize) {
1014 if op_offset >= index.len() {
1015 index.resize(op_offset + 1, NO_INLINE_CACHE_SLOT);
1016 }
1017 index[op_offset] = slot as u32;
1018 }
1019
1020 #[inline]
1029 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
1030 match self.inline_cache_index.get(op_offset).copied() {
1031 None | Some(NO_INLINE_CACHE_SLOT) => None,
1032 Some(slot) => Some(slot as usize),
1033 }
1034 }
1035
1036 pub(crate) fn inline_cache_slot_count(&self) -> usize {
1037 self.inline_cache_slots.len()
1038 }
1039
1040 pub(crate) fn cache_id(&self) -> u64 {
1041 self.cache_id
1042 }
1043
1044 #[cfg(feature = "vm-bench-internals")]
1051 pub fn inline_cache_slot_via_btreemap_for_bench(&self, op_offset: usize) -> Option<usize> {
1052 self.inline_cache_slots.get(&op_offset).copied()
1053 }
1054
1055 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<crate::value::HarnStr> {
1060 let slot = match self.constant_strings.get(idx) {
1061 Some(slot) => slot,
1062 None => {
1066 return match self.constants.get(idx)? {
1067 Constant::String(s) => Some(crate::value::HarnStr::from(s.as_str())),
1068 _ => None,
1069 }
1070 }
1071 };
1072 if let Some(existing) = slot.get() {
1073 return Some(existing.clone());
1074 }
1075 let materialized = match self.constants.get(idx)? {
1076 Constant::String(s) => crate::value::HarnStr::from(s.as_str()),
1077 _ => return None,
1078 };
1079 let _ = slot.set(materialized.clone());
1081 Some(materialized)
1082 }
1083
1084 #[inline]
1087 #[cfg(test)]
1088 pub(crate) fn peek_adaptive_binary_cache(
1089 &self,
1090 slot: usize,
1091 ) -> Option<(AdaptiveBinaryOp, AdaptiveBinaryState)> {
1092 match self.inline_caches.lock().get(slot)? {
1093 &InlineCacheEntry::AdaptiveBinary { op, state } => Some((op, state)),
1094 _ => None,
1095 }
1096 }
1097
1098 #[inline]
1101 #[cfg(test)]
1102 pub(crate) fn peek_method_cache(&self, slot: usize) -> Option<(u16, usize, MethodCacheTarget)> {
1103 match self.inline_caches.lock().get(slot)? {
1104 &InlineCacheEntry::Method {
1105 name_idx,
1106 argc,
1107 target,
1108 } => Some((name_idx, argc, target)),
1109 _ => None,
1110 }
1111 }
1112
1113 #[inline]
1116 #[cfg(test)]
1117 pub(crate) fn peek_property_cache(&self, slot: usize) -> Option<(u16, PropertyCacheTarget)> {
1118 match self.inline_caches.lock().get(slot)? {
1119 InlineCacheEntry::Property { name_idx, target } => Some((*name_idx, target.clone())),
1120 _ => None,
1121 }
1122 }
1123
1124 #[inline]
1127 #[cfg(test)]
1128 pub(crate) fn peek_direct_call_state(&self, slot: usize) -> Option<DirectCallState> {
1129 match self.inline_caches.lock().get(slot)? {
1130 InlineCacheEntry::DirectCall { state } => Some(state.clone()),
1131 _ => None,
1132 }
1133 }
1134
1135 #[cfg(test)]
1136 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1137 if let Some(existing) = self.inline_caches.lock().get_mut(slot) {
1138 *existing = entry;
1139 }
1140 }
1141
1142 pub fn freeze_for_cache(&self) -> CachedChunk {
1143 CachedChunk {
1144 code: self.code.clone(),
1145 constants: self.constants.clone(),
1146 lines: self.lines.clone(),
1147 columns: self.columns.clone(),
1148 source_file: self.source_file.clone(),
1149 current_col: self.current_col,
1150 functions: self
1151 .functions
1152 .iter()
1153 .map(|function| function.freeze_for_cache())
1154 .collect(),
1155 inline_cache_slots: self.inline_cache_slots.clone(),
1156 local_slots: self.local_slots.clone(),
1157 binding_types: self.binding_types.clone(),
1158 references_outer_names: self.references_outer_names,
1159 }
1160 }
1161
1162 pub fn from_cached(cached: CachedChunk) -> Self {
1163 let CachedChunk {
1164 code,
1165 constants,
1166 lines,
1167 columns,
1168 source_file,
1169 current_col,
1170 functions,
1171 inline_cache_slots,
1172 local_slots,
1173 binding_types,
1174 references_outer_names,
1175 } = cached;
1176 let inline_cache_count = inline_cache_slots.len();
1177 let constants_count = constants.len();
1178 let mut inline_cache_index = Vec::new();
1185 inline_cache_index.resize(code.len(), NO_INLINE_CACHE_SLOT);
1186 for (&op_offset, &slot) in &inline_cache_slots {
1187 if op_offset < inline_cache_index.len() {
1188 inline_cache_index[op_offset] = slot as u32;
1189 }
1190 }
1191 Self {
1192 cache_id: next_chunk_cache_id(),
1193 code,
1194 constants,
1195 constant_index: None,
1197 lines,
1198 columns,
1199 source_file,
1200 current_col,
1201 functions: functions
1202 .into_iter()
1203 .map(|function| Arc::new(CompiledFunction::from_cached(function)))
1204 .collect(),
1205 inline_cache_slots,
1206 inline_cache_index,
1207 inline_caches: Arc::new(Mutex::new(vec![
1208 InlineCacheEntry::Empty;
1209 inline_cache_count
1210 ])),
1211 constant_strings: Arc::new(
1212 (0..constants_count)
1213 .map(|_| std::sync::OnceLock::new())
1214 .collect(),
1215 ),
1216 local_slots,
1217 binding_types,
1218 references_outer_names,
1219 #[cfg(debug_assertions)]
1220 balance_depth: 0,
1221 #[cfg(debug_assertions)]
1222 balance_nonlinear: 0,
1223 }
1224 }
1225
1226 #[cfg(test)]
1227 pub(crate) fn add_local_slot(
1228 &mut self,
1229 name: String,
1230 mutable: bool,
1231 scope_depth: usize,
1232 ) -> u16 {
1233 let idx = self.local_slots.len();
1234 self.local_slots.push(LocalSlotInfo {
1235 name,
1236 mutable,
1237 scope_depth,
1238 });
1239 idx as u16
1240 }
1241
1242 pub fn read_u64(&self, pos: usize) -> u64 {
1244 u64::from_be_bytes([
1245 self.code[pos],
1246 self.code[pos + 1],
1247 self.code[pos + 2],
1248 self.code[pos + 3],
1249 self.code[pos + 4],
1250 self.code[pos + 5],
1251 self.code[pos + 6],
1252 self.code[pos + 7],
1253 ])
1254 }
1255
1256 pub fn disassemble(&self, name: &str) -> String {
1260 let mut out = format!("== {name} ==\n");
1261 let mut ip = 0;
1262 while ip < self.code.len() {
1263 let op_start = ip;
1264 let op_byte = self.code[ip];
1265 let line = self.lines.get(ip).copied().unwrap_or(0);
1266 out.push_str(&format!("{ip:04} [{line:>4}] "));
1267 ip += 1;
1268
1269 if let Some(op) = Op::from_byte(op_byte) {
1270 self.disassemble_op(op, &mut ip, &mut out);
1271 debug_assert_eq!(
1272 ip,
1273 op_start + op.instruction_len(),
1274 "disassembler operand width drifted for {}",
1275 op.name(),
1276 );
1277 } else {
1278 out.push_str(&format!("UNKNOWN(0x{op_byte:02x})\n"));
1279 }
1280 }
1281 out
1282 }
1283}
1284
1285impl Default for Chunk {
1286 fn default() -> Self {
1287 Self::new()
1288 }
1289}
1290
1291#[cfg(test)]
1292#[path = "chunk_tests.rs"]
1293mod tests;