Skip to content

Commit 8f9719a

Browse files
committed
[compiler] Foundation of new mutability and aliasing (alternate take)
Alternate take at a new mutability and alising model, aiming to replace `InferReferenceEffects` and `InferMutableRanges`. My initial passes at this were more complicated than necessary, and I've iterated to refine and distill this down to the core concepts. There are two effects that track information flow: `capture` and `alias`: * Given distinct values A and B. After capture A -> B, mutate(B) does *not* modify A. This more precisely captures the semantic of the previous `Store` effect. As an example, `array.push(item)` has the effect `capture item -> array` and `mutate(array)`. The array is modified, not the item. * Given distinct values A and B. After alias A -> B, mutate(B) *does* modify A. This is because B now refers to the same value as A. * Given distinct values A and B, after *either* capture A -> B *or* alias A -> B, transitiveMutate(B) counts as a mutation of A. Conceptually "capture A -> B" means that a reference to A was "captured" (or stored) within A, but there is not a directly aliasing. Whereas "alias A -> B" means literal value aliasing. The idea is that our previous sequential fixpoint loops in InferMutableRanges can instead work by first looking at transitive mutations, then look at non-transitive mutations. And aliasing groups can be built purely based on the `alias` effect. Lots more to do here but the structure is coming together. ghstack-source-id: 5b9c88a Pull Request resolved: #33346
1 parent d125704 commit 8f9719a

File tree

9 files changed

+1592
-20
lines changed

9 files changed

+1592
-20
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureF
104104
import {CompilerError} from '..';
105105
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
106106
import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
107+
import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
108+
import {inferMutationAliasingRanges} from '../Inference/InferMutationAliasingRanges';
107109

108110
export type CompilerPipelineValue =
109111
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -226,6 +228,16 @@ function runWithEnvironment(
226228
analyseFunctions(hir);
227229
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
228230

231+
const mutabilityAliasingErrors = inferMutationAliasingEffects(hir);
232+
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
233+
if (env.isInferredMemoEnabled) {
234+
if (mutabilityAliasingErrors.isErr()) {
235+
throw mutabilityAliasingErrors.unwrapErr();
236+
}
237+
}
238+
inferMutationAliasingRanges(hir);
239+
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
240+
229241
const fnEffectErrors = inferReferenceEffects(hir);
230242
if (env.isInferredMemoEnabled) {
231243
if (fnEffectErrors.length > 0) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export type ReactiveInstruction = {
101101
id: InstructionId;
102102
lvalue: Place | null;
103103
value: ReactiveValue;
104+
effects?: Array<AliasingEffect> | null; // TODO make non-optional
104105
loc: SourceLocation;
105106
};
106107

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import {CompilerError} from '../CompilerError';
9+
import {AliasingSignature} from '../Inference/InferMutationAliasingEffects';
910
import {Effect, ValueKind, ValueReason} from './HIR';
1011
import {
1112
BuiltInType,
@@ -179,6 +180,8 @@ export type FunctionSignature = {
179180
impure?: boolean;
180181

181182
canonicalName?: string;
183+
184+
aliasing?: AliasingSignature | null;
182185
};
183186

184187
/*
@@ -332,6 +335,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
332335
returnValueKind: ValueKind.Mutable,
333336
noAlias: true,
334337
mutableOnlyIfOperandsAreMutable: true,
338+
aliasing: null,
335339
}),
336340
],
337341
[

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

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ import type {
3535
Type,
3636
} from './HIR';
3737
import {GotoVariant, InstructionKind} from './HIR';
38+
import {
39+
AliasedPlace,
40+
AliasingEffect,
41+
} from '../Inference/InferMutationAliasingEffects';
3842

3943
export type Options = {
4044
indent: number;
@@ -151,7 +155,10 @@ export function printMixedHIR(
151155

152156
export function printInstruction(instr: ReactiveInstruction): string {
153157
const id = `[${instr.id}]`;
154-
const value = printInstructionValue(instr.value);
158+
let value = printInstructionValue(instr.value);
159+
if (instr.effects != null) {
160+
value += `\n ${instr.effects.map(printAliasingEffect).join('\n ')}`;
161+
}
155162

156163
if (instr.lvalue !== null) {
157164
return `${id} ${printPlace(instr.lvalue)} = ${value}`;
@@ -933,3 +940,46 @@ function getFunctionName(
933940
return defaultValue;
934941
}
935942
}
943+
944+
export function printAliasingEffect(effect: AliasingEffect): string {
945+
switch (effect.kind) {
946+
case 'Alias': {
947+
return `Alias ${printPlaceForAliasEffect(effect.from)} -> ${printPlaceForAliasEffect(effect.into)}`;
948+
}
949+
case 'Capture': {
950+
return `Capture ${printPlaceForAliasEffect(effect.from)} -> ${printPlaceForAliasEffect(effect.into)}`;
951+
}
952+
case 'Create': {
953+
return `Create ${printPlaceForAliasEffect(effect.into)} = ${effect.value}`;
954+
}
955+
case 'CreateFrom': {
956+
return `Create ${printPlaceForAliasEffect(effect.into)} = kindOf(${printPlaceForAliasEffect(effect.from)})`;
957+
}
958+
case 'Apply': {
959+
const params = effect.params.map(printAliasedPlace).join(', ');
960+
const rest = effect.rest != null ? printAliasedPlace(effect.rest) : '';
961+
const returns = printAliasedPlace(effect.returns);
962+
return `Apply ${returns} = ${printAliasedPlace(effect.function)} as ${effect.receiver} ( ${params} ${params.length !== 0 && rest !== '' ? ', ...' : ''}${rest})`;
963+
}
964+
case 'Freeze': {
965+
return `Freeze ${printPlaceForAliasEffect(effect.value)} ${effect.reason}`;
966+
}
967+
case 'Mutate':
968+
case 'MutateConditionally':
969+
case 'MutateTransitive':
970+
case 'MutateTransitiveConditionally': {
971+
return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}`;
972+
}
973+
default: {
974+
assertExhaustive(effect, `Unexpected kind '${(effect as any).kind}'`);
975+
}
976+
}
977+
}
978+
979+
function printPlaceForAliasEffect(place: Place): string {
980+
return printIdentifier(place.identifier);
981+
}
982+
983+
function printAliasedPlace(place: AliasedPlace): string {
984+
return place.kind + ' ' + printPlaceForAliasEffect(place.place);
985+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export function debugAliases(aliases: DisjointSet<Identifier>): void {
119119
*
120120
* This ensures that we fixpoint the mutable ranges themselves and not just the alias sets.
121121
*/
122-
function canonicalize(
122+
export function canonicalize(
123123
aliases: DisjointSet<Identifier>,
124124
): Map<Identifier, string> {
125125
const entries = new Map<Identifier, string>();
@@ -132,7 +132,7 @@ function canonicalize(
132132
return entries;
133133
}
134134

135-
function areEqualMaps<T, U>(a: Map<T, U>, b: Map<T, U>): boolean {
135+
export function areEqualMaps<T, U>(a: Map<T, U>, b: Map<T, U>): boolean {
136136
if (a.size !== b.size) {
137137
return false;
138138
}

0 commit comments

Comments
 (0)