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