Skip to content

Commit d58c07b

Browse files
committed
[compiler] New mutability/aliasing model
Squashed, review-friendly version of the stack from #33488. This is new version of our mutability and inference model, designed to replace the core algorithm for determining the sets of instructions involved in constructing a given value or set of values. The new model replaces InferReferenceEffects, InferMutableRanges (and all of its subcomponents), and parts of AnalyzeFunctions. The new model does not use per-Place effect values, but in order to make this drop-in the end _result_ of the inference adds these per-Place effects. I'll write up a larger document on the model, first i'm doing some housekeeping to rebase the PR.
1 parent 631b1cf commit d58c07b

File tree

119 files changed

+7247
-343
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

119 files changed

+7247
-343
lines changed

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureF
104104
import {CompilerError} from '..';
105105
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
106106
import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
107+
import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
108+
import {inferMutationAliasingRanges} from '../Inference/InferMutationAliasingRanges';
107109

108110
export type CompilerPipelineValue =
109111
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -227,15 +229,27 @@ function runWithEnvironment(
227229
analyseFunctions(hir);
228230
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
229231

230-
const fnEffectErrors = inferReferenceEffects(hir);
231-
if (env.isInferredMemoEnabled) {
232-
if (fnEffectErrors.length > 0) {
233-
CompilerError.throw(fnEffectErrors[0]);
232+
if (!env.config.enableNewMutationAliasingModel) {
233+
const fnEffectErrors = inferReferenceEffects(hir);
234+
if (env.isInferredMemoEnabled) {
235+
if (fnEffectErrors.length > 0) {
236+
CompilerError.throw(fnEffectErrors[0]);
237+
}
238+
}
239+
log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
240+
} else {
241+
const mutabilityAliasingErrors = inferMutationAliasingEffects(hir);
242+
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
243+
if (env.isInferredMemoEnabled) {
244+
if (mutabilityAliasingErrors.isErr()) {
245+
throw mutabilityAliasingErrors.unwrapErr();
246+
}
234247
}
235248
}
236-
log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
237249

238-
validateLocalsNotReassignedAfterRender(hir);
250+
if (!env.config.enableNewMutationAliasingModel) {
251+
validateLocalsNotReassignedAfterRender(hir);
252+
}
239253

240254
// Note: Has to come after infer reference effects because "dead" code may still affect inference
241255
deadCodeElimination(hir);
@@ -249,8 +263,21 @@ function runWithEnvironment(
249263
pruneMaybeThrows(hir);
250264
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
251265

252-
inferMutableRanges(hir);
253-
log({kind: 'hir', name: 'InferMutableRanges', value: hir});
266+
if (!env.config.enableNewMutationAliasingModel) {
267+
inferMutableRanges(hir);
268+
log({kind: 'hir', name: 'InferMutableRanges', value: hir});
269+
} else {
270+
const mutabilityAliasingErrors = inferMutationAliasingRanges(hir, {
271+
isFunctionExpression: false,
272+
});
273+
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
274+
if (env.isInferredMemoEnabled) {
275+
if (mutabilityAliasingErrors.isErr()) {
276+
throw mutabilityAliasingErrors.unwrapErr();
277+
}
278+
validateLocalsNotReassignedAfterRender(hir);
279+
}
280+
}
254281

255282
if (env.isInferredMemoEnabled) {
256283
if (env.config.assertValidMutableRanges) {
@@ -277,7 +304,10 @@ function runWithEnvironment(
277304
validateNoImpureFunctionsInRender(hir).unwrap();
278305
}
279306

280-
if (env.config.validateNoFreezingKnownMutableFunctions) {
307+
if (
308+
env.config.validateNoFreezingKnownMutableFunctions ||
309+
env.config.enableNewMutationAliasingModel
310+
) {
281311
validateNoFreezingKnownMutableFunctions(hir).unwrap();
282312
}
283313
}

compiler/packages/babel-plugin-react-compiler/src/HIR/AssertValidMutableRanges.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import invariant from 'invariant';
9-
import {HIRFunction, Identifier, MutableRange} from './HIR';
8+
import {HIRFunction, MutableRange, Place} from './HIR';
109
import {
1110
eachInstructionLValue,
1211
eachInstructionOperand,
1312
eachTerminalOperand,
1413
} from './visitors';
14+
import {CompilerError} from '..';
15+
import {printPlace} from './PrintHIR';
1516

1617
/*
1718
* Checks that all mutable ranges in the function are well-formed, with
@@ -20,38 +21,43 @@ import {
2021
export function assertValidMutableRanges(fn: HIRFunction): void {
2122
for (const [, block] of fn.body.blocks) {
2223
for (const phi of block.phis) {
23-
visitIdentifier(phi.place.identifier);
24-
for (const [, operand] of phi.operands) {
25-
visitIdentifier(operand.identifier);
24+
visit(phi.place, `phi for block bb${block.id}`);
25+
for (const [pred, operand] of phi.operands) {
26+
visit(operand, `phi predecessor bb${pred} for block bb${block.id}`);
2627
}
2728
}
2829
for (const instr of block.instructions) {
2930
for (const operand of eachInstructionLValue(instr)) {
30-
visitIdentifier(operand.identifier);
31+
visit(operand, `instruction [${instr.id}]`);
3132
}
3233
for (const operand of eachInstructionOperand(instr)) {
33-
visitIdentifier(operand.identifier);
34+
visit(operand, `instruction [${instr.id}]`);
3435
}
3536
}
3637
for (const operand of eachTerminalOperand(block.terminal)) {
37-
visitIdentifier(operand.identifier);
38+
visit(operand, `terminal [${block.terminal.id}]`);
3839
}
3940
}
4041
}
4142

42-
function visitIdentifier(identifier: Identifier): void {
43-
validateMutableRange(identifier.mutableRange);
44-
if (identifier.scope !== null) {
45-
validateMutableRange(identifier.scope.range);
43+
function visit(place: Place, description: string): void {
44+
validateMutableRange(place, place.identifier.mutableRange, description);
45+
if (place.identifier.scope !== null) {
46+
validateMutableRange(place, place.identifier.scope.range, description);
4647
}
4748
}
4849

49-
function validateMutableRange(mutableRange: MutableRange): void {
50-
invariant(
51-
(mutableRange.start === 0 && mutableRange.end === 0) ||
52-
mutableRange.end > mutableRange.start,
53-
'Identifier scope mutableRange was invalid: [%s:%s]',
54-
mutableRange.start,
55-
mutableRange.end,
50+
function validateMutableRange(
51+
place: Place,
52+
range: MutableRange,
53+
description: string,
54+
): void {
55+
CompilerError.invariant(
56+
(range.start === 0 && range.end === 0) || range.end > range.start,
57+
{
58+
reason: `Invalid mutable range: [${range.start}:${range.end}]`,
59+
description: `${printPlace(place)} in ${description}`,
60+
loc: place.loc,
61+
},
5662
);
5763
}

compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import {
4747
makeType,
4848
promoteTemporary,
4949
} from './HIR';
50-
import HIRBuilder, {Bindings} from './HIRBuilder';
50+
import HIRBuilder, {Bindings, createTemporaryPlace} from './HIRBuilder';
5151
import {BuiltInArrayId} from './ObjectShape';
5252

5353
/*
@@ -181,6 +181,7 @@ export function lower(
181181
loc: GeneratedSource,
182182
value: lowerExpressionToTemporary(builder, body),
183183
id: makeInstructionId(0),
184+
effects: null,
184185
};
185186
builder.terminateWithContinuation(terminal, fallthrough);
186187
} else if (body.isBlockStatement()) {
@@ -210,6 +211,7 @@ export function lower(
210211
loc: GeneratedSource,
211212
}),
212213
id: makeInstructionId(0),
214+
effects: null,
213215
},
214216
null,
215217
);
@@ -220,13 +222,15 @@ export function lower(
220222
fnType: bindings == null ? env.fnType : 'Other',
221223
returnTypeAnnotation: null, // TODO: extract the actual return type node if present
222224
returnType: makeType(),
225+
returns: createTemporaryPlace(env, func.node.loc ?? GeneratedSource),
223226
body: builder.build(),
224227
context,
225228
generator: func.node.generator === true,
226229
async: func.node.async === true,
227230
loc: func.node.loc ?? GeneratedSource,
228231
env,
229232
effects: null,
233+
aliasingEffects: null,
230234
directives,
231235
});
232236
}
@@ -287,6 +291,7 @@ function lowerStatement(
287291
loc: stmt.node.loc ?? GeneratedSource,
288292
value,
289293
id: makeInstructionId(0),
294+
effects: null,
290295
};
291296
builder.terminate(terminal, 'block');
292297
return;
@@ -1237,6 +1242,7 @@ function lowerStatement(
12371242
kind: 'Debugger',
12381243
loc,
12391244
},
1245+
effects: null,
12401246
loc,
12411247
});
12421248
return;
@@ -1894,6 +1900,7 @@ function lowerExpression(
18941900
place: leftValue,
18951901
loc: exprLoc,
18961902
},
1903+
effects: null,
18971904
loc: exprLoc,
18981905
});
18991906
builder.terminateWithContinuation(
@@ -2829,6 +2836,7 @@ function lowerOptionalCallExpression(
28292836
args,
28302837
loc,
28312838
},
2839+
effects: null,
28322840
loc,
28332841
});
28342842
} else {
@@ -2842,6 +2850,7 @@ function lowerOptionalCallExpression(
28422850
args,
28432851
loc,
28442852
},
2853+
effects: null,
28452854
loc,
28462855
});
28472856
}
@@ -3465,9 +3474,10 @@ export function lowerValueToTemporary(
34653474
const place: Place = buildTemporaryPlace(builder, value.loc);
34663475
builder.push({
34673476
id: makeInstructionId(0),
3477+
lvalue: {...place},
34683478
value: value,
3479+
effects: null,
34693480
loc: value.loc,
3470-
lvalue: {...place},
34713481
});
34723482
return place;
34733483
}

compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ export const EnvironmentConfigSchema = z.object({
243243
*/
244244
enableUseTypeAnnotations: z.boolean().default(false),
245245

246+
/**
247+
* Enable a new model for mutability and aliasing inference
248+
*/
249+
enableNewMutationAliasingModel: z.boolean().default(false),
250+
246251
/**
247252
* Enables inference of optional dependency chains. Without this flag
248253
* a property chain such as `props?.items?.foo` will infer as a dep on

compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {Effect, ValueKind, ValueReason} from './HIR';
8+
import {Effect, makeIdentifierId, ValueKind, ValueReason} from './HIR';
99
import {
1010
BUILTIN_SHAPES,
1111
BuiltInArrayId,
@@ -34,6 +34,7 @@ import {
3434
addFunction,
3535
addHook,
3636
addObject,
37+
signatureArgument,
3738
} from './ObjectShape';
3839
import {BuiltInType, ObjectType, PolyType} from './Types';
3940
import {TypeConfig} from './TypeSchema';
@@ -644,6 +645,41 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
644645
calleeEffect: Effect.Read,
645646
hookKind: 'useEffect',
646647
returnValueKind: ValueKind.Frozen,
648+
aliasing: {
649+
receiver: makeIdentifierId(0),
650+
params: [],
651+
rest: makeIdentifierId(1),
652+
returns: makeIdentifierId(2),
653+
temporaries: [signatureArgument(3)],
654+
effects: [
655+
// Freezes the function and deps
656+
{
657+
kind: 'Freeze',
658+
value: signatureArgument(1),
659+
reason: ValueReason.Effect,
660+
},
661+
// Internally creates an effect object that captures the function and deps
662+
{
663+
kind: 'Create',
664+
into: signatureArgument(3),
665+
value: ValueKind.Frozen,
666+
reason: ValueReason.KnownReturnSignature,
667+
},
668+
// The effect stores the function and dependencies
669+
{
670+
kind: 'Capture',
671+
from: signatureArgument(1),
672+
into: signatureArgument(3),
673+
},
674+
// Returns undefined
675+
{
676+
kind: 'Create',
677+
into: signatureArgument(2),
678+
value: ValueKind.Primitive,
679+
reason: ValueReason.KnownReturnSignature,
680+
},
681+
],
682+
},
647683
},
648684
BuiltInUseEffectHookId,
649685
),

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {Environment, ReactFunctionType} from './Environment';
1313
import type {HookKind} from './ObjectShape';
1414
import {Type, makeType} from './Types';
1515
import {z} from 'zod';
16+
import type {AliasingEffect} from '../Inference/AliasingEffects';
1617

1718
/*
1819
* *******************************************************************************************
@@ -100,6 +101,7 @@ export type ReactiveInstruction = {
100101
id: InstructionId;
101102
lvalue: Place | null;
102103
value: ReactiveValue;
104+
effects?: Array<AliasingEffect> | null; // TODO make non-optional
103105
loc: SourceLocation;
104106
};
105107

@@ -278,12 +280,14 @@ export type HIRFunction = {
278280
params: Array<Place | SpreadPattern>;
279281
returnTypeAnnotation: t.FlowType | t.TSType | null;
280282
returnType: Type;
283+
returns: Place;
281284
context: Array<Place>;
282285
effects: Array<FunctionEffect> | null;
283286
body: HIR;
284287
generator: boolean;
285288
async: boolean;
286289
directives: Array<string>;
290+
aliasingEffects?: Array<AliasingEffect> | null;
287291
};
288292

289293
export type FunctionEffect =
@@ -449,6 +453,7 @@ export type ReturnTerminal = {
449453
value: Place;
450454
id: InstructionId;
451455
fallthrough?: never;
456+
effects: Array<AliasingEffect> | null;
452457
};
453458

454459
export type GotoTerminal = {
@@ -609,6 +614,7 @@ export type MaybeThrowTerminal = {
609614
id: InstructionId;
610615
loc: SourceLocation;
611616
fallthrough?: never;
617+
effects: Array<AliasingEffect> | null;
612618
};
613619

614620
export type ReactiveScopeTerminal = {
@@ -645,12 +651,14 @@ export type Instruction = {
645651
lvalue: Place;
646652
value: InstructionValue;
647653
loc: SourceLocation;
654+
effects: Array<AliasingEffect> | null;
648655
};
649656

650657
export type TInstruction<T extends InstructionValue> = {
651658
id: InstructionId;
652659
lvalue: Place;
653660
value: T;
661+
effects: Array<AliasingEffect> | null;
654662
loc: SourceLocation;
655663
};
656664

@@ -1380,6 +1388,11 @@ export enum ValueReason {
13801388
*/
13811389
JsxCaptured = 'jsx-captured',
13821390

1391+
/**
1392+
* Passed to an effect
1393+
*/
1394+
Effect = 'effect',
1395+
13831396
/**
13841397
* Return value of a function with known frozen return value, e.g. `useState`.
13851398
*/

0 commit comments

Comments
 (0)