Skip to content

Commit

Permalink
feat(cfg): add BasicBlockFlags
Browse files Browse the repository at this point in the history
  • Loading branch information
DonIsaac committed Oct 7, 2024
1 parent b848846 commit 3c6498f
Show file tree
Hide file tree
Showing 47 changed files with 977 additions and 393 deletions.
81 changes: 75 additions & 6 deletions crates/oxc_cfg/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use bitflags::bitflags;
use oxc_syntax::node::NodeId;

#[derive(Debug, Clone)]
pub struct BasicBlock {
pub instructions: Vec<Instruction>,
unreachable: bool,
flags: BasicBlockFlags,
}

impl BasicBlock {
pub(crate) fn new() -> Self {
BasicBlock { instructions: Vec::new(), unreachable: false }
pub(crate) fn new(flags: BasicBlockFlags) -> Self {
BasicBlock { instructions: Vec::new(), flags }
}

pub fn flags(&self) -> BasicBlockFlags {
self.flags
}

pub fn instructions(&self) -> &Vec<Instruction> {
Expand All @@ -17,17 +22,81 @@ impl BasicBlock {

#[inline]
pub fn is_unreachable(&self) -> bool {
self.unreachable
self.flags.contains(BasicBlockFlags::Unreachable)
}

#[inline]
pub fn mark_as_unreachable(&mut self) {
self.unreachable = true;
self.flags.set(BasicBlockFlags::Unreachable, true);
}

#[inline]
pub fn mark_as_reachable(&mut self) {
self.unreachable = false;
self.flags.set(BasicBlockFlags::Unreachable, false);
}

#[inline]
pub(crate) fn set_referenced(&mut self) {
self.flags |= if self.flags.contains(BasicBlockFlags::Referenced) {
BasicBlockFlags::Shared
} else {
BasicBlockFlags::Referenced
}
}
}

bitflags! {
/// Flags describing a basic block in a [`ControlFlowGraph`](crate::ControlFlowGraph).
///
/// Most of these match TypeScript's `FlowFlags`, but some new flags have
/// been added for our own use.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BasicBlockFlags: u16 {
// From TypeScript

/// Unreachable code
const Unreachable = 1 << 0;
/// Start of flow graph or subgraph. Could be a program, function,
/// module, etc.
const Start = 1 << 1;
/// Non-looping junction
const BranchLabel = 1 << 2;
/// Looping junction
const LoopLabel = 1 << 3;
/// Assignment
const Assignment = 1 << 4;
/// Condition known to be true
const TrueCondition = 1 << 5;
/// Condition known to be false
const FalseCondition = 1 << 6;
/// Switch statement clause
const SwitchClause = 1 << 7;
/// Potential array mutation
const ArrayMutation = 1 << 8;
/// Potential assertion call
const Call = 1 << 9;
/// Temporarily reduce antecedents of label
const ReduceLabel = 1 << 10;
/// Referenced as antecedent once
const Referenced = 1 << 11;
/// Referenced as antecedent more than once
const Shared = 1 << 12;

// New flags

/// A node reached only via implicit control flow
const Implicit = 1 << 13;
/// An error harness node
const Error = 1 << 14;
const Finalize = 1 << 15;

const ImplicitError = Self::Implicit.bits() | Self::Error.bits();

// Also from TypeScript

const Label = Self::BranchLabel.bits() | Self::LoopLabel.bits();
const Condition = Self::TrueCondition.bits() | Self::FalseCondition.bits();

}
}

Expand Down
36 changes: 28 additions & 8 deletions crates/oxc_cfg/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use super::{
BasicBlock, BlockNodeId, ControlFlowGraph, EdgeType, ErrorEdgeKind, Graph, Instruction,
InstructionKind, IterationInstructionKind, LabeledInstruction,
};
use crate::{BasicBlockId, ReturnInstructionKind};
use crate::{BasicBlockFlags, BasicBlockId, ReturnInstructionKind};

#[derive(Debug, Default)]
struct ErrorHarness(ErrorEdgeKind, BlockNodeId);
Expand Down Expand Up @@ -58,23 +58,32 @@ impl<'a> ControlFlowGraphBuilder<'a> {
.expect("expected `self.current_node_ix` to be a valid node index in self.graph")
}

pub(self) fn new_basic_block(&mut self) -> BlockNodeId {
pub(self) fn add_new_basic_block(&mut self, flags: BasicBlockFlags) -> BlockNodeId {
// current length would be the index of block we are adding on the next line.
let basic_block_ix = self.basic_blocks.push(BasicBlock::new());
let basic_block_ix = self.basic_blocks.push(BasicBlock::new(flags));
self.graph.add_node(basic_block_ix)
}

#[must_use]
#[inline]
pub fn new_basic_block_function(&mut self) -> BlockNodeId {
// we might want to differentiate between function blocks and normal blocks down the road.
self.new_basic_block_normal()
self.new_basic_block(BasicBlockFlags::Start)
}

/// # Panics
/// if there is no error harness to attach to.
#[must_use]
#[inline]
pub fn new_basic_block_normal(&mut self) -> BlockNodeId {
let graph_ix = self.new_basic_block();
self.new_basic_block(BasicBlockFlags::empty())
}

/// # Panics
/// if there is no error harness to attach to.
#[must_use]
pub fn new_basic_block(&mut self, flags: BasicBlockFlags) -> BlockNodeId {
let graph_ix = self.add_new_basic_block(flags);
self.current_node_ix = graph_ix;

// add an error edge to this block.
Expand Down Expand Up @@ -104,6 +113,11 @@ impl<'a> ControlFlowGraphBuilder<'a> {
{
self.basic_block_mut(b).mark_as_reachable();
}

if !matches!(weight, EdgeType::Error(ErrorEdgeKind::Implicit)) {
self.basic_block_mut(a).set_referenced();
}

self.graph.add_edge(a, b, weight);
}

Expand All @@ -118,7 +132,12 @@ impl<'a> ControlFlowGraphBuilder<'a> {
/// Creates and push a new `BasicBlockId` onto `self.error_path` stack.
/// Returns the `BasicBlockId` of the created error harness block.
pub fn attach_error_harness(&mut self, kind: ErrorEdgeKind) -> BlockNodeId {
let graph_ix = self.new_basic_block();
let flags = if matches!(kind, ErrorEdgeKind::Implicit) {
BasicBlockFlags::ImplicitError
} else {
BasicBlockFlags::Error
};
let graph_ix = self.add_new_basic_block(flags);
self.error_path.push(ErrorHarness(kind, graph_ix));
graph_ix
}
Expand All @@ -140,7 +159,7 @@ impl<'a> ControlFlowGraphBuilder<'a> {
/// Creates and push a new `BasicBlockId` onto `self.finalizers` stack.
/// Returns the `BasicBlockId` of the created finalizer block.
pub fn attach_finalizer(&mut self) -> BlockNodeId {
let graph_ix = self.new_basic_block();
let graph_ix = self.add_new_basic_block(BasicBlockFlags::Finalize);
self.finalizers.push(Some(graph_ix));
graph_ix
}
Expand Down Expand Up @@ -209,7 +228,8 @@ impl<'a> ControlFlowGraphBuilder<'a> {

pub fn append_unreachable(&mut self) {
let current_node_ix = self.current_node_ix;
let basic_block_with_unreachable_graph_ix = self.new_basic_block_normal();
let basic_block_with_unreachable_graph_ix =
self.new_basic_block(BasicBlockFlags::Unreachable);
self.push_instruction(InstructionKind::Unreachable, None);
self.current_basic_block().mark_as_unreachable();
self.add_edge(
Expand Down
10 changes: 6 additions & 4 deletions crates/oxc_cfg/src/dot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use rustc_hash::FxHashMap;

use super::IterationInstructionKind;
use crate::{
BasicBlock, ControlFlowGraph, EdgeType, Instruction, InstructionKind, LabeledInstruction,
ReturnInstructionKind,
BasicBlock, BasicBlockFlags, ControlFlowGraph, EdgeType, Instruction, InstructionKind,
LabeledInstruction, ReturnInstructionKind,
};

pub trait DisplayDot {
Expand Down Expand Up @@ -42,7 +42,7 @@ impl DisplayDot for ControlFlowGraph {
let block = &self.basic_blocks[*node.1];
let mut attrs = Attrs::default().with("label", block.display_dot());

if *node.1 == 0 {
if block.flags().contains(BasicBlockFlags::Start) {
attrs += ("color", "green");
}
if block.is_unreachable() {
Expand All @@ -58,7 +58,9 @@ impl DisplayDot for ControlFlowGraph {

impl DisplayDot for BasicBlock {
fn display_dot(&self) -> String {
self.instructions().iter().map(DisplayDot::display_dot).join("\n")
std::iter::once(format!("({:?})", self.flags()))
.chain(self.instructions.iter().map(DisplayDot::display_dot))
.join("\n")
}
}

Expand Down
36 changes: 22 additions & 14 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use rustc_hash::FxHashMap;
#[allow(clippy::wildcard_imports)]
use oxc_ast::{ast::*, AstKind, Trivias, Visit};
use oxc_cfg::{
ControlFlowGraphBuilder, CtxCursor, CtxFlags, EdgeType, ErrorEdgeKind,
BasicBlockFlags, ControlFlowGraphBuilder, CtxCursor, CtxFlags, EdgeType, ErrorEdgeKind,
IterationInstructionKind, ReturnInstructionKind,
};
use oxc_diagnostics::OxcDiagnostic;
Expand Down Expand Up @@ -642,7 +642,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
/* cfg */
let error_harness = control_flow!(self, |cfg| {
let error_harness = cfg.attach_error_harness(ErrorEdgeKind::Implicit);
let _program_basic_block = cfg.new_basic_block_normal();
let _program_basic_block = cfg.new_basic_block(BasicBlockFlags::Start);
error_harness
});
/* cfg - must be above directives as directives are in cfg */
Expand Down Expand Up @@ -800,7 +800,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
/* cfg */
let (before_do_while_stmt_graph_ix, start_body_graph_ix) = control_flow!(self, |cfg| {
let before_do_while_stmt_graph_ix = cfg.current_node_ix;
let start_body_graph_ix = cfg.new_basic_block_normal();
let start_body_graph_ix = cfg.new_basic_block(BasicBlockFlags::LoopLabel);
cfg.ctx(None).default().allow_break().allow_continue();
(before_do_while_stmt_graph_ix, start_body_graph_ix)
});
Expand Down Expand Up @@ -896,7 +896,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
let cfg_ixs = control_flow!(self, |cfg| {
if expr.operator.is_logical() {
let target_end_ix = cfg.current_node_ix;
let expr_start_ix = cfg.new_basic_block_normal();
let expr_start_ix = cfg.new_basic_block(BasicBlockFlags::Assignment);
Some((target_end_ix, expr_start_ix))
} else {
None
Expand Down Expand Up @@ -930,7 +930,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
let (before_conditional_graph_ix, start_of_condition_graph_ix) =
control_flow!(self, |cfg| {
let before_conditional_graph_ix = cfg.current_node_ix;
let start_of_condition_graph_ix = cfg.new_basic_block_normal();
let start_of_condition_graph_ix = cfg.new_basic_block(BasicBlockFlags::Condition);
(before_conditional_graph_ix, start_of_condition_graph_ix)
});
/* cfg */
Expand Down Expand Up @@ -1000,7 +1000,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
/* cfg */
let (before_for_graph_ix, test_graph_ix) = control_flow!(self, |cfg| {
let before_for_graph_ix = cfg.current_node_ix;
let test_graph_ix = cfg.new_basic_block_normal();
let test_graph_ix = cfg.new_basic_block(BasicBlockFlags::LoopLabel);
(before_for_graph_ix, test_graph_ix)
});
/* cfg */
Expand Down Expand Up @@ -1063,8 +1063,10 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
self.visit_for_statement_left(&stmt.left);

/* cfg */
let (before_for_stmt_graph_ix, start_prepare_cond_graph_ix) =
control_flow!(self, |cfg| (cfg.current_node_ix, cfg.new_basic_block_normal(),));
let (before_for_stmt_graph_ix, start_prepare_cond_graph_ix) = control_flow!(self, |cfg| (
cfg.current_node_ix,
cfg.new_basic_block(BasicBlockFlags::LoopLabel),
));
/* cfg */

self.record_ast_nodes();
Expand Down Expand Up @@ -1122,8 +1124,10 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
self.visit_for_statement_left(&stmt.left);

/* cfg */
let (before_for_stmt_graph_ix, start_prepare_cond_graph_ix) =
control_flow!(self, |cfg| (cfg.current_node_ix, cfg.new_basic_block_normal()));
let (before_for_stmt_graph_ix, start_prepare_cond_graph_ix) = control_flow!(self, |cfg| (
cfg.current_node_ix,
cfg.new_basic_block(BasicBlockFlags::LoopLabel)
));
/* cfg */

self.record_ast_nodes();
Expand Down Expand Up @@ -1177,8 +1181,10 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
self.enter_node(kind);

/* cfg - condition basic block */
let (before_if_stmt_graph_ix, start_of_condition_graph_ix) =
control_flow!(self, |cfg| (cfg.current_node_ix, cfg.new_basic_block_normal(),));
let (before_if_stmt_graph_ix, start_of_condition_graph_ix) = control_flow!(self, |cfg| (
cfg.current_node_ix,
cfg.new_basic_block(BasicBlockFlags::Condition)
));
/* cfg */

self.record_ast_nodes();
Expand Down Expand Up @@ -1547,8 +1553,10 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
self.enter_node(kind);

/* cfg - condition basic block */
let (before_while_stmt_graph_ix, condition_graph_ix) =
control_flow!(self, |cfg| (cfg.current_node_ix, cfg.new_basic_block_normal()));
let (before_while_stmt_graph_ix, condition_graph_ix) = control_flow!(self, |cfg| (
cfg.current_node_ix,
cfg.new_basic_block(BasicBlockFlags::LoopLabel)
));
/* cfg */

self.record_ast_nodes();
Expand Down
9 changes: 4 additions & 5 deletions crates/oxc_semantic/src/dot.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use itertools::Itertools as _;
use oxc_ast::{
ast::{BreakStatement, ContinueStatement},
AstKind,
Expand Down Expand Up @@ -114,11 +115,9 @@ impl DebugDot for ControlFlowGraph {

impl DebugDot for BasicBlock {
fn debug_dot(&self, ctx: DebugDotContext) -> String {
self.instructions().iter().fold(String::new(), |mut acc, it| {
acc.push_str(it.debug_dot(ctx).as_str());
acc.push('\n');
acc
})
std::iter::once(format!("({:?})", self.flags()))
.chain(self.instructions.iter().map(|it| it.debug_dot(ctx)))
.join("\n")
}
}

Expand Down
Loading

0 comments on commit 3c6498f

Please sign in to comment.