Skip to main content

just_engine/runner/jit/
vm.rs

1//! Stack-based bytecode virtual machine.
2//!
3//! Executes the bytecode emitted by the compiler. Uses a flat instruction
4//! dispatch loop instead of recursive AST walking, which eliminates
5//! per-node function call overhead and improves cache locality.
6
7use crate::runner::ds::error::JErrorType;
8use crate::runner::ds::value::{JsNumberType, JsValue};
9use crate::runner::plugin::types::EvalContext;
10use crate::runner::ds::lex_env::JsLexEnvironmentType;
11use crate::runner::ds::object::{JsObject, ObjectType};
12use crate::runner::ds::object_property::{PropertyDescriptor, PropertyDescriptorData, PropertyKey};
13use crate::runner::ds::value::JsValueOrSelf;
14use crate::runner::ds::operations::type_conversion::to_string;
15use crate::runner::eval::expression::{SimpleFunctionObject, call_function_object};
16
17use std::cell::RefCell;
18use std::rc::Rc;
19
20use super::bytecode::{Chunk, OpCode};
21
22/// Result of VM execution.
23pub enum VmResult {
24    /// Normal completion with a value.
25    Ok(JsValue),
26    /// Runtime error.
27    Error(JErrorType),
28}
29
30#[derive(Clone)]
31struct EnvCacheEntry {
32    version: u64,
33    env: JsLexEnvironmentType,
34}
35
36#[derive(Clone)]
37struct PropCacheEntry {
38    obj: crate::runner::ds::object::JsObjectType,
39    prop: String,
40}
41
42/// The bytecode virtual machine.
43pub struct Vm<'a> {
44    chunk: &'a Chunk,
45    /// Instruction pointer.
46    ip: usize,
47    /// Operand stack.
48    stack: Vec<JsValue>,
49    /// Local slots for fast var access.
50    locals: Vec<JsValue>,
51    /// Inline caches for GetVar.
52    get_var_cache: Vec<Option<EnvCacheEntry>>,
53    /// Inline caches for SetVar.
54    set_var_cache: Vec<Option<EnvCacheEntry>>,
55    /// Inline caches for GetProp.
56    get_prop_cache: Vec<Option<PropCacheEntry>>,
57    /// Evaluation context (variables, scoping, heap).
58    ctx: EvalContext,
59}
60
61impl<'a> Vm<'a> {
62    pub fn new(chunk: &'a Chunk, ctx: EvalContext) -> Self {
63        Vm {
64            chunk,
65            ip: 0,
66            stack: Vec::with_capacity(256),
67            locals: vec![JsValue::Undefined; chunk.locals.len()],
68            get_var_cache: vec![None; chunk.code.len()],
69            set_var_cache: vec![None; chunk.code.len()],
70            get_prop_cache: vec![None; chunk.code.len()],
71            ctx,
72        }
73    }
74
75    /// Consume the VM and return the evaluation context (for variable inspection after execution).
76    pub fn into_ctx(mut self) -> EvalContext {
77        self.sync_locals_into_ctx();
78        self.ctx
79    }
80
81    /// Run the bytecode to completion.
82    pub fn run(&mut self) -> VmResult {
83        loop {
84            if self.ip >= self.chunk.code.len() {
85                return VmResult::Ok(self.stack.pop().unwrap_or(JsValue::Undefined));
86            }
87
88            let instr_index = self.ip;
89            let instr = &self.chunk.code[instr_index];
90            let op = instr.op;
91            let operand = instr.operand;
92            let operand2 = instr.operand2;
93            let operand3 = instr.operand3;
94            self.ip += 1;
95
96            match op {
97                // ── Constants & Literals ──────────────────────
98                OpCode::Constant => {
99                    let val = self.chunk.constants[operand as usize].clone();
100                    self.stack.push(val);
101                }
102                OpCode::Undefined => self.stack.push(JsValue::Undefined),
103                OpCode::Null => self.stack.push(JsValue::Null),
104                OpCode::True => self.stack.push(JsValue::Boolean(true)),
105                OpCode::False => self.stack.push(JsValue::Boolean(false)),
106
107                // ── Arithmetic ───────────────────────────────
108                OpCode::Add => {
109                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
110                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
111                    self.stack.push(self.js_add(a, b));
112                }
113                OpCode::Sub => {
114                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
115                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
116                    self.stack.push(self.js_sub(a, b));
117                }
118                OpCode::Mul => {
119                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
120                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
121                    self.stack.push(self.js_mul(a, b));
122                }
123                OpCode::Div => {
124                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
125                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
126                    self.stack.push(self.js_div(a, b));
127                }
128                OpCode::Mod => {
129                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
130                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
131                    self.stack.push(self.js_mod(a, b));
132                }
133                OpCode::Negate => {
134                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
135                    self.stack.push(self.js_negate(a));
136                }
137
138                // ── Bitwise ──────────────────────────────────
139                OpCode::BitAnd => {
140                    let bv = self.stack.pop().unwrap_or(JsValue::Undefined);
141                    let av = self.stack.pop().unwrap_or(JsValue::Undefined);
142                    let b = self.to_i32(bv);
143                    let a = self.to_i32(av);
144                    self.stack.push(JsValue::Number(JsNumberType::Integer((a & b) as i64)));
145                }
146                OpCode::BitOr => {
147                    let bv = self.stack.pop().unwrap_or(JsValue::Undefined);
148                    let av = self.stack.pop().unwrap_or(JsValue::Undefined);
149                    let b = self.to_i32(bv);
150                    let a = self.to_i32(av);
151                    self.stack.push(JsValue::Number(JsNumberType::Integer((a | b) as i64)));
152                }
153                OpCode::BitXor => {
154                    let bv = self.stack.pop().unwrap_or(JsValue::Undefined);
155                    let av = self.stack.pop().unwrap_or(JsValue::Undefined);
156                    let b = self.to_i32(bv);
157                    let a = self.to_i32(av);
158                    self.stack.push(JsValue::Number(JsNumberType::Integer((a ^ b) as i64)));
159                }
160                OpCode::BitNot => {
161                    let av = self.stack.pop().unwrap_or(JsValue::Undefined);
162                    let a = self.to_i32(av);
163                    self.stack.push(JsValue::Number(JsNumberType::Integer((!a) as i64)));
164                }
165                OpCode::ShiftLeft => {
166                    let bv = self.stack.pop().unwrap_or(JsValue::Undefined);
167                    let av = self.stack.pop().unwrap_or(JsValue::Undefined);
168                    let b = self.to_u32(bv);
169                    let a = self.to_i32(av);
170                    self.stack.push(JsValue::Number(JsNumberType::Integer(
171                        (a << (b & 0x1f)) as i64,
172                    )));
173                }
174                OpCode::ShiftRight => {
175                    let bv = self.stack.pop().unwrap_or(JsValue::Undefined);
176                    let av = self.stack.pop().unwrap_or(JsValue::Undefined);
177                    let b = self.to_u32(bv);
178                    let a = self.to_i32(av);
179                    self.stack.push(JsValue::Number(JsNumberType::Integer(
180                        (a >> (b & 0x1f)) as i64,
181                    )));
182                }
183                OpCode::UShiftRight => {
184                    let bv = self.stack.pop().unwrap_or(JsValue::Undefined);
185                    let av = self.stack.pop().unwrap_or(JsValue::Undefined);
186                    let b = self.to_u32(bv);
187                    let a = self.to_u32(av);
188                    self.stack.push(JsValue::Number(JsNumberType::Integer(
189                        (a >> (b & 0x1f)) as i64,
190                    )));
191                }
192
193                // ── Comparison ───────────────────────────────
194                OpCode::StrictEqual => {
195                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
196                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
197                    self.stack.push(JsValue::Boolean(self.js_strict_equal(&a, &b)));
198                }
199                OpCode::StrictNotEqual => {
200                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
201                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
202                    self.stack.push(JsValue::Boolean(!self.js_strict_equal(&a, &b)));
203                }
204                OpCode::Equal => {
205                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
206                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
207                    self.stack.push(JsValue::Boolean(self.js_abstract_equal(&a, &b)));
208                }
209                OpCode::NotEqual => {
210                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
211                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
212                    self.stack.push(JsValue::Boolean(!self.js_abstract_equal(&a, &b)));
213                }
214                OpCode::LessThan => {
215                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
216                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
217                    self.stack.push(JsValue::Boolean(self.js_less_than(&a, &b)));
218                }
219                OpCode::LessEqual => {
220                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
221                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
222                    // a <= b  is  !(b < a)
223                    self.stack.push(JsValue::Boolean(!self.js_less_than(&b, &a)));
224                }
225                OpCode::GreaterThan => {
226                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
227                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
228                    self.stack.push(JsValue::Boolean(self.js_less_than(&b, &a)));
229                }
230                OpCode::GreaterEqual => {
231                    let b = self.stack.pop().unwrap_or(JsValue::Undefined);
232                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
233                    self.stack.push(JsValue::Boolean(!self.js_less_than(&a, &b)));
234                }
235
236                // ── Logical / Unary ──────────────────────────
237                OpCode::Not => {
238                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
239                    self.stack.push(JsValue::Boolean(!self.is_truthy(&a)));
240                }
241                OpCode::TypeOf => {
242                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
243                    let type_str = match &a {
244                        JsValue::Undefined => "undefined",
245                        JsValue::Null => "object",
246                        JsValue::Boolean(_) => "boolean",
247                        JsValue::Number(_) => "number",
248                        JsValue::String(_) => "string",
249                        JsValue::Symbol(_) => "symbol",
250                        JsValue::Object(_) => "object",
251                    };
252                    self.stack.push(JsValue::String(type_str.to_string()));
253                }
254                OpCode::Void => {
255                    self.stack.pop();
256                    self.stack.push(JsValue::Undefined);
257                }
258                OpCode::UnaryPlus => {
259                    let a = self.stack.pop().unwrap_or(JsValue::Undefined);
260                    self.stack.push(self.to_number_value(a));
261                }
262
263                // ── Variables ────────────────────────────────
264                OpCode::GetVar => {
265                    let name = self.chunk.get_name(operand);
266                    match self.get_var_cached(instr_index, name) {
267                        Ok(val) => self.stack.push(val),
268                        Err(e) => return VmResult::Error(e),
269                    }
270                }
271                OpCode::SetVar => {
272                    let name = self.chunk.get_name(operand);
273                    let val = self.stack.pop().unwrap_or(JsValue::Undefined);
274                    if let Err(e) = self.set_var_cached(instr_index, name, val) {
275                        return VmResult::Error(e);
276                    }
277                }
278                OpCode::DeclareVar => {
279                    let name = self.chunk.get_name(operand);
280                    if !self.ctx.has_var_binding(name) {
281                        if let Err(e) = self.ctx.create_var_binding(name) {
282                            return VmResult::Error(e);
283                        }
284                    }
285                }
286                OpCode::DeclareLet => {
287                    let name = self.chunk.get_name(operand);
288                    if let Err(e) = self.ctx.create_binding(name, false) {
289                        return VmResult::Error(e);
290                    }
291                }
292                OpCode::DeclareConst => {
293                    let name = self.chunk.get_name(operand);
294                    if let Err(e) = self.ctx.create_binding(name, true) {
295                        return VmResult::Error(e);
296                    }
297                }
298                OpCode::InitVar => {
299                    let name = self.chunk.get_name(operand);
300                    let val = self.stack.pop().unwrap_or(JsValue::Undefined);
301                    // First try initialize (works when binding is uninitialized).
302                    // If that silently no-ops (binding already initialized),
303                    // fall back to set (works when binding is already initialized).
304                    if let Err(e) = self.ctx.initialize_var_binding(name, val.clone()) {
305                        return VmResult::Error(e);
306                    }
307                    // Also set, in case initialize was a no-op (already initialized).
308                    // set_var_binding will error on uninitialized, so ignore that error.
309                    let _ = self.ctx.set_var_binding(name, val);
310                }
311                OpCode::InitBinding => {
312                    let name = self.chunk.get_name(operand);
313                    let val = self.stack.pop().unwrap_or(JsValue::Undefined);
314                    if let Err(e) = self.ctx.initialize_binding(name, val) {
315                        return VmResult::Error(e);
316                    }
317                }
318
319                OpCode::GetLocal => {
320                    let slot = operand as usize;
321                    let val = self.locals.get(slot).cloned().unwrap_or(JsValue::Undefined);
322                    self.stack.push(val);
323                }
324                OpCode::SetLocal | OpCode::InitLocal => {
325                    let slot = operand as usize;
326                    let val = self.stack.pop().unwrap_or(JsValue::Undefined);
327                    if let Some(local) = self.locals.get_mut(slot) {
328                        *local = val;
329                    }
330                }
331
332                // ── Control Flow ─────────────────────────────
333                OpCode::Jump => {
334                    self.ip = operand as usize;
335                }
336                OpCode::JumpIfFalse => {
337                    let val = self.stack.pop().unwrap_or(JsValue::Undefined);
338                    if !self.is_truthy(&val) {
339                        self.ip = operand as usize;
340                    }
341                }
342                OpCode::JumpIfTrue => {
343                    let val = self.stack.pop().unwrap_or(JsValue::Undefined);
344                    if self.is_truthy(&val) {
345                        self.ip = operand as usize;
346                    }
347                }
348
349                OpCode::LoopStart => {
350                    // No-op marker
351                }
352
353                // ── Stack manipulation ───────────────────────
354                OpCode::Pop => {
355                    self.stack.pop();
356                }
357                OpCode::Dup => {
358                    if let Some(top) = self.stack.last() {
359                        self.stack.push(top.clone());
360                    }
361                }
362                OpCode::Dup2 => {
363                    if self.stack.len() >= 2 {
364                        let len = self.stack.len();
365                        let first = self.stack[len - 2].clone();
366                        let second = self.stack[len - 1].clone();
367                        self.stack.push(first);
368                        self.stack.push(second);
369                    }
370                }
371
372                // ── Scope ────────────────────────────────────
373                OpCode::PushScope => {
374                    self.ctx.push_block_scope();
375                }
376                OpCode::PopScope => {
377                    self.ctx.pop_block_scope();
378                }
379
380                // ── Objects & Properties ─────────────────────
381                OpCode::GetProp => {
382                    let obj_val = self.stack.pop().unwrap_or(JsValue::Undefined);
383                    let prop_name = self.chunk.get_name(operand);
384                    match self.get_prop_cached(instr_index, &obj_val, prop_name) {
385                        Ok(val) => self.stack.push(val),
386                        Err(e) => return VmResult::Error(e),
387                    }
388                }
389                OpCode::SetProp => {
390                    let val = self.stack.pop().unwrap_or(JsValue::Undefined);
391                    let obj_val = self.stack.pop().unwrap_or(JsValue::Undefined);
392                    let prop_name = self.chunk.get_name(operand);
393                    if let Err(e) = self.set_prop_value(&obj_val, prop_name, val.clone()) {
394                        return VmResult::Error(e);
395                    }
396                    self.stack.push(val);
397                }
398                OpCode::GetElem => {
399                    let key_val = self.stack.pop().unwrap_or(JsValue::Undefined);
400                    let obj_val = self.stack.pop().unwrap_or(JsValue::Undefined);
401                    let prop_name = match self.to_property_key(&key_val) {
402                        Ok(key) => key,
403                        Err(e) => return VmResult::Error(e),
404                    };
405                    match self.get_prop_cached(instr_index, &obj_val, &prop_name) {
406                        Ok(val) => self.stack.push(val),
407                        Err(e) => return VmResult::Error(e),
408                    }
409                }
410                OpCode::SetElem => {
411                    let val = self.stack.pop().unwrap_or(JsValue::Undefined);
412                    let key_val = self.stack.pop().unwrap_or(JsValue::Undefined);
413                    let obj_val = self.stack.pop().unwrap_or(JsValue::Undefined);
414                    let prop_name = match self.to_property_key(&key_val) {
415                        Ok(key) => key,
416                        Err(e) => return VmResult::Error(e),
417                    };
418                    if let Err(e) = self.set_prop_value(&obj_val, &prop_name, val.clone()) {
419                        return VmResult::Error(e);
420                    }
421                    self.stack.push(val);
422                }
423
424                // ── Function calls ───────────────────────────
425                OpCode::Call => {
426                    let argc = operand as usize;
427                    let mut args = Vec::with_capacity(argc);
428                    for _ in 0..argc {
429                        args.push(self.stack.pop().unwrap_or(JsValue::Undefined));
430                    }
431                    args.reverse();
432                    let callee = self.stack.pop().unwrap_or(JsValue::Undefined);
433                    match &callee {
434                        JsValue::Object(obj) => {
435                            let this_val = JsValue::Undefined;
436                            match call_function_object(obj, this_val, args, &mut self.ctx) {
437                                Ok(result) => self.stack.push(result),
438                                Err(e) => return VmResult::Error(e),
439                            }
440                        }
441                        _ => return VmResult::Error(JErrorType::TypeError(
442                            format!("{} is not a function", callee),
443                        )),
444                    }
445                }
446                OpCode::CallMethod => {
447                    let argc = operand as usize;
448                    let method_name = self.chunk.get_name(operand2);
449                    let obj_var_name = self.chunk.get_name(operand3);
450                    let mut args = Vec::with_capacity(argc);
451                    for _ in 0..argc {
452                        args.push(self.stack.pop().unwrap_or(JsValue::Undefined));
453                    }
454                    args.reverse();
455                    let object = self.stack.pop().unwrap_or(JsValue::Undefined);
456
457                    // 1. Try super-global dispatch using compile-time object name
458                    //    (e.g. "Math", "console")
459                    if !obj_var_name.is_empty() {
460                        let sg = self.ctx.super_global.clone();
461                        let result = sg.borrow().call_method(
462                            obj_var_name, method_name,
463                            &mut self.ctx, object.clone(), args.clone(),
464                        );
465                        if let Some(res) = result {
466                            match res {
467                                Ok(val) => { self.stack.push(val); continue; }
468                                Err(e) => return VmResult::Error(e),
469                            }
470                        }
471                    }
472
473                    // 2. Type-based super-global dispatch (e.g. String, Number)
474                    let type_name = match &object {
475                        JsValue::String(_) => Some("String"),
476                        JsValue::Number(_) => Some("Number"),
477                        _ => None,
478                    };
479                    if let Some(type_name) = type_name {
480                        let sg = self.ctx.super_global.clone();
481                        let result = sg.borrow().call_method(
482                            type_name, method_name,
483                            &mut self.ctx, object.clone(), args.clone(),
484                        );
485                        if let Some(res) = result {
486                            match res {
487                                Ok(val) => { self.stack.push(val); continue; }
488                                Err(e) => return VmResult::Error(e),
489                            }
490                        }
491                    }
492
493                    // 3. Fallback: look up the method as a property on the object
494                    if let JsValue::Object(ref obj) = object {
495                        let prop_key = PropertyKey::Str(method_name.to_string());
496                        let method_val = {
497                            let obj_ref = obj.borrow();
498                            obj_ref
499                                .as_js_object()
500                                .get_own_property(&prop_key)
501                                .ok()
502                                .flatten()
503                                .and_then(|desc| match desc {
504                                    PropertyDescriptor::Data(data) => Some(data.value.clone()),
505                                    _ => None,
506                                })
507                        };
508                        let method_val = method_val.or_else(|| {
509                            let obj_ref = obj.borrow();
510                            let proto = obj_ref.as_js_object().get_prototype_of();
511                            if let Some(proto) = proto {
512                                let proto_ref = proto.borrow();
513                                proto_ref
514                                    .as_js_object()
515                                    .get_own_property(&prop_key)
516                                    .ok()
517                                    .flatten()
518                                    .and_then(|desc| match desc {
519                                        PropertyDescriptor::Data(data) => Some(data.value.clone()),
520                                        _ => None,
521                                    })
522                            } else {
523                                None
524                            }
525                        });
526
527                        if let Some(JsValue::Object(method_obj)) = method_val {
528                            match call_function_object(&method_obj, object.clone(), args, &mut self.ctx) {
529                                Ok(result) => {
530                                    self.stack.push(result);
531                                    continue;
532                                }
533                                Err(e) => return VmResult::Error(e),
534                            }
535                        }
536                    }
537
538                    // Final fallback: undefined
539                    self.stack.push(JsValue::Undefined);
540                }
541
542                // ── Misc ─────────────────────────────────────
543                OpCode::Return => {
544                    let val = self.stack.pop().unwrap_or(JsValue::Undefined);
545                    return VmResult::Ok(val);
546                }
547                OpCode::Halt => {
548                    return VmResult::Ok(self.stack.pop().unwrap_or(JsValue::Undefined));
549                }
550
551                // ── Pre/Post increment/decrement ─────────────
552                OpCode::PreIncVar => {
553                    let name = self.chunk.get_name(operand);
554                    match self.get_var_cached(instr_index, name) {
555                        Ok(val) => {
556                            let num = self.to_f64(&val) + 1.0;
557                            let new_val = self.f64_to_jsvalue(num);
558                            if let Err(e) = self.set_var_cached(instr_index, name, new_val.clone()) {
559                                return VmResult::Error(e);
560                            }
561                            self.stack.push(new_val);
562                        }
563                        Err(e) => return VmResult::Error(e),
564                    }
565                }
566                OpCode::PreDecVar => {
567                    let name = self.chunk.get_name(operand);
568                    match self.get_var_cached(instr_index, name) {
569                        Ok(val) => {
570                            let num = self.to_f64(&val) - 1.0;
571                            let new_val = self.f64_to_jsvalue(num);
572                            if let Err(e) = self.set_var_cached(instr_index, name, new_val.clone()) {
573                                return VmResult::Error(e);
574                            }
575                            self.stack.push(new_val);
576                        }
577                        Err(e) => return VmResult::Error(e),
578                    }
579                }
580                OpCode::PostIncVar => {
581                    let name = self.chunk.get_name(operand);
582                    match self.get_var_cached(instr_index, name) {
583                        Ok(val) => {
584                            let old_val = val.clone();
585                            let num = self.to_f64(&val) + 1.0;
586                            let new_val = self.f64_to_jsvalue(num);
587                            if let Err(e) = self.set_var_cached(instr_index, name, new_val) {
588                                return VmResult::Error(e);
589                            }
590                            self.stack.push(old_val);
591                        }
592                        Err(e) => return VmResult::Error(e),
593                    }
594                }
595                OpCode::PostDecVar => {
596                    let name = self.chunk.get_name(operand);
597                    match self.get_var_cached(instr_index, name) {
598                        Ok(val) => {
599                            let old_val = val.clone();
600                            let num = self.to_f64(&val) - 1.0;
601                            let new_val = self.f64_to_jsvalue(num);
602                            if let Err(e) = self.set_var_cached(instr_index, name, new_val) {
603                                return VmResult::Error(e);
604                            }
605                            self.stack.push(old_val);
606                        }
607                        Err(e) => return VmResult::Error(e),
608                    }
609                }
610
611                OpCode::GetVarForUpdate => {
612                    let name = self.chunk.get_name(operand);
613                    match self.get_var_cached(instr_index, name) {
614                        Ok(val) => self.stack.push(val),
615                        Err(e) => return VmResult::Error(e),
616                    }
617                }
618
619                // ── Closures ─────────────────────────────────
620                OpCode::MakeClosure => {
621                    let func_template = &self.chunk.functions[operand as usize];
622                    let body_ptr = func_template.body_ptr;
623                    let params_ptr = func_template.params_ptr;
624                    let environment = self.ctx.lex_env.clone();
625
626                    let mut func_obj = SimpleFunctionObject::new(body_ptr, params_ptr, environment);
627
628                    // Add marker property to identify as callable
629                    func_obj.get_object_base_mut().properties.insert(
630                        PropertyKey::Str("__simple_function__".to_string()),
631                        PropertyDescriptor::Data(PropertyDescriptorData {
632                            value: JsValue::Boolean(true),
633                            writable: false,
634                            enumerable: false,
635                            configurable: false,
636                        }),
637                    );
638
639                    let obj_ref = Rc::new(RefCell::new(ObjectType::Ordinary(Box::new(func_obj))));
640                    self.stack.push(JsValue::Object(obj_ref));
641                }
642            }
643        }
644    }
645
646    // Helper methods
647    // ════════════════════════════════════════════════════════════
648
649    fn get_var_cached(&mut self, instr_index: usize, name: &str) -> Result<JsValue, JErrorType> {
650        if let Some(Some(entry)) = self.get_var_cache.get(instr_index) {
651            if entry.version == self.ctx.current_lex_env_version() {
652                if let Ok(val) = self.ctx.get_binding_in_env(&entry.env, name) {
653                    return Ok(val);
654                }
655            }
656        }
657
658        let (val, env) = self.ctx.get_binding_with_env(name)?;
659        let entry = EnvCacheEntry {
660            version: self.ctx.current_lex_env_version(),
661            env,
662        };
663        if let Some(slot) = self.get_var_cache.get_mut(instr_index) {
664            *slot = Some(entry);
665        }
666        Ok(val)
667    }
668
669    fn set_var_cached(
670        &mut self,
671        instr_index: usize,
672        name: &str,
673        value: JsValue,
674    ) -> Result<(), JErrorType> {
675        if let Some(Some(entry)) = self.set_var_cache.get(instr_index) {
676            if entry.version == self.ctx.current_lex_env_version() {
677                if self
678                    .ctx
679                    .set_binding_in_env_cached(&entry.env, name, value.clone())
680                    .is_ok()
681                {
682                    return Ok(());
683                }
684            }
685        }
686
687        let env = self.ctx.set_binding_with_env(name, value)?;
688        let entry = EnvCacheEntry {
689            version: self.ctx.current_lex_env_version(),
690            env,
691        };
692        if let Some(slot) = self.set_var_cache.get_mut(instr_index) {
693            *slot = Some(entry);
694        }
695        Ok(())
696    }
697
698    fn get_prop_cached(
699        &mut self,
700        instr_index: usize,
701        obj_val: &JsValue,
702        prop_name: &str,
703    ) -> Result<JsValue, JErrorType> {
704        if let JsValue::Object(obj) = obj_val {
705            if let Some(Some(entry)) = self.get_prop_cache.get(instr_index) {
706                if std::rc::Rc::ptr_eq(obj, &entry.obj) && entry.prop == prop_name {
707                    let prop_key = PropertyKey::Str(prop_name.to_string());
708                    return obj
709                        .borrow()
710                        .as_js_object()
711                        .get(&mut self.ctx.ctx_stack, &prop_key, JsValueOrSelf::SelfValue);
712                }
713            }
714
715            let prop_key = PropertyKey::Str(prop_name.to_string());
716            let val = obj
717                .borrow()
718                .as_js_object()
719                .get(&mut self.ctx.ctx_stack, &prop_key, JsValueOrSelf::SelfValue)?;
720            let entry = PropCacheEntry {
721                obj: obj.clone(),
722                prop: prop_name.to_string(),
723            };
724            if let Some(slot) = self.get_prop_cache.get_mut(instr_index) {
725                *slot = Some(entry);
726            }
727            Ok(val)
728        } else if matches!(obj_val, JsValue::Undefined | JsValue::Null) {
729            Err(JErrorType::TypeError("Cannot read property of null/undefined".to_string()))
730        } else {
731            Ok(JsValue::Undefined)
732        }
733    }
734
735    fn set_prop_value(
736        &mut self,
737        obj_val: &JsValue,
738        prop_name: &str,
739        value: JsValue,
740    ) -> Result<(), JErrorType> {
741        match obj_val {
742            JsValue::Object(obj) => {
743                let prop_key = PropertyKey::Str(prop_name.to_string());
744                let mut obj_ref = obj.borrow_mut();
745                let ok = obj_ref
746                    .as_js_object_mut()
747                    .set(&mut self.ctx.ctx_stack, prop_key, value, JsValueOrSelf::SelfValue)?;
748                if ok {
749                    Ok(())
750                } else {
751                    Err(JErrorType::TypeError("Failed to set property".to_string()))
752                }
753            }
754            _ => Err(JErrorType::TypeError("Cannot set property on non-object".to_string())),
755        }
756    }
757
758    fn to_property_key(&mut self, value: &JsValue) -> Result<String, JErrorType> {
759        to_string(&mut self.ctx.ctx_stack, value)
760    }
761
762    fn sync_locals_into_ctx(&mut self) {
763        for (slot, val) in self.locals.iter().enumerate() {
764            let name = self.chunk.get_local_name(slot as u32);
765            if !self.ctx.has_var_binding(name) {
766                let _ = self.ctx.create_var_binding(name);
767                let _ = self.ctx.initialize_var_binding(name, val.clone());
768            } else {
769                let _ = self.ctx.set_var_binding(name, val.clone());
770            }
771        }
772    }
773
774    // ── Type coercion helpers ────────────────────────────────
775
776    fn is_truthy(&self, val: &JsValue) -> bool {
777        match val {
778            JsValue::Undefined | JsValue::Null => false,
779            JsValue::Boolean(b) => *b,
780            JsValue::Number(n) => match n {
781                JsNumberType::Integer(i) => *i != 0,
782                JsNumberType::Float(f) => *f != 0.0 && !f.is_nan(),
783                JsNumberType::NaN => false,
784                JsNumberType::PositiveInfinity | JsNumberType::NegativeInfinity => true,
785            },
786            JsValue::String(s) => !s.is_empty(),
787            _ => true,
788        }
789    }
790
791    fn to_f64(&self, val: &JsValue) -> f64 {
792        match val {
793            JsValue::Number(JsNumberType::Integer(i)) => *i as f64,
794            JsValue::Number(JsNumberType::Float(f)) => *f,
795            JsValue::Number(JsNumberType::NaN) => f64::NAN,
796            JsValue::Number(JsNumberType::PositiveInfinity) => f64::INFINITY,
797            JsValue::Number(JsNumberType::NegativeInfinity) => f64::NEG_INFINITY,
798            JsValue::Boolean(true) => 1.0,
799            JsValue::Boolean(false) => 0.0,
800            JsValue::Null => 0.0,
801            JsValue::Undefined => f64::NAN,
802            JsValue::String(s) => s.parse::<f64>().unwrap_or(f64::NAN),
803            _ => f64::NAN,
804        }
805    }
806
807    fn to_i32(&self, val: JsValue) -> i32 {
808        let n = self.to_f64(&val);
809        if n.is_nan() || n.is_infinite() || n == 0.0 {
810            0
811        } else {
812            (n as i64 & 0xFFFFFFFF) as i32
813        }
814    }
815
816    fn to_u32(&self, val: JsValue) -> u32 {
817        let n = self.to_f64(&val);
818        if n.is_nan() || n.is_infinite() || n == 0.0 {
819            0
820        } else {
821            (n as i64 & 0xFFFFFFFF) as u32
822        }
823    }
824
825    fn f64_to_jsvalue(&self, n: f64) -> JsValue {
826        if n.is_nan() {
827            JsValue::Number(JsNumberType::NaN)
828        } else if n.is_infinite() {
829            if n > 0.0 {
830                JsValue::Number(JsNumberType::PositiveInfinity)
831            } else {
832                JsValue::Number(JsNumberType::NegativeInfinity)
833            }
834        } else if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 {
835            JsValue::Number(JsNumberType::Integer(n as i64))
836        } else {
837            JsValue::Number(JsNumberType::Float(n))
838        }
839    }
840
841    fn to_number_value(&self, val: JsValue) -> JsValue {
842        match val {
843            JsValue::Number(_) => val,
844            _ => self.f64_to_jsvalue(self.to_f64(&val)),
845        }
846    }
847
848    // ── Arithmetic helpers ───────────────────────────────────
849
850    fn js_add(&self, a: JsValue, b: JsValue) -> JsValue {
851        // String concatenation takes priority
852        match (&a, &b) {
853            (JsValue::String(sa), JsValue::String(sb)) => {
854                JsValue::String(format!("{}{}", sa, sb))
855            }
856            (JsValue::String(sa), _) => {
857                JsValue::String(format!("{}{}", sa, self.to_display_string(&b)))
858            }
859            (_, JsValue::String(sb)) => {
860                JsValue::String(format!("{}{}", self.to_display_string(&a), sb))
861            }
862            _ => {
863                if let Some(val) = self.fast_number_binop(&a, &b, |x, y| x + y) {
864                    return val;
865                }
866                self.f64_to_jsvalue(self.to_f64(&a) + self.to_f64(&b))
867            }
868        }
869    }
870
871    fn js_sub(&self, a: JsValue, b: JsValue) -> JsValue {
872        if let Some(val) = self.fast_number_binop(&a, &b, |x, y| x - y) {
873            return val;
874        }
875        self.f64_to_jsvalue(self.to_f64(&a) - self.to_f64(&b))
876    }
877
878    fn js_mul(&self, a: JsValue, b: JsValue) -> JsValue {
879        if let Some(val) = self.fast_number_binop(&a, &b, |x, y| x * y) {
880            return val;
881        }
882        self.f64_to_jsvalue(self.to_f64(&a) * self.to_f64(&b))
883    }
884
885    fn js_div(&self, a: JsValue, b: JsValue) -> JsValue {
886        if let Some(nb) = self.as_number(&b) {
887            let nb = self.num_to_f64(nb);
888            if nb == 0.0 {
889                let na = self.to_f64(&a);
890                if na == 0.0 || na.is_nan() {
891                    return JsValue::Number(JsNumberType::NaN);
892                } else if na > 0.0 {
893                    return JsValue::Number(JsNumberType::PositiveInfinity);
894                } else {
895                    return JsValue::Number(JsNumberType::NegativeInfinity);
896                }
897            }
898            if let Some(val) = self.fast_number_binop(&a, &b, |x, y| x / y) {
899                return val;
900            }
901        }
902
903        let nb = self.to_f64(&b);
904        if nb == 0.0 {
905            let na = self.to_f64(&a);
906            if na == 0.0 || na.is_nan() {
907                JsValue::Number(JsNumberType::NaN)
908            } else if na > 0.0 {
909                JsValue::Number(JsNumberType::PositiveInfinity)
910            } else {
911                JsValue::Number(JsNumberType::NegativeInfinity)
912            }
913        } else {
914            self.f64_to_jsvalue(self.to_f64(&a) / nb)
915        }
916    }
917
918    fn js_mod(&self, a: JsValue, b: JsValue) -> JsValue {
919        if let Some(val) = self.fast_number_binop(&a, &b, |x, y| x % y) {
920            return val;
921        }
922        let na = self.to_f64(&a);
923        let nb = self.to_f64(&b);
924        if nb == 0.0 {
925            JsValue::Number(JsNumberType::NaN)
926        } else {
927            self.f64_to_jsvalue(na % nb)
928        }
929    }
930
931    fn js_negate(&self, a: JsValue) -> JsValue {
932        if let JsValue::Number(n) = &a {
933            return self.f64_to_jsvalue(-self.num_to_f64(n));
934        }
935        self.f64_to_jsvalue(-self.to_f64(&a))
936    }
937
938    fn as_number<'b>(&self, v: &'b JsValue) -> Option<&'b JsNumberType> {
939        match v {
940            JsValue::Number(n) => Some(n),
941            _ => None,
942        }
943    }
944
945    fn fast_number_binop(
946        &self,
947        a: &JsValue,
948        b: &JsValue,
949        op: fn(f64, f64) -> f64,
950    ) -> Option<JsValue> {
951        match (self.as_number(a), self.as_number(b)) {
952            (Some(na), Some(nb)) => {
953                let fa = self.num_to_f64(na);
954                let fb = self.num_to_f64(nb);
955                Some(self.f64_to_jsvalue(op(fa, fb)))
956            }
957            _ => None,
958        }
959    }
960
961    // ── Comparison helpers ───────────────────────────────────
962
963    fn js_strict_equal(&self, a: &JsValue, b: &JsValue) -> bool {
964        match (a, b) {
965            (JsValue::Undefined, JsValue::Undefined) => true,
966            (JsValue::Null, JsValue::Null) => true,
967            (JsValue::Boolean(a), JsValue::Boolean(b)) => a == b,
968            (JsValue::Number(a), JsValue::Number(b)) => {
969                let fa = self.num_to_f64(a);
970                let fb = self.num_to_f64(b);
971                if fa.is_nan() || fb.is_nan() {
972                    false
973                } else {
974                    fa == fb
975                }
976            }
977            (JsValue::String(a), JsValue::String(b)) => a == b,
978            _ => false,
979        }
980    }
981
982    fn js_abstract_equal(&self, a: &JsValue, b: &JsValue) -> bool {
983        // Simplified abstract equality
984        match (a, b) {
985            (JsValue::Undefined, JsValue::Null) | (JsValue::Null, JsValue::Undefined) => true,
986            _ => {
987                // Fall back to strict equality for same types
988                if std::mem::discriminant(a) == std::mem::discriminant(b) {
989                    self.js_strict_equal(a, b)
990                } else {
991                    // Numeric comparison
992                    let na = self.to_f64(a);
993                    let nb = self.to_f64(b);
994                    if na.is_nan() || nb.is_nan() {
995                        false
996                    } else {
997                        na == nb
998                    }
999                }
1000            }
1001        }
1002    }
1003
1004    fn js_less_than(&self, a: &JsValue, b: &JsValue) -> bool {
1005        match (a, b) {
1006            (JsValue::String(sa), JsValue::String(sb)) => sa < sb,
1007            _ => {
1008                let na = self.to_f64(a);
1009                let nb = self.to_f64(b);
1010                if na.is_nan() || nb.is_nan() {
1011                    false
1012                } else {
1013                    na < nb
1014                }
1015            }
1016        }
1017    }
1018
1019    fn num_to_f64(&self, n: &JsNumberType) -> f64 {
1020        match n {
1021            JsNumberType::Integer(i) => *i as f64,
1022            JsNumberType::Float(f) => *f,
1023            JsNumberType::NaN => f64::NAN,
1024            JsNumberType::PositiveInfinity => f64::INFINITY,
1025            JsNumberType::NegativeInfinity => f64::NEG_INFINITY,
1026        }
1027    }
1028
1029    fn to_display_string(&self, val: &JsValue) -> String {
1030        match val {
1031            JsValue::Undefined => "undefined".to_string(),
1032            JsValue::Null => "null".to_string(),
1033            JsValue::Boolean(b) => b.to_string(),
1034            JsValue::Number(n) => match n {
1035                JsNumberType::Integer(i) => i.to_string(),
1036                JsNumberType::Float(f) => f.to_string(),
1037                JsNumberType::NaN => "NaN".to_string(),
1038                JsNumberType::PositiveInfinity => "Infinity".to_string(),
1039                JsNumberType::NegativeInfinity => "-Infinity".to_string(),
1040            },
1041            JsValue::String(s) => s.clone(),
1042            _ => "[object Object]".to_string(),
1043        }
1044    }
1045}