Skip to content

Commit

Permalink
feat(semantic/cfg): add iteration instructions. (#3566)
Browse files Browse the repository at this point in the history
  • Loading branch information
rzvxa committed Jun 13, 2024
1 parent defef05 commit f2dfd66
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 43 deletions.
1 change: 1 addition & 0 deletions crates/oxc_linter/src/rules/eslint/getter_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ impl GetterReturn {
// Ignore irrelevant elements.
| InstructionKind::Break(_)
| InstructionKind::Continue(_)
| InstructionKind::Iteration(_)
| InstructionKind::Statement => {}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_linter/src/rules/react/require_render_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ fn contains_return_statement<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> b
InstructionKind::Return(ReturnInstructionKind::ImplicitUndefined)
| InstructionKind::Break(_)
| InstructionKind::Continue(_)
| InstructionKind::Iteration(_)
| InstructionKind::Statement => {}
}
}
Expand Down
78 changes: 43 additions & 35 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use crate::{
checker::{EarlyErrorJavaScript, EarlyErrorTypeScript},
class::ClassTableBuilder,
control_flow::{
ControlFlowGraphBuilder, CtxCursor, CtxFlags, EdgeType, ErrorEdgeKind, Register,
ReturnInstructionKind,
ControlFlowGraphBuilder, CtxCursor, CtxFlags, EdgeType, ErrorEdgeKind,
IterationInstructionKind, Register, ReturnInstructionKind,
},
diagnostics::redeclaration,
jsdoc::JSDocBuilder,
Expand Down Expand Up @@ -71,6 +71,8 @@ pub struct SemanticBuilder<'a> {
pub cfg: ControlFlowGraphBuilder<'a>,

pub class_table_builder: ClassTableBuilder,

ast_nodes_records: Vec<Vec<AstNodeId>>,
}

pub struct SemanticBuilderReturn<'a> {
Expand Down Expand Up @@ -105,6 +107,7 @@ impl<'a> SemanticBuilder<'a> {
check_syntax_error: false,
cfg: ControlFlowGraphBuilder::default(),
class_table_builder: ClassTableBuilder::new(),
ast_nodes_records: Vec::new(),
}
}

Expand Down Expand Up @@ -207,6 +210,7 @@ impl<'a> SemanticBuilder<'a> {
} else {
self.nodes.add_node(ast_node, Some(self.current_node_id))
};
self.record_ast_node();
}

fn pop_ast_node(&mut self) {
Expand All @@ -215,6 +219,20 @@ impl<'a> SemanticBuilder<'a> {
}
}

fn record_ast_nodes(&mut self) {
self.ast_nodes_records.push(Vec::new());
}

fn retrieve_recorded_ast_nodes(&mut self) -> Vec<AstNodeId> {
self.ast_nodes_records.pop().expect("there is no ast nodes record to stop.")
}

fn record_ast_node(&mut self) {
if let Some(records) = self.ast_nodes_records.last_mut() {
records.push(self.current_node_id);
}
}

pub fn current_scope_flags(&self) -> ScopeFlags {
self.scope.get_flags(self.current_scope_id)
}
Expand Down Expand Up @@ -741,15 +759,15 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
let after_body_graph_ix = self.cfg.current_node_ix;
let after_for_stmt = self.cfg.new_basic_block_normal();
self.cfg.add_edge(before_for_graph_ix, test_graph_ix, EdgeType::Normal);
self.cfg.add_edge(after_test_graph_ix, before_body_graph_ix, EdgeType::Normal);
self.cfg.add_edge(after_test_graph_ix, before_body_graph_ix, EdgeType::Jump);
self.cfg.add_edge(after_body_graph_ix, update_graph_ix, EdgeType::Backedge);
self.cfg.add_edge(update_graph_ix, test_graph_ix, EdgeType::Backedge);
self.cfg.add_edge(after_test_graph_ix, after_for_stmt, EdgeType::Normal);

