mirror of https://github.com/boa-dev/boa.git
Browse Source
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
12 changed files with 1452 additions and 5 deletions
@ -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}"), |
||||
} |
||||
} |
||||
} |
@ -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_; |
||||
} |
||||
} |
@ -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 |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
@ -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, |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue