Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cfg): add BasicBlockFlags #6339

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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