self.cfg
.ctx(None)
.mark_break(after_for_stmt)
.mark_continue(test_graph_ix)
.mark_continue(update_graph_ix)
.resolve_with_upper_label();
/* cfg */

Expand Down Expand Up @@ -782,19 +800,22 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
stmt.scope_id.set(Some(self.current_scope_id));
}
self.enter_node(kind);

self.visit_for_statement_left(&stmt.left);

/* cfg */
let before_for_stmt_graph_ix = self.cfg.current_node_ix;
let start_prepare_cond_graph_ix = self.cfg.new_basic_block_normal();
/* cfg */

self.record_ast_nodes();
self.visit_expression(&stmt.right);
let right_node = self.retrieve_recorded_ast_nodes().into_iter().next();

/* cfg */
let end_of_prepare_cond_graph_ix = self.cfg.current_node_ix;
// this basic block is always empty since there's no update condition in a for-in loop.
let basic_block_with_backedge_graph_ix = self.cfg.new_basic_block_normal();
let iteration_graph_ix = self.cfg.new_basic_block_normal();
self.cfg.append_iteration(right_node, IterationInstructionKind::In);
let body_graph_ix = self.cfg.new_basic_block_normal();

self.cfg.ctx(None).default().allow_break().allow_continue();
Expand All @@ -808,28 +829,20 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
// connect before for statement to the iterable expression
self.cfg.add_edge(before_for_stmt_graph_ix, start_prepare_cond_graph_ix, EdgeType::Normal);
// connect the end of the iterable expression to the basic block with back edge
self.cfg.add_edge(
end_of_prepare_cond_graph_ix,
basic_block_with_backedge_graph_ix,
EdgeType::Normal,
);
self.cfg.add_edge(end_of_prepare_cond_graph_ix, iteration_graph_ix, EdgeType::Normal);
// connect the basic block with back edge to the start of the body
self.cfg.add_edge(basic_block_with_backedge_graph_ix, body_graph_ix, EdgeType::Normal);
self.cfg.add_edge(iteration_graph_ix, body_graph_ix, EdgeType::Jump);
// connect the end of the body back to the basic block
// with back edge for the next iteration
self.cfg.add_edge(
end_of_body_graph_ix,
basic_block_with_backedge_graph_ix,
EdgeType::Backedge,
);
self.cfg.add_edge(end_of_body_graph_ix, iteration_graph_ix, EdgeType::Backedge);
// connect the basic block with back edge to the basic block after the for loop
// for when there are no more iterations left in the iterable
self.cfg.add_edge(basic_block_with_backedge_graph_ix, after_for_graph_ix, EdgeType::Normal);
self.cfg.add_edge(iteration_graph_ix, after_for_graph_ix, EdgeType::Normal);

self.cfg
.ctx(None)
.mark_break(after_for_graph_ix)
.mark_continue(basic_block_with_backedge_graph_ix)
.mark_continue(iteration_graph_ix)
.resolve_with_upper_label();
/* cfg */

Expand All @@ -847,19 +860,22 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
stmt.scope_id.set(Some(self.current_scope_id));
}
self.enter_node(kind);

self.visit_for_statement_left(&stmt.left);

/* cfg */
let before_for_stmt_graph_ix = self.cfg.current_node_ix;
let start_prepare_cond_graph_ix = self.cfg.new_basic_block_normal();
/* cfg */

self.record_ast_nodes();
self.visit_expression(&stmt.right);
let right_node = self.retrieve_recorded_ast_nodes().into_iter().next();

/* cfg */
let end_of_prepare_cond_graph_ix = self.cfg.current_node_ix;
// this basic block is always empty since there's no update condition in a for-of loop.
let basic_block_with_backedge_graph_ix = self.cfg.new_basic_block_normal();
let iteration_graph_ix = self.cfg.new_basic_block_normal();
self.cfg.append_iteration(right_node, IterationInstructionKind::Of);
let body_graph_ix = self.cfg.new_basic_block_normal();

