Skip to main content

luau_bytecode/
model.rs

1use luau_common::{BString, ByteSlice};
2use std::borrow::Borrow;
3
4use crate::opcodes::{InvalidOpcode, Opcode};
5
6/// VM register index encoded in bytecode.
7pub type Register = u8;
8
9/// Proto constant table index encoded in bytecode.
10pub type ConstantIndex = i32;
11
12/// Packed table-template constant value encoded in bytecode.
13pub type PackedTableValue = i32;
14
15/// Raw bytecode instruction word.
16pub type InstructionWord = u32;
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub struct BytecodeClass {
20    pub class_name: ConstantIndex,
21    pub property_names: Vec<ConstantIndex>,
22    pub method_names: Vec<ConstantIndex>,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct BytecodeImportId(u32);
27
28impl BytecodeImportId {
29    pub fn new(id0: ConstantIndex) -> Self {
30        debug_assert!((0..1024).contains(&id0));
31        Self((1 << 30) | ((id0 as u32) << 20))
32    }
33
34    pub fn from_two(id0: ConstantIndex, id1: ConstantIndex) -> Self {
35        debug_assert!((0..1024).contains(&id0));
36        debug_assert!((0..1024).contains(&id1));
37        Self((2 << 30) | ((id0 as u32) << 20) | ((id1 as u32) << 10))
38    }
39
40    pub fn from_three(id0: ConstantIndex, id1: ConstantIndex, id2: ConstantIndex) -> Self {
41        debug_assert!((0..1024).contains(&id0));
42        debug_assert!((0..1024).contains(&id1));
43        debug_assert!((0..1024).contains(&id2));
44        Self((3 << 30) | ((id0 as u32) << 20) | ((id1 as u32) << 10) | id2 as u32)
45    }
46
47    pub fn from_raw(value: u32) -> Self {
48        Self(value)
49    }
50
51    pub fn raw(self) -> u32 {
52        self.0
53    }
54
55    pub fn components(self) -> Vec<ConstantIndex> {
56        let count = self.0 >> 30;
57        let mut result = Vec::with_capacity(count as usize);
58        if count > 0 {
59            result.push(((self.0 >> 20) & 1023) as ConstantIndex);
60        }
61        if count > 1 {
62            result.push(((self.0 >> 10) & 1023) as ConstantIndex);
63        }
64        if count > 2 {
65            result.push((self.0 & 1023) as ConstantIndex);
66        }
67        result
68    }
69}
70
71#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
72pub struct BytecodeString(BString);
73
74impl BytecodeString {
75    pub fn as_bytes(&self) -> &[u8] {
76        self.0.as_bytes()
77    }
78
79    pub fn len(&self) -> usize {
80        self.0.len()
81    }
82
83    pub fn is_empty(&self) -> bool {
84        self.0.is_empty()
85    }
86}
87
88impl Borrow<[u8]> for BytecodeString {
89    fn borrow(&self) -> &[u8] {
90        self.as_bytes()
91    }
92}
93
94impl From<&[u8]> for BytecodeString {
95    fn from(value: &[u8]) -> Self {
96        Self(BString::from(value))
97    }
98}
99
100impl<const N: usize> From<&[u8; N]> for BytecodeString {
101    fn from(value: &[u8; N]) -> Self {
102        Self::from(value.as_slice())
103    }
104}
105
106impl From<Vec<u8>> for BytecodeString {
107    fn from(value: Vec<u8>) -> Self {
108        Self(BString::new(value))
109    }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
113pub struct ClosureIndex(u32);
114
115impl ClosureIndex {
116    pub fn new(index: u32) -> Self {
117        Self(index)
118    }
119
120    pub fn get(self) -> u32 {
121        self.0
122    }
123
124    pub fn as_usize(self) -> usize {
125        self.0 as usize
126    }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct TableShape {
131    entries: [TableShapeEntry; Self::MAX_LENGTH],
132    length: u8,
133    has_constants: bool,
134}
135
136impl TableShape {
137    pub const MAX_LENGTH: usize = 32;
138
139    pub fn new(entries: Vec<TableShapeEntry>) -> Self {
140        debug_assert!(entries.len() <= Self::MAX_LENGTH);
141        let has_constants = entries.iter().any(|entry| entry.value.is_some());
142        let mut storage = [TableShapeEntry::default(); Self::MAX_LENGTH];
143        let length = entries.len();
144        storage[..length].copy_from_slice(&entries);
145        Self {
146            entries: storage,
147            length: length as u8,
148            has_constants,
149        }
150    }
151
152    pub fn entries(&self) -> &[TableShapeEntry] {
153        &self.entries[..usize::from(self.length)]
154    }
155
156    pub fn len(&self) -> usize {
157        usize::from(self.length)
158    }
159
160    pub fn is_empty(&self) -> bool {
161        self.length == 0
162    }
163
164    pub fn has_constants(&self) -> bool {
165        self.has_constants
166    }
167}
168
169#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
170pub struct TableShapeEntry {
171    pub key: ConstantIndex,
172    pub value: Option<PackedTableValue>,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq)]
176pub struct BytecodeVector {
177    values: [f32; 4],
178}
179
180impl BytecodeVector {
181    pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
182        Self {
183            values: [x, y, z, w],
184        }
185    }
186
187    pub fn x(self) -> f32 {
188        self.values[0]
189    }
190
191    pub fn y(self) -> f32 {
192        self.values[1]
193    }
194
195    pub fn z(self) -> f32 {
196        self.values[2]
197    }
198
199    pub fn w(self) -> f32 {
200        self.values[3]
201    }
202
203    pub fn to_bits(self) -> [u32; 4] {
204        self.values.map(f32::to_bits)
205    }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq)]
209pub struct BytecodeVectorDouble {
210    values: [f64; 4],
211}
212
213impl BytecodeVectorDouble {
214    pub fn new(x: f64, y: f64, z: f64, w: f64) -> Self {
215        Self {
216            values: [x, y, z, w],
217        }
218    }
219
220    pub fn x(self) -> f64 {
221        self.values[0]
222    }
223
224    pub fn y(self) -> f64 {
225        self.values[1]
226    }
227
228    pub fn z(self) -> f64 {
229        self.values[2]
230    }
231
232    pub fn w(self) -> f64 {
233        self.values[3]
234    }
235
236    pub fn to_bits(self) -> [u64; 4] {
237        self.values.map(f64::to_bits)
238    }
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub struct BytecodeTypedLocal {
243    pub ty: u8,
244    pub register: Register,
245    pub start_pc: u32,
246    pub end_pc: u32,
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub struct BytecodeFeedbackSlot {
251    pub pc: u32,
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255pub enum BytecodeFeedbackType {
256    CallTarget,
257}
258
259#[derive(Debug, Clone, PartialEq, Eq)]
260pub struct BytecodeUserdataType {
261    pub name: BytecodeString,
262    pub name_ref: u32,
263    pub used: bool,
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267#[repr(transparent)]
268pub struct Instruction(InstructionWord);
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271#[repr(transparent)]
272pub struct InstructionAux(InstructionWord);
273
274impl Instruction {
275    pub fn new(word: InstructionWord) -> Self {
276        Self(word)
277    }
278
279    pub fn abc(opcode: Opcode, a: u8, b: u8, c: u8) -> Self {
280        Self(opcode.word() | ((a as u32) << 8) | ((b as u32) << 16) | ((c as u32) << 24))
281    }
282
283    pub fn ad(opcode: Opcode, a: u8, d: i16) -> Self {
284        Self(opcode.word() | ((a as u32) << 8) | (((d as u16) as u32) << 16))
285    }
286
287    pub fn ae(opcode: Opcode, e: i32) -> Self {
288        Self(opcode.word() | (((e as u32) & 0x00ff_ffff) << 8))
289    }
290
291    pub fn word(self) -> InstructionWord {
292        self.0
293    }
294
295    pub fn with_d(self, d: i16) -> Self {
296        Self((self.0 & 0x0000_ffff) | (((d as u16) as u32) << 16))
297    }
298
299    pub fn with_c(self, c: u8) -> Self {
300        Self((self.0 & 0x00ff_ffff) | ((c as u32) << 24))
301    }
302
303    /// Decodes the opcode byte when this word is an instruction header.
304    pub fn try_opcode(self) -> Result<Opcode, InvalidOpcode> {
305        let opcode = (self.0 & 0xff) as u8;
306        Opcode::from_byte(opcode).ok_or(InvalidOpcode::new(opcode))
307    }
308
309    /// Decodes the opcode byte without checking that it is in range.
310    ///
311    /// # Safety
312    ///
313    /// The word must be an instruction header from bytecode that has already
314    /// passed opcode validation. AUX words and VM cache/sentinel words must not
315    /// be decoded through this function.
316    pub unsafe fn opcode_unchecked(self) -> Opcode {
317        unsafe { self.try_opcode().unwrap_unchecked() }
318    }
319
320    pub fn a(self) -> u8 {
321        ((self.0 >> 8) & 0xff) as u8
322    }
323
324    pub fn b(self) -> u8 {
325        ((self.0 >> 16) & 0xff) as u8
326    }
327
328    pub fn c(self) -> u8 {
329        ((self.0 >> 24) & 0xff) as u8
330    }
331
332    pub fn d(self) -> i16 {
333        (self.0 >> 16) as u16 as i16
334    }
335
336    pub fn e(self) -> i32 {
337        (self.0 as i32) >> 8
338    }
339
340    /// Computes the jump target for a checked instruction header.
341    pub fn try_jump_target(self, pc: u32) -> Result<Option<i32>, InvalidOpcode> {
342        Ok(self.try_opcode()?.jump_target(self, pc))
343    }
344
345    /// Computes the jump target without checking the opcode byte.
346    ///
347    /// # Safety
348    ///
349    /// The word must satisfy the safety requirements of [`Self::opcode_unchecked`].
350    /// Non-jump opcodes are valid inputs and return `None`.
351    pub unsafe fn jump_target_unchecked(self, pc: u32) -> Option<i32> {
352        unsafe { self.opcode_unchecked().jump_target(self, pc) }
353    }
354}
355
356impl InstructionAux {
357    pub fn new(word: InstructionWord) -> Self {
358        Self(word)
359    }
360
361    pub fn word(self) -> InstructionWord {
362        self.0
363    }
364
365    pub fn a(self) -> u8 {
366        (self.0 & 0xff) as u8
367    }
368
369    pub fn b(self) -> u8 {
370        ((self.0 >> 8) & 0xff) as u8
371    }
372
373    pub fn kv(self) -> u32 {
374        self.0 & 0x00ff_ffff
375    }
376
377    pub fn kb(self) -> bool {
378        (self.0 & 1) != 0
379    }
380
381    pub fn is_negated(self) -> bool {
382        (self.0 >> 31) != 0
383    }
384
385    pub fn kv16(self) -> u16 {
386        (self.0 & 0xffff) as u16
387    }
388
389    pub fn slot(self) -> u16 {
390        (self.0 >> 16) as u16
391    }
392}