Skip to content

Commit 280d60e

Browse files
committed
[compiler][optim] Add Effect.ConditionallyMutateIterator
Adds Effect.ConditionallyMutateIterator, which has the following effects: - capture for known array, map, and sets - mutate for all other values An alternative to this approach could be to add polymorphic shape definitions
1 parent 652d598 commit 280d60e

18 files changed

+398
-126
lines changed

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
6565
'Int8Array',
6666
'Int16Array',
6767
'Int32Array',
68-
'Map',
69-
'Set',
7068
'WeakMap',
7169
'Uint8Array',
7270
'Uint8ClampedArray',
@@ -140,7 +138,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
140138
'from',
141139
addFunction(DEFAULT_SHAPES, [], {
142140
positionalParams: [
143-
Effect.ConditionallyMutate,
141+
Effect.ConditionallyMutateIterator,
144142
Effect.ConditionallyMutate,
145143
Effect.ConditionallyMutate,
146144
],
@@ -466,7 +464,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
466464
DEFAULT_SHAPES,
467465
[],
468466
{
469-
positionalParams: [Effect.ConditionallyMutate],
467+
positionalParams: [Effect.ConditionallyMutateIterator],
470468
restParam: null,
471469
returnType: {kind: 'Object', shapeId: BuiltInMapId},
472470
calleeEffect: Effect.Read,
@@ -482,7 +480,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
482480
DEFAULT_SHAPES,
483481
[],
484482
{
485-
positionalParams: [Effect.ConditionallyMutate],
483+
positionalParams: [Effect.ConditionallyMutateIterator],
486484
restParam: null,
487485
returnType: {kind: 'Object', shapeId: BuiltInSetId},
488486
calleeEffect: Effect.Read,

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,7 @@ export enum Effect {
13961396
Read = 'read',
13971397
// This reference reads and stores the value
13981398
Capture = 'capture',
1399+
ConditionallyMutateIterator = 'mutate-iterator?',
13991400
/*
14001401
* This reference *may* write to (mutate) the value. This covers two similar cases:
14011402
* - The compiler is being conservative and assuming that a value *may* be mutated
@@ -1414,11 +1415,11 @@ export enum Effect {
14141415
// This reference may alias to (mutate) the value
14151416
Store = 'store',
14161417
}
1417-
14181418
export const EffectSchema = z.enum([
14191419
Effect.Read,
14201420
Effect.Mutate,
14211421
Effect.ConditionallyMutate,
1422+
Effect.ConditionallyMutateIterator,
14221423
Effect.Capture,
14231424
Effect.Store,
14241425
Effect.Freeze,
@@ -1432,6 +1433,7 @@ export function isMutableEffect(
14321433
case Effect.Capture:
14331434
case Effect.Store:
14341435
case Effect.ConditionallyMutate:
1436+
case Effect.ConditionallyMutateIterator:
14351437
case Effect.Mutate: {
14361438
return true;
14371439
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import {
1111
Identifier,
1212
InstructionId,
1313
InstructionKind,
14+
isArrayType,
15+
isMapType,
1416
isRefOrRefValue,
17+
isSetType,
1518
makeInstructionId,
1619
Place,
1720
} from '../HIR/HIR';
@@ -90,6 +93,17 @@ function inferPlace(
9093
infer(place, instrId);
9194
}
9295
return;
96+
case Effect.ConditionallyMutateIterator: {
97+
const identifier = place.identifier;
98+
if (
99+
!isArrayType(identifier) &&
100+
!isSetType(identifier) &&
101+
!isMapType(identifier)
102+
) {
103+
infer(place, instrId);
104+
}
105+
return;
106+
}
93107
case Effect.ConditionallyMutate:
94108
case Effect.Mutate: {
95109
infer(place, instrId);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
230230
case Effect.Capture:
231231
case Effect.Store:
232232
case Effect.ConditionallyMutate:
233+
case Effect.ConditionallyMutateIterator:
233234
case Effect.Mutate: {
234235
if (isMutable(instruction, operand)) {
235236
reactiveIdentifiers.markReactive(operand);

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

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ import {
2929
ValueKind,
3030
ValueReason,
3131
isArrayType,
32+
isMapType,
3233
isMutableEffect,
3334
isObjectType,
35+
isSetType,
3436
} from '../HIR/HIR';
3537
import {FunctionSignature} from '../HIR/ObjectShape';
3638
import {
@@ -469,6 +471,25 @@ class InferenceState {
469471
}
470472
break;
471473
}
474+
case Effect.ConditionallyMutateIterator: {
475+
if (
476+
valueKind.kind === ValueKind.Mutable ||
477+
valueKind.kind === ValueKind.Context
478+
) {
479+
if (
480+
isArrayType(place.identifier) ||
481+
isSetType(place.identifier) ||
482+
isMapType(place.identifier)
483+
) {
484+
effect = Effect.Capture;
485+
} else {
486+
effect = Effect.ConditionallyMutate;
487+
}
488+
} else {
489+
effect = Effect.Read;
490+
}
491+
break;
492+
}
472493
case Effect.Mutate: {
473494
effect = Effect.Mutate;
474495
break;
@@ -880,9 +901,7 @@ function inferBlock(
880901
state.referenceAndRecordEffects(
881902
freezeActions,
882903
element.place,
883-
isArrayType(element.place.identifier)
884-
? Effect.Capture
885-
: Effect.ConditionallyMutate,
904+
Effect.ConditionallyMutateIterator,
886905
ValueReason.Other,
887906
);
888907
} else if (element.kind === 'Identifier') {
@@ -1643,7 +1662,13 @@ function inferBlock(
16431662
kind === ValueKind.Mutable || kind === ValueKind.Context;
16441663
let effect;
16451664
let valueKind: AbstractValue;
1646-
if (!isMutable || isArrayType(instrValue.collection.identifier)) {
1665+
const iterator = instrValue.collection.identifier;
1666+
if (
1667+
!isMutable ||
1668+
isArrayType(iterator) ||
1669+
isMapType(iterator) ||
1670+
isSetType(iterator)
1671+
) {
16471672
// Case 1, assume iterator is a separate mutable object
16481673
effect = {
16491674
kind: Effect.Read,
@@ -1684,7 +1709,7 @@ function inferBlock(
16841709
state.referenceAndRecordEffects(
16851710
freezeActions,
16861711
instrValue.iterator,
1687-
Effect.ConditionallyMutate,
1712+
Effect.ConditionallyMutateIterator,
16881713
ValueReason.Other,
16891714
);
16901715
/**
@@ -1846,6 +1871,7 @@ export function isKnownMutableEffect(effect: Effect): boolean {
18461871
switch (effect) {
18471872
case Effect.Store:
18481873
case Effect.ConditionallyMutate:
1874+
case Effect.ConditionallyMutateIterator:
18491875
case Effect.Mutate: {
18501876
return true;
18511877
}
@@ -1949,7 +1975,7 @@ function getArgumentEffect(
19491975
});
19501976
}
19511977
// effects[i] is Effect.Capture | Effect.Read | Effect.Store
1952-
return Effect.ConditionallyMutate;
1978+
return Effect.ConditionallyMutateIterator;
19531979
}
19541980
} else {
19551981
return Effect.ConditionallyMutate;

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,55 @@ import { useIdentity, Stringify } from "shared-runtime";
5050
* (2) the 1st argument might mutate its callee
5151
*/
5252
function Component(t0) {
53-
const $ = _c(4);
53+
const $ = _c(10);
5454
const { value } = t0;
55-
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
56-
useIdentity();
57-
const derived = Array.from(arr, _temp);
5855
let t1;
59-
if ($[0] !== derived) {
60-
t1 = derived.at(-1);
61-
$[0] = derived;
62-
$[1] = t1;
56+
let t2;
57+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
58+
t1 = { value: "foo" };
59+
t2 = { value: "bar" };
60+
$[0] = t1;
61+
$[1] = t2;
6362
} else {
64-
t1 = $[1];
63+
t1 = $[0];
64+
t2 = $[1];
6565
}
66-
let t2;
67-
if ($[2] !== t1) {
68-
t2 = <Stringify>{t1}</Stringify>;
69-
$[2] = t1;
70-
$[3] = t2;
66+
let t3;
67+
if ($[2] !== value) {
68+
t3 = [t1, t2, { value }];
69+
$[2] = value;
70+
$[3] = t3;
71+
} else {
72+
t3 = $[3];
73+
}
74+
const arr = t3;
75+
useIdentity();
76+
let t4;
77+
if ($[4] !== arr) {
78+
t4 = Array.from(arr, _temp);
79+
$[4] = arr;
80+
$[5] = t4;
81+
} else {
82+
t4 = $[5];
83+
}
84+
const derived = t4;
85+
let t5;
86+
if ($[6] !== derived) {
87+
t5 = derived.at(-1);
88+
$[6] = derived;
89+
$[7] = t5;
90+
} else {
91+
t5 = $[7];
92+
}
93+
let t6;
94+
if ($[8] !== t5) {
95+
t6 = <Stringify>{t5}</Stringify>;
96+
$[8] = t5;
97+
$[9] = t6;
7198
} else {
72-
t2 = $[3];
99+
t6 = $[9];
73100
}
74-
return t2;
101+
return t6;
75102
}
76103
function _temp(x, idx) {
77104
return { ...x, id: idx };

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,55 @@ import { useIdentity, Stringify } from "shared-runtime";
5050
* (2) the 1st argument might mutate its callee
5151
*/
5252
function Component(t0) {
53-
const $ = _c(4);
53+
const $ = _c(10);
5454
const { value } = t0;
55-
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
56-
useIdentity();
57-
const derived = Array.from(arr);
5855
let t1;
59-
if ($[0] !== derived) {
60-
t1 = derived.at(-1);
61-
$[0] = derived;
62-
$[1] = t1;
56+
let t2;
57+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
58+
t1 = { value: "foo" };
59+
t2 = { value: "bar" };
60+
$[0] = t1;
61+
$[1] = t2;
6362
} else {
64-
t1 = $[1];
63+
t1 = $[0];
64+
t2 = $[1];
6565
}
66-
let t2;
67-
if ($[2] !== t1) {
68-
t2 = <Stringify>{t1}</Stringify>;
69-
$[2] = t1;
70-
$[3] = t2;
66+
let t3;
67+
if ($[2] !== value) {
68+
t3 = [t1, t2, { value }];
69+
$[2] = value;
70+
$[3] = t3;
71+
} else {
72+
t3 = $[3];
73+
}
74+
const arr = t3;
75+
useIdentity();
76+
let t4;
77+
if ($[4] !== arr) {
78+
t4 = Array.from(arr);
79+
$[4] = arr;
80+
$[5] = t4;
81+
} else {
82+
t4 = $[5];
83+
}
84+
const derived = t4;
85+
let t5;
86+
if ($[6] !== derived) {
87+
t5 = derived.at(-1);
88+
$[6] = derived;
89+
$[7] = t5;
90+
} else {
91+
t5 = $[7];
92+
}
93+
let t6;
94+
if ($[8] !== t5) {
95+
t6 = <Stringify>{t5}</Stringify>;
96+
$[8] = t5;
97+
$[9] = t6;
7198
} else {
72-
t2 = $[3];
99+
t6 = $[9];
73100
}
74-
return t2;
101+
return t6;
75102
}
76103

77104
export const FIXTURE_ENTRYPOINT = {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
77
function Component({value}) {
88
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
99
useIdentity();
10-
const derived = Array.from(arr, mutateAndReturn);
10+
const derived = Array.from(arr).map(mutateAndReturn);
1111
return (
1212
<Stringify>
1313
{derived.at(0)}
@@ -19,7 +19,7 @@ function Component({value}) {
1919
export const FIXTURE_ENTRYPOINT = {
2020
fn: Component,
2121
params: [{value: 5}],
22-
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
22+
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
2323
};
2424

2525
```
@@ -35,7 +35,7 @@ function Component(t0) {
3535
const { value } = t0;
3636
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
3737
useIdentity();
38-
const derived = Array.from(arr, mutateAndReturn);
38+
const derived = Array.from(arr).map(mutateAndReturn);
3939
let t1;
4040
if ($[0] !== derived) {
4141
t1 = derived.at(0);
@@ -72,12 +72,13 @@ function Component(t0) {
7272
export const FIXTURE_ENTRYPOINT = {
7373
fn: Component,
7474
params: [{ value: 5 }],
75-
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
75+
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }],
7676
};
7777

7878
```
7979
8080
### Eval output
8181
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
8282
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
83-
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
83+
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
84+
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":7,"wat0":"joe"}]}</div>

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
33
function Component({value}) {
44
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
55
useIdentity();
6-
const derived = Array.from(arr, mutateAndReturn);
6+
const derived = Array.from(arr).map(mutateAndReturn);
77
return (
88
<Stringify>
99
{derived.at(0)}
@@ -15,5 +15,5 @@ function Component({value}) {
1515
export const FIXTURE_ENTRYPOINT = {
1616
fn: Component,
1717
params: [{value: 5}],
18-
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
18+
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
1919
};

0 commit comments

Comments
 (0)