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 last parameter is a rest parameter (`...name`).
494    pub has_rest_param: bool,
495}
496
497impl Chunk {
498    pub fn new() -> Self {
499        Self {
500            code: Vec::new(),
501            constants: Vec::new(),
502            lines: Vec::new(),
503            columns: Vec::new(),
504            source_file: None,
505            current_col: 0,
506            functions: Vec::new(),
507            inline_cache_slots: BTreeMap::new(),
508            inline_caches: Rc::new(RefCell::new(Vec::new())),
509            local_slots: Vec::new(),
510        }
511    }
512
513    /// Set the current column for subsequent emit calls.
514    pub fn set_column(&mut self, col: u32) {
515        self.current_col = col;
516    }
517
518    /// Add a constant and return its index.
519    pub fn add_constant(&mut self, constant: Constant) -> u16 {
520        for (i, c) in self.constants.iter().enumerate() {
521            if c == &constant {
522                return i as u16;
523            }
524        }
525        let idx = self.constants.len();
526        self.constants.push(constant);
527        idx as u16
528    }
529
530    /// Emit a single-byte instruction.
531    pub fn emit(&mut self, op: Op, line: u32) {
532        let col = self.current_col;
533        self.code.push(op as u8);
534        self.lines.push(line);
535        self.columns.push(col);
536    }
537
538    /// Emit an instruction with a u16 argument.
539    pub fn emit_u16(&mut self, op: Op, arg: u16, line: u32) {
540        let col = self.current_col;
541        let op_offset = self.code.len();
542        self.code.push(op as u8);
543        self.code.push((arg >> 8) as u8);
544        self.code.push((arg & 0xFF) as u8);
545        self.lines.push(line);
546        self.lines.push(line);
547        self.lines.push(line);
548        self.columns.push(col);
549        self.columns.push(col);
550        self.columns.push(col);
551        if matches!(
552            op,
553            Op::GetProperty | Op::GetPropertyOpt | Op::MethodCallSpread
554        ) {
555            self.register_inline_cache(op_offset);
556        }
557    }
558
559    /// Emit an instruction with a u8 argument.
560    pub fn emit_u8(&mut self, op: Op, arg: u8, line: u32) {
561        let col = self.current_col;
562        self.code.push(op as u8);
563        self.code.push(arg);
564        self.lines.push(line);
565        self.lines.push(line);
566        self.columns.push(col);
567        self.columns.push(col);
568    }
569
570    /// Emit a direct builtin call.
571    pub fn emit_call_builtin(
572        &mut self,
573        id: crate::BuiltinId,
574        name_idx: u16,
575        arg_count: u8,
576        line: u32,
577    ) {
578        let col = self.current_col;
579        self.code.push(Op::CallBuiltin as u8);
580        self.code.extend_from_slice(&id.raw().to_be_bytes());
581        self.code.push((name_idx >> 8) as u8);
582        self.code.push((name_idx & 0xFF) as u8);
583        self.code.push(arg_count);
584        for _ in 0..12 {
585            self.lines.push(line);
586            self.columns.push(col);
587        }
588    }
589
590    /// Emit a direct builtin spread call.
591    pub fn emit_call_builtin_spread(&mut self, id: crate::BuiltinId, name_idx: u16, line: u32) {
592        let col = self.current_col;
593        self.code.push(Op::CallBuiltinSpread as u8);
594        self.code.extend_from_slice(&id.raw().to_be_bytes());
595        self.code.push((name_idx >> 8) as u8);
596        self.code.push((name_idx & 0xFF) as u8);
597        for _ in 0..11 {
598            self.lines.push(line);
599            self.columns.push(col);
600        }
601    }
602
603    /// Emit a method call: op + u16 (method name) + u8 (arg count).
604    pub fn emit_method_call(&mut self, name_idx: u16, arg_count: u8, line: u32) {
605        self.emit_method_call_inner(Op::MethodCall, name_idx, arg_count, line);
606    }
607
608    /// Emit an optional method call (?.) — returns nil if receiver is nil.
609    pub fn emit_method_call_opt(&mut self, name_idx: u16, arg_count: u8, line: u32) {
610        self.emit_method_call_inner(Op::MethodCallOpt, name_idx, arg_count, line);
611    }
612
613    fn emit_method_call_inner(&mut self, op: Op, name_idx: u16, arg_count: u8, line: u32) {
614        let col = self.current_col;
615        let op_offset = self.code.len();
616        self.code.push(op as u8);
617        self.code.push((name_idx >> 8) as u8);
618        self.code.push((name_idx & 0xFF) as u8);
619        self.code.push(arg_count);
620        self.lines.push(line);
621        self.lines.push(line);
622        self.lines.push(line);
623        self.lines.push(line);
624        self.columns.push(col);
625        self.columns.push(col);
626        self.columns.push(col);
627        self.columns.push(col);
628        self.register_inline_cache(op_offset);
629    }
630
631    /// Current code offset (for jump patching).
632    pub fn current_offset(&self) -> usize {
633        self.code.len()
634    }
635
636    /// Emit a jump instruction with a placeholder offset. Returns the position to patch.
637    pub fn emit_jump(&mut self, op: Op, line: u32) -> usize {
638        let col = self.current_col;
639        self.code.push(op as u8);
640        let patch_pos = self.code.len();
641        self.code.push(0xFF);
642        self.code.push(0xFF);
643        self.lines.push(line);
644        self.lines.push(line);
645        self.lines.push(line);
646        self.columns.push(col);
647        self.columns.push(col);
648        self.columns.push(col);
649        patch_pos
650    }
651
652    /// Patch a jump instruction at the given position to jump to the current offset.
653    pub fn patch_jump(&mut self, patch_pos: usize) {
654        let target = self.code.len() as u16;
655        self.code[patch_pos] = (target >> 8) as u8;
656        self.code[patch_pos + 1] = (target & 0xFF) as u8;
657    }
658
659    /// Patch a jump to a specific target position.
660    pub fn patch_jump_to(&mut self, patch_pos: usize, target: usize) {
661        let target = target as u16;
662        self.code[patch_pos] = (target >> 8) as u8;
663        self.code[patch_pos + 1] = (target & 0xFF) as u8;
664    }
665
666    /// Read a u16 argument at the given position.
667    pub fn read_u16(&self, pos: usize) -> u16 {
668        ((self.code[pos] as u16) << 8) | (self.code[pos + 1] as u16)
669    }
670
671    fn register_inline_cache(&mut self, op_offset: usize) {
672        if self.inline_cache_slots.contains_key(&op_offset) {
673            return;
674        }
675        let mut entries = self.inline_caches.borrow_mut();
676        let slot = entries.len();
677        entries.push(InlineCacheEntry::Empty);
678        self.inline_cache_slots.insert(op_offset, slot);
679    }
680
681    pub(crate) fn inline_cache_slot(&self, op_offset: usize) -> Option<usize> {
682        self.inline_cache_slots.get(&op_offset).copied()
683    }
684
685    pub(crate) fn inline_cache_entry(&self, slot: usize) -> InlineCacheEntry {
686        self.inline_caches
687            .borrow()
688            .get(slot)
689            .cloned()
690            .unwrap_or(InlineCacheEntry::Empty)
691    }
692
693    pub(crate) fn set_inline_cache_entry(&self, slot: usize, entry: InlineCacheEntry) {
694        if let Some(existing) = self.inline_caches.borrow_mut().get_mut(slot) {
695            *existing = entry;
696        }
697    }
698
699    pub(crate) fn add_local_slot(
700        &mut self,
701        name: String,
702        mutable: bool,
703        scope_depth: usize,
704    ) -> u16 {
705        let idx = self.local_slots.len();
706        self.local_slots.push(LocalSlotInfo {
707            name,
708            mutable,
709            scope_depth,
710        });
711        idx as u16
712    }
713
714    #[cfg(test)]
715    pub(crate) fn inline_cache_entries(&self) -> Vec<InlineCacheEntry> {
716        self.inline_caches.borrow().clone()
717    }
718
719    /// Read a u64 argument at the given position.
720    pub fn read_u64(&self, pos: usize) -> u64 {
721        u64::from_be_bytes([
722            self.code[pos],
723            self.code[pos + 1],
724            self.code[pos + 2],
725            self.code[pos + 3],
726            self.code[pos + 4],
727            self.code[pos + 5],
728            self.code[pos + 6],
729            self.code[pos + 7],
730        ])
731    }
732
733    /// Disassemble for debugging.
734    pub fn disassemble(&self, name: &str) -> String {
735        let mut out = format!("== {name} ==\n");
736        let mut ip = 0;
737        while ip < self.code.len() {
738            let op = self.code[ip];
739            let line = self.lines.get(ip).copied().unwrap_or(0);
740            out.push_str(&format!("{:04} [{:>4}] ", ip, line));
741            ip += 1;
742
743            match op {
744                x if x == Op::Constant as u8 => {
745                    let idx = self.read_u16(ip);
746                    ip += 2;
747                    let val = &self.constants[idx as usize];
748                    out.push_str(&format!("CONSTANT {:>4} ({})\n", idx, val));
749                }
750                x if x == Op::Nil as u8 => out.push_str("NIL\n"),
751                x if x == Op::True as u8 => out.push_str("TRUE\n"),
752                x if x == Op::False as u8 => out.push_str("FALSE\n"),
753                x if x == Op::GetVar as u8 => {
754                    let idx = self.read_u16(ip);
755                    ip += 2;
756                    out.push_str(&format!(
757                        "GET_VAR {:>4} ({})\n",
758                        idx, self.constants[idx as usize]
759                    ));
760                }
761                x if x == Op::DefLet as u8 => {
762                    let idx = self.read_u16(ip);
763                    ip += 2;
764                    out.push_str(&format!(
765                        "DEF_LET {:>4} ({})\n",
766                        idx, self.constants[idx as usize]
767                    ));
768                }
769                x if x == Op::DefVar as u8 => {
770                    let idx = self.read_u16(ip);
771                    ip += 2;
772                    out.push_str(&format!(
773                        "DEF_VAR {:>4} ({})\n",
774                        idx, self.constants[idx as usize]
775                    ));
776                }
777                x if x == Op::SetVar as u8 => {
778                    let idx = self.read_u16(ip);
779                    ip += 2;
780                    out.push_str(&format!(
781                        "SET_VAR {:>4} ({})\n",
782                        idx, self.constants[idx as usize]
783                    ));
784                }
785                x if x == Op::GetLocalSlot as u8 => {
786                    let slot = self.read_u16(ip);
787                    ip += 2;
788                    out.push_str(&format!("GET_LOCAL_SLOT {:>4}", slot));
789                    if let Some(info) = self.local_slots.get(slot as usize) {
790                        out.push_str(&format!(" ({})", info.name));
791                    }
792                    out.push('\n');
793                }
794                x if x == Op::DefLocalSlot as u8 => {
795                    let slot = self.read_u16(ip);
796                    ip += 2;
797                    out.push_str(&format!("DEF_LOCAL_SLOT {:>4}", slot));
798                    if let Some(info) = self.local_slots.get(slot as usize) {
799                        out.push_str(&format!(" ({})", info.name));
800                    }
801                    out.push('\n');
802                }
803                x if x == Op::SetLocalSlot as u8 => {
804                    let slot = self.read_u16(ip);
805                    ip += 2;
806                    out.push_str(&format!("SET_LOCAL_SLOT {:>4}", slot));
807                    if let Some(info) = self.local_slots.get(slot as usize) {
808                        out.push_str(&format!(" ({})", info.name));
809                    }
810                    out.push('\n');
811                }
812                x if x == Op::PushScope as u8 => out.push_str("PUSH_SCOPE\n"),
813                x if x == Op::PopScope as u8 => out.push_str("POP_SCOPE\n"),
814                x if x == Op::Add as u8 => out.push_str("ADD\n"),
815                x if x == Op::Sub as u8 => out.push_str("SUB\n"),
816                x if x == Op::Mul as u8 => out.push_str("MUL\n"),
817                x if x == Op::Div as u8 => out.push_str("DIV\n"),
818                x if x == Op::Mod as u8 => out.push_str("MOD\n"),
819                x if x == Op::Pow as u8 => out.push_str("POW\n"),
820                x if x == Op::Negate as u8 => out.push_str("NEGATE\n"),
821                x if x == Op::Equal as u8 => out.push_str("EQUAL\n"),
822                x if x == Op::NotEqual as u8 => out.push_str("NOT_EQUAL\n"),
823                x if x == Op::Less as u8 => out.push_str("LESS\n"),
824                x if x == Op::Greater as u8 => out.push_str("GREATER\n"),
825                x if x == Op::LessEqual as u8 => out.push_str("LESS_EQUAL\n"),
826                x if x == Op::GreaterEqual as u8 => out.push_str("GREATER_EQUAL\n"),
827                x if x == Op::Contains as u8 => out.push_str("CONTAINS\n"),
828                x if x == Op::Not as u8 => out.push_str("NOT\n"),
829                x if x == Op::Jump as u8 => {
830                    let target = self.read_u16(ip);
831                    ip += 2;
832                    out.push_str(&format!("JUMP {:>4}\n", target));
833                }
834                x if x == Op::JumpIfFalse as u8 => {
835                    let target = self.read_u16(ip);
836                    ip += 2;
837                    out.push_str(&format!("JUMP_IF_FALSE {:>4}\n", target));
838                }
839                x if x == Op::JumpIfTrue as u8 => {
840                    let target = self.read_u16(ip);
841                    ip += 2;
842                    out.push_str(&format!("JUMP_IF_TRUE {:>4}\n", target));
843                }
844                x if x == Op::Pop as u8 => out.push_str("POP\n"),
845                x if x == Op::Call as u8 => {
846                    let argc = self.code[ip];
847                    ip += 1;
848                    out.push_str(&format!("CALL {:>4}\n", argc));
849                }
850                x if x == Op::TailCall as u8 => {
851                    let argc = self.code[ip];
852                    ip += 1;
853                    out.push_str(&format!("TAIL_CALL {:>4}\n", argc));
854                }
855                x if x == Op::Return as u8 => out.push_str("RETURN\n"),
856                x if x == Op::Closure as u8 => {
857                    let idx = self.read_u16(ip);
858                    ip += 2;
859                    out.push_str(&format!("CLOSURE {:>4}\n", idx));
860                }
861                x if x == Op::BuildList as u8 => {
862                    let count = self.read_u16(ip);
863                    ip += 2;
864                    out.push_str(&format!("BUILD_LIST {:>4}\n", count));
865                }
866                x if x == Op::BuildDict as u8 => {
867                    let count = self.read_u16(ip);
868                    ip += 2;
869                    out.push_str(&format!("BUILD_DICT {:>4}\n", count));
870                }
871                x if x == Op::Subscript as u8 => out.push_str("SUBSCRIPT\n"),
872                x if x == Op::SubscriptOpt as u8 => out.push_str("SUBSCRIPT_OPT\n"),
873                x if x == Op::Slice as u8 => out.push_str("SLICE\n"),
874                x if x == Op::GetProperty as u8 => {
875                    let idx = self.read_u16(ip);
876                    ip += 2;
877                    out.push_str(&format!(
878                        "GET_PROPERTY {:>4} ({})\n",
879                        idx, self.constants[idx as usize]
880                    ));
881                }
882                x if x == Op::GetPropertyOpt as u8 => {
883                    let idx = self.read_u16(ip);
884                    ip += 2;
885                    out.push_str(&format!(
886                        "GET_PROPERTY_OPT {:>4} ({})\n",
887                        idx, self.constants[idx as usize]
888                    ));
889                }
890                x if x == Op::SetProperty as u8 => {
891                    let idx = self.read_u16(ip);
892                    ip += 2;
893                    out.push_str(&format!(
894                        "SET_PROPERTY {:>4} ({})\n",
895                        idx, self.constants[idx as usize]
896                    ));
897                }
898                x if x == Op::SetSubscript as u8 => {
899                    let idx = self.read_u16(ip);
900                    ip += 2;
901                    out.push_str(&format!(
902                        "SET_SUBSCRIPT {:>4} ({})\n",
903                        idx, self.constants[idx as usize]
904                    ));
905                }
906                x if x == Op::MethodCall as u8 => {
907                    let idx = self.read_u16(ip);
908                    ip += 2;
909                    let argc = self.code[ip];
910                    ip += 1;
911                    out.push_str(&format!(
912                        "METHOD_CALL {:>4} ({}) argc={}\n",
913                        idx, self.constants[idx as usize], argc
914                    ));
915                }
916                x if x == Op::MethodCallOpt as u8 => {
917                    let idx = self.read_u16(ip);
918                    ip += 2;
919                    let argc = self.code[ip];
920                    ip += 1;
921                    out.push_str(&format!(
922                        "METHOD_CALL_OPT {:>4} ({}) argc={}\n",
923                        idx, self.constants[idx as usize], argc
924                    ));
925                }
926                x if x == Op::Concat as u8 => {
927                    let count = self.read_u16(ip);
928                    ip += 2;
929                    out.push_str(&format!("CONCAT {:>4}\n", count));
930                }
931                x if x == Op::IterInit as u8 => out.push_str("ITER_INIT\n"),
932                x if x == Op::IterNext as u8 => {
933                    let target = self.read_u16(ip);
934                    ip += 2;
935                    out.push_str(&format!("ITER_NEXT {:>4}\n", target));
936                }
937                x if x == Op::Throw as u8 => out.push_str("THROW\n"),
938                x if x == Op::TryCatchSetup as u8 => {
939                    let target = self.read_u16(ip);
940                    ip += 2;
941                    out.push_str(&format!("TRY_CATCH_SETUP {:>4}\n", target));
942                }
943                x if x == Op::PopHandler as u8 => out.push_str("POP_HANDLER\n"),
944                x if x == Op::Pipe as u8 => out.push_str("PIPE\n"),
945                x if x == Op::Parallel as u8 => out.push_str("PARALLEL\n"),
946                x if x == Op::ParallelMap as u8 => out.push_str("PARALLEL_MAP\n"),
947                x if x == Op::ParallelSettle as u8 => out.push_str("PARALLEL_SETTLE\n"),
948                x if x == Op::Spawn as u8 => out.push_str("SPAWN\n"),
949                x if x == Op::Import as u8 => {
950                    let idx = self.read_u16(ip);
951                    ip += 2;
952                    out.push_str(&format!(
953                        "IMPORT {:>4} ({})\n",
954                        idx, self.constants[idx as usize]
955                    ));
956                }
957                x if x == Op::SelectiveImport as u8 => {
958                    let path_idx = self.read_u16(ip);
959                    ip += 2;
960                    let names_idx = self.read_u16(ip);
961                    ip += 2;
962                    out.push_str(&format!(
963                        "SELECTIVE_IMPORT {:>4} ({}) names: {:>4} ({})\n",
964                        path_idx,
965                        self.constants[path_idx as usize],
966                        names_idx,
967                        self.constants[names_idx as usize]
968                    ));
969                }
970                x if x == Op::SyncMutexEnter as u8 => {
971                    let idx = self.read_u16(ip);
972                    ip += 2;
973                    out.push_str(&format!(
974                        "SYNC_MUTEX_ENTER {:>4} ({})\n",
975                        idx, self.constants[idx as usize]
976                    ));
977                }
978                x if x == Op::DeadlineSetup as u8 => out.push_str("DEADLINE_SETUP\n"),
979                x if x == Op::DeadlineEnd as u8 => out.push_str("DEADLINE_END\n"),
980                x if x == Op::BuildEnum as u8 => {
981                    let enum_idx = self.read_u16(ip);
982                    ip += 2;
983                    let variant_idx = self.read_u16(ip);
984                    ip += 2;
985                    let field_count = self.read_u16(ip);
986                    ip += 2;
987                    out.push_str(&format!(
988                        "BUILD_ENUM {:>4} ({}) {:>4} ({}) fields={}\n",
989                        enum_idx,
990                        self.constants[enum_idx as usize],
991                        variant_idx,
992                        self.constants[variant_idx as usize],
993                        field_count
994                    ));
995                }
996                x if x == Op::MatchEnum as u8 => {
997                    let enum_idx = self.read_u16(ip);
998                    ip += 2;
999                    let variant_idx = self.read_u16(ip);
1000                    ip += 2;
1001                    out.push_str(&format!(
1002                        "MATCH_ENUM {:>4} ({}) {:>4} ({})\n",
1003                        enum_idx,
1004                        self.constants[enum_idx as usize],
1005                        variant_idx,
1006                        self.constants[variant_idx as usize]
1007                    ));
1008                }
1009                x if x == Op::PopIterator as u8 => out.push_str("POP_ITERATOR\n"),
1010                x if x == Op::TryUnwrap as u8 => out.push_str("TRY_UNWRAP\n"),
1011                x if x == Op::TryWrapOk as u8 => out.push_str("TRY_WRAP_OK\n"),
1012                x if x == Op::CallSpread as u8 => out.push_str("CALL_SPREAD\n"),
1013                x if x == Op::CallBuiltin as u8 => {
1014                    let id = self.read_u64(ip);
1015                    ip += 8;
1016                    let idx = self.read_u16(ip);
1017                    ip += 2;
1018                    let argc = self.code[ip];
1019                    ip += 1;
1020                    out.push_str(&format!(
1021                        "CALL_BUILTIN {id:#018x} {:>4} ({}) argc={}\n",
1022                        idx, self.constants[idx as usize], argc
1023                    ));
1024                }
1025                x if x == Op::CallBuiltinSpread as u8 => {
1026                    let id = self.read_u64(ip);
1027                    ip += 8;
1028                    let idx = self.read_u16(ip);
1029                    ip += 2;
1030                    out.push_str(&format!(
1031                        "CALL_BUILTIN_SPREAD {id:#018x} {:>4} ({})\n",
1032                        idx, self.constants[idx as usize]
1033                    ));
1034                }
1035                x if x == Op::MethodCallSpread as u8 => {
1036                    let idx = self.read_u16(ip + 1);
1037                    ip += 2;
1038                    out.push_str(&format!("METHOD_CALL_SPREAD {idx}\n"));
1039                }
1040                x if x == Op::Dup as u8 => out.push_str("DUP\n"),
1041                x if x == Op::Swap as u8 => out.push_str("SWAP\n"),
1042                x if x == Op::AddInt as u8 => out.push_str("ADD_INT\n"),
1043                x if x == Op::SubInt as u8 => out.push_str("SUB_INT\n"),
1044                x if x == Op::MulInt as u8 => out.push_str("MUL_INT\n"),
1045                x if x == Op::DivInt as u8 => out.push_str("DIV_INT\n"),
1046                x if x == Op::ModInt as u8 => out.push_str("MOD_INT\n"),
1047                x if x == Op::AddFloat as u8 => out.push_str("ADD_FLOAT\n"),
1048                x if x == Op::SubFloat as u8 => out.push_str("SUB_FLOAT\n"),
1049                x if x == Op::MulFloat as u8 => out.push_str("MUL_FLOAT\n"),
1050                x if x == Op::DivFloat as u8 => out.push_str("DIV_FLOAT\n"),
1051                x if x == Op::ModFloat as u8 => out.push_str("MOD_FLOAT\n"),
1052                x if x == Op::EqualInt as u8 => out.push_str("EQUAL_INT\n"),
1053                x if x == Op::NotEqualInt as u8 => out.push_str("NOT_EQUAL_INT\n"),
1054                x if x == Op::LessInt as u8 => out.push_str("LESS_INT\n"),
1055                x if x == Op::GreaterInt as u8 => out.push_str("GREATER_INT\n"),
1056                x if x == Op::LessEqualInt as u8 => out.push_str("LESS_EQUAL_INT\n"),
1057                x if x == Op::GreaterEqualInt as u8 => out.push_str("GREATER_EQUAL_INT\n"),
1058                x if x == Op::EqualFloat as u8 => out.push_str("EQUAL_FLOAT\n"),
1059                x if x == Op::NotEqualFloat as u8 => out.push_str("NOT_EQUAL_FLOAT\n"),
1060                x if x == Op::LessFloat as u8 => out.push_str("LESS_FLOAT\n"),
1061                x if x == Op::GreaterFloat as u8 => out.push_str("GREATER_FLOAT\n"),
1062                x if x == Op::LessEqualFloat as u8 => out.push_str("LESS_EQUAL_FLOAT\n"),
1063                x if x == Op::GreaterEqualFloat as u8 => out.push_str("GREATER_EQUAL_FLOAT\n"),
1064                x if x == Op::EqualBool as u8 => out.push_str("EQUAL_BOOL\n"),
1065                x if x == Op::NotEqualBool as u8 => out.push_str("NOT_EQUAL_BOOL\n"),
1066                x if x == Op::EqualString as u8 => out.push_str("EQUAL_STRING\n"),
1067                x if x == Op::NotEqualString as u8 => out.push_str("NOT_EQUAL_STRING\n"),
1068                x if x == Op::Yield as u8 => out.push_str("YIELD\n"),
1069                _ => {
1070                    out.push_str(&format!("UNKNOWN(0x{:02x})\n", op));
1071                }
1072            }
1073        }
1074        out
1075    }
1076}
1077
1078impl Default for Chunk {
1079    fn default() -> Self {
1080        Self::new()
1081    }
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086    use super::Op;
1087
1088    #[test]
1089    fn op_from_byte_matches_repr_order() {
1090        for (byte, op) in Op::ALL.iter().copied().enumerate() {
1091            assert_eq!(byte as u8, op as u8);
1092            assert_eq!(Op::from_byte(byte as u8), Some(op));
1093        }
1094        assert_eq!(Op::from_byte(Op::ALL.len() as u8), None);
1095    }
1096}