diff --git a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts index 14e6506e69..ed0acaa722 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts @@ -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 }); 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 ff91ae19ff..ce04ab1ac9 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts @@ -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, + }; + } + } +} diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/index.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/index.ts index e7aa1f134a..b6f3ad631f 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/index.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/index.ts @@ -15,6 +15,7 @@ export * from "./HIR"; export { markInstructionIds, markPredecessors, + removeUnnecessaryTryCatch, removeUnreachableFallthroughs, reversePostorderBlocks, } from "./HIRBuilder"; diff --git a/compiler/packages/babel-plugin-react-forget/src/Optimization/ConstantPropagation.ts b/compiler/packages/babel-plugin-react-forget/src/Optimization/ConstantPropagation.ts index 28598faeef..56b3dc6fd5 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Optimization/ConstantPropagation.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Optimization/ConstantPropagation.ts @@ -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); diff --git a/compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts b/compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts index abfe0dbd03..c437d97309 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts @@ -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); } } diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-empty-try.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-empty-try.expect.md new file mode 100644 index 0000000000..9172e1423c --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-empty-try.expect.md @@ -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 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-empty-try.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-empty-try.js new file mode 100644 index 0000000000..0927b1e0b8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-empty-try.js @@ -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 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-returns.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-returns.expect.md new file mode 100644 index 0000000000..acb5734487 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-returns.expect.md @@ -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 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-returns.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-returns.js new file mode 100644 index 0000000000..b05afadc6d --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-returns.js @@ -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 }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-throws-after-constant-propagation.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-throws-after-constant-propagation.expect.md new file mode 100644 index 0000000000..9003edefd8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-throws-after-constant-propagation.expect.md @@ -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 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-throws-after-constant-propagation.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-throws-after-constant-propagation.js new file mode 100644 index 0000000000..f0b8f7351a --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-immediately-throws-after-constant-propagation.js @@ -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 }], +};