diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index c23c75cc046..a5727463e04 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -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::(); + match opcode { + Opcode::RotateLeft | Opcode::RotateRight => { + let operands = self.read::(pc).to_string(); + pc += size_of::(); + 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::(pc).to_string(); + pc += size_of::(); + 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::(pc).to_string(); + pc += size_of::(); + 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::(pc).to_string(); + pc += size_of::(); + 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::(pc); + pc += size_of::(); + 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::(pc); + pc += size_of::(); + 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::(pc); + pc += size_of::(); + 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::(pc); + pc += size_of::(); + 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::(pc).to_string(); + pc += size_of::(); + 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::(pc); + pc += size_of::(); + let operand2 = self.read::(pc); + pc += size_of::(); + 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::(pc); + let fn_name = interner.resolve_expect(self.functions[operand as usize].name).to_string(); + pc += size_of::(); + 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::(pc); + pc += size_of::(); + 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::(pc); + pc += size_of::(); + 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); @@ -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 } }