gbf_core/basic_block.rs
1#![deny(missing_docs)]
2
3use std::{
4 fmt::{self, Write},
5 ops::{Deref, Index},
6 vec,
7};
8
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 cfg_dot::RenderableNode,
13 instruction::Instruction,
14 utils::{
15 GBF_BLUE, GBF_GREEN, GBF_RED, GBF_YELLOW, Gs2BytecodeAddress, OPERAND_TRUNCATE_LENGTH,
16 html_encode,
17 },
18};
19
20/// Represents the type of basic block.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
22pub enum BasicBlockType {
23 /// Used for blocks that are entry blocks of a function.
24 Entry,
25 /// Used for blocks that are exit blocks of a function.
26 Exit,
27 /// Used for blocks that are neither entry nor exit blocks to a function.
28 Normal,
29 /// Used for blocks that are both entry and exit blocks. This is
30 /// possible when a function has a single block, or when a block
31 /// is both the entry and exit block of a function.
32 ///
33 /// Example:
34 /// ```js, no_run
35 /// function onCreated()
36 /// {
37 /// temp.foo = 1;
38 /// return temp.foo == 1 ? 1 : 0;
39 /// }
40 /// ```
41 EntryAndExit,
42 /// Special case for a block that is at the end of a module
43 ModuleEnd,
44}
45
46/// Represents the identifier of a basic block.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
48pub struct BasicBlockId {
49 index: usize,
50
51 /// The type of the basic block.
52 pub block_type: BasicBlockType,
53
54 /// The offset of the block
55 pub address: Gs2BytecodeAddress,
56}
57
58/// Represents the edge type between two basic blocks.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
60pub enum BasicBlockConnectionType {
61 /// The connection represents a conditional branch.
62 Conditional,
63
64 /// The edge represents a fallthrough.
65 Fallthrough,
66
67 /// The edge represents an unconditional branch.
68 Unconditional,
69
70 /// The edge represents the start of a "With" block.
71 With,
72
73 /// The edge represents the start of a "ForEach" block.
74 ForEach,
75
76 /// The edge represents a short-circuit
77 ShortCircuit,
78}
79
80/// Represents an edge between two basic blocks.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
82pub struct BasicBlockConnection {
83 /// The type of the connection.
84 pub connection_type: BasicBlockConnectionType,
85}
86
87impl BasicBlockId {
88 /// Create a new `BasicBlockId`.
89 ///
90 /// # Arguments
91 /// - `index`: The index of the basic block in the function.
92 ///
93 /// # Returns
94 /// - A new `BasicBlockId` instance.
95 ///
96 /// Example
97 /// ```
98 /// use gbf_core::basic_block::BasicBlockId;
99 /// use gbf_core::basic_block::BasicBlockType;
100 ///
101 /// let block = BasicBlockId::new(0, BasicBlockType::Normal, 0);
102 /// ```
103 pub fn new(index: usize, block_type: BasicBlockType, offset: Gs2BytecodeAddress) -> Self {
104 Self {
105 index,
106 block_type,
107 address: offset,
108 }
109 }
110}
111
112/// Represents a basic block in a function.
113#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
114pub struct BasicBlock {
115 /// The identifier of the basic block.
116 pub id: BasicBlockId,
117 /// The instructions in the basic block.
118 pub instructions: Vec<Instruction>,
119}
120
121impl BasicBlock {
122 /// Create a new `BasicBlock`.
123 ///
124 /// # Arguments
125 /// - `id`: The `BasicBlockId` of the block.
126 ///
127 /// # Returns
128 /// - A new `BasicBlock` instance.
129 ///
130 /// # Example
131 /// ```
132 /// use gbf_core::basic_block::{BasicBlock, BasicBlockId, BasicBlockType};
133 ///
134 /// let block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 0));
135 /// ```
136 pub fn new(id: BasicBlockId) -> Self {
137 Self {
138 id,
139 instructions: Vec::new(),
140 }
141 }
142
143 /// Add an instruction to the basic block.
144 ///
145 /// # Arguments
146 /// - `instruction`: The instruction to add.
147 ///
148 /// # Example
149 /// ```
150 /// use gbf_core::basic_block::{BasicBlock, BasicBlockId, BasicBlockType};
151 /// use gbf_core::instruction::Instruction;
152 /// use gbf_core::opcode::Opcode;
153 /// use gbf_core::operand::Operand;
154 ///
155 /// let mut block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 0));
156 /// block.add_instruction(Instruction::new_with_operand(Opcode::PushNumber, 0, Operand::new_number(42)));
157 /// ```
158 pub fn add_instruction(&mut self, instruction: Instruction) {
159 self.instructions.push(instruction);
160 }
161
162 /// Gets the last instruction in the block.
163 ///
164 /// # Returns
165 /// - A reference to the last instruction in the block
166 ///
167 /// # Example
168 /// ```
169 /// use gbf_core::basic_block::{BasicBlock, BasicBlockId, BasicBlockType};
170 /// use gbf_core::instruction::Instruction;
171 /// use gbf_core::opcode::Opcode;
172 ///
173 /// let mut block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 0));
174 /// block.add_instruction(Instruction::new(Opcode::PushNumber, 0));
175 /// block.add_instruction(Instruction::new(Opcode::Ret, 1));
176 /// let last_instruction = block.last_instruction();
177 /// assert_eq!(last_instruction.unwrap().opcode, Opcode::Ret);
178 /// ```
179 pub fn last_instruction(&self) -> Option<&Instruction> {
180 self.instructions.last()
181 }
182
183 /// Get the number of instructions in the block.
184 ///
185 /// # Returns
186 /// - The number of instructions in the block.
187 ///
188 /// # Example
189 /// ```
190 /// use gbf_core::basic_block::{BasicBlock, BasicBlockId, BasicBlockType};
191 ///
192 /// let mut block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 0));
193 /// assert_eq!(block.len(), 0);
194 /// ```
195 pub fn len(&self) -> usize {
196 self.instructions.len()
197 }
198
199 /// Check if the block is empty.
200 ///
201 /// # Returns
202 /// - `true` if the block is empty, `false` otherwise.
203 ///
204 /// # Example
205 /// ```
206 /// use gbf_core::basic_block::{BasicBlock, BasicBlockId, BasicBlockType};
207 ///
208 /// let mut block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 0));
209 /// assert!(block.is_empty());
210 /// ```
211 pub fn is_empty(&self) -> bool {
212 self.instructions.is_empty()
213 }
214}
215
216// == Implementations ==
217impl fmt::Display for BasicBlockId {
218 /// Display the `BasicBlockId`.
219 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
220 write!(f, "Block at address 0x{:X}", self.address)
221 }
222}
223
224impl Default for BasicBlockId {
225 /// Create a default `BasicBlockId`.
226 fn default() -> Self {
227 Self {
228 index: 0,
229 block_type: BasicBlockType::Normal,
230 address: 0,
231 }
232 }
233}
234
235/// Implement Deref
236impl Deref for BasicBlock {
237 type Target = Vec<Instruction>;
238
239 /// Get a reference to the instructions in the block.
240 ///
241 /// # Returns
242 /// - A reference to the instructions in the block.
243 fn deref(&self) -> &Self::Target {
244 &self.instructions
245 }
246}
247
248impl Index<usize> for BasicBlock {
249 type Output = Instruction;
250
251 /// Get an instruction by index.
252 ///
253 /// # Arguments
254 /// - `index`: The index of the instruction to get.
255 ///
256 /// # Returns
257 /// - A reference to the instruction at the given index.
258 fn index(&self, index: usize) -> &Self::Output {
259 &self.instructions[index]
260 }
261}
262
263/// IntoIterator implementation immutable reference
264impl<'a> IntoIterator for &'a BasicBlock {
265 type Item = &'a Instruction;
266 type IntoIter = vec::IntoIter<&'a Instruction>;
267
268 /// Get an iterator over the instructions in the block.
269 ///
270 /// # Returns
271 /// - An iterator over the instructions in the block.
272 fn into_iter(self) -> Self::IntoIter {
273 self.instructions.iter().collect::<Vec<_>>().into_iter()
274 }
275}
276
277/// IntoIterator implementation mutable reference
278impl<'a> IntoIterator for &'a mut BasicBlock {
279 type Item = &'a mut Instruction;
280 type IntoIter = vec::IntoIter<&'a mut Instruction>;
281
282 /// Get a mutable iterator over the instructions in the block.
283 ///
284 /// # Returns
285 /// - A mutable iterator over the instructions in the block.
286 fn into_iter(self) -> Self::IntoIter {
287 self.instructions.iter_mut().collect::<Vec<_>>().into_iter()
288 }
289}
290
291impl RenderableNode for BasicBlock {
292 /// Render the block's node representation for Graphviz with customizable padding.
293 ///
294 /// # Arguments
295 ///
296 /// * `padding` - The number of spaces to use for indentation.
297 fn render_node(&self, padding: usize) -> String {
298 let mut label = String::new();
299 let indent = " ".repeat(padding);
300
301 // Start the HTML-like table for Graphviz.
302 writeln!(
303 &mut label,
304 r#"{indent}<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" CELLPADDING="0">"#,
305 indent = indent
306 )
307 .unwrap();
308
309 if self.id.block_type == BasicBlockType::ModuleEnd {
310 writeln!(
311 &mut label,
312 r#"{indent} <TR>
313{indent} <TD ALIGN="CENTER"><FONT COLOR="{GBF_RED}">Module End</FONT></TD>
314{indent} </TR>"#,
315 indent = indent
316 )
317 .unwrap();
318 } else {
319 // Render each instruction as a table row with indentation.
320 for inst in &self.instructions {
321 // Get the string of an operand, if it exists, or a space.
322 // If the resulting operand exceeds OPERAND_TRUNCATE_LENGTH,
323 // truncate it and append an ellipsis.
324
325 let operand = inst
326 .operand
327 .as_ref()
328 .map(|op| {
329 let mut op_str = op.to_string();
330 if op_str.len() > OPERAND_TRUNCATE_LENGTH {
331 op_str.truncate(OPERAND_TRUNCATE_LENGTH);
332 op_str.push_str("...");
333 }
334
335 if !op_str.is_empty() {
336 op_str
337 } else {
338 " ".to_string()
339 }
340 })
341 .unwrap_or_else(|| " ".to_string());
342 let operand = html_encode(operand);
343
344 writeln!(
345 &mut label,
346 r##"{indent} <TR>
347{indent} <TD ALIGN="LEFT"><FONT COLOR="{GBF_GREEN}">{:04X}</FONT></TD>
348{indent} <TD ALIGN="LEFT"> </TD>
349{indent} <TD ALIGN="LEFT"><FONT COLOR="{GBF_YELLOW}">{}</FONT></TD>
350{indent} <TD ALIGN="LEFT"> </TD>
351{indent} <TD ALIGN="LEFT"><FONT COLOR="{GBF_BLUE}">{} </FONT></TD>
352{indent} </TR>"##,
353 inst.address,
354 inst.opcode,
355 operand,
356 indent = indent
357 )
358 .unwrap();
359 }
360 }
361
362 // Close the HTML-like table.
363 writeln!(&mut label, "{indent}</TABLE>", indent = indent).unwrap();
364
365 label
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372 use crate::{instruction::Instruction, opcode::Opcode, operand::Operand};
373
374 #[test]
375 fn test_basic_block_id_display() {
376 let block = BasicBlockId::new(0, BasicBlockType::Normal, 3);
377 assert_eq!(block.to_string(), "Block at address 0x3");
378 }
379
380 #[test]
381 fn test_basic_block_new() {
382 let block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 4));
383 assert_eq!(block.id.index, 0);
384 assert_eq!(block.id.block_type, BasicBlockType::Normal);
385 assert!(block.instructions.is_empty());
386 }
387
388 #[test]
389 fn test_basic_block_add_instruction() {
390 let mut block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 7));
391 block.add_instruction(Instruction::new_with_operand(
392 Opcode::PushNumber,
393 0,
394 Operand::new_number(42),
395 ));
396 assert_eq!(block.instructions.len(), 1);
397 }
398
399 #[test]
400 fn test_basic_block_len() {
401 let mut block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 32));
402 block.add_instruction(Instruction::new_with_operand(
403 Opcode::PushNumber,
404 0,
405 Operand::new_number(42),
406 ));
407 assert_eq!(block.len(), 1);
408 }
409
410 #[test]
411 fn test_basic_block_is_empty() {
412 let block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 23));
413 assert!(block.is_empty());
414 }
415
416 #[test]
417 fn test_basic_block_into_iter() {
418 let mut block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 11));
419 block.add_instruction(Instruction::new_with_operand(
420 Opcode::PushNumber,
421 0,
422 Operand::new_number(42),
423 ));
424 block.add_instruction(Instruction::new_with_operand(
425 Opcode::PushNumber,
426 1,
427 Operand::new_number(42),
428 ));
429 let mut iter = block.into_iter();
430 assert_eq!(iter.next().unwrap().address, 0);
431 assert_eq!(iter.next().unwrap().address, 1);
432 assert!(iter.next().is_none());
433 }
434
435 #[test]
436 fn test_basic_block_into_iter_ref() {
437 let mut block = BasicBlock::new(BasicBlockId::new(0, BasicBlockType::Normal, 42));
438 block.add_instruction(Instruction::new_with_operand(
439 Opcode::PushNumber,
440 0,
441 Operand::new_number(42),
442 ));
443 block.add_instruction(Instruction::new_with_operand(
444 Opcode::PushNumber,
445 1,
446 Operand::new_number(42),
447 ));
448 let mut iter = block.iter();
449 assert_eq!(iter.next().unwrap().address, 0);
450 assert_eq!(iter.next().unwrap().address, 1);
451 assert!(iter.next().is_none());
452 }
453}