Skip to content

Commit 4b5029c

Browse files
committed
[compiler][newinference] Fix for phi types, extracting primitives from objects
First a quick fix: if we have a known type for the lvalue of CreateFrom, we can drop the effect. This is a bit awkward generally because the types and abstract values overlap a bit, and i'd prefer to only look at types during one phase. So maybe move all the type-checking to where we generate effects, and then after that applyEffect() doesn't have to consider types. The larger fix is for InferMutationAliasingRanges. When processing a mutation we have to walk the graph in both forward and backwards directions. Consider `alias a -> b, mutate(b)`. We have to walk back the alias chain from b to a, and mark a as mutated too. But for `alias a -> b, mutate(a)`, we also have to mark b as mutated — walking forwards along the alias chain. But phis are a bit different. You can have a mutation of one of the phi operands, such that you have `a, b -> phi c, mutate(a)`. Here, we do need to mark c as mutated, but we should not walk back to b — it's a different operand! a and b don't alias together. There are now about 150 fixtures failing, but they're in a few categories and all of them are addressable: * Infinite loops. `applyEffect()` creates new un-memoized effect values which means that any input with a backedge (loop) will spin until my infinite loop detection throws. This is somewhat tedious to address but it's a pragmatic concern and not a flaw in the model. I also need to convince myself that the approach in InferMutationAliasingRanges is safe for loops, but first i have to get inputs w loops to even reach that phase. * LoadContext/StoreContext - i currently treat these too similarly to regular load/store, ie assuming the mutations happen in order. One idea is to treat LoadContext as a mutate instead of an alias, just to make sure all instances get grouped together. * InvalidReact detection. We already synthesize MutateFrozen/MutateGlobal effects but we don't throw these as errors yet. This is probably the largest category of failing tests, which means overall this is actually "pretty close" (so, 50% of the way there). ghstack-source-id: 7d6374c Pull Request resolved: #33440
1 parent fc325c6 commit 4b5029c

9 files changed

