compiler/packages/babel-plugin-react-compiler/docs/passes/03-eliminateRedundantPhi.md MARKDOWN 91 lines View on github.com → Search inside
1# eliminateRedundantPhi23## File4`src/SSA/EliminateRedundantPhi.ts`56## Purpose7Eliminates phi nodes whose operands are trivially the same, replacing all usages of the phi's output identifier with the single source identifier. This simplifies the HIR by removing unnecessary join points that do not actually merge distinct values.89## Input Invariants10- The function must be in SSA form (i.e., `enterSSA` has already run)11- Blocks are in reverse postorder (guaranteed by the HIR structure)12- Phi nodes exist at the start of blocks where control flow merges1314## Output Guarantees15- All redundant phi nodes are removed from the HIR16- All references to eliminated phi identifiers are rewritten to the source identifier17- Non-redundant phi nodes (those merging two or more distinct values) are preserved18- Nested function expressions (FunctionExpression, ObjectMethod) also have their redundant phis eliminated and contexts rewritten1920## Algorithm21A phi node is considered redundant when:221. **All operands are the same identifier**: e.g., `x2 = phi(x1, x1, x1)` - the phi is replaced with `x1`232. **All operands are either the same identifier OR the phi's output**: e.g., `x2 = phi(x1, x2, x1, x2)` - this handles loop back-edges where the phi references itself2425The algorithm works as follows:261. Visit blocks in reverse postorder, building a rewrite table (`Map<Identifier, Identifier>`)272. For each phi node in a block:28   - First rewrite operands using any existing rewrites (to handle cascading eliminations)29   - Check if all operands (excluding self-references) point to the same identifier30   - If so, add a mapping from the phi's output to that identifier and delete the phi313. After processing phis, rewrite all instruction lvalues, operands, and terminal operands324. For nested functions, recursively call `eliminateRedundantPhi` with shared rewrites335. If the CFG has back-edges (loops) and new rewrites were added, repeat the entire process3435The loop termination condition `rewrites.size > size && hasBackEdge` ensures:36- Without loops: completes in a single pass (reverse postorder guarantees forward propagation)37- With loops: repeats until no new rewrites are found (fixpoint)3839## Key Data Structures40- **`Phi`** (from `src/HIR/HIR.ts`): Represents a phi node with:41  - `place: Place` - the output identifier42  - `operands: Map<BlockId, Place>` - maps predecessor block IDs to source places43- **`rewrites: Map<Identifier, Identifier>`**: Maps eliminated phi outputs to their replacement identifier44- **`visited: Set<BlockId>`**: Tracks visited blocks to detect back-edges (loops)4546## Edge Cases47- **Loop back-edges**: When a block has a predecessor that hasn't been visited yet (in reverse postorder), that predecessor is a back-edge. The algorithm handles self-referential phis like `x2 = phi(x1, x2)` by ignoring operands equal to the phi's output.48- **Cascading eliminations**: When one phi's output is used in another phi's operands, the algorithm rewrites operands before checking redundancy, enabling transitive elimination in a single pass (for non-loop cases).49- **Nested functions**: FunctionExpression and ObjectMethod values contain nested HIR that may have their own phis. The algorithm recursively processes these with a shared rewrite table, ensuring context captures are also rewritten.50- **Empty phi check**: The algorithm includes an invariant check that phi operands are never empty (which would be invalid HIR).5152## TODOs53(None found in the source code)5455## Example5657Consider this fixture from `rewrite-phis-in-lambda-capture-context.js`:5859```javascript60function Component() {61  const x = 4;62  const get4 = () => {63    while (bar()) {64      if (baz) { bar(); }65    }66    return () => x;67  };68  return get4;69}70```7172**After SSA pass**, the inner function has redundant phis due to the loop:7374```75bb2 (loop):76  predecessor blocks: bb1 bb577  x$29: phi(bb1: x$21, bb5: x$30)  // Loop header phi78  ...79bb5 (block):80  predecessor blocks: bb6 bb481  x$30: phi(bb6: x$29, bb4: x$29)  // Redundant: both operands are x$2982  ...83```8485**After EliminateRedundantPhi**:86- `x$30 = phi(x$29, x$29)` is eliminated because both operands are `x$29`87- `x$29 = phi(x$21, x$30)` becomes `x$29 = phi(x$21, x$29)` after rewriting, which is also redundant (one operand is the phi itself, the other is `x$21`)88- Both phis are eliminated, and all uses of `x$29` and `x$30` are rewritten to `x$21`8990The result: the context capture `@context[x$29]` becomes `@context[x$21]`, correctly propagating that `x` is never modified inside the loop.

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.