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}