@@ -4,6 +4,7 @@ import {Set_intersect, Set_union, getOrInsertDefault} from '../Utils/utils';
4
4
import {
5
5
BasicBlock ,
6
6
BlockId ,
7
+ DependencyPathEntry ,
7
8
GeneratedSource ,
8
9
HIRFunction ,
9
10
Identifier ,
@@ -66,7 +67,9 @@ export function collectHoistablePropertyLoads(
66
67
fn : HIRFunction ,
67
68
temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
68
69
) : ReadonlyMap < ScopeId , BlockInfo > {
69
- const nodes = collectNonNullsInBlocks ( fn , temporaries ) ;
70
+ const registry = new PropertyPathRegistry ( ) ;
71
+
72
+ const nodes = collectNonNullsInBlocks ( fn , temporaries , registry ) ;
70
73
propagateNonNull ( fn , nodes ) ;
71
74
72
75
const nodesKeyedByScopeId = new Map < ScopeId , BlockInfo > ( ) ;
@@ -84,33 +87,33 @@ export function collectHoistablePropertyLoads(
84
87
85
88
export type BlockInfo = {
86
89
block : BasicBlock ;
87
- assumedNonNullObjects : ReadonlySet < PropertyLoadNode > ;
90
+ assumedNonNullObjects : ReadonlySet < PropertyPathNode > ;
88
91
} ;
89
92
90
93
/**
91
- * Tree data structure to dedupe property loads (e.g. a.b.c)
94
+ * PropertyLoadRegistry data structure to dedupe property loads (e.g. a.b.c)
92
95
* and make computing sets intersections simpler.
93
96
*/
94
97
type RootNode = {
95
- properties : Map < string , PropertyLoadNode > ;
98
+ properties : Map < string , PropertyPathNode > ;
96
99
parent : null ;
97
100
// Recorded to make later computations simpler
98
101
fullPath : ReactiveScopeDependency ;
99
102
root : IdentifierId ;
100
103
} ;
101
104
102
- type PropertyLoadNode =
105
+ type PropertyPathNode =
103
106
| {
104
- properties : Map < string , PropertyLoadNode > ;
105
- parent : PropertyLoadNode ;
107
+ properties : Map < string , PropertyPathNode > ;
108
+ parent : PropertyPathNode ;
106
109
fullPath : ReactiveScopeDependency ;
107
110
}
108
111
| RootNode ;
109
112
110
- class Tree {
113
+ class PropertyPathRegistry {
111
114
roots : Map < IdentifierId , RootNode > = new Map ( ) ;
112
115
113
- getOrCreateRoot ( identifier : Identifier ) : PropertyLoadNode {
116
+ getOrCreateIdentifier ( identifier : Identifier ) : PropertyPathNode {
114
117
/**
115
118
* Reads from a statically scoped variable are always safe in JS,
116
119
* with the exception of TDZ (not addressed by this pass).
@@ -132,49 +135,61 @@ class Tree {
132
135
return rootNode ;
133
136
}
134
137
135
- static #getOrCreateProperty(
136
- node : PropertyLoadNode ,
137
- property : string ,
138
- ) : PropertyLoadNode {
139
- let child = node . properties . get ( property ) ;
138
+ static getOrCreatePropertyEntry (
139
+ parent : PropertyPathNode ,
140
+ entry : DependencyPathEntry ,
141
+ ) : PropertyPathNode {
142
+ if ( entry . optional ) {
143
+ CompilerError . throwTodo ( {
144
+ reason : 'handle optional nodes' ,
145
+ loc : GeneratedSource ,
146
+ } ) ;
147
+ }
148
+ let child = parent . properties . get ( entry . property ) ;
140
149
if ( child == null ) {
141
150
child = {
142
151
properties : new Map ( ) ,
143
- parent : node ,
152
+ parent : parent ,
144
153
fullPath : {
145
- identifier : node . fullPath . identifier ,
146
- path : node . fullPath . path . concat ( [ { property , optional : false } ] ) ,
154
+ identifier : parent . fullPath . identifier ,
155
+ path : parent . fullPath . path . concat ( entry ) ,
147
156
} ,
148
157
} ;
149
- node . properties . set ( property , child ) ;
158
+ parent . properties . set ( entry . property , child ) ;
150
159
}
151
160
return child ;
152
161
}
153
162
154
- getPropertyLoadNode ( n : ReactiveScopeDependency ) : PropertyLoadNode {
163
+ getOrCreateProperty ( n : ReactiveScopeDependency ) : PropertyPathNode {
155
164
/**
156
165
* We add ReactiveScopeDependencies according to instruction ordering,
157
166
* so all subpaths of a PropertyLoad should already exist
158
167
* (e.g. a.b is added before a.b.c),
159
168
*/
160
- let currNode = this . getOrCreateRoot ( n . identifier ) ;
169
+ let currNode = this . getOrCreateIdentifier ( n . identifier ) ;
161
170
if ( n . path . length === 0 ) {
162
171
return currNode ;
163
172
}
164
173
for ( let i = 0 ; i < n . path . length - 1 ; i ++ ) {
165
- currNode = assertNonNull ( currNode . properties . get ( n . path [ i ] . property ) ) ;
174
+ currNode = PropertyPathRegistry . getOrCreatePropertyEntry (
175
+ currNode ,
176
+ n . path [ i ] ,
177
+ ) ;
166
178
}
167
179
168
- return Tree . #getOrCreateProperty( currNode , n . path . at ( - 1 ) ! . property ) ;
180
+ return PropertyPathRegistry . getOrCreatePropertyEntry (
181
+ currNode ,
182
+ n . path . at ( - 1 ) ! ,
183
+ ) ;
169
184
}
170
185
}
171
186
172
- function pushPropertyLoadNode (
173
- loadSource : Identifier ,
174
- loadSourceNode : PropertyLoadNode ,
187
+ function addNonNullPropertyPath (
188
+ source : Identifier ,
189
+ sourceNode : PropertyPathNode ,
175
190
instrId : InstructionId ,
176
191
knownImmutableIdentifiers : Set < IdentifierId > ,
177
- result : Set < PropertyLoadNode > ,
192
+ result : Set < PropertyPathNode > ,
178
193
) : void {
179
194
/**
180
195
* Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
@@ -187,26 +202,22 @@ function pushPropertyLoadNode(
187
202
* See comment at top of function for why we track known immutable identifiers.
188
203
*/
189
204
const isMutableAtInstr =
190
- loadSource . mutableRange . end > loadSource . mutableRange . start + 1 &&
191
- loadSource . scope != null &&
192
- inRange ( { id : instrId } , loadSource . scope . range ) ;
205
+ source . mutableRange . end > source . mutableRange . start + 1 &&
206
+ source . scope != null &&
207
+ inRange ( { id : instrId } , source . scope . range ) ;
193
208
if (
194
209
! isMutableAtInstr ||
195
- knownImmutableIdentifiers . has ( loadSourceNode . fullPath . identifier . id )
210
+ knownImmutableIdentifiers . has ( sourceNode . fullPath . identifier . id )
196
211
) {
197
- let curr : PropertyLoadNode | null = loadSourceNode ;
198
- while ( curr != null ) {
199
- result . add ( curr ) ;
200
- curr = curr . parent ;
201
- }
212
+ result . add ( sourceNode ) ;
202
213
}
203
214
}
204
215
205
216
function collectNonNullsInBlocks (
206
217
fn : HIRFunction ,
207
218
temporaries : ReadonlyMap < IdentifierId , ReactiveScopeDependency > ,
219
+ registry : PropertyPathRegistry ,
208
220
) : ReadonlyMap < BlockId , BlockInfo > {
209
- const tree = new Tree ( ) ;
210
221
/**
211
222
* Due to current limitations of mutable range inference, there are edge cases in
212
223
* which we infer known-immutable values (e.g. props or hook params) to have a
@@ -227,18 +238,18 @@ function collectNonNullsInBlocks(
227
238
* Known non-null objects such as functional component props can be safely
228
239
* read from any block.
229
240
*/
230
- const knownNonNullIdentifiers = new Set < PropertyLoadNode > ( ) ;
241
+ const knownNonNullIdentifiers = new Set < PropertyPathNode > ( ) ;
231
242
if (
232
243
fn . fnType === 'Component' &&
233
244
fn . params . length > 0 &&
234
245
fn . params [ 0 ] . kind === 'Identifier'
235
246
) {
236
247
const identifier = fn . params [ 0 ] . identifier ;
237
- knownNonNullIdentifiers . add ( tree . getOrCreateRoot ( identifier ) ) ;
248
+ knownNonNullIdentifiers . add ( registry . getOrCreateIdentifier ( identifier ) ) ;
238
249
}
239
250
const nodes = new Map < BlockId , BlockInfo > ( ) ;
240
251
for ( const [ _ , block ] of fn . body . blocks ) {
241
- const assumedNonNullObjects = new Set < PropertyLoadNode > (
252
+ const assumedNonNullObjects = new Set < PropertyPathNode > (
242
253
knownNonNullIdentifiers ,
243
254
) ;
244
255
for ( const instr of block . instructions ) {
@@ -247,9 +258,9 @@ function collectNonNullsInBlocks(
247
258
identifier : instr . value . object . identifier ,
248
259
path : [ ] ,
249
260
} ;
250
- pushPropertyLoadNode (
261
+ addNonNullPropertyPath (
251
262
instr . value . object . identifier ,
252
- tree . getPropertyLoadNode ( source ) ,
263
+ registry . getOrCreateProperty ( source ) ,
253
264
instr . id ,
254
265
knownImmutableIdentifiers ,
255
266
assumedNonNullObjects ,
@@ -258,9 +269,9 @@ function collectNonNullsInBlocks(
258
269
const source = instr . value . value . identifier . id ;
259
270
const sourceNode = temporaries . get ( source ) ;
260
271
if ( sourceNode != null ) {
261
- pushPropertyLoadNode (
272
+ addNonNullPropertyPath (
262
273
instr . value . value . identifier ,
263
- tree . getPropertyLoadNode ( sourceNode ) ,
274
+ registry . getOrCreateProperty ( sourceNode ) ,
264
275
instr . id ,
265
276
knownImmutableIdentifiers ,
266
277
assumedNonNullObjects ,
@@ -270,9 +281,9 @@ function collectNonNullsInBlocks(
270
281
const source = instr . value . object . identifier . id ;
271
282
const sourceNode = temporaries . get ( source ) ;
272
283
if ( sourceNode != null ) {
273
- pushPropertyLoadNode (
284
+ addNonNullPropertyPath (
274
285
instr . value . object . identifier ,
275
- tree . getPropertyLoadNode ( sourceNode ) ,
286
+ registry . getOrCreateProperty ( sourceNode ) ,
276
287
instr . id ,
277
288
knownImmutableIdentifiers ,
278
289
assumedNonNullObjects ,
@@ -314,7 +325,6 @@ function propagateNonNull(
314
325
nodeId : BlockId ,
315
326
direction : 'forward' | 'backward' ,
316
327
traversalState : Map < BlockId , 'active' | 'done' > ,
317
- nonNullObjectsByBlock : Map < BlockId , ReadonlySet < PropertyLoadNode > > ,
318
328
) : boolean {
319
329
/**
320
330
* Avoid re-visiting computed or currently active nodes, which can
@@ -345,7 +355,6 @@ function propagateNonNull(
345
355
pred ,
346
356
direction ,
347
357
traversalState ,
348
- nonNullObjectsByBlock ,
349
358
) ;
350
359
changed ||= neighborChanged ;
351
360
}
@@ -374,38 +383,36 @@ function propagateNonNull(
374
383
const neighborAccesses = Set_intersect (
375
384
Array . from ( neighbors )
376
385
. filter ( n => traversalState . get ( n ) === 'done' )
377
- . map ( n => assertNonNull ( nonNullObjectsByBlock . get ( n ) ) ) ,
386
+ . map ( n => assertNonNull ( nodes . get ( n ) ) . assumedNonNullObjects ) ,
378
387
) ;
379
388
380
- const prevObjects = assertNonNull ( nonNullObjectsByBlock . get ( nodeId ) ) ;
381
- const newObjects = Set_union ( prevObjects , neighborAccesses ) ;
389
+ const prevObjects = assertNonNull ( nodes . get ( nodeId ) ) . assumedNonNullObjects ;
390
+ const mergedObjects = Set_union ( prevObjects , neighborAccesses ) ;
382
391
383
- nonNullObjectsByBlock . set ( nodeId , newObjects ) ;
392
+ assertNonNull ( nodes . get ( nodeId ) ) . assumedNonNullObjects = mergedObjects ;
384
393
traversalState . set ( nodeId , 'done' ) ;
385
- changed ||= prevObjects . size !== newObjects . size ;
394
+ changed ||= prevObjects . size !== mergedObjects . size ;
386
395
return changed ;
387
396
}
388
- const fromEntry = new Map < BlockId , ReadonlySet < PropertyLoadNode > > ( ) ;
389
- const fromExit = new Map < BlockId , ReadonlySet < PropertyLoadNode > > ( ) ;
390
- for ( const [ blockId , blockInfo ] of nodes ) {
391
- fromEntry . set ( blockId , blockInfo . assumedNonNullObjects ) ;
392
- fromExit . set ( blockId , blockInfo . assumedNonNullObjects ) ;
393
- }
394
397
const traversalState = new Map < BlockId , 'done' | 'active' > ( ) ;
395
398
const reversedBlocks = [ ...fn . body . blocks ] ;
396
399
reversedBlocks . reverse ( ) ;
397
400
398
- let i = 0 ;
399
401
let changed ;
402
+ let i = 0 ;
400
403
do {
401
- i ++ ;
404
+ CompilerError . invariant ( i ++ < 100 , {
405
+ reason :
406
+ '[CollectHoistablePropertyLoads] fixed point iteration did not terminate after 100 loops' ,
407
+ loc : GeneratedSource ,
408
+ } ) ;
409
+
402
410
changed = false ;
403
411
for ( const [ blockId ] of fn . body . blocks ) {
404
412
const forwardChanged = recursivelyPropagateNonNull (
405
413
blockId ,
406
414
'forward' ,
407
415
traversalState ,
408
- fromEntry ,
409
416
) ;
410
417
changed ||= forwardChanged ;
411
418
}
@@ -415,43 +422,14 @@ function propagateNonNull(
415
422
blockId ,
416
423
'backward' ,
417
424
traversalState ,
418
- fromExit ,
419
425
) ;
420
426
changed ||= backwardChanged ;
421
427
}
422
428
traversalState . clear ( ) ;
423
429
} while ( changed ) ;
424
-
425
- /**
426
- * TODO: validate against meta internal code, then remove in future PR.
427
- * Currently cannot come up with a case that requires fixed-point iteration.
428
- */
429
- CompilerError . invariant ( i <= 2 , {
430
- reason : 'require fixed-point iteration' ,
431
- description : `#iterations = ${ i } ` ,
432
- loc : GeneratedSource ,
433
- } ) ;
434
-
435
- CompilerError . invariant (
436
- fromEntry . size === fromExit . size && fromEntry . size === nodes . size ,
437
- {
438
- reason :
439
- 'bad sizes after calculating fromEntry + fromExit ' +
440
- `${ fromEntry . size } ${ fromExit . size } ${ nodes . size } ` ,
441
- loc : GeneratedSource ,
442
- } ,
443
- ) ;
444
-
445
- for ( const [ id , node ] of nodes ) {
446
- const assumedNonNullObjects = Set_union (
447
- assertNonNull ( fromEntry . get ( id ) ) ,
448
- assertNonNull ( fromExit . get ( id ) ) ,
449
- ) ;
450
- node . assumedNonNullObjects = assumedNonNullObjects ;
451
- }
452
430
}
453
431
454
- function assertNonNull < T extends NonNullable < U > , U > (
432
+ export function assertNonNull < T extends NonNullable < U > , U > (
455
433
value : T | null | undefined ,
456
434
source ?: string ,
457
435
) : T {
0 commit comments