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