1# propagateScopeDependenciesHIR23## File4`src/HIR/PropagateScopeDependenciesHIR.ts`56## Purpose7The `propagateScopeDependenciesHIR` pass is responsible for computing and assigning the **dependencies** for each reactive scope in the compiled function. Dependencies are the external values that a scope reads, which determine when the scope needs to re-execute. This is a critical step for memoization correctness - the compiler must track exactly which values a scope depends on so it can generate proper cache invalidation checks.89The pass also populates:10- `scope.dependencies` - The set of `ReactiveScopeDependency` objects the scope reads11- `scope.declarations` - Values declared within the scope that are used outside it1213## Input Invariants14- Reactive scopes must be established (pass runs after `BuildReactiveScopeTerminalsHIR`)15- The function must be in SSA form16- `InferMutationAliasingRanges` must have run to establish when values are being mutated17- `InferReactivePlaces` marks which identifiers are reactive18- Scope ranges have been aligned and normalized by earlier passes1920## Output Guarantees21After this pass completes:22231. Each `ReactiveScope.dependencies` contains the minimal set of dependencies that:24 - Were declared before the scope started25 - Are read within the scope26 - Are not ref values (which are always mutable)27 - Are not object methods (which get codegen'd back into object literals)28292. Each `ReactiveScope.declarations` contains identifiers that:30 - Are assigned within the scope31 - Are used outside the scope (need to be exposed as scope outputs)32333. Property load chains are resolved to their root identifiers with paths (e.g., `props.user.name` becomes `{identifier: props, path: ["user", "name"]}`)34354. Optional chains are handled correctly, distinguishing between `a?.b` and `a.b` access types3637## Algorithm3839### Phase 1: Build Sidemaps40411. **findTemporariesUsedOutsideDeclaringScope**: Identifies temporaries that are used outside the scope where they were declared (cannot be hoisted/reordered safely)42432. **collectTemporariesSidemap**: Creates a mapping from temporary IdentifierIds to their source `ReactiveScopeDependency`. For example:44 ```45 $0 = LoadLocal 'a'46 $1 = PropertyLoad $0.'b'47 ```48 Maps `$1.id` to `{identifier: a, path: [{property: 'b', optional: false}]}`49503. **collectOptionalChainSidemap**: Traverses optional chain blocks to map temporaries within optional chains to their full optional dependency path51524. **collectHoistablePropertyLoads**: Uses CFG analysis to determine which property loads can be safely hoisted5354### Phase 2: Collect Dependencies5556The `collectDependencies` function traverses the HIR, maintaining a stack of active scopes:57581. **Scope Entry/Exit**: When entering a scope terminal, push a new dependency array. When exiting, propagate collected dependencies to parent scopes if valid.59602. **Instruction Processing**: For each instruction:61 - Declare the lvalue with its instruction id and current scope62 - Visit operands to record them as potential dependencies63 - Handle special cases like `StoreLocal` (tracks reassignments), `Destructure`, `PropertyLoad`, etc.64653. **Dependency Validation** (`#checkValidDependency`):66 - Skip ref values (`isRefValueType`)67 - Skip object methods (`isObjectMethodType`)68 - Only include if declared before scope start6970### Phase 3: Derive Minimal Dependencies7172For each scope, use `ReactiveScopeDependencyTreeHIR` to:731. Build a tree from hoistable property loads742. Add all collected dependencies to the tree753. Truncate dependencies at their maximal safe-to-evaluate subpath764. Derive the minimal set (removing redundant nested dependencies)7778## Key Data Structures7980### ReactiveScopeDependency81```typescript82type ReactiveScopeDependency = {83 identifier: Identifier; // Root identifier84 reactive: boolean; // Whether the value is reactive85 path: DependencyPathEntry[]; // Chain of property accesses86}87```8889### DependencyPathEntry90```typescript91type DependencyPathEntry = {92 property: PropertyLiteral; // Property name93 optional: boolean; // Is this `?.` access?94}95```9697### DependencyCollectionContext98Maintains:99- `#declarations`: Map of DeclarationId to {id, scope} recording where each value was declared100- `#reassignments`: Map of Identifier to latest assignment info101- `#scopes`: Stack of currently active ReactiveScopes102- `#dependencies`: Stack of dependency arrays (one per active scope)103- `#temporaries`: Sidemap for resolving property loads104105### ReactiveScopeDependencyTreeHIR106A tree structure for efficient dependency deduplication that stores hoistable objects, tracks access types, and computes minimal dependencies.107108## Edge Cases109110### Values Used Outside Declaring Scope111If a temporary is used outside its declaring scope, it cannot be tracked in the sidemap because reordering the read would be invalid.112113### Ref.current Access114Accessing `ref.current` is treated specially - the dependency is truncated to just `ref`.115116### Optional Chains117Optional chains like `a?.b?.c` produce different dependency paths than `a.b.c`. The pass distinguishes them and may merge optional loads into unconditional ones when control flow proves the object is non-null.118119### Inner Functions120Dependencies from inner functions are collected recursively but with special handling for context variables.121122### Phi Nodes123When a value comes from multiple control flow paths, optional chain dependencies from phi operands are also visited.124125## TODOs1261. Line 374-375: `// TODO(mofeiZ): understand optional chaining` - More documentation needed for optional chain handling127128## Example129130### Fixture: `reactive-control-dependency-if.js`131132**Input:**133```javascript134function Component(props) {135 let x;136 if (props.cond) {137 x = 1;138 } else {139 x = 2;140 }141 return [x];142}143```144145**Before PropagateScopeDependenciesHIR:**146```147Scope scope @0 [12:15] dependencies=[] declarations=[] reassignments=[] block=bb9148```149150**After PropagateScopeDependenciesHIR:**151```152Scope scope @0 [12:15] dependencies=[x$24:TPrimitive] declarations=[$26_@0] reassignments=[] block=bb9153```154155The pass identified that:156- The scope at `[x]` depends on `x$24` (the phi node result from the if/else branches)157- Even though `x` is assigned to constants (1 or 2), its value depends on the reactive control flow condition `props.cond`158- The scope declares `$26_@0` (the array output)
Findings
✓ No findings reported for this file.