Skip to main content

boa_engine/vm/flowgraph/
mod.rs

1//! This module is responsible for generating the vm instruction flowgraph.
2
3use crate::vm::CodeBlock;
4
5mod color;
6mod edge;
7mod graph;
8mod node;
9
10use boa_macros::js_str;
11pub use color::*;
12pub use edge::*;
13pub use graph::*;
14pub use node::*;
15
16use super::{
17    Constant,
18    opcode::{Instruction, InstructionIterator},
19};
20
21impl CodeBlock {
22    /// Output the [`CodeBlock`] VM instructions into a [`Graph`].
23    #[allow(clippy::match_same_arms)]
24    pub fn to_graph(&self, graph: &mut SubGraph) {
25        // Have to remove any invalid graph chars like `<` or `>`.
26        let name = if self.name() == &js_str!("<main>") {
27            "__main__".to_string()
28        } else {
29            self.name().to_std_string_escaped()
30        };
31
32        graph.set_label(name);
33
34        let mut iterator = InstructionIterator::new(&self.bytecode);
35        while let Some((previous_pc, opcode, instruction)) = iterator.next() {
36            let opcode_str = opcode.as_str();
37
38            let label = format!("{opcode_str} {}", self.instruction_operands(&instruction));
39
40            let pc = iterator.pc();
41
42            match instruction {
43                Instruction::StrictEq { .. }
44                | Instruction::StrictNotEq { .. }
45                | Instruction::SetRegisterFromAccumulator { .. }
46                | Instruction::Move { .. }
47                | Instruction::PopIntoRegister { .. }
48                | Instruction::PushFromRegister { .. }
49                | Instruction::Add { .. }
50                | Instruction::Sub { .. }
51                | Instruction::Div { .. }
52                | Instruction::Mul { .. }
53                | Instruction::Mod { .. }
54                | Instruction::Pow { .. }
55                | Instruction::ShiftRight { .. }
56                | Instruction::ShiftLeft { .. }
57                | Instruction::UnsignedShiftRight { .. }
58                | Instruction::BitOr { .. }
59                | Instruction::BitAnd { .. }
60                | Instruction::BitXor { .. }
61                | Instruction::BitNot { .. }
62                | Instruction::In { .. }
63                | Instruction::Eq { .. }
64                | Instruction::NotEq { .. }
65                | Instruction::GreaterThan { .. }
66                | Instruction::GreaterThanOrEq { .. }
67                | Instruction::LessThan { .. }
68                | Instruction::LessThanOrEq { .. }
69                | Instruction::InstanceOf { .. }
70                | Instruction::SetAccumulator { .. }
71                | Instruction::SetFunctionName { .. }
72                | Instruction::Inc { .. }
73                | Instruction::Dec { .. }
74                | Instruction::CreateIteratorResult { .. }
75                | Instruction::Generator
76                | Instruction::AsyncGenerator
77                | Instruction::StoreInt8 { .. }
78                | Instruction::StoreInt16 { .. }
79                | Instruction::StoreInt32 { .. }
80                | Instruction::StoreFloat { .. }
81                | Instruction::StoreDouble { .. }
82                | Instruction::StoreLiteral { .. }
83                | Instruction::StoreRegexp { .. } => {
84                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
85                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
86                }
87                Instruction::Jump { address } => {
88                    graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
89                    graph.add_edge(
90                        previous_pc,
91                        address.as_u32() as usize,
92                        None,
93                        Color::None,
94                        EdgeStyle::Line,
95                    );
96                }
97                Instruction::JumpIfFalse { address, .. }
98                | Instruction::JumpIfTrue { address, .. }
99                | Instruction::JumpIfNotUndefined { address, .. }
100                | Instruction::JumpIfNullOrUndefined { address, .. }
101                | Instruction::JumpIfNotLessThan { address, .. }
102                | Instruction::JumpIfNotLessThanOrEqual { address, .. }
103                | Instruction::JumpIfNotGreaterThan { address, .. }
104                | Instruction::JumpIfNotGreaterThanOrEqual { address, .. }
105                | Instruction::JumpIfNotEqual { address, .. } => {
106                    graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
107                    graph.add_edge(
108                        previous_pc,
109                        address.as_u32() as usize,
110                        Some("YES".into()),
111                        Color::Green,
112                        EdgeStyle::Line,
113                    );
114                    graph.add_edge(
115                        previous_pc,
116                        pc,
117                        Some("NO".into()),
118                        Color::Red,
119                        EdgeStyle::Line,
120                    );
121                }
122                Instruction::TemplateLookup { .. } | Instruction::TemplateCreate { .. } => {
123                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
124                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
125                }
126                Instruction::LogicalAnd { address, .. }
127                | Instruction::LogicalOr { address, .. }
128                | Instruction::Coalesce { address, .. } => {
129                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
130                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
131                    graph.add_edge(
132                        previous_pc,
133                        address.as_u32() as usize,
134                        Some("SHORT CIRCUIT".into()),
135                        Color::Red,
136                        EdgeStyle::Line,
137                    );
138                }
139                Instruction::Case { address, .. } => {
140                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
141                    graph.add_edge(
142                        previous_pc,
143                        pc,
144                        Some("NO".into()),
145                        Color::Red,
146                        EdgeStyle::Line,
147                    );
148                    graph.add_edge(
149                        previous_pc,
150                        address.as_u32() as usize,
151                        Some("YES".into()),
152                        Color::Green,
153                        EdgeStyle::Line,
154                    );
155                }
156                Instruction::CallEval { .. }
157                | Instruction::Call { .. }
158                | Instruction::New { .. }
159                | Instruction::SuperCall { .. }
160                | Instruction::ConcatToString { .. }
161                | Instruction::GetArgument { .. } => {
162                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
163                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
164                }
165                Instruction::CopyDataProperties { .. } => {
166                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
167                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
168                }
169                Instruction::PushScope { .. } => {
170                    let random = rand::random();
171
172                    graph.add_node(
173                        previous_pc,
174                        NodeShape::None,
175                        label.into(),
176                        Color::from_random_number(random),
177                    );
178                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
179                }
180                Instruction::PopEnvironment => {
181                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
182                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
183                }
184                Instruction::GetFunction { .. } => {
185                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
186                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
187                }
188                Instruction::DefVar { .. }
189                | Instruction::DefInitVar { .. }
190                | Instruction::PutLexicalValue { .. }
191                | Instruction::GetName { .. }
192                | Instruction::GetNameGlobal { .. }
193                | Instruction::GetLocator { .. }
194                | Instruction::GetNameAndLocator { .. }
195                | Instruction::GetNameOrUndefined { .. }
196                | Instruction::SetName { .. }
197                | Instruction::DeleteName { .. } => {
198                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
199                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
200                }
201                Instruction::GetPropertyByNameWithThis { .. }
202                | Instruction::GetLengthProperty { .. }
203                | Instruction::GetPropertyByName { .. }
204                | Instruction::GetPropertyByValue { .. }
205                | Instruction::GetPropertyByValuePush { .. }
206                | Instruction::SetPropertyByName { .. }
207                | Instruction::SetPropertyByNameWithThis { .. }
208                | Instruction::DefineOwnPropertyByName { .. }
209                | Instruction::DefineClassStaticMethodByName { .. }
210                | Instruction::DefineClassMethodByName { .. }
211                | Instruction::SetPropertyGetterByName { .. }
212                | Instruction::DefineClassStaticGetterByName { .. }
213                | Instruction::DefineClassGetterByName { .. }
214                | Instruction::SetPropertySetterByName { .. }
215                | Instruction::DefineClassStaticSetterByName { .. }
216                | Instruction::DefineClassSetterByName { .. }
217                | Instruction::SetPrivateField { .. }
218                | Instruction::DefinePrivateField { .. }
219                | Instruction::SetPrivateMethod { .. }
220                | Instruction::SetPrivateSetter { .. }
221                | Instruction::SetPrivateGetter { .. }
222                | Instruction::GetPrivateField { .. }
223                | Instruction::DeletePropertyByName { .. }
224                | Instruction::PushClassFieldPrivate { .. }
225                | Instruction::PushClassPrivateGetter { .. }
226                | Instruction::PushClassPrivateSetter { .. }
227                | Instruction::PushClassPrivateMethod { .. }
228                | Instruction::InPrivate { .. }
229                | Instruction::ThrowMutateImmutable { .. } => {
230                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
231                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
232                }
233                Instruction::ThrowNewTypeError { .. }
234                | Instruction::ThrowNewReferenceError { .. } => {
235                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
236                    if let Some((i, handler)) = self.find_handler(previous_pc as u32) {
237                        graph.add_edge(
238                            previous_pc,
239                            handler.handler().as_u32() as usize,
240                            Some(format!("Handler {i:2}: CAUGHT").into()),
241                            Color::None,
242                            EdgeStyle::Line,
243                        );
244                    }
245                }
246                Instruction::Throw { .. } | Instruction::ReThrow => {
247                    if let Some((i, handler)) = self.find_handler(previous_pc as u32) {
248                        graph.add_node(previous_pc, NodeShape::Record, label.into(), Color::None);
249                        graph.add_edge(
250                            previous_pc,
251                            handler.handler().as_u32() as usize,
252                            Some(format!("Handler {i:2}: CAUGHT").into()),
253                            Color::None,
254                            EdgeStyle::Line,
255                        );
256                    } else {
257                        graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
258                    }
259                }
260                Instruction::PushPrivateEnvironment { .. } => {
261                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
262                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
263                }
264                Instruction::JumpTable {
265                    index: _,
266                    addresses,
267                } => {
268                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
269
270                    graph.add_edge(
271                        previous_pc,
272                        pc,
273                        Some("DEFAULT".into()),
274                        Color::None,
275                        EdgeStyle::Line,
276                    );
277
278                    for (i, address) in addresses.iter().enumerate() {
279                        graph.add_edge(
280                            previous_pc,
281                            address.as_u32() as usize,
282                            Some(format!("{i}").into()),
283                            Color::None,
284                            EdgeStyle::Line,
285                        );
286                    }
287                }
288                Instruction::Pop
289                | Instruction::StoreZero { .. }
290                | Instruction::StoreOne { .. }
291                | Instruction::StoreNan { .. }
292                | Instruction::StorePositiveInfinity { .. }
293                | Instruction::StoreNegativeInfinity { .. }
294                | Instruction::StoreNull { .. }
295                | Instruction::StoreTrue { .. }
296                | Instruction::StoreFalse { .. }
297                | Instruction::StoreUndefined { .. }
298                | Instruction::StoreEmptyObject { .. }
299                | Instruction::StoreClassPrototype { .. }
300                | Instruction::SetClassPrototype { .. }
301                | Instruction::SetHomeObject { .. }
302                | Instruction::GetHomeObject { .. }
303                | Instruction::TypeOf { .. }
304                | Instruction::LogicalNot { .. }
305                | Instruction::Pos { .. }
306                | Instruction::Neg { .. }
307                | Instruction::SetPropertyByValue { .. }
308                | Instruction::DefineOwnPropertyByValue { .. }
309                | Instruction::DefineClassStaticMethodByValue { .. }
310                | Instruction::DefineClassMethodByValue { .. }
311                | Instruction::SetPropertyGetterByValue { .. }
312                | Instruction::DefineClassStaticGetterByValue { .. }
313                | Instruction::DefineClassGetterByValue { .. }
314                | Instruction::SetPropertySetterByValue { .. }
315                | Instruction::DefineClassStaticSetterByValue { .. }
316                | Instruction::DefineClassSetterByValue { .. }
317                | Instruction::DeletePropertyByValue { .. }
318                | Instruction::DeleteSuperThrow
319                | Instruction::GetMethod { .. }
320                | Instruction::ToPropertyKey { .. }
321                | Instruction::This { .. }
322                | Instruction::ThisForObjectEnvironmentName { .. }
323                | Instruction::GetFunctionObject { .. }
324                | Instruction::IncrementLoopIteration
325                | Instruction::CreateForInIterator { .. }
326                | Instruction::GetIterator { .. }
327                | Instruction::GetAsyncIterator { .. }
328                | Instruction::IteratorNext
329                | Instruction::IteratorPop { .. }
330                | Instruction::IteratorPush { .. }
331                | Instruction::IteratorUpdateResult { .. }
332                | Instruction::IteratorFinishAsyncNext { .. }
333                | Instruction::IteratorValue { .. }
334                | Instruction::IteratorResult { .. }
335                | Instruction::IteratorDone { .. }
336                | Instruction::IteratorToArray { .. }
337                | Instruction::IteratorReturn { .. }
338                | Instruction::IteratorStackEmpty { .. }
339                | Instruction::ValueNotNullOrUndefined { .. }
340                | Instruction::RestParameterInit { .. }
341                | Instruction::PushValueToArray { .. }
342                | Instruction::PushElisionToArray { .. }
343                | Instruction::PushIteratorToArray { .. }
344                | Instruction::StoreNewArray { .. }
345                | Instruction::GeneratorYield { .. }
346                | Instruction::AsyncGeneratorYield { .. }
347                | Instruction::AsyncGeneratorClose
348                | Instruction::CreatePromiseCapability
349                | Instruction::PushClassField { .. }
350                | Instruction::SuperCallDerived
351                | Instruction::Await { .. }
352                | Instruction::NewTarget { .. }
353                | Instruction::ImportMeta { .. }
354                | Instruction::CallEvalSpread { .. }
355                | Instruction::CallSpread
356                | Instruction::NewSpread
357                | Instruction::SuperCallSpread
358                | Instruction::SetPrototype { .. }
359                | Instruction::GetPrototype { .. }
360                | Instruction::IsObject { .. }
361                | Instruction::SetNameByLocator { .. }
362                | Instruction::PushObjectEnvironment { .. }
363                | Instruction::PopPrivateEnvironment
364                | Instruction::ImportCall { .. }
365                | Instruction::Exception { .. }
366                | Instruction::MaybeException { .. }
367                | Instruction::CheckReturn
368                | Instruction::BindThisValue { .. }
369                | Instruction::CreateMappedArgumentsObject { .. }
370                | Instruction::CreateUnmappedArgumentsObject { .. } => {
371                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
372                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
373                }
374                Instruction::Return => {
375                    graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::Red);
376                }
377                Instruction::Reserved1
378                | Instruction::Reserved2
379                | Instruction::Reserved3
380                | Instruction::Reserved4
381                | Instruction::Reserved5
382                | Instruction::Reserved6
383                | Instruction::Reserved7
384                | Instruction::Reserved8
385                | Instruction::Reserved9
386                | Instruction::Reserved10
387                | Instruction::Reserved11
388                | Instruction::Reserved12
389                | Instruction::Reserved13
390                | Instruction::Reserved14
391                | Instruction::Reserved15
392                | Instruction::Reserved16
393                | Instruction::Reserved17
394                | Instruction::Reserved18
395                | Instruction::Reserved19
396                | Instruction::Reserved20
397                | Instruction::Reserved21
398                | Instruction::Reserved22
399                | Instruction::Reserved23
400                | Instruction::Reserved24
401                | Instruction::Reserved25
402                | Instruction::Reserved26
403                | Instruction::Reserved27
404                | Instruction::Reserved28
405                | Instruction::Reserved29
406                | Instruction::Reserved30
407                | Instruction::Reserved31
408                | Instruction::Reserved32
409                | Instruction::Reserved33
410                | Instruction::Reserved34
411                | Instruction::Reserved35
412                | Instruction::Reserved36
413                | Instruction::Reserved37
414                | Instruction::Reserved38
415                | Instruction::Reserved39
416                | Instruction::Reserved40
417                | Instruction::Reserved41
418                | Instruction::Reserved42
419                | Instruction::Reserved43
420                | Instruction::Reserved44
421                | Instruction::Reserved45
422                | Instruction::Reserved46
423                | Instruction::Reserved47
424                | Instruction::Reserved48
425                | Instruction::Reserved49
426                | Instruction::Reserved50
427                | Instruction::Reserved51
428                | Instruction::Reserved52
429                | Instruction::Reserved53
430                | Instruction::Reserved54
431                | Instruction::Reserved55
432                | Instruction::Reserved56
433                | Instruction::Reserved57
434                | Instruction::Reserved58
435                | Instruction::Reserved59
436                | Instruction::Reserved60 => unreachable!("Reserved opcodes are unreachable"),
437            }
438        }
439
440        for constant in &self.constants {
441            if let Constant::Function(function) = constant {
442                let subgraph = graph.subgraph(String::new());
443                function.to_graph(subgraph);
444            }
445        }
446    }
447}