Browse Source

Implement instruction flowgraph generator (#2422)

This PR is a WIP implementation of a vm instruction flowgraph generator

This aims to make the vm easier to debug and understand for both newcomers and experienced devs.

For example if we have the following code:
```js
let i = 0;
while (i < 10) {
    if (i == 3) {
        break;
    }
    i++;
}
```
It generates the following instructions (which is hard to read, especially jumps):
<details>

```
----------------------Compiled Output: '<main>'-----------------------
Location  Count   Opcode                     Operands

000000    0000    PushZero
000001    0001    DefInitLet                 0000: 'i'
000006    0002    LoopStart
000007    0003    LoopContinue
000008    0004    GetName                    0000: 'i'
000013    0005    PushInt8                   10
000015    0006    LessThan
000016    0007    JumpIfFalse                78
000021    0008    PushDeclarativeEnvironment 0, 1
000030    0009    GetName                    0000: 'i'
000035    0010    PushInt8                   3
000037    0011    Eq
000038    0012    JumpIfFalse                58
000043    0013    PushDeclarativeEnvironment 0, 0
000052    0014    Jump                       78
000057    0015    PopEnvironment
000058    0016    GetName                    0000: 'i'
000063    0017    IncPost
000064    0018    RotateRight                2
000066    0019    SetName                    0000: 'i'
000071    0020    Pop
000072    0021    PopEnvironment
000073    0022    Jump                       7
000078    0023    LoopEnd

Literals:
    <empty>

Bindings:
    0000: i

Functions:
    <empty>
```

</details>

And the flow graph is generated:
![flowgraph](https://user-images.githubusercontent.com/8566042/200589387-40b36ad7-d2f2-4918-a3e4-5a8fa5eee89b.png)

The beginning of the function is  marked by the `start` node (in green) and end (in red). In branching the "yes" branch is marked  in green and "no" in red.

~~This only generates in [graphviz format](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) (a widely used format) but it would be nice to also generate to a format that `mermaid.js` can understand and that could be put in articles https://github.com/boa-dev/boa-dev.github.io/issues/26~~

TODO:
  - [x] Generate graphviz format
  - [x] Generate mermaid format
  - [x] Programmatically generate colors push and pop env instructions
  - [x] Display nested functions in sub-sub-graphs.
  - [x] Put under a feature (`"flowgraph"`)
  - [x] Handle try/catch, switch instructions
  - [x] CLI option for configuring direction of flow (by default it is top down)
  - [x] Handle `Throw` instruction (requires keeping track of try blocks)
  - [x] Documentation
  - [x] Prevent node name collisions (functions with the same name)
pull/2459/head
Halid Odat 2 years ago
parent
commit
a2964e63dd
  1. 2
      boa_cli/Cargo.toml
  2. 98
      boa_cli/src/main.rs
  3. 3
      boa_engine/Cargo.toml
  4. 81
      boa_engine/src/vm/flowgraph/color.rs
  5. 65
      boa_engine/src/vm/flowgraph/edge.rs
  6. 303
      boa_engine/src/vm/flowgraph/graph.rs
  7. 490
      boa_engine/src/vm/flowgraph/mod.rs
  8. 38
      boa_engine/src/vm/flowgraph/node.rs
  9. 3
      boa_engine/src/vm/mod.rs
  10. 2
      boa_engine/src/vm/opcode/mod.rs
  11. 39
      docs/debugging.md
  12. 333
      docs/img/graphviz_flowgraph.svg

2
boa_cli/Cargo.toml

@ -12,7 +12,7 @@ repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
[dependencies] [dependencies]
boa_engine = { workspace = true, features = ["deser", "console"] } boa_engine = { workspace = true, features = ["deser", "console", "flowgraph"] }
boa_ast = { workspace = true, features = ["serde"]} boa_ast = { workspace = true, features = ["serde"]}
boa_parser.workspace = true boa_parser.workspace = true
rustyline = "10.0.0" rustyline = "10.0.0"

98
boa_cli/src/main.rs

@ -61,7 +61,10 @@
mod helper; mod helper;
use boa_ast::StatementList; use boa_ast::StatementList;
use boa_engine::Context; use boa_engine::{
vm::flowgraph::{Direction, Graph},
Context, JsResult,
};
use clap::{Parser, ValueEnum, ValueHint}; use clap::{Parser, ValueEnum, ValueHint};
use colored::{Color, Colorize}; use colored::{Color, Colorize};
use rustyline::{config::Config, error::ReadlineError, EditMode, Editor}; use rustyline::{config::Config, error::ReadlineError, EditMode, Editor};
@ -95,18 +98,40 @@ struct Opt {
short = 'a', short = 'a',
value_name = "FORMAT", value_name = "FORMAT",
ignore_case = true, ignore_case = true,
value_enum value_enum,
conflicts_with = "graph"
)] )]
#[allow(clippy::option_option)] #[allow(clippy::option_option)]
dump_ast: Option<Option<DumpFormat>>, dump_ast: Option<Option<DumpFormat>>,
/// Dump the AST to stdout with the given format. /// Dump the AST to stdout with the given format.
#[arg(long, short)] #[arg(long, short, conflicts_with = "graph")]
trace: bool, trace: bool,
/// Use vi mode in the REPL /// Use vi mode in the REPL
#[arg(long = "vi")] #[arg(long = "vi")]
vi_mode: bool, vi_mode: bool,
/// Generate instruction flowgraph. Default is Graphviz.
#[arg(
long,
value_name = "FORMAT",
ignore_case = true,
value_enum,
group = "graph"
)]
#[allow(clippy::option_option)]
flowgraph: Option<Option<FlowgraphFormat>>,
/// Specifies the direction of the flowgraph. Default is TopToBottom.
#[arg(
long,
value_name = "FORMAT",
ignore_case = true,
value_enum,
requires = "graph"
)]
flowgraph_direction: Option<FlowgraphDirection>,
} }
impl Opt { impl Opt {
@ -136,6 +161,28 @@ enum DumpFormat {
JsonPretty, JsonPretty,
} }
/// Represents the format of the instruction flowgraph.
#[derive(Debug, Clone, Copy, ValueEnum)]
enum FlowgraphFormat {
/// Generates in [graphviz][graphviz] format.
///
/// [graphviz]: https://graphviz.org/
Graphviz,
/// Generates in [mermaid][mermaid] format.
///
/// [mermaid]: https://mermaid-js.github.io/mermaid/#/
Mermaid,
}
/// Represents the direction of the instruction flowgraph.
#[derive(Debug, Clone, Copy, ValueEnum)]
enum FlowgraphDirection {
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft,
}
/// Parses the the token stream into an AST and returns it. /// Parses the the token stream into an AST and returns it.
/// ///
/// Returns a error of type String with a message, /// Returns a error of type String with a message,
@ -178,6 +225,31 @@ where
Ok(()) Ok(())
} }
fn generate_flowgraph(
context: &mut Context,
src: &[u8],
format: FlowgraphFormat,
direction: Option<FlowgraphDirection>,
) -> JsResult<String> {
let ast = context.parse(src)?;
let code = context.compile(&ast)?;
let direction = match direction {
Some(FlowgraphDirection::TopToBottom) | None => Direction::TopToBottom,
Some(FlowgraphDirection::BottomToTop) => Direction::BottomToTop,
Some(FlowgraphDirection::LeftToRight) => Direction::LeftToRight,
Some(FlowgraphDirection::RightToLeft) => Direction::RightToLeft,
};
let mut graph = Graph::new(direction);
code.to_graph(context.interner(), graph.subgraph(String::default()));
let result = match format {
FlowgraphFormat::Graphviz => graph.to_graphviz_format(),
FlowgraphFormat::Mermaid => graph.to_mermaid_format(),
};
Ok(result)
}
fn main() -> Result<(), io::Error> { fn main() -> Result<(), io::Error> {
let args = Opt::parse(); let args = Opt::parse();
@ -193,6 +265,16 @@ fn main() -> Result<(), io::Error> {
if let Err(e) = dump(&buffer, &args, &mut context) { if let Err(e) = dump(&buffer, &args, &mut context) {
eprintln!("{e}"); eprintln!("{e}");
} }
} else if let Some(flowgraph) = args.flowgraph {
match generate_flowgraph(
&mut context,
&buffer,
flowgraph.unwrap_or(FlowgraphFormat::Graphviz),
args.flowgraph_direction,
) {
Ok(v) => println!("{}", v),
Err(v) => eprintln!("Uncaught {v}"),
}
} else { } else {
match context.eval(&buffer) { match context.eval(&buffer) {
Ok(v) => println!("{}", v.display()), Ok(v) => println!("{}", v.display()),
@ -239,6 +321,16 @@ fn main() -> Result<(), io::Error> {
if let Err(e) = dump(&line, &args, &mut context) { if let Err(e) = dump(&line, &args, &mut context) {
eprintln!("{e}"); eprintln!("{e}");
} }
} else if let Some(flowgraph) = args.flowgraph {
match generate_flowgraph(
&mut context,
line.trim_end().as_bytes(),
flowgraph.unwrap_or(FlowgraphFormat::Graphviz),
args.flowgraph_direction,
) {
Ok(v) => println!("{}", v),
Err(v) => eprintln!("Uncaught {v}"),
}
} else { } else {
match context.eval(line.trim_end()) { match context.eval(line.trim_end()) {
Ok(v) => println!("{}", v.display()), Ok(v) => println!("{}", v.display()),

3
boa_engine/Cargo.toml

@ -26,6 +26,9 @@ intl = [
fuzz = ["boa_ast/fuzz", "boa_interner/fuzz"] fuzz = ["boa_ast/fuzz", "boa_interner/fuzz"]
# Enable Boa's VM instruction flowgraph generator.
flowgraph = []
# Enable Boa's WHATWG console object implementation. # Enable Boa's WHATWG console object implementation.
console = [] console = []

81
boa_engine/src/vm/flowgraph/color.rs

@ -0,0 +1,81 @@
use std::fmt::Display;
/// Represents the color of a node or edge.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Color {
/// Represents the default color.
None,
/// Represents the color red.
Red,
/// Represents the color green.
Green,
/// Represents the color blue.
Blue,
/// Represents the color yellow.
Yellow,
/// Represents the color purple.
Purple,
/// Represents a RGB color.
Rgb { r: u8, g: u8, b: u8 },
}
impl Color {
/// Function for converting HSV to RGB color format.
#[allow(clippy::many_single_char_names)]
#[inline]
pub fn hsv_to_rgb(h: f64, s: f64, v: f64) -> Self {
let h_i = (h * 6.0) as i64;
let f = h * 6.0 - h_i as f64;
let p = v * (1.0 - s);
let q = v * (1.0 - f * s);
let t = v * (1.0 - (1.0 - f) * s);
let (r, g, b) = match h_i {
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
5 => (v, p, q),
_ => unreachable!(),
};
let r = (r * 256.0) as u8;
let g = (g * 256.0) as u8;
let b = (b * 256.0) as u8;
Self::Rgb { r, g, b }
}
/// This funcition takes a random value and converts it to
/// a pleasant to look at RGB color.
#[inline]
pub fn from_random_number(mut random: f64) -> Self {
const GOLDEN_RATIO_CONJUGATE: f64 = 0.618033988749895;
random += GOLDEN_RATIO_CONJUGATE;
random %= 1.0;
Self::hsv_to_rgb(random, 0.7, 0.95)
}
/// Check if the color is [`Self::None`].
#[inline]
pub fn is_none(&self) -> bool {
*self == Self::None
}
}
impl Display for Color {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Color::None => f.write_str(""),
Color::Red => f.write_str("red"),
Color::Green => f.write_str("green"),
Color::Blue => f.write_str("blue"),
Color::Yellow => f.write_str("yellow"),
Color::Purple => f.write_str("purple"),
Color::Rgb { r, g, b } => write!(f, "#{r:02X}{b:02X}{g:02X}"),
}
}
}

65
boa_engine/src/vm/flowgraph/edge.rs

@ -0,0 +1,65 @@
use crate::vm::flowgraph::Color;
/// Represents the edge (connection) style.
#[derive(Debug, Clone, Copy)]
pub enum EdgeStyle {
/// Represents a solid line.
Line,
/// Represents a dotted line.
Dotted,
/// Represents a dashed line.
Dashed,
}
/// Represents the edge type.
#[derive(Debug, Clone, Copy)]
pub enum EdgeType {
/// Represents no decoration on the edge line.
None,
/// Represents arrow edge type.
Arrow,
}
/// Represents an edge/connection in the flowgraph.
#[derive(Debug, Clone)]
pub struct Edge {
/// The location of the source node.
pub(super) from: usize,
/// The location of the destination node.
pub(super) to: usize,
/// The label on top of the edge.
pub(super) label: Option<Box<str>>,
/// The color of the line.
pub(super) color: Color,
/// The style of the line.
pub(super) style: EdgeStyle,
/// The type of the line.
pub(super) type_: EdgeType,
}
impl Edge {
/// Construct a new edge.
#[inline]
pub(super) fn new(
from: usize,
to: usize,
label: Option<Box<str>>,
color: Color,
style: EdgeStyle,
) -> Self {
Self {
from,
to,
label,
color,
style,
type_: EdgeType::Arrow,
}
}
/// Set the type of the edge.
#[inline]
pub fn set_type(&mut self, type_: EdgeType) {
self.type_ = type_;
}
}

303
boa_engine/src/vm/flowgraph/graph.rs

@ -0,0 +1,303 @@
use crate::vm::flowgraph::{Color, Edge, EdgeStyle, EdgeType, Node, NodeShape};
/// This represents the direction of flow in the flowgraph.
#[derive(Debug, Clone, Copy)]
pub enum Direction {
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft,
}
/// Represents a sub-graph in the graph.
///
/// Sub-graphs can be nested.
#[derive(Debug, Clone)]
pub struct SubGraph {
/// The label on the sub-graph.
label: String,
/// The nodes it contains.
nodes: Vec<Node>,
/// The edges/connections in contains.
edges: Vec<Edge>,
/// The direction of flow in the sub-graph.
direction: Direction,
/// The sub-graphs this graph contains.
subgraphs: Vec<SubGraph>,
}
impl SubGraph {
/// Construct a new subgraph.
#[inline]
fn new(label: String) -> Self {
Self {
label,
nodes: Vec::default(),
edges: Vec::default(),
direction: Direction::TopToBottom,
subgraphs: Vec::default(),
}
}
/// Set the label of the subgraph.
#[inline]
pub fn set_label(&mut self, label: String) {
self.label = label;
}
/// Set the direction of the subgraph.
#[inline]
pub fn set_direction(&mut self, direction: Direction) {
self.direction = direction;
}
/// Add a node to the subgraph.
#[inline]
pub fn add_node(&mut self, location: usize, shape: NodeShape, label: Box<str>, color: Color) {
let node = Node::new(location, shape, label, color);
self.nodes.push(node);
}
/// Add an edge to the subgraph.
#[inline]
pub fn add_edge(
&mut self,
from: usize,
to: usize,
label: Option<Box<str>>,
color: Color,
style: EdgeStyle,
) -> &mut Edge {
let edge = Edge::new(from, to, label, color, style);
self.edges.push(edge);
self.edges.last_mut().expect("Already pushed edge")
}
/// Create a subgraph in this subgraph.
#[inline]
pub fn subgraph(&mut self, label: String) -> &mut SubGraph {
self.subgraphs.push(SubGraph::new(label));
let result = self
.subgraphs
.last_mut()
.expect("We just pushed a subgraph");
result.set_direction(self.direction);
result
}
/// Format into the graphviz format.
#[inline]
fn graphviz_format(&self, result: &mut String, prefix: &str) {
result.push_str(&format!("\tsubgraph cluster_{prefix}_{} {{\n", self.label));
result.push_str("\t\tstyle = filled;\n");
result.push_str(&format!(
"\t\tlabel = \"{}\";\n",
if self.label.is_empty() {
"Anonymous Function"
} else {
self.label.as_ref()
}
));
result.push_str(&format!(
"\t\t{prefix}_{}_start [label=\"Start\",shape=Mdiamond,style=filled,color=green]\n",
self.label
));
if !self.nodes.is_empty() {
result.push_str(&format!(
"\t\t{prefix}_{}_start -> {prefix}_{}_i_0\n",
self.label, self.label
));
}
for node in &self.nodes {
let shape = match node.shape {
NodeShape::None => "",
NodeShape::Record => ", shape=record",
NodeShape::Diamond => ", shape=diamond",
};
let color = format!(",style=filled,color=\"{}\"", node.color);
result.push_str(&format!(
"\t\t{prefix}_{}_i_{}[label=\"{:04}: {}\"{shape}{color}];\n",
self.label, node.location, node.location, node.label
));
}
for edge in &self.edges {
let color = format!(",color=\"{}\"", edge.color);
let style = match (edge.style, edge.type_) {
(EdgeStyle::Line, EdgeType::None) => ",dir=none",
(EdgeStyle::Line, EdgeType::Arrow) => "",
(EdgeStyle::Dotted, EdgeType::None) => ",style=dotted,dir=none",
(EdgeStyle::Dotted, EdgeType::Arrow) => ",style=dotted",
(EdgeStyle::Dashed, EdgeType::None) => ",style=dashed,dir=none",
(EdgeStyle::Dashed, EdgeType::Arrow) => ",style=dashed,",
};
result.push_str(&format!(
"\t\t{prefix}_{}_i_{} -> {prefix}_{}_i_{} [label=\"{}\", len=f{style}{color}];\n",
self.label,
edge.from,
self.label,
edge.to,
edge.label.as_deref().unwrap_or("")
));
}
for (index, subgraph) in self.subgraphs.iter().enumerate() {
let prefix = format!("{prefix}_F{index}");
subgraph.graphviz_format(result, &prefix);
}
result.push_str("\t}\n");
}
/// Format into the mermaid format.
#[inline]
fn mermaid_format(&self, result: &mut String, prefix: &str) {
let rankdir = match self.direction {
Direction::TopToBottom => "TB",
Direction::BottomToTop => "BT",
Direction::LeftToRight => "LR",
Direction::RightToLeft => "RL",
};
result.push_str(&format!(
" subgraph {prefix}_{}[\"{}\"]\n",
self.label,
if self.label.is_empty() {
"Anonymous Function"
} else {
self.label.as_ref()
}
));
result.push_str(&format!(" direction {}\n", rankdir));
result.push_str(&format!(" {prefix}_{}_start{{Start}}\n", self.label));
result.push_str(&format!(
" style {prefix}_{}_start fill:green\n",
self.label
));
if !self.nodes.is_empty() {
result.push_str(&format!(
" {prefix}_{}_start --> {prefix}_{}_i_0\n",
self.label, self.label
));
}
for node in &self.nodes {
let (shape_begin, shape_end) = match node.shape {
NodeShape::None | NodeShape::Record => ('[', ']'),
NodeShape::Diamond => ('{', '}'),
};
result.push_str(&format!(
" {prefix}_{}_i_{}{shape_begin}\"{:04}: {}\"{shape_end}\n",
self.label, node.location, node.location, node.label
));
if !node.color.is_none() {
result.push_str(&format!(
" style {prefix}_{}_i_{} fill:{}\n",
self.label, node.location, node.color
));
}
}
for (index, edge) in self.edges.iter().enumerate() {
let style = match (edge.style, edge.type_) {
(EdgeStyle::Line, EdgeType::None) => "---",
(EdgeStyle::Line, EdgeType::Arrow) => "-->",
(EdgeStyle::Dotted | EdgeStyle::Dashed, EdgeType::None) => "-.-",
(EdgeStyle::Dotted | EdgeStyle::Dashed, EdgeType::Arrow) => "-.->",
};
result.push_str(&format!(
" {prefix}_{}_i_{} {style}| {}| {prefix}_{}_i_{}\n",
self.label,
edge.from,
edge.label.as_deref().unwrap_or(""),
self.label,
edge.to,
));
if !edge.color.is_none() {
result.push_str(&format!(
" linkStyle {} stroke:{}, stroke-width: 4px\n",
index + 1,
edge.color
));
}
}
for (index, subgraph) in self.subgraphs.iter().enumerate() {
let prefix = format!("{prefix}_F{index}");
subgraph.mermaid_format(result, &prefix);
}
result.push_str(" end\n");
}
}
/// This represents the main graph that other [`SubGraph`]s can be nested in.
#[derive(Debug)]
pub struct Graph {
subgraphs: Vec<SubGraph>,
direction: Direction,
}
impl Graph {
/// Construct a [`Graph`]
#[inline]
pub fn new(direction: Direction) -> Self {
Graph {
subgraphs: Vec::default(),
direction,
}
}
/// Create a [`SubGraph`] in this [`Graph`].
#[inline]
pub fn subgraph(&mut self, label: String) -> &mut SubGraph {
self.subgraphs.push(SubGraph::new(label));
let result = self
.subgraphs
.last_mut()
.expect("We just pushed a subgraph");
result.set_direction(self.direction);
result
}
/// Output the graph into the graphviz format.
#[inline]
pub fn to_graphviz_format(&self) -> String {
let mut result = String::new();
result += "digraph {\n";
result += "\tnode [shape=record];\n";
let rankdir = match self.direction {
Direction::TopToBottom => "TB",
Direction::BottomToTop => "BT",
Direction::LeftToRight => "LR",
Direction::RightToLeft => "RL",
};
result += &format!("\trankdir={rankdir};\n");
for subgraph in &self.subgraphs {
subgraph.graphviz_format(&mut result, "");
}
result += "}\n";
result
}
/// Output the graph into the mermaid format.
#[inline]
pub fn to_mermaid_format(&self) -> String {
let mut result = String::new();
let rankdir = match self.direction {
Direction::TopToBottom => "TD",
Direction::BottomToTop => "DT",
Direction::LeftToRight => "LR",
Direction::RightToLeft => "RL",
};
result += &format!("graph {}\n", rankdir);
for subgraph in &self.subgraphs {
subgraph.mermaid_format(&mut result, "");
}
result += "\n";
result
}
}

490
boa_engine/src/vm/flowgraph/mod.rs

@ -0,0 +1,490 @@
//! This module is responsible for generating the vm instruction flowgraph.
use std::mem::size_of;
use boa_interner::{Interner, Sym};
use crate::vm::{CodeBlock, Opcode};
mod color;
mod edge;
mod graph;
mod node;
pub use color::*;
pub use edge::*;
pub use graph::*;
pub use node::*;
impl CodeBlock {
/// Output the [`CodeBlock`] VM instructions into a [`Graph`].
#[inline]
pub fn to_graph(&self, interner: &Interner, graph: &mut SubGraph) {
let mut name = interner.resolve_expect(self.name).to_string();
// Have to remove any invalid graph chars like `<` or `>`.
if self.name == Sym::MAIN {
name = "__main__".to_string();
}
graph.set_label(name);
let mut environments = Vec::new();
let mut try_entries = Vec::new();
let mut returns = Vec::new();
let mut pc = 0;
while pc < self.code.len() {
let opcode: Opcode = self.code[pc].try_into().expect("invalid opcode");
let opcode_str = opcode.as_str();
let previous_pc = pc;
let mut tmp = pc;
let label = format!(
"{opcode_str} {}",
self.instruction_operands(&mut tmp, interner)
);
pc += size_of::<Opcode>();
match opcode {
Opcode::RotateLeft | Opcode::RotateRight => {
pc += size_of::<u8>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::PushInt8 => {
pc += size_of::<i8>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::PushInt16 => {
pc += size_of::<i16>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::PushInt32 => {
pc += size_of::<i32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::PushRational => {
pc += size_of::<f64>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::PushLiteral => {
let operand = self.read::<u32>(pc);
pc += size_of::<u32>();
let operand_str = self.literals[operand as usize].display().to_string();
let operand_str = operand_str.escape_debug();
let label = format!("{opcode_str} {}", operand_str);
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::Jump => {
let operand = self.read::<u32>(pc);
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
graph.add_edge(
previous_pc,
operand as usize,
None,
Color::None,
EdgeStyle::Line,
);
}
Opcode::JumpIfFalse
| Opcode::JumpIfNotUndefined
| Opcode::JumpIfNullOrUndefined => {
let operand = self.read::<u32>(pc);
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
graph.add_edge(
previous_pc,
operand as usize,
Some("YES".into()),
Color::Green,
EdgeStyle::Line,
);
graph.add_edge(
previous_pc,
pc,
Some("NO".into()),
Color::Red,
EdgeStyle::Line,
);
}
Opcode::LogicalAnd | Opcode::LogicalOr | Opcode::Coalesce => {
let exit = self.read::<u32>(pc);
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
graph.add_edge(
previous_pc,
exit as usize,
Some("SHORT CIRCUIT".into()),
Color::Red,
EdgeStyle::Line,
);
}
Opcode::Case => {
let address = self.read::<u32>(pc) as usize;
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(
previous_pc,
pc,
Some("NO".into()),
Color::Red,
EdgeStyle::Line,
);
graph.add_edge(
previous_pc,
address,
Some("YES".into()),
Color::Green,
EdgeStyle::Line,
);
}
Opcode::Default => {
let address = self.read::<u32>(pc) as usize;
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, address, None, Color::None, EdgeStyle::Line);
}
Opcode::ForInLoopInitIterator => {
let address = self.read::<u32>(pc) as usize;
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
graph.add_edge(
previous_pc,
address,
Some("NULL OR UNDEFINED".into()),
Color::None,
EdgeStyle::Line,
);
}
Opcode::ForInLoopNext
| Opcode::ForAwaitOfLoopNext
| Opcode::GeneratorNextDelegate => {
let address = self.read::<u32>(pc) as usize;
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
graph.add_edge(
previous_pc,
address,
Some("DONE".into()),
Color::None,
EdgeStyle::Line,
);
}
Opcode::CatchStart
| Opcode::FinallySetJump
| Opcode::CallEval
| Opcode::Call
| Opcode::New
| Opcode::SuperCall
| Opcode::ConcatToString => {
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::TryStart => {
let next_address = self.read::<u32>(pc);
pc += size_of::<u32>();
let finally_address = self.read::<u32>(pc);
pc += size_of::<u32>();
try_entries.push((
previous_pc,
next_address,
if finally_address == 0 {
None
} else {
Some(finally_address)
},
));
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
graph.add_edge(
previous_pc,
next_address as usize,
Some("NEXT".into()),
Color::None,
EdgeStyle::Line,
);
if finally_address != 0 {
graph.add_edge(
previous_pc,
finally_address as usize,
Some("FINALLY".into()),
Color::None,
EdgeStyle::Line,
);
}
}
Opcode::CopyDataProperties => {
let operand1 = self.read::<u32>(pc);
pc += size_of::<u32>();
let operand2 = self.read::<u32>(pc);
pc += size_of::<u32>();
let label = format!("{opcode_str} {operand1}, {operand2}");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::PushDeclarativeEnvironment | Opcode::PushFunctionEnvironment => {
let random = rand::random();
environments.push((previous_pc, random));
pc += size_of::<u32>();
pc += size_of::<u32>();
graph.add_node(
previous_pc,
NodeShape::None,
label.into(),
Color::from_random_number(random),
);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::PopEnvironment => {
let (environment_push, random) = environments
.pop()
.expect("There should be a push evironment before");
let color = Color::from_random_number(random);
graph.add_node(previous_pc, NodeShape::None, label.into(), color);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
graph
.add_edge(
previous_pc,
environment_push,
None,
color,
EdgeStyle::Dotted,
)
.set_type(EdgeType::None);
}
Opcode::GetArrowFunction
| Opcode::GetAsyncArrowFunction
| Opcode::GetFunction
| Opcode::GetFunctionAsync
| Opcode::GetGenerator
| Opcode::GetGeneratorAsync => {
let operand = self.read::<u32>(pc);
let fn_name = interner
.resolve_expect(self.functions[operand as usize].name)
.to_string();
pc += size_of::<u32>();
let label = format!(
"{opcode_str} '{fn_name}' (length: {})",
self.functions[operand as usize].length
);
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::DefInitArg
| Opcode::DefVar
| Opcode::DefInitVar
| Opcode::DefLet
| Opcode::DefInitLet
| Opcode::DefInitConst
| Opcode::GetName
| Opcode::GetNameOrUndefined
| Opcode::SetName
| Opcode::DeleteName => {
let operand = self.read::<u32>(pc);
pc += size_of::<u32>();
let label = format!(
"{opcode_str} '{}'",
interner.resolve_expect(self.bindings[operand as usize].name().sym()),
);
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::GetPropertyByName
| Opcode::SetPropertyByName
| Opcode::DefineOwnPropertyByName
| Opcode::DefineClassMethodByName
| Opcode::SetPropertyGetterByName
| Opcode::DefineClassGetterByName
| Opcode::SetPropertySetterByName
| Opcode::DefineClassSetterByName
| Opcode::AssignPrivateField
| Opcode::SetPrivateField
| Opcode::SetPrivateMethod
| Opcode::SetPrivateSetter
| Opcode::SetPrivateGetter
| Opcode::GetPrivateField
| Opcode::DeletePropertyByName
| Opcode::PushClassFieldPrivate
| Opcode::PushClassPrivateGetter
| Opcode::PushClassPrivateSetter
| Opcode::PushClassPrivateMethod => {
let operand = self.read::<u32>(pc);
pc += size_of::<u32>();
let label = format!(
"{opcode_str} '{}'",
interner.resolve_expect(self.names[operand as usize].sym()),
);
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::Throw => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
if let Some((_try_pc, next, _finally)) = try_entries.last() {
graph.add_edge(
previous_pc,
*next as usize,
Some("CAUGHT".into()),
Color::None,
EdgeStyle::Line,
);
}
}
Opcode::Pop
| Opcode::PopIfThrown
| Opcode::Dup
| Opcode::Swap
| Opcode::PushZero
| Opcode::PushOne
| Opcode::PushNaN
| Opcode::PushPositiveInfinity
| Opcode::PushNegativeInfinity
| Opcode::PushNull
| Opcode::PushTrue
| Opcode::PushFalse
| Opcode::PushUndefined
| Opcode::PushEmptyObject
| Opcode::PushClassPrototype
| Opcode::SetClassPrototype
| Opcode::SetHomeObject
| Opcode::Add
| Opcode::Sub
| Opcode::Div
| Opcode::Mul
| Opcode::Mod
| Opcode::Pow
| Opcode::ShiftRight
| Opcode::ShiftLeft
| Opcode::UnsignedShiftRight
| Opcode::BitOr
| Opcode::BitAnd
| Opcode::BitXor
| Opcode::BitNot
| Opcode::In
| Opcode::Eq
| Opcode::StrictEq
| Opcode::NotEq
| Opcode::StrictNotEq
| Opcode::GreaterThan
| Opcode::GreaterThanOrEq
| Opcode::LessThan
| Opcode::LessThanOrEq
| Opcode::InstanceOf
| Opcode::TypeOf
| Opcode::Void
| Opcode::LogicalNot
| Opcode::Pos
| Opcode::Neg
| Opcode::Inc
| Opcode::IncPost
| Opcode::Dec
| Opcode::DecPost
| Opcode::GetPropertyByValue
| Opcode::GetPropertyByValuePush
| Opcode::SetPropertyByValue
| Opcode::DefineOwnPropertyByValue
| Opcode::DefineClassMethodByValue
| Opcode::SetPropertyGetterByValue
| Opcode::DefineClassGetterByValue
| Opcode::SetPropertySetterByValue
| Opcode::DefineClassSetterByValue
| Opcode::DeletePropertyByValue
| Opcode::DeleteSuperThrow
| Opcode::ToPropertyKey
| Opcode::ToBoolean
| Opcode::CatchEnd
| Opcode::CatchEnd2
| Opcode::FinallyStart
| Opcode::FinallyEnd
| Opcode::This
| Opcode::Super
| Opcode::LoopStart
| Opcode::LoopContinue
| Opcode::LoopEnd
| Opcode::InitIterator
| Opcode::InitIteratorAsync
| Opcode::IteratorNext
| Opcode::IteratorClose
| Opcode::IteratorToArray
| Opcode::RequireObjectCoercible
| Opcode::ValueNotNullOrUndefined
| Opcode::RestParameterInit
| Opcode::RestParameterPop
| Opcode::PushValueToArray
| Opcode::PushElisionToArray
| Opcode::PushIteratorToArray
| Opcode::PushNewArray
| Opcode::PopOnReturnAdd
| Opcode::PopOnReturnSub
| Opcode::Yield
| Opcode::GeneratorNext
| Opcode::AsyncGeneratorNext
| Opcode::PushClassField
| Opcode::SuperCallDerived
| Opcode::Await
| Opcode::PushNewTarget
| Opcode::CallEvalSpread
| Opcode::CallSpread
| Opcode::NewSpread
| Opcode::SuperCallSpread
| Opcode::ForAwaitOfLoopIterate
| Opcode::SetPrototype
| Opcode::Nop => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::TryEnd => {
try_entries
.pop()
.expect("there should already be try block");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::Return => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
if let Some((_try_pc, _next, Some(finally))) = try_entries.last() {
graph.add_edge(
previous_pc,
*finally as usize,
None,
Color::None,
EdgeStyle::Line,
);
} else {
returns.push(previous_pc);
}
}
}
}
for ret in returns {
graph.add_edge(ret, pc, None, Color::None, EdgeStyle::Line);
}
graph.add_node(pc, NodeShape::Diamond, "End".into(), Color::Red);
for function in &self.functions {
let subgraph = graph.subgraph(String::new());
function.to_graph(interner, subgraph);
}
}
}

38
boa_engine/src/vm/flowgraph/node.rs

@ -0,0 +1,38 @@
use crate::vm::flowgraph::Color;
/// Reperesents the shape of a node in the flowgraph.
#[derive(Debug, Clone, Copy)]
pub enum NodeShape {
// Represents the default shape used in the graph.
None,
/// Represents a rectangular node shape.
Record,
/// Represents a diamond node shape.
Diamond,
}
/// This represents a node in the flowgraph.
#[derive(Debug, Clone)]
pub struct Node {
/// The opcode location.
pub(super) location: usize,
/// The shape of the opcode.
pub(super) shape: NodeShape,
/// The label/contents of the node.
pub(super) label: Box<str>,
/// The background color of the node.
pub(super) color: Color,
}
impl Node {
/// Construct a new node.
#[inline]
pub(super) fn new(location: usize, shape: NodeShape, label: Box<str>, color: Color) -> Self {
Self {
location,
shape,
label,
color,
}
}
}

3
boa_engine/src/vm/mod.rs

@ -17,6 +17,9 @@ mod call_frame;
mod code_block; mod code_block;
mod opcode; mod opcode;
#[cfg(feature = "flowgraph")]
pub mod flowgraph;
pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode}; pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode};
pub(crate) use { pub(crate) use {

2
boa_engine/src/vm/opcode/mod.rs

@ -1033,7 +1033,7 @@ generate_impl! {
/// Start of a catch block. /// Start of a catch block.
/// ///
/// Operands: /// Operands: finally_address: `u32`
/// ///
/// Stack: **=>** /// Stack: **=>**
CatchStart, CatchStart,

39
docs/debugging.md

@ -43,6 +43,45 @@ You can print the bytecode and the executed instructions with the command-line f
For more detailed information about the vm and the trace output look [here](./vm.md). For more detailed information about the vm and the trace output look [here](./vm.md).
## Instruction flowgraph
We can to get the vm instructions flowgraph, which is a visual representation of the instruction flow.
The `Start` (in green) and `End` (in red) node in the graph represents the start and end point of execution.
They are not instructions, just markers.
The conditional instructions are diamond shaped, with the `"YES"` branch in green and the `"NO"` branch in red.
The push and pop evironment pairs match colors and are connected by a dotted line.
You can use the `--flowgraph` (or `--flowgraph=mermaid` for [mermaid][mermaid] format) flag which outputs
[graphviz][graphviz] format by default, and pipe it to `dot` (from the `graphviz` package which is installed
on most linux distros by default) or use an online editor like: <https://dreampuf.github.io/GraphvizOnline> to
view the graph.
```bash
cargo run -- test.js --flowgraph | dot -Tpng > test.png
```
You can specify the `-Tsvg` to generate a `svg` instead of a `png` file.
![Graphviz flowgraph](./img/graphviz_flowgraph.svg)
Mermaid graphs can be displayed on github [natively without third-party programs][gihub-mermaid].
By using a `mermaid` block as seen below.
````
```mermaid
// graph contents here...
```
````
Additionaly you can specify the direction of "flow" by using the `--flowgraph-direction` cli option,
for example `--flowgraph-direction=left-to-right`, the default is `top-to-bottom`.
[mermaid]: https://mermaid-js.github.io/
[gihub-mermaid]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-diagrams
[graphviz]: https://graphviz.org/
## Compiler panics ## Compiler panics
In the case of a compiler panic, to get a full backtrace you will need to set In the case of a compiler panic, to get a full backtrace you will need to set

333
docs/img/graphviz_flowgraph.svg

@ -0,0 +1,333 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 7.0.1 (0)
-->
<!-- Pages: 1 -->
<svg width="2868pt" height="197pt"
viewBox="0.00 0.00 2867.57 197.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 193)">
<polygon fill="white" stroke="none" points="-4,4 -4,-193 2863.57,-193 2863.57,4 -4,4"/>
<g id="clust1" class="cluster">
<title>cluster___main__</title>
<polygon fill="lightgrey" stroke="black" points="8,-8 8,-181 2851.57,-181 2851.57,-8 8,-8"/>
<text text-anchor="middle" x="1429.79" y="-165.8" font-family="Times,serif" font-size="14.00">__main__</text>
</g>
<!-- __main___start -->
<g id="node1" class="node">
<title>__main___start</title>
<polygon fill="green" stroke="green" points="54.6,-108 15.9,-90 54.6,-72 93.31,-90 54.6,-108"/>
<polyline fill="none" stroke="green" points="26.78,-95.06 26.78,-84.94"/>
<polyline fill="none" stroke="green" points="43.72,-77.06 65.49,-77.06"/>
<polyline fill="none" stroke="green" points="82.43,-84.94 82.43,-95.06"/>
<polyline fill="none" stroke="green" points="65.49,-102.94 43.72,-102.94"/>
<text text-anchor="middle" x="54.6" y="-86.3" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- __main___i_0 -->
<g id="node2" class="node">
<title>__main___i_0</title>
<polygon fill="none" stroke="black" points="130.21,-72 130.21,-108 240.21,-108 240.21,-72 130.21,-72"/>
<text text-anchor="middle" x="185.21" y="-86.3" font-family="Times,serif" font-size="14.00">0000: PushInt8 3</text>
</g>
<!-- __main___start&#45;&gt;__main___i_0 -->
<g id="edge1" class="edge">
<title>__main___start&#45;&gt;__main___i_0</title>
<path fill="none" stroke="black" d="M94.19,-90C102.02,-90 110.47,-90 118.93,-90"/>
<polygon fill="black" stroke="black" points="118.92,-93.5 128.92,-90 118.92,-86.5 118.92,-93.5"/>
</g>
<!-- __main___i_2 -->
<g id="node3" class="node">
<title>__main___i_2</title>
<polygon fill="none" stroke="black" points="277.21,-72 277.21,-108 397.21,-108 397.21,-72 277.21,-72"/>
<text text-anchor="middle" x="337.21" y="-86.3" font-family="Times,serif" font-size="14.00">0002: DefInitLet &#39;i&#39;</text>
</g>
<!-- __main___i_0&#45;&gt;__main___i_2 -->
<g id="edge2" class="edge">
<title>__main___i_0&#45;&gt;__main___i_2</title>
<path fill="none" stroke="black" d="M239.94,-90C248.36,-90 257.16,-90 265.88,-90"/>
<polygon fill="black" stroke="black" points="265.75,-93.5 275.75,-90 265.75,-86.5 265.75,-93.5"/>
</g>
<!-- __main___i_7 -->
<g id="node4" class="node">
<title>__main___i_7</title>
<polygon fill="none" stroke="black" points="434.21,-72 434.21,-108 550.21,-108 550.21,-72 434.21,-72"/>
<text text-anchor="middle" x="492.21" y="-86.3" font-family="Times,serif" font-size="14.00">0007: GetName &#39;i&#39;</text>
</g>
<!-- __main___i_2&#45;&gt;__main___i_7 -->
<g id="edge3" class="edge">
<title>__main___i_2&#45;&gt;__main___i_7</title>
<path fill="none" stroke="black" d="M396.93,-90C405.48,-90 414.35,-90 423.07,-90"/>
<polygon fill="black" stroke="black" points="422.9,-93.5 432.9,-90 422.9,-86.5 422.9,-93.5"/>
</g>
<!-- __main___i_12 -->
<g id="node5" class="node">
<title>__main___i_12</title>
<polygon fill="none" stroke="black" points="587.21,-72 587.21,-108 703.21,-108 703.21,-72 587.21,-72"/>
<text text-anchor="middle" x="645.21" y="-86.3" font-family="Times,serif" font-size="14.00">0012: PushInt8 10</text>
</g>
<!-- __main___i_7&#45;&gt;__main___i_12 -->
<g id="edge4" class="edge">
<title>__main___i_7&#45;&gt;__main___i_12</title>
<path fill="none" stroke="black" d="M549.87,-90C558.36,-90 567.2,-90 575.9,-90"/>
<polygon fill="black" stroke="black" points="575.72,-93.5 585.72,-90 575.72,-86.5 575.72,-93.5"/>
</g>
<!-- __main___i_14 -->
<g id="node6" class="node">
<title>__main___i_14</title>
<polygon fill="none" stroke="black" points="740.21,-72 740.21,-108 844.21,-108 844.21,-72 740.21,-72"/>
<text text-anchor="middle" x="792.21" y="-86.3" font-family="Times,serif" font-size="14.00">0014: LessThan</text>
</g>
<!-- __main___i_12&#45;&gt;__main___i_14 -->
<g id="edge5" class="edge">
<title>__main___i_12&#45;&gt;__main___i_14</title>
<path fill="none" stroke="black" d="M703.1,-90C711.71,-90 720.62,-90 729.33,-90"/>
<polygon fill="black" stroke="black" points="729.09,-93.5 739.09,-90 729.09,-86.5 729.09,-93.5"/>
</g>
<!-- __main___i_15 -->
<g id="node7" class="node">
<title>__main___i_15</title>
<polygon fill="none" stroke="black" points="1006.21,-108 881.2,-90 1006.21,-72 1131.23,-90 1006.21,-108"/>
<text text-anchor="middle" x="1006.21" y="-86.3" font-family="Times,serif" font-size="14.00">0015: JumpIfFalse 53</text>
</g>
<!-- __main___i_14&#45;&gt;__main___i_15 -->
<g id="edge6" class="edge">
<title>__main___i_14&#45;&gt;__main___i_15</title>
<path fill="none" stroke="black" d="M843.99,-90C851.15,-90 858.78,-90 866.67,-90"/>
<polygon fill="black" stroke="black" points="866.47,-93.5 876.47,-90 866.47,-86.5 866.47,-93.5"/>
</g>
<!-- __main___i_20 -->
<g id="node8" class="node">
<title>__main___i_20</title>
<polygon fill="#48f379" stroke="#48f379" points="1193.22,-45 1193.22,-81 1429.22,-81 1429.22,-45 1193.22,-45"/>
<text text-anchor="middle" x="1311.22" y="-59.3" font-family="Times,serif" font-size="14.00">0020: PushDeclarativeEnvironment 0, 0</text>
</g>
<!-- __main___i_15&#45;&gt;__main___i_20 -->
<g id="edge8" class="edge">
<title>__main___i_15&#45;&gt;__main___i_20</title>
<path fill="none" stroke="red" d="M1081.63,-82.41C1103.41,-80.26 1127.26,-77.97 1149.22,-76 1159.9,-75.04 1170.99,-74.08 1182.12,-73.14"/>
<polygon fill="red" stroke="red" points="1182.07,-76.65 1191.74,-72.33 1181.48,-69.68 1182.07,-76.65"/>
<text text-anchor="middle" x="1162.22" y="-79.8" font-family="Times,serif" font-size="14.00">NO</text>
</g>
<!-- __main___i_53 -->
<g id="node17" class="node">
<title>__main___i_53</title>
<polygon fill="#6148f3" stroke="#6148f3" points="1193.22,-100 1193.22,-136 1429.22,-136 1429.22,-100 1193.22,-100"/>
<text text-anchor="middle" x="1311.22" y="-114.3" font-family="Times,serif" font-size="14.00">0053: PushDeclarativeEnvironment 0, 1</text>
</g>
<!-- __main___i_15&#45;&gt;__main___i_53 -->
<g id="edge7" class="edge">
<title>__main___i_15&#45;&gt;__main___i_53</title>
<path fill="none" stroke="green" d="M1084.85,-97.17C1114.47,-99.91 1149.07,-103.11 1182.15,-106.17"/>
<polygon fill="green" stroke="green" points="1181.51,-109.62 1191.79,-107.06 1182.15,-102.65 1181.51,-109.62"/>
<text text-anchor="middle" x="1162.22" y="-107.8" font-family="Times,serif" font-size="14.00">YES</text>
</g>
<!-- __main___i_29 -->
<g id="node9" class="node">
<title>__main___i_29</title>
<polygon fill="none" stroke="black" points="1466.22,-17 1466.22,-53 1603.22,-53 1603.22,-17 1466.22,-17"/>
<text text-anchor="middle" x="1534.72" y="-31.3" font-family="Times,serif" font-size="14.00">0029: GetName &#39;print&#39;</text>
</g>
<!-- __main___i_20&#45;&gt;__main___i_29 -->
<g id="edge9" class="edge">
<title>__main___i_20&#45;&gt;__main___i_29</title>
<path fill="none" stroke="black" d="M1429.13,-48.22C1437.93,-47.11 1446.67,-46 1455.14,-44.93"/>
<polygon fill="black" stroke="black" points="1455.51,-48.41 1464.99,-43.69 1454.63,-41.47 1455.51,-48.41"/>
</g>
<!-- __main___i_34 -->
<g id="node10" class="node">
<title>__main___i_34</title>
<polygon fill="none" stroke="black" points="1640.22,-17 1640.22,-53 1774.22,-53 1774.22,-17 1640.22,-17"/>
<text text-anchor="middle" x="1707.22" y="-31.3" font-family="Times,serif" font-size="14.00">0034: PushUndefined</text>
</g>
<!-- __main___i_29&#45;&gt;__main___i_34 -->
<g id="edge10" class="edge">
<title>__main___i_29&#45;&gt;__main___i_34</title>
<path fill="none" stroke="black" d="M1603.11,-35C1611.65,-35 1620.44,-35 1629.12,-35"/>
<polygon fill="black" stroke="black" points="1628.92,-38.5 1638.92,-35 1628.92,-31.5 1628.92,-38.5"/>
</g>
<!-- __main___i_35 -->
<g id="node11" class="node">
<title>__main___i_35</title>
<polygon fill="none" stroke="black" points="1811.22,-17 1811.22,-53 1892.22,-53 1892.22,-17 1811.22,-17"/>
<text text-anchor="middle" x="1851.72" y="-31.3" font-family="Times,serif" font-size="14.00">0035: Swap</text>
</g>
<!-- __main___i_34&#45;&gt;__main___i_35 -->
<g id="edge11" class="edge">
<title>__main___i_34&#45;&gt;__main___i_35</title>
<path fill="none" stroke="black" d="M1774.05,-35C1782.87,-35 1791.81,-35 1800.31,-35"/>
<polygon fill="black" stroke="black" points="1800.07,-38.5 1810.07,-35 1800.07,-31.5 1800.07,-38.5"/>
</g>
<!-- __main___i_36 -->
<g id="node12" class="node">
<title>__main___i_36</title>
<polygon fill="none" stroke="black" points="1929.22,-17 1929.22,-53 2045.22,-53 2045.22,-17 1929.22,-17"/>
<text text-anchor="middle" x="1987.22" y="-31.3" font-family="Times,serif" font-size="14.00">0036: GetName &#39;i&#39;</text>
</g>
<!-- __main___i_35&#45;&gt;__main___i_36 -->
<g id="edge12" class="edge">
<title>__main___i_35&#45;&gt;__main___i_36</title>
<path fill="none" stroke="black" d="M1892.05,-35C1900.27,-35 1909.16,-35 1918.09,-35"/>
<polygon fill="black" stroke="black" points="1917.85,-38.5 1927.85,-35 1917.85,-31.5 1917.85,-38.5"/>
</g>
<!-- __main___i_41 -->
<g id="node13" class="node">
<title>__main___i_41</title>
<polygon fill="none" stroke="black" points="2082.22,-17 2082.22,-53 2166.22,-53 2166.22,-17 2082.22,-17"/>
<text text-anchor="middle" x="2124.22" y="-31.3" font-family="Times,serif" font-size="14.00">0041: Call 1</text>
</g>
<!-- __main___i_36&#45;&gt;__main___i_41 -->
<g id="edge13" class="edge">
<title>__main___i_36&#45;&gt;__main___i_41</title>
<path fill="none" stroke="black" d="M2045.08,-35C2053.7,-35 2062.56,-35 2071.07,-35"/>
<polygon fill="black" stroke="black" points="2070.9,-38.5 2080.9,-35 2070.9,-31.5 2070.9,-38.5"/>
</g>
<!-- __main___i_46 -->
<g id="node14" class="node">
<title>__main___i_46</title>
<polygon fill="none" stroke="black" points="2203.22,-17 2203.22,-53 2275.22,-53 2275.22,-17 2203.22,-17"/>
<text text-anchor="middle" x="2239.22" y="-31.3" font-family="Times,serif" font-size="14.00">0046: Pop</text>
</g>
<!-- __main___i_41&#45;&gt;__main___i_46 -->
<g id="edge14" class="edge">
<title>__main___i_41&#45;&gt;__main___i_46</title>
<path fill="none" stroke="black" d="M2166.02,-35C2174.45,-35 2183.39,-35 2191.99,-35"/>
<polygon fill="black" stroke="black" points="2191.94,-38.5 2201.94,-35 2191.94,-31.5 2191.94,-38.5"/>
</g>
<!-- __main___i_47 -->
<g id="node15" class="node">
<title>__main___i_47</title>
<polygon fill="#48f379" stroke="#48f379" points="2312.22,-50 2312.22,-86 2455.22,-86 2455.22,-50 2312.22,-50"/>
<text text-anchor="middle" x="2383.72" y="-64.3" font-family="Times,serif" font-size="14.00">0047: PopEnvironment</text>
</g>
<!-- __main___i_46&#45;&gt;__main___i_47 -->
<g id="edge15" class="edge">
<title>__main___i_46&#45;&gt;__main___i_47</title>
<path fill="none" stroke="black" d="M2275.08,-43.07C2283.1,-44.93 2291.97,-46.98 2301.07,-49.09"/>
<polygon fill="black" stroke="black" points="2300.24,-52.49 2310.77,-51.34 2301.82,-45.67 2300.24,-52.49"/>
</g>
<!-- __main___i_47&#45;&gt;__main___i_20 -->
<g id="edge17" class="edge">
<title>__main___i_47&#45;&gt;__main___i_20</title>
<path fill="none" stroke="#48f379" stroke-dasharray="1,5" d="M2312.43,-71.45C2289.42,-72.31 2263.74,-73 2240.22,-73 1533.72,-73 1533.72,-73 1533.72,-73 1499.75,-73 1462.88,-71.81 1429.14,-70.25"/>
</g>
<!-- __main___i_48 -->
<g id="node16" class="node">
<title>__main___i_48</title>
<polygon fill="none" stroke="black" points="2582.3,-97 2492.14,-79 2582.3,-61 2672.45,-79 2582.3,-97"/>
<text text-anchor="middle" x="2582.3" y="-75.3" font-family="Times,serif" font-size="14.00">0048: Jump 78</text>
</g>
<!-- __main___i_47&#45;&gt;__main___i_48 -->
<g id="edge16" class="edge">
<title>__main___i_47&#45;&gt;__main___i_48</title>
<path fill="none" stroke="black" d="M2455.13,-71.94C2469.13,-72.72 2483.95,-73.55 2498.32,-74.36"/>
<polygon fill="black" stroke="black" points="2498.03,-77.85 2508.21,-74.91 2498.42,-70.86 2498.03,-77.85"/>
</g>
<!-- __main___i_78 -->
<g id="node25" class="node">
<title>__main___i_78</title>
<polygon fill="red" stroke="red" points="2776.47,-118 2709.28,-100 2776.47,-82 2843.67,-100 2776.47,-118"/>
<text text-anchor="middle" x="2776.47" y="-96.3" font-family="Times,serif" font-size="14.00">0078: End</text>
</g>
<!-- __main___i_48&#45;&gt;__main___i_78 -->
<g id="edge18" class="edge">
<title>__main___i_48&#45;&gt;__main___i_78</title>
<path fill="none" stroke="black" d="M2642.52,-85.47C2665.93,-88.03 2692.78,-90.96 2716.2,-93.52"/>
<polygon fill="black" stroke="black" points="2715.56,-96.97 2725.88,-94.58 2716.32,-90.02 2715.56,-96.97"/>
</g>
<!-- __main___i_62 -->
<g id="node18" class="node">
<title>__main___i_62</title>
<polygon fill="none" stroke="black" points="1466.22,-93 1466.22,-129 1603.22,-129 1603.22,-93 1466.22,-93"/>
<text text-anchor="middle" x="1534.72" y="-107.3" font-family="Times,serif" font-size="14.00">0062: GetName &#39;print&#39;</text>
</g>
<!-- __main___i_53&#45;&gt;__main___i_62 -->
<g id="edge19" class="edge">
<title>__main___i_53&#45;&gt;__main___i_62</title>
<path fill="none" stroke="black" d="M1429.13,-114.31C1437.93,-114.03 1446.67,-113.75 1455.14,-113.48"/>
<polygon fill="black" stroke="black" points="1455.09,-116.99 1464.98,-113.17 1454.87,-109.99 1455.09,-116.99"/>
</g>
<!-- __main___i_67 -->
<g id="node19" class="node">
<title>__main___i_67</title>
<polygon fill="none" stroke="black" points="1640.22,-93 1640.22,-129 1774.22,-129 1774.22,-93 1640.22,-93"/>
<text text-anchor="middle" x="1707.22" y="-107.3" font-family="Times,serif" font-size="14.00">0067: PushUndefined</text>
</g>
<!-- __main___i_62&#45;&gt;__main___i_67 -->
<g id="edge20" class="edge">
<title>__main___i_62&#45;&gt;__main___i_67</title>
<path fill="none" stroke="black" d="M1603.11,-111C1611.65,-111 1620.44,-111 1629.12,-111"/>
<polygon fill="black" stroke="black" points="1628.92,-114.5 1638.92,-111 1628.92,-107.5 1628.92,-114.5"/>
</g>
<!-- __main___i_68 -->
<g id="node20" class="node">
<title>__main___i_68</title>
<polygon fill="none" stroke="black" points="1811.22,-93 1811.22,-129 1892.22,-129 1892.22,-93 1811.22,-93"/>
<text text-anchor="middle" x="1851.72" y="-107.3" font-family="Times,serif" font-size="14.00">0068: Swap</text>
</g>
<!-- __main___i_67&#45;&gt;__main___i_68 -->
<g id="edge21" class="edge">
<title>__main___i_67&#45;&gt;__main___i_68</title>
<path fill="none" stroke="black" d="M1774.05,-111C1782.87,-111 1791.81,-111 1800.31,-111"/>
<polygon fill="black" stroke="black" points="1800.07,-114.5 1810.07,-111 1800.07,-107.5 1800.07,-114.5"/>
</g>
<!-- __main___i_69 -->
<g id="node21" class="node">
<title>__main___i_69</title>
<polygon fill="none" stroke="black" points="1929.22,-93 1929.22,-129 2045.22,-129 2045.22,-93 1929.22,-93"/>
<text text-anchor="middle" x="1987.22" y="-107.3" font-family="Times,serif" font-size="14.00">0069: PushInt8 10</text>
</g>
<!-- __main___i_68&#45;&gt;__main___i_69 -->
<g id="edge22" class="edge">
<title>__main___i_68&#45;&gt;__main___i_69</title>
<path fill="none" stroke="black" d="M1892.05,-111C1900.27,-111 1909.16,-111 1918.09,-111"/>
<polygon fill="black" stroke="black" points="1917.85,-114.5 1927.85,-111 1917.85,-107.5 1917.85,-114.5"/>
</g>
<!-- __main___i_71 -->
<g id="node22" class="node">
<title>__main___i_71</title>
<polygon fill="none" stroke="black" points="2082.22,-93 2082.22,-129 2166.22,-129 2166.22,-93 2082.22,-93"/>
<text text-anchor="middle" x="2124.22" y="-107.3" font-family="Times,serif" font-size="14.00">0071: Call 1</text>
</g>
<!-- __main___i_69&#45;&gt;__main___i_71 -->
<g id="edge23" class="edge">
<title>__main___i_69&#45;&gt;__main___i_71</title>
<path fill="none" stroke="black" d="M2045.08,-111C2053.7,-111 2062.56,-111 2071.07,-111"/>
<polygon fill="black" stroke="black" points="2070.9,-114.5 2080.9,-111 2070.9,-107.5 2070.9,-114.5"/>
</g>
<!-- __main___i_76 -->
<g id="node23" class="node">
<title>__main___i_76</title>
<polygon fill="none" stroke="black" points="2203.22,-93 2203.22,-129 2275.22,-129 2275.22,-93 2203.22,-93"/>
<text text-anchor="middle" x="2239.22" y="-107.3" font-family="Times,serif" font-size="14.00">0076: Pop</text>
</g>
<!-- __main___i_71&#45;&gt;__main___i_76 -->
<g id="edge24" class="edge">
<title>__main___i_71&#45;&gt;__main___i_76</title>
<path fill="none" stroke="black" d="M2166.02,-111C2174.45,-111 2183.39,-111 2191.99,-111"/>
<polygon fill="black" stroke="black" points="2191.94,-114.5 2201.94,-111 2191.94,-107.5 2191.94,-114.5"/>
</g>
<!-- __main___i_77 -->
<g id="node24" class="node">
<title>__main___i_77</title>
<polygon fill="#6148f3" stroke="#6148f3" points="2312.22,-105 2312.22,-141 2455.22,-141 2455.22,-105 2312.22,-105"/>
<text text-anchor="middle" x="2383.72" y="-119.3" font-family="Times,serif" font-size="14.00">0077: PopEnvironment</text>
</g>
<!-- __main___i_76&#45;&gt;__main___i_77 -->
<g id="edge25" class="edge">
<title>__main___i_76&#45;&gt;__main___i_77</title>
<path fill="none" stroke="black" d="M2275.08,-113.94C2283.02,-114.6 2291.78,-115.34 2300.77,-116.1"/>
<polygon fill="black" stroke="black" points="2300.48,-119.59 2310.73,-116.94 2301.06,-112.61 2300.48,-119.59"/>
</g>
<!-- __main___i_77&#45;&gt;__main___i_53 -->
<g id="edge27" class="edge">
<title>__main___i_77&#45;&gt;__main___i_53</title>
<path fill="none" stroke="#6148f3" stroke-dasharray="1,5" d="M2312.51,-140.99C2289.62,-145.46 2263.98,-149 2240.22,-149 1533.72,-149 1533.72,-149 1533.72,-149 1489.36,-149 1440.47,-142.83 1399.94,-135.99"/>
</g>
<!-- __main___i_77&#45;&gt;__main___i_78 -->
<g id="edge26" class="edge">
<title>__main___i_77&#45;&gt;__main___i_78</title>
<path fill="none" stroke="black" d="M2455.03,-118.86C2526.84,-114.63 2637.53,-108.12 2708.21,-103.96"/>
<polygon fill="black" stroke="black" points="2708.35,-107.46 2718.12,-103.38 2707.93,-100.47 2708.35,-107.46"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

Loading…
Cancel
Save