@samitouri / QOS-React-2 / commits / 7f5bac171e

TryStatement: try terminal scaffolding

This PR adds the other piece, a 'try' terminal which represents try/catch and the possibility of fallthrough to the code afterwards. For now `finally` is unsupported. We don't yet produce these terminals, see later in the stack.

Joe Savona committed Sep 7, 2023 at 14:22 UTC 7f5bac171eaef4a9576d25417916a2b014016625
11 files changed +131 -2
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+20 -2
@@ -138,7 +138,8 @@ export type ReactiveTerminal =
138 | ReactiveForTerminal
139 | ReactiveForOfTerminal
140 | ReactiveIfTerminal
141 - | ReactiveLabelTerminal;
141 + | ReactiveLabelTerminal
142 + | ReactiveTryTerminal;
143
144 export type ReactiveBreakTerminal = {
145 kind: "break";
@@ -209,6 +210,12 @@ export type ReactiveLabelTerminal = {
210 block: ReactiveBlock;
211 id: InstructionId;
212 };
213 +export type ReactiveTryTerminal = {
214 + kind: "try";
215 + block: ReactiveBlock;
216 + handler: ReactiveBlock;
217 + id: InstructionId;
218 +};
219
220 /**
221 * A function lowered to HIR form, ie where its body is lowered to an HIR control-flow graph
@@ -280,7 +287,8 @@ export type Terminal =
287 | OptionalTerminal
288 | LabelTerminal
289 | SequenceTerminal
283 - | MaybeThrowTerminal;
290 + | MaybeThrowTerminal
291 + | TryTerminal;
292
293 function _staticInvariantTerminalHasLocation(
294 terminal: Terminal
@@ -445,6 +453,16 @@ export type SequenceTerminal = {
453 loc: SourceLocation;
454 };
455
456 +export type TryTerminal = {
457 + kind: "try";
458 + block: BlockId;
459 + handler: BlockId;
460 + // TODO: support `finally`
461 + fallthrough: BlockId | null;
462 + id: InstructionId;
463 + loc: SourceLocation;
464 +};
465 +
466 export type MaybeThrowTerminal = {
467 kind: "maybe-throw";
468 continuation: BlockId;
compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+4
@@ -754,6 +754,10 @@ export function reversePostorderBlocks(func: HIR): void {
754 visit(terminal.continuation);
755 break;
756 }
757 + case "try": {
758 + visit(terminal.block);
759 + break;
760 + }
761 case "unsupported": {
762 break;
763 }
compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts
+9
@@ -80,6 +80,7 @@ export function printMixedHIR(
80 return printInstruction(value);
81 }
82 switch (value.kind) {
83 + case "try":
84 case "maybe-throw":
85 case "sequence":
86 case "label":
@@ -235,6 +236,14 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
236 value = `MaybeThrow continuation=bb${terminal.continuation} handler=bb${terminal.handler}`;
237 break;
238 }
239 + case "try": {
240 + value = `Try block=bb${terminal.block} catch=bb${
241 + terminal.handler
242 + } fallthrough=${
243 + terminal.fallthrough != null ? `bb${terminal.fallthrough}` : ""
244 + }`;
245 + break;
246 + }
247 default: {
248 assertExhaustive(
249 terminal,
compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts
+29
@@ -742,6 +742,20 @@ export function mapTerminalSuccessors(
742 loc: terminal.loc,
743 };
744 }
745 + case "try": {
746 + const block = fn(terminal.block);
747 + const handler = fn(terminal.handler);
748 + const fallthrough =
749 + terminal.fallthrough !== null ? fn(terminal.fallthrough) : null;
750 + return {
751 + kind: "try",
752 + block,
753 + handler,
754 + fallthrough,
755 + id: makeInstructionId(0),
756 + loc: terminal.loc,
757 + };
758 + }
759 case "unsupported": {
760 return terminal;
761 }
@@ -769,6 +783,7 @@ export function terminalFallthrough(terminal: Terminal): BlockId | null {
783 case "unsupported": {
784 return null;
785 }
786 + case "try":
787 case "do-while":
788 case "for-of":
789 case "for":
@@ -858,6 +873,14 @@ export function mapOptionalFallthroughs(
873 const _: BlockId = terminal.fallthrough;
874 break;
875 }
876 + case "try": {
877 + if (terminal.fallthrough !== null) {
878 + terminal.fallthrough = fn(terminal.fallthrough);
879 + } else {
880 + terminal.fallthrough = null;
881 + }
882 + break;
883 + }
884 default: {
885 assertExhaustive(
886 terminal,
@@ -935,6 +958,10 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
958 yield terminal.handler;
959 break;
960 }
961 + case "try": {
962 + yield terminal.block;
963 + break;
964 + }
965 case "unsupported":
966 break;
967 default: {
@@ -974,6 +1001,7 @@ export function mapTerminalOperands(
1001 terminal.value = fn(terminal.value);
1002 break;
1003 }
1004 + case "try":
1005 case "maybe-throw":
1006 case "sequence":
1007 case "label":
@@ -1023,6 +1051,7 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
1051 yield terminal.value;
1052 break;
1053 }
1054 + case "try":
1055 case "maybe-throw":
1056 case "sequence":
1057 case "label":
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+37
@@ -637,12 +637,49 @@ class Driver {
637 break;
638 }
639 case "maybe-throw": {
640 + // ReactiveFunction does not explicit model maybe-throw semantics,
641 + // so these terminals flatten away
642 this.visitBlock(
643 this.cx.ir.blocks.get(terminal.continuation)!,
644 blockValue
645 );
646 break;
647 }
648 + case "try": {
649 + const fallthroughId =
650 + terminal.fallthrough !== null &&
651 + !this.cx.isScheduled(terminal.fallthrough)
652 + ? terminal.fallthrough
653 + : null;
654 + if (fallthroughId !== null) {
655 + const scheduleId = this.cx.schedule(fallthroughId, "if");
656 + scheduleIds.push(scheduleId);
657 + }
658 +
659 + const block = this.traverseBlock(
660 + this.cx.ir.blocks.get(terminal.block)!
661 + );
662 + const handler = this.traverseBlock(
663 + this.cx.ir.blocks.get(terminal.handler)!
664 + );
665 +
666 + this.cx.unscheduleAll(scheduleIds);
667 + blockValue.push({
668 + kind: "terminal",
669 + label: fallthroughId,
670 + terminal: {
671 + kind: "try",
672 + block,
673 + handler,
674 + id: terminal.id,
675 + },
676 + });
677 +
678 + if (fallthroughId !== null) {
679 + this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
680 + }
681 + break;
682 + }
683 case "unsupported": {
684 CompilerError.invariant(false, {
685 reason: "Unexpected unsupported terminal",
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+6
@@ -515,6 +515,12 @@ function codegenTerminal(
515 case "label": {
516 return codegenBlock(cx, terminal.block);
517 }
518 + case "try": {
519 + return t.tryStatement(
520 + codegenBlock(cx, terminal.block),
521 + t.catchClause(null, codegenBlock(cx, terminal.handler))
522 + );
523 + }
524 default: {
525 assertExhaustive(
526 terminal,
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/FlattenReactiveLoops.ts
+1
@@ -54,6 +54,7 @@ class Transform extends ReactiveFunctionTransform<boolean> {
54 break;
55 }
56 // Non-loop terminals passthrough is contextual, inherits the parent isWithinScope
57 + case "try":
58 case "label":
59 case "break":
60 case "continue":
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts
+8
@@ -276,6 +276,14 @@ function printTerminal(writer: Writer, terminal: ReactiveTerminal): void {
276 writer.writeLine("}");
277 break;
278 }
279 + case "try": {
280 + writer.writeLine(`[${terminal.id}] try {`);
281 + printReactiveInstructions(writer, terminal.block);
282 + writer.writeLine(`} catch {`);
283 + printReactiveInstructions(writer, terminal.handler);
284 + writer.writeLine("}");
285 + break;
286 + }
287 default:
288 assertExhaustive(terminal, `Unhandled terminal ${terminal}`);
289 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+5
@@ -670,6 +670,11 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
670 this.visitBlock(terminal.block, context);
671 break;
672 }
673 + case "try": {
674 + this.visitBlock(terminal.block, context);
675 + this.visitBlock(terminal.handler, context);
676 + break;
677 + }
678 default: {
679 assertExhaustive(
680 terminal,
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/visitors.ts
+10
@@ -151,6 +151,11 @@ export class ReactiveFunctionVisitor<TState = void> {
151 this.visitBlock(terminal.block, state);
152 break;
153 }
154 + case "try": {
155 + this.visitBlock(terminal.block, state);
156 + this.visitBlock(terminal.handler, state);
157 + break;
158 + }
159 default: {
160 assertExhaustive(
161 terminal,
@@ -361,6 +366,11 @@ export function mapTerminalBlocks(
366 terminal.block = fn(terminal.block);
367 break;
368 }
369 + case "try": {
370 + terminal.block = fn(terminal.block);
371 + terminal.handler = fn(terminal.handler);
372 + break;
373 + }
374 default: {
375 assertExhaustive(
376 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 "try":
143 case "maybe-throw":
144 case "unsupported": {
145 break;
@@ -247,6 +248,7 @@ function printTerminalArrows(blockId: BlockId, terminal: Terminal): string {
248 buffer.push(printJumpArrow(blockId, terminal.fallthrough, "fallthrough"));
249 break;
250 }
251 + case "try":
252 case "maybe-throw":
253 case "throw":
254 case "return":