Skip to content

Commit

Permalink
refactor(semantic/cfg): cleanup control flow and it's builder. (#3650)
Browse files Browse the repository at this point in the history
Removed remaining parts of the old CFG implementation and some cleanups here and there.
  • Loading branch information
rzvxa committed Jun 13, 2024
1 parent 046ff3f commit f702fb9
Show file tree
Hide file tree
Showing 4 changed files with 9 additions and 101 deletions.
4 changes: 2 additions & 2 deletions crates/oxc_linter/src/rules/react/rules_of_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ impl Rule for RulesOfHooks {
return;
}

if !ctx.semantic().cfg().is_reachabale(func_cfg_id, node_cfg_id) {
if !ctx.semantic().cfg().is_reachable(func_cfg_id, node_cfg_id) {
// There should always be a control flow path between a parent and child node.
// If there is none it means we always do an early exit before reaching our hook call.
// In some cases it might mean that we are operating on an invalid `cfg` but in either
Expand Down Expand Up @@ -302,7 +302,7 @@ fn has_conditional_path_accept_throw(
.into_iter()
.filter(|(_, val)| *val == 0)
.any(|(f, _)| {
!cfg.is_reachabale_filtered(f, to_graph_id, |it| {
!cfg.is_reachable_filtered(f, to_graph_id, |it| {
if cfg
.basic_block(it)
.instructions()
Expand Down
36 changes: 2 additions & 34 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
class::ClassTableBuilder,
control_flow::{
ControlFlowGraphBuilder, CtxCursor, CtxFlags, EdgeType, ErrorEdgeKind,
IterationInstructionKind, Register, ReturnInstructionKind,
IterationInstructionKind, ReturnInstructionKind,
},
diagnostics::redeclaration,
jsdoc::JSDocBuilder,
Expand Down Expand Up @@ -541,13 +541,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
fn visit_debugger_statement(&mut self, stmt: &DebuggerStatement) {
let kind = AstKind::DebuggerStatement(self.alloc(stmt));
self.enter_node(kind);

/* cfg */
// just take the next_label since it should be taken by the next
// statement regardless of whether the statement can use it or not
self.cfg.next_label.take();
/* cfg */

self.leave_node(kind);
}

Expand Down Expand Up @@ -1006,10 +999,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {

self.visit_label_identifier(&stmt.label);

/* cfg */
self.cfg.next_label = Some(stmt.label.name.to_compact_str());
/* cfg */

self.visit_statement(&stmt.body);

/* cfg */
Expand Down Expand Up @@ -1373,8 +1362,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
func.scope_id.set(Some(self.current_scope_id));

/* cfg */
let preserved = self.cfg.preserve_expression_state();

let before_function_graph_ix = self.cfg.current_node_ix;
let error_harness = self.cfg.attach_error_harness(ErrorEdgeKind::Implicit);
let function_graph_ix = self.cfg.new_basic_block_function();
Expand All @@ -1398,7 +1385,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
}

/* cfg */
self.cfg.restore_expression_state(preserved);
self.cfg.ctx(None).resolve_expect(CtxFlags::FUNCTION);
self.cfg.release_error_harness(error_harness);
let after_function_graph_ix = self.cfg.new_basic_block_normal();
Expand Down Expand Up @@ -1435,11 +1421,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {

self.enter_node(kind);

/* cfg */
let preserved = self.cfg.preserve_expression_state();
self.cfg.store_final_assignments_into_this_array.push(vec![]);
/* cfg */

if let Some(id) = &class.id {
self.visit_binding_identifier(id);
}
Expand All @@ -1455,14 +1436,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
}
self.visit_class_body(&class.body);

/* cfg */
let _elements = self.cfg.store_final_assignments_into_this_array.pop().expect(
"expected there to be atleast one vec in the store_final_assignments_into_this_arrays",
);
self.cfg.restore_expression_state(preserved);
self.cfg.spread_indices.push(vec![]);
/* cfg */

self.leave_node(kind);
if is_class_expr {
self.leave_scope();
Expand All @@ -1485,7 +1458,6 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
expr.scope_id.set(Some(self.current_scope_id));

/* cfg */
let preserved = self.cfg.preserve_expression_state();
let current_node_ix = self.cfg.current_node_ix;
let error_harness = self.cfg.attach_error_harness(ErrorEdgeKind::Implicit);
let function_graph_ix = self.cfg.new_basic_block_function();
Expand All @@ -1500,15 +1472,11 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {

/* cfg */
self.cfg.add_edge(current_node_ix, function_graph_ix, EdgeType::NewFunction);
if expr.expression {
self.cfg.store_assignments_into_this_array.push(vec![]);
self.cfg.use_this_register = Some(Register::Return);
}
/* cfg */

self.visit_function_body(&expr.body);

/* cfg */
self.cfg.restore_expression_state(preserved);
self.cfg.ctx(None).resolve_expect(CtxFlags::FUNCTION);
self.cfg.release_error_harness(error_harness);
self.cfg.current_node_ix = current_node_ix;
Expand Down
47 changes: 2 additions & 45 deletions crates/oxc_semantic/src/control_flow/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ pub use context::{CtxCursor, CtxFlags};
use petgraph::Direction;

use super::{
AstNodeId, BasicBlock, BasicBlockId, CompactStr, ControlFlowGraph, EdgeType, ErrorEdgeKind,
Graph, Instruction, InstructionKind, IterationInstructionKind, LabeledInstruction,
PreservedExpressionState, Register,
AstNodeId, BasicBlock, BasicBlockId, ControlFlowGraph, EdgeType, ErrorEdgeKind, Graph,
Instruction, InstructionKind, IterationInstructionKind, LabeledInstruction,
};

#[derive(Debug, Default)]
Expand All @@ -25,27 +24,6 @@ pub struct ControlFlowGraphBuilder<'a> {
error_path: Vec<ErrorHarness>,
/// Stack of finalizers, the top most element is always the appropriate one for current node.
finalizers: Vec<BasicBlockId>,
// note: this should only land in the big box for all things that take arguments
// ie: callexpression, arrayexpression, etc
// todo: add assert that it is used every time?
pub use_this_register: Option<Register>,
pub next_free_register: u32,
pub store_assignments_into_this_array: Vec<Vec<Register>>,
pub store_final_assignments_into_this_array: Vec<Vec<Register>>,
// indexes of spreads in the store_assignments_into_this_array
pub spread_indices: Vec<Vec<usize>>,
// computed member expressions are only executed when we reach
// that part of the chain, so we keep this vec to patch them in later
pub should_save_stores_for_patching: bool,
pub saved_store: Option<usize>,
pub basic_blocks_with_breaks: Vec<Vec<BasicBlockId>>,
pub basic_blocks_with_continues: Vec<Vec<BasicBlockId>>,
// node indexes of the basic blocks of switch case conditions
pub switch_case_conditions: Vec<Vec<BasicBlockId>>,
pub next_label: Option<CompactStr>,
pub label_to_ast_node_ix: Vec<(CompactStr, AstNodeId)>,
pub ast_node_to_break_continue: Vec<(AstNodeId, usize, Option<usize>)>,
pub after_throw_block: Option<BasicBlockId>,
}

impl<'a> ControlFlowGraphBuilder<'a> {
Expand Down Expand Up @@ -243,27 +221,6 @@ impl<'a> ControlFlowGraphBuilder<'a> {
self.basic_block_mut(block).instructions.push(Instruction { kind, node_id });
}

#[must_use]
pub fn preserve_expression_state(&mut self) -> PreservedExpressionState {
let use_this_register = self.use_this_register.take();
let mut store_final_assignments_into_this_array = vec![];
std::mem::swap(
&mut store_final_assignments_into_this_array,
&mut self.store_final_assignments_into_this_array,
);

// DO NOT preserve: saved_stores, should_save_stores_for_patching
// should_save_stores_for_patching must always be active to catch
// all stores, preserving will mess it up.
PreservedExpressionState { use_this_register, store_final_assignments_into_this_array }
}

pub fn restore_expression_state(&mut self, mut preserved_state: PreservedExpressionState) {
self.use_this_register = preserved_state.use_this_register.take();
self.store_final_assignments_into_this_array =
preserved_state.store_final_assignments_into_this_array;
}

pub fn enter_statement(&mut self, stmt: AstNodeId) {
self.push_statement(stmt);
}
Expand Down
23 changes: 3 additions & 20 deletions crates/oxc_semantic/src/control_flow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,11 @@ impl ControlFlowGraph {
self.basic_blocks.get_mut(ix).expect("expected a valid node id in self.basic_blocks")
}

pub fn is_reachabale(&self, from: BasicBlockId, to: BasicBlockId) -> bool {
self.is_reachabale_filtered(from, to, |_| Control::Continue)
pub fn is_reachable(&self, from: BasicBlockId, to: BasicBlockId) -> bool {
self.is_reachable_filtered(from, to, |_| Control::Continue)
}

pub fn is_reachabale_filtered<F: Fn(BasicBlockId) -> Control<bool>>(
pub fn is_reachable_filtered<F: Fn(BasicBlockId) -> Control<bool>>(
&self,
from: BasicBlockId,
to: BasicBlockId,
Expand Down Expand Up @@ -352,21 +352,4 @@ impl ControlFlowGraph {
})
.is_err()
}

pub fn has_conditional_path(&self, from: BasicBlockId, to: BasicBlockId) -> bool {
let graph = &self.graph;
// All nodes should be able to reach the `to` node, Otherwise we have a conditional/branching flow.
petgraph::algo::dijkstra(graph, from, Some(to), |e| match e.weight() {
EdgeType::NewFunction | EdgeType::Error(_) | EdgeType::Finalize | EdgeType::Join => 1,
EdgeType::Jump | EdgeType::Unreachable | EdgeType::Backedge | EdgeType::Normal => 0,
})
.into_iter()
.filter(|(_, val)| *val == 0)
.any(|(f, _)| !self.is_reachabale(f, to))
}
}

pub struct PreservedExpressionState {
pub use_this_register: Option<Register>,
pub store_final_assignments_into_this_array: Vec<Vec<Register>>,
}

0 comments on commit f702fb9

Please sign in to comment.