8
HIRFunction,
9
Identifier,
10
IdentifierId,
11
+ InstructionId,
12
Place,
13
ReactiveScopeDependency,
14
ScopeId,
67
fn: HIRFunction,
68
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
69
): ReadonlyMap<ScopeId, BlockInfo> {
69
- const nodes = collectPropertyLoadsInBlocks(fn, temporaries);
70
+ const nodes = collectNonNullsInBlocks(fn, temporaries);
71
propagateNonNull(fn, nodes);
72
73
const nodesKeyedByScopeId = new Map<ScopeId, BlockInfo>();
166
class Tree {
167
roots: Map<Identifier, RootNode> = new Map();
168
168
- #getOrCreateRoot(identifier: Identifier): PropertyLoadNode {
169
+ getOrCreateRoot(identifier: Identifier): PropertyLoadNode {
170
/**
171
* Reads from a statically scoped variable are always safe in JS,
172
* with the exception of TDZ (not addressed by this pass).
208
}
209
210
getPropertyLoadNode(n: ReactiveScopeDependency): PropertyLoadNode {
210
- CompilerError.invariant(n.path.length > 0, {
211
- reason:
212
- '[CollectHoistablePropertyLoads] Expected property node, found root node',
213
- loc: GeneratedSource,
214
- });
211
/**
212
* We add ReactiveScopeDependencies according to instruction ordering,
213
* so all subpaths of a PropertyLoad should already exist
214
* (e.g. a.b is added before a.b.c),
215
*/
220
- let currNode = this.#getOrCreateRoot(n.identifier);
216
+ let currNode = this.getOrCreateRoot(n.identifier);
217
+ if (n.path.length === 0) {
218
+ return currNode;
219
+ }
220
for (let i = 0; i < n.path.length - 1; i++) {
221
currNode = assertNonNull(currNode.properties.get(n.path[i].property));
222
}
225
}
226
}
227
229
-function collectPropertyLoadsInBlocks(
228
+function pushPropertyLoadNode(
229
+ loadSource: Identifier,
230
+ loadSourceNode: PropertyLoadNode,
231
+ instrId: InstructionId,
232
+ knownImmutableIdentifiers: Set<IdentifierId>,
233
+ result: Set<PropertyLoadNode>,
234
+): void {
235
+ /**
236
+ * Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
237
+ * are not valid with respect to current instruction id numbering.
238
+ * We use attached reactive scope ranges as a proxy for mutable range, but this
239
+ * is an overestimate as (1) scope ranges merge and align to form valid program
240
+ * blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to
241
+ * non-mutable identifiers.
242
+ *
243
+ * See comment at top of function for why we track known immutable identifiers.
244
+ */
245
+ const isMutableAtInstr =
246
+ loadSource.mutableRange.end > loadSource.mutableRange.start + 1 &&
247
+ loadSource.scope != null &&
248
+ inRange({id: instrId}, loadSource.scope.range);
249
+ if (
250
+ !isMutableAtInstr ||
251
+ knownImmutableIdentifiers.has(loadSourceNode.fullPath.identifier.id)
252
+ ) {
253
+ let curr: PropertyLoadNode | null = loadSourceNode;
254
+ while (curr != null) {
255
+ result.add(curr);
256
+ curr = curr.parent;
257
+ }
258
+ }
259
+}
260
+
261
+function collectNonNullsInBlocks(
262
fn: HIRFunction,
263
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
264
): ReadonlyMap<BlockId, BlockInfo> {
265
+ const tree = new Tree();
266
/**
267
* Due to current limitations of mutable range inference, there are edge cases in
268
* which we infer known-immutable values (e.g. props or hook params) to have a
271
* We track known immutable identifiers to reduce regressions (as PropagateScopeDeps
272
* is being rewritten to HIR).
273
*/
241
- const knownImmutableIdentifiers = new Set<Identifier>();
274
+ const knownImmutableIdentifiers = new Set<IdentifierId>();
275
if (fn.fnType === 'Component' || fn.fnType === 'Hook') {
276
for (const p of fn.params) {
277
if (p.kind === 'Identifier') {
245
- knownImmutableIdentifiers.add(p.identifier);
278
+ knownImmutableIdentifiers.add(p.identifier.id);
279
}
280
}
281
}
249
- const tree = new Tree();
282
+ /**
283
+ * Known non-null objects such as functional component props can be safely
284
+ * read from any block.
285
+ */
286
+ const knownNonNullIdentifiers = new Set<PropertyLoadNode>();
287
+ if (
288
+ fn.fnType === 'Component' &&
289
+ fn.params.length > 0 &&
290
+ fn.params[0].kind === 'Identifier'
291
+ ) {
292
+ const identifier = fn.params[0].identifier;
293
+ knownNonNullIdentifiers.add(tree.getOrCreateRoot(identifier));
294
+ }
295
const nodes = new Map<BlockId, BlockInfo>();
296
for (const [_, block] of fn.body.blocks) {
252
- const assumedNonNullObjects = new Set<PropertyLoadNode>();
297
+ const assumedNonNullObjects = new Set<PropertyLoadNode>(
298
+ knownNonNullIdentifiers,
299
+ );
300
for (const instr of block.instructions) {
301
if (instr.value.kind === 'PropertyLoad') {
255
- const property = getProperty(
256
- instr.value.object,
257
- instr.value.property,
258
- temporaries,
302
+ const source = temporaries.get(instr.value.object.identifier.id) ?? {
303
+ identifier: instr.value.object.identifier,
304
+ path: [],
305
+ };
306
+ pushPropertyLoadNode(
307
+ instr.value.object.identifier,
308
+ tree.getPropertyLoadNode(source),
309
+ instr.id,
310
+ knownImmutableIdentifiers,
311
+ assumedNonNullObjects,
312
);
260
- const propertyNode = tree.getPropertyLoadNode(property);
261
- const object = instr.value.object.identifier;
262
- /**
263
- * Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
264
- * are not valid with respect to current instruction id numbering.
265
- * We use attached reactive scope ranges as a proxy for mutable range, but this
266
- * is an overestimate as (1) scope ranges merge and align to form valid program
267
- * blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to
268
- * non-mutable identifiers.
269
- *
270
- * See comment at top of function for why we track known immutable identifiers.
271
- */
272
- const isMutableAtInstr =
273
- object.mutableRange.end > object.mutableRange.start + 1 &&
274
- object.scope != null &&
275
- inRange(instr, object.scope.range);
276
- if (
277
- !isMutableAtInstr ||
278
- knownImmutableIdentifiers.has(propertyNode.fullPath.identifier)
279
- ) {
280
- let curr = propertyNode.parent;
281
- while (curr != null) {
282
- assumedNonNullObjects.add(curr);
283
- curr = curr.parent;
284
- }
313
+ } else if (instr.value.kind === 'Destructure') {
314
+ const source = instr.value.value.identifier.id;
315
+ const sourceNode = temporaries.get(source);
316
+ if (sourceNode != null) {
317
+ pushPropertyLoadNode(
318
+ instr.value.value.identifier,
319
+ tree.getPropertyLoadNode(sourceNode),
320
+ instr.id,
321
+ knownImmutableIdentifiers,
322
+ assumedNonNullObjects,
323
+ );
324
+ }
325
+ } else if (instr.value.kind === 'ComputedLoad') {
326
+ const source = instr.value.object.identifier.id;
327
+ const sourceNode = temporaries.get(source);
328
+ if (sourceNode != null) {
329
+ pushPropertyLoadNode(
330
+ instr.value.object.identifier,
331
+ tree.getPropertyLoadNode(sourceNode),
332
+ instr.id,
333
+ knownImmutableIdentifiers,
334
+ assumedNonNullObjects,
335
+ );
336
}
337
}
287
- // TODO handle destructuring
338
}
339
340
nodes.set(block.id, {
499
);
500
501
for (const [id, node] of nodes) {
452
- node.assumedNonNullObjects = Set_union(
502
+ const assumedNonNullObjects = Set_union(
503
assertNonNull(fromEntry.get(id)),
504
assertNonNull(fromExit.get(id)),
505
);
506
+ node.assumedNonNullObjects = assumedNonNullObjects;
507
}
508
}
509