Skip to content

Commit c567f48

Browse files
committed
[compiler] Sketch of new mutation/aliasing effects
ghstack-source-id: 9921a7c Pull Request resolved: facebook/react#33184
1 parent 861b349 commit c567f48

File tree

2 files changed

+288
-0
lines changed

2 files changed

+288
-0
lines changed

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,6 +1434,46 @@ export const ValueKindSchema = z.enum([
14341434
ValueKind.Context,
14351435
]);
14361436

1437+
export type AliasingEffect =
1438+
/**
1439+
* Freezes the operand if not already frozen
1440+
*/
1441+
| {kind: 'Freeze'; place: Place}
1442+
/**
1443+
* Known mutation of a value, which may effect that value or values that it contains.
1444+
* This is rare!
1445+
*/
1446+
| {kind: 'MutateTransitive'; place: Place}
1447+
/**
1448+
* Known mutation of a specific value, targeting only that specific value but not
1449+
* values that it contains.
1450+
*
1451+
* Example: `array.push(item)` mutates the array but does not mutate items stored in the array.
1452+
*/
1453+
| {kind: 'MutateLocal'; place: Place}
1454+
/**
1455+
* Possible mutation of a specific value
1456+
*/
1457+
| {kind: 'ConditionallyMutate'; place: Place}
1458+
/**
1459+
* Direct aliasing of one identifier by another identifier
1460+
* Examples: `x = y` (from y -> to x) or phis (from operand -> to phi)
1461+
*/
1462+
| {kind: 'Alias'; from: Place; to: Place}
1463+
/**
1464+
* Direct aliasing of an identifier (or a sub-path), or storing a value/sub-path
1465+
* into a part of another object.
1466+
*
1467+
* One of from.path and/or to.path must be non-null (else this is equivalent to Alias)
1468+
*/
1469+
| {
1470+
kind: 'Capture';
1471+
from: {place: Place; path: '*' | null};
1472+
to: {place: Place; path: '*' | null};
1473+
}
1474+
// Known mutation of a global
1475+
| {kind: 'MutateGlobal'; place: Place};
1476+
14371477
// The effect with which a value is modified.
14381478
export enum Effect {
14391479
// Default value: not allowed after lifetime inference
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {CompilerError} from '..';
9+
import {
10+
AliasingEffect,
11+
HIRFunction,
12+
Instruction,
13+
isArrayType,
14+
isMapType,
15+
isSetType,
16+
Place,
17+
} from '../HIR';
18+
import {
19+
eachInstructionValueLValue,
20+
eachInstructionValueOperand,
21+
} from '../HIR/visitors';
22+
import {Result} from '../Utils/Result';
23+
24+
export function inferMutationAliasingEffects(
25+
fn: HIRFunction,
26+
): Result<Array<AliasingEffect>, CompilerError> {}
27+
28+
function computeEffectsForInstruction(
29+
instr: Instruction,
30+
): Array<AliasingEffect> {
31+
const {lvalue, value} = instr;
32+
const effects: Array<AliasingEffect> = [];
33+
switch (value.kind) {
34+
case 'ArrayExpression': {
35+
// All elements are captured into part of the output value
36+
for (const element of value.elements) {
37+
let operand: Place;
38+
if (element.kind === 'Identifier') {
39+
operand = element;
40+
} else if (element.kind === 'Spread') {
41+
operand = element.place;
42+
} else {
43+
continue;
44+
}
45+
effects.push({
46+
kind: 'Capture',
47+
from: {place: operand, path: null},
48+
to: {place: lvalue, path: '*'},
49+
});
50+
}
51+
break;
52+
}
53+
case 'ObjectExpression': {
54+
for (const property of value.properties) {
55+
const operand =
56+
property.kind === 'ObjectProperty' ? property.place : property.place;
57+
effects.push({
58+
kind: 'Capture',
59+
from: {place: operand, path: null},
60+
to: {place: lvalue, path: '*'},
61+
});
62+
}
63+
break;
64+
}
65+
case 'Await': {
66+
// Potentially mutates the receiver (awaiting it changes its state and can run side effects)
67+
effects.push({kind: 'ConditionallyMutate', place: value.value});
68+
/**
69+
* Data from the promise may be returned into the result, but await does not directly return
70+
* the promise itself
71+
*/
72+
effects.push({
73+
kind: 'Capture',
74+
from: {place: value.value, path: '*'},
75+
to: {place: lvalue, path: null},
76+
});
77+
break;
78+
}
79+
case 'MethodCall':
80+
case 'NewExpression':
81+
case 'CallExpression': {
82+
// TODO
83+
break;
84+
}
85+
case 'PropertyDelete':
86+
case 'ComputedDelete': {
87+
// Mutates the object by removing the property, no aliasing
88+
effects.push({kind: 'MutateLocal', place: value.object});
89+
break;
90+
}
91+
case 'PropertyLoad':
92+
case 'ComputedLoad': {
93+
effects.push({
94+
kind: 'Capture',
95+
from: {place: value.object, path: '*'},
96+
to: {place: lvalue, path: null},
97+
});
98+
break;
99+
}
100+
case 'PropertyStore':
101+
case 'ComputedStore': {
102+
effects.push({kind: 'MutateLocal', place: value.object});
103+
effects.push({
104+
kind: 'Capture',
105+
from: {place: value.value, path: null},
106+
to: {place: value.object, path: '*'},
107+
});
108+
effects.push({kind: 'Alias', from: value.value, to: lvalue});
109+
break;
110+
}
111+
case 'PostfixUpdate':
112+
case 'PrefixUpdate': {
113+
effects.push({kind: 'MutateLocal', place: value.value});
114+
break;
115+
}
116+
case 'ObjectMethod':
117+
case 'FunctionExpression': {
118+
// TODO: effects.push(...value.loweredFunc.func.aliasEffects)
119+
break;
120+
}
121+
case 'GetIterator': {
122+
if (
123+
isArrayType(value.collection.identifier) ||
124+
isMapType(value.collection.identifier) ||
125+
isSetType(value.collection.identifier)
126+
) {
127+
/*
128+
* Builtin collections are known to return a fresh iterator on each call,
129+
* so the iterator does not alias the collection
130+
*/
131+
effects.push({
132+
kind: 'Capture',
133+
from: {place: value.collection, path: '*'},
134+
to: {place: lvalue, path: '*'},
135+
});
136+
} else {
137+
/*
138+
* Otherwise, the object may return itself as the iterator, so we have to
139+
* assume that the result directly aliases the collection. Further, the
140+
* method to get the iterator could potentially mutate the collection
141+
*/
142+
effects.push({kind: 'Alias', from: value.collection, to: lvalue});
143+
effects.push({kind: 'ConditionallyMutate', place: value.collection});
144+
}
145+
break;
146+
}
147+
case 'IteratorNext': {
148+
/*
149+
* Technically advancing an iterator will always mutate it (for any reasonable implementation)
150+
* But because we create an alias from the collection to the iterator if we don't know the type,
151+
* then it's possible the iterator is aliased to a frozen value and we wouldn't want to error.
152+
* so we mark this as conditional mutation to allow iterating frozen values.
153+
*/
154+
effects.push({kind: 'ConditionallyMutate', place: value.iterator});
155+
// Extracts part of the original collection into the result
156+
effects.push({
157+
kind: 'Capture',
158+
from: {place: value.iterator, path: '*'},
159+
to: {place: lvalue, path: null},
160+
});
161+
break;
162+
}
163+
case 'NextPropertyOf': {
164+
// TODO
165+
break;
166+
}
167+
case 'JsxExpression':
168+
case 'JsxFragment': {
169+
for (const operand of eachInstructionValueOperand(value)) {
170+
effects.push({kind: 'Freeze', place: operand});
171+
effects.push({
172+
kind: 'Capture',
173+
from: {place: operand, path: null},
174+
to: {place: lvalue, path: '*'},
175+
});
176+
}
177+
break;
178+
}
179+
case 'DeclareContext':
180+
case 'DeclareLocal': {
181+
// no effects
182+
break;
183+
}
184+
case 'Destructure': {
185+
for (const patternLValue of eachInstructionValueLValue(value)) {
186+
effects.push({
187+
kind: 'Capture',
188+
from: {place: value.value, path: '*'},
189+
to: {place: patternLValue, path: null},
190+
});
191+
}
192+
effects.push({kind: 'Alias', from: value.value, to: lvalue});
193+
break;
194+
}
195+
case 'LoadContext': {
196+
effects.push({
197+
kind: 'Capture',
198+
from: {place: value.place, path: '*'},
199+
to: {place: lvalue, path: null},
200+
});
201+
break;
202+
}
203+
case 'LoadLocal': {
204+
effects.push({kind: 'Alias', from: value.place, to: lvalue});
205+
break;
206+
}
207+
case 'StoreContext': {
208+
effects.push({kind: 'MutateLocal', place: value.lvalue.place});
209+
effects.push({
210+
kind: 'Capture',
211+
from: {place: value.value, path: null},
212+
to: {place: value.lvalue.place, path: '*'},
213+
});
214+
effects.push({kind: 'Alias', from: value.value, to: lvalue});
215+
break;
216+
}
217+
case 'StoreGlobal': {
218+
effects.push({kind: 'MutateGlobal', place: value.value});
219+
break;
220+
}
221+
case 'StoreLocal': {
222+
effects.push({kind: 'Alias', from: value.value, to: value.lvalue.place});
223+
effects.push({kind: 'Alias', from: value.value, to: lvalue});
224+
break;
225+
}
226+
case 'TypeCastExpression': {
227+
effects.push({kind: 'Alias', from: value.value, to: lvalue});
228+
break;
229+
}
230+
case 'BinaryExpression':
231+
case 'Debugger':
232+
case 'FinishMemoize':
233+
case 'JSXText':
234+
case 'LoadGlobal':
235+
case 'MetaProperty':
236+
case 'Primitive':
237+
case 'RegExpLiteral':
238+
case 'StartMemoize':
239+
case 'TaggedTemplateExpression':
240+
case 'TemplateLiteral':
241+
case 'UnaryExpression':
242+
case 'UnsupportedNode': {
243+
// no effects
244+
break;
245+
}
246+
}
247+
return effects;
248+
}

0 commit comments

Comments
 (0)