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.
This commit is contained in:
Joe Savona
2023-09-07 14:22:28 -07:00
parent 20203cd889
commit 0977115440
6 changed files with 75 additions and 1 deletions
@@ -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
@@ -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<t.Function>;
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;
}
@@ -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> | string {
value = `Unsupported`;
break;
}
case "maybe-throw": {
value = `MaybeThrow continuation=bb${terminal.continuation} handler=bb${terminal.handler}`;
break;
}
default: {
assertExhaustive(
terminal,
@@ -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<BlockId> {
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<Place> {
yield terminal.value;
break;
}
case "maybe-throw":
case "sequence":
case "label":
case "optional":
@@ -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",
@@ -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": {