diff --git a/Cargo.lock b/Cargo.lock index 680eb662476bf..1d02a61dcc2d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1348,6 +1348,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "oxc_cfg" +version = "0.0.0" +dependencies = [ + "bitflags 2.5.0", + "itertools 0.13.0", + "oxc_syntax", + "petgraph", + "rustc-hash", +] + [[package]] name = "oxc_codegen" version = "0.14.0" @@ -1478,6 +1489,7 @@ dependencies = [ "once_cell", "oxc_allocator", "oxc_ast", + "oxc_cfg", "oxc_codegen", "oxc_diagnostics", "oxc_macros", @@ -1652,18 +1664,17 @@ dependencies = [ name = "oxc_semantic" version = "0.14.0" dependencies = [ - "bitflags 2.5.0", "indexmap", "insta", "itertools 0.13.0", "oxc_allocator", "oxc_ast", + "oxc_cfg", "oxc_diagnostics", "oxc_index", "oxc_parser", "oxc_span", "oxc_syntax", - "petgraph", "phf", "rustc-hash", "serde", diff --git a/Cargo.toml b/Cargo.toml index 4afc75219da03..4f3c7b9e676e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,7 @@ oxc_module_lexer = { version = "0.14.0", path = "crates/oxc_module_lexe oxc_isolated_declarations = { version = "0.14.0", path = "crates/oxc_isolated_declarations" } # publish = false +oxc_cfg = { path = "crates/oxc_cfg" } oxc_macros = { path = "crates/oxc_macros" } oxc_linter = { path = "crates/oxc_linter" } oxc_prettier = { path = "crates/oxc_prettier" } diff --git a/crates/oxc_cfg/Cargo.toml b/crates/oxc_cfg/Cargo.toml new file mode 100644 index 0000000000000..2adcaca57aa15 --- /dev/null +++ b/crates/oxc_cfg/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "oxc_cfg" +version = "0.0.0" +authors.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +categories.workspace = true +include = ["/src"] + +[lints] +workspace = true + +[lib] +doctest = true + +[dependencies] +oxc_syntax = { workspace = true } + +itertools = { workspace = true } +bitflags = { workspace = true } +petgraph = { workspace = true } +rustc-hash = { workspace = true } diff --git a/crates/oxc_semantic/src/control_flow/builder/context.rs b/crates/oxc_cfg/src/builder/context.rs similarity index 99% rename from crates/oxc_semantic/src/control_flow/builder/context.rs rename to crates/oxc_cfg/src/builder/context.rs index 19b73ea2d5195..5ae505cbc201c 100644 --- a/crates/oxc_semantic/src/control_flow/builder/context.rs +++ b/crates/oxc_cfg/src/builder/context.rs @@ -40,6 +40,7 @@ impl<'a> Ctx<'a> { } pub trait CtxCursor { + #![allow(clippy::return_self_not_must_use)] /// Marks the break jump position in the current context. fn mark_break(self, jmp_pos: BasicBlockId) -> Self; /// Marks the continue jump position in the current context. diff --git a/crates/oxc_semantic/src/control_flow/builder/mod.rs b/crates/oxc_cfg/src/builder/mod.rs similarity index 94% rename from crates/oxc_semantic/src/control_flow/builder/mod.rs rename to crates/oxc_cfg/src/builder/mod.rs index d72e9d730b755..ac0cf0d28f0b1 100644 --- a/crates/oxc_semantic/src/control_flow/builder/mod.rs +++ b/crates/oxc_cfg/src/builder/mod.rs @@ -4,11 +4,12 @@ use crate::ReturnInstructionKind; use context::Ctx; pub use context::{CtxCursor, CtxFlags}; +use oxc_syntax::node::AstNodeId; use petgraph::Direction; use super::{ - AstNodeId, BasicBlock, BasicBlockId, ControlFlowGraph, EdgeType, ErrorEdgeKind, Graph, - Instruction, InstructionKind, IterationInstructionKind, LabeledInstruction, + BasicBlock, BasicBlockId, ControlFlowGraph, EdgeType, ErrorEdgeKind, Graph, Instruction, + InstructionKind, IterationInstructionKind, LabeledInstruction, }; #[derive(Debug, Default)] @@ -70,7 +71,8 @@ impl<'a> ControlFlowGraphBuilder<'a> { self.new_basic_block_normal() } - /// # Panics if there is no error harness to attach to. + /// # Panics + /// if there is no error harness to attach to. #[must_use] pub fn new_basic_block_normal(&mut self) -> BasicBlockId { let graph_ix = self.new_basic_block(); @@ -122,7 +124,8 @@ impl<'a> ControlFlowGraphBuilder<'a> { graph_ix } - /// # Panics if there is no error harness pushed onto the stack, + /// # Panics + /// if there is no error harness pushed onto the stack, /// Or last harness doesn't match the expected `BasicBlockId`. pub fn release_error_harness(&mut self, expect: BasicBlockId) { let harness = self @@ -152,7 +155,8 @@ impl<'a> ControlFlowGraphBuilder<'a> { debug_assert!(result.as_ref().is_some_and(Option::is_none)); } - /// # Panics if last finalizer doesn't match the expected `BasicBlockId`. + /// # Panics + /// if last finalizer doesn't match the expected `BasicBlockId`. pub fn release_finalizer(&mut self, expect: BasicBlockId) { // return early if there is no finalizer. let Some(finalizer) = self.finalizers.pop() else { return }; @@ -216,6 +220,7 @@ impl<'a> ControlFlowGraphBuilder<'a> { ); } + /// # Panics #[inline] pub(self) fn push_instruction(&mut self, kind: InstructionKind, node_id: Option) { self.push_instruction_to(self.current_node_ix, kind, node_id); diff --git a/crates/oxc_cfg/src/dot.rs b/crates/oxc_cfg/src/dot.rs new file mode 100644 index 0000000000000..fac18b72b2fb3 --- /dev/null +++ b/crates/oxc_cfg/src/dot.rs @@ -0,0 +1,80 @@ +// use oxc_ast::{ +// ast::{BreakStatement, ContinueStatement}, +// AstKind, +// }; +use petgraph::{ + dot::{Config, Dot}, + visit::EdgeRef, +}; + +use crate::{ + BasicBlock, ControlFlowGraph, EdgeType, Instruction, InstructionKind, LabeledInstruction, + ReturnInstructionKind, +}; + +use super::IterationInstructionKind; + +pub trait DisplayDot { + fn display_dot(&self) -> String; +} + +impl DisplayDot for ControlFlowGraph { + fn display_dot(&self) -> String { + format!( + "{:?}", + Dot::with_attr_getters( + &self.graph, + &[Config::EdgeNoLabel, Config::NodeNoLabel], + &|_graph, edge| { + let weight = edge.weight(); + let label = format!("label = \"{weight:?}\" "); + if matches!(weight, EdgeType::Unreachable) + || self.basic_block(edge.source()).unreachable + { + format!("{label}, style = \"dotted\" ") + } else { + label + } + }, + &|_graph, node| format!( + "label = {:?} ", + self.basic_blocks[*node.1].display_dot().trim() + ), + ) + ) + } +} + +impl DisplayDot for BasicBlock { + fn display_dot(&self) -> String { + self.instructions().iter().fold(String::new(), |mut acc, it| { + acc.push_str(it.display_dot().as_str()); + acc.push('\n'); + acc + }) + } +} + +impl DisplayDot for Instruction { + fn display_dot(&self) -> String { + match self.kind { + InstructionKind::Statement => "statement", + InstructionKind::Unreachable => "unreachable", + InstructionKind::Throw => "throw", + InstructionKind::Condition => "condition", + InstructionKind::Iteration(IterationInstructionKind::Of) => "iteration ", + InstructionKind::Iteration(IterationInstructionKind::In) => "iteration ", + InstructionKind::Break(LabeledInstruction::Labeled) => "break