use std::collections::HashSet;
use react_compiler_hir::environment::{Environment, OutputMode};
use react_compiler_hir::object_shape::HookKind;
use react_compiler_hir::visitors;
use react_compiler_hir::{
ArrayPatternElement, BlockId, BlockKind, HirFunction, IdentifierId,
InstructionKind, InstructionValue, ObjectPropertyOrSpread, Pattern,
};
pub fn dead_code_elimination(func: &mut HirFunction, env: &Environment) {
let state = find_referenced_identifiers(func, env);
let mut instructions_to_rewrite: Vec<react_compiler_hir::InstructionId> = Vec::new();
for (_block_id, block) in &mut func.body.blocks {
block.phis.retain(|phi| {
is_id_or_name_used(&state, &env.identifiers, phi.place.identifier)
});
block.instructions.retain(|instr_id| {
let instr = &func.instructions[instr_id.0 as usize];
is_id_or_name_used(&state, &env.identifiers, instr.lvalue.identifier)
});
let retained_count = block.instructions.len();
for i in 0..retained_count {
let is_block_value =
block.kind != BlockKind::Block && i == retained_count - 1;
if !is_block_value {
instructions_to_rewrite.push(block.instructions[i]);
}
}
}
for instr_id in instructions_to_rewrite {
rewrite_instruction(func, instr_id, &state, env);
}
func.context.retain(|ctx_var| {
is_id_or_name_used(&state, &env.identifiers, ctx_var.identifier)
});
}
struct State {
identifiers: HashSet<IdentifierId>,
named: HashSet<String>,
}
impl State {
fn new() -> Self {
State {
identifiers: HashSet::new(),
named: HashSet::new(),
}
}
fn count(&self) -> usize {
self.identifiers.len()
}
}
fn reference(
state: &mut State,
identifiers: &[react_compiler_hir::Identifier],
identifier_id: IdentifierId,
) {
state.identifiers.insert(identifier_id);
let ident = &identifiers[identifier_id.0 as usize];
if let Some(ref name) = ident.name {
state.named.insert(name.value().to_string());
}
}
fn is_id_or_name_used(
state: &State,
identifiers: &[react_compiler_hir::Identifier],
identifier_id: IdentifierId,
) -> bool {
if state.identifiers.contains(&identifier_id) {
return true;
}
let ident = &identifiers[identifier_id.0 as usize];
if let Some(ref name) = ident.name {
state.named.contains(name.value())
} else {
false
}
}
fn is_id_used(state: &State, identifier_id: IdentifierId) -> bool {
state.identifiers.contains(&identifier_id)
}
fn find_referenced_identifiers(func: &HirFunction, env: &Environment) -> State {
let has_loop = has_back_edge(func);
let reversed_block_ids: Vec<BlockId> = func.body.blocks.keys().rev().copied().collect();
let mut state = State::new();
let mut size;
loop {
size = state.count();
for &block_id in &reversed_block_ids {
let block = &func.body.blocks[&block_id];
for place in visitors::each_terminal_operand(&block.terminal) {
reference(&mut state, &env.identifiers, place.identifier);
}
let instr_count = block.instructions.len();
for i in (0..instr_count).rev() {
let instr_id = block.instructions[i];
let instr = &func.instructions[instr_id.0 as usize];
let is_block_value =
block.kind != BlockKind::Block && i == instr_count - 1;
if is_block_value {
reference(&mut state, &env.identifiers, instr.lvalue.identifier);
for place in visitors::each_instruction_value_operand(&instr.value, env) {
reference(&mut state, &env.identifiers, place.identifier);
}
} else if is_id_or_name_used(&state, &env.identifiers, instr.lvalue.identifier)
|| !pruneable_value(&instr.value, &state, env)
{
reference(&mut state, &env.identifiers, instr.lvalue.identifier);
if let InstructionValue::StoreLocal { lvalue, value, .. } = &instr.value {
if lvalue.kind == InstructionKind::Reassign
|| is_id_used(&state, lvalue.place.identifier)
{
reference(&mut state, &env.identifiers, value.identifier);
}
} else {
for place in visitors::each_instruction_value_operand(&instr.value, env) {
reference(&mut state, &env.identifiers, place.identifier);
}
}
}
}
for phi in &block.phis {
if is_id_or_name_used(&state, &env.identifiers, phi.place.identifier) {
for (_pred, operand) in &phi.operands {
reference(&mut state, &env.identifiers, operand.identifier);
}
}
}
}
if !(state.count() > size && has_loop) {
break;
}
}
state
}
fn rewrite_instruction(
func: &mut HirFunction,
instr_id: react_compiler_hir::InstructionId,
state: &State,
env: &Environment,
) {
let instr = &mut func.instructions[instr_id.0 as usize];
match &mut instr.value {
InstructionValue::Destructure { lvalue, .. } => {
match &mut lvalue.pattern {
Pattern::Array(arr) => {
let mut last_entry_index = 0;
for i in 0..arr.items.len() {
match &arr.items[i] {
ArrayPatternElement::Place(p) => {
if !is_id_or_name_used(state, &env.identifiers, p.identifier) {
arr.items[i] = ArrayPatternElement::Hole;
} else {
last_entry_index = i;
}
}
ArrayPatternElement::Spread(s) => {
if !is_id_or_name_used(state, &env.identifiers, s.place.identifier) {
arr.items[i] = ArrayPatternElement::Hole;
} else {
last_entry_index = i;
}
}
ArrayPatternElement::Hole => {}
}
}
arr.items.truncate(last_entry_index + 1);
}
Pattern::Object(obj) => {
let mut next_properties: Option<Vec<ObjectPropertyOrSpread>> = None;
for prop in &obj.properties {
match prop {
ObjectPropertyOrSpread::Property(p) => {
if is_id_or_name_used(state, &env.identifiers, p.place.identifier) {
next_properties
.get_or_insert_with(Vec::new)
.push(prop.clone());
}
}
ObjectPropertyOrSpread::Spread(s) => {
if is_id_or_name_used(state, &env.identifiers, s.place.identifier) {
next_properties = None;
break;
}
}
}
}
if let Some(props) = next_properties {
obj.properties = props;
}
}
}
}
InstructionValue::StoreLocal {
lvalue,
type_annotation,
loc,
..
} => {
if lvalue.kind != InstructionKind::Reassign
&& !is_id_used(state, lvalue.place.identifier)
{
let new_lvalue = lvalue.clone();
let new_type_annotation = type_annotation.clone();
let new_loc = *loc;
instr.value = InstructionValue::DeclareLocal {
lvalue: new_lvalue,
type_annotation: new_type_annotation,
loc: new_loc,
};
}
}
_ => {}
}
}
fn pruneable_value(
value: &InstructionValue,
state: &State,
env: &Environment,
) -> bool {
match value {
InstructionValue::DeclareLocal { lvalue, .. } => {
!is_id_or_name_used(state, &env.identifiers, lvalue.place.identifier)
}
InstructionValue::StoreLocal { lvalue, .. } => {
if lvalue.kind == InstructionKind::Reassign {
!is_id_used(state, lvalue.place.identifier)
} else {
!is_id_or_name_used(state, &env.identifiers, lvalue.place.identifier)
}
}
InstructionValue::Destructure { lvalue, .. } => {
let mut is_id_or_name_used_flag = false;
let mut is_id_used_flag = false;
for place in visitors::each_pattern_operand(&lvalue.pattern) {
if is_id_used(state, place.identifier) {
is_id_or_name_used_flag = true;
is_id_used_flag = true;
} else if is_id_or_name_used(state, &env.identifiers, place.identifier) {
is_id_or_name_used_flag = true;
}
}
if lvalue.kind == InstructionKind::Reassign {
!is_id_used_flag
} else {
!is_id_or_name_used_flag
}
}
InstructionValue::PostfixUpdate { lvalue, .. }
| InstructionValue::PrefixUpdate { lvalue, .. } => {
!is_id_used(state, lvalue.identifier)
}
InstructionValue::Debugger { .. } => {
false
}
InstructionValue::CallExpression { callee, .. } => {
if env.output_mode == OutputMode::Ssr {
let callee_ty =
&env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize];
if let Some(hook_kind) = env.get_hook_kind_for_type(callee_ty).ok().flatten() {
match hook_kind {
HookKind::UseState | HookKind::UseReducer | HookKind::UseRef => {
return true;
}
_ => {}
}
}
}
false
}
InstructionValue::MethodCall { property, .. } => {
if env.output_mode == OutputMode::Ssr {
let callee_ty =
&env.types[env.identifiers[property.identifier.0 as usize].type_.0 as usize];
if let Some(hook_kind) = env.get_hook_kind_for_type(callee_ty).ok().flatten() {
match hook_kind {
HookKind::UseState | HookKind::UseReducer | HookKind::UseRef => {
return true;
}
_ => {}
}
}
}
false
}
InstructionValue::Await { .. }
| InstructionValue::ComputedDelete { .. }
| InstructionValue::ComputedStore { .. }
| InstructionValue::PropertyDelete { .. }
| InstructionValue::PropertyStore { .. }
| InstructionValue::StoreGlobal { .. } => {
false
}
InstructionValue::NewExpression { .. }
| InstructionValue::UnsupportedNode { .. }
| InstructionValue::TaggedTemplateExpression { .. } => {
false
}
InstructionValue::GetIterator { .. }
| InstructionValue::NextPropertyOf { .. }
| InstructionValue::IteratorNext { .. } => {
false
}
InstructionValue::LoadContext { .. }
| InstructionValue::DeclareContext { .. }
| InstructionValue::StoreContext { .. } => false,
InstructionValue::StartMemoize { .. } | InstructionValue::FinishMemoize { .. } => false,
InstructionValue::RegExpLiteral { .. }
| InstructionValue::MetaProperty { .. }
| InstructionValue::LoadGlobal { .. }
| InstructionValue::ArrayExpression { .. }
| InstructionValue::BinaryExpression { .. }
| InstructionValue::ComputedLoad { .. }
| InstructionValue::ObjectMethod { .. }
| InstructionValue::FunctionExpression { .. }
| InstructionValue::LoadLocal { .. }
| InstructionValue::JsxExpression { .. }
| InstructionValue::JsxFragment { .. }
| InstructionValue::JSXText { .. }
| InstructionValue::ObjectExpression { .. }
| InstructionValue::Primitive { .. }
| InstructionValue::PropertyLoad { .. }
| InstructionValue::TemplateLiteral { .. }
| InstructionValue::TypeCastExpression { .. }
| InstructionValue::UnaryExpression { .. } => {
true
}
}
}
fn has_back_edge(func: &HirFunction) -> bool {
let mut visited: HashSet<BlockId> = HashSet::new();
for (block_id, block) in &func.body.blocks {
for pred_id in &block.preds {
if !visited.contains(pred_id) {
return true;
}
}
visited.insert(*block_id);
}
false
}