use indexmap::IndexMap;
use indexmap::IndexSet;
use react_compiler_ast::scope::BindingId;
use react_compiler_ast::scope::ImportBindingKind;
use react_compiler_ast::scope::ScopeId;
use react_compiler_ast::scope::ScopeInfo;
use react_compiler_diagnostics::CompilerDiagnostic;
use react_compiler_diagnostics::CompilerDiagnosticDetail;
use react_compiler_diagnostics::CompilerError;
use react_compiler_diagnostics::CompilerErrorDetail;
use react_compiler_diagnostics::ErrorCategory;
use react_compiler_hir::environment::Environment;
use react_compiler_hir::visitors::each_terminal_successor;
use react_compiler_hir::visitors::terminal_fallthrough;
use react_compiler_hir::*;
use crate::identifier_loc_index::IdentifierLocIndex;
pub(crate) fn is_always_reserved_word(s: &str) -> bool {
matches!(
s,
"break"
| "case"
| "catch"
| "continue"
| "debugger"
| "default"
| "do"
| "else"
| "finally"
| "for"
| "function"
| "if"
| "in"
| "instanceof"
| "new"
| "return"
| "switch"
| "this"
| "throw"
| "try"
| "typeof"
| "var"
| "void"
| "while"
| "with"
| "class"
| "const"
| "enum"
| "export"
| "extends"
| "import"
| "super"
| "null"
| "true"
| "false"
| "delete"
)
}
pub(crate) fn reserved_identifier_diagnostic(name: &str) -> CompilerDiagnostic {
CompilerDiagnostic::new(
ErrorCategory::Syntax,
"Expected a non-reserved identifier name",
Some(format!(
"`{}` is a reserved word in JavaScript and cannot be used as an identifier name",
name
)),
)
.with_detail(CompilerDiagnosticDetail::Error {
loc: None, message: Some("reserved word".to_string()),
identifier_name: None,
})
}
enum Scope {
Loop {
label: Option<String>,
continue_block: BlockId,
break_block: BlockId,
},
Label {
label: String,
break_block: BlockId,
},
Switch {
label: Option<String>,
break_block: BlockId,
},
}
impl Scope {
fn label(&self) -> Option<&str> {
match self {
Scope::Loop { label, .. } => label.as_deref(),
Scope::Label { label, .. } => Some(label.as_str()),
Scope::Switch { label, .. } => label.as_deref(),
}
}
fn break_block(&self) -> BlockId {
match self {
Scope::Loop { break_block, .. } => *break_block,
Scope::Label { break_block, .. } => *break_block,
Scope::Switch { break_block, .. } => *break_block,
}
}
}
pub struct WipBlock {
pub id: BlockId,
pub instructions: Vec<InstructionId>,
pub kind: BlockKind,
}
fn new_block(id: BlockId, kind: BlockKind) -> WipBlock {
WipBlock {
id,
kind,
instructions: Vec::new(),
}
}
pub struct HirBuilder<'a> {
completed: IndexMap<BlockId, BasicBlock>,
current: WipBlock,
entry: BlockId,
scopes: Vec<Scope>,
context: IndexMap<BindingId, Option<SourceLocation>>,
bindings: IndexMap<BindingId, IdentifierId>,
used_names: IndexMap<String, BindingId>,
env: &'a mut Environment,
scope_info: &'a ScopeInfo,
exception_handler_stack: Vec<BlockId>,
instruction_table: Vec<Instruction>,
pub fbt_depth: u32,
function_scope: ScopeId,
component_scope: ScopeId,
context_identifiers: std::collections::HashSet<BindingId>,
claimed_synthetic_scopes: std::collections::HashSet<ScopeId>,
identifier_locs: &'a IdentifierLocIndex,
}
impl<'a> HirBuilder<'a> {
pub fn new(
env: &'a mut Environment,
scope_info: &'a ScopeInfo,
function_scope: ScopeId,
component_scope: ScopeId,
context_identifiers: std::collections::HashSet<BindingId>,
bindings: Option<IndexMap<BindingId, IdentifierId>>,
context: Option<IndexMap<BindingId, Option<SourceLocation>>>,
entry_block_kind: Option<BlockKind>,
used_names: Option<IndexMap<String, BindingId>>,
identifier_locs: &'a IdentifierLocIndex,
) -> Self {
let entry = env.next_block_id();
let kind = entry_block_kind.unwrap_or(BlockKind::Block);
HirBuilder {
completed: IndexMap::new(),
current: new_block(entry, kind),
entry,
scopes: Vec::new(),
context: context.unwrap_or_default(),
bindings: bindings.unwrap_or_default(),
used_names: used_names.unwrap_or_default(),
env,
scope_info,
exception_handler_stack: Vec::new(),
instruction_table: Vec::new(),
fbt_depth: 0,
function_scope,
component_scope,
context_identifiers,
claimed_synthetic_scopes: std::collections::HashSet::new(),
identifier_locs,
}
}
fn is_scope_within_compiled_function(&self, scope_id: ScopeId) -> bool {
let mut current = Some(scope_id);
while let Some(id) = current {
if id == self.component_scope {
return true;
}
current = self.scope_info.scopes[id.0 as usize].parent;
}
false
}
pub fn environment(&self) -> &Environment {
self.env
}
pub fn environment_mut(&mut self) -> &mut Environment {
self.env
}
pub fn make_type(&mut self) -> Type {
let type_id = self.env.make_type();
Type::TypeVar { id: type_id }
}
pub fn scope_info(&self) -> &ScopeInfo {
self.scope_info
}
pub fn get_identifier_loc(&self, node_id: u32) -> Option<SourceLocation> {
self.identifier_locs
.get(&node_id)
.map(|entry| entry.loc.clone())
}
pub fn is_jsx_identifier_at_pos(&self, offset: u32) -> bool {
self.identifier_locs
.values()
.any(|entry| entry.start == offset && entry.is_jsx)
}
pub fn function_scope(&self) -> ScopeId {
self.function_scope
}
pub fn component_scope(&self) -> ScopeId {
self.component_scope
}
pub fn context(&self) -> &IndexMap<BindingId, Option<SourceLocation>> {
&self.context
}
pub fn context_identifiers(&self) -> &std::collections::HashSet<BindingId> {
&self.context_identifiers
}
pub fn add_context_identifier(&mut self, binding_id: BindingId) {
self.context_identifiers.insert(binding_id);
}
pub fn claim_synthetic_scope(&mut self, scope_id: ScopeId) {
self.claimed_synthetic_scopes.insert(scope_id);
}
pub fn is_synthetic_scope_claimed(&self, scope_id: ScopeId) -> bool {
self.claimed_synthetic_scopes.contains(&scope_id)
}
pub fn scope_info_and_env_mut(&mut self) -> (&ScopeInfo, &mut Environment) {
(self.scope_info, self.env)
}
pub fn identifier_locs(&self) -> &'a IdentifierLocIndex {
self.identifier_locs
}
pub fn bindings(&self) -> &IndexMap<BindingId, IdentifierId> {
&self.bindings
}
pub fn used_names(&self) -> &IndexMap<String, BindingId> {
&self.used_names
}
pub fn merge_used_names(&mut self, child_used_names: IndexMap<String, BindingId>) {
for (name, binding_id) in child_used_names {
self.used_names.entry(name).or_insert(binding_id);
}
}
pub fn merge_bindings(&mut self, child_bindings: IndexMap<BindingId, IdentifierId>) {
for (binding_id, identifier_id) in child_bindings {
self.bindings.entry(binding_id).or_insert(identifier_id);
}
}
pub fn push(&mut self, instruction: Instruction) {
let loc = instruction.loc.clone();
let instr_id = InstructionId(self.instruction_table.len() as u32);
self.instruction_table.push(instruction);
self.current.instructions.push(instr_id);
if let Some(&handler) = self.exception_handler_stack.last() {
let continuation = self.reserve(self.current_block_kind());
self.terminate_with_continuation(
Terminal::MaybeThrow {
continuation: continuation.id,
handler: Some(handler),
id: EvaluationOrder(0),
loc,
effects: None,
},
continuation,
);
}
}
pub fn terminate(&mut self, terminal: Terminal, next_block_kind: Option<BlockKind>) -> BlockId {
let wip = std::mem::replace(
&mut self.current,
new_block(BlockId(u32::MAX), BlockKind::Block),
);
let block_id = wip.id;
self.completed.insert(
block_id,
BasicBlock {
kind: wip.kind,
id: block_id,
instructions: wip.instructions,
terminal,
preds: IndexSet::new(),
phis: Vec::new(),
},
);
if let Some(kind) = next_block_kind {
let next_id = self.env.next_block_id();
self.current = new_block(next_id, kind);
}
block_id
}
pub fn terminate_with_continuation(&mut self, terminal: Terminal, continuation: WipBlock) {
let wip = std::mem::replace(&mut self.current, continuation);
let block_id = wip.id;
self.completed.insert(
block_id,
BasicBlock {
kind: wip.kind,
id: block_id,
instructions: wip.instructions,
terminal,
preds: IndexSet::new(),
phis: Vec::new(),
},
);
}
pub fn reserve(&mut self, kind: BlockKind) -> WipBlock {
let id = self.env.next_block_id();
new_block(id, kind)
}
pub fn complete(&mut self, block: WipBlock, terminal: Terminal) {
let block_id = block.id;
self.completed.insert(
block_id,
BasicBlock {
kind: block.kind,
id: block_id,
instructions: block.instructions,
terminal,
preds: IndexSet::new(),
phis: Vec::new(),
},
);
}
pub fn enter_reserved(&mut self, wip: WipBlock, f: impl FnOnce(&mut Self) -> Terminal) {
let prev = std::mem::replace(&mut self.current, wip);
let terminal = f(self);
let completed_wip = std::mem::replace(&mut self.current, prev);
self.completed.insert(
completed_wip.id,
BasicBlock {
kind: completed_wip.kind,
id: completed_wip.id,
instructions: completed_wip.instructions,
terminal,
preds: IndexSet::new(),
phis: Vec::new(),
},
);
}
pub fn try_enter_reserved(
&mut self,
wip: WipBlock,
f: impl FnOnce(&mut Self) -> Result<Terminal, CompilerDiagnostic>,
) -> Result<(), CompilerDiagnostic> {
let prev = std::mem::replace(&mut self.current, wip);
let terminal = f(self)?;
let completed_wip = std::mem::replace(&mut self.current, prev);
self.completed.insert(
completed_wip.id,
BasicBlock {
kind: completed_wip.kind,
id: completed_wip.id,
instructions: completed_wip.instructions,
terminal,
preds: IndexSet::new(),
phis: Vec::new(),
},
);
Ok(())
}
pub fn enter(
&mut self,
kind: BlockKind,
f: impl FnOnce(&mut Self, BlockId) -> Terminal,
) -> BlockId {
let wip = self.reserve(kind);
let wip_id = wip.id;
self.enter_reserved(wip, |this| f(this, wip_id));
wip_id
}
pub fn try_enter(
&mut self,
kind: BlockKind,
f: impl FnOnce(&mut Self, BlockId) -> Result<Terminal, CompilerDiagnostic>,
) -> Result<BlockId, CompilerDiagnostic> {
let wip = self.reserve(kind);
let wip_id = wip.id;
self.try_enter_reserved(wip, |this| f(this, wip_id))?;
Ok(wip_id)
}
pub fn enter_try_catch(&mut self, handler: BlockId, f: impl FnOnce(&mut Self)) {
self.exception_handler_stack.push(handler);
f(self);
self.exception_handler_stack.pop();
}
pub fn try_enter_try_catch(
&mut self,
handler: BlockId,
f: impl FnOnce(&mut Self) -> Result<(), CompilerDiagnostic>,
) -> Result<(), CompilerDiagnostic> {
self.exception_handler_stack.push(handler);
let result = f(self);
self.exception_handler_stack.pop();
result
}
pub fn resolve_throw_handler(&self) -> Option<BlockId> {
self.exception_handler_stack.last().copied()
}
pub fn loop_scope<T>(
&mut self,
label: Option<String>,
continue_block: BlockId,
break_block: BlockId,
f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>,
) -> Result<T, CompilerDiagnostic> {
self.scopes.push(Scope::Loop {
label: label.clone(),
continue_block,
break_block,
});
let value = f(self)?;
let last = self
.scopes
.pop()
.expect("Mismatched loop scope: stack empty");
match &last {
Scope::Loop {
label: l,
continue_block: c,
break_block: b,
} => {
assert!(
*l == label && *c == continue_block && *b == break_block,
"Mismatched loop scope"
);
}
_ => {
return Err(CompilerDiagnostic::new(
ErrorCategory::Invariant,
"Mismatched loop scope: expected Loop, got other",
None,
));
}
}
Ok(value)
}
pub fn label_scope<T>(
&mut self,
label: String,
break_block: BlockId,
f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>,
) -> Result<T, CompilerDiagnostic> {
self.scopes.push(Scope::Label {
label: label.clone(),
break_block,
});
let value = f(self)?;
let last = self
.scopes
.pop()
.expect("Mismatched label scope: stack empty");
match &last {
Scope::Label {
label: l,
break_block: b,
} => {
assert!(*l == label && *b == break_block, "Mismatched label scope");
}
_ => {
return Err(CompilerDiagnostic::new(
ErrorCategory::Invariant,
"Mismatched label scope: expected Label, got other",
None,
));
}
}
Ok(value)
}
pub fn switch_scope<T>(
&mut self,
label: Option<String>,
break_block: BlockId,
f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>,
) -> Result<T, CompilerDiagnostic> {
self.scopes.push(Scope::Switch {
label: label.clone(),
break_block,
});
let value = f(self)?;
let last = self
.scopes
.pop()
.expect("Mismatched switch scope: stack empty");
match &last {
Scope::Switch {
label: l,
break_block: b,
} => {
assert!(*l == label && *b == break_block, "Mismatched switch scope");
}
_ => {
return Err(CompilerDiagnostic::new(
ErrorCategory::Invariant,
"Mismatched switch scope: expected Switch, got other",
None,
));
}
}
Ok(value)
}
pub fn lookup_break(&self, label: Option<&str>) -> Result<BlockId, CompilerDiagnostic> {
for scope in self.scopes.iter().rev() {
match scope {
Scope::Loop { .. } | Scope::Switch { .. } if label.is_none() => {
return Ok(scope.break_block());
}
_ if label.is_some() && scope.label() == label => {
return Ok(scope.break_block());
}
_ => continue,
}
}
Err(CompilerDiagnostic::new(
ErrorCategory::Invariant,
"Expected a loop or switch to be in scope for break",
None,
))
}
pub fn lookup_continue(&self, label: Option<&str>) -> Result<BlockId, CompilerDiagnostic> {
for scope in self.scopes.iter().rev() {
match scope {
Scope::Loop {
label: scope_label,
continue_block,
..
} => {
if label.is_none() || label == scope_label.as_deref() {
return Ok(*continue_block);
}
}
_ => {
if label.is_some() && scope.label() == label {
return Err(CompilerDiagnostic::new(
ErrorCategory::Invariant,
"Continue may only refer to a labeled loop",
None,
));
}
}
}
}
Err(CompilerDiagnostic::new(
ErrorCategory::Invariant,
"Expected a loop to be in scope for continue",
None,
))
}
pub fn make_temporary(&mut self, loc: Option<SourceLocation>) -> IdentifierId {
let id = self.env.next_identifier_id();
self.env.identifiers[id.0 as usize].loc = loc;
id
}
pub fn set_identifier_loc(&mut self, id: IdentifierId, loc: Option<SourceLocation>) {
self.env.identifiers[id.0 as usize].loc = loc;
}
pub fn record_error(&mut self, error: CompilerErrorDetail) -> Result<(), CompilerError> {
self.env.record_error(error)
}
pub fn record_diagnostic(&mut self, diagnostic: CompilerDiagnostic) {
self.env.record_diagnostic(diagnostic);
}
pub fn has_local_binding(&self, name: &str) -> bool {
if let Some(binding) = self
.scope_info
.find_binding_in_descendants(name, self.component_scope)
{
return binding.scope != self.scope_info.program_scope;
}
false
}
pub fn current_block_kind(&self) -> BlockKind {
self.current.kind
}
pub fn build(
mut self,
) -> Result<
(
HIR,
Vec<Instruction>,
IndexMap<String, BindingId>,
IndexMap<BindingId, IdentifierId>,
),
CompilerError,
> {
let mut hir = HIR {
blocks: std::mem::take(&mut self.completed),
entry: self.entry,
};
let mut instructions = std::mem::take(&mut self.instruction_table);
let rpo_blocks = get_reverse_postordered_blocks(&hir, &instructions);
for (id, block) in &hir.blocks {
if !rpo_blocks.contains_key(id) {
let has_function_expr = block.instructions.iter().any(|&instr_id| {
matches!(
instructions[instr_id.0 as usize].value,
InstructionValue::FunctionExpression { .. }
)
});
if has_function_expr {
let loc = block
.instructions
.first()
.and_then(|&i| instructions[i.0 as usize].loc.clone())
.or_else(|| block.terminal.loc().copied());
self.env.record_error(CompilerErrorDetail {
category: ErrorCategory::Todo,
reason: "Support functions with unreachable code that may contain hoisted declarations".to_string(),
description: None,
loc,
suggestions: None,
})?;
}
}
}
hir.blocks = rpo_blocks;
remove_unreachable_for_updates(&mut hir);
remove_dead_do_while_statements(&mut hir);
remove_unnecessary_try_catch(&mut hir);
mark_instruction_ids(&mut hir, &mut instructions);
mark_predecessors(&mut hir);
let used_names = self.used_names;
let bindings = self.bindings;
Ok((hir, instructions, used_names, bindings))
}
pub fn resolve_binding(
&mut self,
name: &str,
binding_id: BindingId,
) -> Result<IdentifierId, CompilerError> {
self.resolve_binding_with_loc(name, binding_id, None)
}
pub fn resolve_binding_with_loc(
&mut self,
name: &str,
binding_id: BindingId,
loc: Option<SourceLocation>,
) -> Result<IdentifierId, CompilerError> {
if name == "fbt" {
let should_record_fbt_error =
if let Some(&identifier_id) = self.bindings.get(&binding_id) {
match &self.env.identifiers[identifier_id.0 as usize].name {
Some(IdentifierName::Named(resolved_name)) => resolved_name == "fbt",
_ => false,
}
} else {
true
};
if should_record_fbt_error {
let error_loc = self.scope_info.bindings[binding_id.0 as usize]
.declaration_node_id
.and_then(|nid| self.get_identifier_loc(nid))
.or_else(|| loc.clone());
self.env.record_error(CompilerErrorDetail {
category: ErrorCategory::Todo,
reason: "Support local variables named `fbt`".to_string(),
description: Some(
"Local variables named `fbt` may conflict with the fbt plugin and are not yet supported".to_string(),
),
loc: error_loc,
suggestions: None,
})?;
}
}
if let Some(&identifier_id) = self.bindings.get(&binding_id) {
return Ok(identifier_id);
}
if is_always_reserved_word(name) {
return Err(CompilerError::from(reserved_identifier_diagnostic(name)));
}
let mut candidate = name.to_string();
let mut index = 0u32;
loop {
if let Some(&existing_binding_id) = self.used_names.get(&candidate) {
if existing_binding_id == binding_id {
break;
}
candidate = format!("{}_{}", name, index);
index += 1;
} else {
break;
}
}
if candidate != name {
let binding = &self.scope_info.bindings[binding_id.0 as usize];
if let Some(decl_start) = binding.declaration_start {
self.env
.renames
.push(react_compiler_hir::environment::BindingRename {
original: name.to_string(),
renamed: candidate.clone(),
declaration_start: decl_start,
});
}
}
let id = self.env.next_identifier_id();
self.env.identifiers[id.0 as usize].name = Some(IdentifierName::Named(candidate.clone()));
let binding = &self.scope_info.bindings[binding_id.0 as usize];
let decl_loc = binding
.declaration_node_id
.and_then(|nid| self.get_identifier_loc(nid));
if let Some(ref dl) = decl_loc {
self.env.identifiers[id.0 as usize].loc = Some(dl.clone());
} else if let Some(ref loc) = loc {
self.env.identifiers[id.0 as usize].loc = Some(loc.clone());
}
self.used_names.insert(candidate, binding_id);
self.bindings.insert(binding_id, id);
Ok(id)
}
pub fn set_identifier_declaration_loc(
&mut self,
id: IdentifierId,
loc: &Option<SourceLocation>,
) {
if let Some(loc_val) = loc {
self.env.identifiers[id.0 as usize].loc = Some(loc_val.clone());
}
}
pub fn resolve_identifier(
&mut self,
name: &str,
_start_offset: u32,
loc: Option<SourceLocation>,
node_id: Option<u32>,
) -> Result<VariableBinding, CompilerError> {
let binding_data = self.scope_info.resolve_reference_for_node(node_id);
match binding_data {
None => {
Ok(VariableBinding::Global {
name: name.to_string(),
})
}
Some(binding) => {
if matches!(
binding.declaration_type.as_str(),
"TSTypeAliasDeclaration"
| "TSInterfaceDeclaration"
| "TSEnumDeclaration"
| "TSModuleDeclaration"
) {
return Ok(VariableBinding::Global {
name: name.to_string(),
});
}
if binding.scope == self.scope_info.program_scope {
Ok(match &binding.import {
Some(import_info) => match import_info.kind {
ImportBindingKind::Default => VariableBinding::ImportDefault {
name: name.to_string(),
module: import_info.source.clone(),
},
ImportBindingKind::Named => VariableBinding::ImportSpecifier {
name: name.to_string(),
module: import_info.source.clone(),
imported: import_info
.imported
.clone()
.unwrap_or_else(|| name.to_string()),
},
ImportBindingKind::Namespace => VariableBinding::ImportNamespace {
name: name.to_string(),
module: import_info.source.clone(),
},
},
None => VariableBinding::ModuleLocal {
name: name.to_string(),
},
})
} else if !self.is_scope_within_compiled_function(binding.scope) {
Ok(VariableBinding::ModuleLocal {
name: name.to_string(),
})
} else {
let binding_id = binding.id;
let binding_kind = crate::convert_binding_kind(&binding.kind);
let identifier_id = self.resolve_binding_with_loc(name, binding_id, loc)?;
Ok(VariableBinding::Identifier {
identifier: identifier_id,
binding_kind,
})
}
}
}
}
pub fn is_context_identifier(
&self,
_name: &str,
_start_offset: u32,
node_id: Option<u32>,
) -> bool {
let binding = self.scope_info.resolve_reference_for_node(node_id);
match binding {
None => false,
Some(binding_data) => {
if binding_data.scope == self.scope_info.program_scope {
return false;
}
self.context_identifiers.contains(&binding_data.id)
}
}
}
pub fn is_context_binding(&self, binding_id: BindingId) -> bool {
let binding = &self.scope_info.bindings[binding_id.0 as usize];
if binding.scope == self.scope_info.program_scope {
return false;
}
self.context_identifiers.contains(&binding_id)
}
pub fn get_function_declaration_binding(
&self,
function_scope: ScopeId,
name: &str,
) -> Option<BindingId> {
let resolved_name_matches = |bid: BindingId| -> Option<bool> {
let &identifier_id = self.bindings.get(&bid)?;
match &self.env.identifiers[identifier_id.0 as usize].name {
Some(IdentifierName::Named(n)) => Some(n == name),
_ => Some(false),
}
};
let mut current = Some(function_scope);
while let Some(id) = current {
let scope = &self.scope_info.scopes[id.0 as usize];
let mut found = scope
.bindings
.values()
.copied()
.find(|&bid| resolved_name_matches(bid) == Some(true));
if found.is_none() {
if let Some(&bid) = scope.bindings.get(name) {
if resolved_name_matches(bid) != Some(false) {
found = Some(bid);
}
}
}
if let Some(bid) = found {
let binding_scope = self.scope_info.bindings[bid.0 as usize].scope;
if !self.is_scope_within_compiled_function(binding_scope) {
return None;
}
return Some(bid);
}
current = scope.parent;
}
None
}
}
pub fn get_reverse_postordered_blocks(
hir: &HIR,
_instructions: &[Instruction],
) -> IndexMap<BlockId, BasicBlock> {
let mut visited: IndexSet<BlockId> = IndexSet::new();
let mut used: IndexSet<BlockId> = IndexSet::new();
let mut used_fallthroughs: IndexSet<BlockId> = IndexSet::new();
let mut postorder: Vec<BlockId> = Vec::new();
fn visit(
hir: &HIR,
block_id: BlockId,
is_used: bool,
visited: &mut IndexSet<BlockId>,
used: &mut IndexSet<BlockId>,
used_fallthroughs: &mut IndexSet<BlockId>,
postorder: &mut Vec<BlockId>,
) {
let was_used = used.contains(&block_id);
let was_visited = visited.contains(&block_id);
visited.insert(block_id);
if is_used {
used.insert(block_id);
}
if was_visited && (was_used || !is_used) {
return;
}
let block = hir
.blocks
.get(&block_id)
.unwrap_or_else(|| panic!("[HIRBuilder] expected block {:?} to exist", block_id));
let mut successors = each_terminal_successor(&block.terminal);
successors.reverse();
let fallthrough = terminal_fallthrough(&block.terminal);
if let Some(ft) = fallthrough {
if is_used {
used_fallthroughs.insert(ft);
}
visit(hir, ft, false, visited, used, used_fallthroughs, postorder);
}
for successor in successors {
visit(
hir,
successor,
is_used,
visited,
used,
used_fallthroughs,
postorder,
);
}
if !was_visited {
postorder.push(block_id);
}
}
visit(
hir,
hir.entry,
true,
&mut visited,
&mut used,
&mut used_fallthroughs,
&mut postorder,
);
let mut blocks = IndexMap::new();
for block_id in postorder.into_iter().rev() {
let block = hir.blocks.get(&block_id).unwrap();
if used.contains(&block_id) {
blocks.insert(block_id, block.clone());
} else if used_fallthroughs.contains(&block_id) {
blocks.insert(
block_id,
BasicBlock {
kind: block.kind,
id: block_id,
instructions: Vec::new(),
terminal: Terminal::Unreachable {
id: block.terminal.evaluation_order(),
loc: block.terminal.loc().copied(),
},
preds: block.preds.clone(),
phis: Vec::new(),
},
);
}
}
blocks
}
pub fn remove_unreachable_for_updates(hir: &mut HIR) {
let block_ids: IndexSet<BlockId> = hir.blocks.keys().copied().collect();
for block in hir.blocks.values_mut() {
if let Terminal::For { update, .. } = &mut block.terminal {
if let Some(update_id) = *update {
if !block_ids.contains(&update_id) {
*update = None;
}
}
}
}
}
pub fn remove_dead_do_while_statements(hir: &mut HIR) {
let block_ids: IndexSet<BlockId> = hir.blocks.keys().copied().collect();
for block in hir.blocks.values_mut() {
let should_replace = if let Terminal::DoWhile { test, .. } = &block.terminal {
!block_ids.contains(test)
} else {
false
};
if should_replace {
if let Terminal::DoWhile {
loop_block,
id,
loc,
..
} = std::mem::replace(
&mut block.terminal,
Terminal::Unreachable {
id: EvaluationOrder(0),
loc: None,
},
) {
block.terminal = Terminal::Goto {
block: loop_block,
variant: GotoVariant::Break,
id,
loc,
};
}
}
}
}
pub fn remove_unnecessary_try_catch(hir: &mut HIR) {
let block_ids: IndexSet<BlockId> = hir.blocks.keys().copied().collect();
let replacements: Vec<(BlockId, BlockId, BlockId, BlockId, Option<SourceLocation>)> = hir
.blocks
.iter()
.filter_map(|(&block_id, block)| {
if let Terminal::Try {
block: try_block,
handler,
fallthrough,
loc,
..
} = &block.terminal
{
if !block_ids.contains(handler) {
return Some((block_id, *try_block, *handler, *fallthrough, loc.clone()));
}
}
None
})
.collect();
for (block_id, try_block, handler_id, fallthrough_id, loc) in replacements {
if let Some(block) = hir.blocks.get_mut(&block_id) {
block.terminal = Terminal::Goto {
block: try_block,
id: EvaluationOrder(0),
loc,
variant: GotoVariant::Break,
};
}
if let Some(fallthrough) = hir.blocks.get_mut(&fallthrough_id) {
if fallthrough.preds.len() == 1 && fallthrough.preds.contains(&handler_id) {
hir.blocks.shift_remove(&fallthrough_id);
} else {
fallthrough.preds.shift_remove(&handler_id);
}
}
}
}
pub fn mark_instruction_ids(hir: &mut HIR, instructions: &mut [Instruction]) {
let mut order: u32 = 0;
for block in hir.blocks.values_mut() {
for &instr_id in &block.instructions {
order += 1;
instructions[instr_id.0 as usize].id = EvaluationOrder(order);
}
order += 1;
block.terminal.set_evaluation_order(EvaluationOrder(order));
}
}
pub fn mark_predecessors(hir: &mut HIR) {
for block in hir.blocks.values_mut() {
block.preds.clear();
}
let mut visited: IndexSet<BlockId> = IndexSet::new();
fn visit(
hir: &mut HIR,
block_id: BlockId,
prev_block_id: Option<BlockId>,
visited: &mut IndexSet<BlockId>,
) {
if let Some(prev_id) = prev_block_id {
if let Some(block) = hir.blocks.get_mut(&block_id) {
block.preds.insert(prev_id);
} else {
return;
}
}
if visited.contains(&block_id) {
return;
}
visited.insert(block_id);
let successors = if let Some(block) = hir.blocks.get(&block_id) {
each_terminal_successor(&block.terminal)
} else {
return;
};
for successor in successors {
visit(hir, successor, Some(block_id), visited);
}
}
visit(hir, hir.entry, None, &mut visited);
}
pub fn create_temporary_place(env: &mut Environment, loc: Option<SourceLocation>) -> Place {
let id = env.next_identifier_id();
env.identifiers[id.0 as usize].loc = loc;
Place {
identifier: id,
reactive: false,
effect: Effect::Unknown,
loc: None,
}
}