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.