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