Skip to content

Commit 925c20a

Browse files
committed
[compiler] Add fallthrough to branch terminal
Branch terminals didn't have a fallthrough because they correspond to an outer terminal (optional, logical, etc) that has the "real" fallthrough. But understanding how branch terminals correspond to these outer terminals requires knowing the branch fallthrough. For example, `foo?.bar?.baz` creates terminals along the lines of: ``` bb0: optional fallthrough=bb4 bb1: optional fallthrough=bb3 bb2: ... branch ... (fallthrough=bb3) ... bb3: ... branch ... (fallthrough=bb4) ... bb4: ... ``` Without a fallthrough on `branch` terminals, it's unclear that the optional from bb0 has its branch node in bb3. With the fallthroughs, we can see look for a branch with the same fallthrough as the outer optional terminal to match them up. ghstack-source-id: d48c623 Pull Request resolved: #30814
1 parent a718da0 commit 925c20a

File tree

6 files changed

+77
-11
lines changed

6 files changed

+77
-11
lines changed

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ function lowerStatement(
607607
),
608608
consequent: bodyBlock,
609609
alternate: continuationBlock.id,
610+
fallthrough: continuationBlock.id,
610611
id: makeInstructionId(0),
611612
loc: stmt.node.loc ?? GeneratedSource,
612613
},
@@ -656,16 +657,13 @@ function lowerStatement(
656657
},
657658
conditionalBlock,
658659
);
659-
/*
660-
* The conditional block is empty and exists solely as conditional for
661-
* (re)entering or exiting the loop
662-
*/
663660
const test = lowerExpressionToTemporary(builder, stmt.get('test'));
664661
const terminal: BranchTerminal = {
665662
kind: 'branch',
666663
test,
667664
consequent: loopBlock,
668665
alternate: continuationBlock.id,
666+
fallthrough: conditionalBlock.id,
669667
id: makeInstructionId(0),
670668
loc: stmt.node.loc ?? GeneratedSource,
671669
};
@@ -975,6 +973,7 @@ function lowerStatement(
975973
test,
976974
consequent: loopBlock,
977975
alternate: continuationBlock.id,
976+
fallthrough: conditionalBlock.id,
978977
id: makeInstructionId(0),
979978
loc,
980979
};
@@ -1118,6 +1117,7 @@ function lowerStatement(
11181117
consequent: loopBlock,
11191118
alternate: continuationBlock.id,
11201119
loc: stmt.node.loc ?? GeneratedSource,
1120+
fallthrough: continuationBlock.id,
11211121
},
11221122
continuationBlock,
11231123
);
@@ -1203,6 +1203,7 @@ function lowerStatement(
12031203
test,
12041204
consequent: loopBlock,
12051205
alternate: continuationBlock.id,
1206+
fallthrough: continuationBlock.id,
12061207
loc: stmt.node.loc ?? GeneratedSource,
12071208
},
12081209
continuationBlock,
@@ -1800,6 +1801,7 @@ function lowerExpression(
18001801
test: {...testPlace},
18011802
consequent: consequentBlock,
18021803
alternate: alternateBlock,
1804+
fallthrough: continuationBlock.id,
18031805
id: makeInstructionId(0),
18041806
loc: exprLoc,
18051807
},
@@ -1878,6 +1880,7 @@ function lowerExpression(
18781880
test: {...leftPlace},
18791881
consequent,
18801882
alternate,
1883+
fallthrough: continuationBlock.id,
18811884
id: makeInstructionId(0),
18821885
loc: exprLoc,
18831886
},
@@ -2611,6 +2614,7 @@ function lowerOptionalMemberExpression(
26112614
test: {...object},
26122615
consequent: consequent.id,
26132616
alternate,
2617+
fallthrough: continuationBlock.id,
26142618
id: makeInstructionId(0),
26152619
loc,
26162620
};
@@ -2750,6 +2754,7 @@ function lowerOptionalCallExpression(
27502754
test: {...testPlace},
27512755
consequent: consequent.id,
27522756
alternate,
2757+
fallthrough: continuationBlock.id,
27532758
id: makeInstructionId(0),
27542759
loc,
27552760
};
@@ -4025,6 +4030,7 @@ function lowerAssignment(
40254030
test: {...test},
40264031
consequent,
40274032
alternate,
4033+
fallthrough: continuationBlock.id,
40284034
id: makeInstructionId(0),
40294035
loc,
40304036
},

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ export type BranchTerminal = {
491491
alternate: BlockId;
492492
id: InstructionId;
493493
loc: SourceLocation;
494-
fallthrough?: never;
494+
fallthrough: BlockId;
495495
};
496496