self.cfg.ctx(None).default().allow_break().allow_continue();
Expand All @@ -873,28 +889,20 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
// connect before for statement to the iterable expression
self.cfg.add_edge(before_for_stmt_graph_ix, start_prepare_cond_graph_ix, EdgeType::Normal);
// connect the end of the iterable expression to the basic block with back edge
self.cfg.add_edge(
end_of_prepare_cond_graph_ix,
basic_block_with_backedge_graph_ix,
EdgeType::Normal,
);
self.cfg.add_edge(end_of_prepare_cond_graph_ix, iteration_graph_ix, EdgeType::Normal);
// connect the basic block with back edge to the start of the body
self.cfg.add_edge(basic_block_with_backedge_graph_ix, body_graph_ix, EdgeType::Normal);
self.cfg.add_edge(iteration_graph_ix, body_graph_ix, EdgeType::Jump);
// connect the end of the body back to the basic block
// with back edge for the next iteration
self.cfg.add_edge(
end_of_body_graph_ix,
basic_block_with_backedge_graph_ix,
EdgeType::Backedge,
);
self.cfg.add_edge(end_of_body_graph_ix, iteration_graph_ix, EdgeType::Backedge);
// connect the basic block with back edge to the basic block after the for loop
// for when there are no more iterations left in the iterable
self.cfg.add_edge(basic_block_with_backedge_graph_ix, after_for_graph_ix, EdgeType::Normal);
self.cfg.add_edge(iteration_graph_ix, after_for_graph_ix, EdgeType::Normal);

self.cfg
.ctx(None)
.mark_break(after_for_graph_ix)
.mark_continue(basic_block_with_backedge_graph_ix)
.mark_continue(iteration_graph_ix)
.resolve_with_upper_label();
/* cfg */

Expand Down Expand Up @@ -1281,7 +1289,7 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
let after_while_graph_ix = self.cfg.new_basic_block_normal();

self.cfg.add_edge(before_while_stmt_graph_ix, condition_graph_ix, EdgeType::Normal);
self.cfg.add_edge(condition_graph_ix, body_graph_ix, EdgeType::Normal);
self.cfg.add_edge(condition_graph_ix, body_graph_ix, EdgeType::Jump);
self.cfg.add_edge(after_body_graph_ix, condition_graph_ix, EdgeType::Backedge);
self.cfg.add_edge(condition_graph_ix, after_while_graph_ix, EdgeType::Normal);

Expand Down
7 changes: 6 additions & 1 deletion crates/oxc_semantic/src/control_flow/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ pub use context::{CtxCursor, CtxFlags};

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

#[derive(Debug, Default)]
Expand Down Expand Up @@ -144,6 +145,10 @@ impl<'a> ControlFlowGraphBuilder<'a> {
);
}

pub fn append_iteration(&mut self, node: Option<AstNodeId>, kind: IterationInstructionKind) {
self.push_instruction(InstructionKind::Iteration(kind), node);
}

