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.id)) {
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.id)) {
for (const [_pred, operand] of phi.operands) {
state.reference(operand);
}
}
}
}
} 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;
}