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, PartialEq, Serialize, Deserialize)]
42pub enum Constant {
43 Int(i64),
44 Float(f64),
45 String(String),
46 Bool(bool),
47 Nil,
48 Duration(i64),
49}
50
51fn constants_identical(a: &Constant, b: &Constant) -> bool {
60 match (a, b) {
61 (Constant::Float(x), Constant::Float(y)) => x.to_bits() == y.to_bits(),
62 _ => a == b,
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Hash)]
72enum ConstantKey {
73 Int(i64),
74 Float(u64),
75 String(String),
76 Bool(bool),
77 Nil,
78 Duration(i64),
79}
80
81impl From<&Constant> for ConstantKey {
82 fn from(constant: &Constant) -> Self {
83 match constant {
84 Constant::Int(value) => Self::Int(*value),
85 Constant::Float(value) => Self::Float(value.to_bits()),
86 Constant::String(value) => Self::String(value.clone()),
87 Constant::Bool(value) => Self::Bool(*value),
88 Constant::Nil => Self::Nil,
89 Constant::Duration(value) => Self::Duration(*value),
90 }
91 }
92}
93
94fn build_constant_index(constants: &[Constant]) -> HashMap<ConstantKey, u16> {
95 let mut index = HashMap::with_capacity(constants.len());
96 for (slot, constant) in constants.iter().enumerate() {
97 if let Ok(slot) = u16::try_from(slot) {
98 index.entry(ConstantKey::from(constant)).or_insert(slot);
99 }
100 }
101 index
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106pub struct LocalSlotInfo {
107 pub name: String,
108 pub mutable: bool,
109 pub scope_depth: usize,
110}
111
112impl fmt::Display for Constant {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 match self {
115 Constant::Int(n) => write!(f, "{n}"),
116 Constant::Float(n) => write!(f, "{n}"),
117 Constant::String(s) => write!(f, "\"{s}\""),
118 Constant::Bool(b) => write!(f, "{b}"),
119 Constant::Nil => write!(f, "nil"),
120 Constant::Duration(ms) => write!(f, "{ms}ms"),
121 }
122 }
123}
124
125#[derive(Debug)]
127pub struct Chunk {
128 cache_id: u64,
132 pub code: Vec<u8>,
134 pub constants: Vec<Constant>,
136 constant_index: Option<HashMap<ConstantKey, u16>>,
145 pub lines: Vec<u32>,
147 pub columns: Vec<u32>,
150 pub source_file: Option<String>,
155 current_col: u32,
157 pub functions: Vec<CompiledFunctionRef>,
159 inline_cache_slots: BTreeMap<usize, usize>,
165 inline_cache_index: Vec<u32>,
173 inline_caches: Arc<Mutex<Vec<InlineCacheEntry>>>,
177 constant_strings: Arc<Mutex<Vec<Option<crate::value::HarnStr>>>>,
181 pub(crate) local_slots: Vec<LocalSlotInfo>,
183 pub(crate) references_outer_names: bool,
199 #[cfg(debug_assertions)]
213 balance_depth: i32,
214 #[cfg(debug_assertions)]
215 balance_nonlinear: u32,
216}
217
218pub type ChunkRef = Arc<Chunk>;
219pub type CompiledFunctionRef = Arc<CompiledFunction>;
220
221impl Clone for Chunk {
222 fn clone(&self) -> Self {
223 Self {
224 cache_id: self.cache_id,
225 code: self.code.clone(),
226 constants: self.constants.clone(),
227 constant_index: self.constant_index.clone(),
228 lines: self.lines.clone(),
229 columns: self.columns.clone(),
230 source_file: self.source_file.clone(),
231 current_col: self.current_col,
232 functions: self.functions.clone(),
233 inline_cache_slots: self.inline_cache_slots.clone(),
234 inline_cache_index: self.inline_cache_index.clone(),
235 inline_caches: Arc::new(Mutex::new(vec![
236 InlineCacheEntry::Empty;
237 self.inline_cache_slot_count()
238 ])),
239 constant_strings: Arc::new(Mutex::new(vec![None; self.constants.len()])),
240 local_slots: self.local_slots.clone(),
241 references_outer_names: self.references_outer_names,
242 #[cfg(debug_assertions)]
243 balance_depth: self.balance_depth,
244 #[cfg(debug_assertions)]
245 balance_nonlinear: self.balance_nonlinear,
246 }
247 }
248}
249
250#[derive(Debug, Serialize, Deserialize)]
255pub struct CachedChunk {
256 pub(crate) code: Vec<u8>,
257 pub(crate) constants: Vec<Constant>,
258 pub(crate) lines: Vec<u32>,
259 pub(crate) columns: Vec<u32>,
260 pub(crate) source_file: Option<String>,
261 pub(crate) current_col: u32,
262 pub(crate) functions: Vec<CachedCompiledFunction>,
263 pub(crate) inline_cache_slots: BTreeMap<usize, usize>,
264 pub(crate) local_slots: Vec<LocalSlotInfo>,
265 #[serde(default)]
266 pub(crate) references_outer_names: bool,
267}
268
269#[derive(Debug, Serialize, Deserialize)]
270pub struct CachedCompiledFunction {
271 pub(crate) name: String,
272 pub(crate) type_params: Vec<String>,
273 pub(crate) nominal_type_names: Vec<String>,
274 pub(crate) params: Vec<CachedParamSlot>,
275 pub(crate) default_start: Option<usize>,
276 pub(crate) chunk: CachedChunk,
277 pub(crate) is_generator: bool,
278 pub(crate) is_stream: bool,
279 pub(crate) has_rest_param: bool,
280 pub(crate) has_runtime_type_checks: bool,
281}
282
283#[derive(Debug, Serialize, Deserialize)]
284pub(crate) struct CachedParamSlot {
285 pub(crate) name: String,
286 pub(crate) type_expr: Option<TypeExpr>,
287 pub(crate) has_default: bool,
288}
289
290impl CachedParamSlot {
291 fn thaw(self) -> ParamSlot {
292 let runtime_guard = self
293 .type_expr
294 .as_ref()
295 .map(RuntimeParamGuard::from_type_expr);
296 ParamSlot {
297 name: self.name,
298 type_expr: self.type_expr,
299 runtime_guard,
300 has_default: self.has_default,
301 }
302 }
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct ParamSlot {
312 pub name: String,
313 pub type_expr: Option<TypeExpr>,
316 #[serde(skip)]
319 pub(crate) runtime_guard: Option<RuntimeParamGuard>,
320 pub has_default: bool,
324}
325
326impl ParamSlot {
327 pub fn from_typed_param(param: &harn_parser::TypedParam) -> Self {
330 Self::from_typed_param_with_type(param, param.type_expr.clone())
331 }
332
333 pub(crate) fn from_typed_param_with_type(
334 param: &harn_parser::TypedParam,
335 type_expr: Option<TypeExpr>,
336 ) -> Self {
337 let runtime_guard = type_expr.as_ref().map(RuntimeParamGuard::from_type_expr);
338 Self {
339 name: param.name.clone(),
340 type_expr,
341 runtime_guard,
342 has_default: param.default_value.is_some(),
343 }
344 }
345
346 fn freeze_for_cache(&self) -> CachedParamSlot {
347 CachedParamSlot {
348 name: self.name.clone(),
349 type_expr: self.type_expr.clone(),
350 has_default: self.has_default,
351 }
352 }
353
354 pub fn vec_from_typed(params: &[harn_parser::TypedParam]) -> Vec<Self> {
359 params.iter().map(Self::from_typed_param).collect()
360 }
361}
362
363#[derive(Debug, Clone)]
365pub struct CompiledFunction {
366 pub name: String,
367 pub type_params: Vec<String>,
371 pub nominal_type_names: Vec<String>,
375 pub params: Vec<ParamSlot>,
376 pub default_start: Option<usize>,
378 pub chunk: ChunkRef,
379 pub is_generator: bool,
381 pub is_stream: bool,
383 pub has_rest_param: bool,
385 pub has_runtime_type_checks: bool,
390}
391
392impl CompiledFunction {
393 pub(crate) fn from_portable(portable: harn_kernel::CompiledFunction) -> Self {
394 Self {
395 name: portable.name,
396 type_params: portable.type_params,
397 nominal_type_names: portable.nominal_type_names,
398 params: portable
399 .params
400 .into_iter()
401 .map(|param| {
402 let runtime_guard = param
403 .type_expr
404 .as_ref()
405 .map(RuntimeParamGuard::from_type_expr);
406 ParamSlot {
407 name: param.name,
408 type_expr: param.type_expr,
409 runtime_guard,
410 has_default: param.has_default,
411 }
412 })
413 .collect(),
414 default_start: portable.default_start,
415 chunk: Arc::new(Chunk::from_portable((*portable.chunk).clone())),
416 is_generator: portable.is_generator,
417 is_stream: portable.is_stream,
418 has_rest_param: portable.has_rest_param,
419 has_runtime_type_checks: portable.has_runtime_type_checks,
420 }
421 }
422
423 #[cfg(test)]
424 pub(crate) fn has_runtime_type_checks_for_params(params: &[ParamSlot]) -> bool {
425 params.iter().any(|param| param.type_expr.is_some())
426 }
427
428 pub fn param_names(&self) -> impl Iterator<Item = &str> {
431 self.params.iter().map(|p| p.name.as_str())
432 }
433
434 pub fn required_param_count(&self) -> usize {
436 self.default_start.unwrap_or(self.params.len())
437 }
438
439 pub(crate) fn minimum_arg_count(&self) -> usize {
441 if self.has_rest_param {
442 self.required_param_count()
443 .min(self.params.len().saturating_sub(1))
444 } else {
445 self.required_param_count()
446 }
447 }
448
449 pub(crate) fn callee_arg_count(&self, supplied: usize) -> usize {
451 if self.has_rest_param {
452 supplied
453 } else {
454 supplied.min(self.params.len())
455 }
456 }
457
458 pub fn declares_type_param(&self, name: &str) -> bool {
459 self.type_params.iter().any(|param| param == name)
460 }
461
462 pub fn has_nominal_type(&self, name: &str) -> bool {
463 self.nominal_type_names.iter().any(|ty| ty == name)
464 }
465
466 pub(crate) fn freeze_for_cache(&self) -> CachedCompiledFunction {
467 CachedCompiledFunction {
468 name: self.name.clone(),
469 type_params: self.type_params.clone(),
470 nominal_type_names: self.nominal_type_names.clone(),
471 params: self
472 .params
473 .iter()
474 .map(ParamSlot::freeze_for_cache)
475 .collect(),
476 default_start: self.default_start,
477 chunk: self.chunk.freeze_for_cache(),
478 is_generator: self.is_generator,
479 is_stream: self.is_stream,
480 has_rest_param: self.has_rest_param,
481 has_runtime_type_checks: self.has_runtime_type_checks,
482 }
483 }
484
485 pub(crate) fn from_cached(cached: CachedCompiledFunction) -> Self {
486 Self {
487 name: cached.name,
488 type_params: cached.type_params,
489 nominal_type_names: cached.nominal_type_names,
490 params: cached
491 .params
492 .into_iter()
493 .map(CachedParamSlot::thaw)
494 .collect(),
495 default_start: cached.default_start,
496 chunk: Arc::new(Chunk::from_cached(cached.chunk)),
497 is_generator: cached.is_generator,
498 is_stream: cached.is_stream,
499 has_rest_param: cached.has_rest_param,
500 has_runtime_type_checks: cached.has_runtime_type_checks,
501 }
502 }
503}
504
505#[cfg(debug_assertions)]
522fn op_stack_delta(op: Op, count: u16) -> Option<i32> {
523 use Op::*;
524 let count = count as i32;
525 Some(match op {
526 Constant | Nil | True | False | RootHarness | GetVar | GetArgc | GetLocalSlot | Closure
528 | Dup => 1,
529 DefLet | DefVar | DefCell | SetVar | DefLocalSlot | SetLocalSlot | SetProperty
533 | SetLocalSlotProperty | ConcatAssignLocal | Pop => -1,
534 Negate | Not | GetProperty | GetPropertyOpt | CheckType | TryUnwrap | TryWrapOk | Swap
538 | PushScope | PopScope | PopIterator | PopHandler => 0,
539 Add | Sub | Mul | Div | Mod | Pow | AddInt | SubInt | MulInt | DivInt | ModInt
541 | AddFloat | SubFloat | MulFloat | DivFloat | ModFloat | Equal | NotEqual | Less
542 | Greater | LessEqual | GreaterEqual | EqualInt | NotEqualInt | LessInt | GreaterInt
543 | LessEqualInt | GreaterEqualInt | EqualFloat | NotEqualFloat | LessFloat
544 | GreaterFloat | LessEqualFloat | GreaterEqualFloat | EqualBool | NotEqualBool
545 | EqualString | NotEqualString | Contains | Subscript | SubscriptOpt => -1,
546 IterInit => -1,
549 Slice | SetSubscript | SetLocalSlotSubscript => -2,
552 BuildList | Concat | CallBuiltin => 1 - count,
554 BuildDict => 1 - 2 * count,
555 Call | MethodCall | MethodCallOpt => -count,
557 Jump | JumpIfFalse | JumpIfTrue | IterNext | Return | TailCall | Throw | TryCatchSetup
560 | Spawn | Pipe | Parallel | ParallelMap | ParallelMapStream | ParallelSettle
561 | SyncMutexEnter | SyncMutexEnterKeyed | TaskScopeEnter | TaskScopeExit | Import
562 | SelectiveImport | NamespaceImport | DeadlineSetup | DeadlineEnd | BuildEnum
563 | MatchEnum | Yield | CallSpread | CallBuiltinSpread | MethodCallSpread => return None,
564 })
565}
566
567impl Chunk {
568 pub fn from_portable(portable: harn_kernel::Chunk) -> Self {
571 let mut inline_cache_slots = BTreeMap::new();
572 let mut offset = 0usize;
573 while offset < portable.code.len() {
574 let Some(op) = Op::from_byte(portable.code[offset]) else {
575 break;
576 };
577 if is_adaptive_binary_op(op)
578 || matches!(
579 op,
580 Op::GetProperty
581 | Op::GetPropertyOpt
582 | Op::MethodCall
583 | Op::MethodCallOpt
584 | Op::MethodCallSpread
585 | Op::ConcatAssignLocal
586 | Op::Call
587 | Op::CallBuiltin
588 )
589 {
590 let slot = inline_cache_slots.len();
591 inline_cache_slots.insert(offset, slot);
592 }
593 let Some(width) = harn_kernel::program::instruction_len(op, &portable.code[offset..])
594 else {
595 break;
596 };
597 offset = offset.saturating_add(width);
598 }
599
600 let code = portable.code;
601 let constants = portable
602 .constants
603 .into_iter()
604 .map(|constant| match constant {
605 harn_kernel::Constant::Int(value) => Constant::Int(value),
606 harn_kernel::Constant::Float(value) => Constant::Float(value),
607 harn_kernel::Constant::String(value) => Constant::String(value),
608 harn_kernel::Constant::Bool(value) => Constant::Bool(value),
609 harn_kernel::Constant::Nil => Constant::Nil,
610 harn_kernel::Constant::Duration(value) => Constant::Duration(value),
611 })
612 .collect::<Vec<_>>();
613 let constant_count = constants.len();
614 let inline_cache_count = inline_cache_slots.len();
615 let mut inline_cache_index = vec![NO_INLINE_CACHE_SLOT; code.len()];
616 for (&op_offset, &slot) in &inline_cache_slots {
617 inline_cache_index[op_offset] = slot as u32;
618 }
619
620 Self {
621 cache_id: next_chunk_cache_id(),
622 code,
623 constants,
624 constant_index: None,
625 lines: portable.lines,
626 columns: portable.columns,
627 source_file: portable.source_file,
628 current_col: portable.current_col,
629 functions: portable
630 .functions
631 .into_iter()
632 .map(|function| {
633 Arc::new(CompiledFunction::from_portable(function.as_ref().clone()))
634 })
635 .collect(),
636 inline_cache_slots,
637 inline_cache_index,
638 inline_caches: Arc::new(Mutex::new(vec![
639 InlineCacheEntry::Empty;
640 inline_cache_count
641 ])),
642 constant_strings: Arc::new(Mutex::new(vec![None; constant_count])),
643 local_slots: portable
644 .local_slots
645 .into_iter()
646 .map(|slot| LocalSlotInfo {
647 name: slot.name,
648 mutable: slot.mutable,
649 scope_depth: slot.scope_depth,
650 })
651 .collect(),
652 references_outer_names: portable.references_outer_names,
653 #[cfg(debug_assertions)]
654 balance_depth: 0,
655 #[cfg(debug_assertions)]
656 balance_nonlinear: 0,
657 }
658 }
659
660 pub fn new() -> Self {
661 Self {
662 cache_id: next_chunk_cache_id(),
663 code: Vec::new(),
664 constants: Vec::new(),
665 constant_index: Some(HashMap::new()),
666 lines: Vec::new(),
667 columns: Vec::new(),
668 source_file: None,
669 current_col: 0,
670 functions: Vec::new(),
671 inline_cache_slots: BTreeMap::new(),
672 inline_cache_index: Vec::new(),
673 inline_caches: Arc::new(Mutex::new(Vec::new())),
674 constant_strings: Arc::new(Mutex::new(Vec::new())),
675 local_slots: Vec::new(),
676 references_outer_names: false,
677 #[cfg(debug_assertions)]
678 balance_depth: 0,
679 #[cfg(debug_assertions)]
680 balance_nonlinear: 0,
681 }
682 }
683
684 pub fn set_column(&mut self, col: u32) {
686 self.current_col = col;
687 }
688
689 pub fn add_constant(&mut self, constant: Constant) -> u16 {
691 if self.constant_index.is_none() {
692 self.constant_index = Some(build_constant_index(&self.constants));
693 }
694 let index_map = self
695 .constant_index
696 .as_mut()
697 .expect("constant side index was just derived");
698 debug_assert!(
699 index_map.len() <= self.constants.len(),
700 "constant side index cannot outgrow the constant pool"
701 );
702 let key = ConstantKey::from(&constant);
703 if let Some(index) = index_map.get(&key) {
704 debug_assert!(
705 self.constants
706 .get(*index as usize)
707 .is_some_and(|existing| constants_identical(existing, &constant)),
708 "constant side index drifted from the constant pool"
709 );
710 return *index;
711 }
712 let idx = self.constants.len();
713 let idx = u16::try_from(idx).expect("constant pool exceeded u16 operand space");
714 index_map.insert(key, idx);
715 self.constants.push(constant);
716 idx
717 }
718
719 pub fn emit(&mut self, op: Op, line: u32) {
721 #[cfg(debug_assertions)]
722 self.note_balance(op, 0);
723 let col = self.current_col;
724 let op_offset = self.code.len();
725 self.code.push(op as u8);
726 self.lines.push(line);
727 self.columns.push(col);
728 if is_adaptive_binary_op(op) {
729 self.register_inline_cache(op_offset);
730 }
731 if op_reads_outer_name(op) {
732 self.references_outer_names = true;
733 }
734 }
735
736 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
738 #[cfg(debug_assertions)]
739 self.note_balance(op, arg);
740 let col = self.current_col;
741 let op_offset = self.code.len();
742 self.code.push(op as u8);
743 self.code.push((arg >> 8) as u8);
744 self.code.push((arg & 0xFF) as u8);
745 self.lines.push(line);
746 self.lines.push(line);
747 self.lines.push(line);
748 self.columns.push(col);
749 self.columns.push(col);
750 self.columns.push(col);
751 if matches!(
752 op,
753 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread | Op::ConcatAssignLocal
754 ) {
755 self.register_inline_cache(op_offset);
756 }
757 if op_reads_outer_name(op) {
758 self.references_outer_names = true;
759 }
760 }
761
762 pub fn emit_set_local_slot_property(&mut self, prop_idx: u16, slot: u16, line: u32) {
765 #[cfg(debug_assertions)]
766 self.note_balance(Op::SetLocalSlotProperty, 0);
767 let col = self.current_col;
768 self.code.push(Op::SetLocalSlotProperty as u8);
769 self.code.push((prop_idx >> 8) as u8);
770 self.code.push((prop_idx & 0xFF) as u8);
771 self.code.push((slot >> 8) as u8);
772 self.code.push((slot & 0xFF) as u8);
773 for _ in 0..5 {
774 self.lines.push(line);
775 self.columns.push(col);
776 }
777 }
778
779 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
781 #[cfg(debug_assertions)]
782 self.note_balance(op, arg as u16);
783 let col = self.current_col;
784 let op_offset = self.code.len();
785 self.code.push(op as u8);
786 self.code.push(arg);
787 self.lines.push(line);
788 self.lines.push(line);
789 self.columns.push(col);
790 self.columns.push(col);
791 if matches!(op, Op::Call) {
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_call_builtin(
801 &mut self,
802 id: crate::BuiltinId,
803 name_idx: u16,
804 arg_count: u8,
805 line: u32,
806 ) {
807 #[cfg(debug_assertions)]
808 self.note_balance(Op::CallBuiltin, arg_count as u16);
809 let col = self.current_col;
810 let op_offset = self.code.len();
811 self.code.push(Op::CallBuiltin as u8);
812 self.code.extend_from_slice(&id.raw().to_be_bytes());
813 self.code.push((name_idx >> 8) as u8);
814 self.code.push((name_idx & 0xFF) as u8);
815 self.code.push(arg_count);
816 for _ in 0..12 {
817 self.lines.push(line);
818 self.columns.push(col);
819 }
820 self.register_inline_cache(op_offset);
821 self.references_outer_names = true;
822 }
823
824 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
826 #[cfg(debug_assertions)]
827 self.note_balance(Op::CallBuiltinSpread, 0);
828 let col = self.current_col;
829 self.code.push(Op::CallBuiltinSpread as u8);
830 self.code.extend_from_slice(&id.raw().to_be_bytes());
831 self.code.push((name_idx >> 8) as u8);
832 self.code.push((name_idx & 0xFF) as u8);
833 for _ in 0..11 {
834 self.lines.push(line);
835 self.columns.push(col);
836 }
837 self.references_outer_names = true;
838 }
839
840 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
842 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
843 }
844
845 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
847 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
848 }
849
850 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
851 #[cfg(debug_assertions)]
852 self.note_balance(op, arg_count 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((name_idx >> 8) as u8);
857 self.code.push((name_idx & 0xFF) as u8);
858 self.code.push(arg_count);
859 self.lines.push(line);
860 self.lines.push(line);
861 self.lines.push(line);
862 self.lines.push(line);
863 self.columns.push(col);
864 self.columns.push(col);
865 self.columns.push(col);
866 self.columns.push(col);
867 self.register_inline_cache(op_offset);
868 }
869
870 pub fn current_offset(&self) -> usize {
872 self.code.len()
873 }
874
875 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
877 #[cfg(debug_assertions)]
878 self.note_balance(op, 0);
879 let col = self.current_col;
880 self.code.push(op as u8);
881 let patch_pos = self.code.len();
882 self.code.push(0xFF);
883 self.code.push(0xFF);
884 self.lines.push(line);
885 self.lines.push(line);
886 self.lines.push(line);
887 self.columns.push(col);
888 self.columns.push(col);
889 self.columns.push(col);
890 patch_pos
891 }
892
893 pub fn patch_jump(&mut self, patch_pos: usize) {
895 let target = self.code.len() as u16;
896 self.code[patch_pos] = (target >> 8) as u8;
897 self.code[patch_pos + 1] = (target & 0xFF) as u8;
898 }
899
900 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
902 let target = target as u16;
903 self.code[patch_pos] = (target >> 8) as u8;
904 self.code[patch_pos + 1] = (target & 0xFF) as u8;
905 }
906
907 pub fn read_u16(&self, pos: usize) -> u16 {
909 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
910 }
911
912 #[cfg(debug_assertions)]
916 fn note_balance(&mut self, op: Op, count: u16) {
917 match op_stack_delta(op, count) {
918 Some(delta) => self.balance_depth += delta,
919 None => self.balance_nonlinear += 1,
920 }
921 }
922
923 fn register_inline_cache(&mut self, op_offset: usize) {
924 if self.inline_cache_slots.contains_key(&op_offset) {
925 return;
926 }
927 let mut entries = self.inline_caches.lock();
928 let slot = entries.len();
929 entries.push(InlineCacheEntry::Empty);
930 self.inline_cache_slots.insert(op_offset, slot);
931 Self::write_inline_cache_index(&mut self.inline_cache_index, op_offset, slot);
932 }
933
934 fn write_inline_cache_index(index: &mut Vec<u32>, op_offset: usize, slot: usize) {
939 if op_offset >= index.len() {
940 index.resize(op_offset + 1, NO_INLINE_CACHE_SLOT);
941 }
942 index[op_offset] = slot as u32;
943 }
944
945 #[inline]
954 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
955 match self.inline_cache_index.get(op_offset).copied() {
956 None | Some(NO_INLINE_CACHE_SLOT) => None,
957 Some(slot) => Some(slot as usize),
958 }
959 }
960
961 pub(crate) fn inline_cache_slot_count(&self) -> usize {
962 self.inline_cache_slots.len()
963 }
964
965 pub(crate) fn cache_id(&self) -> u64 {
966 self.cache_id
967 }
968
969 #[cfg(feature = "vm-bench-internals")]
976 pub fn inline_cache_slot_via_btreemap_for_bench(&self, op_offset: usize) -> Option<usize> {
977 self.inline_cache_slots.get(&op_offset).copied()
978 }
979
980 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<crate::value::HarnStr> {
985 let mut entries = self.constant_strings.lock();
990 if entries.len() < self.constants.len() {
991 entries.resize(self.constants.len(), None);
992 }
993 if let Some(Some(existing)) = entries.get(idx) {
994 return Some(existing.clone());
995 }
996 let materialized = match self.constants.get(idx)? {
997 Constant::String(s) => crate::value::HarnStr::from(s.as_str()),
998 _ => return None,
999 };
1000 entries[idx] = Some(materialized.clone());
1001 Some(materialized)
1002 }
1003
1004 #[inline]
1007 #[cfg(test)]
1008 pub(crate) fn peek_adaptive_binary_cache(
1009 &self,
1010 slot: usize,
1011 ) -> Option<(AdaptiveBinaryOp, AdaptiveBinaryState)> {
1012 match self.inline_caches.lock().get(slot)? {
1013 &InlineCacheEntry::AdaptiveBinary { op, state } => Some((op, state)),
1014 _ => None,
1015 }
1016 }
1017
1018 #[inline]
1021 #[cfg(test)]
1022 pub(crate) fn peek_method_cache(&self, slot: usize) -> Option<(u16, usize, MethodCacheTarget)> {
1023 match self.inline_caches.lock().get(slot)? {
1024 &InlineCacheEntry::Method {
1025 name_idx,
1026 argc,
1027 target,
1028 } => Some((name_idx, argc, target)),
1029 _ => None,
1030 }
1031 }
1032
1033 #[inline]
1036 #[cfg(test)]
1037 pub(crate) fn peek_property_cache(&self, slot: usize) -> Option<(u16, PropertyCacheTarget)> {
1038 match self.inline_caches.lock().get(slot)? {
1039 InlineCacheEntry::Property { name_idx, target } => Some((*name_idx, target.clone())),
1040 _ => None,
1041 }
1042 }
1043
1044 #[inline]
1047 #[cfg(test)]
1048 pub(crate) fn peek_direct_call_state(&self, slot: usize) -> Option<DirectCallState> {
1049 match self.inline_caches.lock().get(slot)? {
1050 InlineCacheEntry::DirectCall { state } => Some(state.clone()),
1051 _ => None,
1052 }
1053 }
1054
1055 #[cfg(test)]
1056 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1057 if let Some(existing) = self.inline_caches.lock().get_mut(slot) {
1058 *existing = entry;
1059 }
1060 }
1061
1062 pub fn freeze_for_cache(&self) -> CachedChunk {
1063 CachedChunk {
1064 code: self.code.clone(),
1065 constants: self.constants.clone(),
1066 lines: self.lines.clone(),
1067 columns: self.columns.clone(),
1068 source_file: self.source_file.clone(),
1069 current_col: self.current_col,
1070 functions: self
1071 .functions
1072 .iter()
1073 .map(|function| function.freeze_for_cache())
1074 .collect(),
1075 inline_cache_slots: self.inline_cache_slots.clone(),
1076 local_slots: self.local_slots.clone(),
1077 references_outer_names: self.references_outer_names,
1078 }
1079 }
1080
1081 pub fn from_cached(cached: CachedChunk) -> Self {
1082 let CachedChunk {
1083 code,
1084 constants,
1085 lines,
1086 columns,
1087 source_file,
1088 current_col,
1089 functions,
1090 inline_cache_slots,
1091 local_slots,
1092 references_outer_names,
1093 } = cached;
1094 let inline_cache_count = inline_cache_slots.len();
1095 let constants_count = constants.len();
1096 let mut inline_cache_index = Vec::new();
1103 inline_cache_index.resize(code.len(), NO_INLINE_CACHE_SLOT);
1104 for (&op_offset, &slot) in &inline_cache_slots {
1105 if op_offset < inline_cache_index.len() {
1106 inline_cache_index[op_offset] = slot as u32;
1107 }
1108 }
1109 Self {
1110 cache_id: next_chunk_cache_id(),
1111 code,
1112 constants,
1113 constant_index: None,
1115 lines,
1116 columns,
1117 source_file,
1118 current_col,
1119 functions: functions
1120 .into_iter()
1121 .map(|function| Arc::new(CompiledFunction::from_cached(function)))
1122 .collect(),
1123 inline_cache_slots,
1124 inline_cache_index,
1125 inline_caches: Arc::new(Mutex::new(vec![
1126 InlineCacheEntry::Empty;
1127 inline_cache_count
1128 ])),
1129 constant_strings: Arc::new(Mutex::new(vec![None; constants_count])),
1130 local_slots,
1131 references_outer_names,
1132 #[cfg(debug_assertions)]
1133 balance_depth: 0,
1134 #[cfg(debug_assertions)]
1135 balance_nonlinear: 0,
1136 }
1137 }
1138
1139 #[cfg(test)]
1140 pub(crate) fn add_local_slot(
1141 &mut self,
1142 name: String,
1143 mutable: bool,
1144 scope_depth: usize,
1145 ) -> u16 {
1146 let idx = self.local_slots.len();
1147 self.local_slots.push(LocalSlotInfo {
1148 name,
1149 mutable,
1150 scope_depth,
1151 });
1152 idx as u16
1153 }
1154
1155 pub fn read_u64(&self, pos: usize) -> u64 {
1157 u64::from_be_bytes([
1158 self.code[pos],
1159 self.code[pos + 1],
1160 self.code[pos + 2],
1161 self.code[pos + 3],
1162 self.code[pos + 4],
1163 self.code[pos + 5],
1164 self.code[pos + 6],
1165 self.code[pos + 7],
1166 ])
1167 }
1168
1169 pub fn disassemble(&self, name: &str) -> String {
1173 let mut out = format!("== {name} ==\n");
1174 let mut ip = 0;
1175 while ip < self.code.len() {
1176 let op_byte = self.code[ip];
1177 let line = self.lines.get(ip).copied().unwrap_or(0);
1178 out.push_str(&format!("{ip:04} [{line:>4}] "));
1179 ip += 1;
1180
1181 if let Some(op) = Op::from_byte(op_byte) {
1182 self.disassemble_op(op, &mut ip, &mut out);
1183 } else {
1184 out.push_str(&format!("UNKNOWN(0x{op_byte:02x})\n"));
1185 }
1186 }
1187 out
1188 }
1189}
1190
1191impl Default for Chunk {
1192 fn default() -> Self {
1193 Self::new()
1194 }
1195}
1196
1197#[cfg(test)]
1198#[path = "chunk_tests.rs"]
1199mod tests;