Skip to main content

oxide_core/bytecode/
builder.rs

1//! The construction API the lowering emits through: one method per operand
2//! format, forward jumps patched by index, and `finish` into a [`CodeUnit`].
3//!
4//! Each method asserts (in debug builds) that the opcode has the format it is
5//! built with; the publication validator checks the operand values.
6
7use super::{Capture, CodeUnit, CodeUnitId, DebugInfo, Format, Handler, Insn, Opcode};
8use crate::heap::atom::PropertyKey;
9use crate::vm::ic::{FIELD_NO_SITE, GLOBAL_NO_SITE, IcKind};
10
11#[derive(Default)]
12pub struct CodeBuilder {
13    code: Vec<Insn>,
14    handlers: Vec<Handler>,
15}
16
17impl CodeBuilder {
18    pub fn new() -> CodeBuilder {
19        CodeBuilder::default()
20    }
21
22    /// A builder with room for `instructions` before it grows.
23    pub fn with_capacity(instructions: usize) -> CodeBuilder {
24        CodeBuilder {
25            code: Vec::with_capacity(instructions),
26            handlers: Vec::new(),
27        }
28    }
29
30    /// The index the next instruction gets.
31    #[inline]
32    pub fn pc(&self) -> u32 {
33        self.code.len() as u32
34    }
35
36    #[track_caller]
37    fn push(&mut self, format: Format, insn: Insn) -> u32 {
38        debug_assert_eq!(
39            insn.op.format(),
40            format,
41            "{} built with the wrong format",
42            insn.op.name()
43        );
44        let pc = self.pc();
45        self.code.push(insn);
46        pc
47    }
48
49    /// Any instruction, without the format check.
50    pub fn raw(&mut self, insn: Insn) -> u32 {
51        let format = insn.op.format();
52        self.push(format, insn)
53    }
54
55    #[track_caller]
56    pub fn op(&mut self, op: Opcode) -> u32 {
57        self.push(Format::None, Insn::new(op, 0, 0, 0, 0))
58    }
59
60    #[track_caller]
61    pub fn a(&mut self, op: Opcode, a: u8) -> u32 {
62        self.push(Format::A, Insn::new(op, a, 0, 0, 0))
63    }
64
65    #[track_caller]
66    pub fn ab(&mut self, op: Opcode, a: u8, b: u8) -> u32 {
67        self.push(Format::AB, Insn::new(op, a, b, 0, 0))
68    }
69
70    #[track_caller]
71    pub fn abc(&mut self, op: Opcode, a: u8, b: u8, c: u8) -> u32 {
72        self.push(Format::ABC, Insn::new(op, a, b, c, 0))
73    }
74
75    #[track_caller]
76    pub fn a_int(&mut self, op: Opcode, a: u8, value: i32) -> u32 {
77        self.push(Format::AInt, Insn::new(op, a, 0, 0, value as u32))
78    }
79
80    #[track_caller]
81    pub fn ab_int(&mut self, op: Opcode, a: u8, b: u8, value: i32) -> u32 {
82        self.push(Format::ABInt, Insn::new(op, a, b, 0, value as u32))
83    }
84
85    #[track_caller]
86    pub fn a_const(&mut self, op: Opcode, a: u8, index: u32) -> u32 {
87        self.push(Format::AConst, Insn::new(op, a, 0, 0, index))
88    }
89
90    #[track_caller]
91    pub fn key(&mut self, op: Opcode, key: PropertyKey) -> u32 {
92        self.push(Format::Key, Insn::new(op, 0, 0, 0, key.raw()))
93    }
94
95    #[track_caller]
96    pub fn a_key(&mut self, op: Opcode, a: u8, key: PropertyKey) -> u32 {
97        self.push(Format::AKey, Insn::new(op, a, 0, 0, key.raw()))
98    }
99
100    #[track_caller]
101    pub fn ab_key(&mut self, op: Opcode, a: u8, b: u8, key: PropertyKey) -> u32 {
102        self.push(Format::ABKey, Insn::new(op, a, b, 0, key.raw()))
103    }
104
105    #[track_caller]
106    pub fn a_capture(&mut self, op: Opcode, a: u8, capture: u32) -> u32 {
107        self.push(Format::ACapture, Insn::new(op, a, 0, 0, capture))
108    }
109
110    #[track_caller]
111    pub fn a_count(&mut self, op: Opcode, a: u8, count: u32) -> u32 {
112        self.push(Format::ACount, Insn::new(op, a, 0, 0, count))
113    }
114
115    #[track_caller]
116    pub fn ab_range(&mut self, op: Opcode, a: u8, first: u8, count: u32) -> u32 {
117        self.push(Format::ABRange, Insn::new(op, a, first, 0, count))
118    }
119
120    #[track_caller]
121    pub fn a_child(&mut self, op: Opcode, a: u8, child: u32) -> u32 {
122        self.push(Format::AChild, Insn::new(op, a, 0, 0, child))
123    }
124
125    #[track_caller]
126    pub fn call(&mut self, op: Opcode, dst: u8, callee: u8, argc: u8) -> u32 {
127        self.push(Format::Call, Insn::new(op, dst, callee, argc, 0))
128    }
129
130    #[track_caller]
131    pub fn jump(&mut self, op: Opcode, target: u32) -> u32 {
132        self.push(Format::Jump, Insn::new(op, 0, 0, 0, target))
133    }
134
135    #[track_caller]
136    pub fn a_jump(&mut self, op: Opcode, a: u8, target: u32) -> u32 {
137        self.push(Format::AJump, Insn::new(op, a, 0, 0, target))
138    }
139
140    #[track_caller]
141    pub fn ab_jump(&mut self, op: Opcode, a: u8, b: u8, target: u32) -> u32 {
142        self.push(Format::ABJump, Insn::new(op, a, b, 0, target))
143    }
144
145    #[track_caller]
146    pub fn a_short_jump(&mut self, op: Opcode, a: u8, value: i16, target: u32) -> u32 {
147        let [b, c] = value.to_le_bytes();
148        self.push(Format::AShortJump, Insn::new(op, a, b, c, target))
149    }
150
151    #[track_caller]
152    pub fn a_wide(&mut self, op: Opcode, a: u8, register: u32) -> u32 {
153        self.push(Format::AWide, Insn::new(op, a, 0, 0, register))
154    }
155
156    /// Changes the destination register of the instruction at `pc` (the
157    /// lowering's retargeting of a temporary into a local).
158    pub fn retarget(&mut self, pc: u32, a: u8) {
159        self.code[pc as usize].a = a;
160    }
161
162    /// Sets the target of the jump at `pc`, emitted before its target was
163    /// known.
164    #[track_caller]
165    pub fn patch_target(&mut self, pc: u32, target: u32) {
166        let insn = &mut self.code[pc as usize];
167        assert!(insn.has_target(), "{} has no jump target", insn.op.name());
168        insn.imm = target;
169    }
170
171    /// Adds a handler; nested handlers are added before the ones enclosing
172    /// them.
173    pub fn handler(&mut self, handler: Handler) {
174        self.handlers.push(handler);
175    }
176
177    pub fn code(&self) -> &[Insn] {
178        &self.code
179    }
180
181    /// Turns every `Jmp` whose target is at or before it into `JmpBack`,
182    /// once the targets are final: the loop polls for interrupts on
183    /// back-edges only.
184    pub fn mark_back_edges(&mut self) {
185        for (pc, insn) in self.code.iter_mut().enumerate() {
186            if insn.op == Opcode::Jmp && insn.imm as usize <= pc {
187                insn.op = Opcode::JmpBack;
188            }
189        }
190    }
191
192    /// The unit, with the layout and counts the lowering decided, and its cache
193    /// sites numbered: the named-property sites first, in instruction order,
194    /// then the global and `new` sites.
195    pub fn finish(mut self, layout: UnitLayout) -> CodeUnit {
196        let ic_count = number_sites(&mut self.code);
197        CodeUnit {
198            code: self.code.into(),
199            handlers: self.handlers.into(),
200            register_count: layout.register_count,
201            parameter_count: layout.parameter_count,
202            local_count: layout.local_count,
203            flags: layout.flags,
204            this_slots: layout.this_slots,
205            constant_count: layout.constant_count,
206            spill_register: layout.spill_register.unwrap_or(super::NO_SPILL),
207            spill_slots: layout.spill_slots,
208            ic_base: 0,
209            ic_count,
210            children: layout.children.into(),
211            captures: layout.captures.into(),
212            debug: DebugInfo {
213                name: layout.name.into(),
214                filename: layout.filename,
215                positions: Box::new([]),
216                definition: None,
217            },
218            name_atom: layout.name_atom,
219            length: layout.length,
220            source: layout.source,
221            direct_eval_sites: Box::new([]),
222            atoms: Box::new([]),
223        }
224    }
225}
226
227/// Writes each cache site's index into its instruction and returns the
228/// number of entries the unit needs. `GetField` and `SetField` get 0 to 254
229/// in `c`; past 255 sites they share [`FIELD_NO_SITE`], whose entry is
230/// reserved and never filled. The global forms follow in `b | c << 8`, with
231/// the same rule at [`GLOBAL_NO_SITE`]; `New` sites come last, in `imm`.
232fn number_sites(code: &mut [Insn]) -> u32 {
233    let is_field = |insn: &Insn| matches!(IcKind::of(insn.op), Some(IcKind::Get | IcKind::Set));
234    let is_global = |insn: &Insn| IcKind::of(insn.op) == Some(IcKind::Global);
235    let fields = code.iter().filter(|insn| is_field(insn)).count() as u32;
236    let reserved = u32::from(FIELD_NO_SITE);
237    let mut next = 0u32;
238    for insn in code.iter_mut().filter(|insn| is_field(insn)) {
239        insn.c = next.min(reserved) as u8;
240        next += 1;
241    }
242    next = if fields > reserved {
243        reserved + 1
244    } else {
245        fields
246    };
247    let globals = code.iter().filter(|insn| is_global(insn)).count() as u32;
248    let first_global = next;
249    for insn in code.iter_mut().filter(|insn| is_global(insn)) {
250        let site = next.min(GLOBAL_NO_SITE);
251        insn.b = site as u8;
252        insn.c = (site >> 8) as u8;
253        next += 1;
254    }
255    if first_global + globals > GLOBAL_NO_SITE {
256        next = GLOBAL_NO_SITE + 1;
257    }
258    for insn in code.iter_mut().filter(|insn| insn.op == Opcode::New) {
259        insn.imm = next;
260        next += 1;
261    }
262    next
263}
264
265/// The parts of a [`CodeUnit`] that are not instructions.
266#[derive(Clone, Debug, Default)]
267pub struct UnitLayout {
268    pub register_count: u16,
269    pub parameter_count: u16,
270    pub local_count: u16,
271    pub flags: u16,
272    pub this_slots: u16,
273    pub constant_count: u32,
274    pub spill_register: Option<u16>,
275    pub spill_slots: u16,
276    pub children: Vec<CodeUnitId>,
277    pub captures: Vec<Capture>,
278    pub name: String,
279    /// The function's `name` property (an atom), or `None` for `""`.
280    pub name_atom: Option<crate::value::StringId>,
281    /// The function's `length` property.
282    pub length: u16,
283    /// The function's source text, for `Function.prototype.toString`, in
284    /// WTF-8 (a lone surrogate of `Function` constructor source is kept as
285    /// its three-byte form, as QuickJS's `JS_ToCStringLen2` keeps it).
286    pub source: Option<Box<[u8]>>,
287    /// The script or module the function belongs to.
288    pub filename: Option<std::rc::Rc<str>>,
289}