1use luau_common::{BString, ByteSlice};
2
3use super::support::{BuilderClosureNames, BytecodeBuilderScratch};
4use super::*;
5use crate::dump::{BytecodeDebugRemark, dump_constants, dump_function};
6use crate::opcodes::BytecodeTypeTag;
7use std::io::Write;
8
9impl<'src> BytecodeBuilder<'src> {
10 pub fn set_dump_flags(&mut self, flags: BytecodeDumpFlags) {
11 self.dump_flags = flags;
12 self.dump_enabled = true;
13 }
14
15 pub fn set_dump_source(&mut self, source: impl AsRef<[u8]>) {
16 self.dump_source.clear();
17
18 for line in source.as_ref().split(|byte| *byte == b'\n') {
19 self.dump_source
20 .push(BString::from(line.strip_suffix(b"\r").unwrap_or(line)));
21 }
22 }
23
24 pub fn needs_debug_remarks(&self) -> bool {
25 self.dump_flags.remarks()
26 }
27
28 pub fn add_debug_remark(&mut self, text: impl AsRef<[u8]>) {
29 if !self.needs_debug_remarks() {
30 return;
31 }
32
33 let remark = StoredDebugRemark {
34 pc: self.scratch.code.len(),
35 line: self.current_line,
36 text: BString::from(text.as_ref()),
37 };
38 self.scratch.debug_remarks.push(remark.clone());
39 self.dump_remarks.push((remark.line, remark.text.clone()));
40 }
41
42 pub fn dump_function(&self, id: usize) -> BString {
43 self.functions[id].dump.clone()
44 }
45
46 pub fn dump_everything(&self) -> BString {
47 let mut result = Vec::new();
48
49 for (index, function) in self.functions.iter().enumerate() {
50 write!(result, "Function {index} (").unwrap();
51 if function.dump_name.is_empty() {
52 result.extend_from_slice(b"??");
53 } else {
54 result.extend_from_slice(&function.dump_name);
55 }
56 result.extend_from_slice(b"):\n");
57 result.extend_from_slice(&function.dump);
58 result.push(b'\n');
59 }
60
61 BString::new(result)
62 }
63
64 pub fn dump_source_remarks(&self) -> BString {
65 let mut result = Vec::new();
66 let mut remarks = self
67 .dump_remarks
68 .iter()
69 .map(|(line, text)| (*line, text.as_bstr()))
70 .collect::<Vec<_>>();
71 remarks.sort();
72
73 let mut next_remark = 0usize;
74 for (index, line) in self.dump_source.iter().enumerate() {
75 let line_number = i32::try_from(index + 1).unwrap_or(i32::MAX);
76 let indent = line
77 .iter()
78 .take_while(|byte| matches!(byte, b' ' | b'\t'))
79 .count();
80
81 while remarks
82 .get(next_remark)
83 .is_some_and(|remark| remark.0 == line_number)
84 {
85 result.extend_from_slice(&line[..indent]);
86 result.extend_from_slice(b"-- remark: ");
87 result.extend_from_slice(remarks[next_remark].1);
88 result.push(b'\n');
89 next_remark += 1;
90
91 while next_remark < remarks.len()
92 && remarks[next_remark] == remarks[next_remark - 1]
93 {
94 next_remark += 1;
95 }
96 }
97
98 result.extend_from_slice(line);
99 if index + 1 < self.dump_source.len() {
100 result.push(b'\n');
101 }
102 }
103
104 BString::new(result)
105 }
106
107 pub fn dump_type_info(&self) -> BString {
108 let mut result = Vec::new();
109
110 for (index, function) in self.functions.iter().enumerate() {
111 if function.type_info.is_empty() {
112 continue;
113 }
114
115 debug_assert_eq!(function.type_info[0], BytecodeTypeTag::Function as u8);
116 debug_assert!(function.type_info.len() >= 2);
117 let num_params = function.type_info[1];
118 debug_assert!(usize::from(num_params) + 1 < function.type_info.len());
119
120 write!(result, "{index}: function(").unwrap();
121 for parameter in 0..num_params {
122 let ty = function.type_info[2 + usize::from(parameter)];
123 result.extend_from_slice(&self.type_name(ty));
124 if parameter + 1 != num_params {
125 result.extend_from_slice(b", ");
126 }
127 }
128 result.extend_from_slice(b")\n");
129 }
130
131 BString::new(result)
132 }
133
134 pub fn annotate_instruction(
135 &self,
136 result: &mut BString,
137 function_id: u32,
138 instruction_pc: u32,
139 ) {
140 if !self.dump_flags.code() {
141 return;
142 }
143
144 let function = &self.functions[function_id as usize];
145 let offsets = &function.dump_instruction_offsets;
146 let mut next = instruction_pc as usize + 1;
147 debug_assert!(next < offsets.len());
148
149 while next < offsets.len() && offsets[next] == -1 {
150 next += 1;
151 }
152
153 let start = offsets[instruction_pc as usize] as usize;
154 let end = offsets[next] as usize;
155 result.extend_from_slice(&function.dump[start..end]);
156 }
157
158 pub(super) fn dump_current_function(&self, id: usize) -> (BString, Vec<i32>) {
159 if !self.dump_flags.code() && !self.dump_flags.constants() {
160 return (BString::default(), Vec::new());
161 }
162
163 let mut result = Vec::new();
164 let mut offsets = Vec::new();
165 let function = &self.functions[id];
166 let closure_names = BuilderClosureNames {
167 functions: &self.functions,
168 };
169
170 if self.dump_flags.locals() {
171 result.extend_from_slice(&self.dump_locals(function, &self.scratch));
172 }
173
174 if self.dump_flags.types() {
175 result.extend_from_slice(&self.dump_types(function, &self.scratch));
176 }
177
178 if self.dump_flags.constants() {
179 let borrowed_function = self
180 .scratch
181 .borrowed_wire(&self.debug_strings, &self.class_shapes);
182 result.extend_from_slice(&dump_constants(&borrowed_function, &closure_names));
183 }
184
185 let remarks = if self.dump_flags.remarks() {
186 self.scratch
187 .debug_remarks
188 .iter()
189 .map(|remark| BytecodeDebugRemark {
190 pc: remark.pc,
191 text: remark.text.as_bstr(),
192 })
193 .collect::<Vec<_>>()
194 } else {
195 Vec::new()
196 };
197
198 if self.dump_flags.code() {
199 let source_lines = self.dump_flags.source().then_some(
200 self.dump_source
201 .iter()
202 .map(|line| line.as_bstr())
203 .collect::<Vec<_>>(),
204 );
205 let code_start = i32::try_from(result.len()).unwrap_or(i32::MAX);
206 let borrowed_function = self
207 .scratch
208 .borrowed_wire(&self.debug_strings, &self.class_shapes);
209 let (dump, mut dump_offsets) = dump_function(
210 &borrowed_function,
211 source_lines.as_deref(),
212 self.dump_flags.lines(),
213 &remarks,
214 &closure_names,
215 );
216 for offset in &mut dump_offsets {
217 if *offset != -1 {
218 *offset = offset.saturating_add(code_start);
219 }
220 }
221 result.extend_from_slice(&dump);
222 offsets = dump_offsets;
223 }
224
225 (BString::new(result), offsets)
226 }
227
228 fn dump_locals(
229 &self,
230 _function: &BytecodeBuilderFunction,
231 scratch: &BytecodeBuilderScratch<'src>,
232 ) -> Vec<u8> {
233 let mut result = Vec::new();
234
235 for (index, local) in scratch.local_vars.iter().enumerate() {
236 if local.start_pc == local.end_pc {
237 let line = scratch.lines[local.start_pc as usize];
238 writeln!(
239 result,
240 "local {index}: reg {}, start pc {} line {line}, no live range",
241 local.register, local.start_pc
242 )
243 .unwrap();
244 } else {
245 let end_pc = local.end_pc - 1;
246 let start_line = scratch.lines[local.start_pc as usize];
247 let end_line = scratch.lines[end_pc as usize];
248 writeln!(
249 result,
250 "local {index}: reg {}, start pc {} line {start_line}, end pc {end_pc} line {end_line}",
251 local.register, local.start_pc
252 )
253 .unwrap();
254 }
255 }
256
257 result
258 }
259
260 fn dump_types(
261 &self,
262 function: &BytecodeBuilderFunction,
263 scratch: &BytecodeBuilderScratch<'src>,
264 ) -> Vec<u8> {
265 let mut result = Vec::new();
266
267 for (index, ty) in function.type_info.iter().copied().enumerate().skip(2) {
268 write!(result, "R{}: ", index - 2).unwrap();
269 result.extend_from_slice(&self.type_name(ty));
270 result.extend_from_slice(b" [argument]\n");
271 }
272
273 for (index, ty) in scratch.upvalue_types.iter().copied().enumerate() {
274 write!(result, "U{index}: ").unwrap();
275 result.extend_from_slice(&self.type_name(ty));
276 result.push(b'\n');
277 }
278
279 for local in &scratch.local_types {
280 write!(result, "R{}: ", local.register).unwrap();
281 result.extend_from_slice(&self.type_name(local.ty));
282 writeln!(result, " from {} to {}", local.start_pc, local.end_pc).unwrap();
283 }
284
285 result
286 }
287
288 fn type_name(&self, ty: u8) -> Vec<u8> {
289 let optional = ty & BytecodeTypeTag::OptionalBit as u8 != 0;
290 let tag = ty & !(BytecodeTypeTag::OptionalBit as u8);
291 if (BytecodeTypeTag::TaggedUserdataBase as u8..BytecodeTypeTag::TaggedUserdataEnd as u8)
292 .contains(&tag)
293 {
294 let index = usize::from(tag - BytecodeTypeTag::TaggedUserdataBase as u8);
295 if let Some(userdata) = self.userdata_types.get(index) {
296 let mut result = userdata.name.as_bytes().to_vec();
297 if optional {
298 result.push(b'?');
299 }
300 return result;
301 }
302 }
303
304 let name = match tag {
305 tag if tag == BytecodeTypeTag::Nil as u8 => "nil",
306 tag if tag == BytecodeTypeTag::Boolean as u8 => "boolean",
307 tag if tag == BytecodeTypeTag::Number as u8 => "number",
308 tag if tag == BytecodeTypeTag::Integer as u8 => "integer",
309 tag if tag == BytecodeTypeTag::String as u8 => "string",
310 tag if tag == BytecodeTypeTag::Table as u8 => "table",
311 tag if tag == BytecodeTypeTag::Function as u8 => "function",
312 tag if tag == BytecodeTypeTag::Thread as u8 => "thread",
313 tag if tag == BytecodeTypeTag::Userdata as u8 => "userdata",
314 tag if tag == BytecodeTypeTag::Vector as u8 => "vector",
315 tag if tag == BytecodeTypeTag::Buffer as u8 => "buffer",
316 tag if tag == BytecodeTypeTag::Any as u8 => "any",
317 _ => return format!("unknown({tag})").into_bytes(),
318 };
319 let mut result = name.as_bytes().to_vec();
320 if optional {
321 result.push(b'?');
322 }
323 result
324 }
325}