[rhir] Preserve block labels + target blockIds for break/continue terminals
RFC: we can either retain break/continue target ids (instead of pruning them in `buildReactiveFunction`) or re-implement the same logic in `propagateScopeDeps` (ignore implicit breaks; match unlabeled break / continues to their closest loop / while parent terminal). If we go ahead with this approach, I'll clean up this PR (add relevant types and comments)
Mofei Zhang committed
Mar 27, 2024 at 20:26 UTC
2782fa2664784d7c3c941aa708349e570418103d
7 files changed
+120
-116
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+9
-5
@@ -80,7 +80,10 @@ export type ReactiveTerminalStatement<
80
> = {
81
kind: "terminal";
82
terminal: Tterminal;
83
- label: BlockId | null;
83
+ label: {
84
+ id: BlockId;
85
+ implicit: boolean;
86
+ } | null;
87
};
88
89
export type ReactiveInstruction = {
@@ -153,17 +156,18 @@ export type ReactiveTerminal =
156
| ReactiveLabelTerminal
157
| ReactiveTryTerminal;
158
159
+export type ReactiveTerminalTargetKind = "implicit" | "labeled" | "unlabeled";
160
export type ReactiveBreakTerminal = {
161
kind: "break";
158
- label: BlockId | null;
162
+ target: BlockId;
163
id: InstructionId | null;
160
- implicit: boolean;
164
+ targetKind: ReactiveTerminalTargetKind;
165
};
166
export type ReactiveContinueTerminal = {
167
kind: "continue";
164
- label: BlockId | null;
168
+ target: BlockId;
169
id: InstructionId;
166
- implicit: boolean;
170
+ targetKind: ReactiveTerminalTargetKind;
171
};
172
export type ReactiveReturnTerminal = {
173
kind: "return";
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveBlocks.ts
+8
-2
@@ -61,7 +61,10 @@ class Context {
61
this.#builders.at(-1)!.startScope(scope);
62
}
63
64
- append(stmt: ReactiveStatement, label: BlockId | null): void {
64
+ append(
65
+ stmt: ReactiveStatement,
66
+ label: { id: BlockId; implicit: boolean } | null
67
+ ): void {
68
this.#builders.at(-1)!.append(stmt, label);
69
}
70
@@ -93,7 +96,10 @@ class Builder {
96
this.#stack = [{ kind: "block", block }];
97
}
98
96
- append(item: ReactiveStatement, label: BlockId | null): void {
99
+ append(
100
+ item: ReactiveStatement,
101
+ label: { id: BlockId; implicit: boolean } | null
102
+ ): void {
103
if (label !== null) {
104
CompilerError.invariant(item.kind === "terminal", {
105
reason: "Only terminals may have a label",
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+75
-79
@@ -24,6 +24,7 @@ import {
24
ReactiveLogicalValue,
25
ReactiveSequenceValue,
26
ReactiveTerminalStatement,
27
+ ReactiveTerminalTargetKind,
28
ReactiveTernaryValue,
29
ReactiveValue,
30
Terminal,
@@ -157,7 +158,13 @@ class Driver {
158
alternate: alternate,
159
id: terminal.id,
160
},
160
- label: fallthroughId,
161
+ label:
162
+ fallthroughId == null
163
+ ? null
164
+ : {
165
+ id: fallthroughId,
166
+ implicit: false,
167
+ },
168
});
169
if (fallthroughId !== null) {
170
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
@@ -193,7 +200,7 @@ class Driver {
200
const break_ = this.visitBreak(case_.block, null);
201
if (
202
index === 0 &&
196
- break_.terminal.implicit &&
203
+ break_.terminal.targetKind === "implicit" &&
204
case_.block === terminal.fallthrough &&
205
case_.test === null
206
) {
@@ -229,7 +236,13 @@ class Driver {
236
cases,
237
id: terminal.id,
238
},
232
- label: fallthroughId,
239
+ label:
240
+ fallthroughId == null
241
+ ? null
242
+ : {
243
+ id: fallthroughId,
244
+ implicit: false,
245
+ },
246
});
247
if (fallthroughId !== null) {
248
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
@@ -280,7 +293,13 @@ class Driver {
293
loop: loopBody,
294
id: terminal.id,
295
},
283
- label: fallthroughId,
296
+ label:
297
+ fallthroughId == null
298
+ ? null
299
+ : {
300
+ id: fallthroughId,
301
+ implicit: false,
302
+ },
303
});
304
if (fallthroughId !== null) {
305
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
@@ -334,7 +353,13 @@ class Driver {
353
loop: loopBody,
354
id: terminal.id,
355
},
337
- label: fallthroughId,
356
+ label:
357
+ fallthroughId == null
358
+ ? null
359
+ : {
360
+ id: fallthroughId,
361
+ implicit: false,
362
+ },
363
});
364
if (fallthroughId !== null) {
365
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
@@ -421,7 +446,10 @@ class Driver {
446
loop: loopBody,
447
id: terminal.id,
448
},
424
- label: fallthroughId,
449
+ label:
450
+ fallthroughId == null
451
+ ? null
452
+ : { id: fallthroughId, implicit: false },
453
});
454
if (fallthroughId !== null) {
455
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
@@ -496,7 +524,10 @@ class Driver {
524
loop: loopBody,
525
id: terminal.id,
526
},
499
- label: fallthroughId,
527
+ label:
528
+ fallthroughId == null
529
+ ? null
530
+ : { id: fallthroughId, implicit: false },
531
});
532
if (fallthroughId !== null) {
533
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
@@ -571,7 +602,10 @@ class Driver {
602
loop: loopBody,
603
id: terminal.id,
604
},
574
- label: fallthroughId,
605
+ label:
606
+ fallthroughId == null
607
+ ? null
608
+ : { id: fallthroughId, implicit: false },
609
});
610
if (fallthroughId !== null) {
611
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
@@ -651,7 +685,10 @@ class Driver {
685
block,
686
id: terminal.id,
687
},
654
- label: fallthroughId,
688
+ label:
689
+ fallthroughId == null
690
+ ? null
691
+ : { id: fallthroughId, implicit: false },
692
});
693
if (fallthroughId !== null) {
694
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
@@ -753,7 +790,10 @@ class Driver {
790
this.cx.unscheduleAll(scheduleIds);
791
blockValue.push({
792
kind: "terminal",
756
- label: fallthroughId,
793
+ label:
794
+ fallthroughId == null
795
+ ? null
796
+ : { id: fallthroughId, implicit: false },
797
terminal: {
798
kind: "try",
799
block,
@@ -1105,35 +1145,16 @@ class Driver {
1145
suggestions: null,
1146
});
1147
}
1108
- switch (target.type) {
1109
- case "implicit": {
1110
- return {
1111
- kind: "terminal",
1112
- terminal: { kind: "break", label: null, id, implicit: true },
1113
- label: null,
1114
- };
1115
- }
1116
- case "labeled": {
1117
- return {
1118
- kind: "terminal",
1119
- terminal: { kind: "break", label: target.block, id, implicit: false },
1120
- label: null,
1121
- };
1122
- }
1123
- case "unlabeled": {
1124
- return {
1125
- kind: "terminal",
1126
- terminal: { kind: "break", label: null, id, implicit: false },
1127
- label: null,
1128
- };
1129
- }
1130
- default: {
1131
- assertExhaustive(
1132
- target.type,
1133
- `Unexpected break target kind '${(target as any).type}'`
1134
- );
1135
- }
1136
- }
1148
+ return {
1149
+ kind: "terminal",
1150
+ terminal: {
1151
+ kind: "break",
1152
+ target: target.block,
1153
+ id,
1154
+ targetKind: target.type,
1155
+ },
1156
+ label: null,
1157
+ };
1158
}
1159
1160
visitContinue(
@@ -1147,40 +1168,17 @@ class Driver {
1168
loc: null,
1169
suggestions: null,
1170
});
1150
- switch (target.type) {
1151
- case "implicit": {
1152
- return {
1153
- kind: "terminal",
1154
- terminal: { kind: "continue", label: null, id, implicit: true },
1155
- label: null,
1156
- };
1157
- }
1158
- case "labeled": {
1159
- return {
1160
- kind: "terminal",
1161
- terminal: {
1162
- kind: "continue",
1163
- label: target.block,
1164
- id,
1165
- implicit: false,
1166
- },
1167
- label: null,
1168
- };
1169
- }
1170
- case "unlabeled": {
1171
- return {
1172
- kind: "terminal",
1173
- terminal: { kind: "continue", label: null, id, implicit: false },
1174
- label: null,
1175
- };
1176
- }
1177
- default: {
1178
- assertExhaustive(
1179
- target.type,
1180
- `Unexpected continue target kind '${(target as any).type}'`
1181
- );
1182
- }
1183
- }
1171
+
1172
+ return {
1173
+ kind: "terminal",
1174
+ terminal: {
1175
+ kind: "continue",
1176
+ target: target.block,
1177
+ id,
1178
+ targetKind: target.type,
1179
+ },
1180
+ label: null,
1181
+ };
1182
}
1183
}
1184
@@ -1323,12 +1321,12 @@ class Context {
1321
*/
1322
getBreakTarget(
1323
block: BlockId
1326
- ): { block: BlockId; type: ControlFlowKind } | null {
1324
+ ): { block: BlockId; type: ReactiveTerminalTargetKind } | null {
1325
let hasPrecedingLoop = false;
1326
for (let i = this.#controlFlowStack.length - 1; i >= 0; i--) {
1327
const target = this.#controlFlowStack[i]!;
1328
if (target.block === block) {
1331
- let type: ControlFlowKind;
1329
+ let type: ReactiveTerminalTargetKind;
1330
if (target.type === "loop") {
1331
/*
1332
* breaking out of a loop requires an explicit break,
@@ -1367,12 +1365,12 @@ class Context {
1365
*/
1366
getContinueTarget(
1367
block: BlockId
1370
- ): { block: BlockId; type: ControlFlowKind } | null {
1368
+ ): { block: BlockId; type: ReactiveTerminalTargetKind } | null {
1369
let hasPrecedingLoop = false;
1370
for (let i = this.#controlFlowStack.length - 1; i >= 0; i--) {
1371
const target = this.#controlFlowStack[i]!;
1372
if (target.type == "loop" && target.continueBlock === block) {
1375
- let type: ControlFlowKind;
1373
+ let type: ReactiveTerminalTargetKind;
1374
if (hasPrecedingLoop) {
1375
/*
1376
* continuing to a loop that is not the innermost loop always requires
@@ -1407,8 +1405,6 @@ class Context {
1405
}
1406
}
1407
1410
-type ControlFlowKind = "implicit" | "labeled" | "unlabeled";
1411
-
1408
type ControlFlowTarget =
1409
| { type: "if"; block: BlockId; id: number }
1410
| { type: "switch"; block: BlockId; id: number }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+8
-8
@@ -318,13 +318,13 @@ function codegenBlockNoReset(
318
if (statement === null) {
319
break;
320
}
321
- if (item.label !== null) {
321
+ if (item.label !== null && !item.label.implicit) {
322
const block =
323
statement.type === "BlockStatement" && statement.body.length === 1
324
? statement.body[0]
325
: statement;
326
statements.push(
327
- t.labeledStatement(t.identifier(codegenLabel(item.label)), block)
327
+ t.labeledStatement(t.identifier(codegenLabel(item.label.id)), block)
328
);
329
} else if (statement.type === "BlockStatement") {
330
statements.push(...statement.body);
@@ -615,22 +615,22 @@ function codegenTerminal(
615
): t.Statement | null {
616
switch (terminal.kind) {
617
case "break": {
618
- if (terminal.implicit) {
618
+ if (terminal.targetKind === "implicit") {
619
return null;
620
}
621
return t.breakStatement(
622
- terminal.label !== null
623
- ? t.identifier(codegenLabel(terminal.label))
622
+ terminal.targetKind === "labeled"
623
+ ? t.identifier(codegenLabel(terminal.target))
624
: null
625
);
626
}
627
case "continue": {
628
- if (terminal.implicit) {
628
+ if (terminal.targetKind === "implicit") {
629
return null;
630
}
631
return t.continueStatement(
632
- terminal.label !== null
633
- ? t.identifier(codegenLabel(terminal.label))
632
+ terminal.targetKind === "labeled"
633
+ ? t.identifier(codegenLabel(terminal.target))
634
: null
635
);
636
}
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts
+8
-13
@@ -135,7 +135,7 @@ function writeReactiveInstruction(
135
}
136
case "terminal": {
137
if (instr.label !== null) {
138
- writer.write(`bb${instr.label}: `);
138
+ writer.write(`bb${instr.label.id}: `);
139
}
140
writeTerminal(writer, instr.terminal);
141
break;
@@ -229,22 +229,17 @@ function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
229
switch (terminal.kind) {
230
case "break": {
231
const id = terminal.id !== null ? `[${terminal.id}]` : [];
232
- const implicit = terminal.implicit ? "(implicit) " : "";
233
- if (terminal.label !== null) {
234
- writer.writeLine(`${id} ${implicit}break bb${terminal.label}`);
235
- } else {
236
- writer.writeLine(`${id} ${implicit}break`);
237
- }
232
+ writer.writeLine(
233
+ `${id} break bb${terminal.target} (${terminal.targetKind})`
234
+ );
235
+
236
break;
237
}
238
case "continue": {
239
const id = `[${terminal.id}]`;
242
- const implicit = terminal.implicit ? "(implicit) " : "";
243
- if (terminal.label !== null) {
244
- writer.writeLine(`${id} ${implicit}continue bb${terminal.label}`);
245
- } else {
246
- writer.writeLine(`${id} ${implicit}continue`);
247
- }
240
+ writer.writeLine(
241
+ `${id} continue bb${terminal.target} (${terminal.targetKind})`
242
+ );
243
break;
244
}
245
case "do-while": {
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateEarlyReturns.ts
+6
-3
@@ -238,7 +238,10 @@ class Transform extends ReactiveFunctionTransform<State> {
238
},
239
{
240
kind: "terminal",
241
- label: earlyReturnValue.label,
241
+ label: {
242
+ id: earlyReturnValue.label,
243
+ implicit: false,
244
+ },
245
terminal: {
246
kind: "label",
247
id: makeInstructionId(0),
@@ -316,8 +319,8 @@ class Transform extends ReactiveFunctionTransform<State> {
319
terminal: {
320
kind: "break",
321
id: makeInstructionId(0),
319
- implicit: false,
320
- label: earlyReturnValue.label,
322
+ targetKind: "labeled",
323
+ target: earlyReturnValue.label,
324
},
325
},
326
],
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneUnusedLabels.ts
+6
-6
@@ -37,12 +37,12 @@ class Transform extends ReactiveFunctionTransform<Labels> {
37
const { terminal } = stmt;
38
if (
39
(terminal.kind === "break" || terminal.kind === "continue") &&
40
- terminal.label !== null
40
+ terminal.targetKind === "labeled"
41
) {
42
- state.add(terminal.label);
42
+ state.add(terminal.target);
43
}
44
// Is this terminal reachable via a break/continue to its label?
45
- const isReachableLabel = stmt.label !== null && state.has(stmt.label);
45
+ const isReachableLabel = stmt.label !== null && state.has(stmt.label.id);
46
if (stmt.terminal.kind === "label" && !isReachableLabel) {
47
// Flatten labeled terminals where the label isn't necessary
48
const block = [...stmt.terminal.block];
@@ -51,14 +51,14 @@ class Transform extends ReactiveFunctionTransform<Labels> {
51
last !== undefined &&
52
last.kind === "terminal" &&
53
last.terminal.kind === "break" &&
54
- last.terminal.label === null
54
+ last.terminal.target === null
55
) {
56
block.pop();
57
}
58
return { kind: "replace-many", value: block };
59
} else {
60
- if (!isReachableLabel) {
61
- stmt.label = null;
60
+ if (!isReachableLabel && stmt.label != null) {
61
+ stmt.label.implicit = true;
62
}
63
return { kind: "keep" };
64
}