Skip to content

Commit

Permalink
Implement basic instruction to flowgraph visualizer
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed Nov 8, 2022
1 parent bc61243 commit 8772ee2
Showing 1 changed file with 309 additions and 0 deletions.
309 changes: 309 additions & 0 deletions boa_engine/src/vm/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,308 @@ impl CodeBlock {
}
}

impl CodeBlock {
fn to_dot_string(&self, result_final: &mut String, interner: &Interner) {
let mut name = interner.resolve_expect(self.name).to_string();
if self.name == Sym::MAIN {
name = "__main__".to_string();
}

let mut result = String::new();
result += &format!("subgraph cluster_{name} {{\n");
result += "style = filled;\n";
result += &format!("label = \"{name}\";\n");


let mut pc = 0;
let mut count = 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;

pc += size_of::<Opcode>();
match opcode {
Opcode::RotateLeft | Opcode::RotateRight => {
let operands = self.read::<u8>(pc).to_string();
pc += size_of::<u8>();
result += &format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operands}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
}
Opcode::PushInt8 => {
let operands = self.read::<i8>(pc).to_string();
pc += size_of::<i8>();
result += &format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operands}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
}
Opcode::PushInt16 => {
let operands = self.read::<i16>(pc).to_string();
pc += size_of::<i16>();
result += &format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operands}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
}
Opcode::PushInt32 => {
let operands = self.read::<i32>(pc).to_string();
pc += size_of::<i32>();
result += &format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operands}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
}
Opcode::PushRational => {
let operand = self.read::<f64>(pc);
pc += size_of::<f64>();
let operands = ryu_js::Buffer::new().format(operand).to_string();
result += &format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operands}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
}
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().to_string();
result += &format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operand_str}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
}
Opcode::Jump => {
let operand = self.read::<u32>(pc);
pc += size_of::<u32>();
result += &format!("{name}_i_{previous_pc} [shape=diamond, label=\"{opcode_str}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{operand} [color=green, len=f];\n");
}
Opcode::JumpIfFalse
| Opcode::JumpIfNotUndefined
| Opcode::JumpIfNullOrUndefined
=> {
let operand = self.read::<u32>(pc);
pc += size_of::<u32>();
result += &format!("{name}_i_{previous_pc} [shape=diamond, label=\"{opcode_str}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{operand} [color=green, label=\"true\", len=f];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [color=red, label=\"false\", len=f];\n");
}
Opcode::CatchStart
| Opcode::FinallySetJump
| Opcode::Case
| Opcode::Default
| Opcode::LogicalAnd
| Opcode::LogicalOr
| Opcode::Coalesce
| Opcode::CallEval
| Opcode::Call
| Opcode::New
| Opcode::SuperCall
| Opcode::ForInLoopInitIterator
| Opcode::ForInLoopNext
| Opcode::ForAwaitOfLoopNext
| Opcode::ConcatToString
| Opcode::GeneratorNextDelegate => {
let operands = self.read::<u32>(pc).to_string();
pc += size_of::<u32>();
result += &format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operands}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
}
Opcode::TryStart
| Opcode::PushDeclarativeEnvironment
| Opcode::PushFunctionEnvironment
| Opcode::CopyDataProperties => {
let operand1 = self.read::<u32>(pc);
pc += size_of::<u32>();
let operand2 = self.read::<u32>(pc);
pc += size_of::<u32>();
let operands = format!("{operand1}, {operand2}");
result += &format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operands}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
}
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 operands = format!(
"'{fn_name}' (length: {})",
self.functions[operand as usize].length
);
result +=
&format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operands}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
result += &format!("{name}_i_{previous_pc} -> start_{fn_name} [len=f];\n");
}
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 operands = format!(
"'{}'",
interner.resolve_expect(self.bindings[operand as usize].name().sym()),
);
result +=
&format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operands}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
}
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 operands = format!(
"'{}'",
interner.resolve_expect(self.names[operand as usize].sym()),
);
result +=
&format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str} {operands}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
}
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::Throw
| Opcode::TryEnd
| Opcode::CatchEnd
| Opcode::CatchEnd2
| Opcode::FinallyStart
| Opcode::FinallyEnd
| Opcode::This
| Opcode::Super
| Opcode::Return
| Opcode::PopEnvironment
| 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::Nop => {
result +=
&format!("{name}_i_{previous_pc} [style=record, label=\"{opcode_str}\"];\n");
result += &format!("{name}_i_{previous_pc} -> {name}_i_{pc} [len=f];\n");
},
}
count += 1;
}


result += &format!("{name}_i_{pc} [shape=Mdiamond, label=\"End\", style=filled, color=red];\n");
result += &format!("start_{name} -> {name}_i_0;\n");
result += &format!("start_{name} [shape=Mdiamond, label=\"start\", style=filled, color=green];\n");
result += "}\n";

result_final.push_str(&result);

for (_i, code) in self.functions.iter().enumerate() {
code.to_dot_string(result_final, interner);
}
}
}

impl ToInternedString for CodeBlock {
fn to_interned_string(&self, interner: &Interner) -> String {
let name = interner.resolve_expect(self.name);
Expand Down Expand Up @@ -464,6 +766,13 @@ impl ToInternedString for CodeBlock {
}
}

let mut result = String::new();
result += "\ndigraph {\n";
result += "node [shape=record];\nrankdir=LR;\n";
self.to_dot_string(&mut result, interner);
result += "}\n";
println!("{}", result);

f
}
}
Expand Down

0 comments on commit 8772ee2

Please sign in to comment.