diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index c2815ce7f5d..67f1e75a236 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -25,6 +25,7 @@ import { InstructionValue, isArrayType, isMapType, + isPrimitiveType, isSetType, makeIdentifierId, ObjectMethod, @@ -464,12 +465,15 @@ function applyEffect( break; } default: { - effects.push({ - // OK: recording information flow - kind: 'CreateFrom', // prev Alias - from: effect.from, - into: effect.into, - }); + // Even if the source is non-primitive, the destination may be. skip primitives. + if (!isPrimitiveType(effect.into.identifier)) { + effects.push({ + // OK: recording information flow + kind: 'CreateFrom', // prev Alias + from: effect.from, + into: effect.into, + }); + } } } break; @@ -1622,6 +1626,7 @@ function computeSignatureForInstruction( suggestions: null, }, }); + effects.push({kind: 'Assign', from: value.value, into: lvalue}); break; } case 'TypeCastExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts index 22e8e2bc514..39a8e71f256 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts @@ -28,12 +28,13 @@ import {printIdentifier, printPlace} from '../HIR/PrintHIR'; import {MutationKind} from './InferMutationAliasingFunctionEffects'; const DEBUG = false; +const VERBOSE = false; /** * Infers mutable ranges for all values. */ export function inferMutationAliasingRanges(fn: HIRFunction): void { - if (DEBUG) { + if (VERBOSE) { console.log(); console.log(); console.log(); @@ -81,7 +82,7 @@ export function inferMutationAliasingRanges(fn: HIRFunction): void { const seenBlocks = new Set(); for (const block of fn.body.blocks.values()) { for (const phi of block.phis) { - state.create(phi.place); + state.create(phi.place, true); for (const [pred, operand] of phi.operands) { if (!seenBlocks.has(pred)) { // NOTE: annotation required to actually typecheck and not silently infer `any` @@ -172,7 +173,7 @@ export function inferMutationAliasingRanges(fn: HIRFunction): void { } } - if (DEBUG) { + if (VERBOSE) { console.log(state.debug()); console.log(pretty(mutations)); } @@ -185,7 +186,7 @@ export function inferMutationAliasingRanges(fn: HIRFunction): void { mutation.kind, ); } - if (DEBUG) { + if (VERBOSE) { console.log(state.debug()); } fn.aliasingEffects ??= []; @@ -364,7 +365,7 @@ export function inferMutationAliasingRanges(fn: HIRFunction): void { } } - if (DEBUG) { + if (VERBOSE) { console.log(printFunction(fn)); } } @@ -377,11 +378,12 @@ type Node = { edges: Array<{index: number; node: Identifier; kind: 'capture' | 'alias'}>; transitive: MutationKind; local: MutationKind; + phi: boolean; }; class AliasingState { nodes: Map = new Map(); - create(place: Place): void { + create(place: Place, phi: boolean = false): void { this.nodes.set(place.identifier, { id: place.identifier, createdFrom: new Map(), @@ -390,6 +392,7 @@ class AliasingState { edges: [], transitive: MutationKind.None, local: MutationKind.None, + phi, }); } @@ -398,7 +401,7 @@ class AliasingState { const fromNode = this.nodes.get(from.identifier); const toNode = this.nodes.get(into.identifier); if (fromNode == null || toNode == null) { - if (DEBUG) { + if (VERBOSE) { console.log( `skip: createFrom ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`, ); @@ -415,7 +418,7 @@ class AliasingState { const fromNode = this.nodes.get(from.identifier); const toNode = this.nodes.get(into.identifier); if (fromNode == null || toNode == null) { - if (DEBUG) { + if (VERBOSE) { console.log( `skip: capture ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`, ); @@ -432,7 +435,7 @@ class AliasingState { const fromNode = this.nodes.get(from.identifier); const toNode = this.nodes.get(into.identifier); if (fromNode == null || toNode == null) { - if (DEBUG) { + if (VERBOSE) { console.log( `skip: assign ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`, ); @@ -453,9 +456,13 @@ class AliasingState { kind: MutationKind, ): void { const seen = new Set(); - const queue = [{place: start, transitive}]; + const queue: Array<{ + place: Identifier; + transitive: boolean; + direction: 'backwards' | 'forwards'; + }> = [{place: start, transitive, direction: 'backwards'}]; while (queue.length !== 0) { - const {place: current, transitive} = queue.pop()!; + const {place: current, transitive, direction} = queue.pop()!; if (seen.has(current)) { continue; } @@ -471,7 +478,7 @@ class AliasingState { } if (DEBUG) { console.log( - `mutate $${node.id.id} via ${printIdentifier(start)} at [${end}]`, + `[${end}] mutate index=${index} ${printIdentifier(start)}: ${printIdentifier(node.id)}`, ); } node.id.mutableRange.end = makeInstructionId( @@ -491,24 +498,31 @@ class AliasingState { if (edge.index >= index) { break; } - queue.push({place: edge.node, transitive}); + queue.push({place: edge.node, transitive, direction: 'forwards'}); } for (const [alias, when] of node.createdFrom) { if (when >= index) { continue; } - queue.push({place: alias, transitive: true}); + queue.push({place: alias, transitive: true, direction: 'backwards'}); } - /** - * all mutations affect backward alias edges by the rules: - * - Alias a -> b, mutate(b) => mutate(a) - * - Alias a -> b, mutateTransitive(b) => mutate(a) - */ - for (const [alias, when] of node.aliases) { - if (when >= index) { - continue; + if (direction === 'backwards' || !node.phi) { + /** + * all mutations affect backward alias edges by the rules: + * - Alias a -> b, mutate(b) => mutate(a) + * - Alias a -> b, mutateTransitive(b) => mutate(a) + * + * However, if we reached a phi because one of its inputs was mutated + * (and we're advancing "forwards" through that node's edges), then + * we know we've already processed the mutation at its source. The + * phi's other inputs can't be affected. + */ + for (const [alias, when] of node.aliases) { + if (when >= index) { + continue; + } + queue.push({place: alias, transitive, direction: 'backwards'}); } - queue.push({place: alias, transitive}); } /** * but only transitive mutations affect captures @@ -518,10 +532,18 @@ class AliasingState { if (when >= index) { continue; } - queue.push({place: capture, transitive}); + queue.push({place: capture, transitive, direction: 'backwards'}); } } } + if (DEBUG) { + const nodes = new Map(); + for (const id of seen) { + const node = this.nodes.get(id); + nodes.set(id.id, node); + } + console.log(pretty(nodes)); + } } debug(): string { diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 39b2f148980..0e0be0da116 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -44,7 +44,7 @@ import { getHookKind, makeIdentifierName, } from '../HIR/HIR'; -import {printIdentifier, printPlace} from '../HIR/PrintHIR'; +import {printIdentifier, printInstruction, printPlace} from '../HIR/PrintHIR'; import {eachPatternOperand} from '../HIR/visitors'; import {Err, Ok, Result} from '../Utils/Result'; import {GuardKind} from '../Utils/RuntimeDiagnosticConstants'; @@ -1310,7 +1310,7 @@ function codegenInstructionNullable( }); CompilerError.invariant(value?.type === 'FunctionExpression', { reason: 'Expected a function as a function declaration value', - description: null, + description: `Got ${value == null ? String(value) : value.type} at ${printInstruction(instr)}`, loc: instr.value.loc, suggestions: null, }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections-2.expect.md new file mode 100644 index 00000000000..013da083261 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections-2.expect.md @@ -0,0 +1,67 @@ + +## Input + +```javascript +// @enableNewMutationAliasingModel +import {Stringify} from 'shared-runtime'; + +function Component({a, b}) { + const x = {a, b}; + const f = () => { + const y = [x]; + return y[0]; + }; + const x0 = f(); + const z = [x0]; + const x1 = z[0]; + x1.key = 'value'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 0, b: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel +import { Stringify } from "shared-runtime"; + +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let t1; + if ($[0] !== a || $[1] !== b) { + const x = { a, b }; + const f = () => { + const y = [x]; + return y[0]; + }; + + const x0 = f(); + const z = [x0]; + const x1 = z[0]; + x1.key = "value"; + t1 = ; + $[0] = a; + $[1] = b; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 0, b: 1 }], +}; + +``` + +### Eval output +(kind: ok)
{"x":{"a":0,"b":1,"key":"value"}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections-2.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections-2.js new file mode 100644 index 00000000000..6a981e84089 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections-2.js @@ -0,0 +1,20 @@ +// @enableNewMutationAliasingModel +import {Stringify} from 'shared-runtime'; + +function Component({a, b}) { + const x = {a, b}; + const f = () => { + const y = [x]; + return y[0]; + }; + const x0 = f(); + const z = [x0]; + const x1 = z[0]; + x1.key = 'value'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 0, b: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections.expect.md new file mode 100644 index 00000000000..f8ceba27158 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections.expect.md @@ -0,0 +1,67 @@ + +## Input + +```javascript +// @enableNewMutationAliasingModel +import {Stringify} from 'shared-runtime'; + +function Component({a, b}) { + const x = {a, b}; + const y = [x]; + const f = () => { + const x0 = y[0]; + return [x0]; + }; + const z = f(); + const x1 = z[0]; + x1.key = 'value'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 0, b: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel +import { Stringify } from "shared-runtime"; + +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let t1; + if ($[0] !== a || $[1] !== b) { + const x = { a, b }; + const y = [x]; + const f = () => { + const x0 = y[0]; + return [x0]; + }; + + const z = f(); + const x1 = z[0]; + x1.key = "value"; + t1 = ; + $[0] = a; + $[1] = b; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 0, b: 1 }], +}; + +``` + +### Eval output +(kind: ok)
{"x":{"a":0,"b":1,"key":"value"}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections.js new file mode 100644 index 00000000000..aecd27a0930 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-function-call-indirections.js @@ -0,0 +1,20 @@ +// @enableNewMutationAliasingModel +import {Stringify} from 'shared-runtime'; + +function Component({a, b}) { + const x = {a, b}; + const y = [x]; + const f = () => { + const x0 = y[0]; + return [x0]; + }; + const z = f(); + const x1 = z[0]; + x1.key = 'value'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 0, b: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-indirections.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-indirections.expect.md new file mode 100644 index 00000000000..5f14dd1fe07 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-indirections.expect.md @@ -0,0 +1,60 @@ + +## Input + +```javascript +// @enableNewMutationAliasingModel +import {Stringify} from 'shared-runtime'; + +function Component({a, b}) { + const x = {a, b}; + const y = [x]; + const x0 = y[0]; + const z = [x0]; + const x1 = z[0]; + x1.key = 'value'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 0, b: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel +import { Stringify } from "shared-runtime"; + +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let t1; + if ($[0] !== a || $[1] !== b) { + const x = { a, b }; + const y = [x]; + const x0 = y[0]; + const z = [x0]; + const x1 = z[0]; + x1.key = "value"; + t1 = ; + $[0] = a; + $[1] = b; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 0, b: 1 }], +}; + +``` + +### Eval output +(kind: ok)
{"x":{"a":0,"b":1,"key":"value"}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-indirections.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-indirections.js new file mode 100644 index 00000000000..ba8808eedfe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-boxing-unboxing-indirections.js @@ -0,0 +1,17 @@ +// @enableNewMutationAliasingModel +import {Stringify} from 'shared-runtime'; + +function Component({a, b}) { + const x = {a, b}; + const y = [x]; + const x0 = y[0]; + const z = [x0]; + const x1 = z[0]; + x1.key = 'value'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 0, b: 1}], +};