compiler/packages/babel-plugin-react-compiler/docs/passes/10-inferReactivePlaces.md MARKDOWN 170 lines View on github.com → Search inside
1# inferReactivePlaces23## File4`src/Inference/InferReactivePlaces.ts`56## Purpose7Determines which `Place`s (identifiers and temporaries) in the HIR are **reactive** - meaning they may *semantically* change over the course of the component or hook's lifetime. This information is critical for memoization: reactive places form the dependencies that, when changed, should invalidate cached values.89A place is reactive if it derives from any source of reactivity:101. **Props** - Component parameters may change between renders112. **Hooks** - Hooks can access state or context which can change123. **`use` operator** - Can access context which may change134. **Mutation with reactive operands** - Values mutated in instructions that have reactive operands become reactive themselves145. **Conditional assignment based on reactive control flow** - Values assigned in branches controlled by reactive conditions become reactive1516## Input Invariants17- HIR is in SSA form with phi nodes at join points18- `inferMutationAliasingEffects` and `inferMutationAliasingRanges` have run, establishing:19  - Effect annotations on operands (Effect.Capture, Effect.Store, Effect.Mutate, etc.)20  - Mutable ranges on identifiers21  - Aliasing relationships captured by `findDisjointMutableValues`22- All operands have known effects (asserts on `Effect.Unknown`)2324## Output Guarantees25- Every reactive Place has `place.reactive = true`26- Reactivity is transitively complete (derived from reactive  reactive)27- All identifiers in a mutable alias group share reactivity28- Reactivity is propagated to operands used within nested function expressions2930## Algorithm31The algorithm uses **fixpoint iteration** to propagate reactivity forward through the control-flow graph:3233### Initialization341. Create a `ReactivityMap` backed by disjoint sets of mutably-aliased identifiers352. Mark all function parameters as reactive (props are reactive by definition)363. Create a `ControlDominators` helper to identify blocks controlled by reactive conditions3738### Fixpoint Loop39Iterate until no changes occur:4041For each block:421. **Phi Nodes**: Mark phi nodes reactive if:43   - Any operand is reactive, OR44   - Any predecessor block is controlled by a reactive condition (control-flow dependency)45462. **Instructions**: For each instruction:47   - Track stable identifier sources (for hooks like `useRef`, `useState` dispatch)48   - Check if any operand is reactive49   - Hook calls and `use` operator are sources of reactivity50   - If instruction has reactive input:51     - Mark lvalues reactive (unless they are known-stable like `setState` functions)52   - If instruction has reactive input OR is in reactive-controlled block:53     - Mark mutable operands (Capture, Store, Mutate effects) as reactive54553. **Terminals**: Check terminal operands for reactivity5657### Post-processing58Propagate reactivity to inner functions (nested `FunctionExpression` and `ObjectMethod`).5960## Key Data Structures6162### ReactivityMap63```typescript64class ReactivityMap {65  hasChanges: boolean = false;           // Tracks if fixpoint changed66  reactive: Set<IdentifierId> = new Set(); // Set of reactive identifiers67  aliasedIdentifiers: DisjointSet<Identifier>; // Mutable alias groups68}69```70- Uses disjoint sets so that when one identifier in an alias group becomes reactive, they all are effectively reactive71- `isReactive(place)` checks and marks `place.reactive = true` as a side effect72- `snapshot()` resets change tracking and returns whether changes occurred7374### StableSidemap75```typescript76class StableSidemap {77  map: Map<IdentifierId, {isStable: boolean}> = new Map();78}79```80Tracks sources of stability (e.g., `useState()[1]` dispatch function). Forward data-flow analysis that:81- Records hook calls that return stable types82- Propagates stability through PropertyLoad and Destructure from stable containers83- Propagates through LoadLocal and StoreLocal8485### ControlDominators86Uses post-dominator frontier analysis to determine which blocks are controlled by reactive branch conditions.8788## Edge Cases8990### Backward Reactivity Propagation via Mutable Aliasing91```javascript92const x = [];93const z = [x];94x.push(props.input);95return <div>{z}</div>;96```97Here `z` aliases `x` which is later mutated with reactive data. The disjoint set ensures `z` becomes reactive even though the mutation happens after its creation.9899### Stable Types Are Not Reactive100```javascript101const [state, setState] = useState();102// setState is stable - not marked reactive despite coming from reactive hook103```104The `StableSidemap` tracks these and skips marking them reactive.105106### Ternary with Stable Values Still Reactive107```javascript108props.cond ? setState1 : setState2109```110Even though both branches are stable types, the result depends on reactive control flow, so it cannot be marked non-reactive just based on type.111112### Phi Nodes with Reactive Predecessors113When a phi's predecessor block is controlled by a reactive condition, the phi becomes reactive even if its operands are all non-reactive constants.114115## TODOs116No explicit TODO comments are present in the source file. However, comments note:117118- **ComputedLoads not handled for stability**: Only PropertyLoad propagates stability from containers, not ComputedLoad. The comment notes this is safe because stable containers have differently-typed elements, but ComputedLoad handling could be added.119120## Example121122### Fixture: `reactive-dependency-fixpoint.js`123124**Input:**125```javascript126function Component(props) {127  let x = 0;128  let y = 0;129  while (x === 0) {130    x = y;131    y = props.value;132  }133  return [x];134}135```136137**Before InferReactivePlaces:**138```139bb1 (loop):140  store x$26:TPhi:TPhi: phi(bb0: read x$21:TPrimitive, bb3: read x$32:TPhi)141  store y$30:TPhi:TPhi: phi(bb0: read y$24:TPrimitive, bb3: read y$37)142  ...143bb3 (block):144  [12] mutate? $35 = LoadLocal read props$19145  [13] mutate? $36 = PropertyLoad read $35.value146  [14] mutate? $38 = StoreLocal Reassign mutate? y$37 = read $36147```148149**After InferReactivePlaces:**150```151bb1 (loop):152  store x$26:TPhi{reactive}:TPhi: phi(bb0: read x$21:TPrimitive, bb3: read x$32:TPhi{reactive})153  store y$30:TPhi{reactive}:TPhi: phi(bb0: read y$24:TPrimitive, bb3: read y$37{reactive})154  [6] mutate? $27:TPhi{reactive} = LoadLocal read x$26:TPhi{reactive}155  ...156bb3 (block):157  [12] mutate? $35{reactive} = LoadLocal read props$19{reactive}158  [13] mutate? $36{reactive} = PropertyLoad read $35{reactive}.value159  [14] mutate? $38{reactive} = StoreLocal Reassign mutate? y$37{reactive} = read $36{reactive}160```161162**Key observations:**163- `props$19` is marked `{reactive}` as a function parameter164- The reactivity propagates through the loop:165  - First iteration: `y$37` becomes reactive from `props.value`166  - Second iteration: `x$32` becomes reactive from `y$30` (which is reactive via the phi from `y$37`)167  - The phi nodes `x$26` and `y$30` become reactive because their bb3 operands are reactive168- The fixpoint algorithm handles this backward propagation through the loop correctly169- The final output `$40` is reactive, so the array `[x]` will be memoized with `x` as a dependency

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.