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(Clone, 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(Clone, 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(Clone, 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
560 | JumpIfFalse
561 | JumpIfTrue
562 | IterNext
563 | Return
564 | TailCall
565 | Throw
566 | TryCatchSetup
567 | Spawn
568 | Pipe
569 | Parallel
570 | ParallelMap
571 | ParallelMapStream
572 | ParallelSettle
573 | SyncMutexEnter
574 | SyncMutexEnterKeyed
575 | TaskScopeEnter
576 | TaskScopeExit
577 | Import
578 | SelectiveImport
579 | NamespaceImport
580 | NamespaceImportMembers
581 | DeadlineSetup
582 | DeadlineEnd
583 | BuildEnum
584 | MatchEnum
585 | Yield
586 | CallSpread
587 | CallBuiltinSpread
588 | MethodCallSpread => return None,
589 })
590}
591
592impl Chunk {
593 pub fn from_portable(portable: harn_kernel::Chunk) -> Self {
596 let mut inline_cache_slots = BTreeMap::new();
597 let mut offset = 0usize;
598 while offset < portable.code.len() {
599 let Some(op) = Op::from_byte(portable.code[offset]) else {
600 break;
601 };
602 if is_adaptive_binary_op(op)
603 || matches!(
604 op,
605 Op::GetProperty
606 | Op::GetPropertyOpt
607 | Op::MethodCall
608 | Op::MethodCallOpt
609 | Op::MethodCallSpread
610 | Op::ConcatAssignLocal
611 | Op::Call
612 | Op::CallBuiltin
613 )
614 {
615 let slot = inline_cache_slots.len();
616 inline_cache_slots.insert(offset, slot);
617 }
618 let Some(width) = harn_kernel::program::instruction_len(op, &portable.code[offset..])
619 else {
620 break;
621 };
622 offset = offset.saturating_add(width);
623 }
624
625 let code = portable.code;
626 let constants = portable
627 .constants
628 .into_iter()
629 .map(|constant| match constant {
630 harn_kernel::Constant::Int(value) => Constant::Int(value),
631 harn_kernel::Constant::Float(value) => Constant::Float(value),
632 harn_kernel::Constant::String(value) => Constant::String(value),
633 harn_kernel::Constant::Bool(value) => Constant::Bool(value),
634 harn_kernel::Constant::Nil => Constant::Nil,
635 harn_kernel::Constant::Duration(value) => Constant::Duration(value),
636 })
637 .collect::<Vec<_>>();
638 let constant_count = constants.len();
639 let inline_cache_count = inline_cache_slots.len();
640 let mut inline_cache_index = vec![NO_INLINE_CACHE_SLOT; code.len()];
641 for (&op_offset, &slot) in &inline_cache_slots {
642 inline_cache_index[op_offset] = slot as u32;
643 }
644
645 Self {
646 cache_id: next_chunk_cache_id(),
647 code,
648 constants,
649 constant_index: None,
650 lines: portable.lines,
651 columns: portable.columns,
652 source_file: portable.source_file,
653 current_col: portable.current_col,
654 functions: portable
655 .functions
656 .into_iter()
657 .map(|function| {
658 Arc::new(CompiledFunction::from_portable(function.as_ref().clone()))
659 })
660 .collect(),
661 inline_cache_slots,
662 inline_cache_index,
663 inline_caches: Arc::new(Mutex::new(vec![
664 InlineCacheEntry::Empty;
665 inline_cache_count
666 ])),
667 constant_strings: Arc::new(Mutex::new(vec![None; constant_count])),
668 local_slots: portable
669 .local_slots
670 .into_iter()
671 .map(|slot| LocalSlotInfo {
672 name: slot.name,
673 mutable: slot.mutable,
674 scope_depth: slot.scope_depth,
675 })
676 .collect(),
677 references_outer_names: portable.references_outer_names,
678 #[cfg(debug_assertions)]
679 balance_depth: 0,
680 #[cfg(debug_assertions)]
681 balance_nonlinear: 0,
682 }
683 }
684
685 pub fn new() -> Self {
686 Self {
687 cache_id: next_chunk_cache_id(),
688 code: Vec::new(),
689 constants: Vec::new(),
690 constant_index: Some(HashMap::new()),
691 lines: Vec::new(),
692 columns: Vec::new(),
693 source_file: None,
694 current_col: 0,
695 functions: Vec::new(),
696 inline_cache_slots: BTreeMap::new(),
697 inline_cache_index: Vec::new(),
698 inline_caches: Arc::new(Mutex::new(Vec::new())),
699 constant_strings: Arc::new(Mutex::new(Vec::new())),
700 local_slots: Vec::new(),
701 references_outer_names: false,
702 #[cfg(debug_assertions)]
703 balance_depth: 0,
704 #[cfg(debug_assertions)]
705 balance_nonlinear: 0,
706 }
707 }
708
709 pub fn set_column(&mut self, col: u32) {
711 self.current_col = col;
712 }
713
714 pub fn add_constant(&mut self, constant: Constant) -> u16 {
716 if self.constant_index.is_none() {
717 self.constant_index = Some(build_constant_index(&self.constants));
718 }
719 let index_map = self
720 .constant_index
721 .as_mut()
722 .expect("constant side index was just derived");
723 debug_assert!(
724 index_map.len() <= self.constants.len(),
725 "constant side index cannot outgrow the constant pool"
726 );
727 let key = ConstantKey::from(&constant);
728 if let Some(index) = index_map.get(&key) {
729 debug_assert!(
730 self.constants
731 .get(*index as usize)
732 .is_some_and(|existing| constants_identical(existing, &constant)),
733 "constant side index drifted from the constant pool"
734 );
735 return *index;
736 }
737 let idx = self.constants.len();
738 let idx = u16::try_from(idx).expect("constant pool exceeded u16 operand space");
739 index_map.insert(key, idx);
740 self.constants.push(constant);
741 idx
742 }
743
744 pub fn emit(&mut self, op: Op, line: u32) {
746 #[cfg(debug_assertions)]
747 self.note_balance(op, 0);
748 let col = self.current_col;
749 let op_offset = self.code.len();
750 self.code.push(op as u8);
751 self.lines.push(line);
752 self.columns.push(col);
753 if is_adaptive_binary_op(op) {
754 self.register_inline_cache(op_offset);
755 }
756 if op_reads_outer_name(op) {
757 self.references_outer_names = true;
758 }
759 }
760
761 pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
763 #[cfg(debug_assertions)]
764 self.note_balance(op, arg);
765 let col = self.current_col;
766 let op_offset = self.code.len();
767 self.code.push(op as u8);
768 self.code.push((arg >> 8) as u8);
769 self.code.push((arg & 0xFF) as u8);
770 self.lines.push(line);
771 self.lines.push(line);
772 self.lines.push(line);
773 self.columns.push(col);
774 self.columns.push(col);
775 self.columns.push(col);
776 if matches!(
777 op,
778 Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread | Op::ConcatAssignLocal
779 ) {
780 self.register_inline_cache(op_offset);
781 }
782 if op_reads_outer_name(op) {
783 self.references_outer_names = true;
784 }
785 }
786
787 pub fn emit_set_local_slot_property(&mut self, prop_idx: u16, slot: u16, line: u32) {
790 #[cfg(debug_assertions)]
791 self.note_balance(Op::SetLocalSlotProperty, 0);
792 let col = self.current_col;
793 self.code.push(Op::SetLocalSlotProperty as u8);
794 self.code.push((prop_idx >> 8) as u8);
795 self.code.push((prop_idx & 0xFF) as u8);
796 self.code.push((slot >> 8) as u8);
797 self.code.push((slot & 0xFF) as u8);
798 for _ in 0..5 {
799 self.lines.push(line);
800 self.columns.push(col);
801 }
802 }
803
804 pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
806 #[cfg(debug_assertions)]
807 self.note_balance(op, arg as u16);
808 let col = self.current_col;
809 let op_offset = self.code.len();
810 self.code.push(op as u8);
811 self.code.push(arg);
812 self.lines.push(line);
813 self.lines.push(line);
814 self.columns.push(col);
815 self.columns.push(col);
816 if matches!(op, Op::Call) {
817 self.register_inline_cache(op_offset);
818 }
819 if op_reads_outer_name(op) {
820 self.references_outer_names = true;
821 }
822 }
823
824 pub fn emit_call_builtin(
826 &mut self,
827 id: crate::BuiltinId,
828 name_idx: u16,
829 arg_count: u8,
830 line: u32,
831 ) {
832 #[cfg(debug_assertions)]
833 self.note_balance(Op::CallBuiltin, arg_count as u16);
834 let col = self.current_col;
835 let op_offset = self.code.len();
836 self.code.push(Op::CallBuiltin as u8);
837 self.code.extend_from_slice(&id.raw().to_be_bytes());
838 self.code.push((name_idx >> 8) as u8);
839 self.code.push((name_idx & 0xFF) as u8);
840 self.code.push(arg_count);
841 for _ in 0..12 {
842 self.lines.push(line);
843 self.columns.push(col);
844 }
845 self.register_inline_cache(op_offset);
846 self.references_outer_names = true;
847 }
848
849 pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
851 #[cfg(debug_assertions)]
852 self.note_balance(Op::CallBuiltinSpread, 0);
853 let col = self.current_col;
854 self.code.push(Op::CallBuiltinSpread as u8);
855 self.code.extend_from_slice(&id.raw().to_be_bytes());
856 self.code.push((name_idx >> 8) as u8);
857 self.code.push((name_idx & 0xFF) as u8);
858 for _ in 0..11 {
859 self.lines.push(line);
860 self.columns.push(col);
861 }
862 self.references_outer_names = true;
863 }
864
865 pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
867 self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
868 }
869
870 pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
872 self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
873 }
874
875 fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
876 #[cfg(debug_assertions)]
877 self.note_balance(op, arg_count as u16);
878 let col = self.current_col;
879 let op_offset = self.code.len();
880 self.code.push(op as u8);
881 self.code.push((name_idx >> 8) as u8);
882 self.code.push((name_idx & 0xFF) as u8);
883 self.code.push(arg_count);
884 self.lines.push(line);
885 self.lines.push(line);
886 self.lines.push(line);
887 self.lines.push(line);
888 self.columns.push(col);
889 self.columns.push(col);
890 self.columns.push(col);
891 self.columns.push(col);
892 self.register_inline_cache(op_offset);
893 }
894
895 pub fn current_offset(&self) -> usize {
897 self.code.len()
898 }
899
900 pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
902 #[cfg(debug_assertions)]
903 self.note_balance(op, 0);
904 let col = self.current_col;
905 self.code.push(op as u8);
906 let patch_pos = self.code.len();
907 self.code.push(0xFF);
908 self.code.push(0xFF);
909 self.lines.push(line);
910 self.lines.push(line);
911 self.lines.push(line);
912 self.columns.push(col);
913 self.columns.push(col);
914 self.columns.push(col);
915 patch_pos
916 }
917
918 pub fn patch_jump(&mut self, patch_pos: usize) {
920 let target = self.code.len() as u16;
921 self.code[patch_pos] = (target >> 8) as u8;
922 self.code[patch_pos + 1] = (target & 0xFF) as u8;
923 }
924
925 pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
927 let target = target as u16;
928 self.code[patch_pos] = (target >> 8) as u8;
929 self.code[patch_pos + 1] = (target & 0xFF) as u8;
930 }
931
932 pub fn read_u16(&self, pos: usize) -> u16 {
934 ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
935 }
936
937 #[cfg(debug_assertions)]
941 fn note_balance(&mut self, op: Op, count: u16) {
942 match op_stack_delta(op, count) {
943 Some(delta) => self.balance_depth += delta,
944 None => self.balance_nonlinear += 1,
945 }
946 }
947
948 fn register_inline_cache(&mut self, op_offset: usize) {
949 if self.inline_cache_slots.contains_key(&op_offset) {
950 return;
951 }
952 let mut entries = self.inline_caches.lock();
953 let slot = entries.len();
954 entries.push(InlineCacheEntry::Empty);
955 self.inline_cache_slots.insert(op_offset, slot);
956 Self::write_inline_cache_index(&mut self.inline_cache_index, op_offset, slot);
957 }
958
959 fn write_inline_cache_index(index: &mut Vec<u32>, op_offset: usize, slot: usize) {
964 if op_offset >= index.len() {
965 index.resize(op_offset + 1, NO_INLINE_CACHE_SLOT);
966 }
967 index[op_offset] = slot as u32;
968 }
969
970 #[inline]
979 pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
980 match self.inline_cache_index.get(op_offset).copied() {
981 None | Some(NO_INLINE_CACHE_SLOT) => None,
982 Some(slot) => Some(slot as usize),
983 }
984 }
985
986 pub(crate) fn inline_cache_slot_count(&self) -> usize {
987 self.inline_cache_slots.len()
988 }
989
990 pub(crate) fn cache_id(&self) -> u64 {
991 self.cache_id
992 }
993
994 #[cfg(feature = "vm-bench-internals")]
1001 pub fn inline_cache_slot_via_btreemap_for_bench(&self, op_offset: usize) -> Option<usize> {
1002 self.inline_cache_slots.get(&op_offset).copied()
1003 }
1004
1005 pub(crate) fn constant_string_rc(&self, idx: usize) -> Option<crate::value::HarnStr> {
1010 let mut entries = self.constant_strings.lock();
1015 if entries.len() < self.constants.len() {
1016 entries.resize(self.constants.len(), None);
1017 }
1018 if let Some(Some(existing)) = entries.get(idx) {
1019 return Some(existing.clone());
1020 }
1021 let materialized = match self.constants.get(idx)? {
1022 Constant::String(s) => crate::value::HarnStr::from(s.as_str()),
1023 _ => return None,
1024 };
1025 entries[idx] = Some(materialized.clone());
1026 Some(materialized)
1027 }
1028
1029 #[inline]
1032 #[cfg(test)]
1033 pub(crate) fn peek_adaptive_binary_cache(
1034 &self,
1035 slot: usize,
1036 ) -> Option<(AdaptiveBinaryOp, AdaptiveBinaryState)> {
1037 match self.inline_caches.lock().get(slot)? {
1038 &InlineCacheEntry::AdaptiveBinary { op, state } => Some((op, state)),
1039 _ => None,
1040 }
1041 }
1042
1043 #[inline]
1046 #[cfg(test)]
1047 pub(crate) fn peek_method_cache(&self, slot: usize) -> Option<(u16, usize, MethodCacheTarget)> {
1048 match self.inline_caches.lock().get(slot)? {
1049 &InlineCacheEntry::Method {
1050 name_idx,
1051 argc,
1052 target,
1053 } => Some((name_idx, argc, target)),
1054 _ => None,
1055 }
1056 }
1057
1058 #[inline]
1061 #[cfg(test)]
1062 pub(crate) fn peek_property_cache(&self, slot: usize) -> Option<(u16, PropertyCacheTarget)> {
1063 match self.inline_caches.lock().get(slot)? {
1064 InlineCacheEntry::Property { name_idx, target } => Some((*name_idx, target.clone())),
1065 _ => None,
1066 }
1067 }
1068
1069 #[inline]
1072 #[cfg(test)]
1073 pub(crate) fn peek_direct_call_state(&self, slot: usize) -> Option<DirectCallState> {
1074 match self.inline_caches.lock().get(slot)? {
1075 InlineCacheEntry::DirectCall { state } => Some(state.clone()),
1076 _ => None,
1077 }
1078 }
1079
1080 #[cfg(test)]
1081 pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
1082 if let Some(existing) = self.inline_caches.lock().get_mut(slot) {
1083 *existing = entry;
1084 }
1085 }
1086
1087 pub fn freeze_for_cache(&self) -> CachedChunk {
1088 CachedChunk {
1089 code: self.code.clone(),
1090 constants: self.constants.clone(),
1091 lines: self.lines.clone(),
1092 columns: self.columns.clone(),
1093 source_file: self.source_file.clone(),
1094 current_col: self.current_col,
1095 functions: self
1096 .functions
1097 .iter()
1098 .map(|function| function.freeze_for_cache())
1099 .collect(),
1100 inline_cache_slots: self.inline_cache_slots.clone(),
1101 local_slots: self.local_slots.clone(),
1102 references_outer_names: self.references_outer_names,
1103 }
1104 }
1105
1106 pub fn from_cached(cached: CachedChunk) -> Self {
1107 let CachedChunk {
1108 code,
1109 constants,
1110 lines,
1111 columns,
1112 source_file,
1113 current_col,
1114 functions,
1115 inline_cache_slots,
1116 local_slots,
1117 references_outer_names,
1118 } = cached;
1119 let inline_cache_count = inline_cache_slots.len();
1120 let constants_count = constants.len();
1121 let mut inline_cache_index = Vec::new();
1128 inline_cache_index.resize(code.len(), NO_INLINE_CACHE_SLOT);
1129 for (&op_offset, &slot) in &inline_cache_slots {
1130 if op_offset < inline_cache_index.len() {
1131 inline_cache_index[op_offset] = slot as u32;
1132 }
1133 }
1134 Self {
1135 cache_id: next_chunk_cache_id(),
1136 code,
1137 constants,
1138 constant_index: None,
1140 lines,
1141 columns,
1142 source_file,
1143 current_col,
1144 functions: functions
1145 .into_iter()
1146 .map(|function| Arc::new(CompiledFunction::from_cached(function)))
1147 .collect(),
1148 inline_cache_slots,
1149 inline_cache_index,
1150 inline_caches: Arc::new(Mutex::new(vec![
1151 InlineCacheEntry::Empty;
1152 inline_cache_count
1153 ])),
1154 constant_strings: Arc::new(Mutex::new(vec![None; constants_count])),
1155 local_slots,
1156 references_outer_names,
1157 #[cfg(debug_assertions)]
1158 balance_depth: 0,
1159 #[cfg(debug_assertions)]
1160 balance_nonlinear: 0,
1161 }
1162 }
1163
1164 #[cfg(test)]
1165 pub(crate) fn add_local_slot(
1166 &mut self,
1167 name: String,
1168 mutable: bool,
1169 scope_depth: usize,
1170 ) -> u16 {
1171 let idx = self.local_slots.len();
1172 self.local_slots.push(LocalSlotInfo {
1173 name,
1174 mutable,
1175 scope_depth,
1176 });
1177 idx as u16
1178 }
1179
1180 pub fn read_u64(&self, pos: usize) -> u64 {
1182 u64::from_be_bytes([
1183 self.code[pos],
1184 self.code[pos + 1],
1185 self.code[pos + 2],
1186 self.code[pos + 3],
1187 self.code[pos + 4],
1188 self.code[pos + 5],
1189 self.code[pos + 6],
1190 self.code[pos + 7],
1191 ])
1192 }
1193
1194 pub fn disassemble(&self, name: &str) -> String {
1198 let mut out = format!("== {name} ==\n");
1199 let mut ip = 0;
1200 while ip < self.code.len() {
1201 let op_start = ip;
1202 let op_byte = self.code[ip];
1203 let line = self.lines.get(ip).copied().unwrap_or(0);
1204 out.push_str(&format!("{ip:04} [{line:>4}] "));
1205 ip += 1;
1206
1207 if let Some(op) = Op::from_byte(op_byte) {
1208 self.disassemble_op(op, &mut ip, &mut out);
1209 debug_assert_eq!(
1210 ip,
1211 op_start + op.instruction_len(),
1212 "disassembler operand width drifted for {}",
1213 op.name(),
1214 );
1215 } else {
1216 out.push_str(&format!("UNKNOWN(0x{op_byte:02x})\n"));
1217 }
1218 }
1219 out
1220 }
1221}
1222
1223impl Default for Chunk {
1224 fn default() -> Self {
1225 Self::new()
1226 }
1227}
1228
1229#[cfg(test)]
1230#[path = "chunk_tests.rs"]
1231mod tests;