import {
ArrayPattern,
BlockId,
HIRFunction,
Identifier,
IdentifierId,
Instruction,
InstructionKind,
InstructionValue,
ObjectPattern,
} from '../HIR';
import {
eachInstructionValueOperand,
eachPatternOperand,
eachTerminalOperand,
} from '../HIR/visitors';
import {assertExhaustive, retainWhere} from '../Utils/utils';
export function deadCodeElimination(fn: HIRFunction): void {
const state = findReferencedIdentifiers(fn);
for (const [, block] of fn.body.blocks) {
for (const phi of block.phis) {
if (!state.isIdOrNameUsed(phi.place.identifier)) {
block.phis.delete(phi);
}
}
retainWhere(block.instructions, instr =>
state.isIdOrNameUsed(instr.lvalue.identifier),
);
for (let i = 0; i < block.instructions.length; i++) {
const isBlockValue =
block.kind !== 'block' && i === block.instructions.length - 1;
if (!isBlockValue) {
rewriteInstruction(block.instructions[i], state);
}
}
}
}
class State {
named: Set<string> = new Set();
identifiers: Set<IdentifierId> = new Set();
reference(identifier: Identifier): void {
this.identifiers.add(identifier.id);
if (identifier.name !== null) {
this.named.add(identifier.name.value);
}
}
isIdOrNameUsed(identifier: Identifier): boolean {
return (
this.identifiers.has(identifier.id) ||
(identifier.name !== null && this.named.has(identifier.name.value))
);
}
isIdUsed(identifier: Identifier): boolean {
return this.identifiers.has(identifier.id);
}
get count(): number {
return this.identifiers.size;
}
}
function findReferencedIdentifiers(fn: HIRFunction): State {
const hasLoop = hasBackEdge(fn);
const reversedBlocks = [...fn.body.blocks.values()].reverse();
const state = new State();
let size = state.count;
do {
size = state.count;
for (const block of reversedBlocks) {
for (const operand of eachTerminalOperand(block.terminal)) {
state.reference(operand.identifier);
}
for (let i = block.instructions.length - 1; i >= 0; i--) {
const instr = block.instructions[i]!;
const isBlockValue =
block.kind !== 'block' && i === block.instructions.length - 1;
if (isBlockValue) {
state.reference(instr.lvalue.identifier);
for (const place of eachInstructionValueOperand(instr.value)) {
state.reference(place.identifier);
}
} else if (
state.isIdOrNameUsed(instr.lvalue.identifier) ||
!pruneableValue(instr.value, state)
) {
state.reference(instr.lvalue.identifier);
if (instr.value.kind === 'StoreLocal') {
if (
instr.value.lvalue.kind === InstructionKind.Reassign ||
state.isIdUsed(instr.value.lvalue.place.identifier)
) {
state.reference(instr.value.value.identifier);
}
} else {
for (const operand of eachInstructionValueOperand(instr.value)) {
state.reference(operand.identifier);
}
}
}
}
for (const phi of block.phis) {
if (state.isIdOrNameUsed(phi.place.identifier)) {
for (const [_pred, operand] of phi.operands) {
state.reference(operand.identifier);
}
}
}
}
} while (state.count > size && hasLoop);
return state;
}
function rewriteInstruction(instr: Instruction, state: State): void {
if (instr.value.kind === 'Destructure') {
switch (instr.value.lvalue.pattern.kind) {
case 'ArrayPattern': {
let nextItems: ArrayPattern['items'] | null = null;
const originalItems = instr.value.lvalue.pattern.items;
for (let i = originalItems.length - 1; i >= 0; i--) {
const item = originalItems[i];
if (item.kind === 'Identifier') {
if (state.isIdOrNameUsed(item.identifier)) {
nextItems = originalItems.slice(0, i + 1);
break;
}
} else if (item.kind === 'Spread') {
if (state.isIdOrNameUsed(item.place.identifier)) {
nextItems = originalItems.slice(0, i + 1);
break;
}
}
}
if (nextItems !== null) {
instr.value.lvalue.pattern.items = nextItems;
}
break;
}
case 'ObjectPattern': {
let nextProperties: ObjectPattern['properties'] | null = null;
for (const property of instr.value.lvalue.pattern.properties) {
if (property.kind === 'ObjectProperty') {
if (state.isIdOrNameUsed(property.place.identifier)) {
nextProperties ??= [];
nextProperties.push(property);
}
} else {
if (state.isIdOrNameUsed(property.place.identifier)) {
nextProperties = null;
break;
}
}
}
if (nextProperties !== null) {
instr.value.lvalue.pattern.properties = nextProperties;
}
break;
}
default: {
assertExhaustive(
instr.value.lvalue.pattern,
`Unexpected pattern kind '${
(instr.value.lvalue.pattern as any).kind
}'`,
);
}
}
} else if (instr.value.kind === 'StoreLocal') {
if (
instr.value.lvalue.kind !== InstructionKind.Reassign &&
!state.isIdUsed(instr.value.lvalue.place.identifier)
) {
instr.value = {
kind: 'DeclareLocal',
lvalue: instr.value.lvalue,
type: instr.value.type,
loc: instr.value.loc,
};
}
}
}
function pruneableValue(value: InstructionValue, state: State): boolean {
switch (value.kind) {
case 'DeclareLocal': {
return !state.isIdOrNameUsed(value.lvalue.place.identifier);
}
case 'StoreLocal': {
if (value.lvalue.kind === InstructionKind.Reassign) {
return !state.isIdUsed(value.lvalue.place.identifier);
}
return !state.isIdOrNameUsed(value.lvalue.place.identifier);
}
case 'Destructure': {
let isIdOrNameUsed = false;
let isIdUsed = false;
for (const place of eachPatternOperand(value.lvalue.pattern)) {
if (state.isIdUsed(place.identifier)) {
isIdOrNameUsed = true;
isIdUsed = true;
} else if (state.isIdOrNameUsed(place.identifier)) {
isIdOrNameUsed = true;
}
}
if (value.lvalue.kind === InstructionKind.Reassign) {
return !isIdUsed;
} else {
return !isIdOrNameUsed;
}
}
case 'PostfixUpdate':
case 'PrefixUpdate': {
return !state.isIdUsed(value.lvalue.identifier);
}
case 'Debugger': {
return false;
}
case 'Await':
case 'CallExpression':
case 'ComputedDelete':
case 'ComputedStore':
case 'PropertyDelete':
case 'MethodCall':
case 'PropertyStore':
case 'StoreGlobal': {
return false;
}
case 'NewExpression':
case 'UnsupportedNode':
case 'TaggedTemplateExpression': {
return false;
}
case 'GetIterator':
case 'NextPropertyOf':
case 'IteratorNext': {
return false;
}
case 'LoadContext':
case 'DeclareContext':
case 'StoreContext': {
return false;
}
case 'StartMemoize':
case 'FinishMemoize': {
return false;
}
case 'RegExpLiteral':
case 'MetaProperty':
case 'LoadGlobal':
case 'ArrayExpression':
case 'BinaryExpression':
case 'ComputedLoad':
case 'ObjectMethod':
case 'FunctionExpression':
case 'LoadLocal':
case 'JsxExpression':
case 'JsxFragment':
case 'JSXText':
case 'ObjectExpression':
case 'Primitive':
case 'PropertyLoad':
case 'TemplateLiteral':
case 'TypeCastExpression':
case 'UnaryExpression': {
return true;
}
default: {
assertExhaustive(
value,
`Unexepcted value kind \`${(value as any).kind}\``,
);
}
}
}
export function hasBackEdge(fn: HIRFunction): boolean {
return findBlocksWithBackEdges(fn).size > 0;
}
export function findBlocksWithBackEdges(fn: HIRFunction): Set<BlockId> {
const visited = new Set<BlockId>();
const blocks = new Set<BlockId>();
for (const [blockId, block] of fn.body.blocks) {
for (const predId of block.preds) {
if (!visited.has(predId)) {
blocks.add(blockId);
}
}
visited.add(blockId);
}
return blocks;
}