@@ -22,8 +22,10 @@ import {
22
22
mapTerminalSuccessors ,
23
23
terminalFallthrough ,
24
24
} from "../HIR/visitors" ;
25
+ import { retainWhere_Set } from "../Utils/utils" ;
25
26
import { getPlaceScope } from "./BuildReactiveBlocks" ;
26
27
28
+ type InstructionRange = MutableRange ;
27
29
/*
28
30
* Note: this is the 2nd of 4 passes that determine how to break a function into discrete
29
31
* reactive scopes (independently memoizeable units of code):
@@ -66,18 +68,20 @@ import { getPlaceScope } from "./BuildReactiveBlocks";
66
68
* will be the updated end for that scope).
67
69
*/
68
70
export function alignReactiveScopesToBlockScopesHIR ( fn : HIRFunction ) : void {
69
- const blockNodes = new Map < BlockId , BlockNode > ( ) ;
70
- const rootNode : BlockNode = {
71
- kind : "node" ,
72
- valueRange : null ,
73
- children : [ ] ,
74
- id : makeInstructionId ( 0 ) ,
75
- } ;
76
- blockNodes . set ( fn . body . entry , rootNode ) ;
71
+ const activeBlockFallthroughRanges : Array < {
72
+ range : InstructionRange ;
73
+ fallthrough : BlockId ;
74
+ } > = [ ] ;
75
+ const activeScopes = new Set < ReactiveScope > ( ) ;
77
76
const seen = new Set < ReactiveScope > ( ) ;
77
+ const valueBlockNodes = new Map < BlockId , ValueBlockNode > ( ) ;
78
78
const placeScopes = new Map < Place , ReactiveScope > ( ) ;
79
79
80
- function recordPlace ( id : InstructionId , place : Place , node : BlockNode ) : void {
80
+ function recordPlace (
81
+ id : InstructionId ,
82
+ place : Place ,
83
+ node : ValueBlockNode | null
84
+ ) : void {
81
85
if ( place . identifier . scope !== null ) {
82
86
placeScopes . set ( place , place . identifier . scope ) ;
83
87
}
@@ -86,13 +90,14 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
86
90
if ( scope == null ) {
87
91
return ;
88
92
}
89
- node . children . push ( { kind : "scope" , scope, id } ) ;
93
+ activeScopes . add ( scope ) ;
94
+ node ?. children . push ( { kind : "scope" , scope, id } ) ;
90
95
91
96
if ( seen . has ( scope ) ) {
92
97
return ;
93
98
}
94
99
seen . add ( scope ) ;
95
- if ( node . valueRange !== null ) {
100
+ if ( node != null && node . valueRange !== null ) {
96
101
scope . range . start = makeInstructionId (
97
102
Math . min ( node . valueRange . start , scope . range . start )
98
103
) ;
@@ -103,16 +108,25 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
103
108
}
104
109
105
110
for ( const [ , block ] of fn . body . blocks ) {
106
- const { instructions, terminal } = block ;
107
- const node = blockNodes . get ( block . id ) ;
108
- if ( node === undefined ) {
109
- CompilerError . invariant ( false , {
110
- reason : `Expected a node to be initialized for block` ,
111
- loc : instructions [ 0 ] ?. loc ?? terminal . loc ,
112
- description : `No node for block bb${ block . id } (${ block . kind } )` ,
113
- } ) ;
111
+ const startingId = block . instructions [ 0 ] ?. id ?? block . terminal . id ;
112
+ retainWhere_Set ( activeScopes , ( scope ) => scope . range . end > startingId ) ;
113
+ const top = activeBlockFallthroughRanges . at ( - 1 ) ;
114
+ if ( top ?. fallthrough === block . id ) {
115
+ activeBlockFallthroughRanges . pop ( ) ;
116
+ /*
117
+ * All active scopes must have either started before or within the last
118
+ * block-fallthrough range. In either case, they overlap this block-
119
+ * fallthrough range and can have their ranges extended.
120
+ */
121
+ for ( const scope of activeScopes ) {
122
+ scope . range . start = makeInstructionId (
123
+ Math . min ( scope . range . start , top . range . start )
124
+ ) ;
125
+ }
114
126
}
115
127
128
+ const { instructions, terminal } = block ;
129
+ const node = valueBlockNodes . get ( block . id ) ?? null ;
116
130
for ( const instr of instructions ) {
117
131
for ( const lvalue of eachInstructionLValue ( instr ) ) {
118
132
recordPlace ( instr . id , lvalue , node ) ;
@@ -125,36 +139,42 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
125
139
recordPlace ( terminal . id , operand , node ) ;
126
140
}
127
141
128
- // Save the current node for the fallback block, where this block scope continues
129
142
const fallthrough = terminalFallthrough ( terminal ) ;
130
- if ( fallthrough !== null && ! blockNodes . has ( fallthrough ) ) {
143
+ if ( fallthrough !== null ) {
131
144
/*
132
- * Any scopes that carried over across a terminal->fallback need their range extended
133
- * to at least the first instruction of the fallback
134
- *
135
- * Note that it's possible for a terminal such as an if or switch to have a null fallback,
136
- * indicating that all control-flow paths diverge instead of reaching the fallthrough.
137
- * In this case there isn't an instruction id in the program that we can point to for the
138
- * updated range. Since the output is correct in this case we leave it, but it would be
139
- * more correct to find the maximum instuction id in the whole program and set the range.end
140
- * to one greater. Alternatively, we could leave in an unreachable fallthrough (with a new
141
- * "unreachable" terminal variant, perhaps) and use that instruction id.
145
+ * Any currently active scopes that overlaps the block-fallthrough range
146
+ * need their range extended to at least the first instruction of the
147
+ * fallthrough
142
148
*/
143
149
const fallthroughBlock = fn . body . blocks . get ( fallthrough ) ! ;
144
150
const nextId =
145
151
fallthroughBlock . instructions [ 0 ] ?. id ?? fallthroughBlock . terminal . id ;
146
- for ( const child of node . children ) {
147
- if ( child . kind !== "scope" ) {
148
- continue ;
149
- }
150
- const scope = child . scope ;
152
+ for ( const scope of activeScopes ) {
151
153
if ( scope . range . end > terminal . id ) {
152
154
scope . range . end = makeInstructionId (
153
155
Math . max ( scope . range . end , nextId )
154
156
) ;
155
157
}
156
158
}
157
- blockNodes . set ( fallthrough , node ) ;
159
+ /**
160
+ * We also record the block-fallthrough range for future scopes that begin
161
+ * within the range (and overlap with the range end).
162
+ */
163
+ activeBlockFallthroughRanges . push ( {
164
+ fallthrough,
165
+ range : {
166
+ start : terminal . id ,
167
+ end : nextId ,
168
+ } ,
169
+ } ) ;
170
+
171
+ CompilerError . invariant ( ! valueBlockNodes . has ( fallthrough ) , {
172
+ reason : "Expect hir blocks to have unique fallthroughs" ,
173
+ loc : terminal . loc ,
174
+ } ) ;
175
+ if ( node != null ) {
176
+ valueBlockNodes . set ( fallthrough , node ) ;
177
+ }
158
178
}
159
179
160
180
/*
@@ -166,48 +186,35 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
166
186
* just those that are direct successors for normal control-flow ordering.
167
187
*/
168
188
mapTerminalSuccessors ( terminal , ( successor ) => {
169
- if ( blockNodes . has ( successor ) ) {
189
+ if ( valueBlockNodes . has ( successor ) ) {
170
190
return successor ;
171
191
}
172
192
173
193
const successorBlock = fn . body . blocks . get ( successor ) ! ;
174
- /*
175
- * we need the block kind check here because the do..while terminal's successor
176
- * is a block, and try's successor is a catch block
177
- */
178
194
if ( successorBlock . kind === "block" || successorBlock . kind === "catch" ) {
179
- const childNode : BlockNode = {
180
- kind : "node" ,
181
- id : terminal . id ,
182
- children : [ ] ,
183
- valueRange : null ,
184
- } ;
185
- node . children . push ( childNode ) ;
186
- blockNodes . set ( successor , childNode ) ;
195
+ /*
196
+ * we need the block kind check here because the do..while terminal's
197
+ * successor is a block, and try's successor is a catch block
198
+ */
187
199
} else if (
188
- node . valueRange = == null ||
200
+ node == null ||
189
201
terminal . kind === "ternary" ||
190
202
terminal . kind === "logical" ||
191
203
terminal . kind === "optional"
192
204
) {
193
205
/**
194
- * Create a new scope node whenever we transition from block scope -> value scope .
206
+ * Create a new node whenever we transition from non-value -> value block .
195
207
*
196
208
* For compatibility with the previous ReactiveFunction-based scope merging logic,
197
209
* we also create new scope nodes for ternary, logical, and optional terminals.
198
- * However, inside value blocks we always store a range (valueRange) that is the
210
+ * Inside value blocks we always store a range (valueRange) that is the
199
211
* start/end instruction ids at the nearest parent block scope level, so that
200
212
* scopes inside the value blocks can be extended to align with block scope
201
213
* instructions.
202
214
*/
203
- const childNode = {
204
- kind : "node" ,
205
- id : terminal . id ,
206
- children : [ ] ,
207
- valueRange : null ,
208
- } as BlockNode ;
209
- if ( node . valueRange === null ) {
210
- // Transition from block->value scope, derive the outer block scope range
215
+ let valueRange : MutableRange ;
216
+ if ( node == null ) {
217
+ // Transition from block->value block, derive the outer block range
211
218
CompilerError . invariant ( fallthrough !== null , {
212
219
reason : `Expected a fallthrough for value block` ,
213
220
loc : terminal . loc ,
@@ -216,46 +223,50 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
216
223
const nextId =
217
224
fallthroughBlock . instructions [ 0 ] ?. id ??
218
225
fallthroughBlock . terminal . id ;
219
- childNode . valueRange = {
226
+ valueRange = {
220
227
start : terminal . id ,
221
228
end : nextId ,
222
229
} ;
223
230
} else {
224
231
// else value->value transition, reuse the range
225
- childNode . valueRange = node . valueRange ;
232
+ valueRange = node . valueRange ;
226
233
}
227
- node . children . push ( childNode ) ;
228
- blockNodes . set ( successor , childNode ) ;
234
+ const childNode : ValueBlockNode = {
235
+ kind : "node" ,
236
+ id : terminal . id ,
237
+ children : [ ] ,
238
+ valueRange,
239
+ } ;
240
+ node ?. children . push ( childNode ) ;
241
+ valueBlockNodes . set ( successor , childNode ) ;
229
242
} else {
230
243
// this is a value -> value block transition, reuse the node
231
- blockNodes . set ( successor , node ) ;
244
+ valueBlockNodes . set ( successor , node ) ;
232
245
}
233
246
return successor ;
234
247
} ) ;
235
248
}
236
-
237
- // console.log(_debug(rootNode));
238
249
}
239
250
240
- type BlockNode = {
251
+ type ValueBlockNode = {
241
252
kind : "node" ;
242
253
id : InstructionId ;
243
- valueRange : MutableRange | null ;
244
- children : Array < BlockNode | ReactiveScopeNode > ;
254
+ valueRange : MutableRange ;
255
+ children : Array < ValueBlockNode | ReactiveScopeNode > ;
245
256
} ;
246
257
type ReactiveScopeNode = {
247
258
kind : "scope" ;
248
259
id : InstructionId ;
249
260
scope : ReactiveScope ;
250
261
} ;
251
262
252
- function _debug ( node : BlockNode ) : string {
263
+ function _debug ( node : ValueBlockNode ) : string {
253
264
const buf : Array < string > = [ ] ;
254
265
_printNode ( node , buf , 0 ) ;
255
266
return buf . join ( "\n" ) ;
256
267
}
257
268
function _printNode (
258
- node : BlockNode | ReactiveScopeNode ,
269
+ node : ValueBlockNode | ReactiveScopeNode ,
259
270
out : Array < string > ,
260
271
depth : number = 0
261
272
) : void {
@@ -265,10 +276,7 @@ function _printNode(
265
276
`${ prefix } [${ node . id } ] @${ node . scope . id } [${ node . scope . range . start } :${ node . scope . range . end } ]`
266
277
) ;
267
278
} else {
268
- let range =
269
- node . valueRange !== null
270
- ? ` [${ node . valueRange . start } :${ node . valueRange . end } ]`
271
- : "" ;
279
+ let range = ` (range=[${ node . valueRange . start } :${ node . valueRange . end } ])` ;
272
280
out . push ( `${ prefix } [${ node . id } ] node${ range } [` ) ;
273
281
for ( const child of node . children ) {
274
282
_printNode ( child , out , depth + 1 ) ;
0 commit comments