TryStatement: maybe-throw terminal for per-instruction throw points
Adds a "maybe-throw" terminal which represents the possibility that the block may or may not throw, and can either continue forward or exit to an exception handler (`catch`). Also updates HIRBuilder to track the current mode, and when inside a try block to wrap every instruction inside a basic block that ends in a maybe-throw. So far this code isn't used yet, so doesn't affect output.
Joe Savona committed
Sep 7, 2023 at 14:22 UTC
0977115440b53ca505a62e793ab3ec44355f69aa
6 files changed
+75
-1
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+10
-1
@@ -279,7 +279,8 @@ export type Terminal =
279
| TernaryTerminal
280
| OptionalTerminal
281
| LabelTerminal
282
- | SequenceTerminal;
282
+ | SequenceTerminal
283
+ | MaybeThrowTerminal;
284
285
function _staticInvariantTerminalHasLocation(
286
terminal: Terminal
@@ -444,6 +445,14 @@ export type SequenceTerminal = {
445
loc: SourceLocation;
446
};
447
448
+export type MaybeThrowTerminal = {
449
+ kind: "maybe-throw";
450
+ continuation: BlockId;
451
+ handler: BlockId;
452
+ id: InstructionId;
453
+ loc: SourceLocation;
454
+};
455
+
456
/**
457
* Instructions generally represent expressions but with all nesting flattened away,
458
* such that all operands to each instruction are either primitive values OR are
compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+31
@@ -77,6 +77,17 @@ export type Bindings = Map<
77
{ node: t.Identifier; identifier: Identifier }
78
>;
79
80
+// Determines how instructions should be constructed in order to preserve
81
+// exception semantics
82
+type ExceptionsMode =
83
+ // Mode used for code not covered by explicit exception handling, any
84
+ // errors are assumed to be thrown out of the function
85
+ | { kind: "ThrowExceptions" }
86
+ // Mode used for code that *is* covered by explicit exception handling
87
+ // (ie try/catch), which requires modeling the possibility of control
88
+ // flow to the exception handler.
89
+ | { kind: "CatchExceptions"; handler: BlockId };
90
+
91
/**
92
* Helper class for constructing a CFG
93
*/
@@ -88,6 +99,7 @@ export default class HIRBuilder {
99
#context: t.Identifier[];
100
#bindings: Bindings;
101
#env: Environment;
102
+ #mode: ExceptionsMode = { kind: "ThrowExceptions" };
103
parentFunction: NodePath<t.Function>;
104
errors: CompilerError = new CompilerError();
105
@@ -130,6 +142,20 @@ export default class HIRBuilder {
142
*/
143
push(instruction: Instruction): void {
144
this.#current.instructions.push(instruction);
145
+ if (this.#mode.kind === "CatchExceptions") {
146
+ const handler = this.#mode.handler;
147
+ const continuationBlock = this.reserve(this.currentBlockKind());
148
+ this.terminateWithContinuation(
149
+ {
150
+ kind: "maybe-throw",
151
+ continuation: continuationBlock.id,
152
+ handler,
153
+ id: makeInstructionId(0),
154
+ loc: instruction.loc,
155
+ },
156
+ continuationBlock
157
+ );
158
+ }
159
}
160
161
makeTemporary(): Identifier {
@@ -723,6 +749,11 @@ export function reversePostorderBlocks(func: HIR): void {
749
visit(terminal.block);
750
break;
751
}
752
+ case "maybe-throw": {
753
+ visit(terminal.handler);
754
+ visit(terminal.continuation);
755
+ break;
756
+ }
757
case "unsupported": {
758
break;
759
}
compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts
+5
@@ -80,6 +80,7 @@ export function printMixedHIR(
80
return printInstruction(value);
81
}
82
switch (value.kind) {
83
+ case "maybe-throw":
84
case "sequence":
85
case "label":
86
case "optional":
@@ -230,6 +231,10 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
231
value = `Unsupported`;
232
break;
233
}
234
+ case "maybe-throw": {
235
+ value = `MaybeThrow continuation=bb${terminal.continuation} handler=bb${terminal.handler}`;
236
+ break;
237
+ }
238
default: {
239
assertExhaustive(
240
terminal,
compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts
+20
@@ -731,6 +731,17 @@ export function mapTerminalSuccessors(
731
loc: terminal.loc,
732
};
733
}
734
+ case "maybe-throw": {
735
+ const continuation = fn(terminal.continuation);
736
+ const handler = fn(terminal.handler);
737
+ return {
738
+ kind: "maybe-throw",
739
+ continuation,
740
+ handler,
741
+ id: makeInstructionId(0),
742
+ loc: terminal.loc,
743
+ };
744
+ }
745
case "unsupported": {
746
return terminal;
747
}
@@ -750,6 +761,7 @@ export function mapTerminalSuccessors(
761
*/
762
export function terminalFallthrough(terminal: Terminal): BlockId | null {
763
switch (terminal.kind) {
764
+ case "maybe-throw":
765
case "branch":
766
case "goto":
767
case "return":
@@ -784,6 +796,7 @@ export function mapOptionalFallthroughs(
796
fn: (block: BlockId) => BlockId | null
797
): void {
798
switch (terminal.kind) {
799
+ case "maybe-throw":
800
case "branch":
801
case "goto":
802
case "return":
@@ -917,6 +930,11 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
930
yield terminal.block;
931
break;
932
}
933
+ case "maybe-throw": {
934
+ yield terminal.continuation;
935
+ yield terminal.handler;
936
+ break;
937
+ }
938
case "unsupported":
939
break;
940
default: {
@@ -956,6 +974,7 @@ export function mapTerminalOperands(
974
terminal.value = fn(terminal.value);
975
break;
976
}
977
+ case "maybe-throw":
978
case "sequence":
979
case "label":
980
case "optional":
@@ -1004,6 +1023,7 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
1023
yield terminal.value;
1024
break;
1025
}
1026
+ case "maybe-throw":
1027
case "sequence":
1028
case "label":
1029
case "optional":
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+7
@@ -636,6 +636,13 @@ class Driver {
636
}
637
break;
638
}
639
+ case "maybe-throw": {
640
+ this.visitBlock(
641
+ this.cx.ir.blocks.get(terminal.continuation)!,
642
+ blockValue
643
+ );
644
+ break;
645
+ }
646
case "unsupported": {
647
CompilerError.invariant(false, {
648
reason: "Unexpected unsupported terminal",
compiler/packages/babel-plugin-react-forget/src/Utils/VisualizeHIRMermaid.ts
+2
@@ -139,6 +139,7 @@ function printTerminalLabel(terminal: Terminal): string {
139
buffer.push("Sequence");
140
break;
141
}
142
+ case "maybe-throw":
143
case "unsupported": {
144
break;
145
}
@@ -246,6 +247,7 @@ function printTerminalArrows(blockId: BlockId, terminal: Terminal): string {
247
buffer.push(printJumpArrow(blockId, terminal.fallthrough, "fallthrough"));
248
break;
249
}
250
+ case "maybe-throw":
251
case "throw":
252
case "return":
253
case "unsupported": {