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.