deadCodeElimination

File

src/Optimization/DeadCodeElimination.ts

Purpose

Eliminates instructions whose values are unused, reducing generated code size. The pass performs mark-and-sweep analysis to identify and remove dead code while preserving side effects and program semantics.

Input Invariants

Output Guarantees

Algorithm

Two-phase mark-and-sweep with fixed-point iteration for loops:

Phase 1: Mark (findReferencedIdentifiers)

  1. Detect if function has back-edges (loops)
  2. Iterate blocks in reverse postorder (successors before predecessors) to visit usages before declarations
  3. For each block:
    • Mark all terminal operands as referenced
    • Process instructions in reverse order:
      • If lvalue is used OR instruction is not pruneable, mark the lvalue and all operands as referenced
      • Special case for StoreLocal: only mark initializer if the SSA lvalue is actually read
    • Mark phi operands if the phi result is used
  4. If loops exist and new identifiers were marked, repeat until fixed point

Phase 2: Sweep

  1. Remove unused phi nodes from each block
  2. Remove instructions with unused lvalues using retainWhere
  3. Rewrite retained instructions:
    • Array destructuring: Replace unused elements with holes, truncate trailing holes
    • Object destructuring: Remove unused properties (only if rest element is unused or absent)
    • StoreLocal: Convert to DeclareLocal if initializer value is never read
  4. Remove unused context variables

Key Data Structures

Edge Cases

TODOs

Example

Input:

function Component(props) {
  const _ = 42;
  return props.value;
}

After DeadCodeElimination: The const _ = 42 assignment is removed since _ is never used:

function Component(props) {
  return props.value;
}

Array destructuring example:

Input:

function foo(props) {
  const [x, unused, y] = props.a;
  return x + y;
}

Output (middle element becomes a hole):

function foo(props) {
  const [x, , y] = props.a;
  return x + y;
}