Skip to main content

oxide_core/vm/
mod.rs

1//! The register machine: the register file and frame table, the main loop, its
2//! slow paths, exception unwinding and the leaf-native ABI.
3//!
4//! A frame owns a window of the register file: register 0 is `this`, then
5//! the parameters, the locals and the operand temporaries of its code unit. A
6//! call's window starts inside the caller's, on the callee's operands, so no
7//! argument is copied. Both tables live inside the [`Heap`] as fixed roots:
8//! a safepoint marks `registers[..top]` and every frame's function.
9
10pub(crate) mod arguments;
11pub(crate) mod async_generator;
12pub(crate) mod bigint;
13pub(crate) mod class;
14pub mod compile;
15pub(crate) mod error;
16pub mod ic;
17mod interp;
18pub(crate) mod iter;
19pub(crate) mod jobs;
20pub mod module;
21pub mod native;
22pub(crate) mod number;
23pub(crate) mod object;
24pub(crate) mod ops;
25pub(crate) mod print;
26pub(crate) mod property;
27pub(crate) mod proxy;
28pub(crate) mod suspend;
29pub(crate) mod typed;
30pub(crate) mod via;
31pub(crate) mod waiter;
32// Public for the root crate's differential test.
33#[doc(hidden)]
34pub mod radix;
35pub mod regexp;
36
37use std::fmt;
38
39use crate::bytecode::{CodeStore, CodeUnitId, UnitRef};
40use crate::heap::Heap;
41use crate::runtime::Realm;
42use crate::value::{BytecodeId, ObjectId, Value};
43
44pub use self::error::ErrorKind;
45
46/// A thrown JavaScript value. It is not rooted: whoever receives it stores it
47/// in a register, a root or a handle before the next allocation.
48#[derive(Clone, Copy, PartialEq, Eq)]
49pub struct Throw(pub Value);
50
51impl fmt::Debug for Throw {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(f, "Throw({:?})", self.0)
54    }
55}
56
57pub mod frame_flags {
58    /// The frame was entered from Rust: its return leaves the loop.
59    pub const ENTRY: u8 = 1 << 0;
60    /// A `new` call: a non-object return yields the receiver in register 0.
61    pub const CONSTRUCT: u8 = 1 << 1;
62    /// Entered by a tail call: its result is also its caller's result.
63    pub const TAIL: u8 = 1 << 2;
64    /// Called through `Function.prototype.call` (the loop's rewrite): QuickJS
65    /// lists `at call (native)` after its backtrace line.
66    pub const VIA_CALL: u8 = 1 << 3;
67    /// Called through `Function.prototype.apply`, likewise.
68    pub const VIA_APPLY: u8 = 1 << 4;
69    /// Called through a chain of them: `ViaCalls::frames` lists it.
70    pub const VIA_CHAIN: u8 = 1 << 5;
71}
72
73/// One activation. `return_pc` and `return_dst` belong to the caller: where
74/// it resumes, and the absolute register that receives the result.
75#[derive(Clone, Copy, Debug)]
76pub(crate) struct Frame {
77    pub unit: CodeUnitId,
78    pub function: ObjectId,
79    pub bytecode: BytecodeId,
80    pub base: u32,
81    pub return_pc: u32,
82    pub return_dst: u32,
83    /// The actual argument count, which `arguments`, rest parameters and the
84    /// extra arguments kept above the registers read.
85    pub argc: u32,
86    pub flags: u8,
87}
88
89const _: () = assert!(size_of::<Frame>() == 32);
90
91/// Registers a frame addresses without a bounds check: an 8-bit operand, or
92/// a call's `b + 1` (see `interp::window`).
93pub(crate) const SPAN: usize = 512;
94
95/// Native stack bytes nested re-entries may use (debug builds have large
96/// loop frames; the default thread stack of a test is 2 MiB).
97pub const NATIVE_STACK_BUDGET: usize = 1 << 20;
98
99/// Registers the file may hold before a call fails with `InternalError:
100/// stack overflow`: 8 MiB of values.
101pub const DEFAULT_STACK_LIMIT: u32 = 1 << 20;
102
103/// The `pc` of a [`NativeSite`] made by a call from Rust: the caller's
104/// instruction is not known there.
105pub(crate) const NO_PC: u32 = u32::MAX;
106
107/// A native call made by the loop, or from Rust (`pc` [`NO_PC`]).
108#[derive(Clone, Copy, Debug)]
109pub(crate) struct NativeSite {
110    /// The frame count when it was made: the caller is `frames[depth - 1]`.
111    pub depth: u32,
112    pub pc: u32,
113    pub callee: Value,
114}
115
116/// The register file and the frame table.
117pub struct VmStack {
118    pub(crate) registers: Vec<Value>,
119    pub(crate) frames: Vec<Frame>,
120    /// `registers[..top]` are roots: the running frame's window and
121    /// everything below it.
122    pub(crate) top: u32,
123    /// Registers from here up may name records freed by a collection that
124    /// ran while they were above `top`: each collection lowers it to `top`,
125    /// and a frame that resumes clears its registers from here before they
126    /// become roots again (see `interp::raise_top`).
127    pub(crate) stale_from: u32,
128    pub(crate) limit: u32,
129    /// Rust-level re-entries in progress: a slow path calling back into
130    /// JavaScript, or a conversion recursing through an array.
131    pub(crate) nesting: u32,
132    /// The native stack address at the outermost re-entry, for
133    /// [`VmStack::enter_native`].
134    pub(crate) native_base: usize,
135    /// The native stack bytes nested re-entries may use
136    /// ([`NATIVE_STACK_BUDGET`] unless the runtime's options say otherwise).
137    pub(crate) native_budget: usize,
138    /// Nested re-entries allowed at most: the budget for a host whose stack
139    /// the byte measure cannot see (a WebAssembly engine's machine stack).
140    pub(crate) native_depth: u32,
141    /// The frame count and instruction index of the call running the
142    /// current native, for the backtrace of an error it creates.
143    /// The native calls in progress, innermost last: the frame count and
144    /// instruction index of each call and the native it runs, for the
145    /// backtrace's `at name (native)` lines and the positions of the frames
146    /// that called them.
147    pub(crate) natives: Vec<NativeSite>,
148    /// The next native called from Rust is listed in no backtrace: an
149    /// iterator's `next` stepped by [`call_next`].
150    pub(crate) frameless_native: bool,
151    /// The last error whose backtrace has frames it could not place (the
152    /// innermost frame when the loop itself threw; a frame that called into
153    /// Rust, which re-entered the loop): per frame, its depth and its line
154    /// of the backtrace. Each loop the throw unwinds through places its own.
155    pub(crate) unpositioned: Option<(ObjectId, Vec<(u32, u32)>)>,
156    /// The suspendable activations running, innermost last: where `Yield` and
157    /// `Await` save their frame.
158    pub(crate) active: Vec<ObjectId>,
159    /// Whether the last proxy `[[Set]]` or `[[DefineOwnProperty]]` that
160    /// returned false was refused by a trap rather than by the target it
161    /// forwarded to: QuickJS throws `proxy: cannot set property` or `proxy:
162    /// defineProperty exception` only for the former.
163    pub(crate) proxy_refused: bool,
164    /// The bookkeeping of rewritten `call`s and `apply`s, boxed to keep the
165    /// loop's hot fields where they were.
166    pub(crate) via: Box<via::ViaCalls>,
167    /// Back-edges and calls left before the next interrupt poll.
168    pub(crate) poll: u32,
169    /// An interrupt is unwinding: no handler catches it, and no native turns
170    /// it into a completion.
171    pub(crate) interrupted: bool,
172}
173
174/// Back-edges and calls between two interrupt polls. QuickJS's
175/// `JS_INTERRUPT_COUNTER_INIT` is 10,000; a tenth of it costs the loop
176/// nothing measurable (the poll is cold), and stops a script ten times
177/// sooner.
178pub const INTERRUPT_INTERVAL: u32 = 1_000;
179
180impl Default for VmStack {
181    fn default() -> Self {
182        VmStack {
183            registers: Vec::new(),
184            frames: Vec::new(),
185            top: 0,
186            stale_from: u32::MAX,
187            limit: DEFAULT_STACK_LIMIT,
188            nesting: 0,
189            native_base: 0,
190            native_budget: NATIVE_STACK_BUDGET,
191            native_depth: u32::MAX,
192            natives: Vec::new(),
193            frameless_native: false,
194            via: Box::default(),
195            unpositioned: None,
196            active: Vec::new(),
197            proxy_refused: false,
198            poll: INTERRUPT_INTERVAL,
199            interrupted: false,
200        }
201    }
202}
203
204impl VmStack {
205    /// Makes `registers[..end]` addressable, plus [`SPAN`] values of
206    /// padding so a window can be taken at any base below `end`; `false`
207    /// when `end` passes the limit. Growth doubles, and indices stay valid,
208    /// so no window needs fixing up.
209    #[inline]
210    pub(crate) fn reserve(&mut self, end: usize) -> bool {
211        if end + SPAN <= self.registers.len() {
212            return true;
213        }
214        self.grow(end)
215    }
216
217    #[inline(never)]
218    fn grow(&mut self, end: usize) -> bool {
219        if end > self.limit as usize {
220            return false;
221        }
222        let target = (end + SPAN).max(self.registers.len() * 2).max(4 * SPAN);
223        let target = target.min(self.limit as usize + SPAN);
224        self.registers.resize(target, Value::UNDEFINED);
225        true
226    }
227
228    /// Counts one Rust-level re-entry, or fails when the native stack used
229    /// since the outermost one exceeds [`NATIVE_STACK_BUDGET`] (QuickJS's
230    /// `js_check_stack_overflow`). Pair with [`VmStack::leave_native`].
231    #[inline]
232    pub(crate) fn enter_native(&mut self) -> bool {
233        let marker = 0u8;
234        let here = std::ptr::addr_of!(marker) as usize;
235        if self.nesting == 0 {
236            self.native_base = here;
237        } else if self.native_base.abs_diff(here) > self.native_budget
238            || self.nesting >= self.native_depth
239        {
240            return false;
241        }
242        self.nesting += 1;
243        true
244    }
245
246    /// The native stack bytes nested re-entries may still use.
247    pub(crate) fn native_remaining(&self) -> usize {
248        if self.nesting == 0 {
249            return self.native_budget;
250        }
251        let marker = 0u8;
252        let here = std::ptr::addr_of!(marker) as usize;
253        self.native_budget
254            .saturating_sub(self.native_base.abs_diff(here))
255    }
256
257    #[inline]
258    pub(crate) fn leave_native(&mut self) {
259        self.nesting -= 1;
260    }
261
262    pub fn depth(&self) -> usize {
263        self.frames.len()
264    }
265
266    pub fn registers_reserved(&self) -> usize {
267        self.registers.len()
268    }
269}
270
271/// Where `print` writes.
272#[derive(Debug, Default)]
273pub enum Output {
274    #[default]
275    Stdout,
276    /// Collected for the embedder (tests).
277    Capture(String),
278}
279
280/// Runtime state the natives reach beside the heap: output, the random
281/// stream, the symbol registry, and the embedder's [`Host`](crate::host::Host).
282pub struct Services {
283    pub output: Output,
284    /// The embedder's hooks.
285    pub(crate) hooks: Box<dyn crate::host::Host>,
286    /// `Math.random`'s xorshift64* state; never zero.
287    random_state: u64,
288    /// `Symbol.for`'s registry: content to symbol. Registered symbols are
289    /// fixed roots.
290    pub(crate) symbol_registry: crate::heap::hash::FxHashMap<Vec<u16>, crate::SymbolId>,
291    /// Whether `Atomics.wait` may block this agent.
292    pub can_block: bool,
293    /// The embedder's compiler.
294    pub(crate) compile: Option<compile::CompileFn>,
295    /// The embedder's module loader.
296    pub(crate) module_host: Option<module::ModuleHost>,
297    /// The module records by normalized name.
298    pub(crate) modules: crate::heap::hash::FxHashMap<String, ObjectId>,
299    /// The embedder's script runner, for `$262.agent.start`.
300    pub(crate) script_runner: Option<crate::builtins::agent::ScriptRunner>,
301    /// The agents this (main) agent started.
302    pub(crate) agent_hub: Option<std::sync::Arc<crate::builtins::agent::AgentHub>>,
303    /// This runtime as an agent of another.
304    pub(crate) agent: Option<crate::builtins::agent::AgentSelf>,
305    /// Whether unhandled rejected promises are kept in
306    /// `Heap::rejections` for the embedder.
307    pub(crate) track_rejections: bool,
308    /// Set from any thread to interrupt the running script at the next
309    /// poll; it stays set until cleared.
310    pub(crate) interrupt: std::sync::Arc<std::sync::atomic::AtomicBool>,
311}
312
313impl std::fmt::Debug for Services {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        f.debug_struct("Services")
316            .field("output", &self.output)
317            .finish()
318    }
319}
320
321impl Default for Services {
322    fn default() -> Self {
323        // QuickJS seeds from the clock; programs that compare output replace
324        // `Math.random`, as the benchmark harness does.
325        let seed = crate::clock::time_seed();
326        Services {
327            output: Output::default(),
328            hooks: Box::new(crate::host::SystemHost),
329            random_state: seed.max(1),
330            symbol_registry: Default::default(),
331            can_block: true,
332            compile: None,
333            module_host: None,
334            modules: Default::default(),
335            script_runner: None,
336            agent_hub: None,
337            agent: None,
338            track_rejections: false,
339            interrupt: Default::default(),
340        }
341    }
342}
343
344impl Services {
345    pub(crate) fn random_state(&self) -> u64 {
346        self.random_state
347    }
348
349    pub(crate) fn set_random_state(&mut self, state: u64) {
350        self.random_state = state;
351    }
352
353    /// Seeds `Math.random` (zero is replaced by one).
354    pub fn seed_random(&mut self, seed: u64) {
355        self.random_state = seed.max(1);
356    }
357}
358
359/// What the loop and its slow paths borrow from the [`Runtime`]: the heap
360/// mutably, the code store and the realm shared, so the running unit's code
361/// stays borrowed across every heap call ([`Cx::unit`]).
362///
363/// [`Runtime`]: crate::Runtime
364pub(crate) struct Cx<'a> {
365    pub heap: &'a mut Heap,
366    /// The static units only: [`Cx::unit`] reads any unit.
367    pub static_code: &'a CodeStore,
368    pub realm: &'a Realm,
369    pub host: &'a mut Services,
370}
371
372impl<'a> Cx<'a> {
373    /// Unit `id`: borrowed from the code store, or counted from the heap,
374    /// so it stays valid across every heap call either way.
375    #[inline(always)]
376    pub(crate) fn unit(&self, id: CodeUnitId) -> UnitRef<'a> {
377        if id.is_dynamic() {
378            return dynamic_unit(self.heap, id);
379        }
380        let code: &'a CodeStore = self.static_code;
381        UnitRef::Static(code.get(id))
382    }
383}
384
385#[cold]
386#[inline(never)]
387fn dynamic_unit(heap: &Heap, id: CodeUnitId) -> UnitRef<'static> {
388    UnitRef::Dynamic(heap.code.get(id))
389}
390
391impl Cx<'_> {
392    /// Records an intrinsic the builtins created, as a fixed root.
393    pub(crate) fn set_intrinsic(&mut self, intrinsic: crate::Intrinsic, object: ObjectId) {
394        self.heap.add_fixed_root(Value::object(object));
395        self.realm.record_intrinsic(intrinsic, object);
396    }
397}
398
399/// Calls `function` with `this` and `args` and runs it to completion. The
400/// arguments need no rooting: they are stored in the register file before
401/// anything allocates. Re-entrant: a slow path may call back into
402/// JavaScript, which runs a nested loop on the frames above its own.
403pub(crate) fn call(
404    cx: &mut Cx<'_>,
405    function: Value,
406    this: Value,
407    args: &[Value],
408) -> Result<Value, Throw> {
409    let base = cx.heap.vm.top as usize;
410    let end = base + 1 + args.len();
411    if !cx.heap.vm.reserve(end) {
412        return Err(error::stack_overflow(cx));
413    }
414    let saved_top = cx.heap.vm.top;
415    let registers = &mut cx.heap.vm.registers;
416    registers[base] = this;
417    registers[base + 1..end].copy_from_slice(args);
418    // The arguments are rooted from here; `function` is held by the caller
419    // (a register or a property of a rooted object) until the frame names it.
420    cx.heap.vm.top = end as u32;
421    let result = interp::call_from_rust(cx, function, base, args.len());
422    cx.heap.vm.top = saved_top;
423    result
424}
425
426/// [`call`] of an iterator's `next` with no arguments, as QuickJS's
427/// `JS_IteratorNext2` makes it: a native `next` runs without a stack frame
428/// of its own (QuickJS calls its `iterator_next` C functions directly).
429pub(crate) fn call_next(cx: &mut Cx<'_>, next: Value, iterator: Value) -> Result<Value, Throw> {
430    cx.heap.vm.frameless_native = true;
431    let result = call(cx, next, iterator, &[]);
432    cx.heap.vm.frameless_native = false;
433    result
434}
435
436/// `Construct(function, args, new_target)`: runs a constructor to
437/// completion. Re-entrant like [`call`]; the arguments need no rooting.
438pub(crate) fn construct(
439    cx: &mut Cx<'_>,
440    function: Value,
441    args: &[Value],
442    new_target: Value,
443) -> Result<Value, Throw> {
444    let base = cx.heap.vm.top as usize;
445    let end = base + 1 + args.len();
446    if !cx.heap.vm.reserve(end) {
447        return Err(error::stack_overflow(cx));
448    }
449    let saved_top = cx.heap.vm.top;
450    let registers = &mut cx.heap.vm.registers;
451    registers[base] = Value::UNDEFINED;
452    registers[base + 1..end].copy_from_slice(args);
453    cx.heap.vm.top = end as u32;
454    // `new_target` is held by the caller, like `function`.
455    let result = interp::construct_from_rust(cx, function, base, args.len(), new_target);
456    cx.heap.vm.top = saved_top;
457    result
458}