compiler/packages/babel-plugin-react-compiler/docs/passes/15-alignReactiveScopesToBlockScopesHIR.md MARKDOWN 178 lines View on github.com → Search inside
1# alignReactiveScopesToBlockScopesHIR23## File4`src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts`56## Purpose7This is the **2nd of 4 passes** that determine how to break a function into discrete reactive scopes (independently memoizable units of code). The pass aligns reactive scope boundaries to control flow (block scope) boundaries.89The problem it solves: Prior inference passes assign reactive scopes to operands based on mutation ranges at arbitrary instruction points in the control-flow graph. However, to generate memoization blocks around instructions, scopes must be aligned to block-scope boundaries -- you cannot memoize half of a loop or half of an if-block.1011**Example from the source code comments:**12```javascript13function foo(cond, a) {14                    // original scope end15                         // expanded scope end16   const x = [];    |    |17   if (cond) {      |    |18     ...            |    |19     x.push(a);     <--- original scope ended here20     ...                 |21   }                     <--- scope must extend to here22}23```2425## Input Invariants26- `InferReactiveScopeVariables` has run: Each identifier has been assigned a `ReactiveScope` with a `range` (start/end instruction IDs) based on mutation analysis27- The HIR is in SSA form: Blocks have unique IDs, instructions have unique IDs, and control flow is represented with basic blocks28- Each block has a terminal with possible successors and fallthroughs29- Each scope has a mutable range `{start: InstructionId, end: InstructionId}` indicating when the scope is active3031## Output Guarantees32- **Scopes end at valid block boundaries**: A reactive scope may only end at the same block scope level as it began. The scope's `range.end` is updated to the first instruction of the fallthrough block after any control flow structure that the scope overlaps33- **Scopes start at valid block boundaries**: For labeled breaks (gotos to a label), scopes that extend beyond the goto have their `range.start` extended back to include the label34- **Value blocks (ternary, logical, optional) are handled specially**: Scopes inside value blocks are extended to align with the outer block scope's instruction range3536## Algorithm3738The pass performs a single forward traversal over all blocks:3940### 1. Tracking Active Scopes41- Maintains `activeScopes: Set<ReactiveScope>` - scopes whose range overlaps the current block42- Maintains `activeBlockFallthroughRanges: Array<{range, fallthrough}>` - stack of pending block-fallthrough ranges4344### 2. Per-Block Processing45For each block:46- Prune `activeScopes` to only those that extend past the current block's start47- If this block is a fallthrough target, pop the range from the stack and extend all active scopes' start to the range start4849### 3. Recording Places50For each instruction lvalue and operand:51- If the place has a scope, add it to `activeScopes`52- If inside a value block, extend the scope's range to match the value block's outer range5354### 4. Handling Block Fallthroughs55When a terminal has a fallthrough (not a simple branch):56- Extend all active scopes whose `range.end > terminal.id` to at least the first instruction of the fallthrough block57- Push the fallthrough range onto the stack for future scopes5859### 5. Handling Labeled Breaks (Goto)60When encountering a goto to a label (not the natural fallthrough):61- Find the corresponding fallthrough range on the stack62- Extend all active scopes to span from the label start to its fallthrough end6364### 6. Value Block Handling65For ternary, logical, and optional terminals:66- Create `ValueBlockNode` to track the outer block's instruction range67- Scopes inside value blocks inherit this range, ensuring they align to the outer block scope6869## Key Data Structures7071```typescript72type ValueBlockNode = {73  kind: 'node';74  id: InstructionId;75  valueRange: MutableRange;  // Range of outer block scope76  children: Array<ValueBlockNode | ReactiveScopeNode>;77};7879type ReactiveScopeNode = {80  kind: 'scope';81  id: InstructionId;82  scope: ReactiveScope;83};8485// Tracked during traversal:86activeBlockFallthroughRanges: Array<{87  range: InstructionRange;88  fallthrough: BlockId;89}>;90activeScopes: Set<ReactiveScope>;91valueBlockNodes: Map<BlockId, ValueBlockNode>;92```9394## Edge Cases9596### Labeled Breaks97When a `goto` jumps to a label (not the natural fallthrough), scopes must be extended to include the entire labeled block range, preventing the break from jumping out of the scope.9899### Value Blocks (Ternary/Logical/Optional)100These create nested "value" contexts. Scopes inside must be aligned to the outer block scope's boundaries, not the value block's boundaries.101102### Nested Control Flow103Deeply nested if-statements require the scope to be extended through all levels back to the outermost block where the scope started.104105### do-while and try/catch106The terminal's successor might be a block (not value block), which is handled specially.107108## TODOs1091. `// TODO: consider pruning activeScopes per instruction` - Currently, `activeScopes` is only pruned at block start points. Some scopes may no longer be active by the time a goto is encountered.1101112. `// TODO: add a variant of eachTerminalSuccessor() that visits _all_ successors, not just those that are direct successors for normal control-flow ordering.` - The current implementation uses `mapTerminalSuccessors` which may not visit all successors in all cases.112113## Example114115### Fixture: `extend-scopes-if.js`116117**Input:**118```javascript119function foo(a, b, c) {120  let x = [];121  if (a) {122    if (b) {123      if (c) {124        x.push(0);  // Mutation of x ends here (instruction 12-13)125      }126    }127  }128  if (x.length) {  // instruction 16129    return x;130  }131  return null;132}133```134135**Before AlignReactiveScopesToBlockScopesHIR:**136```137x$23_@0[1:13]  // Scope range 1-13138```139The scope for `x` ends at instruction 13 (inside the innermost if block).140141**After AlignReactiveScopesToBlockScopesHIR:**142```143x$23_@0[1:16]  // Scope range extended to 1-16144```145The scope is extended to instruction 16 (the first instruction after all the nested if-blocks), aligning to the block scope boundary.146147**Generated Code:**148```javascript149function foo(a, b, c) {150  const $ = _c(4);151  let x;152  if ($[0] !== a || $[1] !== b || $[2] !== c) {153    x = [];154    if (a) {155      if (b) {156        if (c) {157          x.push(0);158        }159      }160    }161    // Scope ends here, after ALL the if-blocks162    $[0] = a;163    $[1] = b;164    $[2] = c;165    $[3] = x;166  } else {167    x = $[3];168  }169  // Code outside the scope170  if (x.length) {171    return x;172  }173  return null;174}175```176177The memoization block correctly wraps the entire nested if-structure, not just part of it.

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.