mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[rust] Extract HIR initialization passes to hir crate
This commit is contained in:
Generated
+2
@@ -521,10 +521,12 @@ name = "forget_hir"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"forget_diagnostics",
|
||||
"forget_estree",
|
||||
"forget_utils",
|
||||
"indexmap 2.0.0",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::HashSet;
|
||||
|
||||
use bumpalo::boxed::Box;
|
||||
use bumpalo::collections::String;
|
||||
use forget_diagnostics::{Diagnostic, DiagnosticSeverity};
|
||||
use forget_diagnostics::Diagnostic;
|
||||
use forget_estree::{
|
||||
AssignmentTarget, BinaryExpression, BlockStatement, Expression, ForInit, ForStatement,
|
||||
Function, FunctionExpression, IfStatement, JsValue, Literal, Pattern, Statement,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
use std::rc::Rc;
|
||||
|
||||
use bumpalo::boxed::Box;
|
||||
use bumpalo::collections::{String, Vec};
|
||||
use forget_diagnostics::{invariant, Diagnostic, DiagnosticSeverity};
|
||||
use forget_diagnostics::Diagnostic;
|
||||
use forget_hir::{
|
||||
BasicBlock, BlockId, BlockKind, Environment, GotoKind, Identifier, IdentifierData, InstrIx,
|
||||
Instruction, InstructionIdGenerator, InstructionValue, Terminal, TerminalValue, Type, HIR,
|
||||
initialize_hir, BasicBlock, BlockId, BlockKind, Environment, GotoKind, Identifier,
|
||||
IdentifierData, InstrIx, Instruction, InstructionIdGenerator, InstructionValue, Terminal,
|
||||
TerminalValue, Type, HIR,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
@@ -346,161 +346,3 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_hir<'a>(hir: &mut HIR<'a>) -> Result<(), Diagnostic> {
|
||||
reverse_postorder_blocks(hir);
|
||||
remove_unreachable_for_updates(hir);
|
||||
remove_unreachable_fallthroughs(hir);
|
||||
remove_unreachable_do_while_statements(hir);
|
||||
mark_instruction_ids(hir)?;
|
||||
mark_predecessors(hir);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Modifies the HIR to put the blocks in reverse postorder, with predecessors before
|
||||
/// successors (except for the case of loops)
|
||||
pub fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) {
|
||||
let mut visited = HashSet::<BlockId>::with_capacity(hir.blocks.len());
|
||||
let mut postorder = std::vec::Vec::<BlockId>::with_capacity(hir.blocks.len());
|
||||
fn visit<'a>(
|
||||
block_id: BlockId,
|
||||
hir: &HIR<'a>,
|
||||
visited: &mut HashSet<BlockId>,
|
||||
postorder: &mut std::vec::Vec<BlockId>,
|
||||
) {
|
||||
if !visited.insert(block_id) {
|
||||
// already visited
|
||||
return;
|
||||
}
|
||||
let block = hir.block(block_id);
|
||||
let terminal = &block.terminal;
|
||||
match &terminal.value {
|
||||
TerminalValue::Branch(terminal) => {
|
||||
visit(terminal.alternate, hir, visited, postorder);
|
||||
visit(terminal.consequent, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::If(terminal) => {
|
||||
visit(terminal.alternate, hir, visited, postorder);
|
||||
visit(terminal.consequent, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::For(terminal) => {
|
||||
visit(terminal.init, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::DoWhile(terminal) => {
|
||||
visit(terminal.body, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::Goto(terminal) => {
|
||||
visit(terminal.block, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::Return(..) => { /* no-op */ }
|
||||
TerminalValue::Unsupported(..) => {
|
||||
panic!("Unexpected unsupported terminal")
|
||||
}
|
||||
}
|
||||
postorder.push(block_id);
|
||||
}
|
||||
visit(hir.entry, &hir, &mut visited, &mut postorder);
|
||||
|
||||
// NOTE: could consider sorting the blocks in-place by key
|
||||
let mut blocks = IndexMap::with_capacity(hir.blocks.len());
|
||||
for id in postorder.iter().rev().cloned() {
|
||||
blocks.insert(id, hir.blocks.remove(&id).unwrap());
|
||||
}
|
||||
|
||||
hir.blocks = blocks;
|
||||
}
|
||||
|
||||
/// Prunes ForTerminal.update values (sets to None) if they are unreachable
|
||||
pub fn remove_unreachable_for_updates<'a>(hir: &mut HIR<'a>) {
|
||||
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
|
||||
|
||||
for block in hir.blocks.values_mut() {
|
||||
if let TerminalValue::For(terminal) = &mut block.terminal.value {
|
||||
if let Some(update) = terminal.update {
|
||||
if !block_ids.contains(&update) {
|
||||
terminal.update = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prunes unreachable fallthrough values, setting them to None if the referenced
|
||||
/// block was not otherwise reachable.
|
||||
pub fn remove_unreachable_fallthroughs<'a>(hir: &mut HIR<'a>) {
|
||||
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
|
||||
|
||||
for block in hir.blocks.values_mut() {
|
||||
block
|
||||
.terminal
|
||||
.value
|
||||
.map_optional_fallthroughs(|fallthrough| {
|
||||
if block_ids.contains(&fallthrough) {
|
||||
Some(fallthrough)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Rewrites DoWhile statements into Gotos if the test block is not reachable
|
||||
pub fn remove_unreachable_do_while_statements<'a>(hir: &mut HIR<'a>) {
|
||||
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
|
||||
|
||||
for block in hir.blocks.values_mut() {
|
||||
if let TerminalValue::DoWhile(terminal) = &mut block.terminal.value {
|
||||
if !block_ids.contains(&terminal.test) {
|
||||
block.terminal.value = TerminalValue::Goto(forget_hir::GotoTerminal {
|
||||
block: terminal.body,
|
||||
kind: GotoKind::Break,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the instruction ids for all instructions and blocks
|
||||
/// Relies on the blocks being in reverse postorder to ensure that id ordering is correct
|
||||
pub fn mark_instruction_ids<'a>(hir: &mut HIR<'a>) -> Result<(), Diagnostic> {
|
||||
let mut id_gen = InstructionIdGenerator::new();
|
||||
let mut visited = HashSet::<(usize, usize)>::new();
|
||||
for (ii, block) in hir.blocks.values_mut().enumerate() {
|
||||
let block_id = block.id;
|
||||
for (jj, instr_ix) in block.instructions.iter_mut().enumerate() {
|
||||
invariant(visited.insert((ii, jj)), || {
|
||||
Diagnostic::invariant(BuildHIRError::BlockVisitedTwice { block: block_id }, None)
|
||||
})?;
|
||||
let instr = &mut hir.instructions[usize::from(*instr_ix)];
|
||||
instr.id = id_gen.next();
|
||||
}
|
||||
block.terminal.id = id_gen.next();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the predecessors of each block
|
||||
pub fn mark_predecessors<'a>(hir: &mut HIR<'a>) {
|
||||
for block in hir.blocks.values_mut() {
|
||||
block.predecessors.clear();
|
||||
}
|
||||
let mut visited = HashSet::<BlockId>::with_capacity(hir.blocks.len());
|
||||
fn visit<'a>(
|
||||
block_id: BlockId,
|
||||
prev_id: Option<BlockId>,
|
||||
hir: &mut HIR<'a>,
|
||||
visited: &mut HashSet<BlockId>,
|
||||
) {
|
||||
let block = hir.block_mut(block_id);
|
||||
if let Some(prev_id) = prev_id {
|
||||
block.predecessors.insert(prev_id);
|
||||
}
|
||||
if !visited.insert(block_id) {
|
||||
return;
|
||||
}
|
||||
for successor in block.terminal.value.successors() {
|
||||
visit(successor, Some(block_id), hir, visited)
|
||||
}
|
||||
}
|
||||
visit(hir.entry, None, hir, &mut visited);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use forget_hir::BlockId;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors which can occur during HIR construction
|
||||
@@ -32,10 +31,6 @@ pub enum BuildHIRError {
|
||||
#[error("React functions may not reassign variables defined outside of the component or hook")]
|
||||
ReassignedGlobal,
|
||||
|
||||
/// ErrorSeverity::Invariant
|
||||
#[error("Invariant: Expected block {block} not to have been visited yet")]
|
||||
BlockVisitedTwice { block: BlockId },
|
||||
|
||||
/// ErrorSeverity::InvalidSyntax
|
||||
#[error("Could not resolve a target for `break` statement")]
|
||||
UnresolvedBreakTarget,
|
||||
|
||||
@@ -4,9 +4,4 @@ mod context;
|
||||
mod error;
|
||||
|
||||
pub use build::build;
|
||||
pub use builder::{
|
||||
initialize_hir, mark_instruction_ids, mark_predecessors,
|
||||
remove_unreachable_do_while_statements, remove_unreachable_fallthroughs,
|
||||
remove_unreachable_for_updates, reverse_postorder_blocks,
|
||||
};
|
||||
pub use error::*;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,3 +18,5 @@ forget_estree = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
forget_utils = { workspace = true }
|
||||
forget_diagnostics = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use forget_diagnostics::{invariant, Diagnostic};
|
||||
use indexmap::IndexMap;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{BlockId, GotoKind, GotoTerminal, InstructionIdGenerator, TerminalValue, HIR};
|
||||
|
||||
/// Runs a variety of passes to put the HIR in canonical form. This should be called
|
||||
/// after initial HIR construction and after any transformations that change the
|
||||
/// shape of the control-flow graph.
|
||||
pub fn initialize_hir<'a>(hir: &mut HIR<'a>) -> Result<(), Diagnostic> {
|
||||
reverse_postorder_blocks(hir);
|
||||
remove_unreachable_for_updates(hir);
|
||||
remove_unreachable_fallthroughs(hir);
|
||||
remove_unreachable_do_while_statements(hir);
|
||||
mark_instruction_ids(hir)?;
|
||||
mark_predecessors(hir);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Modifies the HIR to put the blocks in reverse postorder, with predecessors before
|
||||
/// successors (except for the case of loops)
|
||||
pub fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) {
|
||||
let mut visited = HashSet::<BlockId>::with_capacity(hir.blocks.len());
|
||||
let mut postorder = std::vec::Vec::<BlockId>::with_capacity(hir.blocks.len());
|
||||
fn visit<'a>(
|
||||
block_id: BlockId,
|
||||
hir: &HIR<'a>,
|
||||
visited: &mut HashSet<BlockId>,
|
||||
postorder: &mut std::vec::Vec<BlockId>,
|
||||
) {
|
||||
if !visited.insert(block_id) {
|
||||
// already visited
|
||||
return;
|
||||
}
|
||||
let block = hir.block(block_id);
|
||||
let terminal = &block.terminal;
|
||||
match &terminal.value {
|
||||
TerminalValue::Branch(terminal) => {
|
||||
visit(terminal.alternate, hir, visited, postorder);
|
||||
visit(terminal.consequent, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::If(terminal) => {
|
||||
visit(terminal.alternate, hir, visited, postorder);
|
||||
visit(terminal.consequent, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::For(terminal) => {
|
||||
visit(terminal.init, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::DoWhile(terminal) => {
|
||||
visit(terminal.body, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::Goto(terminal) => {
|
||||
visit(terminal.block, hir, visited, postorder);
|
||||
}
|
||||
TerminalValue::Return(..) => { /* no-op */ }
|
||||
TerminalValue::Unsupported(..) => {
|
||||
panic!("Unexpected unsupported terminal")
|
||||
}
|
||||
}
|
||||
postorder.push(block_id);
|
||||
}
|
||||
visit(hir.entry, &hir, &mut visited, &mut postorder);
|
||||
|
||||
// NOTE: could consider sorting the blocks in-place by key
|
||||
let mut blocks = IndexMap::with_capacity(hir.blocks.len());
|
||||
for id in postorder.iter().rev().cloned() {
|
||||
blocks.insert(id, hir.blocks.remove(&id).unwrap());
|
||||
}
|
||||
|
||||
hir.blocks = blocks;
|
||||
}
|
||||
|
||||
/// Prunes ForTerminal.update values (sets to None) if they are unreachable
|
||||
pub fn remove_unreachable_for_updates<'a>(hir: &mut HIR<'a>) {
|
||||
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
|
||||
|
||||
for block in hir.blocks.values_mut() {
|
||||
if let TerminalValue::For(terminal) = &mut block.terminal.value {
|
||||
if let Some(update) = terminal.update {
|
||||
if !block_ids.contains(&update) {
|
||||
terminal.update = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prunes unreachable fallthrough values, setting them to None if the referenced
|
||||
/// block was not otherwise reachable.
|
||||
pub fn remove_unreachable_fallthroughs<'a>(hir: &mut HIR<'a>) {
|
||||
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
|
||||
|
||||
for block in hir.blocks.values_mut() {
|
||||
block
|
||||
.terminal
|
||||
.value
|
||||
.map_optional_fallthroughs(|fallthrough| {
|
||||
if block_ids.contains(&fallthrough) {
|
||||
Some(fallthrough)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Rewrites DoWhile statements into Gotos if the test block is not reachable
|
||||
pub fn remove_unreachable_do_while_statements<'a>(hir: &mut HIR<'a>) {
|
||||
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
|
||||
|
||||
for block in hir.blocks.values_mut() {
|
||||
if let TerminalValue::DoWhile(terminal) = &mut block.terminal.value {
|
||||
if !block_ids.contains(&terminal.test) {
|
||||
block.terminal.value = TerminalValue::Goto(GotoTerminal {
|
||||
block: terminal.body,
|
||||
kind: GotoKind::Break,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the instruction ids for all instructions and blocks
|
||||
/// Relies on the blocks being in reverse postorder to ensure that id ordering is correct
|
||||
pub fn mark_instruction_ids<'a>(hir: &mut HIR<'a>) -> Result<(), Diagnostic> {
|
||||
let mut id_gen = InstructionIdGenerator::new();
|
||||
let mut visited = HashSet::<(usize, usize)>::new();
|
||||
for (ii, block) in hir.blocks.values_mut().enumerate() {
|
||||
let block_id = block.id;
|
||||
for (jj, instr_ix) in block.instructions.iter_mut().enumerate() {
|
||||
invariant(visited.insert((ii, jj)), || {
|
||||
Diagnostic::invariant(BlockVisitedTwice { block: block_id }, None)
|
||||
})?;
|
||||
let instr = &mut hir.instructions[usize::from(*instr_ix)];
|
||||
instr.id = id_gen.next();
|
||||
}
|
||||
block.terminal.id = id_gen.next();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Invariant: Expected block {block} not to have been visited yet")]
|
||||
pub struct BlockVisitedTwice {
|
||||
block: BlockId,
|
||||
}
|
||||
|
||||
/// Updates the predecessors of each block
|
||||
pub fn mark_predecessors<'a>(hir: &mut HIR<'a>) {
|
||||
for block in hir.blocks.values_mut() {
|
||||
block.predecessors.clear();
|
||||
}
|
||||
let mut visited = HashSet::<BlockId>::with_capacity(hir.blocks.len());
|
||||
fn visit<'a>(
|
||||
block_id: BlockId,
|
||||
prev_id: Option<BlockId>,
|
||||
hir: &mut HIR<'a>,
|
||||
visited: &mut HashSet<BlockId>,
|
||||
) {
|
||||
let block = hir.block_mut(block_id);
|
||||
if let Some(prev_id) = prev_id {
|
||||
block.predecessors.insert(prev_id);
|
||||
}
|
||||
if !visited.insert(block_id) {
|
||||
return;
|
||||
}
|
||||
for successor in block.terminal.value.successors() {
|
||||
visit(successor, Some(block_id), hir, visited)
|
||||
}
|
||||
}
|
||||
visit(hir.entry, None, hir, &mut visited);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ mod environment;
|
||||
mod features;
|
||||
mod function;
|
||||
mod id_types;
|
||||
mod initialize;
|
||||
mod instruction;
|
||||
mod print;
|
||||
mod registry;
|
||||
@@ -14,6 +15,11 @@ pub use environment::*;
|
||||
pub use features::*;
|
||||
pub use function::*;
|
||||
pub use id_types::*;
|
||||
pub use initialize::{
|
||||
initialize_hir, mark_instruction_ids, mark_predecessors,
|
||||
remove_unreachable_do_while_statements, remove_unreachable_fallthroughs,
|
||||
remove_unreachable_for_updates, reverse_postorder_blocks,
|
||||
};
|
||||
pub use instruction::*;
|
||||
pub use print::Print;
|
||||
pub use registry::Registry;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use forget_build_hir::initialize_hir;
|
||||
use forget_estree::BinaryOperator;
|
||||
use forget_hir::{
|
||||
BlockKind, Environment, Function, GotoKind, GotoTerminal, IdentifierId, Instruction,
|
||||
InstructionValue, LoadGlobal, Operand, Primitive, PrimitiveValue, TerminalValue,
|
||||
initialize_hir, BlockKind, Environment, Function, GotoKind, GotoTerminal, IdentifierId,
|
||||
Instruction, InstructionValue, LoadGlobal, Operand, Primitive, PrimitiveValue, TerminalValue,
|
||||
};
|
||||
use forget_ssa::eliminate_redundant_phis;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user