pub fn append_throw(&mut self, node: AstNodeId) {
self.push_instruction(InstructionKind::Throw, Some(node));
self.append_unreachable();
Expand Down
13 changes: 13 additions & 0 deletions crates/oxc_semantic/src/control_flow/dot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::{
LabeledInstruction, ReturnInstructionKind,
};

use super::IterationInstructionKind;

pub trait DisplayDot {
fn display_dot(&self) -> String;
}
Expand Down Expand Up @@ -78,6 +80,8 @@ impl DisplayDot for Instruction {
InstructionKind::Break(LabeledInstruction::Unlabeled) => "break",
InstructionKind::Continue(LabeledInstruction::Labeled) => "continue <label>",
InstructionKind::Continue(LabeledInstruction::Unlabeled) => "continue",
InstructionKind::Iteration(IterationInstructionKind::Of) => "iteration <of>",
InstructionKind::Iteration(IterationInstructionKind::In) => "iteration <in>",
InstructionKind::Return(ReturnInstructionKind::ImplicitUndefined) => {
"return <implicit undefined>"
}
Expand Down Expand Up @@ -132,6 +136,15 @@ impl DebugDot for Instruction {
}
InstructionKind::Unreachable => "unreachable".to_string(),
InstructionKind::Throw => "throw".to_string(),
InstructionKind::Iteration(ref kind) => {
format!(
"Iteration({} {} {})",
self.node_id.map_or("None".to_string(), |id| ctx.debug_ast_kind(id)),
if matches!(kind, IterationInstructionKind::Of) { "of" } else { "in" },
// TODO: at this point we can't evaluate this note. needs access to the graph information.
"expr"
)
}
InstructionKind::Break(LabeledInstruction::Labeled) => {
let Some(AstKind::BreakStatement(BreakStatement { label: Some(label), .. })) =
self.node_id.map(|id| ctx.0.get_node(id)).map(AstNode::kind)
Expand Down
8 changes: 7 additions & 1 deletion crates/oxc_semantic/src/control_flow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ pub enum InstructionKind {
Break(LabeledInstruction),
Continue(LabeledInstruction),
Throw,
Iteration(IterationInstructionKind),
}

#[derive(Debug, Clone)]
pub enum ReturnInstructionKind {
ImplicitUndefined,
Expand All @@ -162,6 +162,12 @@ pub enum LabeledInstruction {
Unlabeled,
}

#[derive(Debug, Clone)]
pub enum IterationInstructionKind {
Of,
In,
}

#[derive(Debug, Clone)]
pub enum EdgeType {
/// Conditional jumps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ digraph {
6 -> 2 [ label = "Error(Implicit)" ]
7 -> 2 [ label = "Error(Implicit)" ]
3 -> 4 [ label = "Normal" ]
4 -> 6 [ label = "Normal" ]
4 -> 6 [ label = "Jump" ]
6 -> 5 [ label = "Backedge" ]
5 -> 4 [ label = "Backedge" ]
4 -> 7 [ label = "Normal" ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ digraph {
2 [ label = "" ]
3 [ label = "ForInStatement\nVariableDeclaration" ]
4 [ label = "" ]
5 [ label = "" ]
5 [ label = "Iteration(IdentifierReference(array) in expr)" ]
6 [ label = "BlockStatement\nIfStatement" ]
7 [ label = "BlockStatement\nExpressionStatement\nbreak" ]
8 [ label = "unreachable" ]
Expand Down Expand Up @@ -45,7 +45,7 @@ digraph {
14 -> 2 [ label = "Error(Implicit)" ]
3 -> 4 [ label = "Normal" ]
4 -> 5 [ label = "Normal" ]
5 -> 6 [ label = "Normal" ]
5 -> 6 [ label = "Jump" ]
13 -> 5 [ label = "Backedge" ]
5 -> 14 [ label = "Normal" ]
7 -> 14 [ label = "Jump" ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ bb4: {
}

bb5: {

iteration <in>
}

bb6: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ digraph {
3 -> 9 [ label = "Normal" ]
10 -> 0 [ label = "Error(Implicit)" ]
1 -> 2 [ label = "Normal" ]
2 -> 3 [ label = "Normal" ]
2 -> 3 [ label = "Jump" ]
9 -> 2 [ label = "Backedge" ]
2 -> 10 [ label = "Normal" ]
5 -> 2 [ label = "Jump" ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ digraph {
7 -> 8 [ label = "Unreachable" , style = "dotted" ]
9 -> 2 [ label = "Error(Implicit)" ]
3 -> 4 [ label = "Normal" ]
4 -> 7 [ label = "Normal" ]
4 -> 7 [ label = "Jump" ]
8 -> 4 [ label = "Backedge" ]
4 -> 9 [ label = "Normal" ]
10 -> 2 [ label = "Error(Implicit)" ]
Expand Down

0 comments on commit f2dfd66

Please sign in to comment.