Skip to main content

bamts_runtime/
native.rs

1//! The native semantic engine.
2//!
3//! [`NativeEngine`] is the runtime side of the native-execution ABI. It
4//! implements [`bamts_native::NativeOps`] — the panic- and nesting-safe seam the
5//! generated `bamts_*` helpers dispatch into — by reusing the *exact* heap,
6//! global, host, and value-semantic methods of the interpreter [`Machine`]. It
7//! adds no second copy of any JavaScript semantic: `dispatch` funnels every one
8//! of the 30 helpers (which cover all 36 opcodes) into the shared [`Machine`]
9//! methods, so the interpreter and the native engine agree by construction.
10//!
11//! What the engine owns beyond the shared semantics is exactly the layer the
12//! interpreter's dispatch loop cannot be reused for: its own **activation
13//! records** (`this`/`arguments`/`new.target`, resume state), **register
14//! lifetime** (each frame's register file is a driver-stack local, never
15//! engine state, so a validated [`NativeFrame`] view borrows it disjointly),
16//! **completions/handlers**, **suspend/resume**, and **stdout/exit**.
17//!
18//! # `&self` dispatch and interior mutability
19//!
20//! [`NativeOps::dispatch`] takes `&self`: a re-entrant helper (a nested `Call`
21//! or accessor invocation) dispatches back into the *same* engine on the same
22//! thread while an outer `dispatch` is still on the stack. A `&mut self`
23//! receiver would make those nested reborrows aliasing UB, so all mutable
24//! engine state lives behind [`RefCell`]/[`Cell`]. The invariant that keeps this
25//! sound and panic-free: **no borrow guard is ever held across a nested call**
26//! ([`NativeEntryTable::invoke`], [`NativeEngine::execute`], or
27//! [`NativeEngine::invoke_callee`]). Every `machine`/`activations` borrow is
28//! taken, used, and dropped before the nested call begins.
29//!
30//! # Two backends, one semantic core
31//!
32//! * **Reference** ([`NativeEngine::run`]) — the compiler-free driver. It walks
33//!   verified bytecode, and for every value/heap/host operation constructs the
34//!   [`HelperCall`] the code generator would emit and routes it through
35//!   [`NativeOps::dispatch`]. Control flow (branches, calls, handler search,
36//!   suspend/resume) is the driver's, standing in for the CLIF the AOT/JIT
37//!   backend generates. It never invokes [`Machine::run`] or its loop.
38//! * **Linked** ([`run_linked_program`]) — the real AOT/JIT path. It installs
39//!   the engine as the thread's [`NativeOps`] with
40//!   [`bamts_native::with_native_ops`] and invokes compiled entries through a
41//!   borrowed [`bamts_native::NativeEntryTable`]; the compiled code calls the
42//!   `bamts_*` helpers, which dispatch back into the same engine.
43//!
44//! Both backends share the identical `dispatch`/`truthy` implementation.
45//!
46//! No `unsafe` lives here: [`bamts_native::ShadowFrame::new`] and
47//! [`bamts_native::NativeFrame::new`] are safe constructors, and all raw-pointer
48//! dereferencing stays inside `bamts-native`.
49
50use std::borrow::Cow;
51use std::cell::{Cell, RefCell};
52use std::error::Error;
53use std::fmt;
54use std::sync::Arc;
55
56use bamts_bytecode::{
57    ConstantId, EcmaString, FunctionId, Instruction, Module, ModuleId, Program, Verified,
58};
59pub use bamts_native::AbiError;
60use bamts_native::{
61    Completion, CompletionTag, HelperCall, HelperResult, NativeEntryTable, NativeFrame, NativeOps,
62    ShadowFrame, Value, with_native_ops,
63};
64
65use crate::intrinsics::BuiltinOutcome;
66use crate::{
67    CalleeKind, EvalFailure, Execution, ExecutionOutcome, GeneratorResume, GeneratorStart,
68    GeneratorState, GetOutcome, HeapEntry, Host, IteratorNextPrepared, Limits, Machine,
69    PropertyMap, RuntimeError, RuntimeErrorKind, SetOutcome, SuspendedActivation, ThrowOrigin,
70    accessor_from_selector, binary_from_selector, iterator_kind_from_selector, unary_from_selector,
71};
72
73// -- ABI selector encoders (inverse of the shared `*_from_selector` decoders) --
74//
75// The reference driver holds a `bamts_bytecode` operator enum and must produce
76// the raw `u32` selector the code generator would pass, so the value round-trips
77// through the same `HelperCall`/`dispatch` seam generated code uses. The mapping
78// is byte-identical to `bamts_codegen`'s `*_op_selector` functions.
79
80fn unary_to_selector(op: bamts_bytecode::UnaryOp) -> u32 {
81    use bamts_bytecode::UnaryOp;
82    match op {
83        UnaryOp::Void => 0,
84        UnaryOp::TypeOf => 1,
85        UnaryOp::Plus => 2,
86        UnaryOp::Negate => 3,
87        UnaryOp::BitwiseNot => 4,
88        UnaryOp::LogicalNot => 5,
89    }
90}
91
92fn binary_to_selector(op: bamts_bytecode::BinaryOp) -> u32 {
93    use bamts_bytecode::BinaryOp;
94    match op {
95        BinaryOp::Add => 0,
96        BinaryOp::Subtract => 1,
97        BinaryOp::Multiply => 2,
98        BinaryOp::Divide => 3,
99        BinaryOp::Remainder => 4,
100        BinaryOp::Exponent => 5,
101        BinaryOp::BitAnd => 6,
102        BinaryOp::BitOr => 7,
103        BinaryOp::BitXor => 8,
104        BinaryOp::ShiftLeft => 9,
105        BinaryOp::ShiftRight => 10,
106        BinaryOp::UnsignedShiftRight => 11,
107        BinaryOp::Equal => 12,
108        BinaryOp::NotEqual => 13,
109        BinaryOp::StrictEqual => 14,
110        BinaryOp::StrictNotEqual => 15,
111        BinaryOp::LessThan => 16,
112        BinaryOp::LessThanOrEqual => 17,
113        BinaryOp::GreaterThan => 18,
114        BinaryOp::GreaterThanOrEqual => 19,
115        BinaryOp::InstanceOf => 20,
116        BinaryOp::In => 21,
117    }
118}
119
120fn iterator_kind_to_selector(kind: bamts_bytecode::IteratorKind) -> u32 {
121    use bamts_bytecode::IteratorKind;
122    match kind {
123        IteratorKind::Sync => 0,
124        IteratorKind::Async => 1,
125        IteratorKind::Keys => 2,
126    }
127}
128
129fn accessor_to_selector(kind: bamts_bytecode::AccessorKind) -> u32 {
130    use bamts_bytecode::AccessorKind;
131    match kind {
132        AccessorKind::Getter => 0,
133        AccessorKind::Setter => 1,
134    }
135}
136
137// -- Errors ------------------------------------------------------------------
138
139/// A native-execution failure: a runtime error, an AOT/entry linkage error, or
140/// an unrecoverable native trap whose completion value carries a trap id.
141#[derive(Clone, Debug, Eq, PartialEq)]
142pub enum NativeError {
143    /// A deterministic runtime error (throw, limit, or malformed value).
144    Runtime(RuntimeError),
145    /// An AOT image or entry-table linkage failure.
146    Abi(AbiError),
147    /// The entry table was not compiled from the supplied program's canonical bytes.
148    ProgramMismatch,
149    /// A native entry returned `FatalTrap`; `value` is the raw trap id word.
150    FatalTrap { value: Value },
151}
152
153impl fmt::Display for NativeError {
154    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
155        match self {
156            NativeError::Runtime(error) => write!(formatter, "{error}"),
157            NativeError::Abi(error) => write!(formatter, "{error}"),
158            NativeError::ProgramMismatch => {
159                write!(
160                    formatter,
161                    "native entries were compiled from different program bytes"
162                )
163            }
164            NativeError::FatalTrap { value } => {
165                write!(
166                    formatter,
167                    "native fatal trap (value {:#018x})",
168                    value.to_bits()
169                )
170            }
171        }
172    }
173}
174
175impl Error for NativeError {}
176
177impl From<RuntimeError> for NativeError {
178    fn from(error: RuntimeError) -> Self {
179        NativeError::Runtime(error)
180    }
181}
182
183impl From<AbiError> for NativeError {
184    fn from(error: AbiError) -> Self {
185        NativeError::Abi(error)
186    }
187}
188
189// -- Engine state ------------------------------------------------------------
190
191/// Which entry-invocation seam a nested runtime call re-enters.
192#[derive(Clone, Copy, Debug, Eq, PartialEq)]
193enum Backend {
194    /// The compiler-free driver; nested calls recurse into [`NativeEngine::execute`].
195    Reference,
196    /// Compiled entries; nested calls go through the [`NativeEntryTable`].
197    Linked,
198}
199
200/// A resolved bytecode owner that outlives a temporary machine borrow.
201enum CodeRef<'m> {
202    Root(&'m Program<Verified>),
203    Dynamic(Arc<Program<Verified>>),
204}
205
206impl CodeRef<'_> {
207    fn code(&self, module: ModuleId) -> &Module<Verified> {
208        match self {
209            Self::Root(program) => {
210                &program
211                    .module(module)
212                    .expect("verified native module id remains in bounds")
213                    .code
214            }
215            Self::Dynamic(program) => &program.modules()[0].code,
216        }
217    }
218}
219
220/// A native activation record. It holds only per-call *metadata*; the register
221/// file lives as a driver-stack-local `Vec<Value>` so a [`NativeFrame`] can
222/// borrow it disjointly from the engine.
223struct Activation {
224    this_value: Value,
225    new_target: Value,
226    args: Vec<Value>,
227    arguments_object: Option<Value>,
228    /// The resumed value delivered to a pending `ResumeValue` (linked backend).
229    pending_resume: Option<Value>,
230}
231
232/// A thrown value together with its origin, threaded out of `dispatch` through
233/// the engine (the native completion ABI carries only a value).
234#[derive(Clone, Copy)]
235struct PendingThrow {
236    value: Value,
237    origin: ThrowOrigin,
238}
239
240/// How a single driven instruction resolves.
241enum Flow {
242    /// Advance to the next instruction.
243    Next,
244    /// Jump to a program counter.
245    Goto(usize),
246    /// No covering handler; unwind this frame with the thrown value.
247    Unwind(Value, ThrowOrigin),
248}
249
250/// The observable result of driving one activation to termination.
251enum FrameCompletion {
252    Normal(Value),
253    Unwind(Value, ThrowOrigin, usize),
254    Suspend(Value, u32),
255}
256
257#[derive(Clone, Copy)]
258enum FrameDrive {
259    Ordinary,
260    GeneratorStart,
261    GeneratorResume { token: u32, sent: Value },
262}
263
264/// The result of invoking a callee (runtime function, host entity, or foreign
265/// value).
266enum InvokeOutcome {
267    Value(Value),
268    Threw(Value, ThrowOrigin),
269    /// An unrecoverable error; the engine's pending trap state is set.
270    Fatal,
271}
272
273enum ImportFailure {
274    Threw(RuntimeError),
275    Fatal,
276}
277
278/// The native semantic engine over a verified program.
279///
280/// `'m` bounds the module and entry table; `'h` bounds the host borrow. The
281/// embedded [`Machine`] owns the shared heap/globals/host/limits and every
282/// value-semantic method; the engine never calls [`Machine::run`]. All mutable
283/// state is behind [`RefCell`]/[`Cell`] because [`NativeOps`] dispatches on
284/// `&self` (see the module docs on re-entrancy).
285pub struct NativeEngine<'m, 'h, H: Host> {
286    /// The shared semantic core: heap, globals, host, limits, and the one live
287    /// module registry used by interpreter and native execution alike.
288    machine: RefCell<Machine<'h, H>>,
289    /// The verified program whose module-local bytecode drives reference calls.
290    program: &'m Program<Verified>,
291    /// The compiled entry table, used by the linked backend and nested calls.
292    entries: &'m dyn NativeEntryTable,
293    backend: Backend,
294    /// Metadata for each live activation; register files are driver-stack locals.
295    activations: RefCell<Vec<Activation>>,
296    /// Buffered process stdout.
297    stdout: RefCell<Vec<u8>>,
298    /// The process exit code (`0` for normal termination).
299    exit_code: Cell<i32>,
300    /// A throw's value+origin, set by `dispatch`, consumed by the driver.
301    pending_throw: Cell<Option<PendingThrow>>,
302    /// A shallow fatal kind, set by `dispatch`; the driver attaches source.
303    pending_fatal_kind: Cell<Option<RuntimeErrorKind>>,
304    /// A fully-sourced error from a nested activation, propagated verbatim.
305    pending_error: Cell<Option<RuntimeError>>,
306    /// A nested entry-table linkage failure, propagated through `FatalTrap`.
307    pending_abi_error: Cell<Option<AbiError>>,
308}
309
310impl<'m, 'h, H: Host> NativeEngine<'m, 'h, H> {
311    fn build(
312        program: &'m Program<Verified>,
313        entries: &'m dyn NativeEntryTable,
314        host: &'h mut H,
315        limits: Limits,
316        backend: Backend,
317    ) -> Self
318    where
319        'm: 'h,
320    {
321        let mut machine = Machine::new(program, host, limits);
322        machine.frames.clear();
323        machine.live_registers = 0;
324        NativeEngine {
325            machine: RefCell::new(machine),
326            program,
327            entries,
328            backend,
329            activations: RefCell::new(Vec::new()),
330            stdout: RefCell::new(Vec::new()),
331            exit_code: Cell::new(0),
332            pending_throw: Cell::new(None),
333            pending_fatal_kind: Cell::new(None),
334            pending_error: Cell::new(None),
335            pending_abi_error: Cell::new(None),
336        }
337    }
338
339    /// A native engine that drives control flow itself (the compiler-free
340    /// reference backend). `entries` is used only for nested calls under the
341    /// linked backend; the reference backend recurses internally.
342    #[must_use]
343    pub fn new(
344        program: &'m Program<Verified>,
345        entries: &'m dyn NativeEntryTable,
346        host: &'h mut H,
347        limits: Limits,
348    ) -> Self
349    where
350        'm: 'h,
351    {
352        Self::build(program, entries, host, limits, Backend::Reference)
353    }
354
355    /// Buffered process stdout produced during execution.
356    #[must_use]
357    pub fn stdout(&self) -> Vec<u8> {
358        self.stdout.borrow().clone()
359    }
360
361    /// The process exit code.
362    #[must_use]
363    pub fn exit_code(&self) -> i32 {
364        self.exit_code.get()
365    }
366
367    fn max_call_depth(&self) -> usize {
368        self.machine.borrow().limits.max_call_depth
369    }
370
371    fn max_total_registers(&self) -> usize {
372        self.machine.borrow().limits.max_total_registers
373    }
374
375    fn error_at(
376        &self,
377        module: ModuleId,
378        kind: RuntimeErrorKind,
379        function: usize,
380        pc: usize,
381    ) -> RuntimeError {
382        self.machine
383            .borrow()
384            .error_at_in_module(kind, module, function, pc)
385    }
386
387    fn module(&self, module: ModuleId) -> &Module<Verified> {
388        debug_assert!((module.get() as usize) < self.machine.borrow().dynamic_base);
389        &self
390            .program
391            .module(module)
392            .expect("verified native module id remains in bounds")
393            .code
394    }
395
396    fn code_ref(&self, module: ModuleId) -> CodeRef<'m> {
397        let dynamic = {
398            let machine = self.machine.borrow();
399            let index = module.get() as usize;
400            (index >= machine.dynamic_base).then(|| {
401                machine.dynamic[index - machine.dynamic_base]
402                    .program
403                    .clone()
404            })
405        };
406        dynamic.map_or(CodeRef::Root(self.program), CodeRef::Dynamic)
407    }
408
409    fn is_dynamic_module(&self, module: ModuleId) -> bool {
410        module.get() as usize >= self.machine.borrow().dynamic_base
411    }
412
413    // -- Reference backend: control-flow driver ------------------------------
414
415    /// Executes the program entry with the reference driver, then drives the
416    /// shared automatic event loop to quiescence. Never invokes [`Machine::run`];
417    /// it reuses the machine's own loop driver directly.
418    pub fn run(self) -> Result<Execution, RuntimeError> {
419        self.machine.borrow_mut().instantiate_modules()?;
420        let execution = self
421            .evaluate_reference_module(self.program.entry())?
422            .ok_or_else(|| {
423                let module = self.program.entry();
424                let function = self.module(module).entry().get() as usize;
425                self.error_at(
426                    module,
427                    RuntimeErrorKind::InvalidVerifiedProgram {
428                        module,
429                        instruction: Instruction::Halt,
430                    },
431                    function,
432                    0,
433                )
434            })?;
435        // After successful synchronous evaluation, drain microtasks and timers
436        // to quiescence on the single borrowed machine. The guard spans only this
437        // statement: the loop runs through the machine's own &mut methods, so no
438        // nested RefCell borrow is taken and no native callback overlaps it.
439        self.machine.borrow_mut().run_to_quiescence()?;
440        Ok(execution)
441    }
442
443    fn evaluate_reference_module(
444        &self,
445        module: ModuleId,
446    ) -> Result<Option<Execution>, RuntimeError> {
447        let dependencies = match self.machine.borrow_mut().begin_module_evaluation(module)? {
448            crate::ModuleEvaluation::Cycle => return Ok(None),
449            crate::ModuleEvaluation::Evaluated(result) => return result.map(|()| None),
450            crate::ModuleEvaluation::Ready(dependencies) => dependencies,
451        };
452        for dependency in dependencies {
453            if let Err(error) = self.evaluate_reference_module(dependency) {
454                return self
455                    .machine
456                    .borrow_mut()
457                    .finish_module_evaluation(module, Err(error))
458                    .map(Some);
459            }
460        }
461
462        let code = self.module(module);
463        let function = code.entry().get() as usize;
464        let register_count = code.functions()[function].register_count() as usize;
465        let result = if self.max_call_depth() < 1 {
466            Err(self.error_at(
467                module,
468                RuntimeErrorKind::CallDepthExceeded {
469                    limit: self.max_call_depth(),
470                },
471                function,
472                0,
473            ))
474        } else if register_count > self.max_total_registers() {
475            Err(self.error_at(
476                module,
477                RuntimeErrorKind::RegisterLimitExceeded {
478                    limit: self.max_total_registers(),
479                },
480                function,
481                0,
482            ))
483        } else {
484            self.execute(
485                module,
486                function,
487                Value::UNDEFINED,
488                Value::UNDEFINED,
489                Vec::new(),
490                &[],
491            )
492            .and_then(|(completion, registers)| match completion {
493                FrameCompletion::Normal(value) => Ok(Execution {
494                    outcome: ExecutionOutcome {
495                        stdout: self.stdout.borrow().clone(),
496                        exit_code: self.exit_code.get(),
497                    },
498                    value,
499                    link: value,
500                    entry_registers: registers,
501                }),
502                FrameCompletion::Unwind(value, origin, pc) => Err(self.error_at(
503                    module,
504                    RuntimeErrorKind::UncaughtThrow { value, origin },
505                    function,
506                    pc,
507                )),
508                FrameCompletion::Suspend(value, _) => Err(self.error_at(
509                    module,
510                    RuntimeErrorKind::InvalidValue { value },
511                    function,
512                    0,
513                )),
514            })
515        };
516        self.machine
517            .borrow_mut()
518            .finish_module_evaluation(module, result)
519            .map(Some)
520    }
521
522    /// Seeds a fresh register file: leading captures, then parameters, the rest
523    /// uninitialized — identical to the interpreter's frame prologue.
524    fn seed_registers(
525        &self,
526        code: &Module<Verified>,
527        function: usize,
528        captures: &[Value],
529        args: &[Value],
530    ) -> Vec<Value> {
531        let metadata = &code.functions()[function];
532        let register_count = metadata.register_count() as usize;
533        let capture_count = metadata.capture_count() as usize;
534        let parameter_count = metadata.parameter_count() as usize;
535        let mut registers = vec![Value::UNINITIALIZED; register_count];
536        for index in 0..capture_count {
537            if let Some(slot) = registers.get_mut(index) {
538                *slot = captures.get(index).copied().unwrap_or(Value::UNDEFINED);
539            }
540        }
541        for index in 0..parameter_count {
542            if let Some(slot) = registers.get_mut(capture_count + index) {
543                *slot = args.get(index).copied().unwrap_or(Value::UNDEFINED);
544            }
545        }
546        registers
547    }
548
549    /// Drives one activation to termination. Pushes the activation metadata,
550    /// runs the instruction loop over a driver-local register file, and returns
551    /// both the completion and the final register file (the caller ignores the
552    /// registers except for the entry frame).
553    fn execute(
554        &self,
555        module: ModuleId,
556        function: usize,
557        this_value: Value,
558        new_target: Value,
559        args: Vec<Value>,
560        captures: &[Value],
561    ) -> Result<(FrameCompletion, Vec<Value>), RuntimeError> {
562        let handle = self.code_ref(module);
563        let code = handle.code(module);
564        let register_count = code.functions()[function].register_count() as usize;
565        let mut registers = self.seed_registers(code, function, captures, &args);
566        let reserved = self
567            .machine
568            .borrow_mut()
569            .reserve_native_activation(register_count);
570        reserved.map_err(|kind| self.error_at(module, kind, function, 0))?;
571        self.activations.borrow_mut().push(Activation {
572            this_value,
573            new_target,
574            args,
575            arguments_object: None,
576            pending_resume: None,
577        });
578        let completion =
579            self.run_frame(module, function, code, &mut registers, FrameDrive::Ordinary);
580        self.activations.borrow_mut().pop();
581        self.machine
582            .borrow_mut()
583            .release_native_activation(register_count);
584        completion.map(|completion| (completion, registers))
585    }
586
587    /// The instruction loop for one activation. `registers` is the caller-owned
588    /// register file; a [`NativeFrame`] borrows it disjointly from `self`.
589    fn run_frame(
590        &self,
591        module: ModuleId,
592        function: usize,
593        code: &Module<Verified>,
594        registers: &mut Vec<Value>,
595        drive: FrameDrive,
596    ) -> Result<FrameCompletion, RuntimeError> {
597        let length = u16::try_from(registers.len()).map_err(|_| {
598            let limit = self.max_total_registers();
599            self.error_at(
600                module,
601                RuntimeErrorKind::RegisterLimitExceeded { limit },
602                function,
603                0,
604            )
605        })?;
606        let handles = registers.as_mut_ptr();
607        let mut shadow = ShadowFrame::new(std::ptr::null_mut(), 0, module.get(), handles, length);
608        let mut frame =
609            NativeFrame::new(&mut shadow, registers.as_mut_slice()).ok_or_else(|| {
610                self.error_at(
611                    module,
612                    RuntimeErrorKind::InvalidValue {
613                        value: Value::UNDEFINED,
614                    },
615                    function,
616                    0,
617                )
618            })?;
619
620        let target = crate::RuntimeFunction {
621            module,
622            function: FunctionId::new(function as u32),
623        };
624        let mut pc = match drive {
625            FrameDrive::Ordinary | FrameDrive::GeneratorStart => 0,
626            FrameDrive::GeneratorResume { token, sent } => {
627                let suspend_pc = token.checked_sub(1).map(|pc| pc as usize).ok_or_else(|| {
628                    self.error_at(
629                        module,
630                        RuntimeErrorKind::InvalidValue {
631                            value: Value::UNDEFINED,
632                        },
633                        function,
634                        0,
635                    )
636                })?;
637                let Instruction::Suspend { dst, resume, .. } =
638                    code.functions()[function].code()[suspend_pc]
639                else {
640                    return Err(self.error_at(
641                        module,
642                        RuntimeErrorKind::InvalidValue {
643                            value: Value::UNDEFINED,
644                        },
645                        function,
646                        suspend_pc,
647                    ));
648                };
649                frame.set_register(dst.get(), sent);
650                resume.get() as usize
651            }
652        };
653        loop {
654            let instruction = code.functions()[function].code()[pc];
655            if is_inline_instruction(instruction) {
656                let consumed = self.machine.borrow_mut().consume_fuel(1);
657                if let Err(kind) = consumed {
658                    return Err(self.error_at(module, kind, function, pc));
659                }
660            }
661
662            // Control-flow opcodes are the driver's own responsibility (the CLIF
663            // the AOT/JIT backend generates); every value/heap/host opcode is
664            // routed through `dispatch` into the shared semantics.
665            match instruction {
666                Instruction::Move { dst, src } => {
667                    let value = frame.register(src.get());
668                    frame.set_register(dst.get(), value);
669                    pc += 1;
670                }
671                Instruction::Jump { target } => pc = target.get() as usize,
672                Instruction::JumpIfTrue { condition, target } => {
673                    let value = frame.register(condition.get());
674                    pc = if self.truthy(&mut frame, value) {
675                        target.get() as usize
676                    } else {
677                        pc + 1
678                    };
679                }
680                Instruction::JumpIfFalse { condition, target } => {
681                    let value = frame.register(condition.get());
682                    pc = if self.truthy(&mut frame, value) {
683                        pc + 1
684                    } else {
685                        target.get() as usize
686                    };
687                }
688                Instruction::Return { value } => {
689                    return Ok(FrameCompletion::Normal(frame.register(value.get())));
690                }
691                Instruction::Halt => return Ok(FrameCompletion::Normal(Value::UNDEFINED)),
692                Instruction::Throw { value } => {
693                    let thrown = frame.register(value.get());
694                    match self.raise(
695                        &mut frame,
696                        code,
697                        function,
698                        pc,
699                        thrown,
700                        ThrowOrigin::Bytecode,
701                    ) {
702                        Flow::Next => pc += 1,
703                        Flow::Goto(target) => pc = target,
704                        Flow::Unwind(value, origin) => {
705                            return Ok(FrameCompletion::Unwind(value, origin, pc));
706                        }
707                    }
708                }
709                Instruction::Suspend { src, .. }
710                    if matches!(
711                        drive,
712                        FrameDrive::GeneratorStart | FrameDrive::GeneratorResume { .. }
713                    ) =>
714                {
715                    let token = u32::try_from(pc + 1).map_err(|_| {
716                        self.error_at(
717                            module,
718                            RuntimeErrorKind::InvalidValue {
719                                value: Value::UNDEFINED,
720                            },
721                            function,
722                            pc,
723                        )
724                    })?;
725                    return Ok(FrameCompletion::Suspend(frame.register(src.get()), token));
726                }
727                Instruction::Suspend { .. } => {
728                    match self.raise(
729                        &mut frame,
730                        code,
731                        function,
732                        pc,
733                        Value::UNDEFINED,
734                        ThrowOrigin::TypeError {
735                            operation: "suspend outside an engine-owned event loop",
736                        },
737                    ) {
738                        Flow::Next => pc += 1,
739                        Flow::Goto(target) => pc = target,
740                        Flow::Unwind(value, origin) => {
741                            return Ok(FrameCompletion::Unwind(value, origin, pc));
742                        }
743                    }
744                }
745                other => {
746                    let (call, dst) = self.lower(other, &frame);
747                    let result = self.dispatch(&mut frame, call);
748                    match self.apply(&mut frame, target, code, pc, dst, result)? {
749                        Flow::Next => pc += 1,
750                        Flow::Goto(target) => pc = target,
751                        Flow::Unwind(value, origin) => {
752                            return Ok(FrameCompletion::Unwind(value, origin, pc));
753                        }
754                    }
755                }
756            }
757        }
758    }
759
760    /// Lowers a value/heap/host opcode into the [`HelperCall`] the code
761    /// generator would emit plus the destination register (if any). Reading
762    /// operand registers here mirrors codegen's register loads.
763    fn lower(
764        &self,
765        instruction: Instruction,
766        frame: &NativeFrame<'_>,
767    ) -> (HelperCall, Option<u32>) {
768        let register = |r: bamts_bytecode::Register| frame.register(r.get());
769        match instruction {
770            Instruction::LoadConst { dst, constant } => (
771                HelperCall::LoadConstant {
772                    const_id: constant.get(),
773                },
774                Some(dst.get()),
775            ),
776            Instruction::Unary { dst, op, operand } => (
777                HelperCall::Unary {
778                    op: unary_to_selector(op),
779                    operand: register(operand),
780                },
781                Some(dst.get()),
782            ),
783            Instruction::Binary {
784                dst,
785                op,
786                left,
787                right,
788            } => (
789                HelperCall::Binary {
790                    op: binary_to_selector(op),
791                    left: register(left),
792                    right: register(right),
793                },
794                Some(dst.get()),
795            ),
796            Instruction::CreateObject { dst } => (HelperCall::CreateObject, Some(dst.get())),
797            Instruction::CreateArray { dst } => (HelperCall::CreateArray, Some(dst.get())),
798            Instruction::CreateCell { dst } => (HelperCall::CreateCell, Some(dst.get())),
799            Instruction::CreateClosure {
800                dst,
801                function,
802                captures,
803            } => (
804                HelperCall::CreateClosure {
805                    function_id: function.get(),
806                    captures: register(captures),
807                },
808                Some(dst.get()),
809            ),
810            Instruction::GetProperty { dst, object, key } => (
811                HelperCall::GetProperty {
812                    object: register(object),
813                    key: register(key),
814                },
815                Some(dst.get()),
816            ),
817            Instruction::SetProperty { object, key, value } => (
818                HelperCall::SetProperty {
819                    object: register(object),
820                    key: register(key),
821                    value: register(value),
822                },
823                None,
824            ),
825            Instruction::DeleteProperty { dst, object, key } => (
826                HelperCall::DeleteProperty {
827                    object: register(object),
828                    key: register(key),
829                },
830                Some(dst.get()),
831            ),
832            Instruction::DefineAccessor {
833                object,
834                key,
835                accessor,
836                kind,
837            } => (
838                HelperCall::DefineAccessor {
839                    object: register(object),
840                    key: register(key),
841                    accessor: register(accessor),
842                    kind: accessor_to_selector(kind),
843                },
844                None,
845            ),
846            Instruction::Call {
847                dst,
848                callee,
849                this_value,
850                arguments,
851            } => (
852                HelperCall::Call {
853                    callee: register(callee),
854                    this_value: register(this_value),
855                    arguments: register(arguments),
856                },
857                Some(dst.get()),
858            ),
859            Instruction::Construct {
860                dst,
861                callee,
862                arguments,
863            } => (
864                HelperCall::Construct {
865                    callee: register(callee),
866                    arguments: register(arguments),
867                },
868                Some(dst.get()),
869            ),
870            Instruction::LoadGlobal { dst, name } => {
871                (HelperCall::LoadGlobal { name: name.get() }, Some(dst.get()))
872            }
873            Instruction::StoreGlobal { name, value } => (
874                HelperCall::StoreGlobal {
875                    name: name.get(),
876                    value: register(value),
877                },
878                None,
879            ),
880            Instruction::TypeOfGlobal { dst, name } => (
881                HelperCall::TypeOfGlobal { name: name.get() },
882                Some(dst.get()),
883            ),
884            Instruction::LoadThis { dst } => (HelperCall::LoadThis, Some(dst.get())),
885            Instruction::LoadArguments { dst } => (HelperCall::LoadArguments, Some(dst.get())),
886            Instruction::LoadNewTarget { dst } => (HelperCall::LoadNewTarget, Some(dst.get())),
887            Instruction::ArrayPush { array, value } => (
888                HelperCall::ArrayPush {
889                    array: register(array),
890                    value: register(value),
891                },
892                None,
893            ),
894            Instruction::ArrayExtend { array, iterable } => (
895                HelperCall::ArrayExtend {
896                    array: register(array),
897                    iterable: register(iterable),
898                },
899                None,
900            ),
901            Instruction::ObjectSpread { target, source } => (
902                HelperCall::ObjectSpread {
903                    target: register(target),
904                    source: register(source),
905                },
906                None,
907            ),
908            Instruction::SetPrototype { object, prototype } => (
909                HelperCall::SetPrototype {
910                    object: register(object),
911                    prototype: register(prototype),
912                },
913                None,
914            ),
915            Instruction::CreatePrivateName { dst, description } => (
916                HelperCall::CreatePrivateName {
917                    description: description.get(),
918                },
919                Some(dst.get()),
920            ),
921            Instruction::CreateRegExp {
922                dst,
923                pattern,
924                flags,
925            } => (
926                HelperCall::CreateRegExp {
927                    pattern: pattern.get(),
928                    flags: flags.get(),
929                },
930                Some(dst.get()),
931            ),
932            Instruction::GetIterator { dst, src, kind } => (
933                HelperCall::GetIterator {
934                    src: register(src),
935                    kind: iterator_kind_to_selector(kind),
936                },
937                Some(dst.get()),
938            ),
939            Instruction::IteratorNext {
940                done,
941                value,
942                iterator,
943            } => (
944                HelperCall::IteratorNext {
945                    iterator: register(iterator),
946                    done_reg: done.get(),
947                    value_reg: value.get(),
948                },
949                None,
950            ),
951            Instruction::Import { dst, specifier } => (
952                HelperCall::Import {
953                    specifier: specifier.get(),
954                },
955                Some(dst.get()),
956            ),
957            Instruction::Export { name, src } => (
958                HelperCall::Export {
959                    name: name.get(),
960                    src: register(src),
961                },
962                None,
963            ),
964            // Control-flow opcodes are handled by the driver and never lowered.
965            Instruction::Move { .. }
966            | Instruction::Jump { .. }
967            | Instruction::JumpIfTrue { .. }
968            | Instruction::JumpIfFalse { .. }
969            | Instruction::Return { .. }
970            | Instruction::Throw { .. }
971            | Instruction::Suspend { .. }
972            | Instruction::Halt => {
973                unreachable!("control-flow opcode is not lowered to a helper call")
974            }
975        }
976    }
977
978    /// Interprets a [`HelperResult`] against the frame: stores a normal result
979    /// into `dst`, searches handlers for a throw, or turns a trap into an error.
980    fn take_matching_throw(&self, value: Value) -> (Value, ThrowOrigin) {
981        match self.pending_throw.take() {
982            Some(pending) if pending.value == value => (pending.value, pending.origin),
983            Some(_) | None => (value, ThrowOrigin::Bytecode),
984        }
985    }
986
987    fn apply(
988        &self,
989        frame: &mut NativeFrame<'_>,
990        target: crate::RuntimeFunction,
991        code: &Module<Verified>,
992        pc: usize,
993        dst: Option<u32>,
994        result: HelperResult,
995    ) -> Result<Flow, RuntimeError> {
996        let function = target.function.get() as usize;
997        match result.tag {
998            CompletionTag::Normal => {
999                self.pending_throw.take();
1000                if let Some(register) = dst {
1001                    frame.set_register(register, result.value);
1002                }
1003                Ok(Flow::Next)
1004            }
1005            CompletionTag::Throw => {
1006                let (value, origin) = self.take_matching_throw(result.value);
1007                Ok(self.raise(frame, code, function, pc, value, origin))
1008            }
1009            CompletionTag::Suspend => {
1010                // The reference driver drives `Suspend` inline; a helper never
1011                // returns it. Treat as a malformed completion.
1012                Err(self.error_at(
1013                    target.module,
1014                    RuntimeErrorKind::InvalidValue {
1015                        value: result.value,
1016                    },
1017                    function,
1018                    pc,
1019                ))
1020            }
1021            CompletionTag::FatalTrap => {
1022                if let Some(error) = self.pending_error.take() {
1023                    return Err(error);
1024                }
1025                let kind = self.pending_fatal_kind.take().unwrap_or({
1026                    RuntimeErrorKind::InvalidValue {
1027                        value: result.value,
1028                    }
1029                });
1030                Err(self.error_at(target.module, kind, function, pc))
1031            }
1032        }
1033    }
1034
1035    /// Searches the current function's handlers covering `pc`. Binds the thrown
1036    /// value into the handler's catch register and jumps, or signals an unwind.
1037    fn raise(
1038        &self,
1039        frame: &mut NativeFrame<'_>,
1040        code: &Module<Verified>,
1041        function: usize,
1042        pc: usize,
1043        value: Value,
1044        origin: ThrowOrigin,
1045    ) -> Flow {
1046        match crate::innermost_handler(&code.functions()[function], pc) {
1047            Some(handler) => {
1048                frame.set_register(handler.catch_register.get(), value);
1049                Flow::Goto(handler.handler.get() as usize)
1050            }
1051            None => Flow::Unwind(value, origin),
1052        }
1053    }
1054
1055    // -- Callee invocation ---------------------------------------------------
1056
1057    /// Invokes a callee value: a runtime closure re-enters native execution; a
1058    /// host function/foreign value routes to the host. Shared with the
1059    /// interpreter's classification via [`Machine::callee_kind`].
1060    fn invoke_callee(
1061        &self,
1062        callee: Value,
1063        this: Value,
1064        args: &[Value],
1065        new_target: Value,
1066    ) -> InvokeOutcome {
1067        let mut callee = callee;
1068        let mut this = this;
1069        let mut args = Cow::Borrowed(args);
1070        let mut new_target = new_target;
1071        loop {
1072            let kind = self.machine.borrow().callee_kind(callee);
1073            match kind {
1074                Ok(CalleeKind::Runtime { target, captures }) => {
1075                    return self.invoke_runtime(target, &captures, this, new_target, args.as_ref());
1076                }
1077                Ok(CalleeKind::Builtin { id }) => {
1078                    let result = {
1079                        self.machine
1080                            .borrow_mut()
1081                            .call_builtin(id, this, args.as_ref(), false)
1082                    };
1083                    match result {
1084                        Ok(BuiltinOutcome::Value(value)) => {
1085                            return InvokeOutcome::Value(value);
1086                        }
1087                        Ok(BuiltinOutcome::Call {
1088                            callee: next,
1089                            this_value: next_this,
1090                            arguments: next_arguments,
1091                        }) => {
1092                            callee = next;
1093                            this = next_this;
1094                            args = Cow::Owned(next_arguments);
1095                            new_target = Value::UNDEFINED;
1096                        }
1097                        Ok(BuiltinOutcome::ConstructCall { .. }) => {
1098                            return InvokeOutcome::Threw(
1099                                Value::UNDEFINED,
1100                                ThrowOrigin::TypeError { operation: "call" },
1101                            );
1102                        }
1103                        Ok(BuiltinOutcome::GeneratorNext {
1104                            generator,
1105                            resume_value,
1106                        }) => {
1107                            return self.resume_generator(generator, resume_value);
1108                        }
1109                        Err(EvalFailure::Throw(origin)) => {
1110                            return InvokeOutcome::Threw(Value::UNDEFINED, origin);
1111                        }
1112                        Err(EvalFailure::ThrowValue(value)) => {
1113                            return InvokeOutcome::Threw(value, ThrowOrigin::Bytecode);
1114                        }
1115                        Err(EvalFailure::ThrowValueOrigin { value, origin }) => {
1116                            return InvokeOutcome::Threw(value, origin);
1117                        }
1118                        Err(EvalFailure::Runtime(kind)) => {
1119                            self.pending_fatal_kind.set(Some(kind));
1120                            return InvokeOutcome::Fatal;
1121                        }
1122                    }
1123                }
1124                Ok(CalleeKind::Bound) => {
1125                    let bound = self
1126                        .machine
1127                        .borrow()
1128                        .flatten_bound(callee, this, args.as_ref());
1129                    match bound {
1130                        Ok(bound) => {
1131                            callee = bound.target;
1132                            if new_target == Value::UNDEFINED {
1133                                this = bound.this_value;
1134                            }
1135                            args = Cow::Owned(bound.arguments);
1136                        }
1137                        Err(kind) => {
1138                            self.pending_fatal_kind.set(Some(kind));
1139                            return InvokeOutcome::Fatal;
1140                        }
1141                    }
1142                }
1143                Ok(CalleeKind::NotCallable) => {
1144                    return InvokeOutcome::Threw(
1145                        Value::UNDEFINED,
1146                        ThrowOrigin::TypeError { operation: "call" },
1147                    );
1148                }
1149                Err(kind) => {
1150                    self.pending_fatal_kind.set(Some(kind));
1151                    return InvokeOutcome::Fatal;
1152                }
1153            }
1154        }
1155    }
1156
1157    /// Re-enters a runtime function with the active recursion and register
1158    /// ceilings, dispatching to the reference driver or the linked entry table.
1159    fn invoke_runtime(
1160        &self,
1161        target: crate::RuntimeFunction,
1162        captures: &[Value],
1163        this: Value,
1164        new_target: Value,
1165        args: &[Value],
1166    ) -> InvokeOutcome {
1167        let index = target.function.get() as usize;
1168        let handle = self.code_ref(target.module);
1169        let flags = handle.code(target.module).functions()[index].flags();
1170        if flags.is_generator && !flags.is_async {
1171            let created = self.machine.borrow_mut().create_generator(GeneratorStart {
1172                target,
1173                captures: captures.to_vec(),
1174                this_value: this,
1175                new_target,
1176                args: args.to_vec(),
1177            });
1178            return match created {
1179                Ok(generator) => InvokeOutcome::Value(generator),
1180                Err(kind) => {
1181                    self.pending_fatal_kind.set(Some(kind));
1182                    InvokeOutcome::Fatal
1183                }
1184            };
1185        }
1186        if flags.is_async && !flags.is_generator {
1187            // Async calls route through the shared reference Machine state
1188            // machine, which drives the body on the interpreter to its first
1189            // await or completion and returns the implicit Promise. No
1190            // bytecode, codegen, or ABI change is involved.
1191            let outcome = self
1192                .machine
1193                .borrow_mut()
1194                .start_async_call(target, captures, this, new_target, args);
1195            return match outcome {
1196                Ok(promise) => InvokeOutcome::Value(promise),
1197                Err(failure) => self.failure_outcome(failure),
1198            };
1199        }
1200        if self.backend == Backend::Reference || self.is_dynamic_module(target.module) {
1201            return match self.execute(
1202                target.module,
1203                index,
1204                this,
1205                new_target,
1206                args.to_vec(),
1207                captures,
1208            ) {
1209                Ok((FrameCompletion::Normal(value), _)) => InvokeOutcome::Value(value),
1210                Ok((FrameCompletion::Unwind(value, origin, _), _)) => {
1211                    InvokeOutcome::Threw(value, origin)
1212                }
1213                Ok((FrameCompletion::Suspend(value, _), _)) => {
1214                    self.pending_fatal_kind
1215                        .set(Some(RuntimeErrorKind::InvalidValue { value }));
1216                    InvokeOutcome::Fatal
1217                }
1218                Err(error) => {
1219                    self.pending_error.set(Some(error));
1220                    InvokeOutcome::Fatal
1221                }
1222            };
1223        }
1224        self.invoke_linked(target, captures, this, new_target, args)
1225    }
1226
1227    fn resume_generator(&self, generator: Value, resume_value: Value) -> InvokeOutcome {
1228        let state = self.machine.borrow_mut().take_generator_state(generator);
1229        let resumed = match state {
1230            Ok(GeneratorState::Completed) => {
1231                return self.generator_result(Value::UNDEFINED, true);
1232            }
1233            Ok(GeneratorState::SuspendedStart(start)) => {
1234                if self.backend == Backend::Reference || self.is_dynamic_module(start.target.module)
1235                {
1236                    self.start_reference_generator(start)
1237                } else {
1238                    self.start_linked_generator(start)
1239                }
1240            }
1241            Ok(GeneratorState::Suspended(activation)) => {
1242                if self.backend == Backend::Reference
1243                    || self.is_dynamic_module(activation.target.module)
1244                {
1245                    self.resume_reference_generator(activation, resume_value)
1246                } else {
1247                    self.resume_linked_generator(activation, resume_value)
1248                }
1249            }
1250            Ok(GeneratorState::Executing) => {
1251                unreachable!("executing state is rejected by take_generator_state")
1252            }
1253            Err(failure) => return self.failure_outcome(failure),
1254        };
1255
1256        match resumed {
1257            Some(GeneratorResume::Yield { value, activation }) => {
1258                let result = self
1259                    .machine
1260                    .borrow_mut()
1261                    .settle_generator_yield(generator, value, activation);
1262                self.eval_outcome(result)
1263            }
1264            Some(GeneratorResume::Return(value)) => {
1265                let settled = self
1266                    .machine
1267                    .borrow_mut()
1268                    .settle_generator_completed(generator);
1269                if let Err(failure) = settled {
1270                    return self.failure_outcome(failure);
1271                }
1272                self.generator_result(value, true)
1273            }
1274            Some(GeneratorResume::Throw { value, origin, .. }) => {
1275                let settled = self
1276                    .machine
1277                    .borrow_mut()
1278                    .settle_generator_completed(generator);
1279                if let Err(failure) = settled {
1280                    return self.failure_outcome(failure);
1281                }
1282                InvokeOutcome::Threw(value, origin)
1283            }
1284            None => {
1285                if let Err(failure) = self
1286                    .machine
1287                    .borrow_mut()
1288                    .settle_generator_completed(generator)
1289                {
1290                    return self.failure_outcome(failure);
1291                }
1292                InvokeOutcome::Fatal
1293            }
1294        }
1295    }
1296
1297    fn generator_result(&self, value: Value, done: bool) -> InvokeOutcome {
1298        let result = self.machine.borrow_mut().iterator_result(value, done);
1299        self.eval_outcome(result)
1300    }
1301
1302    fn eval_outcome(&self, result: Result<Value, EvalFailure>) -> InvokeOutcome {
1303        match result {
1304            Ok(value) => InvokeOutcome::Value(value),
1305            Err(failure) => self.failure_outcome(failure),
1306        }
1307    }
1308
1309    fn failure_outcome(&self, failure: EvalFailure) -> InvokeOutcome {
1310        match failure {
1311            EvalFailure::Throw(origin) => InvokeOutcome::Threw(Value::UNDEFINED, origin),
1312            EvalFailure::ThrowValue(value) => InvokeOutcome::Threw(value, ThrowOrigin::Bytecode),
1313            EvalFailure::ThrowValueOrigin { value, origin } => InvokeOutcome::Threw(value, origin),
1314            EvalFailure::Runtime(kind) => {
1315                self.pending_fatal_kind.set(Some(kind));
1316                InvokeOutcome::Fatal
1317            }
1318        }
1319    }
1320
1321    fn get_outcome(
1322        &self,
1323        outcome: Result<GetOutcome, EvalFailure>,
1324        receiver: Value,
1325    ) -> InvokeOutcome {
1326        match outcome {
1327            Ok(GetOutcome::Value(value)) => InvokeOutcome::Value(value),
1328            Ok(GetOutcome::Text(text)) => {
1329                match self.machine.borrow_mut().allocate(HeapEntry::String(text)) {
1330                    Ok(value) => InvokeOutcome::Value(value),
1331                    Err(kind) => {
1332                        self.pending_fatal_kind.set(Some(kind));
1333                        InvokeOutcome::Fatal
1334                    }
1335                }
1336            }
1337            Ok(GetOutcome::Getter(getter)) => {
1338                self.invoke_callee(getter, receiver, &[], Value::UNDEFINED)
1339            }
1340            Err(failure) => self.failure_outcome(failure),
1341        }
1342    }
1343
1344    fn get_ascii(&self, object: Value, name: &str) -> InvokeOutcome {
1345        let outcome = self.machine.borrow_mut().resolve_get_ascii(object, name);
1346        self.get_outcome(outcome, object)
1347    }
1348
1349    fn get_iterator_active(
1350        &self,
1351        source: Value,
1352        kind: bamts_bytecode::IteratorKind,
1353    ) -> InvokeOutcome {
1354        if kind == bamts_bytecode::IteratorKind::Keys {
1355            let created = self.machine.borrow_mut().create_iterator(source, kind);
1356            return self.eval_outcome(created);
1357        }
1358        let method = {
1359            let mut machine = self.machine.borrow_mut();
1360            let symbol = machine.intrinsics.builtins.symbol_iterator();
1361            let key = match machine.to_property_key(symbol) {
1362                Ok(key) => key,
1363                Err(failure) => return self.failure_outcome(failure),
1364            };
1365            machine.resolve_get(source, &key)
1366        };
1367        let method = match self.get_outcome(method, source) {
1368            InvokeOutcome::Value(method) => method,
1369            other => return other,
1370        };
1371        match self.machine.borrow().is_callable(method) {
1372            Ok(true) => {}
1373            Ok(false) => {
1374                return InvokeOutcome::Threw(
1375                    Value::UNDEFINED,
1376                    ThrowOrigin::TypeError {
1377                        operation: "value is not iterable",
1378                    },
1379                );
1380            }
1381            Err(failure) => return self.failure_outcome(failure),
1382        }
1383        let iterator = match self.invoke_callee(method, source, &[], Value::UNDEFINED) {
1384            InvokeOutcome::Value(iterator) => iterator,
1385            other => return other,
1386        };
1387        if !self.machine.borrow().is_object(iterator) {
1388            return InvokeOutcome::Threw(
1389                Value::UNDEFINED,
1390                ThrowOrigin::TypeError {
1391                    operation: "iterator method returned a non-object",
1392                },
1393            );
1394        }
1395        let next = match self.get_ascii(iterator, "next") {
1396            InvokeOutcome::Value(next) => next,
1397            other => return other,
1398        };
1399        let created = self
1400            .machine
1401            .borrow_mut()
1402            .create_protocol_iterator(iterator, next);
1403        self.eval_outcome(created)
1404    }
1405
1406    fn iterator_next_active(&self, iterator: Value) -> Result<(bool, Value), InvokeOutcome> {
1407        let prepared = self.machine.borrow_mut().prepare_iterator_next(iterator);
1408        let result = match prepared {
1409            Ok(IteratorNextPrepared::Ready { done, value }) => return Ok((done, value)),
1410            Ok(IteratorNextPrepared::Call { callee, this_value }) => {
1411                match self.invoke_callee(callee, this_value, &[], Value::UNDEFINED) {
1412                    InvokeOutcome::Value(result) => result,
1413                    other => return Err(other),
1414                }
1415            }
1416            Err(failure) => return Err(self.failure_outcome(failure)),
1417        };
1418        if !self.machine.borrow().is_object(result) {
1419            return Err(InvokeOutcome::Threw(
1420                Value::UNDEFINED,
1421                ThrowOrigin::TypeError {
1422                    operation: "iterator next returned a non-object",
1423                },
1424            ));
1425        }
1426        let done = match self.get_ascii(result, "done") {
1427            InvokeOutcome::Value(done) => done,
1428            other => return Err(other),
1429        };
1430        if self.machine.borrow().truthy(done) {
1431            return Ok((true, Value::UNDEFINED));
1432        }
1433        let value = match self.get_ascii(result, "value") {
1434            InvokeOutcome::Value(value) => value,
1435            other => return Err(other),
1436        };
1437        Ok((false, value))
1438    }
1439
1440    fn array_extend_active(&self, array: Value, iterable: Value) -> InvokeOutcome {
1441        let iterator = match self.get_iterator_active(iterable, bamts_bytecode::IteratorKind::Sync)
1442        {
1443            InvokeOutcome::Value(iterator) => iterator,
1444            other => return other,
1445        };
1446        loop {
1447            let (done, value) = match self.iterator_next_active(iterator) {
1448                Ok(step) => step,
1449                Err(outcome) => return outcome,
1450            };
1451            if done {
1452                return InvokeOutcome::Value(Value::UNDEFINED);
1453            }
1454            if let Err(failure) = self.machine.borrow_mut().array_push(array, value) {
1455                return self.failure_outcome(failure);
1456            }
1457        }
1458    }
1459
1460    fn start_reference_generator(&self, start: GeneratorStart) -> Option<GeneratorResume> {
1461        let target = start.target;
1462        let index = target.function.get() as usize;
1463        let handle = self.code_ref(target.module);
1464        let code = handle.code(target.module);
1465        let register_count = code.functions()[index].register_count() as usize;
1466        let mut registers = self.seed_registers(code, index, &start.captures, &start.args);
1467        if let Err(kind) = self
1468            .machine
1469            .borrow_mut()
1470            .reserve_suspended_activation_registers(register_count)
1471        {
1472            self.pending_fatal_kind.set(Some(kind));
1473            return None;
1474        }
1475        if let Err(kind) = self.machine.borrow_mut().enter_native_generator() {
1476            self.machine
1477                .borrow_mut()
1478                .release_suspended_activation_registers(register_count);
1479            self.pending_fatal_kind.set(Some(kind));
1480            return None;
1481        }
1482        self.activations.borrow_mut().push(Activation {
1483            this_value: start.this_value,
1484            new_target: start.new_target,
1485            args: start.args.clone(),
1486            arguments_object: None,
1487            pending_resume: None,
1488        });
1489        let completion = self.run_frame(
1490            target.module,
1491            index,
1492            code,
1493            &mut registers,
1494            FrameDrive::GeneratorStart,
1495        );
1496        let activation = self
1497            .activations
1498            .borrow_mut()
1499            .pop()
1500            .expect("generator activation exists");
1501        self.machine.borrow_mut().leave_native_generator();
1502        self.finish_reference_generator(target, registers, activation, completion)
1503    }
1504
1505    fn resume_reference_generator(
1506        &self,
1507        mut suspended: SuspendedActivation,
1508        sent: Value,
1509    ) -> Option<GeneratorResume> {
1510        let target = suspended.target;
1511        let index = target.function.get() as usize;
1512        let handle = self.code_ref(target.module);
1513        let code = handle.code(target.module);
1514        let register_count = suspended.registers.len();
1515        if let Err(kind) = self.machine.borrow_mut().enter_native_generator() {
1516            self.machine
1517                .borrow_mut()
1518                .release_suspended_activation_registers(register_count);
1519            self.pending_fatal_kind.set(Some(kind));
1520            return None;
1521        }
1522        self.activations.borrow_mut().push(Activation {
1523            this_value: suspended.this_value,
1524            new_target: suspended.new_target,
1525            args: suspended.args.clone(),
1526            arguments_object: suspended.arguments_object,
1527            pending_resume: None,
1528        });
1529        let completion = self.run_frame(
1530            target.module,
1531            index,
1532            code,
1533            &mut suspended.registers,
1534            FrameDrive::GeneratorResume {
1535                token: suspended.resume_token,
1536                sent,
1537            },
1538        );
1539        let activation = self
1540            .activations
1541            .borrow_mut()
1542            .pop()
1543            .expect("generator activation exists");
1544        self.machine.borrow_mut().leave_native_generator();
1545        self.finish_reference_generator(target, suspended.registers, activation, completion)
1546    }
1547
1548    fn finish_reference_generator(
1549        &self,
1550        target: crate::RuntimeFunction,
1551        registers: Vec<Value>,
1552        activation: Activation,
1553        completion: Result<FrameCompletion, RuntimeError>,
1554    ) -> Option<GeneratorResume> {
1555        let register_count = registers.len();
1556        match completion {
1557            Ok(FrameCompletion::Suspend(value, resume_token)) => Some(GeneratorResume::Yield {
1558                value,
1559                activation: SuspendedActivation {
1560                    target,
1561                    registers,
1562                    this_value: activation.this_value,
1563                    new_target: activation.new_target,
1564                    args: activation.args,
1565                    arguments_object: activation.arguments_object,
1566                    resume_token,
1567                },
1568            }),
1569            Ok(FrameCompletion::Normal(value)) => {
1570                self.machine
1571                    .borrow_mut()
1572                    .release_suspended_activation_registers(register_count);
1573                Some(GeneratorResume::Return(value))
1574            }
1575            Ok(FrameCompletion::Unwind(value, origin, _)) => {
1576                self.machine
1577                    .borrow_mut()
1578                    .release_suspended_activation_registers(register_count);
1579                Some(GeneratorResume::Throw { value, origin })
1580            }
1581            Err(error) => {
1582                self.machine
1583                    .borrow_mut()
1584                    .release_suspended_activation_registers(register_count);
1585                self.pending_error.set(Some(error));
1586                None
1587            }
1588        }
1589    }
1590
1591    fn start_linked_generator(&self, start: GeneratorStart) -> Option<GeneratorResume> {
1592        let index = start.target.function.get() as usize;
1593        let handle = self.code_ref(start.target.module);
1594        let code = handle.code(start.target.module);
1595        let registers = self.seed_registers(code, index, &start.captures, &start.args);
1596        if let Err(kind) = self
1597            .machine
1598            .borrow_mut()
1599            .reserve_suspended_activation_registers(registers.len())
1600        {
1601            self.pending_fatal_kind.set(Some(kind));
1602            return None;
1603        }
1604        self.drive_linked_generator(
1605            SuspendedActivation {
1606                target: start.target,
1607                registers,
1608                this_value: start.this_value,
1609                new_target: start.new_target,
1610                args: start.args,
1611                arguments_object: None,
1612                resume_token: 0,
1613            },
1614            None,
1615        )
1616    }
1617
1618    fn resume_linked_generator(
1619        &self,
1620        activation: SuspendedActivation,
1621        sent: Value,
1622    ) -> Option<GeneratorResume> {
1623        self.drive_linked_generator(activation, Some(sent))
1624    }
1625
1626    fn drive_linked_generator(
1627        &self,
1628        mut suspended: SuspendedActivation,
1629        pending_resume: Option<Value>,
1630    ) -> Option<GeneratorResume> {
1631        let register_count = suspended.registers.len();
1632        if let Err(kind) = self.machine.borrow_mut().enter_native_generator() {
1633            self.machine
1634                .borrow_mut()
1635                .release_suspended_activation_registers(register_count);
1636            self.pending_fatal_kind.set(Some(kind));
1637            return None;
1638        }
1639        let length = match u16::try_from(register_count) {
1640            Ok(length) => length,
1641            Err(_) => {
1642                self.machine.borrow_mut().leave_native_generator();
1643                self.machine
1644                    .borrow_mut()
1645                    .release_suspended_activation_registers(register_count);
1646                self.pending_fatal_kind
1647                    .set(Some(RuntimeErrorKind::RegisterLimitExceeded {
1648                        limit: self.max_total_registers(),
1649                    }));
1650                return None;
1651            }
1652        };
1653        self.activations.borrow_mut().push(Activation {
1654            this_value: suspended.this_value,
1655            new_target: suspended.new_target,
1656            args: suspended.args.clone(),
1657            arguments_object: suspended.arguments_object,
1658            pending_resume,
1659        });
1660        let handles = suspended.registers.as_mut_ptr();
1661        let (invoked, next_token, out) = {
1662            let mut shadow = ShadowFrame::new(
1663                std::ptr::null_mut(),
1664                suspended.resume_token,
1665                suspended.target.module.get(),
1666                handles,
1667                length,
1668            );
1669            let mut out = Completion::new(Value::UNDEFINED);
1670            let invoked = self.entries.invoke(
1671                suspended.target.module.get(),
1672                suspended.target.function.get(),
1673                &mut shadow,
1674                &mut out,
1675            );
1676            (invoked, shadow.bytecode_pc, out)
1677        };
1678        let activation = self
1679            .activations
1680            .borrow_mut()
1681            .pop()
1682            .expect("generator activation exists");
1683        self.machine.borrow_mut().leave_native_generator();
1684        suspended.arguments_object = activation.arguments_object;
1685
1686        match invoked {
1687            Ok(CompletionTag::Suspend) if next_token != 0 => {
1688                suspended.resume_token = next_token;
1689                Some(GeneratorResume::Yield {
1690                    value: out.value,
1691                    activation: suspended,
1692                })
1693            }
1694            Ok(CompletionTag::Normal) => {
1695                self.pending_throw.take();
1696                self.machine
1697                    .borrow_mut()
1698                    .release_suspended_activation_registers(register_count);
1699                Some(GeneratorResume::Return(out.value))
1700            }
1701            Ok(CompletionTag::Throw) => {
1702                self.machine
1703                    .borrow_mut()
1704                    .release_suspended_activation_registers(register_count);
1705                let (value, origin) = self.take_matching_throw(out.value);
1706                Some(GeneratorResume::Throw { value, origin })
1707            }
1708            Ok(CompletionTag::Suspend | CompletionTag::FatalTrap) => {
1709                self.machine
1710                    .borrow_mut()
1711                    .release_suspended_activation_registers(register_count);
1712                None
1713            }
1714            Err(error) => {
1715                self.machine
1716                    .borrow_mut()
1717                    .release_suspended_activation_registers(register_count);
1718                self.pending_abi_error.set(Some(error));
1719                None
1720            }
1721        }
1722    }
1723
1724    /// Invokes a compiled entry through the borrowed [`NativeEntryTable`],
1725    /// building a fresh child [`ShadowFrame`] over a driver-local register file.
1726    fn invoke_linked(
1727        &self,
1728        target: crate::RuntimeFunction,
1729        captures: &[Value],
1730        this: Value,
1731        new_target: Value,
1732        args: &[Value],
1733    ) -> InvokeOutcome {
1734        debug_assert!(!self.is_dynamic_module(target.module));
1735        let index = target.function.get() as usize;
1736        let handle = self.code_ref(target.module);
1737        let code = handle.code(target.module);
1738        let register_count = code.functions()[index].register_count() as usize;
1739        let mut registers = self.seed_registers(code, index, captures, args);
1740        let length = match u16::try_from(registers.len()) {
1741            Ok(length) => length,
1742            Err(_) => {
1743                let limit = self.max_total_registers();
1744                self.pending_fatal_kind
1745                    .set(Some(RuntimeErrorKind::RegisterLimitExceeded { limit }));
1746                return InvokeOutcome::Fatal;
1747            }
1748        };
1749        if let Err(kind) = self
1750            .machine
1751            .borrow_mut()
1752            .reserve_native_activation(register_count)
1753        {
1754            self.pending_fatal_kind.set(Some(kind));
1755            return InvokeOutcome::Fatal;
1756        }
1757        self.activations.borrow_mut().push(Activation {
1758            this_value: this,
1759            new_target,
1760            args: args.to_vec(),
1761            arguments_object: None,
1762            pending_resume: None,
1763        });
1764        let handles = registers.as_mut_ptr();
1765        let (tag, out) = {
1766            let mut shadow = ShadowFrame::new(
1767                std::ptr::null_mut(),
1768                0,
1769                target.module.get(),
1770                handles,
1771                length,
1772            );
1773            let mut out = Completion::new(Value::UNDEFINED);
1774            let tag = self.entries.invoke(
1775                target.module.get(),
1776                target.function.get(),
1777                &mut shadow,
1778                &mut out,
1779            );
1780            (tag, out)
1781        };
1782        drop(registers);
1783        self.activations.borrow_mut().pop();
1784        self.machine
1785            .borrow_mut()
1786            .release_native_activation(register_count);
1787        match tag {
1788            Ok(CompletionTag::Normal) => {
1789                self.pending_throw.take();
1790                InvokeOutcome::Value(out.value)
1791            }
1792            Ok(CompletionTag::Throw) => {
1793                let (value, origin) = self.take_matching_throw(out.value);
1794                InvokeOutcome::Threw(value, origin)
1795            }
1796            Ok(CompletionTag::Suspend | CompletionTag::FatalTrap) => InvokeOutcome::Fatal,
1797            Err(error) => {
1798                self.pending_abi_error.set(Some(error));
1799                InvokeOutcome::Fatal
1800            }
1801        }
1802    }
1803
1804    fn evaluate_import(&self, module: ModuleId) -> Result<(), ImportFailure> {
1805        let begun = self.machine.borrow_mut().begin_module_evaluation(module);
1806        let dependencies = match begun {
1807            Err(error) => {
1808                self.pending_error.set(Some(error));
1809                return Err(ImportFailure::Fatal);
1810            }
1811            Ok(crate::ModuleEvaluation::Cycle) => return Ok(()),
1812            Ok(crate::ModuleEvaluation::Evaluated(Ok(()))) => return Ok(()),
1813            Ok(crate::ModuleEvaluation::Evaluated(Err(error))) => {
1814                if matches!(error.kind, RuntimeErrorKind::UncaughtThrow { .. }) {
1815                    return Err(ImportFailure::Threw(error));
1816                }
1817                self.pending_error.set(Some(error));
1818                return Err(ImportFailure::Fatal);
1819            }
1820            Ok(crate::ModuleEvaluation::Ready(dependencies)) => dependencies,
1821        };
1822        for dependency in dependencies {
1823            if let Err(failure) = self.evaluate_import(dependency) {
1824                let mut machine = self.machine.borrow_mut();
1825                match &failure {
1826                    ImportFailure::Threw(error) => {
1827                        machine.settle_module_evaluation(module, Err(error.clone()));
1828                    }
1829                    ImportFailure::Fatal => machine.abort_module_evaluation(module),
1830                }
1831                return Err(failure);
1832            }
1833        }
1834
1835        let function = self.module(module).entry();
1836        let outcome = self.invoke_runtime(
1837            crate::RuntimeFunction { module, function },
1838            &[],
1839            Value::UNDEFINED,
1840            Value::UNDEFINED,
1841            &[],
1842        );
1843        match outcome {
1844            InvokeOutcome::Value(_) => {
1845                self.machine
1846                    .borrow_mut()
1847                    .settle_module_evaluation(module, Ok(()));
1848                Ok(())
1849            }
1850            InvokeOutcome::Threw(value, origin) => {
1851                let error = self.error_at(
1852                    module,
1853                    RuntimeErrorKind::UncaughtThrow { value, origin },
1854                    function.get() as usize,
1855                    0,
1856                );
1857                self.machine
1858                    .borrow_mut()
1859                    .settle_module_evaluation(module, Err(error.clone()));
1860                Err(ImportFailure::Threw(error))
1861            }
1862            InvokeOutcome::Fatal => {
1863                self.machine.borrow_mut().abort_module_evaluation(module);
1864                Err(ImportFailure::Fatal)
1865            }
1866        }
1867    }
1868
1869    fn import_namespace(&self, requester: ModuleId, specifier: u32) -> HelperResult {
1870        let target = self
1871            .machine
1872            .borrow()
1873            .resolve_import(requester, ConstantId::new(specifier));
1874        let target = match target {
1875            Ok(target) => target,
1876            Err(kind) => return self.fatal(kind),
1877        };
1878        if let crate::ImportTarget::Local(module) = target
1879            && let Err(failure) = self.evaluate_import(module)
1880        {
1881            return match failure {
1882                ImportFailure::Threw(error) => self.fail(crate::import_failure(&error)),
1883                ImportFailure::Fatal => HelperResult {
1884                    tag: CompletionTag::FatalTrap,
1885                    value: Value::UNDEFINED,
1886                },
1887            };
1888        }
1889        let namespace = self
1890            .machine
1891            .borrow_mut()
1892            .imported_namespace(requester, target);
1893        match namespace {
1894            Ok(value) => HelperResult::normal(value),
1895            Err(kind) => self.fatal(kind),
1896        }
1897    }
1898
1899    // -- HelperResult constructors -------------------------------------------
1900
1901    fn fatal(&self, kind: RuntimeErrorKind) -> HelperResult {
1902        self.pending_fatal_kind.set(Some(kind));
1903        HelperResult {
1904            tag: CompletionTag::FatalTrap,
1905            value: Value::UNDEFINED,
1906        }
1907    }
1908
1909    fn fail(&self, failure: EvalFailure) -> HelperResult {
1910        match failure {
1911            EvalFailure::Throw(origin) => {
1912                self.pending_throw.set(Some(PendingThrow {
1913                    value: Value::UNDEFINED,
1914                    origin,
1915                }));
1916                HelperResult::throw(Value::UNDEFINED)
1917            }
1918            EvalFailure::ThrowValue(value) => {
1919                self.pending_throw.set(Some(PendingThrow {
1920                    value,
1921                    origin: ThrowOrigin::Bytecode,
1922                }));
1923                HelperResult::throw(value)
1924            }
1925            EvalFailure::ThrowValueOrigin { value, origin } => {
1926                self.pending_throw.set(Some(PendingThrow { value, origin }));
1927                HelperResult::throw(value)
1928            }
1929            EvalFailure::Runtime(kind) => self.fatal(kind),
1930        }
1931    }
1932
1933    fn eval_result(&self, result: Result<Value, EvalFailure>) -> HelperResult {
1934        match result {
1935            Ok(value) => HelperResult::normal(value),
1936            Err(failure) => self.fail(failure),
1937        }
1938    }
1939
1940    fn outcome_result(&self, outcome: InvokeOutcome) -> HelperResult {
1941        match outcome {
1942            InvokeOutcome::Value(value) => HelperResult::normal(value),
1943            InvokeOutcome::Threw(value, origin) => {
1944                self.pending_throw.set(Some(PendingThrow { value, origin }));
1945                HelperResult::throw(value)
1946            }
1947            InvokeOutcome::Fatal => HelperResult {
1948                tag: CompletionTag::FatalTrap,
1949                value: Value::UNDEFINED,
1950            },
1951        }
1952    }
1953
1954    fn validated(&self, value: Value) -> HelperResult {
1955        HelperResult::normal(value)
1956    }
1957
1958    fn allocated(&self, entry: HeapEntry) -> HelperResult {
1959        let result = self.machine.borrow_mut().allocate(entry);
1960        match result {
1961            Ok(value) => HelperResult::normal(value),
1962            Err(kind) => self.fatal(kind),
1963        }
1964    }
1965
1966    fn constant_text(&self, module: ModuleId, id: u32) -> EcmaString {
1967        self.machine
1968            .borrow()
1969            .constant_text(module, ConstantId::new(id))
1970            .clone()
1971    }
1972
1973    /// Materializes the `arguments` object for the current activation, caching it.
1974    fn load_arguments(&self) -> HelperResult {
1975        let args = match self.activations.borrow().last() {
1976            Some(activation) => activation.args.clone(),
1977            None => return HelperResult::normal(Value::UNDEFINED),
1978        };
1979        if let Some(existing) = self
1980            .activations
1981            .borrow()
1982            .last()
1983            .and_then(|activation| activation.arguments_object)
1984        {
1985            return HelperResult::normal(existing);
1986        }
1987        let prototype = self.machine.borrow().intrinsics.array_prototype;
1988        let allocated = self.machine.borrow_mut().allocate(HeapEntry::Array {
1989            elements: args,
1990            properties: PropertyMap::default(),
1991            prototype: Some(prototype),
1992            extensible: true,
1993            length_writable: true,
1994        });
1995        let value = match allocated {
1996            Ok(value) => value,
1997            Err(kind) => return self.fatal(kind),
1998        };
1999        if let Some(activation) = self.activations.borrow_mut().last_mut() {
2000            activation.arguments_object = Some(value);
2001        }
2002        HelperResult::normal(value)
2003    }
2004
2005    /// `Construct`: allocate the instance with the constructor's `prototype`,
2006    /// invoke with `new.target`, and override a non-object return with the
2007    /// instance — the shared construct semantics, over native activations.
2008    fn construct(&self, callee: Value, arguments: &[Value]) -> HelperResult {
2009        let mut callee = callee;
2010        let mut arguments = Cow::Borrowed(arguments);
2011        if matches!(
2012            self.machine.borrow().callee_kind(callee),
2013            Ok(CalleeKind::Bound)
2014        ) {
2015            let bound =
2016                self.machine
2017                    .borrow()
2018                    .flatten_bound(callee, Value::UNDEFINED, arguments.as_ref());
2019            match bound {
2020                Ok(bound) => {
2021                    callee = bound.target;
2022                    arguments = Cow::Owned(bound.arguments);
2023                }
2024                Err(kind) => return self.fatal(kind),
2025            }
2026        }
2027        let kind = self.machine.borrow().callee_kind(callee);
2028        match kind {
2029            Ok(CalleeKind::Builtin { id }) => {
2030                let result = self.machine.borrow_mut().call_builtin(
2031                    id,
2032                    Value::UNDEFINED,
2033                    arguments.as_ref(),
2034                    true,
2035                );
2036                match result {
2037                    Ok(BuiltinOutcome::Value(value)) => HelperResult::normal(value),
2038                    Ok(BuiltinOutcome::Call { .. } | BuiltinOutcome::GeneratorNext { .. }) => {
2039                        self.pending_throw.set(Some(PendingThrow {
2040                            value: Value::UNDEFINED,
2041                            origin: ThrowOrigin::TypeError {
2042                                operation: "construct",
2043                            },
2044                        }));
2045                        HelperResult::throw(Value::UNDEFINED)
2046                    }
2047                    Ok(BuiltinOutcome::ConstructCall {
2048                        callee: continuation,
2049                        this_value,
2050                        arguments: continuation_arguments,
2051                        prototype,
2052                    }) => {
2053                        let instance = match self
2054                            .machine
2055                            .borrow_mut()
2056                            .allocate_constructed_receiver_with(prototype)
2057                        {
2058                            Ok(value) => value,
2059                            Err(kind) => return self.fatal(kind),
2060                        };
2061                        let outcome = self.invoke_callee(
2062                            continuation,
2063                            this_value,
2064                            &continuation_arguments,
2065                            callee,
2066                        );
2067                        match outcome {
2068                            InvokeOutcome::Value(returned) => {
2069                                let is_object = self.machine.borrow().is_object(returned);
2070                                HelperResult::normal(if is_object { returned } else { instance })
2071                            }
2072                            other => self.outcome_result(other),
2073                        }
2074                    }
2075                    Err(failure) => self.fail(failure),
2076                }
2077            }
2078            Ok(CalleeKind::Runtime { target, captures }) => {
2079                let flags = {
2080                    let handle = self.code_ref(target.module);
2081                    handle.code(target.module).functions()[target.function.get() as usize].flags()
2082                };
2083                if flags.is_async && !flags.is_generator {
2084                    self.pending_throw.set(Some(PendingThrow {
2085                        value: Value::UNDEFINED,
2086                        origin: ThrowOrigin::TypeError {
2087                            operation: "construct",
2088                        },
2089                    }));
2090                    return HelperResult::throw(Value::UNDEFINED);
2091                }
2092                let instance = {
2093                    let allocated = self
2094                        .machine
2095                        .borrow_mut()
2096                        .allocate_constructed_receiver(callee);
2097                    match allocated {
2098                        Ok(value) => value,
2099                        Err(kind) => return self.fatal(kind),
2100                    }
2101                };
2102                let outcome =
2103                    self.invoke_runtime(target, &captures, instance, callee, arguments.as_ref());
2104                match outcome {
2105                    InvokeOutcome::Value(returned) => {
2106                        let is_object = self.machine.borrow().is_object(returned);
2107                        HelperResult::normal(if is_object { returned } else { instance })
2108                    }
2109                    other => self.outcome_result(other),
2110                }
2111            }
2112            Ok(CalleeKind::Bound) => self.fatal(RuntimeErrorKind::InvalidValue { value: callee }),
2113            Ok(CalleeKind::NotCallable) => {
2114                self.pending_throw.set(Some(PendingThrow {
2115                    value: Value::UNDEFINED,
2116                    origin: ThrowOrigin::TypeError {
2117                        operation: "construct",
2118                    },
2119                }));
2120                HelperResult::throw(Value::UNDEFINED)
2121            }
2122            Err(kind) => self.fatal(kind),
2123        }
2124    }
2125
2126    // -- Linked backend ------------------------------------------------------
2127
2128    fn run_linked(&mut self) -> Result<ExecutionOutcome, NativeError> {
2129        self.machine.borrow_mut().instantiate_modules()?;
2130        let module = self.program.entry();
2131        let execution = self.evaluate_linked_module(module)?.ok_or_else(|| {
2132            let function = self.module(module).entry().get() as usize;
2133            NativeError::Runtime(self.error_at(
2134                module,
2135                RuntimeErrorKind::InvalidVerifiedProgram {
2136                    module,
2137                    instruction: Instruction::Halt,
2138                },
2139                function,
2140                0,
2141            ))
2142        })?;
2143        // The linked backend shares the interpreter's automatic loop policy: it
2144        // drives the machine to quiescence after successful evaluation. Driver
2145        // failures are runtime failures, surfaced through `NativeError::Runtime`.
2146        self.machine
2147            .borrow_mut()
2148            .run_to_quiescence()
2149            .map_err(NativeError::Runtime)?;
2150        Ok(execution.outcome)
2151    }
2152
2153    fn evaluate_linked_module(
2154        &mut self,
2155        module: ModuleId,
2156    ) -> Result<Option<Execution>, NativeError> {
2157        let dependencies = match self.machine.borrow_mut().begin_module_evaluation(module)? {
2158            crate::ModuleEvaluation::Cycle => return Ok(None),
2159            crate::ModuleEvaluation::Evaluated(result) => {
2160                return result.map(|()| None).map_err(Into::into);
2161            }
2162            crate::ModuleEvaluation::Ready(dependencies) => dependencies,
2163        };
2164        for dependency in dependencies {
2165            match self.evaluate_linked_module(dependency) {
2166                Ok(_) => {}
2167                Err(NativeError::Runtime(error)) => {
2168                    let error = self
2169                        .machine
2170                        .borrow_mut()
2171                        .finish_module_evaluation(module, Err(error))
2172                        .expect_err("dependency failure remains an error");
2173                    return Err(NativeError::Runtime(error));
2174                }
2175                Err(error) => {
2176                    self.machine.borrow_mut().abort_module_evaluation(module);
2177                    return Err(error);
2178                }
2179            }
2180        }
2181
2182        match self.invoke_linked_entry(module) {
2183            Ok(execution) => self
2184                .machine
2185                .borrow_mut()
2186                .finish_module_evaluation(module, Ok(execution))
2187                .map(Some)
2188                .map_err(Into::into),
2189            Err(NativeError::Runtime(error)) => {
2190                let error = self
2191                    .machine
2192                    .borrow_mut()
2193                    .finish_module_evaluation(module, Err(error))
2194                    .expect_err("module failure remains an error");
2195                Err(NativeError::Runtime(error))
2196            }
2197            Err(error) => {
2198                self.machine.borrow_mut().abort_module_evaluation(module);
2199                Err(error)
2200            }
2201        }
2202    }
2203
2204    fn invoke_linked_entry(&mut self, module: ModuleId) -> Result<Execution, NativeError> {
2205        let code = self.module(module);
2206        let function_id = code.entry();
2207        let function = function_id.get() as usize;
2208        let register_count = code.functions()[function].register_count() as usize;
2209        if self.max_call_depth() < 1 {
2210            return Err(NativeError::Runtime(self.error_at(
2211                module,
2212                RuntimeErrorKind::CallDepthExceeded {
2213                    limit: self.max_call_depth(),
2214                },
2215                function,
2216                0,
2217            )));
2218        }
2219        if register_count > self.max_total_registers() {
2220            return Err(NativeError::Runtime(self.error_at(
2221                module,
2222                RuntimeErrorKind::RegisterLimitExceeded {
2223                    limit: self.max_total_registers(),
2224                },
2225                function,
2226                0,
2227            )));
2228        }
2229        let mut registers = self.seed_registers(code, function, &[], &[]);
2230        let length = u16::try_from(register_count).map_err(|_| {
2231            NativeError::Runtime(self.error_at(
2232                module,
2233                RuntimeErrorKind::RegisterLimitExceeded {
2234                    limit: self.max_total_registers(),
2235                },
2236                function,
2237                0,
2238            ))
2239        })?;
2240        let reserved = self
2241            .machine
2242            .borrow_mut()
2243            .reserve_native_activation(register_count);
2244        reserved.map_err(|kind| NativeError::Runtime(self.error_at(module, kind, function, 0)))?;
2245        self.activations.borrow_mut().push(Activation {
2246            this_value: Value::UNDEFINED,
2247            new_target: Value::UNDEFINED,
2248            args: Vec::new(),
2249            arguments_object: None,
2250            pending_resume: None,
2251        });
2252        let handles = registers.as_mut_ptr();
2253        let entries = self.entries;
2254        let (tag, out, fault_pc) = {
2255            let mut shadow =
2256                ShadowFrame::new(std::ptr::null_mut(), 0, module.get(), handles, length);
2257            let mut out = Completion::new(Value::UNDEFINED);
2258            let tag = with_native_ops(self, || {
2259                entries.invoke(module.get(), function_id.get(), &mut shadow, &mut out)
2260            });
2261            (tag, out, shadow.bytecode_pc as usize)
2262        };
2263        self.activations.borrow_mut().pop();
2264        self.machine
2265            .borrow_mut()
2266            .release_native_activation(register_count);
2267        match tag {
2268            Ok(CompletionTag::Normal) => {
2269                self.pending_throw.take();
2270                Ok(Execution {
2271                    outcome: ExecutionOutcome {
2272                        stdout: self.stdout.borrow().clone(),
2273                        exit_code: self.exit_code.get(),
2274                    },
2275                    value: out.value,
2276                    link: out.value,
2277                    entry_registers: registers,
2278                })
2279            }
2280            Ok(CompletionTag::Throw) => {
2281                let (value, origin) = self.take_matching_throw(out.value);
2282                Err(NativeError::Runtime(self.error_at(
2283                    module,
2284                    RuntimeErrorKind::UncaughtThrow { value, origin },
2285                    function,
2286                    fault_pc,
2287                )))
2288            }
2289            Ok(CompletionTag::Suspend | CompletionTag::FatalTrap) => {
2290                if let Some(error) = self.pending_abi_error.take() {
2291                    Err(NativeError::Abi(error))
2292                } else if let Some(error) = self.pending_error.take() {
2293                    Err(NativeError::Runtime(error))
2294                } else if let Some(kind) = self.pending_fatal_kind.take() {
2295                    Err(NativeError::Runtime(
2296                        self.error_at(module, kind, function, fault_pc),
2297                    ))
2298                } else {
2299                    Err(NativeError::FatalTrap { value: out.value })
2300                }
2301            }
2302            Err(error) => Err(NativeError::Abi(error)),
2303        }
2304    }
2305}
2306
2307// -- The NativeOps seam ------------------------------------------------------
2308
2309impl<'m, 'h, H: Host> NativeOps for NativeEngine<'m, 'h, H> {
2310    fn truthy(&self, _frame: &mut NativeFrame<'_>, value: Value) -> bool {
2311        self.machine.borrow().truthy(value)
2312    }
2313
2314    fn dispatch(&self, frame: &mut NativeFrame<'_>, call: HelperCall) -> HelperResult {
2315        let module = ModuleId::new(frame.module_id());
2316        let amount = match call {
2317            HelperCall::ResumeValue => None,
2318            HelperCall::ConsumeFuel { amount } => Some(u64::from(amount)),
2319            _ => Some(1),
2320        };
2321        if let Some(amount) = amount
2322            && let Err(kind) = self.machine.borrow_mut().consume_fuel(amount)
2323        {
2324            return self.fatal(kind);
2325        }
2326        match call {
2327            HelperCall::LoadConstant { const_id } => {
2328                let result = self
2329                    .machine
2330                    .borrow_mut()
2331                    .load_constant_value(module, ConstantId::new(const_id));
2332                match result {
2333                    Ok(value) => HelperResult::normal(value),
2334                    Err(kind) => self.fatal(kind),
2335                }
2336            }
2337            HelperCall::Unary { op, operand } => match unary_from_selector(op) {
2338                Some(op) => {
2339                    let result = self.machine.borrow_mut().eval_unary(op, operand);
2340                    self.eval_result(result)
2341                }
2342                None => self.fatal(RuntimeErrorKind::InvalidValue {
2343                    value: Value::UNDEFINED,
2344                }),
2345            },
2346            HelperCall::Binary { op, left, right } => match binary_from_selector(op) {
2347                Some(op) => {
2348                    let result = self.machine.borrow_mut().eval_binary(op, left, right);
2349                    self.eval_result(result)
2350                }
2351                None => self.fatal(RuntimeErrorKind::InvalidValue {
2352                    value: Value::UNDEFINED,
2353                }),
2354            },
2355            HelperCall::CreateObject => {
2356                let prototype = self.machine.borrow().intrinsics.object_prototype;
2357                self.allocated(HeapEntry::Object {
2358                    properties: PropertyMap::default(),
2359                    prototype: Some(prototype),
2360                    boxed_primitive: None,
2361                    extensible: true,
2362                })
2363            }
2364            HelperCall::CreateArray => {
2365                let prototype = self.machine.borrow().intrinsics.array_prototype;
2366                self.allocated(HeapEntry::Array {
2367                    elements: Vec::new(),
2368                    properties: PropertyMap::default(),
2369                    prototype: Some(prototype),
2370                    extensible: true,
2371                    length_writable: true,
2372                })
2373            }
2374            HelperCall::CreateCell => {
2375                let prototype = self.machine.borrow().intrinsics.array_prototype;
2376                self.allocated(HeapEntry::Array {
2377                    elements: vec![Value::UNINITIALIZED],
2378                    properties: PropertyMap::default(),
2379                    prototype: Some(prototype),
2380                    extensible: true,
2381                    length_writable: true,
2382                })
2383            }
2384            HelperCall::CreateClosure {
2385                function_id,
2386                captures,
2387            } => {
2388                let function = FunctionId::new(function_id);
2389                let materialized = self
2390                    .machine
2391                    .borrow()
2392                    .captures_from_array(module, captures, function);
2393                match materialized {
2394                    Ok(captures) => {
2395                        let prototype = self.machine.borrow().intrinsics.function_prototype;
2396                        self.allocated(HeapEntry::Function {
2397                            module,
2398                            function,
2399                            captures,
2400                            properties: PropertyMap::default(),
2401                            prototype: Some(prototype),
2402                            extensible: true,
2403                        })
2404                    }
2405                    Err(failure) => self.fail(failure),
2406                }
2407            }
2408            HelperCall::GetProperty { object, key } => {
2409                let key = {
2410                    let coerced = self.machine.borrow().to_property_key(key);
2411                    match coerced {
2412                        Ok(key) => key,
2413                        Err(failure) => return self.fail(failure),
2414                    }
2415                };
2416                let outcome = self.machine.borrow_mut().resolve_get(object, &key);
2417                match outcome {
2418                    Ok(GetOutcome::Value(value)) => self.validated(value),
2419                    Ok(GetOutcome::Text(text)) => self.allocated(HeapEntry::String(text)),
2420                    Ok(GetOutcome::Getter(getter)) => {
2421                        let outcome = self.invoke_callee(getter, object, &[], Value::UNDEFINED);
2422                        self.outcome_result(outcome)
2423                    }
2424                    Err(failure) => self.fail(failure),
2425                }
2426            }
2427            HelperCall::SetProperty { object, key, value } => {
2428                let key = {
2429                    let coerced = self.machine.borrow().to_property_key(key);
2430                    match coerced {
2431                        Ok(key) => key,
2432                        Err(failure) => return self.fail(failure),
2433                    }
2434                };
2435                let outcome = self.machine.borrow_mut().resolve_set(object, key, value);
2436                match outcome {
2437                    Ok(SetOutcome::Done) => HelperResult::normal(Value::UNDEFINED),
2438                    Ok(SetOutcome::Setter(setter)) => {
2439                        let outcome =
2440                            self.invoke_callee(setter, object, &[value], Value::UNDEFINED);
2441                        match outcome {
2442                            InvokeOutcome::Value(_) => HelperResult::normal(Value::UNDEFINED),
2443                            other => self.outcome_result(other),
2444                        }
2445                    }
2446                    Err(failure) => self.fail(failure),
2447                }
2448            }
2449            HelperCall::DeleteProperty { object, key } => {
2450                let key = {
2451                    let coerced = self.machine.borrow().to_property_key(key);
2452                    match coerced {
2453                        Ok(key) => key,
2454                        Err(failure) => return self.fail(failure),
2455                    }
2456                };
2457                let deleted = self.machine.borrow_mut().delete_property(object, &key);
2458                match deleted {
2459                    Ok(deleted) => HelperResult::normal(Value::boolean(deleted)),
2460                    Err(failure) => self.fail(failure),
2461                }
2462            }
2463            HelperCall::DefineAccessor {
2464                object,
2465                key,
2466                accessor,
2467                kind,
2468            } => {
2469                let kind = match accessor_from_selector(kind) {
2470                    Some(kind) => kind,
2471                    None => {
2472                        return self.fatal(RuntimeErrorKind::InvalidValue {
2473                            value: Value::UNDEFINED,
2474                        });
2475                    }
2476                };
2477                let key = {
2478                    let coerced = self.machine.borrow().to_property_key(key);
2479                    match coerced {
2480                        Ok(key) => key,
2481                        Err(failure) => return self.fail(failure),
2482                    }
2483                };
2484                let defined = self
2485                    .machine
2486                    .borrow_mut()
2487                    .define_accessor(object, key, accessor, kind);
2488                match defined {
2489                    Ok(()) => HelperResult::normal(Value::UNDEFINED),
2490                    Err(failure) => self.fail(failure),
2491                }
2492            }
2493            HelperCall::Call {
2494                callee,
2495                this_value,
2496                arguments,
2497            } => {
2498                let arguments = {
2499                    let read = self.machine.borrow().arguments_from_array(arguments);
2500                    match read {
2501                        Ok(arguments) => arguments,
2502                        Err(failure) => return self.fail(failure),
2503                    }
2504                };
2505                let outcome = self.invoke_callee(callee, this_value, &arguments, Value::UNDEFINED);
2506                self.outcome_result(outcome)
2507            }
2508            HelperCall::Construct { callee, arguments } => {
2509                let arguments = {
2510                    let read = self.machine.borrow().arguments_from_array(arguments);
2511                    match read {
2512                        Ok(arguments) => arguments,
2513                        Err(failure) => return self.fail(failure),
2514                    }
2515                };
2516                self.construct(callee, &arguments)
2517            }
2518            HelperCall::Import { specifier } => self.import_namespace(module, specifier),
2519            HelperCall::Truthy { value } => {
2520                HelperResult::normal(Value::boolean(self.machine.borrow().truthy(value)))
2521            }
2522            HelperCall::ResumeValue => {
2523                let resumed = self
2524                    .activations
2525                    .borrow_mut()
2526                    .last_mut()
2527                    .and_then(|activation| activation.pending_resume.take());
2528                match resumed {
2529                    Some(value) => self.validated(value),
2530                    None => self.fatal(RuntimeErrorKind::InvalidValue {
2531                        value: Value::UNDEFINED,
2532                    }),
2533                }
2534            }
2535            HelperCall::LoadGlobal { name } => {
2536                let resolved = self
2537                    .machine
2538                    .borrow()
2539                    .load_global(module, ConstantId::new(name));
2540                match resolved {
2541                    Ok(Some(value)) => self.validated(value),
2542                    Ok(None) => {
2543                        self.pending_throw.set(Some(PendingThrow {
2544                            value: Value::UNDEFINED,
2545                            origin: ThrowOrigin::ReferenceError {
2546                                operation: "global is not defined",
2547                            },
2548                        }));
2549                        HelperResult::throw(Value::UNDEFINED)
2550                    }
2551                    Err(kind) => self.fatal(kind),
2552                }
2553            }
2554            HelperCall::StoreGlobal { name, value } => {
2555                let stored =
2556                    self.machine
2557                        .borrow_mut()
2558                        .store_global(module, ConstantId::new(name), value);
2559                match stored {
2560                    Ok(()) => HelperResult::normal(Value::UNDEFINED),
2561                    Err(failure) => self.fail(failure),
2562                }
2563            }
2564            HelperCall::TypeOfGlobal { name } => {
2565                let resolved = self
2566                    .machine
2567                    .borrow()
2568                    .load_global(module, ConstantId::new(name));
2569                let text = match resolved {
2570                    Ok(Some(value)) => EcmaString::from_utf8(self.machine.borrow().type_of(value)),
2571                    Ok(None) => EcmaString::from_utf8("undefined"),
2572                    Err(kind) => return self.fatal(kind),
2573                };
2574                self.allocated(HeapEntry::String(text))
2575            }
2576            HelperCall::LoadThis => HelperResult::normal(
2577                self.activations
2578                    .borrow()
2579                    .last()
2580                    .map_or(Value::UNDEFINED, |activation| activation.this_value),
2581            ),
2582            HelperCall::LoadArguments => self.load_arguments(),
2583            HelperCall::LoadNewTarget => HelperResult::normal(
2584                self.activations
2585                    .borrow()
2586                    .last()
2587                    .map_or(Value::UNDEFINED, |activation| activation.new_target),
2588            ),
2589            HelperCall::ArrayPush { array, value } => {
2590                let result = self.machine.borrow_mut().array_push(array, value);
2591                match result {
2592                    Ok(()) => HelperResult::normal(Value::UNDEFINED),
2593                    Err(failure) => self.fail(failure),
2594                }
2595            }
2596            HelperCall::ArrayExtend { array, iterable } => {
2597                let outcome = self.array_extend_active(array, iterable);
2598                self.outcome_result(outcome)
2599            }
2600            HelperCall::ObjectSpread { target, source } => {
2601                let result = self.machine.borrow_mut().object_spread(target, source);
2602                match result {
2603                    Ok(()) => HelperResult::normal(Value::UNDEFINED),
2604                    Err(failure) => self.fail(failure),
2605                }
2606            }
2607            HelperCall::SetPrototype { object, prototype } => {
2608                let result = self.machine.borrow_mut().set_prototype(object, prototype);
2609                match result {
2610                    Ok(()) => HelperResult::normal(Value::UNDEFINED),
2611                    Err(failure) => self.fail(failure),
2612                }
2613            }
2614            HelperCall::CreatePrivateName { description } => {
2615                let description = self.constant_text(module, description);
2616                self.allocated(HeapEntry::PrivateName { description })
2617            }
2618            HelperCall::CreateRegExp { pattern, flags } => {
2619                let pattern = self.constant_text(module, pattern);
2620                let flags = self.constant_text(module, flags);
2621                let prototype = self.machine.borrow().intrinsics.regexp_prototype();
2622                self.allocated(HeapEntry::RegExp {
2623                    pattern,
2624                    flags,
2625                    properties: PropertyMap::default(),
2626                    prototype: Some(prototype),
2627                    extensible: true,
2628                })
2629            }
2630            HelperCall::GetIterator { src, kind } => match iterator_kind_from_selector(kind) {
2631                Some(kind) => self.outcome_result(self.get_iterator_active(src, kind)),
2632                None => self.fatal(RuntimeErrorKind::InvalidValue {
2633                    value: Value::UNDEFINED,
2634                }),
2635            },
2636            HelperCall::IteratorNext {
2637                iterator,
2638                done_reg,
2639                value_reg,
2640            } => match self.iterator_next_active(iterator) {
2641                Ok((done, value)) => {
2642                    let wrote_done = frame.try_set_register(done_reg, Value::boolean(done));
2643                    let wrote_value = frame.try_set_register(value_reg, value);
2644                    if wrote_done && wrote_value {
2645                        HelperResult::normal(Value::UNDEFINED)
2646                    } else {
2647                        self.fatal(RuntimeErrorKind::InvalidValue {
2648                            value: Value::UNDEFINED,
2649                        })
2650                    }
2651                }
2652                Err(outcome) => self.outcome_result(outcome),
2653            },
2654            HelperCall::Export { .. } => self.fail(EvalFailure::Throw(ThrowOrigin::TypeError {
2655                operation: "export outside an engine-owned module registry",
2656            })),
2657            HelperCall::ConsumeFuel { .. } => HelperResult::normal(Value::UNDEFINED),
2658        }
2659    }
2660}
2661
2662fn is_inline_instruction(instruction: Instruction) -> bool {
2663    match instruction {
2664        Instruction::Move { .. }
2665        | Instruction::Jump { .. }
2666        | Instruction::JumpIfTrue { .. }
2667        | Instruction::JumpIfFalse { .. }
2668        | Instruction::Return { .. }
2669        | Instruction::Halt
2670        | Instruction::Throw { .. }
2671        | Instruction::Suspend { .. } => true,
2672        Instruction::LoadConst { .. }
2673        | Instruction::Unary { .. }
2674        | Instruction::Binary { .. }
2675        | Instruction::CreateObject { .. }
2676        | Instruction::CreateArray { .. }
2677        | Instruction::CreateCell { .. }
2678        | Instruction::CreateClosure { .. }
2679        | Instruction::GetProperty { .. }
2680        | Instruction::SetProperty { .. }
2681        | Instruction::DeleteProperty { .. }
2682        | Instruction::DefineAccessor { .. }
2683        | Instruction::Call { .. }
2684        | Instruction::Construct { .. }
2685        | Instruction::LoadGlobal { .. }
2686        | Instruction::StoreGlobal { .. }
2687        | Instruction::TypeOfGlobal { .. }
2688        | Instruction::LoadThis { .. }
2689        | Instruction::LoadArguments { .. }
2690        | Instruction::LoadNewTarget { .. }
2691        | Instruction::ArrayPush { .. }
2692        | Instruction::ArrayExtend { .. }
2693        | Instruction::ObjectSpread { .. }
2694        | Instruction::SetPrototype { .. }
2695        | Instruction::CreatePrivateName { .. }
2696        | Instruction::CreateRegExp { .. }
2697        | Instruction::GetIterator { .. }
2698        | Instruction::IteratorNext { .. }
2699        | Instruction::Import { .. }
2700        | Instruction::Export { .. } => false,
2701    }
2702}
2703
2704/// Runs a linked program's entry through the native engine and returns its
2705/// observable outcome. This is the runtime side of the AOT `main`: `entries` is
2706/// a compiled [`NativeEntryTable`] (an AOT `LinkedProgram` or a JIT program),
2707/// and the returned [`ExecutionOutcome`] carries buffered stdout and the exit
2708/// code. It is always available and never feature-gated, so a downstream host
2709/// crate can link against it without enabling the runtime's `aot-main` feature.
2710///
2711/// Returns [`NativeError::ProgramMismatch`] before constructing the engine when
2712/// `entries` was not compiled from this program's exact canonical encoding.
2713pub fn run_linked_program<H: Host>(
2714    program: &Program<Verified>,
2715    entries: &dyn NativeEntryTable,
2716    host: &mut H,
2717    limits: &Limits,
2718) -> Result<ExecutionOutcome, NativeError> {
2719    let program_bytes = program.encode();
2720    if program_bytes != entries.program_bytes() {
2721        return Err(NativeError::ProgramMismatch);
2722    }
2723    let mut engine = NativeEngine::build(program, entries, host, limits.clone(), Backend::Linked);
2724    engine.run_linked()
2725}
2726
2727#[cfg(test)]
2728mod tests {
2729    use std::cell::{Cell, RefCell};
2730    use std::sync::Arc;
2731
2732    use bamts_bytecode::{
2733        BinaryOp, Binding, BindingId, BindingKind, Constant, ConstantId, Edge, EdgeId, EdgeKind,
2734        EdgeTarget, ExceptionHandler, Export, ExportSource, Function, FunctionFlags, FunctionId,
2735        Instruction, IteratorKind, Module, ModuleId, Pc, Program, ProgramModule, Register,
2736        Verified,
2737    };
2738    use bamts_native::{
2739        AbiError, Completion, CompletionTag, HelperCall, HelperResult, NativeEntryTable,
2740        NativeFrame, NativeOps, ShadowFrame, Value,
2741    };
2742
2743    use crate::{
2744        GeneratorState, HeapEntry, Host, Limits, Machine, PropertyMap, RuntimeError,
2745        RuntimeErrorKind, ThrowOrigin,
2746    };
2747
2748    use super::EcmaString;
2749    use super::{
2750        Activation, Backend, InvokeOutcome, NativeEngine, NativeError, PendingThrow,
2751        run_linked_program,
2752    };
2753
2754    fn reg(raw: u32) -> Register {
2755        Register::new(raw)
2756    }
2757
2758    fn pc(raw: u32) -> Pc {
2759        Pc::new(raw)
2760    }
2761
2762    fn cid(raw: u32) -> bamts_bytecode::ConstantId {
2763        bamts_bytecode::ConstantId::new(raw)
2764    }
2765
2766    fn entry_function(register_count: u32, code: Vec<Instruction>) -> Function {
2767        Function::new(
2768            None,
2769            0,
2770            0,
2771            register_count,
2772            FunctionFlags::default(),
2773            code,
2774            Vec::new(),
2775        )
2776    }
2777
2778    fn receiver_sum_function() -> Function {
2779        module_function(
2780            0,
2781            5,
2782            vec![
2783                Instruction::LoadThis { dst: reg(0) },
2784                Instruction::LoadConst {
2785                    dst: reg(1),
2786                    constant: cid(1),
2787                },
2788                Instruction::GetProperty {
2789                    dst: reg(2),
2790                    object: reg(0),
2791                    key: reg(1),
2792                },
2793                Instruction::LoadArguments { dst: reg(3) },
2794                Instruction::LoadConst {
2795                    dst: reg(1),
2796                    constant: cid(2),
2797                },
2798                Instruction::GetProperty {
2799                    dst: reg(4),
2800                    object: reg(3),
2801                    key: reg(1),
2802                },
2803                Instruction::Binary {
2804                    dst: reg(2),
2805                    op: BinaryOp::Add,
2806                    left: reg(2),
2807                    right: reg(4),
2808                },
2809                Instruction::LoadConst {
2810                    dst: reg(1),
2811                    constant: cid(3),
2812                },
2813                Instruction::GetProperty {
2814                    dst: reg(4),
2815                    object: reg(3),
2816                    key: reg(1),
2817                },
2818                Instruction::Binary {
2819                    dst: reg(2),
2820                    op: BinaryOp::Add,
2821                    left: reg(2),
2822                    right: reg(4),
2823                },
2824                Instruction::Return { value: reg(2) },
2825            ],
2826        )
2827    }
2828
2829    fn verified(constants: Vec<Constant>, functions: Vec<Function>) -> Module<Verified> {
2830        Module::new(constants, functions, FunctionId::new(0))
2831            .verify()
2832            .expect("module verifies")
2833    }
2834
2835    fn module_function(captures: u32, registers: u32, code: Vec<Instruction>) -> Function {
2836        Function::new(
2837            None,
2838            captures,
2839            0,
2840            registers,
2841            FunctionFlags::default(),
2842            code,
2843            Vec::new(),
2844        )
2845    }
2846
2847    fn program_module(
2848        name: &str,
2849        mut constants: Vec<Constant>,
2850        functions: Vec<Function>,
2851        edges: Vec<Edge>,
2852        bindings: Vec<Binding>,
2853        exports: Vec<Export>,
2854    ) -> ProgramModule<Verified> {
2855        constants.insert(0, Constant::String(EcmaString::from_utf8(name)));
2856        ProgramModule {
2857            name: cid(0),
2858            code: Module::new(constants, functions, FunctionId::new(0))
2859                .verify()
2860                .expect("module fixture verifies"),
2861            edges,
2862            bindings,
2863            exports,
2864        }
2865    }
2866
2867    fn linked(modules: Vec<ProgramModule<Verified>>, entry: u32) -> Program<Verified> {
2868        Program::link(modules, ModuleId::new(entry)).expect("module fixture links")
2869    }
2870
2871    fn dynamic_cycle_program() -> Program<Verified> {
2872        let root = program_module(
2873            "root",
2874            vec![Constant::String(EcmaString::from_utf8("./target"))],
2875            vec![entry_function(
2876                3,
2877                vec![
2878                    Instruction::Import {
2879                        dst: reg(0),
2880                        specifier: cid(1),
2881                    },
2882                    Instruction::Import {
2883                        dst: reg(1),
2884                        specifier: cid(1),
2885                    },
2886                    Instruction::Binary {
2887                        dst: reg(2),
2888                        op: BinaryOp::StrictEqual,
2889                        left: reg(0),
2890                        right: reg(1),
2891                    },
2892                    Instruction::Return { value: reg(2) },
2893                ],
2894            )],
2895            vec![Edge {
2896                specifier: cid(1),
2897                target: EdgeTarget::Local(ModuleId::new(1)),
2898                kind: EdgeKind::Dynamic,
2899            }],
2900            Vec::new(),
2901            Vec::new(),
2902        );
2903        let target = program_module(
2904            "target",
2905            vec![Constant::String(EcmaString::from_utf8("./root"))],
2906            vec![entry_function(1, vec![Instruction::Halt])],
2907            vec![Edge {
2908                specifier: cid(1),
2909                target: EdgeTarget::Local(ModuleId::new(0)),
2910                kind: EdgeKind::Static,
2911            }],
2912            Vec::new(),
2913            Vec::new(),
2914        );
2915        linked(vec![root, target], 0)
2916    }
2917
2918    fn assert_program_parity(
2919        program: &Program<Verified>,
2920    ) -> Result<crate::Execution, RuntimeError> {
2921        let limits = Limits::default();
2922        let mut interpreter_host = SilentHost;
2923        let interpreter = Machine::new(program, &mut interpreter_host, limits.clone()).run();
2924        let mut native_host = SilentHost;
2925        let entries = NoEntries;
2926        let native = NativeEngine::new(program, &entries, &mut native_host, limits).run();
2927        assert_eq!(interpreter, native);
2928        native
2929    }
2930
2931    /// A dummy entry table for the reference backend, which never invokes it.
2932    struct NoEntries;
2933
2934    impl NativeEntryTable for NoEntries {
2935        fn program_bytes(&self) -> &[u8] {
2936            &[]
2937        }
2938
2939        fn invoke(
2940            &self,
2941            module_id: u32,
2942            function_id: u32,
2943            _frame: &mut ShadowFrame,
2944            _out: &mut Completion,
2945        ) -> Result<CompletionTag, AbiError> {
2946            Err(AbiError::UnknownFunction {
2947                module_id,
2948                function_id,
2949            })
2950        }
2951    }
2952
2953    #[test]
2954    fn microtask_checkpoint_matches_interpreter_and_native_engine() {
2955        let program = linked(
2956            vec![program_module(
2957                "root",
2958                vec![
2959                    Constant::String(EcmaString::from_utf8("queueMicrotask")),
2960                    Constant::String(EcmaString::from_utf8("observed")),
2961                    Constant::Int32(7),
2962                    Constant::Undefined,
2963                ],
2964                vec![
2965                    entry_function(
2966                        6,
2967                        vec![
2968                            Instruction::CreateArray { dst: reg(0) },
2969                            Instruction::CreateClosure {
2970                                dst: reg(1),
2971                                function: FunctionId::new(1),
2972                                captures: reg(0),
2973                            },
2974                            Instruction::LoadGlobal {
2975                                dst: reg(2),
2976                                name: cid(1),
2977                            },
2978                            Instruction::CreateArray { dst: reg(3) },
2979                            Instruction::ArrayPush {
2980                                array: reg(3),
2981                                value: reg(1),
2982                            },
2983                            Instruction::LoadConst {
2984                                dst: reg(4),
2985                                constant: cid(4),
2986                            },
2987                            Instruction::Call {
2988                                dst: reg(5),
2989                                callee: reg(2),
2990                                this_value: reg(4),
2991                                arguments: reg(3),
2992                            },
2993                            Instruction::Return { value: reg(4) },
2994                        ],
2995                    ),
2996                    module_function(
2997                        0,
2998                        1,
2999                        vec![
3000                            Instruction::LoadConst {
3001                                dst: reg(0),
3002                                constant: cid(3),
3003                            },
3004                            Instruction::StoreGlobal {
3005                                name: cid(2),
3006                                value: reg(0),
3007                            },
3008                            Instruction::Return { value: reg(0) },
3009                        ],
3010                    ),
3011                ],
3012                Vec::new(),
3013                Vec::new(),
3014                Vec::new(),
3015            )],
3016            0,
3017        );
3018        let observed = EcmaString::from_utf8("observed");
3019
3020        let mut interpreter_host = SilentHost;
3021        let mut interpreter = Machine::new(&program, &mut interpreter_host, Limits::default());
3022        interpreter.evaluate().unwrap();
3023        assert!(!interpreter.globals.contains_key(&observed));
3024        let interpreter_drain = interpreter.drain_microtasks().unwrap();
3025        let interpreter_value = interpreter.globals.get(&observed).copied();
3026
3027        let mut native_host = SilentHost;
3028        let engine = NativeEngine::new(&program, &NoEntries, &mut native_host, Limits::default());
3029        engine.machine.borrow_mut().instantiate_modules().unwrap();
3030        engine
3031            .evaluate_reference_module(program.entry())
3032            .unwrap()
3033            .expect("native entry completes");
3034        assert!(!engine.machine.borrow().globals.contains_key(&observed));
3035        let native_drain = engine.machine.borrow_mut().drain_microtasks().unwrap();
3036        let native_value = engine.machine.borrow().globals.get(&observed).copied();
3037
3038        assert_eq!(interpreter_drain, native_drain);
3039        assert_eq!(interpreter_value, Some(Value::int32(7)));
3040        assert_eq!(native_value, interpreter_value);
3041    }
3042
3043    #[derive(Default)]
3044    struct RecordingHost {
3045        stdout: Vec<u8>,
3046    }
3047
3048    impl Host for RecordingHost {
3049        fn write_stdout(&mut self, bytes: &[u8]) {
3050            self.stdout.extend_from_slice(bytes);
3051        }
3052    }
3053
3054    fn first_window(haystack: &[u8], needle: &[u8]) -> Option<usize> {
3055        haystack.windows(needle.len()).position(|w| w == needle)
3056    }
3057
3058    fn automatic_loop_program(callback: Function) -> Program<Verified> {
3059        linked(
3060            vec![program_module(
3061                "root",
3062                vec![
3063                    Constant::String(EcmaString::from_utf8("console")),
3064                    Constant::String(EcmaString::from_utf8("log")),
3065                    Constant::String(EcmaString::from_utf8("sync")),
3066                    Constant::String(EcmaString::from_utf8("async")),
3067                    Constant::String(EcmaString::from_utf8("queueMicrotask")),
3068                    Constant::Int32(42),
3069                    Constant::Undefined,
3070                ],
3071                vec![
3072                    entry_function(
3073                        7,
3074                        vec![
3075                            Instruction::LoadGlobal { dst: reg(0), name: cid(1) },
3076                            Instruction::LoadConst { dst: reg(2), constant: cid(3) },
3077                            Instruction::LoadConst { dst: reg(1), constant: cid(2) },
3078                            Instruction::GetProperty { dst: reg(1), object: reg(0), key: reg(1) },
3079                            Instruction::CreateArray { dst: reg(3) },
3080                            Instruction::ArrayPush { array: reg(3), value: reg(2) },
3081                            Instruction::LoadConst { dst: reg(4), constant: cid(7) },
3082                            Instruction::Call {
3083                                dst: reg(5),
3084                                callee: reg(1),
3085                                this_value: reg(4),
3086                                arguments: reg(3),
3087                            },
3088                            Instruction::CreateArray { dst: reg(3) },
3089                            Instruction::CreateClosure {
3090                                dst: reg(1),
3091                                function: FunctionId::new(1),
3092                                captures: reg(3),
3093                            },
3094                            Instruction::LoadGlobal { dst: reg(6), name: cid(5) },
3095                            Instruction::CreateArray { dst: reg(3) },
3096                            Instruction::ArrayPush { array: reg(3), value: reg(1) },
3097                            Instruction::LoadConst { dst: reg(4), constant: cid(7) },
3098                            Instruction::Call {
3099                                dst: reg(5),
3100                                callee: reg(6),
3101                                this_value: reg(4),
3102                                arguments: reg(3),
3103                            },
3104                            Instruction::LoadConst { dst: reg(0), constant: cid(6) },
3105                            Instruction::Return { value: reg(0) },
3106                        ],
3107                    ),
3108                    callback,
3109                ],
3110                Vec::new(),
3111                Vec::new(),
3112                Vec::new(),
3113            )],
3114            0,
3115        )
3116    }
3117
3118    fn queue_callback_microtask<H: Host>(
3119        engine: &mut NativeEngine<'_, '_, H>,
3120        function: FunctionId,
3121    ) {
3122        let mut machine = engine.machine.borrow_mut();
3123        let callback = machine
3124            .allocate(HeapEntry::Function {
3125                module: ModuleId::new(0),
3126                function,
3127                captures: Vec::new(),
3128                properties: PropertyMap::default(),
3129                prototype: Some(machine.intrinsics.function_prototype),
3130                extensible: true,
3131            })
3132            .expect("microtask callback allocates");
3133        let queue = machine
3134            .intrinsics
3135            .global("queueMicrotask")
3136            .expect("queueMicrotask is installed");
3137        machine
3138            .call_value(queue, Value::UNDEFINED, &[callback])
3139            .expect("microtask enqueues");
3140    }
3141
3142    #[test]
3143    fn automatic_loop_drains_microtasks_and_preserves_synchronous_result() {
3144        let program = automatic_loop_program(module_function(
3145            0,
3146            4,
3147            vec![
3148                Instruction::LoadGlobal { dst: reg(0), name: cid(1) },
3149                Instruction::LoadConst { dst: reg(2), constant: cid(4) },
3150                Instruction::LoadConst { dst: reg(1), constant: cid(2) },
3151                Instruction::GetProperty { dst: reg(1), object: reg(0), key: reg(1) },
3152                Instruction::CreateArray { dst: reg(3) },
3153                Instruction::ArrayPush { array: reg(3), value: reg(2) },
3154                Instruction::LoadConst { dst: reg(0), constant: cid(7) },
3155                Instruction::Call {
3156                    dst: reg(2),
3157                    callee: reg(1),
3158                    this_value: reg(0),
3159                    arguments: reg(3),
3160                },
3161                Instruction::Return { value: reg(0) },
3162            ],
3163        ));
3164
3165        let mut interpreter_host = RecordingHost::default();
3166        let interpreter = Machine::new(&program, &mut interpreter_host, Limits::default())
3167            .run()
3168            .expect("interpreter drains to quiescence");
3169        assert_eq!(interpreter.value, Value::int32(42));
3170        assert!(interpreter.outcome.stdout.is_empty());
3171        let interp_sync = first_window(&interpreter_host.stdout, b"sync");
3172        let interp_async = first_window(&interpreter_host.stdout, b"async");
3173        assert!(
3174            interp_sync.is_some_and(|s| interp_async.is_some_and(|a| s < a)),
3175            "host received sync-then-async bytes: {:?}",
3176            interpreter_host.stdout,
3177        );
3178
3179        let mut native_host = RecordingHost::default();
3180        let native = NativeEngine::new(&program, &NoEntries, &mut native_host, Limits::default())
3181            .run()
3182            .expect("reference native drains to quiescence");
3183        assert_eq!(native.value, Value::int32(42));
3184        assert!(native.outcome.stdout.is_empty());
3185        assert_eq!(native_host.stdout, interpreter_host.stdout);
3186
3187        let mut linked_host = RecordingHost::default();
3188        let linked_outcome = {
3189            let entries = ForeignEntries {
3190                program_bytes: program.encode(),
3191                invoked: Cell::new(false),
3192            };
3193            let mut linked = NativeEngine::build(
3194                &program,
3195                &entries,
3196                &mut linked_host,
3197                Limits::default(),
3198                Backend::Linked,
3199            );
3200            queue_callback_microtask(&mut linked, FunctionId::new(1));
3201            linked
3202                .run_linked()
3203                .expect("linked native drains to quiescence")
3204        };
3205        assert!(linked_outcome.stdout.is_empty());
3206        assert!(
3207            first_window(&linked_host.stdout, b"async").is_some(),
3208            "linked loop drained the pre-queued microtask: {:?}",
3209            linked_host.stdout,
3210        );
3211    }
3212
3213    #[test]
3214    fn automatic_loop_surfaces_uncaught_callback_error_across_entrypoints() {
3215        let program = automatic_loop_program(module_function(
3216            0,
3217            1,
3218            vec![
3219                Instruction::LoadConst { dst: reg(0), constant: cid(6) },
3220                Instruction::Throw { value: reg(0) },
3221            ],
3222        ));
3223
3224        let mut interpreter_host = SilentHost;
3225        let interpreter_err = Machine::new(&program, &mut interpreter_host, Limits::default())
3226            .run()
3227            .expect_err("interpreter surfaces the uncaught callback");
3228        let thrown = match &interpreter_err.kind {
3229            RuntimeErrorKind::UncaughtThrow { value, .. } => *value,
3230            other => panic!("expected UncaughtThrow, got {other:?}"),
3231        };
3232        assert_eq!(thrown, Value::int32(42));
3233
3234        let mut native_host = SilentHost;
3235        let native_err = NativeEngine::new(&program, &NoEntries, &mut native_host, Limits::default())
3236            .run()
3237            .expect_err("reference native surfaces the uncaught callback");
3238        assert_eq!(native_err.kind, interpreter_err.kind);
3239
3240        let mut linked_host = SilentHost;
3241        let linked_err = {
3242            let entries = ForeignEntries {
3243                program_bytes: program.encode(),
3244                invoked: Cell::new(false),
3245            };
3246            let mut linked = NativeEngine::build(
3247                &program,
3248                &entries,
3249                &mut linked_host,
3250                Limits::default(),
3251                Backend::Linked,
3252            );
3253            queue_callback_microtask(&mut linked, FunctionId::new(1));
3254            linked
3255                .run_linked()
3256                .expect_err("linked native surfaces the uncaught callback")
3257        };
3258        match linked_err {
3259            NativeError::Runtime(RuntimeError { ref kind, .. }) if *kind == interpreter_err.kind => {}
3260            other => panic!("linked mapped the callback throw: {other:?}"),
3261        }
3262    }
3263
3264    fn async_module_function(registers: u32, code: Vec<Instruction>) -> Function {
3265        Function::new(
3266            None,
3267            0,
3268            0,
3269            registers,
3270            FunctionFlags {
3271                is_async: true,
3272                is_generator: false,
3273            },
3274            code,
3275            Vec::new(),
3276        )
3277    }
3278
3279    /// An ordinary async call routed through the native reference engine must
3280    /// produce the same Promise settlement, globals, and drain counts as the
3281    /// interpreter, because native async execution reuses the shared Machine
3282    /// reference state machine.
3283    #[test]
3284    fn async_call_matches_interpreter_and_native_engine() {
3285        let program = linked(
3286            vec![program_module(
3287                "root",
3288                vec![
3289                    Constant::String(EcmaString::from_utf8("observed")),
3290                    Constant::Int32(7),
3291                    Constant::Undefined,
3292                ],
3293                vec![
3294                    entry_function(
3295                        5,
3296                        vec![
3297                            Instruction::CreateArray { dst: reg(0) },
3298                            Instruction::CreateClosure {
3299                                dst: reg(1),
3300                                function: FunctionId::new(1),
3301                                captures: reg(0),
3302                            },
3303                            Instruction::CreateArray { dst: reg(2) },
3304                            Instruction::LoadConst {
3305                                dst: reg(3),
3306                                constant: cid(3),
3307                            },
3308                            Instruction::Call {
3309                                dst: reg(4),
3310                                callee: reg(1),
3311                                this_value: reg(3),
3312                                arguments: reg(2),
3313                            },
3314                            Instruction::Return { value: reg(3) },
3315                        ],
3316                    ),
3317                    async_module_function(
3318                        2,
3319                        vec![
3320                            Instruction::LoadConst {
3321                                dst: reg(0),
3322                                constant: cid(2),
3323                            },
3324                            Instruction::Suspend {
3325                                dst: reg(1),
3326                                src: reg(0),
3327                                resume: pc(2),
3328                            },
3329                            Instruction::StoreGlobal {
3330                                name: cid(1),
3331                                value: reg(1),
3332                            },
3333                            Instruction::Return { value: reg(1) },
3334                        ],
3335                    ),
3336                ],
3337                Vec::new(),
3338                Vec::new(),
3339                Vec::new(),
3340            )],
3341            0,
3342        );
3343        let observed = EcmaString::from_utf8("observed");
3344
3345        let mut interpreter_host = SilentHost;
3346        let mut interpreter = Machine::new(&program, &mut interpreter_host, Limits::default());
3347        interpreter.evaluate().unwrap();
3348        assert!(!interpreter.globals.contains_key(&observed));
3349        let interpreter_drain = interpreter.drain_microtasks().unwrap();
3350        let interpreter_value = interpreter.globals.get(&observed).copied();
3351
3352        let mut native_host = SilentHost;
3353        let engine = NativeEngine::new(&program, &NoEntries, &mut native_host, Limits::default());
3354        engine.machine.borrow_mut().instantiate_modules().unwrap();
3355        engine
3356            .evaluate_reference_module(program.entry())
3357            .unwrap()
3358            .expect("native entry completes");
3359        assert!(!engine.machine.borrow().globals.contains_key(&observed));
3360        let native_drain = engine.machine.borrow_mut().drain_microtasks().unwrap();
3361        let native_value = engine.machine.borrow().globals.get(&observed).copied();
3362
3363        let mut linked_host = SilentHost;
3364        let linked = NativeEngine::build(
3365            &program,
3366            &NoEntries,
3367            &mut linked_host,
3368            Limits::default(),
3369            Backend::Linked,
3370        );
3371        linked.machine.borrow_mut().instantiate_modules().unwrap();
3372        assert!(matches!(
3373            linked.invoke_runtime(
3374                crate::RuntimeFunction {
3375                    module: ModuleId::new(0),
3376                    function: FunctionId::new(1),
3377                },
3378                &[],
3379                Value::UNDEFINED,
3380                Value::UNDEFINED,
3381                &[],
3382            ),
3383            InvokeOutcome::Value(_)
3384        ));
3385        assert!(!linked.machine.borrow().globals.contains_key(&observed));
3386        let linked_drain = linked.machine.borrow_mut().drain_microtasks().unwrap();
3387        let linked_value = linked.machine.borrow().globals.get(&observed).copied();
3388
3389        assert_eq!(interpreter_drain, native_drain);
3390        assert_eq!(interpreter_value, Some(Value::int32(7)));
3391        assert_eq!(native_value, interpreter_value);
3392        assert_eq!(linked_drain, interpreter_drain);
3393        assert_eq!(linked_value, interpreter_value);
3394    }
3395
3396    /// Constructing an async function through the native engine is a TypeError,
3397    /// matching the interpreter's construct guard, with no bytecode/ABI change.
3398    #[test]
3399    fn native_construct_of_async_function_is_a_type_error() {
3400        let program = linked(
3401            vec![program_module(
3402                "root",
3403                Vec::new(),
3404                vec![
3405                    entry_function(
3406                        4,
3407                        vec![
3408                            Instruction::CreateArray { dst: reg(0) },
3409                            Instruction::CreateClosure {
3410                                dst: reg(1),
3411                                function: FunctionId::new(1),
3412                                captures: reg(0),
3413                            },
3414                            Instruction::CreateArray { dst: reg(2) },
3415                            Instruction::Construct {
3416                                dst: reg(3),
3417                                callee: reg(1),
3418                                arguments: reg(2),
3419                            },
3420                            Instruction::Return { value: reg(3) },
3421                        ],
3422                    ),
3423                    async_module_function(1, vec![Instruction::Halt]),
3424                ],
3425                Vec::new(),
3426                Vec::new(),
3427                Vec::new(),
3428            )],
3429            0,
3430        );
3431        let mut host = SilentHost;
3432        let result = NativeEngine::new(&program, &NoEntries, &mut host, Limits::default()).run();
3433        assert!(matches!(
3434            result,
3435            Err(RuntimeError {
3436                kind: RuntimeErrorKind::UncaughtThrow {
3437                    origin: ThrowOrigin::TypeError { .. },
3438                    ..
3439                },
3440                ..
3441            })
3442        ));
3443    }
3444
3445    #[derive(Default)]
3446    struct RecordingEntries {
3447        program_bytes: Vec<u8>,
3448        invoked: RefCell<Vec<(u32, u32)>>,
3449    }
3450
3451    impl NativeEntryTable for RecordingEntries {
3452        fn program_bytes(&self) -> &[u8] {
3453            &self.program_bytes
3454        }
3455
3456        fn invoke(
3457            &self,
3458            module_id: u32,
3459            function_id: u32,
3460            _frame: &mut ShadowFrame,
3461            out: &mut Completion,
3462        ) -> Result<CompletionTag, AbiError> {
3463            self.invoked.borrow_mut().push((module_id, function_id));
3464            *out = Completion::new(Value::UNDEFINED);
3465            Ok(CompletionTag::Normal)
3466        }
3467    }
3468
3469    struct ForeignEntries {
3470        program_bytes: Vec<u8>,
3471        invoked: Cell<bool>,
3472    }
3473
3474    impl NativeEntryTable for ForeignEntries {
3475        fn program_bytes(&self) -> &[u8] {
3476            &self.program_bytes
3477        }
3478
3479        fn invoke(
3480            &self,
3481            _module_id: u32,
3482            _function_id: u32,
3483            _frame: &mut ShadowFrame,
3484            out: &mut Completion,
3485        ) -> Result<CompletionTag, AbiError> {
3486            self.invoked.set(true);
3487            *out = Completion::new(Value::UNDEFINED);
3488            Ok(CompletionTag::Normal)
3489        }
3490    }
3491
3492    struct SmokeEntries {
3493        program_bytes: Vec<u8>,
3494        invoked: Cell<Option<u32>>,
3495    }
3496
3497    impl NativeEntryTable for SmokeEntries {
3498        fn program_bytes(&self) -> &[u8] {
3499            &self.program_bytes
3500        }
3501
3502        fn invoke(
3503            &self,
3504            module_id: u32,
3505            function_id: u32,
3506            _frame: &mut ShadowFrame,
3507            out: &mut Completion,
3508        ) -> Result<CompletionTag, AbiError> {
3509            assert_eq!(module_id, 0);
3510            self.invoked.set(Some(function_id));
3511            *out = Completion::new(Value::UNDEFINED);
3512            Ok(CompletionTag::Normal)
3513        }
3514    }
3515
3516    #[derive(Default)]
3517    struct FailingEntries {
3518        program_bytes: Vec<u8>,
3519    }
3520
3521    impl NativeEntryTable for FailingEntries {
3522        fn program_bytes(&self) -> &[u8] {
3523            &self.program_bytes
3524        }
3525
3526        fn invoke(
3527            &self,
3528            module_id: u32,
3529            function_id: u32,
3530            _frame: &mut ShadowFrame,
3531            _out: &mut Completion,
3532        ) -> Result<CompletionTag, AbiError> {
3533            Err(AbiError::UnknownFunction {
3534                module_id,
3535                function_id,
3536            })
3537        }
3538    }
3539
3540    struct ThrowEntries;
3541
3542    impl NativeEntryTable for ThrowEntries {
3543        fn program_bytes(&self) -> &[u8] {
3544            &[]
3545        }
3546
3547        fn invoke(
3548            &self,
3549            _module_id: u32,
3550            _function_id: u32,
3551            _frame: &mut ShadowFrame,
3552            out: &mut Completion,
3553        ) -> Result<CompletionTag, AbiError> {
3554            *out = Completion::new(Value::UNDEFINED);
3555            Ok(CompletionTag::Throw)
3556        }
3557    }
3558
3559    struct FatalEntries;
3560
3561    impl NativeEntryTable for FatalEntries {
3562        fn program_bytes(&self) -> &[u8] {
3563            &[]
3564        }
3565
3566        fn invoke(
3567            &self,
3568            _module_id: u32,
3569            _function_id: u32,
3570            _frame: &mut ShadowFrame,
3571            out: &mut Completion,
3572        ) -> Result<CompletionTag, AbiError> {
3573            *out = Completion::new(Value::UNDEFINED);
3574            Ok(CompletionTag::FatalTrap)
3575        }
3576    }
3577
3578    #[test]
3579    fn native_program_keeps_same_name_globals_module_local() {
3580        let dependency = |name: &str, value: i32| {
3581            program_module(
3582                name,
3583                vec![
3584                    Constant::String(EcmaString::from_utf8("x")),
3585                    Constant::Int32(value),
3586                ],
3587                vec![module_function(
3588                    0,
3589                    1,
3590                    vec![
3591                        Instruction::LoadConst {
3592                            dst: reg(0),
3593                            constant: cid(2),
3594                        },
3595                        Instruction::StoreGlobal {
3596                            name: cid(1),
3597                            value: reg(0),
3598                        },
3599                        Instruction::Return { value: reg(0) },
3600                    ],
3601                )],
3602                Vec::new(),
3603                vec![Binding {
3604                    name: cid(1),
3605                    kind: BindingKind::Hoisted,
3606                }],
3607                vec![Export {
3608                    name: cid(1),
3609                    source: ExportSource::Local(BindingId::new(0)),
3610                }],
3611            )
3612        };
3613        let root = program_module(
3614            "root",
3615            vec![
3616                Constant::String(EcmaString::from_utf8("left")),
3617                Constant::String(EcmaString::from_utf8("right")),
3618                Constant::String(EcmaString::from_utf8("x")),
3619                Constant::String(EcmaString::from_utf8("one")),
3620                Constant::String(EcmaString::from_utf8("two")),
3621            ],
3622            vec![module_function(
3623                0,
3624                3,
3625                vec![
3626                    Instruction::LoadGlobal {
3627                        dst: reg(0),
3628                        name: cid(1),
3629                    },
3630                    Instruction::LoadGlobal {
3631                        dst: reg(1),
3632                        name: cid(2),
3633                    },
3634                    Instruction::Binary {
3635                        dst: reg(2),
3636                        op: BinaryOp::Add,
3637                        left: reg(0),
3638                        right: reg(1),
3639                    },
3640                    Instruction::Return { value: reg(2) },
3641                ],
3642            )],
3643            vec![
3644                Edge {
3645                    specifier: cid(3),
3646                    target: EdgeTarget::Local(ModuleId::new(0)),
3647                    kind: EdgeKind::Static,
3648                },
3649                Edge {
3650                    specifier: cid(4),
3651                    target: EdgeTarget::Local(ModuleId::new(1)),
3652                    kind: EdgeKind::Static,
3653                },
3654            ],
3655            vec![
3656                Binding {
3657                    name: cid(1),
3658                    kind: BindingKind::Imported {
3659                        edge: EdgeId::new(0),
3660                        name: cid(3),
3661                    },
3662                },
3663                Binding {
3664                    name: cid(2),
3665                    kind: BindingKind::Imported {
3666                        edge: EdgeId::new(1),
3667                        name: cid(3),
3668                    },
3669                },
3670            ],
3671            Vec::new(),
3672        );
3673        let execution = assert_program_parity(&linked(
3674            vec![dependency("one", 1), dependency("two", 2), root],
3675            2,
3676        ))
3677        .unwrap();
3678        assert_eq!(execution.value, Value::int32(3));
3679    }
3680
3681    #[test]
3682    fn native_program_preserves_live_mutation_and_nested_closure_module() {
3683        let dependency = program_module(
3684            "dependency",
3685            vec![
3686                Constant::String(EcmaString::from_utf8("x")),
3687                Constant::Int32(1),
3688                Constant::Int32(2),
3689                Constant::String(EcmaString::from_utf8("set")),
3690            ],
3691            vec![
3692                module_function(
3693                    0,
3694                    3,
3695                    vec![
3696                        Instruction::LoadConst {
3697                            dst: reg(0),
3698                            constant: cid(2),
3699                        },
3700                        Instruction::StoreGlobal {
3701                            name: cid(1),
3702                            value: reg(0),
3703                        },
3704                        Instruction::CreateArray { dst: reg(1) },
3705                        Instruction::CreateClosure {
3706                            dst: reg(2),
3707                            function: FunctionId::new(1),
3708                            captures: reg(1),
3709                        },
3710                        Instruction::StoreGlobal {
3711                            name: cid(4),
3712                            value: reg(2),
3713                        },
3714                        Instruction::Return { value: reg(0) },
3715                    ],
3716                ),
3717                module_function(
3718                    0,
3719                    1,
3720                    vec![
3721                        Instruction::LoadConst {
3722                            dst: reg(0),
3723                            constant: cid(3),
3724                        },
3725                        Instruction::StoreGlobal {
3726                            name: cid(1),
3727                            value: reg(0),
3728                        },
3729                        Instruction::Return { value: reg(0) },
3730                    ],
3731                ),
3732            ],
3733            Vec::new(),
3734            vec![
3735                Binding {
3736                    name: cid(1),
3737                    kind: BindingKind::Hoisted,
3738                },
3739                Binding {
3740                    name: cid(4),
3741                    kind: BindingKind::Hoisted,
3742                },
3743            ],
3744            vec![
3745                Export {
3746                    name: cid(1),
3747                    source: ExportSource::Local(BindingId::new(0)),
3748                },
3749                Export {
3750                    name: cid(4),
3751                    source: ExportSource::Local(BindingId::new(1)),
3752                },
3753            ],
3754        );
3755        let root = program_module(
3756            "root",
3757            vec![
3758                Constant::String(EcmaString::from_utf8("set")),
3759                Constant::String(EcmaString::from_utf8("x")),
3760                Constant::String(EcmaString::from_utf8("dependency")),
3761            ],
3762            vec![module_function(
3763                0,
3764                3,
3765                vec![
3766                    Instruction::LoadGlobal {
3767                        dst: reg(0),
3768                        name: cid(1),
3769                    },
3770                    Instruction::CreateArray { dst: reg(1) },
3771                    Instruction::Call {
3772                        dst: reg(2),
3773                        callee: reg(0),
3774                        this_value: reg(1),
3775                        arguments: reg(1),
3776                    },
3777                    Instruction::LoadGlobal {
3778                        dst: reg(0),
3779                        name: cid(2),
3780                    },
3781                    Instruction::Return { value: reg(0) },
3782                ],
3783            )],
3784            vec![Edge {
3785                specifier: cid(3),
3786                target: EdgeTarget::Local(ModuleId::new(0)),
3787                kind: EdgeKind::Static,
3788            }],
3789            vec![
3790                Binding {
3791                    name: cid(1),
3792                    kind: BindingKind::Imported {
3793                        edge: EdgeId::new(0),
3794                        name: cid(1),
3795                    },
3796                },
3797                Binding {
3798                    name: cid(2),
3799                    kind: BindingKind::Imported {
3800                        edge: EdgeId::new(0),
3801                        name: cid(2),
3802                    },
3803                },
3804            ],
3805            Vec::new(),
3806        );
3807        assert_eq!(
3808            assert_program_parity(&linked(vec![dependency, root], 1))
3809                .unwrap()
3810                .value,
3811            Value::int32(2)
3812        );
3813    }
3814
3815    #[test]
3816    fn native_program_cycle_observes_temporal_dead_zone() {
3817        let first = program_module(
3818            "first",
3819            vec![
3820                Constant::String(EcmaString::from_utf8("a")),
3821                Constant::Int32(1),
3822                Constant::String(EcmaString::from_utf8("second")),
3823            ],
3824            vec![module_function(
3825                0,
3826                1,
3827                vec![
3828                    Instruction::LoadConst {
3829                        dst: reg(0),
3830                        constant: cid(2),
3831                    },
3832                    Instruction::StoreGlobal {
3833                        name: cid(1),
3834                        value: reg(0),
3835                    },
3836                    Instruction::Return { value: reg(0) },
3837                ],
3838            )],
3839            vec![Edge {
3840                specifier: cid(3),
3841                target: EdgeTarget::Local(ModuleId::new(1)),
3842                kind: EdgeKind::Static,
3843            }],
3844            vec![Binding {
3845                name: cid(1),
3846                kind: BindingKind::Lexical,
3847            }],
3848            vec![Export {
3849                name: cid(1),
3850                source: ExportSource::Local(BindingId::new(0)),
3851            }],
3852        );
3853        let second = program_module(
3854            "second",
3855            vec![
3856                Constant::String(EcmaString::from_utf8("a")),
3857                Constant::String(EcmaString::from_utf8("first")),
3858            ],
3859            vec![module_function(
3860                0,
3861                1,
3862                vec![
3863                    Instruction::LoadGlobal {
3864                        dst: reg(0),
3865                        name: cid(1),
3866                    },
3867                    Instruction::Return { value: reg(0) },
3868                ],
3869            )],
3870            vec![Edge {
3871                specifier: cid(2),
3872                target: EdgeTarget::Local(ModuleId::new(0)),
3873                kind: EdgeKind::Static,
3874            }],
3875            vec![Binding {
3876                name: cid(1),
3877                kind: BindingKind::Imported {
3878                    edge: EdgeId::new(0),
3879                    name: cid(1),
3880                },
3881            }],
3882            Vec::new(),
3883        );
3884        let error = assert_program_parity(&linked(vec![first, second], 0)).unwrap_err();
3885        assert!(matches!(
3886            error.kind,
3887            RuntimeErrorKind::TemporalDeadZone { module, binding }
3888                if module == ModuleId::new(1) && binding == BindingId::new(0)
3889        ));
3890    }
3891
3892    #[test]
3893    fn native_program_namespace_reads_shared_export_cell() {
3894        let dependency = program_module(
3895            "dependency",
3896            vec![
3897                Constant::String(EcmaString::from_utf8("x")),
3898                Constant::Int32(7),
3899            ],
3900            vec![module_function(
3901                0,
3902                1,
3903                vec![
3904                    Instruction::LoadConst {
3905                        dst: reg(0),
3906                        constant: cid(2),
3907                    },
3908                    Instruction::StoreGlobal {
3909                        name: cid(1),
3910                        value: reg(0),
3911                    },
3912                    Instruction::Return { value: reg(0) },
3913                ],
3914            )],
3915            Vec::new(),
3916            vec![Binding {
3917                name: cid(1),
3918                kind: BindingKind::Hoisted,
3919            }],
3920            vec![Export {
3921                name: cid(1),
3922                source: ExportSource::Local(BindingId::new(0)),
3923            }],
3924        );
3925        let root = program_module(
3926            "root",
3927            vec![
3928                Constant::String(EcmaString::from_utf8("ns")),
3929                Constant::String(EcmaString::from_utf8("x")),
3930                Constant::String(EcmaString::from_utf8("dependency")),
3931            ],
3932            vec![module_function(
3933                0,
3934                3,
3935                vec![
3936                    Instruction::LoadGlobal {
3937                        dst: reg(0),
3938                        name: cid(1),
3939                    },
3940                    Instruction::LoadConst {
3941                        dst: reg(1),
3942                        constant: cid(2),
3943                    },
3944                    Instruction::GetProperty {
3945                        dst: reg(2),
3946                        object: reg(0),
3947                        key: reg(1),
3948                    },
3949                    Instruction::Return { value: reg(2) },
3950                ],
3951            )],
3952            vec![Edge {
3953                specifier: cid(3),
3954                target: EdgeTarget::Local(ModuleId::new(0)),
3955                kind: EdgeKind::Static,
3956            }],
3957            vec![Binding {
3958                name: cid(1),
3959                kind: BindingKind::Namespace {
3960                    edge: EdgeId::new(0),
3961                },
3962            }],
3963            Vec::new(),
3964        );
3965        assert_eq!(
3966            assert_program_parity(&linked(vec![dependency, root], 1))
3967                .unwrap()
3968                .value,
3969            Value::int32(7)
3970        );
3971    }
3972
3973    #[test]
3974    fn native_program_evaluates_duplicate_static_dependency_once() {
3975        let dependency = program_module(
3976            "dependency",
3977            vec![
3978                Constant::String(EcmaString::from_utf8("count")),
3979                Constant::Int32(0),
3980                Constant::Int32(1),
3981            ],
3982            vec![module_function(
3983                0,
3984                2,
3985                vec![
3986                    Instruction::LoadGlobal {
3987                        dst: reg(0),
3988                        name: cid(1),
3989                    },
3990                    Instruction::JumpIfFalse {
3991                        condition: reg(0),
3992                        target: pc(4),
3993                    },
3994                    Instruction::LoadConst {
3995                        dst: reg(1),
3996                        constant: cid(3),
3997                    },
3998                    Instruction::Jump { target: pc(6) },
3999                    Instruction::LoadConst {
4000                        dst: reg(0),
4001                        constant: cid(2),
4002                    },
4003                    Instruction::LoadConst {
4004                        dst: reg(1),
4005                        constant: cid(3),
4006                    },
4007                    Instruction::Binary {
4008                        dst: reg(0),
4009                        op: BinaryOp::Add,
4010                        left: reg(0),
4011                        right: reg(1),
4012                    },
4013                    Instruction::StoreGlobal {
4014                        name: cid(1),
4015                        value: reg(0),
4016                    },
4017                    Instruction::Return { value: reg(0) },
4018                ],
4019            )],
4020            Vec::new(),
4021            vec![Binding {
4022                name: cid(1),
4023                kind: BindingKind::Hoisted,
4024            }],
4025            vec![Export {
4026                name: cid(1),
4027                source: ExportSource::Local(BindingId::new(0)),
4028            }],
4029        );
4030        let root = program_module(
4031            "root",
4032            vec![
4033                Constant::String(EcmaString::from_utf8("count")),
4034                Constant::String(EcmaString::from_utf8("dependency")),
4035                Constant::String(EcmaString::from_utf8("dependency-again")),
4036            ],
4037            vec![module_function(
4038                0,
4039                1,
4040                vec![
4041                    Instruction::LoadGlobal {
4042                        dst: reg(0),
4043                        name: cid(1),
4044                    },
4045                    Instruction::Return { value: reg(0) },
4046                ],
4047            )],
4048            vec![
4049                Edge {
4050                    specifier: cid(2),
4051                    target: EdgeTarget::Local(ModuleId::new(0)),
4052                    kind: EdgeKind::Static,
4053                },
4054                Edge {
4055                    specifier: cid(3),
4056                    target: EdgeTarget::Local(ModuleId::new(0)),
4057                    kind: EdgeKind::Static,
4058                },
4059            ],
4060            vec![Binding {
4061                name: cid(1),
4062                kind: BindingKind::Imported {
4063                    edge: EdgeId::new(0),
4064                    name: cid(1),
4065                },
4066            }],
4067            Vec::new(),
4068        );
4069        assert_eq!(
4070            assert_program_parity(&linked(vec![dependency, root], 1))
4071                .unwrap()
4072                .value,
4073            Value::int32(1)
4074        );
4075    }
4076
4077    #[test]
4078    fn native_program_memoizes_thrown_object_identity() {
4079        let module = program_module(
4080            "throws",
4081            Vec::new(),
4082            vec![module_function(
4083                0,
4084                1,
4085                vec![
4086                    Instruction::CreateObject { dst: reg(0) },
4087                    Instruction::Throw { value: reg(0) },
4088                ],
4089            )],
4090            Vec::new(),
4091            Vec::new(),
4092            Vec::new(),
4093        );
4094        let error = assert_program_parity(&linked(vec![module], 0)).unwrap_err();
4095        assert!(matches!(error.kind, RuntimeErrorKind::UncaughtThrow { .. }));
4096    }
4097
4098    struct SilentHost;
4099    impl Host for SilentHost {}
4100
4101    #[test]
4102    fn native_matches_interpreter_on_arithmetic() {
4103        let module = verified(
4104            vec![Constant::Int32(3), Constant::Int32(4)],
4105            vec![entry_function(
4106                2,
4107                vec![
4108                    Instruction::LoadConst {
4109                        dst: reg(0),
4110                        constant: cid(0),
4111                    },
4112                    Instruction::LoadConst {
4113                        dst: reg(1),
4114                        constant: cid(1),
4115                    },
4116                    Instruction::Binary {
4117                        dst: reg(0),
4118                        op: BinaryOp::Add,
4119                        left: reg(0),
4120                        right: reg(1),
4121                    },
4122                    Instruction::Return { value: reg(0) },
4123                ],
4124            )],
4125        );
4126        let value = assert_parity(&module, || SilentHost);
4127        assert_eq!(value.as_int32(), Some(7));
4128    }
4129
4130    #[test]
4131    fn reference_and_interpreter_charge_each_mixed_instruction_once() {
4132        let module = verified(
4133            vec![Constant::Int32(1)],
4134            vec![entry_function(
4135                3,
4136                vec![
4137                    Instruction::LoadConst {
4138                        dst: reg(0),
4139                        constant: cid(0),
4140                    },
4141                    Instruction::Move {
4142                        dst: reg(1),
4143                        src: reg(0),
4144                    },
4145                    Instruction::Binary {
4146                        dst: reg(2),
4147                        op: BinaryOp::Add,
4148                        left: reg(0),
4149                        right: reg(1),
4150                    },
4151                    Instruction::Jump { target: pc(4) },
4152                    Instruction::Halt,
4153                ],
4154            )],
4155        );
4156        let program = one_module_program(&module);
4157
4158        for fuel in [0, 4, 5] {
4159            let limits = Limits {
4160                fuel,
4161                ..Limits::default()
4162            };
4163            let mut interpreter_host = SilentHost;
4164            let interpreter = Machine::new(&program, &mut interpreter_host, limits.clone()).run();
4165            let mut reference_host = SilentHost;
4166            let reference =
4167                NativeEngine::new(&program, &NoEntries, &mut reference_host, limits).run();
4168            assert_eq!(interpreter, reference, "fuel={fuel}");
4169            if fuel == 5 {
4170                assert!(reference.is_ok(), "N instructions must fit fuel N");
4171            } else {
4172                assert!(
4173                    matches!(
4174                        reference,
4175                        Err(RuntimeError {
4176                            kind: RuntimeErrorKind::FuelExhausted { limit },
4177                            ..
4178                        }) if limit == fuel
4179                    ),
4180                    "fuel={fuel} must exhaust before the next instruction"
4181                );
4182            }
4183        }
4184    }
4185
4186    #[test]
4187    fn native_reservations_share_interpreter_depth_and_register_ceilings() {
4188        let module = verified(Vec::new(), vec![entry_function(2, vec![Instruction::Halt])]);
4189        let program = one_module_program(&module);
4190        let mut host = SilentHost;
4191        let mut machine = Machine::new(
4192            &program,
4193            &mut host,
4194            Limits {
4195                max_call_depth: 2,
4196                max_total_registers: 3,
4197                ..Limits::default()
4198            },
4199        );
4200
4201        assert_eq!(machine.frames.len(), 1);
4202        assert_eq!(machine.live_registers, 2);
4203        machine.reserve_native_activation(1).unwrap();
4204        assert_eq!((machine.native_depth, machine.live_registers), (1, 3));
4205        assert!(matches!(
4206            machine.reserve_native_activation(0),
4207            Err(RuntimeErrorKind::CallDepthExceeded { limit: 2 })
4208        ));
4209        assert_eq!(
4210            (machine.native_depth, machine.live_registers),
4211            (1, 3),
4212            "failed depth reservation is atomic"
4213        );
4214        machine.release_native_activation(1);
4215
4216        machine.limits.max_call_depth = 3;
4217        machine.limits.max_total_registers = 2;
4218        assert!(matches!(
4219            machine.reserve_native_activation(1),
4220            Err(RuntimeErrorKind::RegisterLimitExceeded { limit: 2 })
4221        ));
4222        assert_eq!(
4223            (machine.native_depth, machine.live_registers),
4224            (0, 2),
4225            "failed register reservation is atomic"
4226        );
4227    }
4228
4229    #[test]
4230    fn native_matches_interpreter_on_loop_and_branches() {
4231        // acc=0; for i in 1..4 { acc += i } -> 6, exercising Binary compare,
4232        // JumpIfFalse, and Jump through the native driver's own control flow.
4233        let module = verified(
4234            vec![Constant::Int32(0), Constant::Int32(1), Constant::Int32(4)],
4235            vec![entry_function(
4236                5,
4237                vec![
4238                    Instruction::LoadConst {
4239                        dst: reg(0),
4240                        constant: cid(0),
4241                    },
4242                    Instruction::LoadConst {
4243                        dst: reg(1),
4244                        constant: cid(1),
4245                    },
4246                    Instruction::LoadConst {
4247                        dst: reg(2),
4248                        constant: cid(2),
4249                    },
4250                    Instruction::LoadConst {
4251                        dst: reg(3),
4252                        constant: cid(1),
4253                    },
4254                    Instruction::Binary {
4255                        dst: reg(4),
4256                        op: BinaryOp::LessThan,
4257                        left: reg(1),
4258                        right: reg(2),
4259                    },
4260                    Instruction::JumpIfFalse {
4261                        condition: reg(4),
4262                        target: pc(9),
4263                    },
4264                    Instruction::Binary {
4265                        dst: reg(0),
4266                        op: BinaryOp::Add,
4267                        left: reg(0),
4268                        right: reg(1),
4269                    },
4270                    Instruction::Binary {
4271                        dst: reg(1),
4272                        op: BinaryOp::Add,
4273                        left: reg(1),
4274                        right: reg(3),
4275                    },
4276                    Instruction::Jump { target: pc(4) },
4277                    Instruction::Return { value: reg(0) },
4278                ],
4279            )],
4280        );
4281        let value = assert_parity(&module, || SilentHost);
4282        assert_eq!(value.as_int32(), Some(6));
4283    }
4284
4285    #[test]
4286    fn native_matches_interpreter_on_object_property_roundtrip() {
4287        let module = verified(
4288            vec![
4289                Constant::String(EcmaString::from_utf8("x")),
4290                Constant::Int32(5),
4291            ],
4292            vec![entry_function(
4293                3,
4294                vec![
4295                    Instruction::CreateObject { dst: reg(0) },
4296                    Instruction::LoadConst {
4297                        dst: reg(1),
4298                        constant: cid(0),
4299                    },
4300                    Instruction::LoadConst {
4301                        dst: reg(2),
4302                        constant: cid(1),
4303                    },
4304                    Instruction::SetProperty {
4305                        object: reg(0),
4306                        key: reg(1),
4307                        value: reg(2),
4308                    },
4309                    Instruction::GetProperty {
4310                        dst: reg(2),
4311                        object: reg(0),
4312                        key: reg(1),
4313                    },
4314                    Instruction::Return { value: reg(2) },
4315                ],
4316            )],
4317        );
4318        let value = assert_parity(&module, || SilentHost);
4319        assert_eq!(value.as_int32(), Some(5));
4320    }
4321
4322    #[test]
4323    fn native_matches_interpreter_on_closure_call() {
4324        // fn0: build empty captures, close over fn1, call it. fn1: return 42.
4325        let entry = entry_function(
4326            3,
4327            vec![
4328                Instruction::CreateArray { dst: reg(0) },
4329                Instruction::CreateClosure {
4330                    dst: reg(1),
4331                    function: FunctionId::new(1),
4332                    captures: reg(0),
4333                },
4334                Instruction::CreateArray { dst: reg(0) },
4335                Instruction::LoadConst {
4336                    dst: reg(2),
4337                    constant: cid(0),
4338                },
4339                Instruction::Call {
4340                    dst: reg(0),
4341                    callee: reg(1),
4342                    this_value: reg(2),
4343                    arguments: reg(0),
4344                },
4345                Instruction::Return { value: reg(0) },
4346            ],
4347        );
4348        let callee = Function::new(
4349            None,
4350            0,
4351            0,
4352            1,
4353            FunctionFlags::default(),
4354            vec![
4355                Instruction::LoadConst {
4356                    dst: reg(0),
4357                    constant: cid(1),
4358                },
4359                Instruction::Return { value: reg(0) },
4360            ],
4361            Vec::new(),
4362        );
4363        let module = verified(
4364            vec![Constant::Undefined, Constant::Int32(42)],
4365            vec![entry, callee],
4366        );
4367        let value = assert_parity(&module, || SilentHost);
4368        assert_eq!(value.as_int32(), Some(42));
4369    }
4370
4371    #[test]
4372    fn native_matches_interpreter_on_throw_and_catch() {
4373        let module = verified(
4374            vec![Constant::Int32(99)],
4375            vec![Function::new(
4376                None,
4377                0,
4378                0,
4379                2,
4380                FunctionFlags::default(),
4381                vec![
4382                    Instruction::LoadConst {
4383                        dst: reg(0),
4384                        constant: cid(0),
4385                    },
4386                    Instruction::Throw { value: reg(0) },
4387                    Instruction::Return { value: reg(1) },
4388                ],
4389                vec![ExceptionHandler {
4390                    start: pc(0),
4391                    end: pc(2),
4392                    handler: pc(2),
4393                    catch_register: reg(1),
4394                }],
4395            )],
4396        );
4397        let value = assert_parity(&module, || SilentHost);
4398        assert_eq!(value.as_int32(), Some(99));
4399    }
4400
4401    fn one_module_program(module: &Module<Verified>) -> Program<Verified> {
4402        let mut constants = module.constants().to_vec();
4403        let name = ConstantId::new(constants.len() as u32);
4404        constants.push(Constant::String(EcmaString::from_utf8("test-module")));
4405        let code = Module::new(constants, module.functions().to_vec(), module.entry())
4406            .verify()
4407            .expect("test module remains verified");
4408        Program::link(
4409            vec![ProgramModule {
4410                name,
4411                code,
4412                edges: Vec::new(),
4413                bindings: Vec::new(),
4414                exports: Vec::new(),
4415            }],
4416            ModuleId::new(0),
4417        )
4418        .expect("one-module reference program links")
4419    }
4420
4421    fn assert_parity<H: Host, F: Fn() -> H>(module: &Module<Verified>, make_host: F) -> Value {
4422        let limits = Limits::default();
4423        let program = one_module_program(module);
4424
4425        let mut interp_host = make_host();
4426        let interpreter = Machine::new(&program, &mut interp_host, limits.clone())
4427            .run()
4428            .expect("interpreter runs");
4429
4430        let mut native_host = make_host();
4431        let entries = NoEntries;
4432        let native = NativeEngine::new(&program, &entries, &mut native_host, limits)
4433            .run()
4434            .expect("native engine runs");
4435
4436        assert_eq!(
4437            interpreter.value, native.value,
4438            "return value parity: interpreter {:?} vs native {:?}",
4439            interpreter.value, native.value
4440        );
4441        assert_eq!(
4442            interpreter.outcome, native.outcome,
4443            "outcome parity: interpreter {:?} vs native {:?}",
4444            interpreter.outcome, native.outcome
4445        );
4446        assert_eq!(
4447            interpreter.entry_registers, native.entry_registers,
4448            "entry register parity"
4449        );
4450        native.value
4451    }
4452
4453    fn trivial_program() -> Program<Verified> {
4454        let code = verified(
4455            vec![Constant::String(EcmaString::from_utf8("<test>"))],
4456            vec![entry_function(1, vec![Instruction::Halt])],
4457        );
4458        Program::link(
4459            vec![ProgramModule {
4460                name: ConstantId::new(0),
4461                code,
4462                edges: Vec::new(),
4463                bindings: Vec::new(),
4464                exports: Vec::new(),
4465            }],
4466            ModuleId::new(0),
4467        )
4468        .expect("valid native test program")
4469    }
4470
4471    #[test]
4472    fn linked_backend_accepts_metadata_empty_single_module_program() {
4473        let program = trivial_program();
4474        let entries = SmokeEntries {
4475            program_bytes: program.encode(),
4476            invoked: Cell::new(None),
4477        };
4478        let mut host = SilentHost;
4479        let outcome = run_linked_program(&program, &entries, &mut host, &Limits::default())
4480            .expect("linked program runs");
4481        assert_eq!(entries.invoked.get(), Some(0), "entry function 0 invoked");
4482        assert_eq!(outcome.exit_code, 0);
4483        assert!(outcome.stdout.is_empty());
4484    }
4485
4486    #[test]
4487    fn linked_backend_propagates_abi_error() {
4488        let program = trivial_program();
4489        let entries = FailingEntries {
4490            program_bytes: program.encode(),
4491        };
4492        let mut host = SilentHost;
4493        let error = run_linked_program(&program, &entries, &mut host, &Limits::default())
4494            .expect_err("entry table failure surfaces");
4495        assert!(matches!(
4496            error,
4497            NativeError::Abi(AbiError::UnknownFunction {
4498                module_id: 0,
4499                function_id: 0
4500            })
4501        ));
4502    }
4503
4504    #[test]
4505    fn linked_backend_rejects_entries_from_same_shape_different_program_before_invocation() {
4506        let program = |constant| {
4507            linked(
4508                vec![program_module(
4509                    "entry",
4510                    vec![Constant::Int32(constant)],
4511                    vec![entry_function(
4512                        1,
4513                        vec![
4514                            Instruction::LoadConst {
4515                                dst: reg(0),
4516                                constant: cid(1),
4517                            },
4518                            Instruction::Return { value: reg(0) },
4519                        ],
4520                    )],
4521                    Vec::new(),
4522                    Vec::new(),
4523                    Vec::new(),
4524                )],
4525                0,
4526            )
4527        };
4528        let compiled_program = program(1);
4529        let supplied_program = program(2);
4530        let entries = ForeignEntries {
4531            program_bytes: compiled_program.encode(),
4532            invoked: Cell::new(false),
4533        };
4534        let mut host = SilentHost;
4535
4536        assert_eq!(
4537            run_linked_program(&supplied_program, &entries, &mut host, &Limits::default()),
4538            Err(NativeError::ProgramMismatch)
4539        );
4540        assert!(
4541            !entries.invoked.get(),
4542            "mismatched entries must not be invoked"
4543        );
4544    }
4545
4546    #[test]
4547    fn linked_backend_invokes_static_dependencies_before_entry_by_tuple() {
4548        let single = trivial_program();
4549        let first = single.modules()[0].clone();
4550        let second = program_module(
4551            "entry",
4552            vec![Constant::String(EcmaString::from_utf8("dependency"))],
4553            vec![entry_function(1, vec![Instruction::Halt])],
4554            vec![Edge {
4555                specifier: cid(1),
4556                target: EdgeTarget::Local(ModuleId::new(0)),
4557                kind: EdgeKind::Static,
4558            }],
4559            Vec::new(),
4560            Vec::new(),
4561        );
4562        let program = Program::link(vec![first, second], ModuleId::new(1)).unwrap();
4563        let entries = RecordingEntries {
4564            program_bytes: program.encode(),
4565            ..RecordingEntries::default()
4566        };
4567        let mut host = SilentHost;
4568        run_linked_program(&program, &entries, &mut host, &Limits::default()).unwrap();
4569        assert_eq!(entries.invoked.borrow().as_slice(), &[(0, 0), (1, 0)]);
4570    }
4571
4572    #[test]
4573    fn dynamic_import_cycle_matches_the_reference_backend() {
4574        let execution = assert_program_parity(&dynamic_cycle_program()).unwrap();
4575        assert_eq!(execution.value, Value::TRUE);
4576        assert_eq!(execution.entry_registers[0], execution.entry_registers[1]);
4577    }
4578
4579    #[test]
4580    fn dynamic_import_throw_is_caught_at_the_requester_in_both_backends() {
4581        let root = program_module(
4582            "root",
4583            vec![Constant::String(EcmaString::from_utf8("./target"))],
4584            vec![Function::new(
4585                None,
4586                0,
4587                0,
4588                2,
4589                FunctionFlags::default(),
4590                vec![
4591                    Instruction::Import {
4592                        dst: reg(0),
4593                        specifier: cid(1),
4594                    },
4595                    Instruction::Halt,
4596                    Instruction::Return { value: reg(1) },
4597                ],
4598                vec![ExceptionHandler {
4599                    start: pc(0),
4600                    end: pc(1),
4601                    handler: pc(2),
4602                    catch_register: reg(1),
4603                }],
4604            )],
4605            vec![Edge {
4606                specifier: cid(1),
4607                target: EdgeTarget::Local(ModuleId::new(1)),
4608                kind: EdgeKind::Dynamic,
4609            }],
4610            Vec::new(),
4611            Vec::new(),
4612        );
4613        let target = program_module(
4614            "target",
4615            vec![Constant::Int32(9)],
4616            vec![entry_function(
4617                1,
4618                vec![
4619                    Instruction::LoadConst {
4620                        dst: reg(0),
4621                        constant: cid(1),
4622                    },
4623                    Instruction::Throw { value: reg(0) },
4624                ],
4625            )],
4626            Vec::new(),
4627            Vec::new(),
4628            Vec::new(),
4629        );
4630
4631        assert_eq!(
4632            assert_program_parity(&linked(vec![root, target], 0))
4633                .unwrap()
4634                .value,
4635            Value::int32(9)
4636        );
4637    }
4638
4639    #[test]
4640    fn linked_dynamic_import_invokes_the_target_once() {
4641        let program = dynamic_cycle_program();
4642        let entries = RecordingEntries {
4643            program_bytes: program.encode(),
4644            ..RecordingEntries::default()
4645        };
4646        let mut host = SilentHost;
4647        let engine = NativeEngine::build(
4648            &program,
4649            &entries,
4650            &mut host,
4651            Limits::default(),
4652            Backend::Linked,
4653        );
4654        engine.machine.borrow_mut().instantiate_modules().unwrap();
4655        assert!(matches!(
4656            engine
4657                .machine
4658                .borrow_mut()
4659                .begin_module_evaluation(ModuleId::new(0))
4660                .unwrap(),
4661            crate::ModuleEvaluation::Ready(_)
4662        ));
4663
4664        let mut registers = [Value::UNINITIALIZED; 3];
4665        let handles = registers.as_mut_ptr();
4666        let mut shadow =
4667            ShadowFrame::new(std::ptr::null_mut(), 0, 0, handles, registers.len() as u16);
4668        let mut frame = NativeFrame::new(&mut shadow, &mut registers).unwrap();
4669        let first = engine.dispatch(&mut frame, HelperCall::Import { specifier: 1 });
4670        let second = engine.dispatch(&mut frame, HelperCall::Import { specifier: 1 });
4671
4672        assert_eq!(first.tag, CompletionTag::Normal);
4673        assert_eq!(second.tag, CompletionTag::Normal);
4674        assert_eq!(first.value, second.value);
4675        assert_eq!(entries.invoked.borrow().as_slice(), &[(1, 0)]);
4676    }
4677
4678    #[test]
4679    fn native_functions_call_and_construct_with_engine_parity() {
4680        let module = verified(
4681            vec![
4682                Constant::String(EcmaString::from_utf8("Object")),
4683                Constant::String(EcmaString::from_utf8("prototype")),
4684                Constant::String(EcmaString::from_utf8("toString")),
4685                Constant::String(EcmaString::from_utf8("call")),
4686                Constant::String(EcmaString::from_utf8("[object Object]")),
4687                Constant::Undefined,
4688            ],
4689            vec![entry_function(
4690                8,
4691                vec![
4692                    Instruction::LoadGlobal {
4693                        dst: reg(0),
4694                        name: cid(0),
4695                    },
4696                    Instruction::CreateArray { dst: reg(1) },
4697                    Instruction::Construct {
4698                        dst: reg(2),
4699                        callee: reg(0),
4700                        arguments: reg(1),
4701                    },
4702                    Instruction::LoadConst {
4703                        dst: reg(3),
4704                        constant: cid(1),
4705                    },
4706                    Instruction::GetProperty {
4707                        dst: reg(4),
4708                        object: reg(0),
4709                        key: reg(3),
4710                    },
4711                    Instruction::LoadConst {
4712                        dst: reg(3),
4713                        constant: cid(2),
4714                    },
4715                    Instruction::GetProperty {
4716                        dst: reg(4),
4717                        object: reg(4),
4718                        key: reg(3),
4719                    },
4720                    Instruction::LoadConst {
4721                        dst: reg(3),
4722                        constant: cid(3),
4723                    },
4724                    Instruction::GetProperty {
4725                        dst: reg(5),
4726                        object: reg(4),
4727                        key: reg(3),
4728                    },
4729                    Instruction::CreateArray { dst: reg(6) },
4730                    Instruction::ArrayPush {
4731                        array: reg(6),
4732                        value: reg(2),
4733                    },
4734                    Instruction::Call {
4735                        dst: reg(5),
4736                        callee: reg(5),
4737                        this_value: reg(4),
4738                        arguments: reg(6),
4739                    },
4740                    Instruction::LoadConst {
4741                        dst: reg(6),
4742                        constant: cid(4),
4743                    },
4744                    Instruction::Binary {
4745                        dst: reg(7),
4746                        op: BinaryOp::StrictEqual,
4747                        left: reg(5),
4748                        right: reg(6),
4749                    },
4750                    Instruction::Return { value: reg(7) },
4751                ],
4752            )],
4753        );
4754        assert_eq!(assert_parity(&module, || SilentHost), Value::TRUE);
4755    }
4756
4757    #[test]
4758    fn builtin_iterators_match_between_engines() {
4759        let module = verified(
4760            vec![Constant::Int32(8)],
4761            vec![entry_function(
4762                5,
4763                vec![
4764                    Instruction::CreateArray { dst: reg(0) },
4765                    Instruction::LoadConst {
4766                        dst: reg(1),
4767                        constant: cid(0),
4768                    },
4769                    Instruction::ArrayPush {
4770                        array: reg(0),
4771                        value: reg(1),
4772                    },
4773                    Instruction::GetIterator {
4774                        dst: reg(2),
4775                        src: reg(0),
4776                        kind: IteratorKind::Sync,
4777                    },
4778                    Instruction::IteratorNext {
4779                        done: reg(3),
4780                        value: reg(4),
4781                        iterator: reg(2),
4782                    },
4783                    Instruction::Return { value: reg(4) },
4784                ],
4785            )],
4786        );
4787
4788        assert_eq!(assert_parity(&module, || SilentHost), Value::int32(8));
4789    }
4790
4791    #[test]
4792    fn bound_calls_match_between_engines() {
4793        let module = verified(
4794            vec![
4795                Constant::String(EcmaString::from_utf8("bind")),
4796                Constant::String(EcmaString::from_utf8("marker")),
4797                Constant::String(EcmaString::from_utf8("0")),
4798                Constant::String(EcmaString::from_utf8("1")),
4799                Constant::Int32(7),
4800                Constant::Int32(1),
4801                Constant::Int32(2),
4802            ],
4803            vec![
4804                entry_function(
4805                    8,
4806                    vec![
4807                        Instruction::CreateArray { dst: reg(0) },
4808                        Instruction::CreateClosure {
4809                            dst: reg(0),
4810                            function: FunctionId::new(1),
4811                            captures: reg(0),
4812                        },
4813                        Instruction::LoadConst {
4814                            dst: reg(1),
4815                            constant: cid(0),
4816                        },
4817                        Instruction::GetProperty {
4818                            dst: reg(2),
4819                            object: reg(0),
4820                            key: reg(1),
4821                        },
4822                        Instruction::CreateObject { dst: reg(3) },
4823                        Instruction::LoadConst {
4824                            dst: reg(4),
4825                            constant: cid(1),
4826                        },
4827                        Instruction::LoadConst {
4828                            dst: reg(6),
4829                            constant: cid(4),
4830                        },
4831                        Instruction::SetProperty {
4832                            object: reg(3),
4833                            key: reg(4),
4834                            value: reg(6),
4835                        },
4836                        Instruction::CreateArray { dst: reg(5) },
4837                        Instruction::ArrayPush {
4838                            array: reg(5),
4839                            value: reg(3),
4840                        },
4841                        Instruction::LoadConst {
4842                            dst: reg(6),
4843                            constant: cid(5),
4844                        },
4845                        Instruction::ArrayPush {
4846                            array: reg(5),
4847                            value: reg(6),
4848                        },
4849                        Instruction::Call {
4850                            dst: reg(7),
4851                            callee: reg(2),
4852                            this_value: reg(0),
4853                            arguments: reg(5),
4854                        },
4855                        Instruction::CreateArray { dst: reg(5) },
4856                        Instruction::LoadConst {
4857                            dst: reg(6),
4858                            constant: cid(6),
4859                        },
4860                        Instruction::ArrayPush {
4861                            array: reg(5),
4862                            value: reg(6),
4863                        },
4864                        Instruction::Call {
4865                            dst: reg(7),
4866                            callee: reg(7),
4867                            this_value: reg(3),
4868                            arguments: reg(5),
4869                        },
4870                        Instruction::Return { value: reg(7) },
4871                    ],
4872                ),
4873                receiver_sum_function(),
4874            ],
4875        );
4876
4877        assert_eq!(
4878            assert_parity(&module, || SilentHost),
4879            crate::number_value(10.0)
4880        );
4881    }
4882
4883    #[test]
4884    fn applied_calls_match_between_engines() {
4885        let module = verified(
4886            vec![
4887                Constant::String(EcmaString::from_utf8("apply")),
4888                Constant::String(EcmaString::from_utf8("marker")),
4889                Constant::String(EcmaString::from_utf8("0")),
4890                Constant::String(EcmaString::from_utf8("1")),
4891                Constant::Int32(7),
4892                Constant::Int32(1),
4893                Constant::Int32(2),
4894                Constant::String(EcmaString::from_utf8("length")),
4895                Constant::Undefined,
4896            ],
4897            vec![
4898                entry_function(
4899                    8,
4900                    vec![
4901                        Instruction::CreateArray { dst: reg(0) },
4902                        Instruction::CreateClosure {
4903                            dst: reg(0),
4904                            function: FunctionId::new(1),
4905                            captures: reg(0),
4906                        },
4907                        Instruction::LoadConst {
4908                            dst: reg(1),
4909                            constant: cid(0),
4910                        },
4911                        Instruction::GetProperty {
4912                            dst: reg(2),
4913                            object: reg(0),
4914                            key: reg(1),
4915                        },
4916                        Instruction::CreateObject { dst: reg(3) },
4917                        Instruction::LoadConst {
4918                            dst: reg(4),
4919                            constant: cid(1),
4920                        },
4921                        Instruction::LoadConst {
4922                            dst: reg(6),
4923                            constant: cid(4),
4924                        },
4925                        Instruction::SetProperty {
4926                            object: reg(3),
4927                            key: reg(4),
4928                            value: reg(6),
4929                        },
4930                        Instruction::CreateArray { dst: reg(5) },
4931                        Instruction::LoadConst {
4932                            dst: reg(6),
4933                            constant: cid(5),
4934                        },
4935                        Instruction::ArrayPush {
4936                            array: reg(5),
4937                            value: reg(6),
4938                        },
4939                        Instruction::LoadConst {
4940                            dst: reg(6),
4941                            constant: cid(6),
4942                        },
4943                        Instruction::ArrayPush {
4944                            array: reg(5),
4945                            value: reg(6),
4946                        },
4947                        Instruction::CreateArray { dst: reg(6) },
4948                        Instruction::ArrayPush {
4949                            array: reg(6),
4950                            value: reg(3),
4951                        },
4952                        Instruction::ArrayPush {
4953                            array: reg(6),
4954                            value: reg(5),
4955                        },
4956                        Instruction::Call {
4957                            dst: reg(7),
4958                            callee: reg(2),
4959                            this_value: reg(0),
4960                            arguments: reg(6),
4961                        },
4962                        Instruction::CreateArray { dst: reg(0) },
4963                        Instruction::CreateClosure {
4964                            dst: reg(0),
4965                            function: FunctionId::new(2),
4966                            captures: reg(0),
4967                        },
4968                        Instruction::GetProperty {
4969                            dst: reg(2),
4970                            object: reg(0),
4971                            key: reg(1),
4972                        },
4973                        Instruction::CreateArray { dst: reg(6) },
4974                        Instruction::ArrayPush {
4975                            array: reg(6),
4976                            value: reg(3),
4977                        },
4978                        Instruction::LoadConst {
4979                            dst: reg(4),
4980                            constant: cid(8),
4981                        },
4982                        Instruction::ArrayPush {
4983                            array: reg(6),
4984                            value: reg(4),
4985                        },
4986                        Instruction::Call {
4987                            dst: reg(4),
4988                            callee: reg(2),
4989                            this_value: reg(0),
4990                            arguments: reg(6),
4991                        },
4992                        Instruction::Binary {
4993                            dst: reg(7),
4994                            op: BinaryOp::Add,
4995                            left: reg(7),
4996                            right: reg(4),
4997                        },
4998                        Instruction::Return { value: reg(7) },
4999                    ],
5000                ),
5001                receiver_sum_function(),
5002                module_function(
5003                    0,
5004                    3,
5005                    vec![
5006                        Instruction::LoadArguments { dst: reg(0) },
5007                        Instruction::LoadConst {
5008                            dst: reg(1),
5009                            constant: cid(7),
5010                        },
5011                        Instruction::GetProperty {
5012                            dst: reg(2),
5013                            object: reg(0),
5014                            key: reg(1),
5015                        },
5016                        Instruction::Return { value: reg(2) },
5017                    ],
5018                ),
5019            ],
5020        );
5021
5022        assert_eq!(
5023            assert_parity(&module, || SilentHost),
5024            crate::number_value(10.0)
5025        );
5026    }
5027
5028    #[test]
5029    fn bound_construction_matches_between_engines() {
5030        let module = verified(
5031            vec![
5032                Constant::String(EcmaString::from_utf8("bind")),
5033                Constant::String(EcmaString::from_utf8("prototype")),
5034                Constant::String(EcmaString::from_utf8("sum")),
5035                Constant::String(EcmaString::from_utf8("0")),
5036                Constant::String(EcmaString::from_utf8("1")),
5037                Constant::Int32(4),
5038                Constant::Int32(5),
5039                Constant::Int32(9),
5040                Constant::Undefined,
5041            ],
5042            vec![
5043                entry_function(
5044                    10,
5045                    vec![
5046                        Instruction::CreateArray { dst: reg(0) },
5047                        Instruction::CreateClosure {
5048                            dst: reg(0),
5049                            function: FunctionId::new(1),
5050                            captures: reg(0),
5051                        },
5052                        Instruction::CreateObject { dst: reg(2) },
5053                        Instruction::LoadConst {
5054                            dst: reg(1),
5055                            constant: cid(1),
5056                        },
5057                        Instruction::SetProperty {
5058                            object: reg(0),
5059                            key: reg(1),
5060                            value: reg(2),
5061                        },
5062                        Instruction::LoadConst {
5063                            dst: reg(1),
5064                            constant: cid(0),
5065                        },
5066                        Instruction::GetProperty {
5067                            dst: reg(3),
5068                            object: reg(0),
5069                            key: reg(1),
5070                        },
5071                        Instruction::CreateObject { dst: reg(4) },
5072                        Instruction::CreateArray { dst: reg(5) },
5073                        Instruction::ArrayPush {
5074                            array: reg(5),
5075                            value: reg(4),
5076                        },
5077                        Instruction::LoadConst {
5078                            dst: reg(6),
5079                            constant: cid(5),
5080                        },
5081                        Instruction::ArrayPush {
5082                            array: reg(5),
5083                            value: reg(6),
5084                        },
5085                        Instruction::Call {
5086                            dst: reg(7),
5087                            callee: reg(3),
5088                            this_value: reg(0),
5089                            arguments: reg(5),
5090                        },
5091                        Instruction::CreateArray { dst: reg(5) },
5092                        Instruction::LoadConst {
5093                            dst: reg(6),
5094                            constant: cid(6),
5095                        },
5096                        Instruction::ArrayPush {
5097                            array: reg(5),
5098                            value: reg(6),
5099                        },
5100                        Instruction::Construct {
5101                            dst: reg(8),
5102                            callee: reg(7),
5103                            arguments: reg(5),
5104                        },
5105                        Instruction::LoadConst {
5106                            dst: reg(1),
5107                            constant: cid(2),
5108                        },
5109                        Instruction::GetProperty {
5110                            dst: reg(9),
5111                            object: reg(8),
5112                            key: reg(1),
5113                        },
5114                        Instruction::LoadConst {
5115                            dst: reg(6),
5116                            constant: cid(7),
5117                        },
5118                        Instruction::Binary {
5119                            dst: reg(9),
5120                            op: BinaryOp::StrictEqual,
5121                            left: reg(9),
5122                            right: reg(6),
5123                        },
5124                        Instruction::Binary {
5125                            dst: reg(6),
5126                            op: BinaryOp::InstanceOf,
5127                            left: reg(8),
5128                            right: reg(7),
5129                        },
5130                        Instruction::Binary {
5131                            dst: reg(9),
5132                            op: BinaryOp::BitAnd,
5133                            left: reg(9),
5134                            right: reg(6),
5135                        },
5136                        Instruction::Return { value: reg(9) },
5137                    ],
5138                ),
5139                module_function(
5140                    0,
5141                    6,
5142                    vec![
5143                        Instruction::LoadThis { dst: reg(0) },
5144                        Instruction::LoadArguments { dst: reg(1) },
5145                        Instruction::LoadConst {
5146                            dst: reg(2),
5147                            constant: cid(3),
5148                        },
5149                        Instruction::GetProperty {
5150                            dst: reg(3),
5151                            object: reg(1),
5152                            key: reg(2),
5153                        },
5154                        Instruction::LoadConst {
5155                            dst: reg(2),
5156                            constant: cid(4),
5157                        },
5158                        Instruction::GetProperty {
5159                            dst: reg(4),
5160                            object: reg(1),
5161                            key: reg(2),
5162                        },
5163                        Instruction::Binary {
5164                            dst: reg(3),
5165                            op: BinaryOp::Add,
5166                            left: reg(3),
5167                            right: reg(4),
5168                        },
5169                        Instruction::LoadConst {
5170                            dst: reg(2),
5171                            constant: cid(2),
5172                        },
5173                        Instruction::SetProperty {
5174                            object: reg(0),
5175                            key: reg(2),
5176                            value: reg(3),
5177                        },
5178                        Instruction::LoadConst {
5179                            dst: reg(5),
5180                            constant: cid(8),
5181                        },
5182                        Instruction::Return { value: reg(5) },
5183                    ],
5184                ),
5185            ],
5186        );
5187
5188        assert_eq!(assert_parity(&module, || SilentHost), Value::int32(1));
5189    }
5190
5191    #[test]
5192    fn linked_entry_preserves_pending_throw_origin() {
5193        let program = trivial_program();
5194        let mut host = SilentHost;
5195        let mut engine = NativeEngine::build(
5196            &program,
5197            &ThrowEntries,
5198            &mut host,
5199            Limits::default(),
5200            Backend::Linked,
5201        );
5202        engine.pending_throw.set(Some(PendingThrow {
5203            value: Value::UNDEFINED,
5204            origin: ThrowOrigin::ReferenceError {
5205                operation: "fixture",
5206            },
5207        }));
5208        let error = engine.run_linked().unwrap_err();
5209        assert!(matches!(
5210            error,
5211            NativeError::Runtime(RuntimeError {
5212                kind: RuntimeErrorKind::UncaughtThrow {
5213                    origin: ThrowOrigin::ReferenceError {
5214                        operation: "fixture"
5215                    },
5216                    ..
5217                },
5218                ..
5219            })
5220        ));
5221    }
5222
5223    #[test]
5224    fn stale_pending_throw_cannot_replace_a_new_bytecode_throw() {
5225        let program = trivial_program();
5226        let mut host = SilentHost;
5227        let engine = NativeEngine::build(
5228            &program,
5229            &NoEntries,
5230            &mut host,
5231            Limits::default(),
5232            Backend::Linked,
5233        );
5234        engine.pending_throw.set(Some(PendingThrow {
5235            value: Value::int32(1),
5236            origin: ThrowOrigin::ReferenceError { operation: "stale" },
5237        }));
5238
5239        assert_eq!(
5240            engine.take_matching_throw(Value::int32(2)),
5241            (Value::int32(2), ThrowOrigin::Bytecode)
5242        );
5243        assert!(engine.pending_throw.get().is_none());
5244    }
5245
5246    #[test]
5247    fn linked_entry_sources_pending_runtime_fatal_kind() {
5248        let program = trivial_program();
5249        let mut host = SilentHost;
5250        let mut engine = NativeEngine::build(
5251            &program,
5252            &FatalEntries,
5253            &mut host,
5254            Limits::default(),
5255            Backend::Linked,
5256        );
5257        engine
5258            .pending_fatal_kind
5259            .set(Some(RuntimeErrorKind::InvalidValue { value: Value::NULL }));
5260        let error = engine.run_linked().unwrap_err();
5261        assert!(matches!(
5262            error,
5263            NativeError::Runtime(RuntimeError {
5264                kind: RuntimeErrorKind::InvalidValue { value },
5265                ..
5266            }) if value == Value::NULL
5267        ));
5268    }
5269
5270    #[test]
5271    fn nested_linked_unknown_tuple_remains_abi_error() {
5272        let module = verified(
5273            Vec::new(),
5274            vec![
5275                entry_function(1, vec![Instruction::Halt]),
5276                entry_function(1, vec![Instruction::Halt]),
5277            ],
5278        );
5279        let program = one_module_program(&module);
5280        let entries = FailingEntries::default();
5281        let mut host = SilentHost;
5282        let engine = NativeEngine::build(
5283            &program,
5284            &entries,
5285            &mut host,
5286            Limits::default(),
5287            Backend::Linked,
5288        );
5289        let outcome = engine.invoke_runtime(
5290            crate::RuntimeFunction {
5291                module: ModuleId::new(0),
5292                function: FunctionId::new(1),
5293            },
5294            &[],
5295            Value::UNDEFINED,
5296            Value::UNDEFINED,
5297            &[],
5298        );
5299        assert!(matches!(outcome, InvokeOutcome::Fatal));
5300        assert!(matches!(
5301            engine.pending_abi_error.take(),
5302            Some(AbiError::UnknownFunction {
5303                module_id: 0,
5304                function_id: 1
5305            })
5306        ));
5307    }
5308
5309    #[test]
5310    fn linked_abi_failure_leaves_module_retryable() {
5311        let program = trivial_program();
5312        let entries = FailingEntries::default();
5313        let mut host = SilentHost;
5314        let mut engine = NativeEngine::build(
5315            &program,
5316            &entries,
5317            &mut host,
5318            Limits::default(),
5319            Backend::Linked,
5320        );
5321        engine.machine.borrow_mut().instantiate_modules().unwrap();
5322        for _ in 0..2 {
5323            assert!(matches!(
5324                engine.evaluate_linked_module(ModuleId::new(0)),
5325                Err(NativeError::Abi(AbiError::UnknownFunction {
5326                    module_id: 0,
5327                    function_id: 0
5328                }))
5329            ));
5330        }
5331    }
5332
5333    #[test]
5334    fn linked_backend_routes_dynamic_targets_away_from_entry_table() {
5335        let root = one_module_program(&verified(
5336            Vec::new(),
5337            vec![entry_function(1, vec![Instruction::Halt])],
5338        ));
5339        let script = Arc::new(one_module_program(&verified(
5340            vec![Constant::Int32(42)],
5341            vec![entry_function(
5342                1,
5343                vec![
5344                    Instruction::LoadConst {
5345                        dst: reg(0),
5346                        constant: cid(0),
5347                    },
5348                    Instruction::Return { value: reg(0) },
5349                ],
5350            )],
5351        )));
5352        let entries = RecordingEntries::default();
5353        let mut host = SilentHost;
5354        let engine = NativeEngine::build(
5355            &root,
5356            &entries,
5357            &mut host,
5358            Limits::default(),
5359            Backend::Linked,
5360        );
5361        engine.machine.borrow_mut().instantiate_modules().unwrap();
5362        let module = engine
5363            .machine
5364            .borrow_mut()
5365            .install_script_reserving(script, 0, 0)
5366            .unwrap();
5367
5368        let outcome = engine.invoke_runtime(
5369            crate::RuntimeFunction {
5370                module,
5371                function: FunctionId::new(0),
5372            },
5373            &[],
5374            Value::UNDEFINED,
5375            Value::UNDEFINED,
5376            &[],
5377        );
5378        assert!(matches!(outcome, InvokeOutcome::Value(value) if value == Value::int32(42)));
5379        assert!(entries.invoked.borrow().is_empty());
5380    }
5381    fn generator_program(code: Vec<Instruction>, constants: Vec<Constant>) -> Program<Verified> {
5382        let generator = Function::new(
5383            None,
5384            0,
5385            0,
5386            3,
5387            FunctionFlags {
5388                is_async: false,
5389                is_generator: true,
5390            },
5391            code,
5392            Vec::new(),
5393        );
5394        one_module_program(&verified(
5395            constants,
5396            vec![entry_function(1, vec![Instruction::Halt]), generator],
5397        ))
5398    }
5399
5400    fn yielding_generator_program() -> Program<Verified> {
5401        generator_program(
5402            vec![
5403                Instruction::LoadConst {
5404                    dst: reg(0),
5405                    constant: cid(0),
5406                },
5407                Instruction::Suspend {
5408                    dst: reg(1),
5409                    src: reg(0),
5410                    resume: pc(2),
5411                },
5412                Instruction::LoadConst {
5413                    dst: reg(0),
5414                    constant: cid(1),
5415                },
5416                Instruction::Suspend {
5417                    dst: reg(1),
5418                    src: reg(0),
5419                    resume: pc(4),
5420                },
5421                Instruction::Return { value: reg(1) },
5422            ],
5423            vec![Constant::Int32(4), Constant::Int32(5)],
5424        )
5425    }
5426
5427    fn invoke_test_generator<H: Host>(engine: &NativeEngine<'_, '_, H>) -> Value {
5428        match engine.invoke_runtime(
5429            crate::RuntimeFunction {
5430                module: ModuleId::new(0),
5431                function: FunctionId::new(1),
5432            },
5433            &[],
5434            Value::UNDEFINED,
5435            Value::UNDEFINED,
5436            &[],
5437        ) {
5438            InvokeOutcome::Value(generator) => generator,
5439            _ => panic!("generator call must return its lazy generator object"),
5440        }
5441    }
5442
5443    fn assert_iterator_result<H: Host>(
5444        engine: &NativeEngine<'_, '_, H>,
5445        outcome: InvokeOutcome,
5446        expected_value: Value,
5447        expected_done: bool,
5448    ) {
5449        let result = match outcome {
5450            InvokeOutcome::Value(result) => result,
5451            _ => panic!("generator next must return an iterator result"),
5452        };
5453        let mut machine = engine.machine.borrow_mut();
5454        let value = machine
5455            .get_named_property(result, "value")
5456            .expect("iterator result has value");
5457        let done = machine
5458            .get_named_property(result, "done")
5459            .expect("iterator result has done");
5460        assert_eq!(value, expected_value);
5461        assert_eq!(done, Value::boolean(expected_done));
5462    }
5463
5464    #[test]
5465    fn reference_generator_is_lazy_yields_resumes_and_completes_stickily() {
5466        let program = yielding_generator_program();
5467        let mut host = SilentHost;
5468        let entries = NoEntries;
5469        let engine = NativeEngine::new(&program, &entries, &mut host, Limits::default());
5470        let generator = invoke_test_generator(&engine);
5471
5472        {
5473            let machine = engine.machine.borrow();
5474            let index = machine.runtime_slot(generator).unwrap().unwrap();
5475            assert!(matches!(
5476                &machine.heap[index],
5477                HeapEntry::Generator {
5478                    state: GeneratorState::SuspendedStart(_),
5479                    ..
5480                }
5481            ));
5482        }
5483
5484        assert_iterator_result(
5485            &engine,
5486            engine.resume_generator(generator, Value::int32(999)),
5487            Value::int32(4),
5488            false,
5489        );
5490        {
5491            let machine = engine.machine.borrow();
5492            let index = machine.runtime_slot(generator).unwrap().unwrap();
5493            let HeapEntry::Generator {
5494                state: GeneratorState::Suspended(activation),
5495                ..
5496            } = &machine.heap[index]
5497            else {
5498                panic!("generator must retain its suspended activation");
5499            };
5500            assert_eq!(activation.resume_token, 2);
5501            assert_eq!(activation.registers[0], Value::int32(4));
5502        }
5503
5504        assert_iterator_result(
5505            &engine,
5506            engine.resume_generator(generator, Value::int32(99)),
5507            Value::int32(5),
5508            false,
5509        );
5510        assert_iterator_result(
5511            &engine,
5512            engine.resume_generator(generator, Value::int32(7)),
5513            Value::int32(7),
5514            true,
5515        );
5516        assert_iterator_result(
5517            &engine,
5518            engine.resume_generator(generator, Value::int32(8)),
5519            Value::UNDEFINED,
5520            true,
5521        );
5522        let machine = engine.machine.borrow();
5523        assert_eq!(machine.live_registers, 0);
5524        assert_eq!(machine.native_depth, 0);
5525    }
5526
5527    #[test]
5528    fn reference_generator_throw_completes_and_preserves_value_and_origin() {
5529        let program = generator_program(
5530            vec![
5531                Instruction::LoadConst {
5532                    dst: reg(0),
5533                    constant: cid(0),
5534                },
5535                Instruction::Throw { value: reg(0) },
5536            ],
5537            vec![Constant::Int32(42)],
5538        );
5539        let mut host = SilentHost;
5540        let entries = NoEntries;
5541        let engine = NativeEngine::new(&program, &entries, &mut host, Limits::default());
5542        let generator = invoke_test_generator(&engine);
5543        assert!(matches!(
5544            engine.resume_generator(generator, Value::UNDEFINED),
5545            InvokeOutcome::Threw(value, ThrowOrigin::Bytecode) if value == Value::int32(42)
5546        ));
5547        assert_iterator_result(
5548            &engine,
5549            engine.resume_generator(generator, Value::UNDEFINED),
5550            Value::UNDEFINED,
5551            true,
5552        );
5553        let machine = engine.machine.borrow();
5554        assert_eq!(machine.live_registers, 0);
5555        assert_eq!(machine.native_depth, 0);
5556    }
5557
5558    #[test]
5559    fn suspended_reference_generator_registers_enforce_the_global_ceiling() {
5560        let program = yielding_generator_program();
5561        let mut host = SilentHost;
5562        let entries = NoEntries;
5563        let mut limits = Limits::default();
5564        limits.max_total_registers = 3;
5565        let engine = NativeEngine::new(&program, &entries, &mut host, limits);
5566        let first = invoke_test_generator(&engine);
5567        let second = invoke_test_generator(&engine);
5568        assert_iterator_result(
5569            &engine,
5570            engine.resume_generator(first, Value::UNDEFINED),
5571            Value::int32(4),
5572            false,
5573        );
5574        assert!(matches!(
5575            engine.resume_generator(second, Value::UNDEFINED),
5576            InvokeOutcome::Fatal
5577        ));
5578        assert!(matches!(
5579            engine.pending_fatal_kind.take(),
5580            Some(RuntimeErrorKind::RegisterLimitExceeded { limit: 3 })
5581        ));
5582    }
5583
5584    #[derive(Clone, Copy)]
5585    struct GeneratorEntryStep {
5586        token: u32,
5587        next_token: u32,
5588        tag: CompletionTag,
5589        value: Value,
5590    }
5591
5592    struct GeneratorEntries {
5593        steps: Vec<GeneratorEntryStep>,
5594        call: Cell<usize>,
5595        handles: Cell<Option<*mut Value>>,
5596    }
5597
5598    impl GeneratorEntries {
5599        fn yielding() -> Self {
5600            Self {
5601                steps: vec![
5602                    GeneratorEntryStep {
5603                        token: 0,
5604                        next_token: 2,
5605                        tag: CompletionTag::Suspend,
5606                        value: Value::int32(4),
5607                    },
5608                    GeneratorEntryStep {
5609                        token: 2,
5610                        next_token: 4,
5611                        tag: CompletionTag::Suspend,
5612                        value: Value::int32(5),
5613                    },
5614                    GeneratorEntryStep {
5615                        token: 4,
5616                        next_token: 4,
5617                        tag: CompletionTag::Normal,
5618                        value: Value::int32(7),
5619                    },
5620                ],
5621                call: Cell::new(0),
5622                handles: Cell::new(None),
5623            }
5624        }
5625
5626        fn terminal(tag: CompletionTag, value: Value) -> Self {
5627            Self {
5628                steps: vec![GeneratorEntryStep {
5629                    token: 0,
5630                    next_token: 1,
5631                    tag,
5632                    value,
5633                }],
5634                call: Cell::new(0),
5635                handles: Cell::new(None),
5636            }
5637        }
5638    }
5639
5640    impl NativeEntryTable for GeneratorEntries {
5641        fn program_bytes(&self) -> &[u8] {
5642            &[]
5643        }
5644
5645        fn invoke(
5646            &self,
5647            module_id: u32,
5648            function_id: u32,
5649            frame: &mut ShadowFrame,
5650            out: &mut Completion,
5651        ) -> Result<CompletionTag, AbiError> {
5652            assert_eq!(module_id, 0);
5653            assert_eq!(function_id, 1);
5654            let call = self.call.get();
5655            let step = self.steps[call];
5656            self.call.set(call + 1);
5657            assert_eq!(frame.bytecode_pc, step.token);
5658            match self.handles.get() {
5659                Some(handles) => assert_eq!(
5660                    handles, frame.handles,
5661                    "linked resumes must reuse the saved register allocation"
5662                ),
5663                None => self.handles.set(Some(frame.handles)),
5664            }
5665            frame.bytecode_pc = step.next_token;
5666            *out = Completion::new(step.value);
5667            Ok(step.tag)
5668        }
5669    }
5670
5671    #[test]
5672    fn linked_generator_reuses_saved_registers_and_dispatches_resume_tokens() {
5673        let program = yielding_generator_program();
5674        let entries = GeneratorEntries::yielding();
5675        let mut host = SilentHost;
5676        let engine = NativeEngine::build(
5677            &program,
5678            &entries,
5679            &mut host,
5680            Limits::default(),
5681            Backend::Linked,
5682        );
5683        let generator = invoke_test_generator(&engine);
5684        assert_eq!(entries.call.get(), 0, "generator call is lazy");
5685        assert_iterator_result(
5686            &engine,
5687            engine.resume_generator(generator, Value::int32(999)),
5688            Value::int32(4),
5689            false,
5690        );
5691        assert_iterator_result(
5692            &engine,
5693            engine.resume_generator(generator, Value::int32(99)),
5694            Value::int32(5),
5695            false,
5696        );
5697        assert_iterator_result(
5698            &engine,
5699            engine.resume_generator(generator, Value::int32(7)),
5700            Value::int32(7),
5701            true,
5702        );
5703        assert_iterator_result(
5704            &engine,
5705            engine.resume_generator(generator, Value::UNDEFINED),
5706            Value::UNDEFINED,
5707            true,
5708        );
5709        assert_eq!(entries.call.get(), 3);
5710        let machine = engine.machine.borrow();
5711        assert_eq!(machine.live_registers, 0);
5712        assert_eq!(machine.native_depth, 0);
5713    }
5714
5715    #[test]
5716    fn linked_resume_value_dispatch_consumes_the_sent_value_once() {
5717        let program = yielding_generator_program();
5718        let entries = NoEntries;
5719        let mut host = SilentHost;
5720        let engine = NativeEngine::build(
5721            &program,
5722            &entries,
5723            &mut host,
5724            Limits::default(),
5725            Backend::Linked,
5726        );
5727        engine.activations.borrow_mut().push(Activation {
5728            this_value: Value::UNDEFINED,
5729            new_target: Value::UNDEFINED,
5730            args: Vec::new(),
5731            arguments_object: None,
5732            pending_resume: Some(Value::int32(77)),
5733        });
5734        let mut registers = vec![Value::UNINITIALIZED];
5735        let mut shadow = ShadowFrame::new(std::ptr::null_mut(), 2, 0, registers.as_mut_ptr(), 1);
5736        let mut frame = NativeFrame::new(&mut shadow, &mut registers).unwrap();
5737        let resumed = engine.dispatch(&mut frame, HelperCall::ResumeValue);
5738        assert_eq!(resumed, HelperResult::normal(Value::int32(77)));
5739        assert_eq!(
5740            engine.dispatch(&mut frame, HelperCall::ResumeValue).tag,
5741            CompletionTag::FatalTrap
5742        );
5743        engine.activations.borrow_mut().pop();
5744    }
5745
5746    #[test]
5747    fn linked_generator_throw_and_fatal_release_once_and_complete_stickily() {
5748        for (tag, expected_throw) in [
5749            (CompletionTag::Throw, true),
5750            (CompletionTag::FatalTrap, false),
5751        ] {
5752            let program = generator_program(vec![Instruction::Halt], Vec::new());
5753            let entries = GeneratorEntries::terminal(tag, Value::int32(42));
5754            let mut host = SilentHost;
5755            let engine = NativeEngine::build(
5756                &program,
5757                &entries,
5758                &mut host,
5759                Limits::default(),
5760                Backend::Linked,
5761            );
5762            let generator = invoke_test_generator(&engine);
5763            let outcome = engine.resume_generator(generator, Value::UNDEFINED);
5764            if expected_throw {
5765                assert!(matches!(
5766                    outcome,
5767                    InvokeOutcome::Threw(value, ThrowOrigin::Bytecode)
5768                        if value == Value::int32(42)
5769                ));
5770            } else {
5771                assert!(matches!(outcome, InvokeOutcome::Fatal));
5772            }
5773            assert_iterator_result(
5774                &engine,
5775                engine.resume_generator(generator, Value::UNDEFINED),
5776                Value::UNDEFINED,
5777                true,
5778            );
5779            let machine = engine.machine.borrow();
5780            assert_eq!(machine.live_registers, 0);
5781            assert_eq!(machine.native_depth, 0);
5782        }
5783    }
5784
5785    #[test]
5786    fn linked_array_extend_drives_generator_through_linked_entries() {
5787        let program = yielding_generator_program();
5788        let entries = GeneratorEntries::yielding();
5789        let mut host = SilentHost;
5790        let engine = NativeEngine::build(
5791            &program,
5792            &entries,
5793            &mut host,
5794            Limits::default(),
5795            Backend::Linked,
5796        );
5797        let generator = invoke_test_generator(&engine);
5798        let array = {
5799            let prototype = engine.machine.borrow().intrinsics.array_prototype;
5800            engine
5801                .machine
5802                .borrow_mut()
5803                .allocate(HeapEntry::Array {
5804                    elements: Vec::new(),
5805                    properties: PropertyMap::default(),
5806                    prototype: Some(prototype),
5807                    extensible: true,
5808                    length_writable: true,
5809                })
5810                .unwrap()
5811        };
5812        assert!(matches!(
5813            engine.array_extend_active(array, generator),
5814            InvokeOutcome::Value(value) if value == Value::UNDEFINED
5815        ));
5816        assert_eq!(
5817            engine.machine.borrow().arguments_from_array(array).unwrap(),
5818            vec![Value::int32(4), Value::int32(5)]
5819        );
5820        assert_eq!(entries.call.get(), 3);
5821    }
5822    #[test]
5823    fn create_cell_helper_seeds_tdz_and_preserves_reference_error_origin() {
5824        let program = trivial_program();
5825        let entries = NoEntries;
5826        let mut host = SilentHost;
5827        let engine = NativeEngine::build(
5828            &program,
5829            &entries,
5830            &mut host,
5831            Limits::default(),
5832            Backend::Reference,
5833        );
5834        let mut registers = vec![Value::UNINITIALIZED];
5835        let mut shadow = ShadowFrame::new(std::ptr::null_mut(), 0, 0, registers.as_mut_ptr(), 1);
5836        let mut frame = NativeFrame::new(&mut shadow, &mut registers).unwrap();
5837        let created = engine.dispatch(&mut frame, HelperCall::CreateCell);
5838        assert_eq!(created.tag, CompletionTag::Normal);
5839        let read = engine.dispatch(
5840            &mut frame,
5841            HelperCall::GetProperty {
5842                object: created.value,
5843                key: Value::int32(0),
5844            },
5845        );
5846        assert_eq!(read.tag, CompletionTag::Throw);
5847        assert!(matches!(
5848            engine.pending_throw.get(),
5849            Some(PendingThrow {
5850                origin: ThrowOrigin::ReferenceError { .. },
5851                ..
5852            })
5853        ));
5854    }
5855}