Skip to main content

luau_bytecode/builder/
debug.rs

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}