Skip to content

Commit 812b5d0

Browse files
committed
[compiler] Maintain RPO and uniquely instruction ids when constructing scope terminals
Later passes may rely on HIR invariants such as blocks being in RPO or instructions having unique, ascending InstructionIds. However, BuildReactiveScopeTerminalsHIR doesn't currently gurantee this. This PR updates that pass to first restore RPO, fixup predecessors (the previous logic tried to do this but failed on unreachable blocks, where `markPredecessors()` handles that case), and renumber instructions. Then it walks instructions and scopes to update identifier and scope ranges given the new instruction ids. ghstack-source-id: 5a3e3dc Pull Request resolved: #30399
1 parent 6859a29 commit 812b5d0

File tree

3 files changed

+59
-10
lines changed

3 files changed

+59
-10
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export function assertTerminalPredsExist(fn: HIRFunction): void {
3939
[...eachTerminalSuccessor(predBlock.terminal)].includes(block.id),
4040
{
4141
reason: 'Terminal successor does not reference correct predecessor',
42+
description: `Block bb${block.id} has bb${predBlock.id} as a predecessor, but bb${predBlock.id}'s successors do not include bb${block.id}`,
4243
loc: GeneratedSource,
4344
},
4445
);

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

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,22 @@ import {
99
GotoVariant,
1010
HIRFunction,
1111
InstructionId,
12+
makeInstructionId,
13+
Place,
1214
ReactiveScope,
1315
ReactiveScopeTerminal,
1416
ScopeId,
1517
} from './HIR';
18+
import {
19+
markInstructionIds,
20+
markPredecessors,
21+
reversePostorderBlocks,
22+
} from './HIRBuilder';
23+
import {
24+
eachInstructionLValue,
25+
eachInstructionValueOperand,
26+
eachTerminalOperand,
27+
} from './visitors';
1628

1729
/**
1830
* This pass assumes that all program blocks are properly nested with respect to fallthroughs
@@ -142,16 +154,9 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
142154

143155
/**
144156
* Step 3:
145-
* Repoint preds and phis when they refer to a rewritten block.
157+
* Repoint phis when they refer to a rewritten block.
146158
*/
147159
for (const [, block] of originalBlocks) {
148-
for (const pred of block.preds) {
149-
const newId = rewrittenFinalBlocks.get(pred);
150-
if (newId != null) {
151-
block.preds.delete(pred);
152-
block.preds.add(newId);
153-
}
154-
}
155160
for (const phi of block.phis) {
156161
for (const [originalId, value] of phi.operands) {
157162
const newId = rewrittenFinalBlocks.get(originalId);
@@ -162,6 +167,50 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
162167
}
163168
}
164169
}
170+
171+
/**
172+
* Step 4:
173+
* Fixup the HIR to restore RPO, ensure correct predecessors, and
174+
* renumber instructions. Note that the renumbering instructions
175+
* invalidates scope and identifier ranges, so we fix them in the
176+
* next step.
177+
*/
178+
reversePostorderBlocks(fn.body);
179+
markPredecessors(fn.body);
180+
markInstructionIds(fn.body);
181+
182+
/**
183+
* Step 5:
184+
* Fix scope and identifier ranges to account for renumbered instructions
185+
*/
186+
for (const [, block] of fn.body.blocks) {
187+
for (const instruction of block.instructions) {
188+
for (const lvalue of eachInstructionLValue(instruction)) {
189+
// Any lvalues whose mutable range was a single instruction must have
190+
// started at the current instruction, so update the range to match
191+
// the instruction's new id
192+
if (
193+
lvalue.identifier.mutableRange.end ===
194+
lvalue.identifier.mutableRange.start + 1
195+
) {
196+
lvalue.identifier.mutableRange.start = instruction.id;
197+
lvalue.identifier.mutableRange.end = makeInstructionId(
198+
instruction.id + 1,
199+
);
200+
}
201+
}
202+
}
203+
const terminal = block.terminal;
204+
if (terminal.kind === 'scope' || terminal.kind === 'pruned-scope') {
205+
// Scope ranges should always align to start at the 'scope' terminal
206+
// and end at the first instruction of the fallthrough block
207+
const fallthroughBlock = fn.body.blocks.get(terminal.fallthrough)!;
208+
const firstId =
209+
fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
210+
terminal.scope.range.start = terminal.id;
211+
terminal.scope.range.end = firstId;
212+
}
213+
}
165214
}
166215

167216
type TerminalRewriteInfo =

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
ScopeId,
2323
SourceLocation,
2424
} from '../HIR';
25-
import {printManualMemoDependency} from '../HIR/PrintHIR';
25+
import {printManualMemoDependency, printPlace} from '../HIR/PrintHIR';
2626
import {eachInstructionValueOperand} from '../HIR/visitors';
2727
import {collectMaybeMemoDependencies} from '../Inference/DropManualMemoization';
2828
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
@@ -470,7 +470,6 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
470470
state.errors.push({
471471
reason:
472472
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value may be mutated later, which could cause the value to change unexpectedly',
473-
description: null,
474473
severity: ErrorSeverity.CannotPreserveMemoization,
475474
loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null,
476475
suggestions: null,

0 commit comments

Comments
 (0)