Skip to content

Mirror of upstream PR #33184 #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
18e4165
[compiler] Repro for false positive ValidateNoFreezingKnownMutableFun…
josephsavona May 3, 2025
0ee6891
Update on "[compiler] Repro for false positive ValidateNoFreezingKnow…
josephsavona May 3, 2025
e9b19a6
[compiler] Correctly infer context mutation places as outer (context)…
josephsavona May 3, 2025
15307de
Update base for Update on "[compiler] Correctly infer context mutatio…
josephsavona May 8, 2025
3d646a8
Update on "[compiler] Correctly infer context mutation places as oute…
josephsavona May 8, 2025
cd2537c
[compiler][wip] Infer alias effects for function expressions
josephsavona May 8, 2025
05e75d8
Update base for Update on "[compiler][wip] Infer alias effects for fu…
josephsavona May 8, 2025
c3a733f
Update on "[compiler][wip] Infer alias effects for function expressions"
josephsavona May 8, 2025
7936a99
Update base for Update on "[compiler][wip] Infer alias effects for fu…
josephsavona May 8, 2025
97442d7
Update on "[compiler][wip] Infer alias effects for function expressions"
josephsavona May 8, 2025
fd0df94
Update base for Update on "[compiler][wip] Infer alias effects for fu…
josephsavona May 8, 2025
1fec32e
Update on "[compiler][wip] Infer alias effects for function expressions"
josephsavona May 8, 2025
3b83903
[compiler] Move co-mutation range extension to InferMutableRanges
josephsavona May 8, 2025
390d516
Update base for Update on "[compiler] Move co-mutation range extensio…
josephsavona May 9, 2025
ec552a5
Update on "[compiler] Move co-mutation range extension to InferMutabl…
josephsavona May 9, 2025
63e29f7
Update base for Update on "[compiler] Move co-mutation range extensio…
josephsavona May 9, 2025
206e7e9
Update on "[compiler] Move co-mutation range extension to InferMutabl…
josephsavona May 9, 2025
a25a7e1
[compiler] Fixture tests for PropertyStore effects
josephsavona May 9, 2025
90ed7e8
[compiler] Fix for PropertyStore object effect
josephsavona May 9, 2025
b9367e0
Update base for Update on "[compiler] Fix for PropertyStore object ef…
josephsavona May 9, 2025
5bf3eb6
Update on "[compiler] Fix for PropertyStore object effect"
josephsavona May 9, 2025
52a34e4
Update base for Update on "[compiler] Fix for PropertyStore object ef…
josephsavona May 9, 2025
98b02b9
Update on "[compiler] Fix for PropertyStore object effect"
josephsavona May 9, 2025
23222b4
Update base for Update on "[compiler] Fix for PropertyStore object ef…
josephsavona May 9, 2025
45a889c
Update on "[compiler] Fix for PropertyStore object effect"
josephsavona May 9, 2025
275866f
Update base for Update on "[compiler] Fix for PropertyStore object ef…
josephsavona May 12, 2025
4afa39d
Update on "[compiler] Fix for PropertyStore object effect"
josephsavona May 12, 2025
68ab155
[compiler] allow local fixtures to be excluded from git w "nocommit" …
josephsavona May 12, 2025
5fc610e
[compiler] avoid empty switch case bodies
josephsavona May 12, 2025
bfff07b
[compiler] Repro for imprecise memo due to closure capturing changes
josephsavona May 12, 2025
f40a865
Update base for Update on "[compiler] Repro for imprecise memo due to…
josephsavona May 13, 2025
b0b09a7
Update on "[compiler] Repro for imprecise memo due to closure capturi…
josephsavona May 13, 2025
c3b87cc
[compiler] Sketch of new mutation/aliasing effects
josephsavona May 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
debug/
target/

nocommit*.js
nocommit*.expect.md