497497
export type SwitchTerminal = {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,11 +660,13 @@ export function mapTerminalSuccessors(
660660
case 'branch': {
661661
const consequent = fn(terminal.consequent);
662662
const alternate = fn(terminal.alternate);
663+
const fallthrough = fn(terminal.fallthrough);
663664
return {
664665
kind: 'branch',
665666
test: terminal.test,
666667
consequent,
667668
alternate,
669+
fallthrough,
668670
id: makeInstructionId(0),
669671
loc: terminal.loc,
670672
};
@@ -883,7 +885,6 @@ export function terminalHasFallthrough<
883885
>(terminal: T): terminal is U {
884886
switch (terminal.kind) {
885887
case 'maybe-throw':
886-
case 'branch':
887888
case 'goto':
888889
case 'return':
889890
case 'throw':
@@ -892,6 +893,7 @@ export function terminalHasFallthrough<
892893
const _: undefined = terminal.fallthrough;
893894
return false;
894895
}
896+
case 'branch':
895897
case 'try':
896898
case 'do-while':
897899
case 'for-of':

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

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type IdentifierSidemap = {
4242
react: Set<IdentifierId>;
4343
maybeDepsLists: Map<IdentifierId, Array<Place>>;
4444
maybeDeps: Map<IdentifierId, ManualMemoDependency>;
45+
optionals: Set<IdentifierId>;
4546
};
4647

4748
/**
@@ -52,6 +53,7 @@ type IdentifierSidemap = {
5253
export function collectMaybeMemoDependencies(
5354
value: InstructionValue,
5455
maybeDeps: Map<IdentifierId, ManualMemoDependency>,
56+
optional: boolean,
5557
): ManualMemoDependency | null {
5658
switch (value.kind) {
5759
case 'LoadGlobal': {
@@ -69,7 +71,7 @@ export function collectMaybeMemoDependencies(
6971
return {
7072
root: object.root,
7173
// TODO: determine if the access is optional
72-
path: [...object.path, {property: value.property, optional: false}],
74+
path: [...object.path, {property: value.property, optional}],
7375
};
7476
}
7577
break;
@@ -162,7 +164,11 @@ function collectTemporaries(
162164
break;
163165
}
164166
}
165-
const maybeDep = collectMaybeMemoDependencies(value, sidemap.maybeDeps);
167+
const maybeDep = collectMaybeMemoDependencies(
168+
value,
169+
sidemap.maybeDeps,
170+
sidemap.optionals.has(lvalue.identifier.id),
171+
);
166172
// We don't expect named lvalues during this pass (unlike ValidatePreservingManualMemo)
167173
if (maybeDep != null) {
168174
sidemap.maybeDeps.set(lvalue.identifier.id, maybeDep);
@@ -338,12 +344,14 @@ export function dropManualMemoization(func: HIRFunction): void {
338344
func.env.config.validatePreserveExistingMemoizationGuarantees ||
339345
func.env.config.validateNoSetStateInRender ||
340346
func.env.config.enablePreserveExistingMemoizationGuarantees;
347+
const optionals = findOptionalPlaces(func);
341348
const sidemap: IdentifierSidemap = {
342349
functions: new Map(),
343350
manualMemos: new Map(),
344351
react: new Set(),
345352
maybeDeps: new Map(),
346353
maybeDepsLists: new Map(),
354+
optionals,
347355
};
348356
let nextManualMemoId = 0;
349357

@@ -476,3 +484,46 @@ export function dropManualMemoization(func: HIRFunction): void {
476484
}
477485
}
478486
}
487+
488+
function findOptionalPlaces(fn: HIRFunction): Set<IdentifierId> {
489+
const optionals = new Set<IdentifierId>();
490+
for (const [, block] of fn.body.blocks) {
491+
if (block.terminal.kind === 'optional') {
492+
const optionalTerminal = block.terminal;
493+
let testBlock = fn.body.blocks.get(block.terminal.test)!;
494+
loop: while (true) {
495+
const terminal = testBlock.terminal;
496+
switch (terminal.kind) {
497+
case 'branch': {
498+
if (terminal.fallthrough === optionalTerminal.fallthrough) {
499+
// found it
500+
const consequent = fn.body.blocks.get(terminal.consequent)!;
501+
const last = consequent.instructions.at(-1);
502+
if (last !== undefined && last.value.kind === 'StoreLocal') {
503+
optionals.add(last.value.value.identifier.id);
504+
}
505+
break loop;
506+
} else {
507+
testBlock = fn.body.blocks.get(terminal.fallthrough)!;
508+
}
509+
break;
510+
}
511+
case 'optional':
512+
case 'logical':
513+
case 'sequence':
514+
case 'ternary': {
515+
testBlock = fn.body.blocks.get(terminal.fallthrough)!;
516+
break;
517+
}
518+
default: {
519+
CompilerError.invariant(false, {
520+
reason: `Unexpected terminal in optional`,
521+
loc: terminal.loc,
522+
});
523+
}
524+
}
525+
}
526+
}
527+
}
528+
return optionals;
529+
}

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
140140
}
141141

142142
const fallthrough = terminalFallthrough(terminal);
143-
if (fallthrough !== null) {
143+
if (fallthrough !== null && terminal.kind !== 'branch') {
144144
/*
145145
* Any currently active scopes that overlaps the block-fallthrough range
146146
* need their range extended to at least the first instruction of the

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ function compareDeps(
167167

168168
let isSubpath = true;
169169
for (let i = 0; i < Math.min(inferred.path.length, source.path.length); i++) {
170-
if (inferred.path[i].property !== source.path[i].property) {
170+
if (
171+
inferred.path[i].property !== source.path[i].property ||
172+
inferred.path[i].optional !== source.path[i].optional
173+
) {
171174
isSubpath = false;
172175
break;
173176
}
@@ -339,7 +342,11 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
339342
return null;
340343
}
341344
default: {
342-
const dep = collectMaybeMemoDependencies(value, this.temporaries);
345+
const dep = collectMaybeMemoDependencies(
346+
value,
347+
this.temporaries,
348+
false,
349+
);
343350
if (value.kind === 'StoreLocal' || value.kind === 'StoreContext') {
344351
const storeTarget = value.lvalue.place;
345352
state.manualMemoState?.decls.add(

0 commit comments

Comments
 (0)