Skip to content

Commit 1893fac

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 5dc1b21 commit 1893fac

File tree

118 files changed

+7282
-352
lines changed

Some content is hidden

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

118 files changed

+7282
-352
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ export class CompilerErrorDetail {
115115
export class CompilerError extends Error {
116116
details: Array<CompilerErrorDetail> = [];
117117

118+
static from(details: Array<CompilerErrorDetailOptions>): CompilerError {
119+
const error = new CompilerError();
120+
for (const detail of details) {
121+
error.push(detail);
122+
}
123+
return error;
124+
}
125+
118126
static invariant(
119127
condition: unknown,
120128
options: Omit<CompilerErrorDetailOptions, 'severity'>,

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}
@@ -226,15 +228,27 @@ function runWithEnvironment(
226228
analyseFunctions(hir);
227229
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
228230

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

237-
validateLocalsNotReassignedAfterRender(hir);
249+
if (!env.config.enableNewMutationAliasingModel) {
250+
validateLocalsNotReassignedAfterRender(hir);
251+
}
238252

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

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

254281
if (env.isInferredMemoEnabled) {
255282
if (env.config.assertValidMutableRanges) {
@@ -276,7 +303,10 @@ function runWithEnvironment(
276303
validateNoImpureFunctionsInRender(hir).unwrap();
277304
}
278305

279-
if (env.config.validateNoFreezingKnownMutableFunctions) {
306+
if (
307+
env.config.validateNoFreezingKnownMutableFunctions ||
308+
env.config.enableNewMutationAliasingModel
309+
) {
280310
validateNoFreezingKnownMutableFunctions(hir).unwrap();
281311
}
282312
}

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
/*
@@ -179,6 +179,7 @@ export function lower(
179179
loc: GeneratedSource,
180180
value: lowerExpressionToTemporary(builder, body),
181181
id: makeInstructionId(0),
182+
effects: null,
182183
};
183184
builder.terminateWithContinuation(terminal, fallthrough);
184185
} else if (body.isBlockStatement()) {
@@ -208,6 +209,7 @@ export function lower(
208209
loc: GeneratedSource,
209210
}),
210211
id: makeInstructionId(0),
212+
effects: null,
211213
},
212214
null,
213215
);
@@ -218,13 +220,15 @@ export function lower(
218220
fnType: parent == null ? env.fnType : 'Other',
219221
returnTypeAnnotation: null, // TODO: extract the actual return type node if present
220222
returnType: makeType(),
223+
returns: createTemporaryPlace(env, func.node.loc ?? GeneratedSource),
221224
body: builder.build(),
222225
context,
223226
generator: func.node.generator === true,
224227
async: func.node.async === true,
225228
loc: func.node.loc ?? GeneratedSource,
226229
env,
227230
effects: null,
231+
aliasingEffects: null,
228232
directives,
229233
});
230234
}
@@ -285,6 +289,7 @@ function lowerStatement(
285289
loc: stmt.node.loc ?? GeneratedSource,
286290
value,
287291
id: makeInstructionId(0),
292+
effects: null,
288293
};
289294
builder.terminate(terminal, 'block');
290295
return;
@@ -1235,6 +1240,7 @@ function lowerStatement(
12351240
kind: 'Debugger',
12361241
loc,
12371242
},
1243+
effects: null,
12381244
loc,
12391245
});
12401246
return;
@@ -1892,6 +1898,7 @@ function lowerExpression(
18921898
place: leftValue,
18931899
loc: exprLoc,
18941900
},
1901+
effects: null,
18951902
loc: exprLoc,
18961903
});
18971904
builder.terminateWithContinuation(
@@ -2827,6 +2834,7 @@ function lowerOptionalCallExpression(
28272834
args,
28282835
loc,
28292836
},
2837+
effects: null,
28302838
loc,
28312839
});
28322840
} else {
@@ -2840,6 +2848,7 @@ function lowerOptionalCallExpression(
28402848
args,
28412849
loc,
28422850
},
2851+
effects: null,
28432852
loc,
28442853
});
28452854
}
@@ -3466,9 +3475,10 @@ function lowerValueToTemporary(
34663475
const place: Place = buildTemporaryPlace(builder, value.loc);
34673476
builder.push({
34683477
id: makeInstructionId(0),
3478+
lvalue: {...place},
34693479
value: value,
3480+
effects: null,
34703481
loc: value.loc,
3471-
lvalue: {...place},
34723482
});
34733483
return place;
34743484
}

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,
@@ -32,6 +32,7 @@ import {
3232
addFunction,
3333
addHook,
3434
addObject,
35+
signatureArgument,
3536
} from './ObjectShape';
3637
import {BuiltInType, ObjectType, PolyType} from './Types';
3738
import {TypeConfig} from './TypeSchema';
@@ -642,6 +643,41 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
642643
calleeEffect: Effect.Read,
643644
hookKind: 'useEffect',
644645
returnValueKind: ValueKind.Frozen,
646+
aliasing: {
647+
receiver: makeIdentifierId(0),
648+
params: [],
649+
rest: makeIdentifierId(1),
650+
returns: makeIdentifierId(2),
651+
temporaries: [signatureArgument(3)],
652+
effects: [
653+
// Freezes the function and deps
654+
{
655+
kind: 'Freeze',
656+
value: signatureArgument(1),
657+
reason: ValueReason.Effect,
658+
},
659+
// Internally creates an effect object that captures the function and deps
660+
{
661+
kind: 'Create',
662+
into: signatureArgument(3),
663+
value: ValueKind.Frozen,
664+
reason: ValueReason.KnownReturnSignature,
665+
},
666+
// The effect stores the function and dependencies
667+
{
668+
kind: 'Capture',
669+
from: signatureArgument(1),
670+
into: signatureArgument(3),
671+
},
672+
// Returns undefined
673+
{
674+
kind: 'Create',
675+
into: signatureArgument(2),
676+
value: ValueKind.Primitive,
677+
reason: ValueReason.KnownReturnSignature,
678+
},
679+
],
680+
},
645681
},
646682
BuiltInUseEffectHookId,
647683
),

0 commit comments

Comments
 (0)