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                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
75                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
76                }
77                Instruction::CreateIteratorResult { .. } => {
78                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
79                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
80                }
81                Instruction::Generator { .. } => {
82                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
83                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
84                }
85                Instruction::PushInt8 { .. } => {
86                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
87                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
88                }
89                Instruction::PushInt16 { .. } => {
90                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
91                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
92                }
93                Instruction::PushInt32 { .. } => {
94                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
95                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
96                }
97                Instruction::PushFloat { .. } => {
98                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
99                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
100                }
101                Instruction::PushDouble { .. } => {
102                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
103                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
104                }
105                Instruction::PushLiteral { .. }
106                | Instruction::PushRegexp { .. }
107                | Instruction::HasRestrictedGlobalProperty { .. }
108                | Instruction::CanDeclareGlobalFunction { .. }
109                | Instruction::CanDeclareGlobalVar { .. } => {
110                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
111                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
112                }
113                Instruction::Jump { address } => {
114                    graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
115                    graph.add_edge(
116                        previous_pc,
117                        address as usize,
118                        None,
119                        Color::None,
120                        EdgeStyle::Line,
121                    );
122                }
123                Instruction::JumpIfFalse { address, .. }
124                | Instruction::JumpIfTrue { address, .. }
125                | Instruction::JumpIfNotUndefined { address, .. }
126                | Instruction::JumpIfNullOrUndefined { address, .. } => {
127                    graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
128                    graph.add_edge(
129                        previous_pc,
130                        address as usize,
131                        Some("YES".into()),
132                        Color::Green,
133                        EdgeStyle::Line,
134                    );
135                    graph.add_edge(
136                        previous_pc,
137                        pc,
138                        Some("NO".into()),
139                        Color::Red,
140                        EdgeStyle::Line,
141                    );
142                }
143                Instruction::TemplateLookup { .. } | Instruction::TemplateCreate { .. } => {
144                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
145                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
146                }
147                Instruction::LogicalAnd { address, .. }
148                | Instruction::LogicalOr { address, .. }
149                | Instruction::Coalesce { address, .. } => {
150                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
151                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
152                    graph.add_edge(
153                        previous_pc,
154                        address as usize,
155                        Some("SHORT CIRCUIT".into()),
156                        Color::Red,
157                        EdgeStyle::Line,
158                    );
159                }
160                Instruction::Case { address, .. } => {
161                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
162                    graph.add_edge(
163                        previous_pc,
164                        pc,
165                        Some("NO".into()),
166                        Color::Red,
167                        EdgeStyle::Line,
168                    );
169                    graph.add_edge(
170                        previous_pc,
171                        address as usize,
172                        Some("YES".into()),
173                        Color::Green,
174                        EdgeStyle::Line,
175                    );
176                }
177                Instruction::GeneratorDelegateNext {
178                    return_method_undefined,
179                    throw_method_undefined,
180                    ..
181                } => {
182                    graph.add_node(
183                        previous_pc,
184                        NodeShape::Diamond,
185                        opcode_str.into(),
186                        Color::None,
187                    );
188                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
189                    graph.add_edge(
190                        previous_pc,
191                        throw_method_undefined as usize,
192                        Some("`throw` undefined".into()),
193                        Color::Red,
194                        EdgeStyle::Line,
195                    );
196                    graph.add_edge(
197                        previous_pc,
198                        return_method_undefined as usize,
199                        Some("`return` undefined".into()),
200                        Color::Blue,
201                        EdgeStyle::Line,
202                    );
203                }
204                Instruction::GeneratorDelegateResume { r#return, exit, .. } => {
205                    graph.add_node(
206                        previous_pc,
207                        NodeShape::Diamond,
208                        opcode_str.into(),
209                        Color::None,
210                    );
211                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
212                    graph.add_edge(
213                        previous_pc,
214                        r#return as usize,
215                        Some("return".into()),
216                        Color::Yellow,
217                        EdgeStyle::Line,
218                    );
219                    graph.add_edge(
220                        previous_pc,
221                        exit as usize,
222                        Some("done".into()),
223                        Color::Blue,
224                        EdgeStyle::Line,
225                    );
226                }
227                Instruction::CallEval { .. }
228                | Instruction::Call { .. }
229                | Instruction::New { .. }
230                | Instruction::SuperCall { .. }
231                | Instruction::ConcatToString { .. }
232                | Instruction::GetArgument { .. } => {
233                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
234                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
235                }
236                Instruction::JumpIfNotResumeKind { address, .. } => {
237                    graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
238                    graph.add_edge(
239                        previous_pc,
240                        address as usize,
241                        Some("EXIT".into()),
242                        Color::Red,
243                        EdgeStyle::Line,
244                    );
245                    graph.add_edge(previous_pc, pc, None, Color::Green, EdgeStyle::Line);
246                }
247                Instruction::CopyDataProperties { .. } => {
248                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
249                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
250                }
251                Instruction::PushScope { .. } => {
252                    let random = rand::random();
253
254                    graph.add_node(
255                        previous_pc,
256                        NodeShape::None,
257                        label.into(),
258                        Color::from_random_number(random),
259                    );
260                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
261                }
262                Instruction::PopEnvironment => {
263                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
264                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
265                }
266                Instruction::GetFunction { .. } => {
267                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
268                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
269                }
270                Instruction::DefVar { .. }
271                | Instruction::DefInitVar { .. }
272                | Instruction::PutLexicalValue { .. }
273                | Instruction::GetName { .. }
274                | Instruction::GetNameGlobal { .. }
275                | Instruction::GetLocator { .. }
276                | Instruction::GetNameAndLocator { .. }
277                | Instruction::GetNameOrUndefined { .. }
278                | Instruction::SetName { .. }
279                | Instruction::DeleteName { .. } => {
280                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
281                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
282                }
283                Instruction::GetPropertyByName { .. }
284                | Instruction::GetPropertyByValue { .. }
285                | Instruction::GetPropertyByValuePush { .. }
286                | Instruction::SetPropertyByName { .. }
287                | Instruction::DefineOwnPropertyByName { .. }
288                | Instruction::DefineClassStaticMethodByName { .. }
289                | Instruction::DefineClassMethodByName { .. }
290                | Instruction::SetPropertyGetterByName { .. }
291                | Instruction::DefineClassStaticGetterByName { .. }
292                | Instruction::DefineClassGetterByName { .. }
293                | Instruction::SetPropertySetterByName { .. }
294                | Instruction::DefineClassStaticSetterByName { .. }
295                | Instruction::DefineClassSetterByName { .. }
296                | Instruction::SetPrivateField { .. }
297                | Instruction::DefinePrivateField { .. }
298                | Instruction::SetPrivateMethod { .. }
299                | Instruction::SetPrivateSetter { .. }
300                | Instruction::SetPrivateGetter { .. }
301                | Instruction::GetPrivateField { .. }
302                | Instruction::DeletePropertyByName { .. }
303                | Instruction::PushClassFieldPrivate { .. }
304                | Instruction::PushClassPrivateGetter { .. }
305                | Instruction::PushClassPrivateSetter { .. }
306                | Instruction::PushClassPrivateMethod { .. }
307                | Instruction::InPrivate { .. }
308                | Instruction::ThrowMutateImmutable { .. } => {
309                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
310                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
311                }
312                Instruction::ThrowNewTypeError { .. }
313                | Instruction::ThrowNewSyntaxError { .. }
314                | Instruction::ThrowNewReferenceError { .. } => {
315                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
316                    if let Some((i, handler)) = self.find_handler(previous_pc as u32) {
317                        graph.add_edge(
318                            previous_pc,
319                            handler.handler() as usize,
320                            Some(format!("Handler {i:2}: CAUGHT").into()),
321                            Color::None,
322                            EdgeStyle::Line,
323                        );
324                    }
325                }
326                Instruction::Throw { .. } | Instruction::ReThrow => {
327                    if let Some((i, handler)) = self.find_handler(previous_pc as u32) {
328                        graph.add_node(previous_pc, NodeShape::Record, label.into(), Color::None);
329                        graph.add_edge(
330                            previous_pc,
331                            handler.handler() as usize,
332                            Some(format!("Handler {i:2}: CAUGHT").into()),
333                            Color::None,
334                            EdgeStyle::Line,
335                        );
336                    } else {
337                        graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
338                    }
339                }
340                Instruction::PushPrivateEnvironment { .. } => {
341                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
342                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
343                }
344                Instruction::JumpTable {
345                    index: _,
346                    default,
347                    addresses,
348                } => {
349                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
350                    graph.add_edge(
351                        previous_pc,
352                        default as usize,
353                        Some("DEFAULT".into()),
354                        Color::None,
355                        EdgeStyle::Line,
356                    );
357
358                    for (i, address) in addresses.iter().enumerate() {
359                        graph.add_edge(
360                            previous_pc,
361                            *address as usize,
362                            Some(format!("Index: {i}").into()),
363                            Color::None,
364                            EdgeStyle::Line,
365                        );
366                    }
367                }
368                Instruction::Pop
369                | Instruction::PushZero { .. }
370                | Instruction::PushOne { .. }
371                | Instruction::PushNan { .. }
372                | Instruction::PushPositiveInfinity { .. }
373                | Instruction::PushNegativeInfinity { .. }
374                | Instruction::PushNull { .. }
375                | Instruction::PushTrue { .. }
376                | Instruction::PushFalse { .. }
377                | Instruction::PushUndefined { .. }
378                | Instruction::PushEmptyObject { .. }
379                | Instruction::PushClassPrototype { .. }
380                | Instruction::SetClassPrototype { .. }
381                | Instruction::SetHomeObject { .. }
382                | Instruction::TypeOf { .. }
383                | Instruction::LogicalNot { .. }
384                | Instruction::Pos { .. }
385                | Instruction::Neg { .. }
386                | Instruction::SetPropertyByValue { .. }
387                | Instruction::DefineOwnPropertyByValue { .. }
388                | Instruction::DefineClassStaticMethodByValue { .. }
389                | Instruction::DefineClassMethodByValue { .. }
390                | Instruction::SetPropertyGetterByValue { .. }
391                | Instruction::DefineClassStaticGetterByValue { .. }
392                | Instruction::DefineClassGetterByValue { .. }
393                | Instruction::SetPropertySetterByValue { .. }
394                | Instruction::DefineClassStaticSetterByValue { .. }
395                | Instruction::DefineClassSetterByValue { .. }
396                | Instruction::DeletePropertyByValue { .. }
397                | Instruction::DeleteSuperThrow
398                | Instruction::ToPropertyKey { .. }
399                | Instruction::This { .. }
400                | Instruction::ThisForObjectEnvironmentName { .. }
401                | Instruction::Super { .. }
402                | Instruction::IncrementLoopIteration
403                | Instruction::CreateForInIterator { .. }
404                | Instruction::GetIterator { .. }
405                | Instruction::GetAsyncIterator { .. }
406                | Instruction::IteratorNext
407                | Instruction::IteratorFinishAsyncNext { .. }
408                | Instruction::IteratorValue { .. }
409                | Instruction::IteratorResult { .. }
410                | Instruction::IteratorDone { .. }
411                | Instruction::IteratorToArray { .. }
412                | Instruction::IteratorReturn { .. }
413                | Instruction::IteratorStackEmpty { .. }
414                | Instruction::ValueNotNullOrUndefined { .. }
415                | Instruction::RestParameterInit { .. }
416                | Instruction::PushValueToArray { .. }
417                | Instruction::PushElisionToArray { .. }
418                | Instruction::PushIteratorToArray { .. }
419                | Instruction::PushNewArray { .. }
420                | Instruction::GeneratorYield { .. }
421                | Instruction::AsyncGeneratorYield { .. }
422                | Instruction::AsyncGeneratorClose
423                | Instruction::CreatePromiseCapability
424                | Instruction::CompletePromiseCapability
425                | Instruction::GeneratorNext { .. }
426                | Instruction::PushClassField { .. }
427                | Instruction::SuperCallDerived
428                | Instruction::Await { .. }
429                | Instruction::NewTarget { .. }
430                | Instruction::ImportMeta { .. }
431                | Instruction::CallEvalSpread { .. }
432                | Instruction::CallSpread
433                | Instruction::NewSpread
434                | Instruction::SuperCallSpread
435                | Instruction::SuperCallPrepare { .. }
436                | Instruction::SetPrototype { .. }
437                | Instruction::IsObject { .. }
438                | Instruction::SetNameByLocator { .. }
439                | Instruction::PushObjectEnvironment { .. }
440                | Instruction::PopPrivateEnvironment
441                | Instruction::ImportCall { .. }
442                | Instruction::Exception { .. }
443                | Instruction::MaybeException { .. }
444                | Instruction::CheckReturn
445                | Instruction::BindThisValue { .. }
446                | Instruction::CreateMappedArgumentsObject { .. }
447                | Instruction::CreateUnmappedArgumentsObject { .. }
448                | Instruction::CreateGlobalFunctionBinding { .. }
449                | Instruction::CreateGlobalVarBinding { .. } => {
450                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
451                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
452                }
453                Instruction::Return => {
454                    graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::Red);
455                }
456                Instruction::Reserved1
457                | Instruction::Reserved2
458                | Instruction::Reserved3
459                | Instruction::Reserved4
460                | Instruction::Reserved5
461                | Instruction::Reserved6
462                | Instruction::Reserved7
463                | Instruction::Reserved8
464                | Instruction::Reserved9
465                | Instruction::Reserved10
466                | Instruction::Reserved11
467                | Instruction::Reserved12
468                | Instruction::Reserved13
469                | Instruction::Reserved14
470                | Instruction::Reserved15
471                | Instruction::Reserved16
472                | Instruction::Reserved17
473                | Instruction::Reserved18
474                | Instruction::Reserved19
475                | Instruction::Reserved20
476                | Instruction::Reserved21
477                | Instruction::Reserved22
478                | Instruction::Reserved23
479                | Instruction::Reserved24
480                | Instruction::Reserved25
481                | Instruction::Reserved26
482                | Instruction::Reserved27
483                | Instruction::Reserved28
484                | Instruction::Reserved29
485                | Instruction::Reserved30
486                | Instruction::Reserved31
487                | Instruction::Reserved32
488                | Instruction::Reserved33
489                | Instruction::Reserved34
490                | Instruction::Reserved35
491                | Instruction::Reserved36
492                | Instruction::Reserved37
493                | Instruction::Reserved38
494                | Instruction::Reserved39
495                | Instruction::Reserved40
496                | Instruction::Reserved41
497                | Instruction::Reserved42
498                | Instruction::Reserved43
499                | Instruction::Reserved44
500                | Instruction::Reserved45
501                | Instruction::Reserved46
502                | Instruction::Reserved47
503                | Instruction::Reserved48
504                | Instruction::Reserved49
505                | Instruction::Reserved50
506                | Instruction::Reserved51
507                | Instruction::Reserved52
508                | Instruction::Reserved53
509                | Instruction::Reserved54
510                | Instruction::Reserved55
511                | Instruction::Reserved56
512                | Instruction::Reserved57
513                | Instruction::Reserved58
514                | Instruction::Reserved59
515                | Instruction::Reserved60
516                | Instruction::Reserved61
517                | Instruction::Reserved62
518                | Instruction::Reserved63 => unreachable!("Reserved opcodes are unreachable"),
519            }
520        }
521
522        for constant in &self.constants {
523            if let Constant::Function(function) = constant {
524                let subgraph = graph.subgraph(String::new());
525                function.to_graph(subgraph);
526            }
527        }
528    }
529}