Skip to content

Commit c76400f

Browse files
committed
[compiler][newinference] Explicitly model StoreContext variables as mutable boxes
Context variables — in the {Declare,Store,Load}Context sense — are conceptually like mutable boxes that are stored into and read out of at various points. Previously some fixtures were failing in the new inference bc i wasn't fully modeling them this way, so this PR moves toward more explicitly modeling these variables exactly like a mutable object that we're storing into and reading out of. One catch is that unlike with regular variables, a `StoreContext Let x = ...` may not be the initial declaration of a value — for hoisted bindings, they may be a `DeclareContext HoistedLet x` first. So we first do a scan over the HIR to determine which declaration ids have hoisted let/const/function bindings. Then we model the "first" declaration of each context declaration id as the creation of fresh mutable value (box), then model subsequent reassignments as mutations of that box plus capturing of a value into that box, and model loads as CreateFrom from the box. Thus StoreContext assignments have equivalent effects to PropertyStore and LoadContext has equivalent effects to PropertyLoad. ghstack-source-id: 8b8a6e3 Pull Request resolved: #33465
1 parent 5e32638 commit c76400f

File tree

3 files changed

+87
-30
lines changed

3 files changed

+87
-30
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,10 @@ function runWithEnvironment(
302302
validateNoImpureFunctionsInRender(hir).unwrap();
303303
}
304304

305-
if (env.config.validateNoFreezingKnownMutableFunctions) {
305+
if (
306+
env.config.validateNoFreezingKnownMutableFunctions ||
307+
env.config.enableNewMutationAliasingModel
308+
) {
306309
validateNoFreezingKnownMutableFunctions(hir).unwrap();
307310
}
308311
}

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/Inference/InferMutationAliasingEffects.ts

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import {
1616
BasicBlock,
1717
BlockId,
18+
DeclarationId,
1819
Environment,
1920
FunctionExpression,
2021
HIRFunction,
@@ -157,7 +158,13 @@ export function inferMutationAliasingEffects(
157158
}
158159
queue(fn.body.entry, initialState);
159160

160-
const context = new Context(isFunctionExpression, fn);
161+
const hoistedContextDeclarations = findHoistedContextDeclarations(fn);
162+
163+
const context = new Context(
164+
isFunctionExpression,
165+
fn,
166+
hoistedContextDeclarations,
167+
);
161168

162169
let count = 0;
163170
while (queuedStates.size !== 0) {
@@ -189,6 +196,25 @@ export function inferMutationAliasingEffects(
189196
return Ok(undefined);
190197
}
191198

199+
function findHoistedContextDeclarations(fn: HIRFunction): Set<DeclarationId> {
200+
const hoisted = new Set<DeclarationId>();
201+
for (const block of fn.body.blocks.values()) {
202+
for (const instr of block.instructions) {
203+
if (instr.value.kind === 'DeclareContext') {
204+
const kind = instr.value.lvalue.kind;
205+
if (
206+
kind == InstructionKind.HoistedConst ||
207+
kind == InstructionKind.HoistedFunction ||
208+
kind == InstructionKind.HoistedLet
209+
) {
210+
hoisted.add(instr.value.lvalue.place.identifier.declarationId);
211+
}
212+
}
213+
}
214+
}
215+
return hoisted;
216+
}
217+
192218
class Context {
193219
internedEffects: Map<string, AliasingEffect> = new Map();
194220
instructionSignatureCache: Map<Instruction, InstructionSignature> = new Map();
@@ -197,10 +223,16 @@ class Context {
197223
catchHandlers: Map<BlockId, Place> = new Map();
198224
isFuctionExpression: boolean;
199225
fn: HIRFunction;
226+
hoistedContextDeclarations: Set<DeclarationId>;
200227

201-
constructor(isFunctionExpression: boolean, fn: HIRFunction) {
228+
constructor(
229+
isFunctionExpression: boolean,
230+
fn: HIRFunction,
231+
hoistedContextDeclarations: Set<DeclarationId>,
232+
) {
202233
this.isFuctionExpression = isFunctionExpression;
203234
this.fn = fn;
235+
this.hoistedContextDeclarations = hoistedContextDeclarations;
204236
}
205237

206238
internEffect(effect: AliasingEffect): AliasingEffect {
@@ -241,7 +273,11 @@ function inferBlock(
241273
for (const instr of block.instructions) {
242274
let instructionSignature = context.instructionSignatureCache.get(instr);
243275
if (instructionSignature == null) {
244-
instructionSignature = computeSignatureForInstruction(state.env, instr);
276+
instructionSignature = computeSignatureForInstruction(
277+
context,
278+
state.env,
279+
instr,
280+
);
245281
context.instructionSignatureCache.set(instr, instructionSignature);
246282
}
247283
const effects = applySignature(context, state, instructionSignature, instr);
@@ -1265,6 +1301,7 @@ type InstructionSignature = {
12651301
* an instruction.
12661302
*/
12671303
function computeSignatureForInstruction(
1304+
context: Context,
12681305
env: Environment,
12691306
instr: Instruction,
12701307
): InstructionSignature {
@@ -1603,26 +1640,37 @@ function computeSignatureForInstruction(
16031640
break;
16041641
}
16051642
case 'LoadContext': {
1606-
// We load from the context
1607-
effects.push({kind: 'Assign', from: value.place, into: lvalue});
1643+
/*
1644+
* Context variables are like mutable boxes. Loading from one
1645+
* is equivalent to a PropertyLoad from the box, so we model it
1646+
* with the same effect we use there (CreateFrom)
1647+
*/
1648+
effects.push({kind: 'CreateFrom', from: value.place, into: lvalue});
16081649
break;
16091650
}
16101651
case 'StoreContext': {
1611-
// We're storing into the conceptual box
1612-
if (value.lvalue.kind === InstructionKind.Reassign) {
1652+
/*
1653+
* Context variables are like mutable boxes, so semantically
1654+
* we're either creating (let/const) or mutating (reassign) a box,
1655+
* and then capturing the value into it.
1656+
*/
1657+
if (
1658+
value.lvalue.kind === InstructionKind.Reassign ||
1659+
context.hoistedContextDeclarations.has(
1660+
value.lvalue.place.identifier.declarationId,
1661+
)
1662+
) {
16131663
effects.push({kind: 'Mutate', value: value.lvalue.place});
16141664
} else {
1615-
// Context variables are conceptually like mutable boxes
16161665
effects.push({
16171666
kind: 'Create',
16181667
into: value.lvalue.place,
16191668
value: ValueKind.Mutable,
16201669
reason: ValueReason.Other,
16211670
});
16221671
}
1623-
// Which aliases the value
16241672
effects.push({
1625-
kind: 'Alias',
1673+
kind: 'Capture',
16261674
from: value.value,
16271675
into: value.lvalue.place,
16281676
});

0 commit comments

Comments
 (0)