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