# These are backup files generated by rustfmt
**/*.rs.bk

Expand Down
44 changes: 44 additions & 0 deletions compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ export type FunctionEffect =
places: ReadonlySet<Place>;
effect: Effect;
loc: SourceLocation;
}
| {
kind: 'CaptureEffect';
places: ReadonlySet<Place>;
};

/*
Expand Down Expand Up @@ -1430,6 +1434,46 @@ export const ValueKindSchema = z.enum([
ValueKind.Context,
]);

export type AliasingEffect =
/**
* Freezes the operand if not already frozen
*/
| {kind: 'Freeze'; place: Place}
/**
* Known mutation of a value, which may effect that value or values that it contains.
* This is rare!
*/
| {kind: 'MutateTransitive'; place: Place}
/**
* Known mutation of a specific value, targeting only that specific value but not
* values that it contains.
*
* Example: `array.push(item)` mutates the array but does not mutate items stored in the array.
*/
| {kind: 'MutateLocal'; place: Place}
/**
* Possible mutation of a specific value
*/
| {kind: 'ConditionallyMutate'; place: Place}
/**
* Direct aliasing of one identifier by another identifier
* Examples: `x = y` (from y -> to x) or phis (from operand -> to phi)
*/
| {kind: 'Alias'; from: Place; to: Place}
/**
* Direct aliasing of an identifier (or a sub-path), or storing a value/sub-path
* into a part of another object.
*
* One of from.path and/or to.path must be non-null (else this is equivalent to Alias)
*/
| {
kind: 'Capture';
from: {place: Place; path: '*' | null};
to: {place: Place; path: '*' | null};
}
// Known mutation of a global
| {kind: 'MutateGlobal'; place: Place};

