13
Identifier,
14
IdentifierId,
15
InstructionId,
16
+ isJsxType,
17
makeInstructionId,
18
+ ValueKind,
19
+ ValueReason,
20
Place,
21
} from '../HIR/HIR';
22
import {
25
eachTerminalOperand,
26
} from '../HIR/visitors';
27
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
25
-import {MutationKind} from './InferFunctionExpressionAliasingEffectsSignature';
26
-import {Result} from '../Utils/Result';
28
+import {Err, Ok, Result} from '../Utils/Result';
29
+import {AliasingEffect} from './AliasingEffects';
30
31
/**
29
- * Infers mutable ranges for all values in the program, using previously inferred
30
- * mutation/aliasing effects. This pass builds a data flow graph using the effects,
31
- * tracking an abstract notion of "when" each effect occurs relative to the others.
32
- * It then walks each mutation effect against the graph, updating the range of each
33
- * node that would be reachable at the "time" that the effect occurred.
32
+ * This pass builds an abstract model of the heap and interprets the effects of the
33
+ * given function in order to determine the following:
34
+ * - The mutable ranges of all identifiers in the function
35
+ * - The externally-visible effects of the function, such as mutations of params and
36
+ * context-vars, aliasing between params/context-vars/return-value, and impure side
37
+ * effects.
38
+ * - The legacy `Effect` to store on each Place.
39
+ *
40
+ * This pass builds a data flow graph using the effects, tracking an abstract notion
41
+ * of "when" each effect occurs relative to the others. It then walks each mutation
42
+ * effect against the graph, updating the range of each node that would be reachable
43
+ * at the "time" that the effect occurred.
44
*
45
* This pass also validates against invalid effects: any function that is reachable
46
* by being called, or via a Render effect, is validated against mutating globals
47
* or calling impure code.
48
*
49
* Note that this function also populates the outer function's aliasing effects with
40
- * any mutations that apply to its params or context variables. For example, a
41
- * function expression such as the following:
50
+ * any mutations that apply to its params or context variables.
51
+ *
52
+ * ## Example
53
+ * A function expression such as the following:
54
*
55
* ```
56
* (x) => { x.y = true }
57
* ```
58
*
59
* Would populate a `Mutate x` aliasing effect on the outer function.
60
+ *
61
+ * ## Returned Function Effects
62
+ *
63
+ * The function returns (if successful) a list of externally-visible effects.
64
+ * This is determined by simulating a conditional, transitive mutation against
65
+ * each param, context variable, and return value in turn, and seeing which other
66
+ * such values are affected. If they're affected, they must be captured, so we
67
+ * record a Capture.
68
+ *
69
+ * The only tricky bit is the return value, which could _alias_ (or even assign)
70
+ * one or more of the params/context-vars rather than just capturing. So we have
71
+ * to do a bit more tracking for returns.
72
*/
73
export function inferMutationAliasingRanges(
74
fn: HIRFunction,
75
{isFunctionExpression}: {isFunctionExpression: boolean},
52
-): Result<void, CompilerError> {
76
+): Result<Array<AliasingEffect>, CompilerError> {
77
+ // The set of externally-visible effects
78
+ const functionEffects: Array<AliasingEffect> = [];
79
+
80
/**
81
* Part 1: Infer mutable ranges for values. We build an abstract model of
82
* values, the alias/capture edges between them, and the set of mutations.
195
effect.kind === 'Impure'
196
) {
197
errors.push(effect.error);
198
+ functionEffects.push(effect);
199
} else if (effect.kind === 'Render') {
200
renders.push({index: index++, place: effect.place});
201
+ functionEffects.push(effect);
202
}
203
}
204
}
244
for (const render of renders) {
245
state.render(render.index, render.place.identifier, errors);
246
}
218
- fn.aliasingEffects ??= [];
247
for (const param of [...fn.context, ...fn.params]) {
248
const place = param.kind === 'Identifier' ? param : param.place;
249
const node = state.nodes.get(place.identifier);
254
if (node.local != null) {
255
if (node.local.kind === MutationKind.Conditional) {
256
mutated = true;
229
- fn.aliasingEffects.push({
257
+ functionEffects.push({
258
kind: 'MutateConditionally',
259
value: {...place, loc: node.local.loc},
260
});
261
} else if (node.local.kind === MutationKind.Definite) {
262
mutated = true;
235
- fn.aliasingEffects.push({
263
+ functionEffects.push({
264
kind: 'Mutate',
265
value: {...place, loc: node.local.loc},
266
});
269
if (node.transitive != null) {
270
if (node.transitive.kind === MutationKind.Conditional) {
271
mutated = true;
244
- fn.aliasingEffects.push({
272
+ functionEffects.push({
273
kind: 'MutateTransitiveConditionally',
274
value: {...place, loc: node.transitive.loc},
275
});
276
} else if (node.transitive.kind === MutationKind.Definite) {
277
mutated = true;
250
- fn.aliasingEffects.push({
278
+ functionEffects.push({
279
kind: 'MutateTransitive',
280
value: {...place, loc: node.transitive.loc},
281
});
464
}
465
}
466
439
- return errors.asResult();
467
+ /**
468
+ * Part 3
469
+ * Finish populating the externally visible effects. Above we bubble-up the side effects
470
+ * (MutateFrozen/MutableGlobal/Impure/Render) as well as mutations of context variables.
471
+ * Here we populate an effect to create the return value as well as populating alias/capture
472
+ * effects for how data flows between the params, context vars, and return.
473
+ */
474
+ functionEffects.push({
475
+ kind: 'Create',
476
+ into: fn.returns,
477
+ value:
478
+ fn.returnType.kind === 'Primitive'
479
+ ? ValueKind.Primitive
480
+ : isJsxType(fn.returnType)
481
+ ? ValueKind.Frozen
482
+ : ValueKind.Mutable,
483
+ reason: ValueReason.KnownReturnSignature,
484
+ });
485
+ /**
486
+ * Determine precise data-flow effects by simulating transitive mutations of the params/
487
+ * captures and seeing what other params/context variables are affected. Anything that
488
+ * would be transitively mutated needs a capture relationship.
489
+ */
490
+ const tracked: Array<Place> = [];
491
+ const ignoredErrors = new CompilerError();
492
+ for (const param of [...fn.params, ...fn.context, fn.returns]) {
493
+ const place = param.kind === 'Identifier' ? param : param.place;
494
+ tracked.push(place);
495
+ }
496
+ for (const into of tracked) {
497
+ const mutationIndex = index++;
498
+ state.mutate(
499
+ mutationIndex,
500
+ into.identifier,
501
+ null,
502
+ true,
503
+ MutationKind.Conditional,
504
+ into.loc,
505
+ ignoredErrors,
506
+ );
507
+ for (const from of tracked) {
508
+ if (
509
+ from.identifier.id === into.identifier.id ||
510
+ from.identifier.id === fn.returns.identifier.id
511
+ ) {
512
+ continue;
513
+ }
514
+ const fromNode = state.nodes.get(from.identifier);
515
+ CompilerError.invariant(fromNode != null, {
516
+ reason: `Expected a node to exist for all parameters and context variables`,
517
+ loc: into.loc,
518
+ });
519
+ if (fromNode.lastMutated === mutationIndex) {
520
+ if (into.identifier.id === fn.returns.identifier.id) {
521
+ // The return value could be any of the params/context variables
522
+ functionEffects.push({
523
+ kind: 'Alias',
524
+ from,
525
+ into,
526
+ });
527
+ } else {
528
+ // Otherwise params/context-vars can only capture each other
529
+ functionEffects.push({
530
+ kind: 'Capture',
531
+ from,
532
+ into,
533
+ });
534
+ }
535
+ }
536
+ }
537
+ }
538
+
539
+ if (errors.hasErrors() && !isFunctionExpression) {
540
+ return Err(errors);
541
+ }
542
+ return Ok(functionEffects);
543
}
544
545
function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
555
}
556
}
557
558
+export enum MutationKind {
559
+ None = 0,
560
+ Conditional = 1,
561
+ Definite = 2,
562
+}
563
+
564
type Node = {
565
id: Identifier;
566
createdFrom: Map<Identifier, number>;
569
edges: Array<{index: number; node: Identifier; kind: 'capture' | 'alias'}>;
570
transitive: {kind: MutationKind; loc: SourceLocation} | null;
571
local: {kind: MutationKind; loc: SourceLocation} | null;
572
+ lastMutated: number;
573
value:
574
| {kind: 'Object'}
575
| {kind: 'Phi'}
587
edges: [],
588
transitive: null,
589
local: null,
590
+ lastMutated: 0,
591
value,
592
});
593
}
669
mutate(
670
index: number,
671
start: Identifier,
561
- end: InstructionId,
672
+ // Null is used for simulated mutations
673
+ end: InstructionId | null,
674
transitive: boolean,
675
kind: MutationKind,
676
loc: SourceLocation,
692
if (node == null) {
693
continue;
694
}
583
- node.id.mutableRange.end = makeInstructionId(
584
- Math.max(node.id.mutableRange.end, end),
585
- );
695
+ node.lastMutated = Math.max(node.lastMutated, index);
696
+ if (end != null) {
697
+ node.id.mutableRange.end = makeInstructionId(
698
+ Math.max(node.id.mutableRange.end, end),
699
+ );
700
+ }
701
if (
702
node.value.kind === 'Function' &&
703
node.transitive == null &&