From 5464ce413c58fed9f609707f0c01bdb727b46bd9 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Thu, 1 Aug 2024 15:54:00 -0700 Subject: [PATCH] [wip][compiler] Stop rewriting IdentifierId in LeaveSSA ghstack-source-id: 1b653587414b4103f33f415ab6574e8dbedd90b1 Pull Request resolved: https://github.com/facebook/react/pull/30573 --- .../src/HIR/PrintHIR.ts | 3 +- .../ReactiveScopes/CodegenReactiveFunction.ts | 23 +-- .../ReactiveScopes/PromoteUsedTemporaries.ts | 55 +++++-- .../PropagateScopeDependencies.ts | 34 ++-- .../ReactiveScopes/PruneHoistedContexts.ts | 7 +- .../ReactiveScopes/PruneNonEscapingScopes.ts | 58 ++++--- .../ReactiveScopes/PruneTemporaryLValues.ts | 12 +- .../src/ReactiveScopes/RenameVariables.ts | 13 +- .../src/ReactiveScopes/index.ts | 2 +- .../src/SSA/LeaveSSA.ts | 149 +++++++++++++++++- 10 files changed, 277 insertions(+), 79 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index fd17822af0a7e..4fa2364bf7d33 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -465,7 +465,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { break; } case 'DeclareContext': { - value = `DeclareContext ${instrValue.lvalue.kind} ${printPlace( + value = `DeclareContext (${instrValue.lvalue.kind}) ${instrValue.lvalue.kind} ${printPlace( instrValue.lvalue.place, )}`; break; @@ -833,6 +833,7 @@ export function printPlace(place: Place): string { } export function printIdentifier(id: Identifier): string { + // return `${printName(id.name)}\$${id.id}${Number(id.declarationId) !== id.id ? `_d${id.declarationId}` : ''}${printScope(id.scope)}`; return `${printName(id.name)}\$${id.id}${printScope(id.scope)}`; } 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 b773192d57304..c5b200ae7ead2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -18,6 +18,7 @@ import {Environment, EnvironmentConfig, ExternalFunction} from '../HIR'; import { ArrayPattern, BlockId, + DeclarationId, GeneratedSource, Identifier, IdentifierId, @@ -309,9 +310,9 @@ function codegenReactiveFunction( ): Result { for (const param of fn.params) { if (param.kind === 'Identifier') { - cx.temp.set(param.identifier.id, null); + cx.temp.set(param.identifier.declarationId, null); } else { - cx.temp.set(param.place.identifier.id, null); + cx.temp.set(param.place.identifier.declarationId, null); } } @@ -392,7 +393,7 @@ class Context { env: Environment; fnName: string; #nextCacheIndex: number = 0; - #declarations: Set = new Set(); + #declarations: Set = new Set(); temp: Temporaries; errors: CompilerError = new CompilerError(); objectMethods: Map = new Map(); @@ -418,11 +419,11 @@ class Context { } declare(identifier: Identifier): void { - this.#declarations.add(identifier.id); + this.#declarations.add(identifier.declarationId); } hasDeclared(identifier: Identifier): boolean { - return this.#declarations.has(identifier.id); + return this.#declarations.has(identifier.declarationId); } synthesizeName(name: string): ValidIdentifierName { @@ -1147,7 +1148,7 @@ function codegenTerminal( let catchParam = null; if (terminal.handlerBinding !== null) { catchParam = convertIdentifier(terminal.handlerBinding.identifier); - cx.temp.set(terminal.handlerBinding.identifier.id, null); + cx.temp.set(terminal.handlerBinding.identifier.declarationId, null); } return t.tryStatement( codegenBlock(cx, terminal.block), @@ -1205,7 +1206,7 @@ function codegenInstructionNullable( kind !== InstructionKind.Reassign && place.identifier.name === null ) { - cx.temp.set(place.identifier.id, null); + cx.temp.set(place.identifier.declarationId, null); } const isDeclared = cx.hasDeclared(place.identifier); hasReasign ||= isDeclared; @@ -1261,7 +1262,7 @@ function codegenInstructionNullable( ); if (instr.lvalue !== null) { if (instr.value.kind !== 'StoreContext') { - cx.temp.set(instr.lvalue.identifier.id, expr); + cx.temp.set(instr.lvalue.identifier.declarationId, expr); return null; } else { // Handle chained reassignments for context variables @@ -1530,7 +1531,7 @@ function createCallExpression( } } -type Temporaries = Map; +type Temporaries = Map; function codegenLabel(id: BlockId): string { return `bb${id}`; @@ -1549,7 +1550,7 @@ function codegenInstruction( } if (instr.lvalue.identifier.name === null) { // temporary - cx.temp.set(instr.lvalue.identifier.id, value); + cx.temp.set(instr.lvalue.identifier.declarationId, value); return t.emptyStatement(); } else { const expressionValue = convertValueToExpression(value); @@ -2498,7 +2499,7 @@ function codegenPlaceToExpression(cx: Context, place: Place): t.Expression { } function codegenPlace(cx: Context, place: Place): t.Expression | t.JSXText { - let tmp = cx.temp.get(place.identifier.id); + let tmp = cx.temp.get(place.identifier.declarationId); if (tmp != null) { return tmp; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PromoteUsedTemporaries.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PromoteUsedTemporaries.ts index 65b27fe639f23..1f4733b00478b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PromoteUsedTemporaries.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PromoteUsedTemporaries.ts @@ -8,8 +8,8 @@ import {CompilerError} from '../CompilerError'; import {GeneratedSource} from '../HIR'; import { + DeclarationId, Identifier, - IdentifierId, InstructionId, Place, PrunedReactiveScopeBlock, @@ -24,7 +24,6 @@ import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; class Visitor extends ReactiveFunctionVisitor { override visitScope(scopeBlock: ReactiveScopeBlock, state: State): void { - this.traverseScope(scopeBlock, state); for (const dep of scopeBlock.scope.dependencies) { const {identifier} = dep; if (identifier.name == null) { @@ -43,21 +42,23 @@ class Visitor extends ReactiveFunctionVisitor { promoteIdentifier(declaration.identifier, state); } } + this.traverseScope(scopeBlock, state); } override visitPrunedScope( scopeBlock: PrunedReactiveScopeBlock, state: State, ): void { - this.traversePrunedScope(scopeBlock, state); for (const [, declaration] of scopeBlock.scope.declarations) { if ( declaration.identifier.name == null && - state.pruned.get(declaration.identifier.id)?.usedOutsideScope === true + state.pruned.get(declaration.identifier.declarationId) + ?.usedOutsideScope === true ) { promoteIdentifier(declaration.identifier, state); } } + this.traversePrunedScope(scopeBlock, state); } override visitParam(place: Place, state: State): void { @@ -93,11 +94,38 @@ class Visitor extends ReactiveFunctionVisitor { } } -type JsxExpressionTags = Set; +class Visitor2 extends ReactiveFunctionVisitor { + override visitPlace(_id: InstructionId, place: Place, state: State): void { + if ( + place.identifier.name === null && + state.promoted.has(place.identifier.declarationId) + ) { + promoteIdentifier(place.identifier, state); + } + } + override visitLValue( + _id: InstructionId, + _lvalue: Place, + _state: State, + ): void { + this.visitPlace(_id, _lvalue, _state); + } + override visitReactiveFunctionValue( + _id: InstructionId, + _dependencies: Array, + fn: ReactiveFunction, + state: State, + ): void { + visitReactiveFunction(fn, this, state); + } +} + +type JsxExpressionTags = Set; type State = { tags: JsxExpressionTags; + promoted: Set; pruned: Map< - IdentifierId, + DeclarationId, {activeScopes: Array; usedOutsideScope: boolean} >; // true if referenced within another scope, false if only accessed outside of scopes }; @@ -108,9 +136,9 @@ class CollectPromotableTemporaries extends ReactiveFunctionVisitor { override visitPlace(_id: InstructionId, place: Place, state: State): void { if ( this.activeScopes.length !== 0 && - state.pruned.has(place.identifier.id) + state.pruned.has(place.identifier.declarationId) ) { - const prunedPlace = state.pruned.get(place.identifier.id)!; + const prunedPlace = state.pruned.get(place.identifier.declarationId)!; if (prunedPlace.activeScopes.indexOf(this.activeScopes.at(-1)!) === -1) { prunedPlace.usedOutsideScope = true; } @@ -124,7 +152,7 @@ class CollectPromotableTemporaries extends ReactiveFunctionVisitor { ): void { this.traverseValue(id, value, state); if (value.kind === 'JsxExpression' && value.tag.kind === 'Identifier') { - state.tags.add(value.tag.identifier.id); + state.tags.add(value.tag.identifier.declarationId); } } @@ -132,8 +160,8 @@ class CollectPromotableTemporaries extends ReactiveFunctionVisitor { scopeBlock: PrunedReactiveScopeBlock, state: State, ): void { - for (const [id] of scopeBlock.scope.declarations) { - state.pruned.set(id, { + for (const [_id, decl] of scopeBlock.scope.declarations) { + state.pruned.set(decl.identifier.declarationId, { activeScopes: [...this.activeScopes], usedOutsideScope: false, }); @@ -151,6 +179,7 @@ class CollectPromotableTemporaries extends ReactiveFunctionVisitor { export function promoteUsedTemporaries(fn: ReactiveFunction): void { const state: State = { tags: new Set(), + promoted: new Set(), pruned: new Map(), }; visitReactiveFunction(fn, new CollectPromotableTemporaries(), state); @@ -161,6 +190,7 @@ export function promoteUsedTemporaries(fn: ReactiveFunction): void { } } visitReactiveFunction(fn, new Visitor(), state); + visitReactiveFunction(fn, new Visitor2(), state); } function promoteIdentifier(identifier: Identifier, state: State): void { @@ -171,9 +201,10 @@ function promoteIdentifier(identifier: Identifier, state: State): void { loc: GeneratedSource, suggestions: null, }); - if (state.tags.has(identifier.id)) { + if (state.tags.has(identifier.declarationId)) { promoteTemporaryJsxTag(identifier); } else { promoteTemporary(identifier); } + state.promoted.add(identifier.declarationId); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts index 086ce017de533..7b4ed42fa008d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts @@ -8,6 +8,7 @@ import {CompilerError} from '../CompilerError'; import { BlockId, + DeclarationId, GeneratedSource, Identifier, IdentifierId, @@ -76,9 +77,9 @@ type TemporariesUsedOutsideDefiningScope = { * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) * and the scope where they are defined */ - declarations: Map; + declarations: Map; // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; + usedOutsideDeclaringScope: Set; }; class FindPromotedTemporaries extends ReactiveFunctionVisitor { scopes: Array = []; @@ -107,7 +108,10 @@ class FindPromotedTemporaries extends ReactiveFunctionVisitor; +type DeclMap = Map; type Decl = { id: InstructionId; scope: Stack; @@ -280,7 +286,7 @@ class PoisonState { } class Context { - #temporariesUsedOutsideScope: Set; + #temporariesUsedOutsideScope: Set; #declarations: DeclMap = new Map(); #reassignments: Map = new Map(); // Reactive dependencies used in the current reactive scope. @@ -307,7 +313,7 @@ class Context { #scopes: Stack = empty(); poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - constructor(temporariesUsedOutsideScope: Set) { + constructor(temporariesUsedOutsideScope: Set) { this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; } @@ -377,7 +383,9 @@ class Context { } isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has(place.identifier.id); + return this.#temporariesUsedOutsideScope.has( + place.identifier.declarationId, + ); } /* @@ -440,8 +448,8 @@ class Context { * on itself. */ declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.id)) { - this.#declarations.set(identifier.id, decl); + if (!this.#declarations.has(identifier.declarationId)) { + this.#declarations.set(identifier.declarationId, decl); } this.#reassignments.set(identifier, decl); } @@ -533,7 +541,7 @@ class Context { */ const currentDeclaration = this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.id); + this.#declarations.get(identifier.declarationId); const currentScope = this.currentScope.value?.value; return ( currentScope != null && @@ -599,7 +607,7 @@ class Context { * (all other decls e.g. `let x;` should be initialized in BuildHIR) */ const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.id, + maybeDependency.identifier.declarationId, ); if ( originalDeclaration !== undefined && diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts index 94efd27a694e0..f126454936702 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts @@ -6,6 +6,7 @@ */ import { + DeclarationId, Identifier, InstructionKind, ReactiveFunction, @@ -27,7 +28,7 @@ export function pruneHoistedContexts(fn: ReactiveFunction): void { visitReactiveFunction(fn, new Visitor(), hoistedIdentifiers); } -type HoistedIdentifiers = Set; +type HoistedIdentifiers = Set; class Visitor extends ReactiveFunctionTransform { override transformInstruction( @@ -39,13 +40,13 @@ class Visitor extends ReactiveFunctionTransform { instruction.value.kind === 'DeclareContext' && instruction.value.lvalue.kind === 'HoistedConst' ) { - state.add(instruction.value.lvalue.place.identifier); + state.add(instruction.value.lvalue.place.identifier.declarationId); return {kind: 'remove'}; } if ( instruction.value.kind === 'StoreContext' && - state.has(instruction.value.lvalue.place.identifier) + state.has(instruction.value.lvalue.place.identifier.declarationId) ) { return { kind: 'replace', diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts index aa9baec851f75..c7e8058c3084b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts @@ -7,6 +7,7 @@ import {CompilerError} from '../CompilerError'; import { + DeclarationId, Environment, IdentifierId, InstructionId, @@ -115,9 +116,9 @@ export function pruneNonEscapingScopes(fn: ReactiveFunction): void { const state = new State(fn.env); for (const param of fn.params) { if (param.kind === 'Identifier') { - state.declare(param.identifier.id); + state.declare(param.identifier.declarationId); } else { - state.declare(param.place.identifier.id); + state.declare(param.place.identifier.declarationId); } } visitReactiveFunction(fn, new CollectDependenciesVisitor(fn.env), state); @@ -193,14 +194,14 @@ function joinAliases( type IdentifierNode = { level: MemoizationLevel; memoized: boolean; - dependencies: Set; + dependencies: Set; scopes: Set; seen: boolean; }; // A scope node describing its dependencies type ScopeNode = { - dependencies: Array; + dependencies: Array; seen: boolean; }; @@ -211,18 +212,18 @@ class State { * Maps lvalues for LoadLocal to the identifier being loaded, to resolve indirections * in subsequent lvalues/rvalues */ - definitions: Map = new Map(); + definitions: Map = new Map(); - identifiers: Map = new Map(); + identifiers: Map = new Map(); scopes: Map = new Map(); - escapingValues: Set = new Set(); + escapingValues: Set = new Set(); constructor(env: Environment) { this.env = env; } // Declare a new identifier, used for function id and params - declare(id: IdentifierId): void { + declare(id: DeclarationId): void { this.identifiers.set(id, { level: MemoizationLevel.Never, memoized: false, @@ -240,14 +241,16 @@ class State { visitOperand( id: InstructionId, place: Place, - identifier: IdentifierId, + identifier: DeclarationId, ): void { const scope = getPlaceScope(id, place); if (scope !== null) { let node = this.scopes.get(scope.id); if (node === undefined) { node = { - dependencies: [...scope.dependencies].map(dep => dep.identifier.id), + dependencies: [...scope.dependencies].map( + dep => dep.identifier.declarationId, + ), seen: false, }; this.scopes.set(scope.id, node); @@ -269,11 +272,11 @@ class State { * to determine which other values should be memoized. Returns a set of all identifiers * that should be memoized. */ -function computeMemoizedIdentifiers(state: State): Set { - const memoized = new Set(); +function computeMemoizedIdentifiers(state: State): Set { + const memoized = new Set(); // Visit an identifier, optionally forcing it to be memoized - function visit(id: IdentifierId, forceMemoize: boolean = false): boolean { + function visit(id: DeclarationId, forceMemoize: boolean = false): boolean { const node = state.identifiers.get(id); CompilerError.invariant(node !== undefined, { reason: `Expected a node for all identifiers, none found for \`${id}\``, @@ -832,14 +835,16 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor { // Associate all the rvalues with the instruction's scope if it has one for (const operand of aliasing.rvalues) { const operandId = - state.definitions.get(operand.identifier.id) ?? operand.identifier.id; + state.definitions.get(operand.identifier.declarationId) ?? + operand.identifier.declarationId; state.visitOperand(instruction.id, operand, operandId); } // Add the operands as dependencies of all lvalues. for (const {place: lvalue, level} of aliasing.lvalues) { const lvalueId = - state.definitions.get(lvalue.identifier.id) ?? lvalue.identifier.id; + state.definitions.get(lvalue.identifier.declarationId) ?? + lvalue.identifier.declarationId; let node = state.identifiers.get(lvalueId); if (node === undefined) { node = { @@ -858,7 +863,8 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor { */ for (const operand of aliasing.rvalues) { const operandId = - state.definitions.get(operand.identifier.id) ?? operand.identifier.id; + state.definitions.get(operand.identifier.declarationId) ?? + operand.identifier.declarationId; if (operandId === lvalueId) { continue; } @@ -870,8 +876,8 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor { if (instruction.value.kind === 'LoadLocal' && instruction.lvalue !== null) { state.definitions.set( - instruction.lvalue.identifier.id, - instruction.value.place.identifier.id, + instruction.lvalue.identifier.declarationId, + instruction.value.place.identifier.declarationId, ); } else if ( instruction.value.kind === 'CallExpression' || @@ -897,7 +903,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor { } for (const operand of instruction.value.args) { const place = operand.kind === 'Spread' ? operand.place : operand; - state.escapingValues.add(place.identifier.id); + state.escapingValues.add(place.identifier.declarationId); } } } @@ -910,20 +916,20 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor { this.traverseTerminal(stmt, state); if (stmt.terminal.kind === 'return') { - state.escapingValues.add(stmt.terminal.value.identifier.id); + state.escapingValues.add(stmt.terminal.value.identifier.declarationId); } } } // Prune reactive scopes that do not have any memoized outputs class PruneScopesTransform extends ReactiveFunctionTransform< - Set + Set > { prunedScopes: Set = new Set(); override transformScope( scopeBlock: ReactiveScopeBlock, - state: Set, + state: Set, ): Transformed { this.visitScope(scopeBlock, state); @@ -945,11 +951,11 @@ class PruneScopesTransform extends ReactiveFunctionTransform< } const hasMemoizedOutput = - Array.from(scopeBlock.scope.declarations.keys()).some(id => - state.has(id), + Array.from(scopeBlock.scope.declarations.values()).some(decl => + state.has(decl.identifier.declarationId), ) || Array.from(scopeBlock.scope.reassignments).some(identifier => - state.has(identifier.id), + state.has(identifier.declarationId), ); if (hasMemoizedOutput) { return {kind: 'keep'}; @@ -964,7 +970,7 @@ class PruneScopesTransform extends ReactiveFunctionTransform< override transformInstruction( instruction: ReactiveInstruction, - state: Set, + state: Set, ): Transformed { this.traverseInstruction(instruction, state); diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneTemporaryLValues.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneTemporaryLValues.ts index 535a4e5e911d4..97d5c3431a6dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneTemporaryLValues.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneTemporaryLValues.ts @@ -6,7 +6,9 @@ */ import { + DeclarationId, Identifier, + IdentifierId, InstructionId, Place, ReactiveFunction, @@ -18,19 +20,19 @@ import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; * Nulls out lvalues for temporary variables that are never accessed later. This only * nulls out the lvalue itself, it does not remove the corresponding instructions. */ -export function pruneTemporaryLValues(fn: ReactiveFunction): void { - const lvalues = new Map(); +export function pruneUnusedLValues(fn: ReactiveFunction): void { + const lvalues = new Map(); visitReactiveFunction(fn, new Visitor(), lvalues); for (const [, instr] of lvalues) { instr.lvalue = null; } } -type LValues = Map; +type LValues = Map; class Visitor extends ReactiveFunctionVisitor { override visitPlace(id: InstructionId, place: Place, state: LValues): void { - state.delete(place.identifier); + state.delete(place.identifier.declarationId); } override visitInstruction( instruction: ReactiveInstruction, @@ -41,7 +43,7 @@ class Visitor extends ReactiveFunctionVisitor { instruction.lvalue !== null && instruction.lvalue.identifier.name === null ) { - state.set(instruction.lvalue.identifier, instruction); + state.set(instruction.lvalue.identifier.declarationId, instruction); } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/RenameVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/RenameVariables.ts index a68bdfa5bfda9..e88740b838a49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/RenameVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/RenameVariables.ts @@ -7,6 +7,7 @@ import {CompilerError} from '../CompilerError'; import { + DeclarationId, Identifier, IdentifierId, IdentifierName, @@ -121,8 +122,8 @@ class Visitor extends ReactiveFunctionVisitor { } class Scopes { - #seen: Map = new Map(); - #stack: Array> = [new Map()]; + #seen: Map = new Map(); + #stack: Array> = [new Map()]; #globals: Set; names: Set = new Set(); @@ -135,7 +136,7 @@ class Scopes { if (originalName === null) { return; } - const mappedName = this.#seen.get(identifier.id); + const mappedName = this.#seen.get(identifier.declarationId); if (mappedName !== undefined) { identifier.name = mappedName; return; @@ -158,12 +159,12 @@ class Scopes { } const identifierName = makeIdentifierName(name); identifier.name = identifierName; - this.#seen.set(identifier.id, identifierName); - this.#stack.at(-1)!.set(identifierName.value, identifier.id); + this.#seen.set(identifier.declarationId, identifierName); + this.#stack.at(-1)!.set(identifierName.value, identifier.declarationId); this.names.add(identifierName.value); } - #lookup(name: string): IdentifierId | null { + #lookup(name: string): DeclarationId | null { for (let i = this.#stack.length - 1; i >= 0; i--) { const scope = this.#stack[i]!; const entry = scope.get(name); diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index 3dd64a26d21ad..dd982f8cb79af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -27,7 +27,7 @@ export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; export {pruneNonReactiveDependencies} from './PruneNonReactiveDependencies'; -export {pruneTemporaryLValues as pruneUnusedLValues} from './PruneTemporaryLValues'; +export {pruneUnusedLValues} from './PruneTemporaryLValues'; export {pruneUnusedLabels} from './PruneUnusedLabels'; export {pruneUnusedScopes} from './PruneUnusedScopes'; export {renameVariables} from './RenameVariables'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/LeaveSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/LeaveSSA.ts index 8cf569a490e8b..ea3c9ae4ce581 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/LeaveSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/LeaveSSA.ts @@ -9,6 +9,7 @@ import {CompilerError} from '../CompilerError'; import { BasicBlock, BlockId, + DeclarationId, HIRFunction, Identifier, InstructionKind, @@ -27,6 +28,152 @@ import { terminalFallthrough, } from '../HIR/visitors'; +export function leaveSSA(fn: HIRFunction): void { + const declarations = new Map(); + for (const param of fn.params) { + let place: Place = param.kind === 'Identifier' ? param : param.place; + if (place.identifier.name !== null) { + declarations.set(place.identifier.declarationId, { + kind: InstructionKind.Let, + place, + }); + } + } + for (const place of fn.context) { + if (place.identifier.name !== null) { + declarations.set(place.identifier.declarationId, { + kind: InstructionKind.Let, + place, + }); + } + } + for (const [, block] of fn.body.blocks) { + for (const instr of block.instructions) { + const {value} = instr; + switch (value.kind) { + case 'DeclareLocal': { + const lvalue = value.lvalue; + CompilerError.invariant( + !declarations.has(lvalue.place.identifier.declarationId), + { + reason: `Expected variable not to be defined prior to declaration`, + description: `${printPlace(lvalue.place)} was already defined`, + loc: lvalue.place.loc, + }, + ); + declarations.set(lvalue.place.identifier.declarationId, lvalue); + break; + } + case 'StoreLocal': { + const lvalue = value.lvalue; + if (lvalue.place.identifier.name !== null) { + const declaration = declarations.get( + lvalue.place.identifier.declarationId, + ); + if (declaration === undefined) { + CompilerError.invariant( + !declarations.has(lvalue.place.identifier.declarationId), + { + reason: `Expected variable not to be defined prior to declaration`, + description: `${printPlace(lvalue.place)} was already defined`, + loc: lvalue.place.loc, + }, + ); + declarations.set(lvalue.place.identifier.declarationId, lvalue); + if (lvalue.kind === InstructionKind.Let) { + lvalue.kind = InstructionKind.Const; + } + } else { + declaration.kind = InstructionKind.Let; + lvalue.kind = InstructionKind.Reassign; + } + } + break; + } + case 'Destructure': { + const lvalue = value.lvalue; + let kind: InstructionKind | null = null; + for (const place of eachPatternOperand(lvalue.pattern)) { + if (place.identifier.name === null) { + CompilerError.invariant( + kind === null || kind === InstructionKind.Const, + { + reason: `Expected consistent kind for destructuring`, + description: `other places were \`${kind}\` but '${printPlace( + place, + )}' is const`, + loc: place.loc, + suggestions: null, + }, + ); + kind = InstructionKind.Const; + } else { + const declaration = declarations.get( + place.identifier.declarationId, + ); + if (declaration === undefined) { + CompilerError.invariant(block.kind !== 'value', { + reason: `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`, + description: null, + loc: place.loc, + suggestions: null, + }); + declarations.set(place.identifier.declarationId, lvalue); + CompilerError.invariant( + kind === null || kind === InstructionKind.Const, + { + reason: `Expected consistent kind for destructuring`, + description: `Other places were \`${kind}\` but '${printPlace( + place, + )}' is const`, + loc: place.loc, + suggestions: null, + }, + ); + kind = InstructionKind.Const; + } else { + CompilerError.invariant( + kind === null || kind === InstructionKind.Reassign, + { + reason: `Expected consistent kind for destructuring`, + description: `Other places were \`${kind}\` but '${printPlace( + place, + )}' is reassigned`, + loc: place.loc, + suggestions: null, + }, + ); + kind = InstructionKind.Reassign; + declaration.kind = InstructionKind.Let; + } + } + } + CompilerError.invariant(kind !== null, { + reason: 'Expected at least one operand', + description: null, + loc: null, + suggestions: null, + }); + lvalue.kind = kind; + break; + } + case 'PostfixUpdate': + case 'PrefixUpdate': { + const lvalue = value.lvalue; + const declaration = declarations.get(lvalue.identifier.declarationId); + CompilerError.invariant(declaration !== undefined, { + reason: `Expected variable to have been defined`, + description: `No declaration for ${printPlace(lvalue)}`, + loc: lvalue.loc, + }); + declaration.kind = InstructionKind.Let; + break; + } + } + } + } +} + /* * Removes SSA form by converting all phis into explicit bindings and assignments. There are two main categories * of phis: @@ -89,7 +236,7 @@ import { * ) * ``` */ -export function leaveSSA(fn: HIRFunction): void { +export function _leaveSSA(fn: HIRFunction): void { // Maps identifier names to their original declaration. const declarations: Map< string,