+310
-32
lines changed

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
InstructionValue,
2626
isArrayType,
2727
isMapType,
28+
isPrimitiveType,
2829
isSetType,
2930
makeIdentifierId,
3031
ObjectMethod,
@@ -464,12 +465,15 @@ function applyEffect(
464465
break;
465466
}
466467
default: {
467-
effects.push({
468-
// OK: recording information flow
469-
kind: 'CreateFrom', // prev Alias
470-
from: effect.from,
471-
into: effect.into,
472-
});
468+
// Even if the source is non-primitive, the destination may be. skip primitives.
469+
if (!isPrimitiveType(effect.into.identifier)) {
470+
effects.push({
471+
// OK: recording information flow
472+
kind: 'CreateFrom', // prev Alias
473+
from: effect.from,
474+
into: effect.into,
475+
});
476+
}
473477
}
474478
}
475479
break;
@@ -1622,6 +1626,7 @@ function computeSignatureForInstruction(
16221626
suggestions: null,
16231627
},
16241628
});
1629+
effects.push({kind: 'Assign', from: value.value, into: lvalue});
16251630
break;
16261631
}
16271632
case 'TypeCastExpression': {

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

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ import {printIdentifier, printPlace} from '../HIR/PrintHIR';
2828
import {MutationKind} from './InferMutationAliasingFunctionEffects';
2929

3030
const DEBUG = false;
31+
const VERBOSE = false;
3132

3233
/**
3334
* Infers mutable ranges for all values.
3435
*/
3536
export function inferMutationAliasingRanges(fn: HIRFunction): void {
36-
if (DEBUG) {
37+
if (VERBOSE) {
3738
console.log();
3839
console.log();
3940
console.log();
@@ -81,7 +82,7 @@ export function inferMutationAliasingRanges(fn: HIRFunction): void {
8182
const seenBlocks = new Set<BlockId>();
8283
for (const block of fn.body.blocks.values()) {
8384
for (const phi of block.phis) {
84-
state.create(phi.place);
85+
state.create(phi.place, true);
8586
for (const [pred, operand] of phi.operands) {
8687
if (!seenBlocks.has(pred)) {
8788
// NOTE: annotation required to actually typecheck and not silently infer `any`
@@ -172,7 +173,7 @@ export function inferMutationAliasingRanges(fn: HIRFunction): void {
172173
}
173174
}
174175

175-
if (DEBUG) {
176+
if (VERBOSE) {
176177
console.log(state.debug());
177178
console.log(pretty(mutations));
178179
}
@@ -185,7 +186,7 @@ export function inferMutationAliasingRanges(fn: HIRFunction): void {
185186
mutation.kind,
186187
);
187188
}
188-
if (DEBUG) {
189+
if (VERBOSE) {
189190
console.log(state.debug());
190191
}
191192
fn.aliasingEffects ??= [];
@@ -364,7 +365,7 @@ export function inferMutationAliasingRanges(fn: HIRFunction): void {
364365
}
365366
}
366367

367-
if (DEBUG) {
368+
if (VERBOSE) {
368369
console.log(printFunction(fn));
369370
}
370371
}
@@ -377,11 +378,12 @@ type Node = {
377378
edges: Array<{index: number; node: Identifier; kind: 'capture' | 'alias'}>;
378379
transitive: MutationKind;
379380
local: MutationKind;
381+
phi: boolean;
380382
};
381383
class AliasingState {
382384
nodes: Map<Identifier, Node> = new Map();
383385

384-
create(place: Place): void {
386+
create(place: Place, phi: boolean = false): void {
385387
this.nodes.set(place.identifier, {
386388
id: place.identifier,
387389
createdFrom: new Map(),
@@ -390,6 +392,7 @@ class AliasingState {
390392
edges: [],
391393
transitive: MutationKind.None,
392394
local: MutationKind.None,
395+
phi,
393396
});
394397
}
395398

@@ -398,7 +401,7 @@ class AliasingState {
398401
const fromNode = this.nodes.get(from.identifier);
399402
const toNode = this.nodes.get(into.identifier);
400403
if (fromNode == null || toNode == null) {
401-
if (DEBUG) {
404+
if (VERBOSE) {
402405
console.log(
403406
`skip: createFrom ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
404407
);
@@ -415,7 +418,7 @@ class AliasingState {
415418
const fromNode = this.nodes.get(from.identifier);
416419
const toNode = this.nodes.get(into.identifier);
417420
if (fromNode == null || toNode == null) {
418-
if (DEBUG) {
421+
if (VERBOSE) {
419422
console.log(
420423
`skip: capture ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
421424
);
@@ -432,7 +435,7 @@ class AliasingState {
432435
const fromNode = this.nodes.get(from.identifier);
433436
const toNode = this.nodes.get(into.identifier);
434437
if (fromNode == null || toNode == null) {
435-
if (DEBUG) {
438+
if (VERBOSE) {
436439
console.log(
437440
`skip: assign ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
438441
);
@@ -453,9 +456,13 @@ class AliasingState {
453456
kind: MutationKind,
454457
): void {
455458
const seen = new Set<Identifier>();
456-
const queue = [{place: start, transitive}];
459+
const queue: Array<{
460+
place: Identifier;
461+
transitive: boolean;
462+
direction: 'backwards' | 'forwards';
463+
}> = [{place: start, transitive, direction: 'backwards'}];
457464
while (queue.length !== 0) {
458-
const {place: current, transitive} = queue.pop()!;
465+
const {place: current, transitive, direction} = queue.pop()!;
459466
if (seen.has(current)) {
460467
continue;
461468
}
@@ -471,7 +478,7 @@ class AliasingState {
471478
}
472479
if (DEBUG) {
473480
console.log(
474-
`mutate $${node.id.id} via ${printIdentifier(start)} at [${end}]`,
481+
`[${end}] mutate index=${index} ${printIdentifier(start)}: ${printIdentifier(node.id)}`,
475482
);
476483
}
477484
node.id.mutableRange.end = makeInstructionId(
@@ -491,24 +498,31 @@ class AliasingState {
491498
if (edge.index >= index) {
492499
break;
493500
}
494-
queue.push({place: edge.node, transitive});
501+
queue.push({place: edge.node, transitive, direction: 'forwards'});
495502
}
496503
for (const [alias, when] of node.createdFrom) {
497504
if (when >= index) {
498505
continue;
499506
}
500-
queue.push({place: alias, transitive: true});
507+
queue.push({place: alias, transitive: true, direction: 'backwards'});
501508
}
502-
/**
503-
* all mutations affect backward alias edges by the rules:
504-
* - Alias a -> b, mutate(b) => mutate(a)
505-
* - Alias a -> b, mutateTransitive(b) => mutate(a)
506-
*/
507-
for (const [alias, when] of node.aliases) {
508-
if (when >= index) {
509-
continue;
509+
if (direction === 'backwards' || !node.phi) {
510+
/**
511+
* all mutations affect backward alias edges by the rules:
512+
* - Alias a -> b, mutate(b) => mutate(a)
513+
* - Alias a -> b, mutateTransitive(b) => mutate(a)
514+
*
515+
* However, if we reached a phi because one of its inputs was mutated
516+
* (and we're advancing "forwards" through that node's edges), then
517+
* we know we've already processed the mutation at its source. The
518+
* phi's other inputs can't be affected.
519+
*/
520+
for (const [alias, when] of node.aliases) {
521+
if (when >= index) {
522+
continue;
523+
}
524+
queue.push({place: alias, transitive, direction: 'backwards'});
510525
}
511-
queue.push({place: alias, transitive});
512526
}
513527
/**
514528
* but only transitive mutations affect captures
@@ -518,10 +532,18 @@ class AliasingState {
518532
if (when >= index) {
519533
continue;
520534
}
521-
queue.push({place: capture, transitive});
535+
queue.push({place: capture, transitive, direction: 'backwards'});
522536
}
523537
}
524538
}
539+
if (DEBUG) {
540+
const nodes = new Map();
541+
for (const id of seen) {
542+
const node = this.nodes.get(id);
543+
nodes.set(id.id, node);
544+
}
545+
console.log(pretty(nodes));
546+
}
525547
}
526548

527549
debug(): string {

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
getHookKind,
4545
makeIdentifierName,
4646
} from '../HIR/HIR';
47-
import {printIdentifier, printPlace} from '../HIR/PrintHIR';
47+
import {printIdentifier, printInstruction, printPlace} from '../HIR/PrintHIR';
4848
import {eachPatternOperand} from '../HIR/visitors';
4949
import {Err, Ok, Result} from '../Utils/Result';
5050
import {GuardKind} from '../Utils/RuntimeDiagnosticConstants';
@@ -1310,7 +1310,7 @@ function codegenInstructionNullable(
13101310
});
13111311
CompilerError.invariant(value?.type === 'FunctionExpression', {
13121312
reason: 'Expected a function as a function declaration value',
1313-
description: null,
1313+
description: `Got ${value == null ? String(value) : value.type} at ${printInstruction(instr)}`,
13141314
loc: instr.value.loc,
13151315
suggestions: null,
13161316
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @enableNewMutationAliasingModel
6+
import {Stringify} from 'shared-runtime';
7+
8+
function Component({a, b}) {
9+
const x = {a, b};
10+
const f = () => {
11+
const y = [x];
12+
return y[0];
13+
};
14+
const x0 = f();
15+
const z = [x0];
16+
const x1 = z[0];
17+
x1.key = 'value';
18+
return <Stringify x={x} />;
19+
}
20+
21+
export const FIXTURE_ENTRYPOINT = {
22+
fn: Component,
23+
params: [{a: 0, b: 1}],
24+
};
25+
26+
```
27+
28+
## Code
29+
30+
```javascript
31+
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
32+
import { Stringify } from "shared-runtime";
33+
34+
function Component(t0) {
35+
const $ = _c(3);
36+
const { a, b } = t0;
37+
let t1;
38+
if ($[0] !== a || $[1] !== b) {
39+
const x = { a, b };
40+
const f = () => {
41+
const y = [x];
42+
return y[0];
43+
};
44+
45+
const x0 = f();
46+
const z = [x0];
47+
const x1 = z[0];
48+
x1.key = "value";
49+
t1 = <Stringify x={x} />;
50+
$[0] = a;
51+
$[1] = b;
52+
$[2] = t1;
53+
} else {
54+
t1 = $[2];
55+
}
56+
return t1;
57+
}
58+
59+
export const FIXTURE_ENTRYPOINT = {
60+
fn: Component,
61+
params: [{ a: 0, b: 1 }],
62+
};
63+
64+
```
65+
66+
### Eval output
67+
(kind: ok) <div>{"x":{"a":0,"b":1,"key":"value"}}</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// @enableNewMutationAliasingModel
2+
import {Stringify} from 'shared-runtime';
3+
4+
function Component({a, b}) {
5+
const x = {a, b};
6+
const f = () => {
7+
const y = [x];
8+
return y[0];
9+
};
10+
const x0 = f();
11+
const z = [x0];
12+
const x1 = z[0];
13+
x1.key = 'value';
14+
return <Stringify x={x} />;
15+
}
16+
17+
export const FIXTURE_ENTRYPOINT = {
18+
fn: Component,
19+
params: [{a: 0, b: 1}],
20+
};

0 commit comments

Comments
 (0)