main
md 161 lines 6.01 KB
Rendered Raw
1 # buildReactiveScopeTerminalsHIR
2
3 ## File
4 `src/HIR/BuildReactiveScopeTerminalsHIR.ts`
5
6 ## Purpose
7 This pass transforms the HIR by inserting `ReactiveScopeTerminal` nodes to explicitly demarcate the boundaries of reactive scopes within the control flow graph. It converts the implicit scope ranges (stored on identifiers as `identifier.scope.range`) into explicit control flow structure by:
8
9 1. Inserting a `scope` terminal at the **start** of each reactive scope
10 2. Inserting a `goto` terminal at the **end** of each reactive scope
11 3. Creating fallthrough blocks to properly connect the scopes to the rest of the CFG
12
13 This transformation makes scope boundaries first-class elements in the CFG, which is essential for later passes that generate the memoization code (the `if ($[n] !== dep)` checks).
14
15 ## Input Invariants
16 - **Properly nested scopes and blocks**: The pass assumes `assertValidBlockNesting` has passed, meaning all program blocks and reactive scopes form a proper tree hierarchy
17 - **Aligned scope ranges**: Reactive scope ranges have been correctly aligned and merged by previous passes
18 - **Valid instruction IDs**: All instructions have sequential IDs that define the scope boundaries
19 - **Scopes attached to identifiers**: Reactive scopes are found by traversing all `Place` operands and collecting unique non-empty scopes
20
21 ## Output Guarantees
22 - **Explicit scope terminals**: Each reactive scope is represented in the CFG as a `ReactiveScopeTerminal` with:
23 - `block` - The BlockId containing the scope's instructions
24 - `fallthrough` - The BlockId that executes after the scope
25 - **Proper block structure**: Original blocks are split at scope boundaries
26 - **Restored HIR invariants**: The pass restores RPO ordering, predecessor sets, instruction IDs, and scope/identifier ranges
27 - **Updated phi nodes**: Phi operands are repointed when their source blocks are split
28
29 ## Algorithm
30
31 ### Step 1: Collect Scope Rewrites
32 ```
33 for each reactive scope (in range pre-order):
34 push StartScope rewrite at scope.range.start
35 push EndScope rewrite at scope.range.end
36 ```
37 The `recursivelyTraverseItems` helper traverses scopes in pre-order (outer scopes before inner scopes).
38
39 ### Step 2: Apply Rewrites by Splitting Blocks
40 ```
41 reverse queuedRewrites (to pop in ascending instruction order)
42 for each block:
43 for each instruction (or terminal):
44 while there are rewrites <= current instruction ID:
45 split block at current index
46 insert scope terminal (for start) or goto terminal (for end)
47 emit final block segment with original terminal
48 ```
49
50 ### Step 3: Repoint Phi Nodes
51 When a block is split, its final segment gets a new BlockId. Phi operands that referenced the original block are updated to reference the new final block.
52
53 ### Step 4: Restore HIR Invariants
54 - Recompute RPO (reverse post-order) block traversal
55 - Recalculate predecessor sets
56 - Renumber instruction IDs
57 - Fix scope and identifier ranges to match new instruction IDs
58
59 ## Key Data Structures
60
61 ### TerminalRewriteInfo
62 ```typescript
63 type TerminalRewriteInfo =
64 | {
65 kind: 'StartScope';
66 blockId: BlockId; // New block for scope content
67 fallthroughId: BlockId; // Block after scope ends
68 instrId: InstructionId; // Where to insert
69 scope: ReactiveScope; // The scope being created
70 }
71 | {
72 kind: 'EndScope';
73 instrId: InstructionId; // Where to insert
74 fallthroughId: BlockId; // Same as corresponding StartScope
75 };
76 ```
77
78 ### RewriteContext
79 ```typescript
80 type RewriteContext = {
81 source: BasicBlock; // Original block being split
82 instrSliceIdx: number; // Current slice start index
83 nextPreds: Set<BlockId>; // Predecessors for next emitted block
84 nextBlockId: BlockId; // BlockId for next emitted block
85 rewrites: Array<BasicBlock>; // Accumulated split blocks
86 };
87 ```
88
89 ### ScopeTraversalContext
90 ```typescript
91 type ScopeTraversalContext = {
92 fallthroughs: Map<ScopeId, BlockId>; // Cache: scope -> its fallthrough block
93 rewrites: Array<TerminalRewriteInfo>;
94 env: Environment;
95 };
96 ```
97
98 ## Edge Cases
99
100 ### Multiple Rewrites at Same Instruction ID
101 The while loop in Step 2 handles multiple scope start/ends at the same instruction ID.
102
103 ### Nested Scopes
104 The pre-order traversal ensures outer scopes are processed before inner scopes, creating proper nesting in the CFG.
105
106 ### Empty Blocks After Split
107 When a scope boundary falls at the start of a block, the split may create a block with no instructions (only a terminal).
108
109 ### Control Flow Within Scopes
110 The pass preserves existing control flow (if/else, loops) within scopes; it only adds scope entry/exit points.
111
112 ### Early Returns
113 When a return occurs within a scope, the scope terminal still has a fallthrough block, but that block may contain `Unreachable` terminal.
114
115 ## TODOs
116 Line 283-284:
117 ```typescript
118 // TODO make consistent instruction IDs instead of reusing
119 ```
120
121 ## Example
122
123 ### Fixture: `reactive-scopes-if.js`
124
125 **Before BuildReactiveScopeTerminalsHIR:**
126 ```
127 bb0 (block):
128 [1] $29_@0[1:22] = Array [] // x with scope @0 range [1:22]
129 [2] StoreLocal x$30_@0 = $29_@0
130 [3] $32 = LoadLocal a$26
131 [4] If ($32) then:bb2 else:bb3 fallthrough=bb1
132 bb2:
133 [5] $33_@1[5:11] = Array [] // y with scope @1 range [5:11]
134 ...
135 ```
136
137 **After BuildReactiveScopeTerminalsHIR:**
138 ```
139 bb0 (block):
140 [1] Scope @0 [1:28] block=bb9 fallthrough=bb10 // <-- scope terminal inserted
141 bb9:
142 [2] $29_@0 = Array []
143 [3] StoreLocal x$30_@0 = $29_@0
144 [4] $32 = LoadLocal a$26
145 [5] If ($32) then:bb2 else:bb3 fallthrough=bb1
146 bb2:
147 [6] Scope @1 [6:14] block=bb11 fallthrough=bb12 // <-- nested scope terminal
148 bb11:
149 [7] $33_@1 = Array []
150 ...
151 [13] Goto bb12 // <-- scope end goto
152 bb12:
153 ...
154 bb1:
155 [27] Goto bb10 // <-- scope @0 end goto
156 bb10:
157 [28] $50 = LoadLocal x$30_@0
158 [29] Return $50
159 ```
160
161 The key transformation is that scope boundaries become explicit control flow: a `Scope` terminal enters the scope content block, and a `Goto` terminal exits to the fallthrough block. This structure is later used to generate the memoization checks.