Skip to main content

oxide_core/bytecode/
mod.rs

1//! Register bytecode: fixed-width instructions, code units and the store that
2//! owns them.
3//!
4//! A code unit is built once by the lowering, checked once by
5//! [`validate`](validate::validate) when it is published, and then run
6//! without any decoding: the main loop matches on the typed [`Opcode`] and
7//! indexes registers with plain `[]`. Units published before the program
8//! runs live in the [`CodeStore`] for the runtime's lifetime; units compiled
9//! at run time live in the heap, which frees each once no function, frame
10//! or job reaches it.
11
12pub mod builder;
13pub mod disasm;
14pub mod opcode;
15pub mod validate;
16
17use std::fmt;
18
19pub use self::builder::CodeBuilder;
20pub use self::disasm::disassemble;
21pub use self::opcode::{Format, Opcode};
22pub use self::validate::{ValidateError, ValidateErrorKind};
23use crate::heap::atom::PropertyKey;
24
25/// One register instruction. `a`, `b`, `c` are register operands or small
26/// counts; `imm` is a constant index, an absolute jump target, a key's bits,
27/// a scope slot or an `i32` literal, as the opcode's [`Format`] says.
28#[repr(C)]
29#[derive(Clone, Copy, PartialEq, Eq)]
30pub struct Insn {
31    pub op: Opcode,
32    pub a: u8,
33    pub b: u8,
34    pub c: u8,
35    pub imm: u32,
36}
37
38const _: () = assert!(size_of::<Insn>() == 8);
39
40/// Register operands address a 256-register window; registers above it are
41/// reached through `LoadWide` and `StoreWide`.
42pub const WINDOW: u32 = 256;
43
44impl Insn {
45    #[inline]
46    pub const fn new(op: Opcode, a: u8, b: u8, c: u8, imm: u32) -> Insn {
47        Insn { op, a, b, c, imm }
48    }
49
50    /// The `imm` field read as an `i32` literal.
51    #[inline]
52    pub const fn int(self) -> i32 {
53        self.imm as i32
54    }
55
56    /// The `i16` literal of an [`Format::AShortJump`] instruction.
57    #[inline]
58    pub const fn short(self) -> i16 {
59        (self.b as u16 | (self.c as u16) << 8) as i16
60    }
61
62    /// The `imm` field read as a property key.
63    #[inline]
64    pub const fn key(self) -> PropertyKey {
65        PropertyKey::from_raw(self.imm)
66    }
67
68    /// The cache site of an instruction that has one: `c` for `GetField` and
69    /// `SetField`, `b | c << 8` for the global forms, `imm` for `New`. An index
70    /// into the unit's entries of the [`IcStore`](crate::vm::ic::IcStore).
71    #[inline]
72    pub const fn site(self) -> Option<u32> {
73        match self.op {
74            Opcode::GetField | Opcode::SetField => Some(self.c as u32),
75            Opcode::GetGlobal | Opcode::GetGlobalOrUndefined | Opcode::SetGlobal => {
76                Some(self.b as u32 | (self.c as u32) << 8)
77            }
78            Opcode::New => Some(self.imm),
79            _ => None,
80        }
81    }
82
83    /// Whether `imm` is a jump target.
84    #[inline]
85    pub const fn has_target(self) -> bool {
86        matches!(
87            self.op.format(),
88            Format::Jump | Format::AJump | Format::ABJump | Format::AShortJump
89        )
90    }
91}
92
93impl fmt::Debug for Insn {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(
96            f,
97            "{} a={} b={} c={} imm={:#x}",
98            self.op.name(),
99            self.a,
100            self.b,
101            self.c,
102            self.imm
103        )
104    }
105}
106
107/// Index of a [`CodeUnit`] in the [`CodeStore`].
108#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
109pub struct CodeUnitId(pub(crate) u32);
110
111impl CodeUnitId {
112    #[inline]
113    pub const fn index(self) -> u32 {
114        self.0
115    }
116}
117
118impl fmt::Debug for CodeUnitId {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        write!(f, "CodeUnitId({})", self.0)
121    }
122}
123
124/// An exception handler: a throw at an instruction in `start..end` stores the
125/// thrown value in `register` (the temporary at the handler's stack depth)
126/// and continues at `target`. Entries are ordered innermost first.
127#[derive(Clone, Copy, Debug, PartialEq, Eq)]
128pub struct Handler {
129    pub start: u32,
130    pub end: u32,
131    pub target: u32,
132    pub register: u16,
133}
134
135pub mod flags {
136    /// Strict-mode code: `this` is not coerced.
137    pub const STRICT: u16 = 1 << 0;
138    /// The function may be called with `new`.
139    pub const CONSTRUCTOR: u16 = 1 << 1;
140    /// An arrow function: `this` is lexical, never a constructor.
141    pub const ARROW: u16 = 1 << 2;
142    /// A script body.
143    pub const SCRIPT: u16 = 1 << 3;
144    /// The function reads its actual arguments past its parameters
145    /// (`arguments` or a rest parameter): entering it keeps the extra
146    /// arguments above its registers.
147    pub const ARGUMENTS: u16 = 1 << 4;
148    /// The function reads `new.target`: its first local, right after the
149    /// parameters, receives it when it is entered by a construction.
150    pub const NEW_TARGET: u16 = 1 << 5;
151    /// A class constructor: calling it without `new` throws.
152    pub const CLASS_CONSTRUCTOR: u16 = 1 << 6;
153    /// A derived class constructor: no receiver is created; `this` is bound
154    /// by `super()`.
155    pub const DERIVED: u16 = 1 << 7;
156    /// A generator function.
157    pub const GENERATOR: u16 = 1 << 8;
158    /// An async function (with `GENERATOR`, an async generator).
159    pub const ASYNC: u16 = 1 << 9;
160    /// `MakeClosure` gives the function a `prototype` object (an ordinary
161    /// function or a generator; class constructors get theirs from
162    /// `MakeClass`).
163    pub const PROTOTYPE: u16 = 1 << 10;
164    /// Module code.
165    pub const MODULE: u16 = 1 << 11;
166    /// A class field or static initializer that brands its receiver with its
167    /// home object's private-method brand before it runs.
168    pub const BRAND: u16 = 1 << 12;
169    /// The root of `eval` code: `import()` in it resolves against the code
170    /// that called `eval`.
171    pub const EVAL: u16 = 1 << 13;
172}
173
174/// Where a closure created by `MakeClosure` gets one captured cell, read in
175/// the frame that runs the `MakeClosure`.
176#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub enum Capture {
178    /// A slot with no cell (a global the closure reads by name).
179    None,
180    /// The cell in this register of the creating frame.
181    Register(u16),
182    /// The creating function's own capture at this index.
183    Outer(u16),
184    /// The cell in this slot of the creating frame's spill scope (a local
185    /// past the register window).
186    Slot(u16),
187}
188
189/// [`CodeUnit::spill_register`] of a unit without a spill scope.
190pub const NO_SPILL: u16 = u16::MAX;
191
192/// Source information kept for the disassembler and error messages.
193#[derive(Clone, Debug, Default, PartialEq, Eq)]
194pub struct DebugInfo {
195    pub name: Box<str>,
196    /// The script or module the function belongs to, for `import()`.
197    pub filename: Option<std::rc::Rc<str>>,
198    /// Where the instructions came from, sorted by `pc`: each entry holds
199    /// for its instruction and the ones after it up to the next. Empty when
200    /// the producer recorded none.
201    pub positions: Box<[SourcePosition]>,
202    /// The one-based line and column of the function's definition, for
203    /// `Function.prototype.lineNumber` and `columnNumber`.
204    pub definition: Option<(u32, u32)>,
205}
206
207/// A one-based source line and column, from instruction `pc` on.
208#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub struct SourcePosition {
210    pub pc: u32,
211    pub line: u32,
212    pub column: u32,
213}
214
215impl DebugInfo {
216    /// The line and column of the instruction at `pc`, as QuickJS's
217    /// `find_line_num` reads its table: the last entry at or before it.
218    pub fn position(&self, pc: u32) -> Option<(u32, u32)> {
219        let after = self.positions.partition_point(|entry| entry.pc <= pc);
220        let entry = self.positions[..after].last()?;
221        Some((entry.line, entry.column))
222    }
223}
224
225/// The compiled form of one function.
226///
227/// The register window of a frame is laid out as: register 0 is `this`, then
228/// `parameter_count` parameters, then `local_count` locals, then the operand
229/// temporaries up to `register_count`.
230#[derive(Clone, Debug, PartialEq, Eq)]
231pub struct CodeUnit {
232    pub code: Box<[Insn]>,
233    pub handlers: Box<[Handler]>,
234    pub register_count: u16,
235    pub parameter_count: u16,
236    pub local_count: u16,
237    pub flags: u16,
238    /// Distinct `this.x =` names in a constructor body: the slots `New`
239    /// reserves on the receiver.
240    pub this_slots: u16,
241    /// The length of the constant table attached on the heap.
242    pub constant_count: u32,
243    /// The register holding the frame's spill scope, or [`NO_SPILL`]: a
244    /// function with more locals than the register window holds keeps the
245    /// rest in the slots of one scope object, created by `NewSpill`.
246    pub spill_register: u16,
247    /// The slots of the spill scope.
248    pub spill_slots: u16,
249    /// The unit's first entry in the cache store, set when it is published.
250    pub ic_base: u32,
251    /// Cache sites, numbered by [`CodeBuilder::finish`](builder::CodeBuilder::finish).
252    pub ic_count: u32,
253    /// Inner functions, by `MakeClosure` index. Published before the unit.
254    pub children: Box<[CodeUnitId]>,
255    /// The cells a closure over this unit captures, from the frame that
256    /// creates it; `LoadCaptured` indexes them.
257    pub captures: Box<[Capture]>,
258    pub debug: DebugInfo,
259    /// The function's `name` (an atom), or `None` for `""`.
260    pub name_atom: Option<crate::value::StringId>,
261    /// The function's `length`.
262    pub length: u16,
263    /// The function's source text, for `Function.prototype.toString`, in
264    /// WTF-8 (a lone surrogate of `Function` constructor source is kept as
265    /// its three-byte form, as QuickJS's `JS_ToCStringLen2` keeps it).
266    pub source: Option<Box<[u8]>>,
267    /// Sorted indices of the calls written as a direct `eval`, which runs as
268    /// indirect eval: a backtrace lists no native frame for them, as QuickJS's
269    /// direct eval has none.
270    pub direct_eval_sites: Box<[u32]>,
271    /// The atoms a dynamic unit keeps alive ([`CodeUnit::key_atoms`]), set
272    /// when it is published; empty for a static unit, whose atoms are
273    /// pinned.
274    pub atoms: Box<[crate::value::StringId]>,
275}
276
277impl CodeUnit {
278    /// The first operand temporary, `T(0)`: after the locals, or after the
279    /// spill register when the locals do not all fit in the window.
280    #[inline]
281    pub const fn temp_base(&self) -> u32 {
282        if self.spill_register != NO_SPILL {
283            self.spill_register as u32 + 1
284        } else {
285            1 + self.parameter_count as u32 + self.local_count as u32
286        }
287    }
288
289    #[inline]
290    pub const fn has(&self, flag: u16) -> bool {
291        self.flags & flag != 0
292    }
293}
294
295/// Marks a [`CodeUnitId`] of a unit compiled at run time, which the heap
296/// owns.
297pub(crate) const DYNAMIC: u32 = 1 << 31;
298
299impl CodeUnitId {
300    /// Whether the unit was compiled at run time: the heap owns it and
301    /// frees it once no bytecode record names it.
302    #[inline]
303    pub const fn is_dynamic(self) -> bool {
304        self.0 & DYNAMIC != 0
305    }
306}
307
308/// A borrowed code unit: a unit of the [`CodeStore`] for the store's
309/// lifetime, or a counted reference to a unit the heap owns, which keeps it
310/// alive while the loop runs it across a collection.
311pub enum UnitRef<'a> {
312    Static(&'a CodeUnit),
313    Dynamic(std::rc::Rc<CodeUnit>),
314}
315
316impl std::ops::Deref for UnitRef<'_> {
317    type Target = CodeUnit;
318
319    #[inline(always)]
320    fn deref(&self) -> &CodeUnit {
321        match self {
322            UnitRef::Static(unit) => unit,
323            UnitRef::Dynamic(unit) => unit,
324        }
325    }
326}
327
328/// The units published before the program runs. It lives in the runtime
329/// beside the heap, not in it, so the main loop can hold `&[Insn]` across
330/// heap calls; units compiled at run time live in the heap
331/// ([`DynamicUnits`](crate::heap::code::DynamicUnits)).
332#[derive(Default)]
333pub struct CodeStore {
334    units: Vec<CodeUnit>,
335}
336
337impl CodeStore {
338    /// Validates `unit` and appends it. Its cache entries are the caller's:
339    /// [`Runtime::publish`](crate::Runtime::publish) reserves them.
340    pub fn publish(&mut self, unit: CodeUnit) -> Result<CodeUnitId, ValidateError> {
341        validate::validate(&unit, &|id| self.lookup(id))?;
342        Ok(self.push(unit))
343    }
344
345    /// Appends a validated unit.
346    pub(crate) fn push(&mut self, unit: CodeUnit) -> CodeUnitId {
347        let id = u32::try_from(self.units.len())
348            .ok()
349            .filter(|&id| id < DYNAMIC)
350            .expect("code store overflow");
351        self.units.push(unit);
352        CodeUnitId(id)
353    }
354
355    /// The unit `id`, if it is a unit of this store.
356    pub fn lookup(&self, id: CodeUnitId) -> Option<&CodeUnit> {
357        if id.is_dynamic() {
358            return None;
359        }
360        self.units.get(id.0 as usize)
361    }
362
363    /// The static unit `id`.
364    #[inline]
365    pub fn get(&self, id: CodeUnitId) -> &CodeUnit {
366        debug_assert!(!id.is_dynamic(), "a unit of the code store");
367        &self.units[id.0 as usize]
368    }
369
370    pub fn len(&self) -> usize {
371        self.units.len()
372    }
373
374    /// Every unit with its id, in publication order.
375    pub fn units(&self) -> impl Iterator<Item = (CodeUnitId, &CodeUnit)> {
376        self.units
377            .iter()
378            .enumerate()
379            .map(|(index, unit)| (CodeUnitId(index as u32), unit))
380    }
381
382    pub fn is_empty(&self) -> bool {
383        self.units.is_empty()
384    }
385
386    /// `id` and its descendants in pre-order ([`Units::tree`]).
387    pub fn tree(&self, id: CodeUnitId) -> Vec<CodeUnitId> {
388        tree(id, |id| &self.get(id).children[..])
389    }
390}
391
392/// `id` and its descendants in pre-order, as `children` gives them.
393fn tree<'a>(id: CodeUnitId, children: impl Fn(CodeUnitId) -> &'a [CodeUnitId]) -> Vec<CodeUnitId> {
394    let mut order = Vec::new();
395    let mut pending = vec![id];
396    while let Some(id) = pending.pop() {
397        order.push(id);
398        pending.extend(children(id).iter().rev());
399    }
400    order
401}
402
403/// Every unit the runtime holds, static and dynamic, for reading.
404#[derive(Clone, Copy)]
405pub struct Units<'a> {
406    pub(crate) code: &'a CodeStore,
407    pub(crate) dynamic: &'a crate::heap::code::DynamicUnits,
408}
409
410impl<'a> Units<'a> {
411    /// The unit `id`.
412    #[inline]
413    pub fn get(self, id: CodeUnitId) -> UnitRef<'a> {
414        if id.is_dynamic() {
415            return UnitRef::Dynamic(self.dynamic.get(id));
416        }
417        UnitRef::Static(self.code.get(id))
418    }
419
420    /// The unit `id`, if it is published.
421    pub fn lookup(self, id: CodeUnitId) -> Option<&'a CodeUnit> {
422        if id.is_dynamic() {
423            return self.dynamic.lookup(id);
424        }
425        self.code.lookup(id)
426    }
427
428    /// `id` and its descendants in pre-order: the order in which
429    /// [`Runtime::instantiate`](crate::Runtime::instantiate) takes their
430    /// constant tables.
431    pub fn tree(self, id: CodeUnitId) -> Vec<CodeUnitId> {
432        tree(id, |id| {
433            &self.lookup(id).expect("a published unit").children[..]
434        })
435    }
436
437    /// Every unit with its id: the static ones in publication order, then
438    /// the live dynamic ones by slot.
439    pub fn all(self) -> impl Iterator<Item = (CodeUnitId, &'a CodeUnit)> {
440        self.code.units().chain(self.dynamic.units())
441    }
442}
443
444impl CodeUnit {
445    /// The distinct atoms the instructions name as property keys, with the
446    /// function's name: what a live dynamic unit keeps alive.
447    pub(crate) fn key_atoms(&self) -> Box<[crate::value::StringId]> {
448        use crate::heap::atom::KeyKind;
449        let mut atoms: Vec<crate::value::StringId> = self
450            .code
451            .iter()
452            .filter(|insn| matches!(insn.op.format(), Format::Key | Format::AKey | Format::ABKey))
453            .filter_map(|insn| match insn.key().kind() {
454                KeyKind::String(id) => Some(id),
455                _ => None,
456            })
457            .chain(self.name_atom)
458            .collect();
459        atoms.sort_unstable();
460        atoms.dedup();
461        atoms.into()
462    }
463
464    /// Bytes the unit holds: its instructions, tables, positions, source
465    /// text and cache entries. A dynamic unit charges them to the heap.
466    pub fn bytes(&self) -> usize {
467        size_of::<CodeUnit>()
468            + std::mem::size_of_val(&*self.code)
469            + std::mem::size_of_val(&*self.handlers)
470            + std::mem::size_of_val(&*self.children)
471            + std::mem::size_of_val(&*self.captures)
472            + std::mem::size_of_val(&*self.debug.positions)
473            + self.debug.name.len()
474            + self.source.as_ref().map_or(0, |source| source.len())
475            + std::mem::size_of_val(&*self.direct_eval_sites)
476            + std::mem::size_of_val(&*self.atoms)
477            + self.ic_count as usize * crate::vm::ic::ENTRY_BYTES
478    }
479}