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::CallMath { .. }
159                | Instruction::PrepareMath { .. }
160                | Instruction::New { .. }
161                | Instruction::SuperCall { .. }
162                | Instruction::ConcatToString { .. }
163                | Instruction::GetArgument { .. } => {
164                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
165                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
166                }
167                Instruction::CopyDataProperties { .. } => {
168                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
169                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
170                }
171                Instruction::PushScope { .. } => {
172                    let random = rand::random();
173
174                    graph.add_node(
175                        previous_pc,
176                        NodeShape::None,
177                        label.into(),
178                        Color::from_random_number(random),
179                    );
180                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
181                }
182                Instruction::PopEnvironment => {
183                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
184                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
185                }
186                Instruction::GetFunction { .. } => {
187                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
188                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
189                }
190                Instruction::DefVar { .. }
191                | Instruction::DefEvalVar { .. }
192                | Instruction::DefInitVar { .. }
193                | Instruction::PutLexicalValue { .. }
194                | Instruction::GetName { .. }
195                | Instruction::GetNameGlobal { .. }
196                | Instruction::GetLocator { .. }
197                | Instruction::GetNameAndLocator { .. }
198                | Instruction::GetNameOrUndefined { .. }
199                | Instruction::SetName { .. }
200                | Instruction::DeleteName { .. } => {
201                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
202                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
203                }
204                Instruction::GetPropertyByNameWithThis { .. }
205                | Instruction::GetLengthProperty { .. }
206                | Instruction::GetPropertyByName { .. }
207                | Instruction::GetPropertyByValue { .. }
208                | Instruction::GetPropertyByValuePush { .. }
209                | Instruction::GetPropertyByIndex { .. }
210                | Instruction::SetPropertyByIndex { .. }
211                | Instruction::SetPropertyByName { .. }
212                | Instruction::SetPropertyByNameWithThis { .. }
213                | Instruction::DefineOwnPropertyByName { .. }
214                | Instruction::DefineClassStaticMethodByName { .. }
215                | Instruction::DefineClassMethodByName { .. }
216                | Instruction::SetPropertyGetterByName { .. }
217                | Instruction::DefineClassStaticGetterByName { .. }
218                | Instruction::DefineClassGetterByName { .. }
219                | Instruction::SetPropertySetterByName { .. }
220                | Instruction::DefineClassStaticSetterByName { .. }
221                | Instruction::DefineClassSetterByName { .. }
222                | Instruction::SetPrivateField { .. }
223                | Instruction::DefinePrivateField { .. }
224                | Instruction::SetPrivateMethod { .. }
225                | Instruction::SetPrivateSetter { .. }
226                | Instruction::SetPrivateGetter { .. }
227                | Instruction::GetPrivateField { .. }
228                | Instruction::DeletePropertyByName { .. }
229                | Instruction::PushClassFieldPrivate { .. }
230                | Instruction::PushClassPrivateGetter { .. }
231                | Instruction::PushClassPrivateSetter { .. }
232                | Instruction::PushClassPrivateMethod { .. }
233                | Instruction::InPrivate { .. }
234                | Instruction::ThrowMutateImmutable { .. } => {
235                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
236                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
237                }
238                Instruction::ThrowNewTypeError { .. }
239                | Instruction::ThrowNewReferenceError { .. } => {
240                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
241                    if let Some((i, handler)) = self.find_handler(previous_pc as u32) {
242                        graph.add_edge(
243                            previous_pc,
244                            handler.handler().as_u32() as usize,
245                            Some(format!("Handler {i:2}: CAUGHT").into()),
246                            Color::None,
247                            EdgeStyle::Line,
248                        );
249                    }
250                }
251                Instruction::Throw { .. } | Instruction::ReThrow => {
252                    if let Some((i, handler)) = self.find_handler(previous_pc as u32) {
253                        graph.add_node(previous_pc, NodeShape::Record, label.into(), Color::None);
254                        graph.add_edge(
255                            previous_pc,
256                            handler.handler().as_u32() as usize,
257                            Some(format!("Handler {i:2}: CAUGHT").into()),
258                            Color::None,
259                            EdgeStyle::Line,
260                        );
261                    } else {
262                        graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
263                    }
264                }
265                Instruction::PushPrivateEnvironment { .. } => {
266                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
267                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
268                }
269                Instruction::JumpTable {
270                    index: _,
271                    addresses,
272                } => {
273                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
274
275                    graph.add_edge(
276                        previous_pc,
277                        pc,
278                        Some("DEFAULT".into()),
279                        Color::None,
280                        EdgeStyle::Line,
281                    );
282
283                    for (i, address) in addresses.iter().enumerate() {
284                        graph.add_edge(
285                            previous_pc,
286                            address.as_u32() as usize,
287                            Some(format!("{i}").into()),
288                            Color::None,
289                            EdgeStyle::Line,
290                        );
291                    }
292                }
293                Instruction::Pop
294                | Instruction::StoreZero { .. }
295                | Instruction::StoreOne { .. }
296                | Instruction::StoreNan { .. }
297                | Instruction::StorePositiveInfinity { .. }
298                | Instruction::StoreNegativeInfinity { .. }
299                | Instruction::StoreNull { .. }
300                | Instruction::StoreTrue { .. }
301                | Instruction::StoreFalse { .. }
302                | Instruction::StoreUndefined { .. }
303                | Instruction::StoreEmptyObject { .. }
304                | Instruction::StoreClassPrototype { .. }
305                | Instruction::SetClassPrototype { .. }
306                | Instruction::SetHomeObject { .. }
307                | Instruction::GetHomeObject { .. }
308                | Instruction::TypeOf { .. }
309                | Instruction::LogicalNot { .. }
310                | Instruction::Pos { .. }
311                | Instruction::Neg { .. }
312                | Instruction::SetPropertyByValue { .. }
313                | Instruction::DefineOwnPropertyByValue { .. }
314                | Instruction::DefineClassStaticMethodByValue { .. }
315                | Instruction::DefineClassMethodByValue { .. }
316                | Instruction::SetPropertyGetterByValue { .. }
317                | Instruction::DefineClassStaticGetterByValue { .. }
318                | Instruction::DefineClassGetterByValue { .. }
319                | Instruction::SetPropertySetterByValue { .. }
320                | Instruction::DefineClassStaticSetterByValue { .. }
321                | Instruction::DefineClassSetterByValue { .. }
322                | Instruction::DeletePropertyByValue { .. }
323                | Instruction::DeleteSuperThrow
324                | Instruction::GetMethod { .. }
325                | Instruction::ToPropertyKey { .. }
326                | Instruction::This { .. }
327                | Instruction::ThisForObjectEnvironmentName { .. }
328                | Instruction::GetFunctionObject { .. }
329                | Instruction::IncrementLoopIteration
330                | Instruction::CreateForInIterator { .. }
331                | Instruction::GetIterator { .. }
332                | Instruction::GetAsyncIterator { .. }
333                | Instruction::IteratorNext
334                | Instruction::IteratorPop { .. }
335                | Instruction::IteratorPush { .. }
336                | Instruction::IteratorUpdateResult { .. }
337                | Instruction::IteratorFinishAsyncNext { .. }
338                | Instruction::IteratorValue { .. }
339                | Instruction::IteratorResult { .. }
340                | Instruction::IteratorDone { .. }
341                | Instruction::IteratorToArray { .. }
342                | Instruction::IteratorReturn { .. }
343                | Instruction::IteratorStackEmpty { .. }
344                | Instruction::ValueNotNullOrUndefined { .. }
345                | Instruction::RestParameterInit { .. }
346                | Instruction::PushValueToArray { .. }
347                | Instruction::PushElisionToArray { .. }
348                | Instruction::PushIteratorToArray { .. }
349                | Instruction::StoreNewArray { .. }
350                | Instruction::GeneratorYield { .. }
351                | Instruction::AsyncGeneratorYield { .. }
352                | Instruction::AsyncGeneratorClose
353                | Instruction::CreatePromiseCapability
354                | Instruction::PushClassField { .. }
355                | Instruction::SuperCallDerived
356                | Instruction::Await { .. }
357                | Instruction::NewTarget { .. }
358                | Instruction::ImportMeta { .. }
359                | Instruction::CallEvalSpread { .. }
360                | Instruction::CallSpread
361                | Instruction::NewSpread
362                | Instruction::SuperCallSpread
363                | Instruction::SetPrototype { .. }
364                | Instruction::GetPrototype { .. }
365                | Instruction::IsObject { .. }
366                | Instruction::SetNameByLocator { .. }
367                | Instruction::PushObjectEnvironment { .. }
368                | Instruction::PopPrivateEnvironment
369                | Instruction::ImportCall { .. }
370                | Instruction::Exception { .. }
371                | Instruction::MaybeException { .. }
372                | Instruction::CheckReturn
373                | Instruction::BindThisValue { .. }
374                | Instruction::CreateMappedArgumentsObject { .. }
375                | Instruction::CreateUnmappedArgumentsObject { .. } => {
376                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
377                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
378                }
379                Instruction::Return => {
380                    graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::Red);
381                }
382                Instruction::Reserved3
383                | Instruction::Reserved4
384                | Instruction::Reserved5
385                | Instruction::Reserved6
386                | Instruction::Reserved7
387                | Instruction::Reserved8
388                | Instruction::Reserved9
389                | Instruction::Reserved10
390                | Instruction::Reserved11
391                | Instruction::Reserved12
392                | Instruction::Reserved13
393                | Instruction::Reserved14
394                | Instruction::Reserved15
395                | Instruction::Reserved16
396                | Instruction::Reserved17
397                | Instruction::Reserved18
398                | Instruction::Reserved19
399                | Instruction::Reserved20
400                | Instruction::Reserved21
401                | Instruction::Reserved22
402                | Instruction::Reserved23
403                | Instruction::Reserved24
404                | Instruction::Reserved25
405                | Instruction::Reserved26
406                | Instruction::Reserved27
407                | Instruction::Reserved28
408                | Instruction::Reserved29
409                | Instruction::Reserved30
410                | Instruction::Reserved31
411                | Instruction::Reserved32
412                | Instruction::Reserved33
413                | Instruction::Reserved34
414                | Instruction::Reserved35
415                | Instruction::Reserved36
416                | Instruction::Reserved37
417                | Instruction::Reserved38
418                | Instruction::Reserved39
419                | Instruction::Reserved40
420                | Instruction::Reserved41
421                | Instruction::Reserved42
422                | Instruction::Reserved43
423                | Instruction::Reserved44
424                | Instruction::Reserved45
425                | Instruction::Reserved46
426                | Instruction::Reserved47
427                | Instruction::Reserved48
428                | Instruction::Reserved49
429                | Instruction::Reserved50
430                | Instruction::Reserved51
431                | Instruction::Reserved52
432                | Instruction::Reserved53
433                | Instruction::Reserved54
434                | Instruction::Reserved55
435                | Instruction::Reserved56
436                | Instruction::Reserved57 => 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}