Skip to main content

harn_vm/
chunk.rs

1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::fmt;
4use std::rc::Rc;
5
6/// Bytecode opcodes for the Harn VM.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[repr(u8)]
9pub enum Op {
10    /// Push a constant from the constant pool onto the stack.
11    Constant, // arg: u16 constant index
12    /// Push nil onto the stack.
13    Nil,
14    /// Push true onto the stack.
15    True,
16    /// Push false onto the stack.
17    False,
18
19    // --- Variable operations ---
20    /// Get a variable by name (from constant pool).
21    GetVar, // arg: u16 constant index (name)
22    /// Define a new immutable variable. Pops value from stack.
23    DefLet, // arg: u16 constant index (name)
24    /// Define a new mutable variable. Pops value from stack.
25    DefVar, // arg: u16 constant index (name)
26    /// Assign to an existing mutable variable. Pops value from stack.
27    SetVar, // arg: u16 constant index (name)
28    /// Push a new lexical scope onto the environment stack.
29    PushScope,
30    /// Pop the current lexical scope from the environment stack.
31    PopScope,
32
33    // --- Arithmetic ---
34    Add,
35    Sub,
36    Mul,
37    Div,
38    Mod,
39    Pow,
40    Negate,
41
42    // --- Comparison ---
43    Equal,
44    NotEqual,
45    Less,
46    Greater,
47    LessEqual,
48    GreaterEqual,
49
50    // --- Logical ---
51    Not,
52
53    // --- Control flow ---
54    /// Jump unconditionally. arg: u16 offset.
55    Jump,
56    /// Jump if top of stack is falsy. Does not pop. arg: u16 offset.
57    JumpIfFalse,
58    /// Jump if top of stack is truthy. Does not pop. arg: u16 offset.
59    JumpIfTrue,
60    /// Pop top of stack (discard).
61    Pop,
62
63    // --- Functions ---
64    /// Call a function/builtin. arg: u8 = arg count. Name is on stack below args.
65    Call,
66    /// Tail call: like Call, but replaces the current frame instead of pushing
67    /// a new one. Used for `return f(x)` to enable tail call optimization.
68    /// For builtins, behaves like a regular Call (no frame to replace).
69    TailCall,
70    /// Return from current function. Pops return value.
71    Return,
72    /// Create a closure. arg: u16 = chunk index in function table.
73    Closure,
74
75    // --- Collections ---
76    /// Build a list. arg: u16 = element count. Elements are on stack.
77    BuildList,
78    /// Build a dict. arg: u16 = entry count. Key-value pairs on stack.
79    BuildDict,
80    /// Subscript access: stack has [object, index]. Pushes result.
81    Subscript,
82    /// Optional subscript (`obj?[index]`). Like `Subscript` but pushes nil
83    /// instead of indexing when the object is nil.
84    SubscriptOpt,
85    /// Slice access: stack has [object, start_or_nil, end_or_nil]. Pushes sublist/substring.
86    Slice,
87
88    // --- Object operations ---
89    /// Property access. arg: u16 = constant index (property name).
90    GetProperty,
91    /// Optional property access (?.). Like GetProperty but returns nil
92    /// instead of erroring when the object is nil. arg: u16 = constant index.
93    GetPropertyOpt,
94    /// Property assignment. arg: u16 = constant index (property name).
95    /// Stack: [value] → assigns to the named variable's property.
96    SetProperty,
97    /// Subscript assignment. arg: u16 = constant index (variable name).
98    /// Stack: [index, value] → assigns to variable[index] = value.
99    SetSubscript,
100    /// Method call. arg1: u16 = constant index (method name), arg2: u8 = arg count.
101    MethodCall,
102    /// Optional method call (?.). Like MethodCall but returns nil if the
103    /// receiver is nil instead of dispatching. arg1: u16, arg2: u8.
104    MethodCallOpt,
105
106    // --- String ---
107    /// String concatenation of N parts. arg: u16 = part count.
108    Concat,
109
110    // --- Iteration ---
111    /// Set up a for-in loop. Expects iterable on stack. Pushes iterator state.
112    IterInit,
113    /// Advance iterator. If exhausted, jumps. arg: u16 = jump offset.
114    /// Pushes next value and the variable name is set via DefVar before the loop.
115    IterNext,
116
117    // --- Pipe ---
118    /// Pipe: pops [value, callable], invokes callable(value).
119    Pipe,
120
121    // --- Error handling ---
122    /// Pop value, raise as error.
123    Throw,
124    /// Push exception handler. arg: u16 = offset to catch handler.
125    TryCatchSetup,
126    /// Remove top exception handler (end of try body).
127    PopHandler,
128
129    // --- Concurrency ---
130    /// Execute closure N times sequentially, push results as list.
131    /// Stack: count, closure → result_list
132    Parallel,
133    /// Execute closure for each item in list, push results as list.
134    /// Stack: list, closure → result_list
135    ParallelMap,
136    /// Execute closure for each item in list, push a stream that emits in completion order.
137    /// Stack: list, closure → stream
138    ParallelMapStream,
139    /// Like ParallelMap but wraps each result in Result.Ok/Err, never fails.
140    /// Stack: list, closure → {results: [Result], succeeded: int, failed: int}
141    ParallelSettle,
142    /// Store closure for deferred execution, push TaskHandle.
143    /// Stack: closure → TaskHandle
144    Spawn,
145    /// Acquire a process-local mutex for the current lexical scope.
146    /// arg: u16 constant index (key string).
147    SyncMutexEnter,
148
149    // --- Imports ---
150    /// Import a file. arg: u16 = constant index (path string).
151    Import,
152    /// Selective import. arg1: u16 = path string, arg2: u16 = names list constant.
153    SelectiveImport,
154
155    // --- Deadline ---
156    /// Pop duration value, push deadline onto internal deadline stack.
157    DeadlineSetup,
158    /// Pop deadline from internal deadline stack.
159    DeadlineEnd,
160
161    // --- Enum ---
162    /// Build an enum variant value.
163    /// arg1: u16 = constant index (enum name), arg2: u16 = constant index (variant name),
164    /// arg3: u16 = field count. Fields are on stack.
165    BuildEnum,
166
167    // --- Match ---
168    /// Match an enum pattern. Checks enum_name + variant on the top of stack (dup'd match value).
169    /// arg1: u16 = constant index (enum name), arg2: u16 = constant index (variant name).
170    /// If match succeeds, pushes true; else pushes false.
171    MatchEnum,
172
173    // --- Loop control ---
174    /// Pop the top iterator from the iterator stack (cleanup on break from for-in).
175    PopIterator,
176
177    // --- Defaults ---
178    /// Push the number of arguments passed to the current function call.
179    GetArgc,
180
181    // --- Type checking ---
182    /// Runtime type check on a variable.
183    /// arg1: u16 = constant index (variable name),
184    /// arg2: u16 = constant index (expected type name).
185    /// Throws a TypeError if the variable's type doesn't match.
186    CheckType,
187
188    // --- Result try operator ---
189    /// Try-unwrap: if top is Result.Ok(v), replace with v. If Result.Err(e), return it.
190    TryUnwrap,
191    /// Wrap top of stack in Result.Ok unless it is already a Result.
192    TryWrapOk,
193
194    // --- Spread call ---
195    /// Call with spread arguments. Stack: [callee, args_list] -> result.
196    CallSpread,
197    /// Direct builtin call. Followed by u64 builtin ID, u16 name constant, u8 arg count.
198    /// Runtime still checks closure shadowing before using the ID.
199    CallBuiltin,
200    /// Direct builtin spread call. Followed by u64 builtin ID and u16 name constant.
201    /// Stack: [args_list] -> result.
202    CallBuiltinSpread,
203    /// Method call with spread arguments. Stack: [object, args_list] -> result.
204    /// Followed by 2 bytes for method name constant index.
205    MethodCallSpread,
206
207    // --- Misc ---
208    /// Duplicate top of stack.
209    Dup,
210    /// Swap top two stack values.
211    Swap,
212    /// Membership test: stack has [item, collection]. Pushes bool.
213    /// Works for lists (item in list), dicts (key in dict), strings (substr in string), and sets.
214    Contains,
215
216    // --- Typed arithmetic/comparison fast paths ---
217    AddInt,
218    SubInt,
219    MulInt,
220    DivInt,
221    ModInt,
222    AddFloat,
223    SubFloat,
224    MulFloat,
225    DivFloat,
226    ModFloat,
227    EqualInt,
228    NotEqualInt,
229    LessInt,
230    GreaterInt,
231    LessEqualInt,
232    GreaterEqualInt,
233    EqualFloat,
234    NotEqualFloat,
235    LessFloat,
236    GreaterFloat,
237    LessEqualFloat,
238    GreaterEqualFloat,
239    EqualBool,
240    NotEqualBool,
241    EqualString,
242    NotEqualString,
243
244    /// Yield a value from a generator. Pops value, sends through channel, suspends.
245    Yield,
246
247    // --- Slot-indexed locals ---
248    /// Get a frame-local slot. arg: u16 slot index.
249    GetLocalSlot,
250    /// Define or initialize a frame-local slot. Pops value from stack.
251    DefLocalSlot,
252    /// Assign an existing frame-local slot. Pops value from stack.
253    SetLocalSlot,
254}
255
256impl Op {
257    pub(crate) const ALL: &'static [Self] = &[
258        Op::Constant,
259        Op::Nil,
260        Op::True,
261        Op::False,
262        Op::GetVar,
263        Op::DefLet,
264        Op::DefVar,
265        Op::SetVar,
266        Op::PushScope,
267        Op::PopScope,
268        Op::Add,
269        Op::Sub,
270        Op::Mul,
271        Op::Div,
272        Op::Mod,
273        Op::Pow,
274        Op::Negate,
275        Op::Equal,
276        Op::NotEqual,
277        Op::Less,
278        Op::Greater,
279        Op::LessEqual,
280        Op::GreaterEqual,
281        Op::Not,
282        Op::Jump,
283        Op::JumpIfFalse,
284        Op::JumpIfTrue,
285        Op::Pop,
286        Op::Call,
287        Op::TailCall,
288        Op::Return,
289        Op::Closure,
290        Op::BuildList,
291        Op::BuildDict,
292        Op::Subscript,
293        Op::SubscriptOpt,
294        Op::Slice,
295        Op::GetProperty,
296        Op::GetPropertyOpt,
297        Op::SetProperty,
298        Op::SetSubscript,
299        Op::MethodCall,
300        Op::MethodCallOpt,
301        Op::Concat,
302        Op::IterInit,
303        Op::IterNext,
304        Op::Pipe,
305        Op::Throw,
306        Op::TryCatchSetup,
307        Op::PopHandler,
308        Op::Parallel,
309        Op::ParallelMap,
310        Op::ParallelMapStream,
311        Op::ParallelSettle,
312        Op::Spawn,
313        Op::SyncMutexEnter,
314        Op::Import,
315        Op::SelectiveImport,
316        Op::DeadlineSetup,
317        Op::DeadlineEnd,
318        Op::BuildEnum,
319        Op::MatchEnum,
320        Op::PopIterator,
321        Op::GetArgc,
322        Op::CheckType,
323        Op::TryUnwrap,
324        Op::TryWrapOk,
325        Op::CallSpread,
326        Op::CallBuiltin,
327        Op::CallBuiltinSpread,
328        Op::MethodCallSpread,
329        Op::Dup,
330        Op::Swap,
331        Op::Contains,
332        Op::AddInt,
333        Op::SubInt,
334        Op::MulInt,
335        Op::DivInt,
336        Op::ModInt,
337        Op::AddFloat,
338        Op::SubFloat,
339        Op::MulFloat,
340        Op::DivFloat,
341        Op::ModFloat,
342        Op::EqualInt,
343        Op::NotEqualInt,
344        Op::LessInt,
345        Op::GreaterInt,
346        Op::LessEqualInt,
347        Op::GreaterEqualInt,
348        Op::EqualFloat,
349        Op::NotEqualFloat,
350        Op::LessFloat,
351        Op::GreaterFloat,
352        Op::LessEqualFloat,
353        Op::GreaterEqualFloat,
354        Op::EqualBool,
355        Op::NotEqualBool,
356        Op::EqualString,
357        Op::NotEqualString,
358        Op::Yield,
359        Op::GetLocalSlot,
360        Op::DefLocalSlot,
361        Op::SetLocalSlot,
362    ];
363
364    pub(crate) fn from_byte(byte: u8) -> Option<Self> {
365        Self::ALL.get(byte as usize).copied()
366    }
367}
368
369/// A constant value in the constant pool.
370#[derive(Debug, Clone, PartialEq)]
371pub enum Constant {
372    Int(i64),
373    Float(f64),
374    String(String),
375    Bool(bool),
376    Nil,
377    Duration(i64),
378}
379
380/// Monomorphic inline-cache state for bytecode instructions that repeatedly
381/// resolve the same property or builtin method. Cache guards are intentionally
382/// conservative: each entry is tied to the instruction's name constant index
383/// and a single receiver variant. Harn collection values are immutable or
384/// copy-on-write at the VM level, so list/string/pair/range/set/dict receiver
385/// kind caches do not need invalidation; dynamic dict fields and struct fields
386/// are left on the generic path until they have stable layout metadata.
387#[derive(Debug, Clone, PartialEq, Eq)]
388pub(crate) enum InlineCacheEntry {
389    Empty,
390    Property {
391        name_idx: u16,
392        target: PropertyCacheTarget,
393    },
394    Method {
395        name_idx: u16,
396        argc: usize,
397        target: MethodCacheTarget,
398    },
399}
400
401#[derive(Debug, Clone, Copy, PartialEq, Eq)]
402pub(crate) enum PropertyCacheTarget {
403    ListCount,
404    ListEmpty,
405    ListFirst,
406    ListLast,
407    StringCount,
408    StringEmpty,
409    PairFirst,
410    PairSecond,
411    EnumVariant,
412    EnumFields,
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
416pub(crate) enum MethodCacheTarget {
417    ListCount,
418    ListEmpty,
419    StringCount,
420    StringEmpty,
421    DictCount,
422    RangeCount,
423    RangeLen,
424    RangeEmpty,
425    RangeFirst,
426    RangeLast,
427    SetCount,
428    SetLen,
429    SetEmpty,
430}
431
432/// Debug metadata for a slot-indexed local in a compiled chunk.
433#[derive(Debug, Clone, PartialEq, Eq)]
434pub struct LocalSlotInfo {
435    pub name: String,
436    pub mutable: bool,
437    pub scope_depth: usize,
438}
439
440impl fmt::Display for Constant {
441    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442        match self {
443            Constant::Int(n) => write!(f, "{n}"),
444            Constant::Float(n) => write!(f, "{n}"),
445            Constant::String(s) => write!(f, "\"{s}\""),
446            Constant::Bool(b) => write!(f, "{b}"),
447            Constant::Nil => write!(f, "nil"),
448            Constant::Duration(ms) => write!(f, "{ms}ms"),
449        }
450    }
451}
452
453/// A compiled chunk of bytecode.
454#[derive(Debug, Clone)]
455pub struct Chunk {
456    /// The bytecode instructions.
457    pub code: Vec<u8>,
458    /// Constant pool.
459    pub constants: Vec<Constant>,
460    /// Source line numbers for each instruction (for error reporting).
461    pub lines: Vec<u32>,
462    /// Source column numbers for each instruction (for error reporting).
463    /// Parallel to `lines`; 0 means no column info available.
464    pub columns: Vec<u32>,
465    /// Source file that this chunk was compiled from, when known. Set for
466    /// chunks compiled from imported modules so runtime errors can report
467    /// the correct file path for each frame instead of always pointing at
468    /// the entry-point pipeline.
469    pub source_file: Option<String>,
470    /// Current column to use when emitting instructions (set by compiler).
471    current_col: u32,
472    /// Compiled function bodies (for closures).
473    pub functions: Vec<CompiledFunctionRef>,
474    /// Instruction offset to inline-cache slot. Slots are assigned at emit time
475    /// for cacheable instructions while bytecode bytes remain immutable.
476    inline_cache_slots: BTreeMap<usize, usize>,
477    /// Shared cache entries so cloned chunks in call frames warm the same side
478    /// table as the compiled chunk used by tests/debugging.
479    inline_caches: Rc<RefCell<Vec<InlineCacheEntry>>>,
480    /// Source-name metadata for slot-indexed locals in this chunk.
481    pub(crate) local_slots: Vec<LocalSlotInfo>,
482}
483
484pub type ChunkRef = Rc<Chunk>;
485pub type CompiledFunctionRef = Rc<CompiledFunction>;
486
487/// A compiled function (closure body).
488#[derive(Debug, Clone)]
489pub struct CompiledFunction {
490    pub name: String,
491    pub params: Vec<String>,
492    /// Index of the first parameter with a default value, or None if all required.
493    pub default_start: Option<usize>,
494    pub chunk: ChunkRef,
495    /// True if the function body contains `yield` expressions (generator function).
496    pub is_generator: bool,
497    /// True if the function was declared as `gen fn` and should return Stream.
498    pub is_stream: bool,
499    /// True if the last parameter is a rest parameter (`...name`).
500    pub has_rest_param: bool,
501}
502
503impl Chunk {
504    pub fn new() -> Self {
505        Self {
506            code: Vec::new(),
507            constants: Vec::new(),
508            lines: Vec::new(),
509            columns: Vec::new(),
510            source_file: None,
511            current_col: 0,
512            functions: Vec::new(),
513            inline_cache_slots: BTreeMap::new(),
514            inline_caches: Rc::new(RefCell::new(Vec::new())),
515            local_slots: Vec::new(),
516        }
517    }
518
519    /// Set the current column for subsequent emit calls.
520    pub fn set_column(&mut self, col: u32) {
521        self.current_col = col;
522    }
523
524    /// Add a constant and return its index.
525    pub fn add_constant(&mut self, constant: Constant) -> u16 {
526        for (i, c) in self.constants.iter().enumerate() {
527            if c == &constant {
528                return i as u16;
529            }
530        }
531        let idx = self.constants.len();
532        self.constants.push(constant);
533        idx as u16
534    }
535
536    /// Emit a single-byte instruction.
537    pub fn emit(&mut self, op: Op, line: u32) {
538        let col = self.current_col;
539        self.code.push(op as u8);
540        self.lines.push(line);
541        self.columns.push(col);
542    }
543
544    /// Emit an instruction with a u16 argument.
545    pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
546        let col = self.current_col;
547        let op_offset = self.code.len();
548        self.code.push(op as u8);
549        self.code.push((arg >> 8) as u8);
550        self.code.push((arg & 0xFF) as u8);
551        self.lines.push(line);
552        self.lines.push(line);
553        self.lines.push(line);
554        self.columns.push(col);
555        self.columns.push(col);
556        self.columns.push(col);
557        if matches!(
558            op,
559            Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread
560        ) {
561            self.register_inline_cache(op_offset);
562        }
563    }
564
565    /// Emit an instruction with a u8 argument.
566    pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
567        let col = self.current_col;
568        self.code.push(op as u8);
569        self.code.push(arg);
570        self.lines.push(line);
571        self.lines.push(line);
572        self.columns.push(col);
573        self.columns.push(col);
574    }
575
576    /// Emit a direct builtin call.
577    pub fn emit_call_builtin(
578        &mut self,
579        id: crate::BuiltinId,
580        name_idx: u16,
581        arg_count: u8,
582        line: u32,
583    ) {
584        let col = self.current_col;
585        self.code.push(Op::CallBuiltin as u8);
586        self.code.extend_from_slice(&id.raw().to_be_bytes());
587        self.code.push((name_idx >> 8) as u8);
588        self.code.push((name_idx & 0xFF) as u8);
589        self.code.push(arg_count);
590        for _ in 0..12 {
591            self.lines.push(line);
592            self.columns.push(col);
593        }
594    }
595
596    /// Emit a direct builtin spread call.
597    pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
598        let col = self.current_col;
599        self.code.push(Op::CallBuiltinSpread as u8);
600        self.code.extend_from_slice(&id.raw().to_be_bytes());
601        self.code.push((name_idx >> 8) as u8);
602        self.code.push((name_idx & 0xFF) as u8);
603        for _ in 0..11 {
604            self.lines.push(line);
605            self.columns.push(col);
606        }
607    }
608
609    /// Emit a method call: op + u16 (method name) + u8 (arg count).
610    pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
611        self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
612    }
613
614    /// Emit an optional method call (?.) — returns nil if receiver is nil.
615    pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
616        self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
617    }
618
619    fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
620        let col = self.current_col;
621        let op_offset = self.code.len();
622        self.code.push(op as u8);
623        self.code.push((name_idx >> 8) as u8);
624        self.code.push((name_idx & 0xFF) as u8);
625        self.code.push(arg_count);
626        self.lines.push(line);
627        self.lines.push(line);
628        self.lines.push(line);
629        self.lines.push(line);
630        self.columns.push(col);
631        self.columns.push(col);
632        self.columns.push(col);
633        self.columns.push(col);
634        self.register_inline_cache(op_offset);
635    }
636
637    /// Current code offset (for jump patching).
638    pub fn current_offset(&self) -> usize {
639        self.code.len()
640    }
641
642    /// Emit a jump instruction with a placeholder offset. Returns the position to patch.
643    pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
644        let col = self.current_col;
645        self.code.push(op as u8);
646        let patch_pos = self.code.len();
647        self.code.push(0xFF);
648        self.code.push(0xFF);
649        self.lines.push(line);
650        self.lines.push(line);
651        self.lines.push(line);
652        self.columns.push(col);
653        self.columns.push(col);
654        self.columns.push(col);
655        patch_pos
656    }
657
658    /// Patch a jump instruction at the given position to jump to the current offset.
659    pub fn patch_jump(&mut self, patch_pos: usize) {
660        let target = self.code.len() as u16;
661        self.code[patch_pos] = (target >> 8) as u8;
662        self.code[patch_pos + 1] = (target & 0xFF) as u8;
663    }
664
665    /// Patch a jump to a specific target position.
666    pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
667        let target = target as u16;
668        self.code[patch_pos] = (target >> 8) as u8;
669        self.code[patch_pos + 1] = (target & 0xFF) as u8;
670    }
671
672    /// Read a u16 argument at the given position.
673    pub fn read_u16(&self, pos: usize) -> u16 {
674        ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
675    }
676
677    fn register_inline_cache(&mut self, op_offset: usize) {
678        if self.inline_cache_slots.contains_key(&op_offset) {
679            return;
680        }
681        let mut entries = self.inline_caches.borrow_mut();
682        let slot = entries.len();
683        entries.push(InlineCacheEntry::Empty);
684        self.inline_cache_slots.insert(op_offset, slot);
685    }
686
687    pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
688        self.inline_cache_slots.get(&op_offset).copied()
689    }
690
691    pub(crate) fn inline_cache_entry(&self, slot: usize) -> InlineCacheEntry {
692        self.inline_caches
693            .borrow()
694            .get(slot)
695            .cloned()
696            .unwrap_or(InlineCacheEntry::Empty)
697    }
698
699    pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
700        if let Some(existing) = self.inline_caches.borrow_mut().get_mut(slot) {
701            *existing = entry;
702        }
703    }
704
705    pub(crate) fn add_local_slot(
706        &mut self,
707        name: String,
708        mutable: bool,
709        scope_depth: usize,
710    ) -> u16 {
711        let idx = self.local_slots.len();
712        self.local_slots.push(LocalSlotInfo {
713            name,
714            mutable,
715            scope_depth,
716        });
717        idx as u16
718    }
719
720    #[cfg(test)]
721    pub(crate) fn inline_cache_entries(&self) -> Vec<InlineCacheEntry> {
722        self.inline_caches.borrow().clone()
723    }
724
725    /// Read a u64 argument at the given position.
726    pub fn read_u64(&self, pos: usize) -> u64 {
727        u64::from_be_bytes([
728            self.code[pos],
729            self.code[pos + 1],
730            self.code[pos + 2],
731            self.code[pos + 3],
732            self.code[pos + 4],
733            self.code[pos + 5],
734            self.code[pos + 6],
735            self.code[pos + 7],
736        ])
737    }
738
739    /// Disassemble for debugging.
740    pub fn disassemble(&self, name: &str) -> String {
741        let mut out = format!("== {name} ==\n");
742        let mut ip = 0;
743        while ip < self.code.len() {
744            let op = self.code[ip];
745            let line = self.lines.get(ip).copied().unwrap_or(0);
746            out.push_str(&format!("{:04} [{:>4}] ", ip, line));
747            ip += 1;
748
749            match op {
750                x if x == Op::Constant as u8 => {
751                    let idx = self.read_u16(ip);
752                    ip += 2;
753                    let val = &self.constants[idx as usize];
754                    out.push_str(&format!("CONSTANT {:>4} ({})\n", idx, val));
755                }
756                x if x == Op::Nil as u8 => out.push_str("NIL\n"),
757                x if x == Op::True as u8 => out.push_str("TRUE\n"),
758                x if x == Op::False as u8 => out.push_str("FALSE\n"),
759                x if x == Op::GetVar as u8 => {
760                    let idx = self.read_u16(ip);
761                    ip += 2;
762                    out.push_str(&format!(
763                        "GET_VAR {:>4} ({})\n",
764                        idx, self.constants[idx as usize]
765                    ));
766                }
767                x if x == Op::DefLet as u8 => {
768                    let idx = self.read_u16(ip);
769                    ip += 2;
770                    out.push_str(&format!(
771                        "DEF_LET {:>4} ({})\n",
772                        idx, self.constants[idx as usize]
773                    ));
774                }
775                x if x == Op::DefVar as u8 => {
776                    let idx = self.read_u16(ip);
777                    ip += 2;
778                    out.push_str(&format!(
779                        "DEF_VAR {:>4} ({})\n",
780                        idx, self.constants[idx as usize]
781                    ));
782                }
783                x if x == Op::SetVar as u8 => {
784                    let idx = self.read_u16(ip);
785                    ip += 2;
786                    out.push_str(&format!(
787                        "SET_VAR {:>4} ({})\n",
788                        idx, self.constants[idx as usize]
789                    ));
790                }
791                x if x == Op::GetLocalSlot as u8 => {
792                    let slot = self.read_u16(ip);
793                    ip += 2;
794                    out.push_str(&format!("GET_LOCAL_SLOT {:>4}", slot));
795                    if let Some(info) = self.local_slots.get(slot as usize) {
796                        out.push_str(&format!(" ({})", info.name));
797                    }
798                    out.push('\n');
799                }
800                x if x == Op::DefLocalSlot as u8 => {
801                    let slot = self.read_u16(ip);
802                    ip += 2;
803                    out.push_str(&format!("DEF_LOCAL_SLOT {:>4}", slot));
804                    if let Some(info) = self.local_slots.get(slot as usize) {
805                        out.push_str(&format!(" ({})", info.name));
806                    }
807                    out.push('\n');
808                }
809                x if x == Op::SetLocalSlot as u8 => {
810                    let slot = self.read_u16(ip);
811                    ip += 2;
812                    out.push_str(&format!("SET_LOCAL_SLOT {:>4}", slot));
813                    if let Some(info) = self.local_slots.get(slot as usize) {
814                        out.push_str(&format!(" ({})", info.name));
815                    }
816                    out.push('\n');
817                }
818                x if x == Op::PushScope as u8 => out.push_str("PUSH_SCOPE\n"),
819                x if x == Op::PopScope as u8 => out.push_str("POP_SCOPE\n"),
820                x if x == Op::Add as u8 => out.push_str("ADD\n"),
821                x if x == Op::Sub as u8 => out.push_str("SUB\n"),
822                x if x == Op::Mul as u8 => out.push_str("MUL\n"),
823                x if x == Op::Div as u8 => out.push_str("DIV\n"),
824                x if x == Op::Mod as u8 => out.push_str("MOD\n"),
825                x if x == Op::Pow as u8 => out.push_str("POW\n"),
826                x if x == Op::Negate as u8 => out.push_str("NEGATE\n"),
827                x if x == Op::Equal as u8 => out.push_str("EQUAL\n"),
828                x if x == Op::NotEqual as u8 => out.push_str("NOT_EQUAL\n"),
829                x if x == Op::Less as u8 => out.push_str("LESS\n"),
830                x if x == Op::Greater as u8 => out.push_str("GREATER\n"),
831                x if x == Op::LessEqual as u8 => out.push_str("LESS_EQUAL\n"),
832                x if x == Op::GreaterEqual as u8 => out.push_str("GREATER_EQUAL\n"),
833                x if x == Op::Contains as u8 => out.push_str("CONTAINS\n"),
834                x if x == Op::Not as u8 => out.push_str("NOT\n"),
835                x if x == Op::Jump as u8 => {
836                    let target = self.read_u16(ip);
837                    ip += 2;
838                    out.push_str(&format!("JUMP {:>4}\n", target));
839                }
840                x if x == Op::JumpIfFalse as u8 => {
841                    let target = self.read_u16(ip);
842                    ip += 2;
843                    out.push_str(&format!("JUMP_IF_FALSE {:>4}\n", target));
844                }
845                x if x == Op::JumpIfTrue as u8 => {
846                    let target = self.read_u16(ip);
847                    ip += 2;
848                    out.push_str(&format!("JUMP_IF_TRUE {:>4}\n", target));
849                }
850                x if x == Op::Pop as u8 => out.push_str("POP\n"),
851                x if x == Op::Call as u8 => {
852                    let argc = self.code[ip];
853                    ip += 1;
854                    out.push_str(&format!("CALL {:>4}\n", argc));
855                }
856                x if x == Op::TailCall as u8 => {
857                    let argc = self.code[ip];
858                    ip += 1;
859                    out.push_str(&format!("TAIL_CALL {:>4}\n", argc));
860                }
861                x if x == Op::Return as u8 => out.push_str("RETURN\n"),
862                x if x == Op::Closure as u8 => {
863                    let idx = self.read_u16(ip);
864                    ip += 2;
865                    out.push_str(&format!("CLOSURE {:>4}\n", idx));
866                }
867                x if x == Op::BuildList as u8 => {
868                    let count = self.read_u16(ip);
869                    ip += 2;
870                    out.push_str(&format!("BUILD_LIST {:>4}\n", count));
871                }
872                x if x == Op::BuildDict as u8 => {
873                    let count = self.read_u16(ip);
874                    ip += 2;
875                    out.push_str(&format!("BUILD_DICT {:>4}\n", count));
876                }
877                x if x == Op::Subscript as u8 => out.push_str("SUBSCRIPT\n"),
878                x if x == Op::SubscriptOpt as u8 => out.push_str("SUBSCRIPT_OPT\n"),
879                x if x == Op::Slice as u8 => out.push_str("SLICE\n"),
880                x if x == Op::GetProperty as u8 => {
881                    let idx = self.read_u16(ip);
882                    ip += 2;
883                    out.push_str(&format!(
884                        "GET_PROPERTY {:>4} ({})\n",
885                        idx, self.constants[idx as usize]
886                    ));
887                }
888                x if x == Op::GetPropertyOpt as u8 => {
889                    let idx = self.read_u16(ip);
890                    ip += 2;
891                    out.push_str(&format!(
892                        "GET_PROPERTY_OPT {:>4} ({})\n",
893                        idx, self.constants[idx as usize]
894                    ));
895                }
896                x if x == Op::SetProperty as u8 => {
897                    let idx = self.read_u16(ip);
898                    ip += 2;
899                    out.push_str(&format!(
900                        "SET_PROPERTY {:>4} ({})\n",
901                        idx, self.constants[idx as usize]
902                    ));
903                }
904                x if x == Op::SetSubscript as u8 => {
905                    let idx = self.read_u16(ip);
906                    ip += 2;
907                    out.push_str(&format!(
908                        "SET_SUBSCRIPT {:>4} ({})\n",
909                        idx, self.constants[idx as usize]
910                    ));
911                }
912                x if x == Op::MethodCall as u8 => {
913                    let idx = self.read_u16(ip);
914                    ip += 2;
915                    let argc = self.code[ip];
916                    ip += 1;
917                    out.push_str(&format!(
918                        "METHOD_CALL {:>4} ({}) argc={}\n",
919                        idx, self.constants[idx as usize], argc
920                    ));
921                }
922                x if x == Op::MethodCallOpt as u8 => {
923                    let idx = self.read_u16(ip);
924                    ip += 2;
925                    let argc = self.code[ip];
926                    ip += 1;
927                    out.push_str(&format!(
928                        "METHOD_CALL_OPT {:>4} ({}) argc={}\n",
929                        idx, self.constants[idx as usize], argc
930                    ));
931                }
932                x if x == Op::Concat as u8 => {
933                    let count = self.read_u16(ip);
934                    ip += 2;
935                    out.push_str(&format!("CONCAT {:>4}\n", count));
936                }
937                x if x == Op::IterInit as u8 => out.push_str("ITER_INIT\n"),
938                x if x == Op::IterNext as u8 => {
939                    let target = self.read_u16(ip);
940                    ip += 2;
941                    out.push_str(&format!("ITER_NEXT {:>4}\n", target));
942                }
943                x if x == Op::Throw as u8 => out.push_str("THROW\n"),
944                x if x == Op::TryCatchSetup as u8 => {
945                    let target = self.read_u16(ip);
946                    ip += 2;
947                    out.push_str(&format!("TRY_CATCH_SETUP {:>4}\n", target));
948                }
949                x if x == Op::PopHandler as u8 => out.push_str("POP_HANDLER\n"),
950                x if x == Op::Pipe as u8 => out.push_str("PIPE\n"),
951                x if x == Op::Parallel as u8 => out.push_str("PARALLEL\n"),
952                x if x == Op::ParallelMap as u8 => out.push_str("PARALLEL_MAP\n"),
953                x if x == Op::ParallelMapStream as u8 => out.push_str("PARALLEL_MAP_STREAM\n"),
954                x if x == Op::ParallelSettle as u8 => out.push_str("PARALLEL_SETTLE\n"),
955                x if x == Op::Spawn as u8 => out.push_str("SPAWN\n"),
956                x if x == Op::Import as u8 => {
957                    let idx = self.read_u16(ip);
958                    ip += 2;
959                    out.push_str(&format!(
960                        "IMPORT {:>4} ({})\n",
961                        idx, self.constants[idx as usize]
962                    ));
963                }
964                x if x == Op::SelectiveImport as u8 => {
965                    let path_idx = self.read_u16(ip);
966                    ip += 2;
967                    let names_idx = self.read_u16(ip);
968                    ip += 2;
969                    out.push_str(&format!(
970                        "SELECTIVE_IMPORT {:>4} ({}) names: {:>4} ({})\n",
971                        path_idx,
972                        self.constants[path_idx as usize],
973                        names_idx,
974                        self.constants[names_idx as usize]
975                    ));
976                }
977                x if x == Op::SyncMutexEnter as u8 => {
978                    let idx = self.read_u16(ip);
979                    ip += 2;
980                    out.push_str(&format!(
981                        "SYNC_MUTEX_ENTER {:>4} ({})\n",
982                        idx, self.constants[idx as usize]
983                    ));
984                }
985                x if x == Op::DeadlineSetup as u8 => out.push_str("DEADLINE_SETUP\n"),
986                x if x == Op::DeadlineEnd as u8 => out.push_str("DEADLINE_END\n"),
987                x if x == Op::BuildEnum as u8 => {
988                    let enum_idx = self.read_u16(ip);
989                    ip += 2;
990                    let variant_idx = self.read_u16(ip);
991                    ip += 2;
992                    let field_count = self.read_u16(ip);
993                    ip += 2;
994                    out.push_str(&format!(
995                        "BUILD_ENUM {:>4} ({}) {:>4} ({}) fields={}\n",
996                        enum_idx,
997                        self.constants[enum_idx as usize],
998                        variant_idx,
999                        self.constants[variant_idx as usize],
1000                        field_count
1001                    ));
1002                }
1003                x if x == Op::MatchEnum as u8 => {
1004                    let enum_idx = self.read_u16(ip);
1005                    ip += 2;
1006                    let variant_idx = self.read_u16(ip);
1007                    ip += 2;
1008                    out.push_str(&format!(
1009                        "MATCH_ENUM {:>4} ({}) {:>4} ({})\n",
1010                        enum_idx,
1011                        self.constants[enum_idx as usize],
1012                        variant_idx,
1013                        self.constants[variant_idx as usize]
1014                    ));
1015                }
1016                x if x == Op::PopIterator as u8 => out.push_str("POP_ITERATOR\n"),
1017                x if x == Op::TryUnwrap as u8 => out.push_str("TRY_UNWRAP\n"),
1018                x if x == Op::TryWrapOk as u8 => out.push_str("TRY_WRAP_OK\n"),
1019                x if x == Op::CallSpread as u8 => out.push_str("CALL_SPREAD\n"),
1020                x if x == Op::CallBuiltin as u8 => {
1021                    let id = self.read_u64(ip);
1022                    ip += 8;
1023                    let idx = self.read_u16(ip);
1024                    ip += 2;
1025                    let argc = self.code[ip];
1026                    ip += 1;
1027                    out.push_str(&format!(
1028                        "CALL_BUILTIN {id:#018x} {:>4} ({}) argc={}\n",
1029                        idx, self.constants[idx as usize], argc
1030                    ));
1031                }
1032                x if x == Op::CallBuiltinSpread as u8 => {
1033                    let id = self.read_u64(ip);
1034                    ip += 8;
1035                    let idx = self.read_u16(ip);
1036                    ip += 2;
1037                    out.push_str(&format!(
1038                        "CALL_BUILTIN_SPREAD {id:#018x} {:>4} ({})\n",
1039                        idx, self.constants[idx as usize]
1040                    ));
1041                }
1042                x if x == Op::MethodCallSpread as u8 => {
1043                    let idx = self.read_u16(ip + 1);
1044                    ip += 2;
1045                    out.push_str(&format!("METHOD_CALL_SPREAD {idx}\n"));
1046                }
1047                x if x == Op::Dup as u8 => out.push_str("DUP\n"),
1048                x if x == Op::Swap as u8 => out.push_str("SWAP\n"),
1049                x if x == Op::AddInt as u8 => out.push_str("ADD_INT\n"),
1050                x if x == Op::SubInt as u8 => out.push_str("SUB_INT\n"),
1051                x if x == Op::MulInt as u8 => out.push_str("MUL_INT\n"),
1052                x if x == Op::DivInt as u8 => out.push_str("DIV_INT\n"),
1053                x if x == Op::ModInt as u8 => out.push_str("MOD_INT\n"),
1054                x if x == Op::AddFloat as u8 => out.push_str("ADD_FLOAT\n"),
1055                x if x == Op::SubFloat as u8 => out.push_str("SUB_FLOAT\n"),
1056                x if x == Op::MulFloat as u8 => out.push_str("MUL_FLOAT\n"),
1057                x if x == Op::DivFloat as u8 => out.push_str("DIV_FLOAT\n"),
1058                x if x == Op::ModFloat as u8 => out.push_str("MOD_FLOAT\n"),
1059                x if x == Op::EqualInt as u8 => out.push_str("EQUAL_INT\n"),
1060                x if x == Op::NotEqualInt as u8 => out.push_str("NOT_EQUAL_INT\n"),
1061                x if x == Op::LessInt as u8 => out.push_str("LESS_INT\n"),
1062                x if x == Op::GreaterInt as u8 => out.push_str("GREATER_INT\n"),
1063                x if x == Op::LessEqualInt as u8 => out.push_str("LESS_EQUAL_INT\n"),
1064                x if x == Op::GreaterEqualInt as u8 => out.push_str("GREATER_EQUAL_INT\n"),
1065                x if x == Op::EqualFloat as u8 => out.push_str("EQUAL_FLOAT\n"),
1066                x if x == Op::NotEqualFloat as u8 => out.push_str("NOT_EQUAL_FLOAT\n"),
1067                x if x == Op::LessFloat as u8 => out.push_str("LESS_FLOAT\n"),
1068                x if x == Op::GreaterFloat as u8 => out.push_str("GREATER_FLOAT\n"),
1069                x if x == Op::LessEqualFloat as u8 => out.push_str("LESS_EQUAL_FLOAT\n"),
1070                x if x == Op::GreaterEqualFloat as u8 => out.push_str("GREATER_EQUAL_FLOAT\n"),
1071                x if x == Op::EqualBool as u8 => out.push_str("EQUAL_BOOL\n"),
1072                x if x == Op::NotEqualBool as u8 => out.push_str("NOT_EQUAL_BOOL\n"),
1073                x if x == Op::EqualString as u8 => out.push_str("EQUAL_STRING\n"),
1074                x if x == Op::NotEqualString as u8 => out.push_str("NOT_EQUAL_STRING\n"),
1075                x if x == Op::Yield as u8 => out.push_str("YIELD\n"),
1076                _ => {
1077                    out.push_str(&format!("UNKNOWN(0x{:02x})\n", op));
1078                }
1079            }
1080        }
1081        out
1082    }
1083}
1084
1085impl Default for Chunk {
1086    fn default() -> Self {
1087        Self::new()
1088    }
1089}
1090
1091#[cfg(test)]
1092mod tests {
1093    use super::Op;
1094
1095    #[test]
1096    fn op_from_byte_matches_repr_order() {
1097        for (byte, op) in Op::ALL.iter().copied().enumerate() {
1098            assert_eq!(byte as u8, op as u8);
1099            assert_eq!(Op::from_byte(byte as u8), Some(op));
1100        }
1101        assert_eq!(Op::from_byte(Op::ALL.len() as u8), None);
1102    }
1103}