[rust] Extract HIR initialization passes to hir crate

This commit is contained in:
Joe Savona
2023-07-14 15:46:51 +09:00
parent 19b0a49967
commit 0a79974d0e
10 changed files with 1287 additions and 1068 deletions
+2
View File
@@ -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;