// The effect with which a value is modified.
export enum Effect {
// Default value: not allowed after lifetime inference
Expand Down
25 changes: 18 additions & 7 deletions compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,12 +546,23 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
const effects =
instrValue.loweredFunc.func.effects
?.map(effect => {
if (effect.kind === 'ContextMutation') {
return `ContextMutation places=[${[...effect.places]
.map(place => printPlace(place))
.join(', ')}] effect=${effect.effect}`;
} else {
return `GlobalMutation`;
switch (effect.kind) {
case 'ContextMutation': {
return `ContextMutation places=[${[...effect.places]
.map(place => printPlace(place))
.join(', ')}] effect=${effect.effect}`;
}
case 'GlobalMutation': {
return 'GlobalMutation';
}
case 'ReactMutation': {
return 'ReactMutation';
}
case 'CaptureEffect': {
return `CaptureEffect places=[${[...effect.places]
.map(place => printPlace(place))
.join(', ')}]`;
}
}
})
.join(', ') ?? '';
Expand Down Expand Up @@ -720,7 +731,7 @@ function isMutable(range: MutableRange): boolean {
}

const DEBUG_MUTABLE_RANGES = false;
function printMutableRange(identifier: Identifier): string {
export function printMutableRange(identifier: Identifier): string {
if (DEBUG_MUTABLE_RANGES) {
// if debugging, print both the identifier and scope range if they differ
const range = identifier.mutableRange;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
HIRFunction,
Identifier,
LoweredFunction,
Place,
isRefOrRefValue,
makeInstructionId,
} from '../HIR';
Expand All @@ -19,15 +20,21 @@ import {inferReactiveScopeVariables} from '../ReactiveScopes';
import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
import {inferMutableRanges} from './InferMutableRanges';
import inferReferenceEffects from './InferReferenceEffects';
import DisjointSet from '../Utils/DisjointSet';
import {
eachInstructionLValue,
eachInstructionValueOperand,
} from '../HIR/visitors';
import {Iterable_some} from '../Utils/utils';

export default function analyseFunctions(func: HIRFunction): void {
for (const [_, block] of func.body.blocks) {
for (const instr of block.instructions) {
switch (instr.value.kind) {
case 'ObjectMethod':
case 'FunctionExpression': {
lower(instr.value.loweredFunc.func);
infer(instr.value.loweredFunc);
const aliases = lower(instr.value.loweredFunc.func);
infer(instr.value.loweredFunc, aliases);

/**
* Reset mutable range for outer inferReferenceEffects
Expand All @@ -44,21 +51,73 @@ export default function analyseFunctions(func: HIRFunction): void {
}
}

function lower(func: HIRFunction): void {
function lower(func: HIRFunction): DisjointSet<Identifier> {
analyseFunctions(func);
inferReferenceEffects(func, {isFunctionExpression: true});
deadCodeElimination(func);
inferMutableRanges(func);
const aliases = inferMutableRanges(func);
rewriteInstructionKindsBasedOnReassignment(func);
inferReactiveScopeVariables(func);
func.env.logger?.debugLogIRs?.({
kind: 'hir',
name: 'AnalyseFunction (inner)',
value: func,
});
inferAliasesForCapturing(func, aliases);
return aliases;
}

/**
* The alias sets returned by InferMutableRanges() accounts only for aliases that
* are known to mutate together. Notably this skips cases where a value is captured
* into some other value, but neither is subsequently mutated. An example is pushing
* a mutable value onto an array, where neither the array or value are subsequently
* mutated.
*
* This function extends the aliases sets to account for such capturing, so that we
* can detect cases where one of the values in a set is mutated later (in an outer function)
* we can correctly infer them as mutating together.
*/
function inferAliasesForCapturing(
fn: HIRFunction,
aliases: DisjointSet<Identifier>,
): void {
for (const block of fn.body.blocks.values()) {
for (const instr of block.instructions) {
const {lvalue, value} = instr;
const hasCapture =
lvalue.effect === Effect.Store ||
Iterable_some(
eachInstructionValueOperand(value),
operand => operand.effect === Effect.Capture,
);
if (!hasCapture) {
continue;
}
const operands: Array<Identifier> = [];
for (const lvalue of eachInstructionLValue(instr)) {
operands.push(lvalue.identifier);
}
for (const operand of eachInstructionValueOperand(instr.value)) {
if (
operand.effect === Effect.Store ||
operand.effect === Effect.Mutate ||
operand.effect === Effect.Capture
) {
operands.push(operand.identifier);
}
}
if (operands.length > 1) {
aliases.union(operands);
}
}
}
}

function infer(loweredFunc: LoweredFunction): void {
function infer(
loweredFunc: LoweredFunction,
aliases: DisjointSet<Identifier>,
): void {
for (const operand of loweredFunc.func.context) {
const identifier = operand.identifier;
CompilerError.invariant(operand.effect === Effect.Unknown, {
Expand All @@ -85,6 +144,23 @@ function infer(loweredFunc: LoweredFunction): void {
operand.effect = Effect.Read;
}
}
const contextIdentifiers = new Map(
loweredFunc.func.context.map(place => [place.identifier, place]),
);
for (const set of aliases.buildSets()) {
const contextOperands: Set<Place> = new Set(
[...set]
.map(identifier => contextIdentifiers.get(identifier))
.filter(place => place != null) as Array<Place>,
);
if (contextOperands.size !== 0) {
loweredFunc.func.effects ??= [];
loweredFunc.func.effects?.push({
kind: 'CaptureEffect',
places: contextOperands,
});
}
}
}

function isMutatedOrReassigned(id: Identifier): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ function inferInstr(
alias = instrValue.value;
break;
}
case 'IteratorNext': {
alias = instrValue.collection;
break;
}
default:
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {HIRFunction, Identifier} from '../HIR/HIR';
import DisjointSet from '../Utils/DisjointSet';

export function inferAliasForFunctionCaptureEffects(
func: HIRFunction,
aliases: DisjointSet<Identifier>,
): void {
for (const [_, block] of func.body.blocks) {
for (const instr of block.instructions) {
const {value} = instr;
if (
value.kind !== 'FunctionExpression' &&
value.kind !== 'ObjectMethod'
) {
continue;
}
const loweredFunction = value.loweredFunc.func;
for (const effect of loweredFunction.effects ?? []) {
if (effect.kind !== 'CaptureEffect') {
continue;
}
aliases.union([...effect.places].map(place => place.identifier));
}
}
}
}
Loading
Loading