Skip to content

Commit 5cc872c

Browse files
committed
[compiler] First example of an aliasing signature (array push)
Adds an aliasing signature for Array.prototype.push and fixes up the logic for consuming these signatures during effect inference. As the test fixture shows, we correctly model the capturing. Mutable values that are captured into the array count as co-mutated if the array itself is transitively mutated later, while mutable values captured after such transitive mutation are not considered co-mutated. The implementation is based on the fact that the final `push` call is only locally mutating the array. During the phase that looks at local mutations we are only tracking direct aliases, and the push doesn't directly alias the items to the array, it only captures them. ghstack-source-id: 204e369 Pull Request resolved: #33370
1 parent 7ffe06d commit 5cc872c

File tree

5 files changed

+155
-22
lines changed

5 files changed

+155
-22
lines changed

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

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,20 @@
77

88
import {CompilerError} from '../CompilerError';
99
import {AliasingSignature} from '../Inference/InferMutationAliasingEffects';
10-
import {Effect, ValueKind, ValueReason} from './HIR';
10+
import {
11+
Effect,
12+
GeneratedSource,
13+
makeDeclarationId,
14+
makeIdentifierId,
15+
makeInstructionId,
16+
Place,
17+
ValueKind,
18+
ValueReason,
19+
} from './HIR';
1120
import {
1221
BuiltInType,
1322
FunctionType,
23+
makeType,
1424
ObjectType,
1525
PolyType,
1626
PrimitiveType,
@@ -305,6 +315,28 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
305315
returnType: PRIMITIVE_TYPE,
306316
calleeEffect: Effect.Store,
307317
returnValueKind: ValueKind.Primitive,
318+
aliasing: {
319+
receiver: makeIdentifierId(0),
320+
params: [],
321+
rest: makeIdentifierId(1),
322+
returns: makeIdentifierId(2),
323+
effects: [
324+
// Push directly mutates the array itself
325+
{kind: 'Mutate', value: signatureArgument(0)},
326+
// The arguments are captured into the array
327+
{
328+
kind: 'Capture',
329+
from: signatureArgument(1),
330+
into: signatureArgument(0),
331+
},
332+
// Returns the new length, a primitive
333+
{
334+
kind: 'Create',
335+
into: signatureArgument(2),
336+
value: ValueKind.Primitive,
337+
},
338+
],
339+
},
308340
}),
309341
],
310342
[
@@ -1173,3 +1205,22 @@ export const DefaultNonmutatingHook = addHook(
11731205
},
11741206
'DefaultNonmutatingHook',
11751207
);
1208+
1209+
export function signatureArgument(id: number): Place {
1210+
const place: Place = {
1211+
kind: 'Identifier',
1212+
effect: Effect.Unknown,
1213+
loc: GeneratedSource,
1214+
reactive: false,
1215+
identifier: {
1216+
declarationId: makeDeclarationId(id),
1217+
id: makeIdentifierId(id),
1218+
loc: GeneratedSource,
1219+
mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
1220+
name: null,
1221+
scope: null,
1222+
type: makeType(),
1223+
},
1224+
};
1225+
return place;
1226+
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {GotoVariant, InstructionKind} from './HIR';
3838
import {
3939
AliasedPlace,
4040
AliasingEffect,
41+
AliasingSignature,
4142
} from '../Inference/InferMutationAliasingEffects';
4243

4344
export type Options = {
@@ -991,3 +992,21 @@ function printPlaceForAliasEffect(place: Place): string {
991992
function printAliasedPlace(place: AliasedPlace): string {
992993
return place.kind + ' ' + printPlaceForAliasEffect(place.place);
993994
}
995+
996+
export function printAliasingSignature(signature: AliasingSignature): string {
997+
const tokens: Array<string> = ['function '];
998+
tokens.push('(');
999+
tokens.push('this=$' + String(signature.receiver));
1000+
for (const param of signature.params) {
1001+
tokens.push(', $' + String(param));
1002+
}
1003+
if (signature.rest != null) {
1004+
tokens.push(`, ...$${String(signature.rest)}`);
1005+
}
1006+
tokens.push('): ');
1007+
tokens.push('$' + String(signature.returns) + ':');
1008+
for (const effect of signature.effects) {
1009+
tokens.push('\n ' + printAliasingEffect(effect));
1010+
}
1011+
return tokens.join('');
1012+
}

compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -920,15 +920,19 @@ function computeSignatureForInstruction(
920920
case 'CallExpression':
921921
case 'MethodCall': {
922922
let callee;
923+
let receiver;
923924
let mutatesCallee = false;
924925
if (value.kind === 'NewExpression') {
925926
callee = value.callee;
927+
receiver = value.callee;
926928
mutatesCallee = false;
927929
} else if (value.kind === 'CallExpression') {
928930
callee = value.callee;
931+
receiver = value.callee;
929932
mutatesCallee = true;
930933
} else if (value.kind === 'MethodCall') {
931934
callee = value.property;
935+
receiver = value.receiver;
932936
mutatesCallee = false;
933937
} else {
934938
assertExhaustive(
@@ -942,11 +946,11 @@ function computeSignatureForInstruction(
942946
? computeEffectsForSignature(
943947
signature.aliasing,
944948
lvalue,
945-
callee,
949+
receiver,
946950
value.args,
947951
)
948952
: null;
949-
if (signatureEffects != null) {
953+
if (signatureEffects != null && signature?.aliasing != null) {
950954
effects.push(...signatureEffects);
951955
} else {
952956
effects.push({kind: 'Create', into: lvalue, value: ValueKind.Mutable});
@@ -1292,34 +1296,30 @@ function computeEffectsForSignature(
12921296
const effects: Array<AliasingEffect> = [];
12931297
for (const effect of signature.effects) {
12941298
switch (effect.kind) {
1295-
case 'Alias': {
1296-
const from = substitutions.get(effect.from.identifier.id) ?? [];
1297-
const to = substitutions.get(effect.into.identifier.id) ?? [];
1298-
for (const fromId of from) {
1299-
for (const toId of to) {
1300-
effects.push({kind: 'Alias', from: fromId, into: toId});
1301-
}
1302-
}
1303-
break;
1304-
}
1299+
case 'ImmutableCapture':
1300+
case 'Alias':
1301+
case 'CreateFrom':
13051302
case 'Capture': {
13061303
const from = substitutions.get(effect.from.identifier.id) ?? [];
13071304
const to = substitutions.get(effect.into.identifier.id) ?? [];
13081305
for (const fromId of from) {
13091306
for (const toId of to) {
13101307
effects.push({
1311-
kind: 'Capture',
1308+
kind: effect.kind,
13121309
from: fromId,
13131310
into: toId,
13141311
});
13151312
}
13161313
}
13171314
break;
13181315
}
1316+
case 'Mutate':
1317+
case 'MutateTransitive':
1318+
case 'MutateTransitiveConditionally':
13191319
case 'MutateConditionally': {
13201320
const values = substitutions.get(effect.value.identifier.id) ?? [];
13211321
for (const id of values) {
1322-
effects.push({kind: 'MutateConditionally', value: id});
1322+
effects.push({kind: effect.kind, value: id});
13231323
}
13241324
break;
13251325
}
@@ -1337,14 +1337,9 @@ function computeEffectsForSignature(
13371337
}
13381338
break;
13391339
}
1340-
case 'ImmutableCapture':
1341-
case 'CreateFrom':
1342-
case 'Apply':
1343-
case 'Mutate':
1344-
case 'MutateTransitive':
1345-
case 'MutateTransitiveConditionally': {
1340+
case 'Apply': {
13461341
CompilerError.throwTodo({
1347-
reason: 'Handle other types for function declarations',
1342+
reason: `Handle ${effect.kind} effects for function declarations`,
13481343
loc: lvalue.loc,
13491344
});
13501345
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @enableNewMutationAliasingModel
6+
function Component({a, b, c}) {
7+
const x = [];
8+
x.push(a);
9+
const merged = {b}; // could be mutated by mutate(x) below
10+
x.push(merged);
11+
mutate(x);
12+
const independent = {c}; // can't be later mutated
13+
x.push(independent);
14+
return <Foo value={x} />;
15+
}
16+
17+
```
18+
19+
## Code
20+
21+
```javascript
22+
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
23+
function Component(t0) {
24+
const $ = _c(6);
25+
const { a, b, c } = t0;
26+
let t1;
27+
if ($[0] !== a || $[1] !== b || $[2] !== c) {
28+
const x = [];
29+
x.push(a);
30+
const merged = { b };
31+
x.push(merged);
32+
mutate(x);
33+
let t2;
34+
if ($[4] !== c) {
35+
t2 = { c };
36+
$[4] = c;
37+
$[5] = t2;
38+
} else {
39+
t2 = $[5];
40+
}
41+
const independent = t2;
42+
x.push(independent);
43+
t1 = <Foo value={x} />;
44+
$[0] = a;
45+
$[1] = b;
46+
$[2] = c;
47+
$[3] = t1;
48+
} else {
49+
t1 = $[3];
50+
}
51+
return t1;
52+
}
53+
54+
```
55+
56+
### Eval output
57+
(kind: exception) Fixture not implemented
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @enableNewMutationAliasingModel
2+
function Component({a, b, c}) {
3+
const x = [];
4+
x.push(a);
5+
const merged = {b}; // could be mutated by mutate(x) below
6+
x.push(merged);
7+
mutate(x);
8+
const independent = {c}; // can't be later mutated
9+
x.push(independent);
10+
return <Foo value={x} />;
11+
}

0 commit comments

Comments
 (0)