Skip to main content

luau_bytecode/builder/
emit.rs

1use super::support::{BytecodeBuilderLocal, BytecodeBuilderScratch, Jump};
2use super::*;
3use crate::model::{Instruction, InstructionWord, Register};
4use crate::opcodes::Opcode;
5
6impl<'src> BytecodeBuilder<'src> {
7    pub fn emit_abc(&mut self, opcode: Opcode, a: u8, b: u8, c: u8) {
8        self.emit(Instruction::abc(opcode, a, b, c));
9    }
10
11    pub fn set_debug_function_name(&mut self, name: impl Into<BytecodeStringRef<'src>>) {
12        let name = name.into();
13        let index = self.add_string_table_entry(&name);
14        let dump_enabled = self.dump_enabled;
15        let function = self.current_function_meta();
16        function.debug_name = Some(index);
17        if dump_enabled {
18            function.dump_name.clear();
19            function.dump_name.extend_from_slice(name.as_bytes());
20        }
21    }
22
23    pub fn set_debug_function_line_defined(&mut self, line: i32) {
24        self.current_function_meta().line_defined = line;
25    }
26
27    pub fn add_flags(&mut self, flags: u8) {
28        self.current_function_meta().flags |= flags;
29    }
30
31    pub fn push_open_debug_local(
32        &mut self,
33        name: impl Into<BytecodeStringRef<'src>>,
34        register: Register,
35    ) {
36        let start_pc = self.current_function().code.len() as u32;
37        self.push_debug_local(name, register, start_pc, u32::MAX);
38    }
39
40    pub fn push_debug_local(
41        &mut self,
42        name: impl Into<BytecodeStringRef<'src>>,
43        register: Register,
44        start_pc: u32,
45        end_pc: u32,
46    ) {
47        let name = name.into();
48        let name_ref = self.add_string_table_entry(&name);
49        self.current_function()
50            .local_vars
51            .push(BytecodeBuilderLocal {
52                name: name_ref,
53                start_pc,
54                end_pc,
55                register,
56            });
57    }
58
59    pub fn close_debug_locals_from(&mut self, register: Register) {
60        let end_pc = self.current_function().code.len() as u32;
61        for local in self
62            .current_function()
63            .local_vars
64            .iter_mut()
65            .filter(|local| local.register >= register && local.end_pc == u32::MAX)
66        {
67            local.end_pc = end_pc;
68        }
69    }
70
71    pub fn push_debug_upvalue(&mut self, name: impl Into<BytecodeStringRef<'src>>) {
72        let name = name.into();
73        let name_ref = self.add_string_table_entry(&name);
74        self.current_function().upvalues.push(name_ref);
75    }
76
77    pub fn emit_ad(&mut self, opcode: Opcode, a: u8, d: i16) {
78        self.emit(Instruction::ad(opcode, a, d));
79    }
80
81    pub fn emit_e(&mut self, opcode: Opcode, e: i32) {
82        self.emit(Instruction::ae(opcode, e));
83    }
84
85    pub fn set_debug_line(&mut self, line: usize) {
86        self.current_line = line.try_into().unwrap_or(i32::MAX);
87    }
88
89    pub fn current_line(&self) -> i32 {
90        self.current_line
91    }
92
93    pub fn restore_line(&mut self, line: i32) {
94        self.current_line = line;
95    }
96
97    pub fn emit_aux(&mut self, aux: InstructionWord) {
98        let line = self.current_line;
99        self.current_function().code.push(Instruction::new(aux));
100        self.current_function().lines.push(line);
101    }
102
103    pub fn undo_emit(&mut self, opcode: Opcode) {
104        let instruction = self
105            .current_function()
106            .code
107            .pop()
108            .expect("BytecodeBuilder::undo_emit requires an emitted instruction");
109        debug_assert_eq!(unsafe { instruction.opcode_unchecked() }, opcode);
110        self.current_function().lines.pop();
111    }
112
113    pub fn emit_label(&mut self) -> usize {
114        self.current_function().code.len()
115    }
116
117    pub fn patch_jump_d(&mut self, jump_label: usize, target_label: usize) -> bool {
118        let offset = target_label as isize - jump_label as isize - 1;
119        let jump_instruction = self
120            .current_function_ref()
121            .code
122            .get(jump_label)
123            .copied()
124            .expect("jump label must point at an emitted instruction");
125        debug_assert!(unsafe { jump_instruction.opcode_unchecked() }.is_jump_d());
126        debug_assert_eq!(jump_instruction.d(), 0);
127        debug_assert!(target_label <= self.current_function_ref().code.len());
128
129        if let Ok(offset) = i16::try_from(offset) {
130            let instruction = self
131                .current_function()
132                .code
133                .get_mut(jump_label)
134                .expect("jump label must point at an emitted instruction");
135            *instruction = instruction.with_d(offset);
136        } else if offset.unsigned_abs() < (1 << 23) {
137            self.current_function().has_long_jumps = true;
138        } else {
139            return false;
140        }
141
142        self.current_function().jumps.push(Jump {
143            source: jump_label,
144            target: target_label,
145        });
146        true
147    }
148
149    pub fn patch_jump_e(&mut self, jump_label: usize, target_label: usize) -> bool {
150        let offset = target_label as isize - jump_label as isize - 1;
151        let jump_instruction = self
152            .current_function_ref()
153            .code
154            .get(jump_label)
155            .copied()
156            .expect("jump label must point at an emitted instruction");
157        debug_assert_eq!(
158            unsafe { jump_instruction.opcode_unchecked() },
159            Opcode::JumpX
160        );
161        debug_assert_eq!(jump_instruction.e(), 0);
162        debug_assert!(target_label <= self.current_function_ref().code.len());
163
164        if !(-(1 << 23)..(1 << 23)).contains(&offset) {
165            return false;
166        }
167
168        let instruction = self
169            .current_function()
170            .code
171            .get_mut(jump_label)
172            .expect("jump label must point at an emitted instruction");
173        *instruction = Instruction::ae(Opcode::JumpX, offset as i32);
174        true
175    }
176
177    pub fn patch_skip_c(&mut self, jump_label: usize, target_label: usize) -> bool {
178        let offset = target_label as isize - jump_label as isize - 1;
179        let jump_instruction = self
180            .current_function_ref()
181            .code
182            .get(jump_label)
183            .copied()
184            .expect("jump label must point at an emitted instruction");
185        debug_assert!(
186            unsafe { jump_instruction.opcode_unchecked() }.is_skip_c()
187                || unsafe { jump_instruction.opcode_unchecked() }.is_fast_call()
188        );
189        debug_assert_eq!(jump_instruction.c(), 0);
190        let Ok(offset) = u8::try_from(offset) else {
191            return false;
192        };
193
194        let instruction = self
195            .current_function()
196            .code
197            .get_mut(jump_label)
198            .expect("jump label must point at an emitted instruction");
199        *instruction = instruction.with_c(offset);
200        true
201    }
202
203    pub fn patch_aux(&mut self, target_aux: usize, value: i32) {
204        let instruction = self
205            .current_function()
206            .code
207            .get_mut(target_aux)
208            .expect("aux patch target must point at an emitted instruction");
209        *instruction = Instruction::new(value as u32);
210    }
211
212    pub fn fold_jumps(&mut self) {
213        if self.current_function_ref().has_long_jumps {
214            return;
215        }
216
217        for jump_index in 0..self.current_function_ref().jumps.len() {
218            let jump_label = self.current_function_ref().jumps[jump_index].source;
219            let jump_instruction = self.current_function().code[jump_label];
220            let mut target_label: usize =
221                (jump_label as isize + 1 + isize::from(jump_instruction.d()))
222                    .try_into()
223                    .expect("jump target must be non-negative");
224            let mut target_instruction = self.current_function().code[target_label];
225
226            while unsafe { target_instruction.opcode_unchecked() } == Opcode::Jump
227                && target_instruction.d() >= 0
228            {
229                target_label = (target_label as isize + 1 + isize::from(target_instruction.d()))
230                    .try_into()
231                    .expect("jump target must be non-negative");
232                target_instruction = self.current_function().code[target_label];
233            }
234
235            let offset = target_label as isize - jump_label as isize - 1;
236            let instruction = self
237                .current_function()
238                .code
239                .get_mut(jump_label)
240                .expect("jump label must point at an emitted instruction");
241
242            if unsafe { jump_instruction.opcode_unchecked() } == Opcode::Jump
243                && unsafe { target_instruction.opcode_unchecked() } == Opcode::Return
244            {
245                *instruction = target_instruction;
246            } else if let Ok(offset) = i16::try_from(offset) {
247                *instruction = instruction.with_d(offset);
248            }
249
250            self.current_function().jumps[jump_index].target = target_label;
251        }
252    }
253
254    pub fn expand_jumps(&mut self) {
255        if !self.current_function_ref().has_long_jumps {
256            return;
257        }
258
259        const MAX_JUMP_DISTANCE_CONSERVATIVE: isize = 32767 / 3;
260
261        let (jumps, old_code, old_lines) = {
262            let function = self.current_function();
263            function.jumps.sort_by_key(|jump| jump.source);
264            (
265                std::mem::take(&mut function.jumps),
266                std::mem::take(&mut function.code),
267                std::mem::take(&mut function.lines),
268            )
269        };
270        let mut remap = vec![0usize; old_code.len()];
271        let mut new_code = Vec::with_capacity(old_code.len());
272        let mut new_lines = Vec::with_capacity(old_lines.len());
273
274        let mut current_jump = 0usize;
275        let mut pending_trampolines = 0usize;
276
277        let mut pc = 0usize;
278        while pc < old_code.len() {
279            let instruction = old_code[pc];
280            if current_jump < jumps.len() && jumps[current_jump].source == pc {
281                let offset =
282                    jumps[current_jump].target as isize - jumps[current_jump].source as isize - 1;
283
284                if offset.abs() > MAX_JUMP_DISTANCE_CONSERVATIVE {
285                    new_code.push(Instruction::ad(Opcode::Jump, 0, 1));
286                    new_code.push(Instruction::ae(Opcode::JumpX, 0));
287                    new_lines.push(old_lines[pc]);
288                    new_lines.push(old_lines[pc]);
289                    pending_trampolines += 1;
290                }
291
292                current_jump += 1;
293            }
294
295            let opcode = unsafe { instruction.opcode_unchecked() };
296            for word_pc in pc..pc + opcode.length() {
297                remap[word_pc] = new_code.len();
298                new_code.push(old_code[word_pc]);
299                new_lines.push(old_lines[word_pc]);
300            }
301            pc += opcode.length();
302        }
303
304        for jump in &jumps {
305            let offset = jump.target as isize - jump.source as isize - 1;
306            let new_offset = remap[jump.target] as isize - remap[jump.source] as isize - 1;
307
308            if offset.abs() > MAX_JUMP_DISTANCE_CONSERVATIVE {
309                let trampoline = remap[jump.source] - 1;
310                new_code[trampoline] = Instruction::ae(Opcode::JumpX, (new_offset + 1) as i32);
311                new_code[remap[jump.source]] = new_code[remap[jump.source]].with_d(-2);
312                pending_trampolines -= 1;
313            } else {
314                let new_offset =
315                    i16::try_from(new_offset).expect("expanded jump offset must fit i16");
316                new_code[remap[jump.source]] = new_code[remap[jump.source]].with_d(new_offset);
317            }
318        }
319
320        debug_assert_eq!(pending_trampolines, 0);
321
322        let function = self.current_function();
323        function.jumps = jumps;
324        function.code = new_code;
325        function.lines = new_lines;
326        for local in &mut function.local_vars {
327            local.end_pc = if local.start_pc != local.end_pc {
328                remap[local.end_pc as usize - 1] as u32 + 1
329            } else {
330                remap[local.end_pc as usize] as u32
331            };
332            local.start_pc = remap[local.start_pc as usize] as u32;
333        }
334        for local in &mut function.local_types {
335            local.end_pc = if local.start_pc != local.end_pc {
336                remap[local.end_pc as usize - 1] as u32 + 1
337            } else {
338                remap[local.end_pc as usize] as u32
339            };
340            local.start_pc = remap[local.start_pc as usize] as u32;
341        }
342    }
343
344    pub fn get_instruction_count(&self) -> usize {
345        self.current_function_ref().code.len()
346    }
347
348    pub fn get_total_instruction_count(&self) -> usize {
349        self.total_instruction_count
350    }
351
352    pub fn get_debug_pc(&self) -> u32 {
353        self.get_instruction_count() as u32
354    }
355
356    pub(super) fn current_function(&mut self) -> &mut BytecodeBuilderScratch<'src> {
357        &mut self.scratch
358    }
359
360    pub(super) fn current_function_ref(&self) -> &BytecodeBuilderScratch<'src> {
361        &self.scratch
362    }
363
364    pub(super) fn current_function_meta(&mut self) -> &mut BytecodeBuilderFunction {
365        let id = self.current_function_id();
366        &mut self.functions[id]
367    }
368
369    pub(super) fn current_function_id(&self) -> usize {
370        self.current_function
371            .expect("bytecode emission requires an active function")
372    }
373
374    pub(super) fn emit(&mut self, instruction: Instruction) {
375        let line = self.current_line;
376        let proto = self.current_function();
377        proto.code.push(instruction);
378        proto.lines.push(line);
379    }
380}