From ea1a18ec169f459bc5b389c35af197db268cbe31 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Tue, 8 Nov 2022 21:14:10 -0800 Subject: [PATCH] Emit labeled ifs/switch/break; gen each block exactly once The approach is very similar to what BuildHIR does to resolve break and continue targets during IR construction: * We annotate goto targets as either a break or a continue (during HIR construction). This is necessary to reconstruct the right kind in codegen. * Codegen continues to work by traversing the IR as if it were a tree, relying on the `fallthrough` branches of if/switch to be able to visit the consequent/alternate recursively and then emit the fallthrough branch. * We track a Set of blocks that are scheduled to be emitted by some parent in the tree. Nested ifs may all have the same fallthrough branch, which we only want to emit once. This set helps us to know that a parent is already going to emit some block, such that children can skip it. * We also keep a stack of break targets that are in scope, and use this to convert gotos appropriately, as either a break, continue, or nothing at all (for example a switch case that falls through has no explicit syntax to model this fall-through, the only option is to emit nothing for the goto). * Then, if/switch have to carefully check whether each branch should be emitted or not. For example, if the alternate is already scheduled to be emitted (by a parent), then we emit a block with a break statement instead. * Switch in particular is tricky, because we need to know that subsequent cases are scheduled, but only for preceding blocks. So we visit the cases in reverse order (not surprisingly, we do the same thing during IR construction for similar reasons!). The bookkeeping is a bit finicky but this works reliably. There are some cases where we could try to emit an unlabeled break instead of a labeled break, or avoid emitting a label at all (if nothing will explicitly break to that label), but overall the generated code is readable enough that i'm inclined to ship and iterate. I'm open to feedback though, as always! --- compiler/forget/src/HIR/BuildHIR.ts | 26 +- compiler/forget/src/HIR/Codegen.ts | 307 +++++++++++++++--- compiler/forget/src/HIR/HIR.ts | 14 +- compiler/forget/src/HIR/visitors.ts | 1 + .../fixtures/hir/component.expect.md | 1 - .../fixtures/hir/conditional-break.expect.md | 12 +- .../hir/conditional-on-mutable.expect.md | 12 +- ...reeze-possibly-mutable-arguments.expect.md | 4 +- .../hir/independent-across-if.expect.md | 3 +- .../hir/interdependent-across-if.expect.md | 3 +- .../fixtures/hir/logical-expression.expect.md | 12 +- .../hir/mutable-lifetime-loops.expect.md | 1 - .../mutable-lifetime-with-aliasing.expect.md | 160 +-------- .../hir/mutable-liverange-loop.expect.md | 1 - .../hir/reassignment-conditional.expect.md | 3 +- .../fixtures/hir/reverse-postorder.expect.md | 17 +- .../__tests__/fixtures/hir/simple.expect.md | 2 +- .../fixtures/hir/ssa-call-jsx-2.expect.md | 4 +- .../hir/ssa-complex-multiple-if.expect.md | 6 +- .../hir/ssa-complex-single-if.expect.md | 3 +- .../fixtures/hir/ssa-for-of.expect.md | 1 - .../__tests__/fixtures/hir/ssa-for.expect.md | 1 - .../fixtures/hir/ssa-if-else.expect.md | 4 +- .../ssa-nested-loops-no-reassign.expect.md | 1 - .../hir/ssa-objectexpression-phi.expect.md | 4 +- .../fixtures/hir/ssa-return.expect.md | 3 +- .../fixtures/hir/ssa-simple-phi.expect.md | 4 +- .../fixtures/hir/ssa-single-if.expect.md | 3 +- .../fixtures/hir/ssa-switch.expect.md | 7 +- .../fixtures/hir/ssa-throw.expect.md | 3 +- .../hir/ssa-while-no-reassign.expect.md | 1 - .../fixtures/hir/ssa-while.expect.md | 1 - .../hir/switch-non-final-default.expect.md | 15 +- .../hir/switch-with-fallthrough.expect.md | 124 +++++++ .../fixtures/hir/switch-with-fallthrough.js | 27 ++ .../__tests__/fixtures/hir/switch.expect.md | 4 +- 36 files changed, 486 insertions(+), 309 deletions(-) create mode 100644 compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.js diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 3bfb676a70..c53fb7c000 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -12,6 +12,7 @@ import { invariant } from "../CompilerError"; import { Effect, GeneratedSource, + GotoVariant, HIRFunction, IfTerminal, InstructionKind, @@ -133,6 +134,7 @@ function lowerStatement( return { kind: "goto", block: continuationBlock.id, + variant: GotoVariant.Break, }; }); // Block for the alternate (if the test is not truthy) @@ -144,6 +146,7 @@ function lowerStatement( return { kind: "goto", block: continuationBlock.id, + variant: GotoVariant.Break, }; }); } else { @@ -172,6 +175,7 @@ function lowerStatement( builder.terminate({ kind: "goto", block, + variant: GotoVariant.Break, }); return; } @@ -181,6 +185,7 @@ function lowerStatement( builder.terminate({ kind: "goto", block, + variant: GotoVariant.Continue, }); return; } @@ -200,8 +205,7 @@ function lowerStatement( return { kind: "goto", block: conditionalBlock.id, - fallthrough: null, - tests: null, + variant: GotoVariant.Continue, }; } ); @@ -211,6 +215,7 @@ function lowerStatement( { kind: "goto", block: conditionalBlock.id, + variant: GotoVariant.Break, }, conditionalBlock ); @@ -250,8 +255,7 @@ function lowerStatement( return { kind: "goto", block: conditionalBlock.id, - fallthrough: null, - tests: null, + variant: GotoVariant.Continue, }; } ); @@ -261,6 +265,7 @@ function lowerStatement( { kind: "goto", block: conditionalBlock.id, + variant: GotoVariant.Break, }, conditionalBlock ); @@ -309,6 +314,7 @@ function lowerStatement( builder.complete(updateBlock, { kind: "goto", block: conditionalBlock.id, + variant: GotoVariant.Break, }); /** * Construct the loop itself: the loop body wraps around to the update block @@ -320,7 +326,7 @@ function lowerStatement( return { kind: "goto", block: updateBlock.id, - fallthrough: null, + variant: GotoVariant.Continue, }; }); }); @@ -329,6 +335,7 @@ function lowerStatement( { kind: "goto", block: conditionalBlock.id, + variant: GotoVariant.Break, }, conditionalBlock ); @@ -355,6 +362,7 @@ function lowerStatement( terminal = { kind: "goto", block: loopBlock, + variant: GotoVariant.Break, }; } builder.terminateWithContinuation(terminal, continuationBlock); @@ -385,7 +393,7 @@ function lowerStatement( }); // do-while unconditionally enters the loop builder.terminateWithContinuation( - { kind: "goto", block: loopBlock }, + { kind: "goto", block: loopBlock, variant: GotoVariant.Break }, continuationBlock ); return; @@ -407,6 +415,7 @@ function lowerStatement( return { kind: "goto", block: conditionalBlock.id, + variant: GotoVariant.Continue, }; } ); @@ -419,6 +428,7 @@ function lowerStatement( { kind: "goto", block: conditionalBlock.id, + variant: GotoVariant.Break, }, conditionalBlock ); @@ -464,6 +474,7 @@ function lowerStatement( { kind: "goto", block: continuationBlock.id, + variant: GotoVariant.Break, }, continuationBlock ); @@ -509,6 +520,7 @@ function lowerStatement( return { kind: "goto", block: fallthrough, + variant: GotoVariant.Break, }; }); }); @@ -1005,6 +1017,7 @@ function lowerConditional( return { kind: "goto", block: continuationBlock.id, + variant: GotoVariant.Break, }; }); // Block for the alternate (if the test is not truthy) @@ -1019,6 +1032,7 @@ function lowerConditional( return { kind: "goto", block: continuationBlock.id, + variant: GotoVariant.Break, }; }); const terminal: IfTerminal = { diff --git a/compiler/forget/src/HIR/Codegen.ts b/compiler/forget/src/HIR/Codegen.ts index 32cccf4b90..466f44a298 100644 --- a/compiler/forget/src/HIR/Codegen.ts +++ b/compiler/forget/src/HIR/Codegen.ts @@ -10,6 +10,8 @@ import { assertExhaustive } from "../Common/utils"; import { invariant } from "../CompilerError"; import { BasicBlock, + BlockId, + GotoVariant, HIR, HIRFunction, Identifier, @@ -43,7 +45,7 @@ import { todoInvariant } from "./todo"; */ export default function codegen(fn: HIRFunction): t.Function { const entry = fn.body.blocks.get(fn.body.entry)!; - const cx: Context = { ir: fn.body, temp: new Map() }; + const cx = new Context(fn.body); const body = codegenBlock(cx, entry); const params = fn.params.map((param) => convertIdentifier(param.identifier)); return t.functionDeclaration( @@ -55,12 +57,112 @@ export default function codegen(fn: HIRFunction): t.Function { ); } -type Context = { +class Context { ir: HIR; - temp: Map; + temp: Map = new Map(); + #nextScheduleId: number = 0; + + /** + * Used to track which blocks *have been* generated already in order to + * abort if a block is generated a second time. This is an error catching + * mechanism for debugging purposes, and is not used by the codegen algorithm + * to drive decisions about how to emit blocks. + */ + emitted: Set = new Set(); + + /** + * A set of blocks that are already scheduled to be emitted by eg a parent. + * This allows child nodes to avoid re-emitting the same block and emit eg + * a break instead. + */ + #scheduled: Set = new Set(); + + /** + * A stack of blocks that are in scope, used to decide whether/how to emit + * break and continue statements. All blocks in the stack must also be + * in 'scheduled'. + */ + #breakTargets: Array = []; + + constructor(ir: HIR) { + this.ir = ir; + } + + /** + * Record that the given block will be emitted (eg by the codegen of a parent node) + * so that child nodes can avoid re-emitting it. + */ + schedule(block: BlockId, type: "if" | "switch" | "case"): number { + const id = this.#nextScheduleId++; + invariant(!this.#scheduled.has(block), "Block is already scheduled"); + this.#scheduled.add(block); + this.#breakTargets.push({ block, id, type }); + return id; + } + + /** + * Removes a block that was scheduled; must be called after that block is emitted. + */ + unschedule(scheduleId: number): void { + const last = this.#breakTargets.pop(); + invariant( + last !== undefined && last.id === scheduleId, + "Can only unschedule the last target" + ); + this.#scheduled.delete(last.block); + } + + /** + * Helper to unschedule multiple scheduled blocks. The ids should be in + * the order in which they were scheduled, ie most recently scheduled last. + */ + unscheduleAll(scheduleIds: Array): void { + for (let i = scheduleIds.length - 1; i >= 0; i--) { + this.unschedule(scheduleIds[i]!); + } + } + + /** + * Check if the given @param block is scheduled or not. + */ + isScheduled(block: BlockId): boolean { + return this.#scheduled.has(block); + } + + /** + * Lookup the break target for the given @param block. This will return non-null + * if and only if isScheduled() returns true for the given @param block. Returns + * the break target and whether this is the most recent target (which can be used + * to elide unnecessary break statemetns). + */ + getBreakTarget( + block: BlockId + ): { target: BreakTarget; last: boolean } | null { + for (let i = this.#breakTargets.length - 1; i >= 0; i--) { + const target = this.#breakTargets[i]!; + if (target.block === block) { + return { + target, + last: i === this.#breakTargets.length - 1, + }; + } + } + return null; + } +} + +type BreakTarget = { + block: BlockId; + id: number; + type: "if" | "switch" | "case"; }; function codegenBlock(cx: Context, block: BasicBlock): t.BlockStatement { + invariant( + !cx.emitted.has(block.id), + `Cannot emit the same block twice: bb${block.id}` + ); + cx.emitted.add(block.id); const body: Array = []; writeBlock(cx, block, body); return t.blockStatement(body); @@ -71,6 +173,7 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array) { writeInstr(cx, instr, body); } const terminal = block.terminal; + const scheduleIds = []; switch (terminal.kind) { case "return": { const value = @@ -85,64 +188,140 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array) { } case "if": { const test = codegenPlace(cx, terminal.test); - const consequent = codegenBlock( - cx, - cx.ir.blocks.get(terminal.consequent)! - ); - const fallthrough = - terminal.fallthrough !== null && - terminal.fallthrough !== terminal.alternate + const fallthroughId = + terminal.fallthrough !== null && !cx.isScheduled(terminal.fallthrough) ? terminal.fallthrough : null; - if (fallthrough !== null) { - const alternate = codegenBlock( - cx, - cx.ir.blocks.get(terminal.alternate)! - ); - body.push(t.ifStatement(test, consequent, alternate)); - const fallthroughBlock = cx.ir.blocks.get(fallthrough)!; - writeBlock(cx, fallthroughBlock, body); + const alternateId = + terminal.alternate !== terminal.fallthrough ? terminal.alternate : null; + + if (fallthroughId !== null) { + const scheduleId = cx.schedule(fallthroughId, "if"); + scheduleIds.push(scheduleId); + } + + let consequent: t.Statement | null = null; + if (cx.isScheduled(terminal.consequent)) { + consequent = codegenBreak(cx, terminal.consequent); } else { - body.push(t.ifStatement(test, consequent)); - writeBlock(cx, cx.ir.blocks.get(terminal.alternate)!, body); + consequent = codegenBlock(cx, cx.ir.blocks.get(terminal.consequent)!); + } + + let alternate: t.Statement | null = null; + if (alternateId !== null) { + if (cx.isScheduled(alternateId)) { + alternate = codegenBreak(cx, alternateId); + } else { + alternate = codegenBlock(cx, cx.ir.blocks.get(alternateId)!); + } + } + + if (fallthroughId !== null) { + if (consequent === null && alternate === null) { + body.push(t.expressionStatement(test)); + } else { + body.push( + t.labeledStatement( + t.identifier(`bb${fallthroughId}`), + t.ifStatement(test, consequent ?? t.blockStatement([]), alternate) + ) + ); + } + writeBlock(cx, cx.ir.blocks.get(fallthroughId)!, body); + } else { + if (consequent === null && alternate === null) { + body.push(t.expressionStatement(test)); + } else { + body.push( + t.ifStatement(test, consequent ?? t.blockStatement([]), alternate) + ); + } + } + break; + } + case "switch": { + const test = codegenPlace(cx, terminal.test); + const fallthroughId = + terminal.fallthrough !== null && !cx.isScheduled(terminal.fallthrough) + ? terminal.fallthrough + : null; + if (fallthroughId !== null) { + const scheduleId = cx.schedule(fallthroughId, "switch"); + scheduleIds.push(scheduleId); + } + + const cases: Array = []; + [...terminal.cases].reverse().forEach((case_, index) => { + const test = case_.test !== null ? codegenPlace(cx, case_.test) : null; + + let consequent; + if (cx.isScheduled(case_.block)) { + // cases which are empty or contain only a `break` may point to blocks + // that are already scheduled. emit as follows: + // - if the block is for another case branch, don't emit a break and fall-through + // - else, emit an explicit break. + const break_ = codegenBreak(cx, case_.block); + if ( + index === 0 && + break_ === null && + case_.block === terminal.fallthrough && + case_.test === null + ) { + // If the last case statement (first in reverse order) is a default that + // jumps to the fallthrough, then we would emit a useless `default: {}`, + // so instead skip this case. + return; + } + const block = []; + if (break_ !== null) { + block.push(break_); + } + consequent = t.blockStatement(block); + } else { + consequent = codegenBlock(cx, cx.ir.blocks.get(case_.block)!); + const scheduleId = cx.schedule(case_.block, "case"); + scheduleIds.push(scheduleId); + } + cases.push(t.switchCase(test, [consequent])); + }); + cases.reverse(); + + if (fallthroughId !== null) { + body.push( + t.labeledStatement( + t.identifier(`bb${fallthroughId}`), + t.switchStatement(test, cases) + ) + ); + writeBlock(cx, cx.ir.blocks.get(fallthroughId)!, body); + } else { + body.push(t.switchStatement(test, cases)); } break; } case "goto": { - body.push( - t.expressionStatement( - t.stringLiteral("<>") - ) - ); - break; - } - case "switch": { - const cases: Array = []; - terminal.cases.forEach((case_, index) => { - const test = case_.test !== null ? codegenPlace(cx, case_.test) : null; - // If the final case is a `default` *and* points directly to the - // fallthrough branch, then we can skip emitting `default: break` - // since this implied. For a default in any other position, or - // for a default pointing to a different block, emit a case - // normally. - if ( - index === terminal.cases.length - 1 && - test === null && - case_.block === terminal.fallthrough - ) { - return; - } else if (case_.block === terminal.fallthrough) { - // Otherwise for any block that points directly to the fallthrough, - // emit a break instead - cases.push(t.switchCase(test, [t.breakStatement()])); - } else { - const consequent = codegenBlock(cx, cx.ir.blocks.get(case_.block)!); - cases.push(t.switchCase(test, [consequent])); + switch (terminal.variant) { + case GotoVariant.Break: { + const break_ = codegenBreak(cx, terminal.block); + if (break_ !== null) { + body.push(break_); + } + break; + } + case GotoVariant.Continue: { + invariant( + cx.isScheduled(terminal.block), + "Expected continue target to be scheduled" + ); + body.push(t.continueStatement(t.identifier(`bb${terminal.block}`))); + break; + } + default: { + assertExhaustive( + terminal.variant, + `Unexpected goto variant '${terminal.variant}'` + ); } - }); - body.push(t.switchStatement(codegenPlace(cx, terminal.test), cases)); - if (terminal.fallthrough !== null) { - writeBlock(cx, cx.ir.blocks.get(terminal.fallthrough)!, body); } break; } @@ -150,6 +329,28 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array) { assertExhaustive(terminal, "Unexpected terminal"); } } + cx.unscheduleAll(scheduleIds); +} + +function codegenBreak(cx: Context, block: BlockId): t.Statement | null { + const breakTarget = cx.getBreakTarget(block); + if (breakTarget === null) { + // TODO: we should always have a target + return null; + } + const { target, last } = breakTarget; + if (target.type === "case") { + // This break is transitioning to the next case statement. JS doesn't allow + // labeling cases, the only option is to emit a plain break. + return null; + } else if (last) { + // This break is to the most recent break target. Control flow will naturally + // transition to this target, so a break is not required. + return null; + } else { + // We're trying to break somewhere else, emit a label + return t.breakStatement(t.identifier(`bb${block}`)); + } } function writeInstr(cx: Context, instr: Instruction, body: Array) { diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index e95f27b7e7..4bb1c6c363 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -121,8 +121,20 @@ export type Terminal = | SwitchTerminal; export type ThrowTerminal = { kind: "throw"; value: Place }; + export type ReturnTerminal = { kind: "return"; value: Place | null }; -export type GotoTerminal = { kind: "goto"; block: BlockId }; + +export type GotoTerminal = { + kind: "goto"; + block: BlockId; + variant: GotoVariant; +}; + +export enum GotoVariant { + Break = "Break", + Continue = "Continue", +} + export type IfTerminal = { kind: "if"; test: Place; diff --git a/compiler/forget/src/HIR/visitors.ts b/compiler/forget/src/HIR/visitors.ts index 7e4dd2143b..d19bfe512e 100644 --- a/compiler/forget/src/HIR/visitors.ts +++ b/compiler/forget/src/HIR/visitors.ts @@ -142,6 +142,7 @@ export function mapTerminalSuccessors( return { kind: "goto", block: target, + variant: terminal.variant, }; } case "if": { diff --git a/compiler/forget/src/__tests__/fixtures/hir/component.expect.md b/compiler/forget/src/__tests__/fixtures/hir/component.expect.md index f641ac34dd..fb91041897 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/component.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/component.expect.md @@ -93,7 +93,6 @@ function Component$0(props$26) { const renderedItems$29 = []; const seen$30 = new Set$6(); const max$32 = Math$8.max(0, maxItems$28); - ("<>"); } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/conditional-break.expect.md b/compiler/forget/src/__tests__/fixtures/hir/conditional-break.expect.md index 3c62fd6bfa..b8bbf62288 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/conditional-break.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/conditional-break.expect.md @@ -97,7 +97,7 @@ bb1: function Component$0(props$4) { const a_DEBUG$5 = []; a_DEBUG$5.push(props$4.a); - if (props$4.b) { + bb1: if (props$4.b) { return null; } @@ -129,9 +129,8 @@ bb1: function Component$0(props$3) { const a$4 = []; a$4.push(props$3.a); - if (props$3.b) { + bb1: if (props$3.b) { a$4.push(props$3.c); - ("<>"); } a$4.push(props$3.d); @@ -163,7 +162,7 @@ bb1: function Component$0(props$4) { const a$5 = []; a$5.push(props$4.a); - if (props$4.b) { + bb1: if (props$4.b) { a$5.push(props$4.c); return null; } @@ -196,7 +195,7 @@ bb1: function Component$0(props$3) { const a$4 = []; a$4.push(props$3.a); - if (props$3.b) { + bb1: if (props$3.b) { a$4.push(props$3.c); return a$4; } @@ -229,13 +228,12 @@ bb1: function Component$0(props$3) { const a$4 = []; a$4.push(props$3.a); - if (props$3.b) { + bb2: if (props$3.b) { a$4.push(props$3.d); return a$4; } a$4.push(props$3.c); - ("<>"); } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md b/compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md index 59ac6c8e60..102b79b8f7 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md @@ -93,14 +93,12 @@ bb3: function Component$0(props$6) { const a$7 = []; const b$8 = []; - if (b$8) { + bb1: if (b$8) { a$7.push(props$6.p0); - ("<>"); } - if (props$6.p1) { + bb3: if (props$6.p1) { b$8.push(props$6.p2); - ("<>"); } return ; @@ -138,14 +136,12 @@ bb3: function Component$0(props$8) { const a$9 = []; const b$10 = []; - if (mayMutate$4(b$10)) { + bb1: if (mayMutate$4(b$10)) { a$9.push(props$8.p0); - ("<>"); } - if (props$8.p1) { + bb3: if (props$8.p1) { b$10.push(props$8.p2); - ("<>"); } return ; diff --git a/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md index fef3216a20..cedbeacbf1 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md @@ -54,12 +54,10 @@ function Component$0(props$7) { const cond$8 = props$7.cond; const x$9 = props$7.x; let a$10 = undefined; - if (cond$8) { + bb1: if (cond$8) { a$11 = x$9; - ("<>"); } else { a$12 = []; - ("<>"); } useFreeze$5(a$14); diff --git a/compiler/forget/src/__tests__/fixtures/hir/independent-across-if.expect.md b/compiler/forget/src/__tests__/fixtures/hir/independent-across-if.expect.md index e0b43d1597..77ec0a40aa 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/independent-across-if.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/independent-across-if.expect.md @@ -117,10 +117,9 @@ bb1: function Component$0(props$8) { const a$9 = compute$3(props$8.a); const b$10 = compute$3(props$8.b); - if (props$8.c) { + bb1: if (props$8.c) { mutate$5(a$9); mutate$5(b$10); - ("<>"); } return ; diff --git a/compiler/forget/src/__tests__/fixtures/hir/interdependent-across-if.expect.md b/compiler/forget/src/__tests__/fixtures/hir/interdependent-across-if.expect.md index 9fbe734999..a086d557c1 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/interdependent-across-if.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/interdependent-across-if.expect.md @@ -95,9 +95,8 @@ bb1: function Component$0(props$8) { const a$9 = compute$3(props$8.a); const b$10 = compute$3(props$8.b); - if (props$8.c) { + bb1: if (props$8.c) { foo$5(a$9, b$10); - ("<>"); } return ; diff --git a/compiler/forget/src/__tests__/fixtures/hir/logical-expression.expect.md b/compiler/forget/src/__tests__/fixtures/hir/logical-expression.expect.md index 99dc706b61..074ea39abd 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/logical-expression.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/logical-expression.expect.md @@ -43,10 +43,8 @@ bb1: ```javascript function And$0() { - if (f$1()) { - ("<>"); + bb1: if (f$1()) { } else { - ("<>"); } return t8; } @@ -76,10 +74,8 @@ bb1: ```javascript function Or$0() { - if (f$1()) { - ("<>"); + bb1: if (f$1()) { } else { - ("<>"); } return t8; } @@ -111,10 +107,8 @@ bb1: ```javascript function QuestionQuestion$0(props$8) { - if (f$2() != null) { - ("<>"); + bb1: if (f$2() != null) { } else { - ("<>"); } return t14; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-loops.expect.md b/compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-loops.expect.md index 954120c3ae..3ebca18d96 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-loops.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-loops.expect.md @@ -127,7 +127,6 @@ function Component$0(props$12) { let b$14 = {}; let c$15 = {}; let d$16 = {}; - ("<>"); } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-with-aliasing.expect.md b/compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-with-aliasing.expect.md index aa33bf8ec4..31977b9255 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-with-aliasing.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-with-aliasing.expect.md @@ -94,161 +94,11 @@ function Component$0(props$10) { const x$15 = {}; x$15 = b$12; const y$16 = mutate$8(x$15, d$14); - if (a$11) { - if (b$12) { - if (c$13) { - if (d$14) { - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (d$14) { - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (c$13) { - if (d$14) { - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (d$14) { - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (b$12) { - if (c$13) { - if (d$14) { - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (d$14) { - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (c$13) { - if (d$14) { - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (d$14) { - if (y$16) { - mutate$8(x$15, null); - return; - } - - mutate$8(x$15, null); - return; - } - - if (y$16) { - mutate$8(x$15, null); - return; - } - + a$11; + b$12; + c$13; + d$14; + y$16; mutate$8(x$15, null); return; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/mutable-liverange-loop.expect.md b/compiler/forget/src/__tests__/fixtures/hir/mutable-liverange-loop.expect.md index 7c1f565124..36c9a8bfc4 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/mutable-liverange-loop.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/mutable-liverange-loop.expect.md @@ -109,7 +109,6 @@ function Component$0(props$11) { let b$13 = {}; let c$14 = {}; let d$15 = {}; - ("<>"); } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md index 381a43c0f3..527df2ecd4 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md @@ -47,9 +47,8 @@ function Component$0(props$6) { let x$7 = []; x$7.push(props$6.p0); let y$8 = x$7; - if (props$6.p1) { + bb1: if (props$6.p1) { x$9 = []; - ("<>"); } let _$12 = ; diff --git a/compiler/forget/src/__tests__/fixtures/hir/reverse-postorder.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reverse-postorder.expect.md index 0f68566208..8ba7c8b9ce 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/reverse-postorder.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/reverse-postorder.expect.md @@ -83,42 +83,31 @@ bb1: ```javascript function Component$0(props$6) { let x$7 = undefined; - if (props$6.cond) { + bb1: if (props$6.cond) { switch (props$6.test) { case 0: { x$11 = props$6.v0; - ("<>"); + break bb1; } case 1: { x$12 = props$6.v1; - ("<>"); + break bb1; } case 2: { - x$13 = props$6.v2; - ("<>"); } default: { x$13 = props$6.v2; - ("<>"); } } - - x$16; - return; } else { if (props$6.cond2) { x$14 = props$6.b; - ("<>"); } else { x$15 = props$6.c; - ("<>"); } - - x$16; - return; } x$16; diff --git a/compiler/forget/src/__tests__/fixtures/hir/simple.expect.md b/compiler/forget/src/__tests__/fixtures/hir/simple.expect.md index 47bedb33ee..411bee861f 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/simple.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/simple.expect.md @@ -33,7 +33,7 @@ bb1: ```javascript function foo$0(x$8, y$9) { - if (x$8) { + bb1: if (x$8) { return foo$0(false, y$9); } return [y$9 * 10]; diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-call-jsx-2.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-call-jsx-2.expect.md index f436bd8709..7e9396dad2 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-call-jsx-2.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-call-jsx-2.expect.md @@ -62,10 +62,8 @@ function Component$0(props$10) { const a$11 = []; const b$12 = {}; foo$4(a$11, b$12); - if (foo$4()) { + bb1: if (foo$4()) { let _$15 =
; - - ("<>"); } foo$4(a$11, b$12); diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-complex-multiple-if.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-complex-multiple-if.expect.md index 549e167937..7e66da4401 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-complex-multiple-if.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-complex-multiple-if.expect.md @@ -53,14 +53,12 @@ bb3: function foo$0() { let x$7 = 1; let y$8 = 2; - if (y$8 === 2) { + bb1: if (y$8 === 2) { x$11 = 3; - ("<>"); } - if (y$8 === 3) { + bb3: if (y$8 === 3) { x$15 = 5; - ("<>"); } y$18 = x$16; diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-complex-single-if.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-complex-single-if.expect.md index 02c4ad3059..ad136317dd 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-complex-single-if.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-complex-single-if.expect.md @@ -40,9 +40,8 @@ bb1: function foo$0() { let x$5 = 1; let y$6 = 2; - if (y$6 === 2) { + bb1: if (y$6 === 2) { x$9 = 3; - ("<>"); } y$11 = x$10; diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-for-of.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-for-of.expect.md index d4b10a2296..d34cab088c 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-for-of.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-for-of.expect.md @@ -42,7 +42,6 @@ bb2: ```javascript function foo$0(cond$4) { let items$5 = []; - ("<>"); } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-for.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-for.expect.md index 408d658cee..dfe24fe2df 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-for.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-for.expect.md @@ -39,7 +39,6 @@ bb2: ```javascript function foo$0() { let x$5 = 0; - ("<>"); } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-if-else.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-if-else.expect.md index 84479fb53c..c7d44a43b5 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-if-else.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-if-else.expect.md @@ -41,12 +41,10 @@ bb1: function foo$0() { let x$5 = 1; let y$6 = 2; - if (y$6) { + bb1: if (y$6) { let z$7 = x$5 + y$6; - ("<>"); } else { let z$8 = x$5; - ("<>"); } return; diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md index 9fa01d8484..3feeee56b9 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md @@ -46,7 +46,6 @@ bb2: ```javascript function foo$0(a$6, b$7, c$8) { let x$9 = 0; - ("<>"); } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression-phi.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression-phi.expect.md index 9960e3a350..8729e4112b 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression-phi.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression-phi.expect.md @@ -49,12 +49,10 @@ bb1: function foo$0() { let x$6 = 1; let y$7 = 2; - if (x$6 > 1) { + bb1: if (x$6 > 1) { x$10 = 2; - ("<>"); } else { y$11 = 3; - ("<>"); } let t$14 = { diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-return.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-return.expect.md index bdffb7a035..d291e97cef 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-return.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-return.expect.md @@ -36,9 +36,8 @@ bb1: ```javascript function foo$0() { let x$4 = 1; - if (x$4 === 1) { + bb1: if (x$4 === 1) { x$7 = 2; - ("<>"); } return x$8; diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-simple-phi.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-simple-phi.expect.md index 2f0495755d..c60ef6bdb5 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-simple-phi.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-simple-phi.expect.md @@ -44,12 +44,10 @@ bb1: ```javascript function foo$0() { let y$5 = 2; - if (y$5 > 1) { + bb1: if (y$5 > 1) { y$8 = 1; - ("<>"); } else { y$9 = 2; - ("<>"); } let x$11 = y$10; diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-single-if.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-single-if.expect.md index 85664e30cf..3b12fe25ed 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-single-if.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-single-if.expect.md @@ -35,9 +35,8 @@ bb1: function foo$0() { let x$4 = 1; let y$5 = 2; - if (y$5) { + bb1: if (y$5) { let z$6 = x$4 + y$5; - ("<>"); } return; diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md index f82fe7ce36..cf3506a2dd 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md @@ -64,20 +64,19 @@ bb1: ```javascript function foo$0() { let x$10 = 1; - switch (x$10) { + bb1: switch (x$10) { case x$10 === 1: { x$16 = x$10 + 1; - ("<>"); + break bb1; } case x$10 === 2: { x$18 = x$10 + 2; - ("<>"); + break bb1; } default: { x$20 = x$10 + 3; - ("<>"); } } diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-throw.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-throw.expect.md index 7a199a5958..03269a912a 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-throw.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-throw.expect.md @@ -35,9 +35,8 @@ bb1: ```javascript function foo$0() { let x$4 = 1; - if (x$4 === 1) { + bb1: if (x$4 === 1) { x$7 = 2; - ("<>"); } throw x$8; diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md index 7ccca6c7e2..100cb6fac7 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md @@ -39,7 +39,6 @@ bb2: ```javascript function foo$0() { let x$5 = 1; - ("<>"); } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-while.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-while.expect.md index 35b563716a..7c76dda790 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-while.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-while.expect.md @@ -40,7 +40,6 @@ bb2: ```javascript function foo$0() { let x$5 = 1; - ("<>"); } ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md b/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md index 29d5eb777d..9e8546f4d6 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md @@ -66,22 +66,23 @@ bb1: function Component$0(props$9) { let x$10 = []; let y$11 = undefined; - switch (props$9.p0) { - case 1: - break; + bb1: switch (props$9.p0) { + case 1: { + break bb1; + } case true: { x$10.push(props$9.p2); y$15 = []; - ("<>"); + break bb1; } - default: - break; + default: { + break bb1; + } case false: { y$16 = x$10; - ("<>"); } } diff --git a/compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.expect.md b/compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.expect.md new file mode 100644 index 0000000000..25b4c66acc --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.expect.md @@ -0,0 +1,124 @@ + +## Input + +```javascript +function foo(x) { + let y; + switch (x) { + case 0: { + y = 0; + } + case 1: { + y = 1; + } + case 2: { + break; + } + case 3: { + y = 3; + break; + } + case 4: { + y = 4; + } + case 5: { + y = 5; + } + default: { + y = 0; + } + } +} + +``` + +## HIR + +``` +bb0: + [1] Let mutate y$10 = undefined + [2] Const mutate $11 = 5 + [3] Const mutate $12 = 4 + [4] Const mutate $13 = 3 + [5] Const mutate $14 = 2 + [6] Const mutate $15 = 1 + [7] Const mutate $16 = 0 + Switch (read x$9) + Case read $16: bb10 + Case read $15: bb9 + Case read $14: bb1 + Case read $13: bb5 + Case read $12: bb4 + Case read $11: bb3 + Default: bb2 +bb10: + predecessor blocks: bb0 + [8] Reassign mutate y$17 = 0 + Goto bb9 +bb9: + predecessor blocks: bb10 bb0 + [9] Reassign mutate y$18 = 1 + Goto bb1 +bb5: + predecessor blocks: bb0 + [10] Reassign mutate y$19 = 3 + Goto bb1 +bb4: + predecessor blocks: bb0 + [11] Reassign mutate y$20 = 4 + Goto bb3 +bb3: + predecessor blocks: bb4 bb0 + [12] Reassign mutate y$21 = 5 + Goto bb2 +bb2: + predecessor blocks: bb3 bb0 + [13] Reassign mutate y$22 = 0 + Goto bb1 +bb1: + predecessor blocks: bb9 bb0 bb5 bb2 + Return +``` + +## Code + +```javascript +function foo$0(x$9) { + let y$10 = undefined; + bb1: switch (x$9) { + case 0: { + y$17 = 0; + } + + case 1: { + y$18 = 1; + break bb1; + } + + case 2: { + break bb1; + } + + case 3: { + y$19 = 3; + break bb1; + } + + case 4: { + y$20 = 4; + } + + case 5: { + y$21 = 5; + } + + default: { + y$22 = 0; + } + } + + return; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.js b/compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.js new file mode 100644 index 0000000000..234838a115 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.js @@ -0,0 +1,27 @@ +function foo(x) { + let y; + switch (x) { + case 0: { + y = 0; + } + case 1: { + y = 1; + } + case 2: { + break; + } + case 3: { + y = 3; + break; + } + case 4: { + y = 4; + } + case 5: { + y = 5; + } + default: { + y = 0; + } + } +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/switch.expect.md b/compiler/forget/src/__tests__/fixtures/hir/switch.expect.md index 22521499b8..385a690ad2 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/switch.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/switch.expect.md @@ -61,17 +61,15 @@ bb1: function Component$0(props$8) { let x$9 = []; let y$10 = undefined; - switch (props$8.p0) { + bb1: switch (props$8.p0) { case true: { x$9.push(props$8.p2); x$9.push(props$8.p3); y$13 = []; - ("<>"); } case false: { y$15 = x$9; - ("<>"); } }