luau_bytecode/builder/
emit.rs1use 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}