sphinx/debug/
dasm.rs

1use core::fmt;
2use core::fmt::{Write, Formatter};
3use core::iter;
4use string_interner::Symbol as _;
5
6use crate::language::FloatType;
7use crate::codegen::OpCode;
8use crate::codegen::chunk::{UnloadedProgram, Chunk};
9use crate::codegen::consts::{Constant, ConstID};
10use crate::codegen::funproto::{UnloadedFunction, FunctionID};
11use crate::debug::symbol::{DebugSymbol, DebugSymbolTable, ResolvedSymbol, ResolvedSymbolTable, ChunkSymbols};
12use crate::debug::symbol::errors::SymbolResolutionError;
13
14
15const PAD_WIDTH: usize = 60;
16
17pub struct Disassembler<'c, 's> {
18    program: &'c UnloadedProgram,
19    symbols: Option<&'s ChunkSymbols>,
20    symbol_table: Option<&'s ResolvedSymbolTable<'s>>,
21}
22
23// helper for Disassembler::try_resolve_symbol()
24type ResolvedSymbolResult<'s> = Result<&'s ResolvedSymbol, &'s SymbolResolutionError>;
25enum Symbol<'s> {
26    // None means "repeat"
27    Unresolved(Option<&'s DebugSymbol>),
28    Resolved(Option<ResolvedSymbolResult<'s>>),
29}
30
31impl<'c, 's> Disassembler<'c, 's> {
32    pub fn new(program: &'c UnloadedProgram) -> Self {
33        Self { program, symbols: None, symbol_table: None }
34    }
35    
36    pub fn with_symbols(mut self, symbols: &'s ChunkSymbols) -> Self {
37        self.symbols.replace(symbols); self
38    }
39    
40    pub fn with_symbol_table(mut self, symbol_table: &'s ResolvedSymbolTable<'s>) -> Self {
41        self.symbol_table.replace(symbol_table); self
42    }
43    
44    pub fn write_disassembly(&self, fmt: &mut impl Write) -> fmt::Result {
45        writeln!(fmt, "\n\nmain:\n")?;
46        let symbols = self.symbols.and_then(|symbols| symbols.get(&Chunk::Main));
47        self.decode_chunk(fmt, self.program.main(), symbols)?;
48        
49        for (chunk_id, chunk) in self.program.iter_chunks() {
50            match chunk_id {
51                Chunk::Function(fun_id) => {
52                    let function = self.program.get_function(fun_id);
53                    let name = function.signature.name
54                        .and_then(|cid| self.try_get_string(cid));
55                    
56                    if let Some(name) = name {
57                        writeln!(fmt, "\n\nchunk {} ({}):\n", fun_id, name)?;
58                    } else {
59                        
60                        writeln!(fmt, "\n\nchunk {}:\n", fun_id)?;
61                    }
62                },
63                
64                _ => {
65                    writeln!(fmt, "\n\nchunk:\n")?;
66                }
67            }
68            
69            let symbols = self.symbols.and_then(|symbols| symbols.get(&chunk_id));
70            self.decode_chunk(fmt, chunk, symbols)?;
71        }
72        
73        Ok(())
74    }
75    
76    fn decode_chunk(&self, fmt: &mut impl Write, chunk: &[u8], symbols: Option<&'s DebugSymbolTable>) -> fmt::Result {
77        let mut symbols = symbols.map(|symbols| symbols.iter().peekable());
78        let mut last_symbol = None;
79        
80        let mut offset = 0;
81        while offset < chunk.len() {
82            let (_, bytes) = chunk.split_at(offset);
83            
84            // get the next unresolved symbol if there are any
85            let unresolved = symbols.as_mut().and_then(|iter| Self::seek_next_symbol(offset, iter));
86            let symbol = self.try_resolve_symbol(unresolved, last_symbol);
87            last_symbol = unresolved;
88            
89            offset = self.decode_instr(fmt, &offset, bytes, symbol)?;
90        }
91        Ok(())
92    }
93    
94    fn seek_next_symbol(offset: usize, symbols: &mut iter::Peekable<impl Iterator<Item=(usize, &'s DebugSymbol)>>) -> Option<&'s DebugSymbol> {
95        while matches!(symbols.peek(), Some((next_offset, _)) if *next_offset < offset) {
96            symbols.next();
97        }
98        
99        match symbols.next() {
100            Some((next_offset, symbol)) if next_offset == offset => Some(symbol),
101            _ => None,
102        }
103    }
104    
105    // handles all the logic around whether we have a symbol table, if there was a symbol resolution error, repeats...
106    fn try_resolve_symbol<'a>(&self, unresolved: Option<&'a DebugSymbol>, last_symbol: Option<&DebugSymbol>) -> Option<Symbol<'a>> where 's: 'a {
107        let resolved = unresolved.and_then(|symbol| self.symbol_table.and_then(
108            |symbol_table| symbol_table.lookup(symbol)
109        ));
110        
111        let is_repeat = last_symbol.and(unresolved).is_some() && last_symbol.unwrap() == unresolved.unwrap();
112
113        if resolved.is_some() {
114            if !is_repeat { Some(Symbol::Resolved(resolved)) }
115            else { Some(Symbol::Resolved(None)) }
116        } else if unresolved.is_some() {
117            if !is_repeat { Some(Symbol::Unresolved(unresolved)) }
118            else { Some(Symbol::Unresolved(None)) }
119        } else { None }
120    }
121
122    fn decode_instr(&self, fmt: &mut impl Write, offset: &usize, instr: &[u8], symbol: Option<Symbol>) -> Result<usize, fmt::Error> {        let mut line = String::new();
123        
124        write!(line, "{:04X} ", offset)?;
125        
126        
127        let opcode = OpCode::from_byte(instr[0]);
128        match opcode {
129            Some(opcode) => match opcode {
130                
131                OpCode::Drop | OpCode::DropLocals => {
132                    let len = instr[1];
133                    write!(line, "{:16} {: >4}", opcode, len)?;
134                }
135                
136                OpCode::LoadConst => {
137                    let cid = ConstID::from(instr[1]);
138                    write!(line, "{:16} {: >4}    ", opcode, cid)?;
139                    self.write_const(&mut line, self.program.get_const(cid))?;
140                },
141                
142                OpCode::LoadConst16 => {
143                    let cid =  ConstID::from_le_bytes(instr[1..=2].try_into().unwrap());
144                    write!(line, "{:16} {: >4}    ", opcode, cid)?;
145                    self.write_const(&mut line, self.program.get_const(cid))?;
146                },
147                
148                OpCode::LoadFunction => {
149                    let fun_id = FunctionID::from(instr[1]);
150                    write!(line, "{:16} {: >4}    ", opcode, fun_id)?;
151                    self.write_function(&mut line, self.program.get_function(fun_id))?;
152                },
153                
154                OpCode::LoadFunction16 => {
155                    let fun_id = FunctionID::from_le_bytes(instr[1..=2].try_into().unwrap());
156                    write!(line, "{:16} {: >4}    ", opcode, fun_id)?;
157                    self.write_function(&mut line, self.program.get_function(fun_id))?;
158                },
159                
160                OpCode::StoreLocal | OpCode::LoadLocal => {
161                    let index = instr[1];
162                    write!(line, "{:16} {: >4}", opcode, index)?;
163                },
164                OpCode::StoreLocal16 | OpCode::LoadLocal16 => {
165                    let index =  u16::from_le_bytes(instr[1..=2].try_into().unwrap());
166                    write!(line, "{:16} {: >4}", opcode, index)?;
167                },
168                
169                OpCode::StoreUpvalue | OpCode::LoadUpvalue => {
170                    let index = instr[1];
171                    write!(line, "{:16} {: >4}", opcode, index)?;
172                }
173                OpCode::StoreUpvalue16 | OpCode::LoadUpvalue16 => {
174                    let index =  u16::from_le_bytes(instr[1..=2].try_into().unwrap());
175                    write!(line, "{:16} {: >4}", opcode, index)?;
176                }
177                
178                OpCode::CloseUpvalue => {
179                    let index = instr[1];
180                    write!(line, "{:16} {: >4}", opcode, index)?;
181                }
182                OpCode::CloseUpvalue16 => {
183                    let index =  u16::from_le_bytes(instr[1..=2].try_into().unwrap());
184                    write!(line, "{:16} {: >4}", opcode, index)?;
185                }
186                
187                OpCode::Tuple => {
188                    let len = instr[1];
189                    write!(line, "{:16} {: >4}", opcode, len)?;
190                }
191                
192                OpCode::UInt8 => {
193                    let value = Constant::Integer(instr[1].into());
194                    write!(line, "{:16}         ", opcode)?;
195                    self.write_const(&mut line, &value)?;
196                }
197                
198                OpCode::Int8 => {
199                    let value = Constant::Integer(i8::from_le_bytes([instr[1]]).into());
200                    write!(line, "{:16}         ", opcode)?;
201                    self.write_const(&mut line, &value)?;
202                }
203                
204                OpCode::Int16 => {
205                    let value = Constant::Integer(i16::from_le_bytes([instr[1], instr[2]]).into());
206                    write!(line, "{:16}         ", opcode)?;
207                    self.write_const(&mut line, &value)?;
208                }
209                
210                OpCode::Jump           |
211                OpCode::JumpIfFalse    |
212                OpCode::JumpIfTrue     |
213                OpCode::PopJumpIfFalse |
214                OpCode::PopJumpIfTrue  => {
215                    let jmp = i16::from_le_bytes(instr[1..=2].try_into().unwrap());
216                    let dest = i128::from(jmp) + i128::try_from(offset + opcode.instr_len()).expect("offset too large");
217                    let relative = i64::from(jmp) + i64::try_from(opcode.instr_len()).unwrap();
218                    write!(line, "{:16} {: >4} -> {:04X}", opcode, relative, dest)?;
219                }
220                
221                OpCode::LongJump           |
222                OpCode::LongJumpIfFalse    |
223                OpCode::LongJumpIfTrue     |
224                OpCode::PopLongJumpIfFalse |
225                OpCode::PopLongJumpIfTrue  => {
226                    let jmp = i32::from_le_bytes(instr[1..=4].try_into().unwrap());
227                    let dest = i128::from(jmp) + i128::try_from(offset + opcode.instr_len()).expect("offset too large");
228                    let relative = i64::from(jmp) + i64::try_from(opcode.instr_len()).unwrap();
229                    write!(line, "{:16} {: >4} -> {:04X}", opcode, relative, dest)?;
230                }
231                
232                opcode => write!(line, "{:16}", opcode)?,
233            },
234            
235            None => write!(line, "Unknown! {:#x}", instr[0])?,
236        }
237        
238        if let Some(symbol) = symbol {
239            if line.len() < PAD_WIDTH {
240                line.extend(iter::repeat(' ').take(PAD_WIDTH - line.len()))
241            }
242            match symbol {
243                Symbol::Unresolved(symbol) => self.write_unresolved_symbol(&mut line, symbol)?,
244                Symbol::Resolved(symbol) => self.write_debug_symbol(&mut line, symbol)?,
245            }
246        }
247        
248        writeln!(fmt, "{}", line)?;
249        
250        Ok(offset + opcode.map_or(1, |op| op.instr_len()))
251    }
252    
253    fn write_unresolved_symbol(&self, fmt: &mut impl fmt::Write, symbol: Option<&DebugSymbol>) -> fmt::Result {
254        match symbol {
255            Some(symbol) => write!(fmt, "| ${}:{}", symbol.start(), symbol.end()),
256            None => write!(fmt, "|"),  // repeats
257        }
258        
259    }
260    
261    fn write_debug_symbol(&self, fmt: &mut impl fmt::Write, symbol: Option<ResolvedSymbolResult>) -> fmt::Result {
262        match symbol {
263            Some(Ok(symbol)) => {
264                write!(fmt, "{: >4}| ", symbol.lineno())?;
265                
266                let line = symbol.iter_whole_lines().next().unwrap_or("").trim_end();
267                if symbol.is_multiline() {
268                    let (before, sym_text) = line.split_at(symbol.start());
269                    write!(fmt, "{}`{}...`", before, sym_text)
270                } else {
271                    let (before, rest) = line.split_at(symbol.start());
272                    let (sym_text, after) = rest.split_at(symbol.end() - symbol.start());
273                    write!(fmt, "{}`{}`{}", before, sym_text, after)
274                }
275            },
276            
277            Some(Err(error)) => write!(fmt, "   ERROR: {}", error),
278            
279            None => write!(fmt, "    |"),
280        }
281    }
282    
283    fn write_const(&self, fmt: &mut impl fmt::Write, value: &Constant) -> fmt::Result {
284        if let Constant::String(index) = value {
285            let string = self.program.get_string(*index);
286            if string.len() > 16 {
287                return write!(fmt, "\"{}...\"", &string[..13]);
288            }
289            return write!(fmt, "\"{}\"", string);
290        }
291        
292        write!(fmt, "{}", value)
293    }
294    
295    fn write_function(&self, fmt: &mut impl fmt::Write, function: &UnloadedFunction) -> fmt::Result {
296        if let Some(name) = function.signature.name.and_then(|cid| self.try_get_string(cid)) {
297            write!(fmt, "'fun {}()'", name)
298        } else {
299            write!(fmt, "'fun()'")
300        }
301    }
302    
303    fn try_get_string(&self, cid: ConstID) -> Option<&str> {
304        match self.program.get_const(cid) {
305            Constant::String(index) => Some(self.program.get_string(*index)),
306            _ => None,
307        }
308    }
309}
310
311impl fmt::Display for Disassembler<'_, '_> {
312    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
313        self.write_disassembly(fmt)
314    }
315}
316
317
318impl fmt::Display for Constant {
319    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
320        match self {
321            Self::Integer(value) => write!(fmt, "'{}'", value),
322            Self::Float(bytes) => write!(fmt, "'{:.6}'", FloatType::from_le_bytes(*bytes)),
323            Self::String(symbol) => write!(fmt, "${}", symbol.to_usize() + 1),
324            Self::Error { error, .. } => write!(fmt, "{:?}", error),
325        }
326    }
327}
328
329impl fmt::Display for UnloadedFunction {
330    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
331        write!(fmt, "chunk {}", self.fun_id)
332    }
333}