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; - ("<>"); } }