TryStatement: partial lowering

Implements the core lowering logic for try/catch: 

* Inside of the `try` block, we use the new HIRBuilder mode to wrap every 
instruction in a separate basic block with a maybe-throw terminal 

* We emit a 'try' terminal for the try/catch itself 

For basic examples this already works correctly. But we don't handle catch 
clause params yet.
This commit is contained in:
Joe Savona
2023-09-07 16:32:53 -07:00
parent 7f5bac171e
commit 936b96b24f
9 changed files with 252 additions and 2 deletions
@@ -913,6 +913,68 @@ function lowerStatement(
case "EmptyStatement": {
return;
}
case "TryStatement": {
const stmt = stmtPath as NodePath<t.TryStatement>;
const continuationBlock = builder.reserve("block");
const handlerPath = stmt.get("handler");
// NOTE: null check is for older babel versions, the hasNode() check is for TS to refine the type
if (handlerPath.node == null || !handlerPath.hasNode()) {
builder.errors.push({
reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch clause`,
severity: ErrorSeverity.Todo,
loc: stmt.node.loc ?? null,
suggestions: null,
});
return;
}
const handler = builder.enter("block", (_blockId) => {
lowerStatement(builder, handlerPath.get("body"));
return {
kind: "goto",
block: continuationBlock.id,
variant: GotoVariant.Break,
id: makeInstructionId(0),
loc: handlerPath.node.loc ?? GeneratedSource,
};
});
if (stmt.get("finalizer").node != null) {
builder.errors.push({
reason: `(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`,
severity: ErrorSeverity.Todo,
loc: stmt.node.loc ?? null,
suggestions: null,
});
}
const block = builder.enter("block", (_blockId) => {
const block = stmt.get("block");
builder.enterTryCatch(handler, () => {
lowerStatement(builder, block);
});
return {
kind: "goto",
block: continuationBlock.id,
variant: GotoVariant.Break,
id: makeInstructionId(0),
loc: block.node.loc ?? GeneratedSource,
};
});
builder.terminateWithContinuation(
{
kind: "try",
block,
handler,
fallthrough: continuationBlock.id,
id: makeInstructionId(0),
loc: stmt.node.loc ?? GeneratedSource,
},
continuationBlock
);
return;
}
case "ForInStatement":
case "ClassDeclaration":
case "DeclareClass":
@@ -932,7 +994,6 @@ function lowerStatement(
case "ImportDeclaration":
case "InterfaceDeclaration":
case "OpaqueType":
case "TryStatement":
case "TypeAlias":
case "TSDeclareFunction":
case "TSEnumDeclaration":
@@ -79,7 +79,7 @@ export type Bindings = Map<
// Determines how instructions should be constructed in order to preserve
// exception semantics
type ExceptionsMode =
export 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" }
@@ -158,6 +158,13 @@ export default class HIRBuilder {
}
}
enterTryCatch(handler: BlockId, fn: () => void): void {
const prevMode = this.#mode;
this.#mode = { kind: "CatchExceptions", handler };
fn();
this.#mode = prevMode;
}
makeTemporary(): Identifier {
const id = this.nextIdentifierId;
return {
@@ -0,0 +1,53 @@
## Input
```javascript
function Component(props) {
const x = [];
try {
x.push(foo());
} catch {
x.push(bar());
}
return x;
}
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(3);
let x;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
x = [];
try {
let t0;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t0 = foo();
$[1] = t0;
} else {
t0 = $[1];
}
x.push(t0);
} catch {
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = bar();
$[2] = t1;
} else {
t1 = $[2];
}
x.push(t1);
}
$[0] = x;
} else {
x = $[0];
}
return x;
}
```
@@ -0,0 +1,9 @@
function Component(props) {
const x = [];
try {
x.push(foo());
} catch {
x.push(bar());
}
return x;
}
@@ -0,0 +1,58 @@
## Input
```javascript
function Component(props) {
const x = [];
try {
x.push(foo());
} catch {
x.push(bar());
}
x.push(props.value); // extend the mutable range to include the try/catch
return x;
}
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(4);
const c_0 = $[0] !== props.value;
let x;
if (c_0) {
x = [];
try {
let t0;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t0 = foo();
$[2] = t0;
} else {
t0 = $[2];
}
x.push(t0);
} catch {
let t1;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t1 = bar();
$[3] = t1;
} else {
t1 = $[3];
}
x.push(t1);
}
x.push(props.value);
$[0] = props.value;
$[1] = x;
} else {
x = $[1];
}
return x;
}
```
@@ -0,0 +1,10 @@
function Component(props) {
const x = [];
try {
x.push(foo());
} catch {
x.push(bar());
}
x.push(props.value); // extend the mutable range to include the try/catch
return x;
}
@@ -0,0 +1,40 @@
## Input
```javascript
function Component(props) {
let x;
try {
x = foo();
} catch {
x = null;
}
return x;
}
```
## Code
```javascript
import { unstable_useMemoCache as useMemoCache } from "react";
function Component(props) {
const $ = useMemoCache(1);
let x = undefined;
try {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = foo();
$[0] = t0;
} else {
t0 = $[0];
}
x = t0;
} catch {
x = null;
}
return x;
}
```
@@ -0,0 +1,9 @@
function Component(props) {
let x;
try {
x = foo();
} catch {
x = null;
}
return x;
}
@@ -412,6 +412,9 @@ const skipFilter = new Set([
"fbt-call-complex-param-value",
"fbt-template-string-same-scope",
"component-declaration-basic.flow",
"try-catch-mutate-outer-value",
"try-catch-within-mutable-range",
"try-catch",
// TODO: 🌲
"forest-basic",