Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Implement instruction flowgraph generator #2422

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5bda107
Implement generic graph with graphviz format
HalidOdat Nov 8, 2022
0a87747
Generate mermaid graphs
HalidOdat Nov 9, 2022
c8426b5
generate colored nodes and edges
HalidOdat Nov 10, 2022
6acf808
Simplify adding nodes and edges
HalidOdat Nov 10, 2022
6b19f04
Add subgraphs
HalidOdat Nov 10, 2022
6315aef
Add recursive subgraphs
HalidOdat Nov 10, 2022
2b1b457
Move to_graph to graph module
HalidOdat Nov 10, 2022
130d3eb
Add CLI option for generating flowgraphs
HalidOdat Nov 10, 2022
6e945a1
Put under feature-gate
HalidOdat Nov 10, 2022
0676727
Programmatically generate colors
HalidOdat Nov 10, 2022
b4fc149
Make Return instruction point to End
HalidOdat Nov 10, 2022
12e11db
Handle Switch and Try/Catch instructions
HalidOdat Nov 10, 2022
4d9bf79
Make returns point to finally if it exists
HalidOdat Nov 10, 2022
40f828f
Make Throw point to catch block
HalidOdat Nov 10, 2022
ad149b8
Add CLI option for flowgraph direction
HalidOdat Nov 11, 2022
fe440a3
Include opcode locations
HalidOdat Nov 11, 2022
12757d9
Documentation
HalidOdat Nov 13, 2022
1dd9022
Document mermaid block fix
HalidOdat Nov 13, 2022
050fc64
Revent node name collisions
HalidOdat Nov 13, 2022
b0ca0fd
Split into sub-modules
HalidOdat Nov 13, 2022
5f48fb2
Refactor `Color`
HalidOdat Nov 13, 2022
60d3333
Add documentation to flowgraph feature
HalidOdat Nov 13, 2022
c27e382
Run rrettier on `debugging.md`
HalidOdat Nov 13, 2022
a56b0ee
Merge branch 'main' into feature/instruction-flowgraph
HalidOdat Nov 22, 2022
c6e956f
Fix clippy error
HalidOdat Nov 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion boa_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ repository.workspace = true
rust-version.workspace = true

[dependencies]
boa_engine = { workspace = true, features = ["deser", "console"] }
boa_engine = { workspace = true, features = ["deser", "console", "flowgraph"] }
boa_ast = { workspace = true, features = ["serde"]}
boa_interner.workspace = true
boa_parser.workspace = true
Expand Down
97 changes: 94 additions & 3 deletions boa_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@
)]

use boa_ast::StatementList;
use boa_engine::Context;
use boa_engine::{
vm::flowgraph::{Direction, Graph},
Context, JsResult,
};
use clap::{Parser, ValueEnum, ValueHint};
use colored::{Color, Colorize};
use rustyline::{config::Config, error::ReadlineError, EditMode, Editor};
Expand Down Expand Up @@ -96,17 +99,38 @@ struct Opt {
short = 'a',
value_name = "FORMAT",
ignore_case = true,
value_enum
value_enum,
conflicts_with = "graph"
)]
dump_ast: Option<Option<DumpFormat>>,

/// Dump the AST to stdout with the given format.
#[arg(long, short)]
#[arg(long, short, conflicts_with = "graph")]
trace: bool,

/// Use vi mode in the REPL
#[arg(long = "vi")]
vi_mode: bool,

/// Generate instruction flowgraph. Default is Graphviz.
#[arg(
long,
value_name = "FORMAT",
ignore_case = true,
value_enum,
group = "graph"
)]
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 {
Expand Down Expand Up @@ -136,6 +160,28 @@ enum DumpFormat {
JsonPretty,
}

/// Represents the format of the instruction flowgraph.
#[derive(Debug, Clone, Copy, ValueEnum)]
HalidOdat marked this conversation as resolved.
Show resolved Hide resolved
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,
}
HalidOdat marked this conversation as resolved.
Show resolved Hide resolved

/// Parses the the token stream into an AST and returns it.
///
/// Returns a error of type String with a message,
Expand Down Expand Up @@ -184,6 +230,31 @@ where
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)
}

pub fn main() -> Result<(), io::Error> {
let args = Opt::parse();

Expand All @@ -199,6 +270,16 @@ pub fn main() -> Result<(), io::Error> {
if let Err(e) = dump(&buffer, &args, &mut context) {
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 {
match context.eval(&buffer) {
Ok(v) => println!("{}", v.display()),
Expand Down Expand Up @@ -245,6 +326,16 @@ pub fn main() -> Result<(), io::Error> {
if let Err(e) = dump(&line, &args, &mut context) {
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 {
match context.eval(line.trim_end()) {
Ok(v) => println!("{}", v.display()),
Expand Down
3 changes: 3 additions & 0 deletions boa_engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ intl = [

fuzz = ["boa_ast/fuzz", "boa_interner/fuzz"]

# Enable Boa's VM instruction flowgraph generator.
flowgraph = []

# Enable Boa's WHATWG console object implementation.
console = []

Expand Down
81 changes: 81 additions & 0 deletions boa_engine/src/vm/flowgraph/color.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 65 additions & 0 deletions boa_engine/src/vm/flowgraph/edge.rs
Original file line number Diff line number Diff line change
@@ -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_;
}
}
Loading