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