TryStatement: handle edge case control flows

Adds handling for some cases where the handler is unreachable (or is provably 
unreachable after analysis & optimization), where the try/catch can be flattened 
away: 

* The try block is empty. Nothing can throw, so the handler is unreachable. 

* The try block will always return. It can't return anything interesting (ie the 
result of a function call or variable load) since those could throw, but a try 
block that always return a primitive means the handler is unreachable. 

* The same, except where we only determine that the try block always returns via 
constant propagation.
This commit is contained in:
Joe Savona
2023-09-08 13:54:45 -07:00
parent c8cebdd946
commit de2b684bc5
11 changed files with 226 additions and 0 deletions
@@ -137,6 +137,9 @@ export function* run(
deadCodeElimination(hir);
yield log({ kind: "hir", name: "DeadCodeElimination", value: hir });
pruneMaybeThrows(hir);
yield log({ kind: "hir", name: "PruneMaybeThrows", value: hir });
inferMutableRanges(hir);
yield log({ kind: "hir", name: "InferMutableRanges", value: hir });
@@ -316,6 +316,7 @@ export default class HIRBuilder {
removeUnreachableForUpdates(ir);
removeUnreachableFallthroughs(ir);
removeDeadDoWhileStatements(ir);
removeUnnecessaryTryCatch(ir);
markInstructionIds(ir);
markPredecessors(ir);
@@ -846,3 +847,24 @@ function getTargetIfIndirection(block: BasicBlock): number | null {
? block.terminal.block
: null;
}
/**
* Finds try terminals where the handler is unreachable, and converts the try
* to a goto(terminal.fallthrough)
*/
export function removeUnnecessaryTryCatch(fn: HIR): void {
for (const [, block] of fn.blocks) {
if (
block.terminal.kind === "try" &&
!fn.blocks.has(block.terminal.handler)
) {
block.terminal = {
kind: "goto",
block: block.terminal.block,
id: makeInstructionId(0),
loc: block.terminal.loc,
variant: GotoVariant.Break,
};
}
}
}
@@ -15,6 +15,7 @@ export * from "./HIR";
export {
markInstructionIds,
markPredecessors,
removeUnnecessaryTryCatch,
removeUnreachableFallthroughs,
reversePostorderBlocks,
} from "./HIRBuilder";
@@ -28,6 +28,7 @@ import {
} from "../HIR";
import {
removeDeadDoWhileStatements,
removeUnnecessaryTryCatch,
removeUnreachableForUpdates,
} from "../HIR/HIRBuilder";
import { eliminateRedundantPhi } from "../SSA";
@@ -66,6 +67,7 @@ function constantPropagationImpl(fn: HIRFunction, constants: Constants): void {
removeUnreachableFallthroughs(fn.body);
removeUnreachableForUpdates(fn.body);
removeDeadDoWhileStatements(fn.body);
removeUnnecessaryTryCatch(fn.body);
markInstructionIds(fn.body);
markPredecessors(fn.body);
@@ -9,8 +9,20 @@ import {
GotoVariant,
HIRFunction,
Instruction,
assertConsistentIdentifiers,
assertTerminalSuccessorsExist,
mergeConsecutiveBlocks,
removeUnreachableFallthroughs,
reversePostorderBlocks,
} from "../HIR";
import {
markInstructionIds,
markPredecessors,
removeDeadDoWhileStatements,
removeUnnecessaryTryCatch,
removeUnreachableForUpdates,
} from "../HIR/HIRBuilder";
import { eliminateRedundantPhi } from "../SSA";
/**
* This pass prunes `maybe-throw` terminals for blocks that can provably *never* throw.
@@ -20,7 +32,35 @@ import {
export function pruneMaybeThrows(fn: HIRFunction): void {
const didPrune = pruneMaybeThrowsImpl(fn);
if (didPrune) {
// If terminals have changed then blocks may have become newly unreachable.
// Re-run minification of the graph (incl reordering instruction ids)
reversePostorderBlocks(fn.body);
removeUnreachableFallthroughs(fn.body);
removeUnreachableForUpdates(fn.body);
removeDeadDoWhileStatements(fn.body);
removeUnnecessaryTryCatch(fn.body);
markInstructionIds(fn.body);
markPredecessors(fn.body);
// Now that predecessors are updated, prune phi operands that can never be reached
for (const [, block] of fn.body.blocks) {
for (const phi of block.phis) {
for (const [predecessor] of phi.operands) {
if (!block.preds.has(predecessor)) {
phi.operands.delete(predecessor);
}
}
}
}
// By removing some phi operands, there may be phis that were not previously
// redundant but now are
eliminateRedundantPhi(fn);
// Finally, merge together any blocks that are now guaranteed to execute
// consecutively
mergeConsecutiveBlocks(fn);
assertConsistentIdentifiers(fn);
assertTerminalSuccessorsExist(fn);
}
}
@@ -0,0 +1,35 @@
## Input
```javascript
function Component(props) {
let x = props.default;
try {
} catch (e) {
x = e;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ default: 42 }],
};
```
## Code
```javascript
function Component(props) {
const x = props.default;
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ default: 42 }],
};
```
@@ -0,0 +1,13 @@
function Component(props) {
let x = props.default;
try {
} catch (e) {
x = e;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ default: 42 }],
};
@@ -0,0 +1,38 @@
## Input
```javascript
function Component(props) {
let x = props.default;
try {
// note: has to be a primitive, we want an instruction that cannot throw
// to ensure there is no maybe-throw terminal
const y = 42;
return y;
} catch (e) {
x = e;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ default: 42 }],
};
```
## Code
```javascript
function Component(props) {
return 42;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ default: 42 }],
};
```
@@ -0,0 +1,17 @@
function Component(props) {
let x = props.default;
try {
// note: has to be a primitive, we want an instruction that cannot throw
// to ensure there is no maybe-throw terminal
const y = 42;
return y;
} catch (e) {
x = e;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ default: 42 }],
};
@@ -0,0 +1,38 @@
## Input
```javascript
function Component(props) {
let x = props.default;
const y = 42;
try {
// note: this constant propagates so that we know
// the handler is unreachable
return y;
} catch (e) {
x = e;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ default: 42 }],
};
```
## Code
```javascript
function Component(props) {
return 42;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ default: 42 }],
};
```
@@ -0,0 +1,17 @@
function Component(props) {
let x = props.default;
const y = 42;
try {
// note: this constant propagates so that we know
// the handler is unreachable
return y;
} catch (e) {
x = e;
}
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ default: 42 }],
};