diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts index ce89491a10..f92ccfceed 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts @@ -279,7 +279,8 @@ export type Terminal = | TernaryTerminal | OptionalTerminal | LabelTerminal - | SequenceTerminal; + | SequenceTerminal + | MaybeThrowTerminal; function _staticInvariantTerminalHasLocation( terminal: Terminal @@ -444,6 +445,14 @@ export type SequenceTerminal = { loc: SourceLocation; }; +export type MaybeThrowTerminal = { + kind: "maybe-throw"; + continuation: BlockId; + handler: BlockId; + id: InstructionId; + loc: SourceLocation; +}; + /** * Instructions generally represent expressions but with all nesting flattened away, * such that all operands to each instruction are either primitive values OR are diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts index 8c9d63955c..1e3bee140d 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts @@ -77,6 +77,17 @@ export type Bindings = Map< { node: t.Identifier; identifier: Identifier } >; +// Determines how instructions should be constructed in order to preserve +// exception semantics +type ExceptionsMode = + // Mode used for code not covered by explicit exception handling, any + // errors are assumed to be thrown out of the function + | { kind: "ThrowExceptions" } + // Mode used for code that *is* covered by explicit exception handling + // (ie try/catch), which requires modeling the possibility of control + // flow to the exception handler. + | { kind: "CatchExceptions"; handler: BlockId }; + /** * Helper class for constructing a CFG */ @@ -88,6 +99,7 @@ export default class HIRBuilder { #context: t.Identifier[]; #bindings: Bindings; #env: Environment; + #mode: ExceptionsMode = { kind: "ThrowExceptions" }; parentFunction: NodePath; errors: CompilerError = new CompilerError(); @@ -130,6 +142,20 @@ export default class HIRBuilder { */ push(instruction: Instruction): void { this.#current.instructions.push(instruction); + if (this.#mode.kind === "CatchExceptions") { + const handler = this.#mode.handler; + const continuationBlock = this.reserve(this.currentBlockKind()); + this.terminateWithContinuation( + { + kind: "maybe-throw", + continuation: continuationBlock.id, + handler, + id: makeInstructionId(0), + loc: instruction.loc, + }, + continuationBlock + ); + } } makeTemporary(): Identifier { @@ -723,6 +749,11 @@ export function reversePostorderBlocks(func: HIR): void { visit(terminal.block); break; } + case "maybe-throw": { + visit(terminal.handler); + visit(terminal.continuation); + break; + } case "unsupported": { break; } diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts index f7de1aa59c..fc952b5578 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts @@ -80,6 +80,7 @@ export function printMixedHIR( return printInstruction(value); } switch (value.kind) { + case "maybe-throw": case "sequence": case "label": case "optional": @@ -230,6 +231,10 @@ export function printTerminal(terminal: Terminal): Array | string { value = `Unsupported`; break; } + case "maybe-throw": { + value = `MaybeThrow continuation=bb${terminal.continuation} handler=bb${terminal.handler}`; + break; + } default: { assertExhaustive( terminal, diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts index a6e1d1816a..b8d5c789ef 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts @@ -731,6 +731,17 @@ export function mapTerminalSuccessors( loc: terminal.loc, }; } + case "maybe-throw": { + const continuation = fn(terminal.continuation); + const handler = fn(terminal.handler); + return { + kind: "maybe-throw", + continuation, + handler, + id: makeInstructionId(0), + loc: terminal.loc, + }; + } case "unsupported": { return terminal; } @@ -750,6 +761,7 @@ export function mapTerminalSuccessors( */ export function terminalFallthrough(terminal: Terminal): BlockId | null { switch (terminal.kind) { + case "maybe-throw": case "branch": case "goto": case "return": @@ -784,6 +796,7 @@ export function mapOptionalFallthroughs( fn: (block: BlockId) => BlockId | null ): void { switch (terminal.kind) { + case "maybe-throw": case "branch": case "goto": case "return": @@ -917,6 +930,11 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable { yield terminal.block; break; } + case "maybe-throw": { + yield terminal.continuation; + yield terminal.handler; + break; + } case "unsupported": break; default: { @@ -956,6 +974,7 @@ export function mapTerminalOperands( terminal.value = fn(terminal.value); break; } + case "maybe-throw": case "sequence": case "label": case "optional": @@ -1004,6 +1023,7 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable { yield terminal.value; break; } + case "maybe-throw": case "sequence": case "label": case "optional": diff --git a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts index 3378064bb7..72c7b25d79 100644 --- a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts @@ -636,6 +636,13 @@ class Driver { } break; } + case "maybe-throw": { + this.visitBlock( + this.cx.ir.blocks.get(terminal.continuation)!, + blockValue + ); + break; + } case "unsupported": { CompilerError.invariant(false, { reason: "Unexpected unsupported terminal", diff --git a/compiler/packages/babel-plugin-react-forget/src/Utils/VisualizeHIRMermaid.ts b/compiler/packages/babel-plugin-react-forget/src/Utils/VisualizeHIRMermaid.ts index 6696c85ef5..994578e62c 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Utils/VisualizeHIRMermaid.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Utils/VisualizeHIRMermaid.ts @@ -139,6 +139,7 @@ function printTerminalLabel(terminal: Terminal): string { buffer.push("Sequence"); break; } + case "maybe-throw": case "unsupported": { break; } @@ -246,6 +247,7 @@ function printTerminalArrows(blockId: BlockId, terminal: Terminal): string { buffer.push(printJumpArrow(blockId, terminal.fallthrough, "fallthrough")); break; } + case "maybe-throw": case "throw": case "return": case "unsupported": {