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!
This commit is contained in:
Joe Savona
2022-11-08 21:14:10 -08:00
parent 22d1481125
commit ea1a18ec16
36 changed files with 486 additions and 309 deletions
+20 -6
View File
@@ -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 = {
+254 -53
View File
@@ -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<IdentifierId, t.Expression>;
temp: Map<IdentifierId, t.Expression> = 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<BlockId> = 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<BlockId> = 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<BreakTarget> = [];
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<number>): 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<t.Statement> = [];
writeBlock(cx, block, body);
return t.blockStatement(body);
@@ -71,6 +173,7 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
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<t.Statement>) {
}
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<t.SwitchCase> = [];
[...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("<<TODO: handle complex control flow in codegen>>")
)
);
break;
}
case "switch": {
const cases: Array<t.SwitchCase> = [];
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<t.Statement>) {
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<t.Statement>) {
+13 -1
View File
@@ -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;
+1
View File
@@ -142,6 +142,7 @@ export function mapTerminalSuccessors(
return {
kind: "goto",
block: target,
variant: terminal.variant,
};
}
case "if": {
@@ -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);
("<<TODO: handle complex control flow in codegen>>");
}
```
@@ -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);
("<<TODO: handle complex control flow in codegen>>");
}
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);
("<<TODO: handle complex control flow in codegen>>");
}
```
@@ -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);
("<<TODO: handle complex control flow in codegen>>");
}
if (props$6.p1) {
bb3: if (props$6.p1) {
b$8.push(props$6.p2);
("<<TODO: handle complex control flow in codegen>>");
}
return <Foo$4 a={a$7} b={b$8}></Foo$4>;
@@ -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);
("<<TODO: handle complex control flow in codegen>>");
}
if (props$8.p1) {
bb3: if (props$8.p1) {
b$10.push(props$8.p2);
("<<TODO: handle complex control flow in codegen>>");
}
return <Foo$6 a={a$9} b={b$10}></Foo$6>;
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
} else {
a$12 = [];
("<<TODO: handle complex control flow in codegen>>");
}
useFreeze$5(a$14);
@@ -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);
("<<TODO: handle complex control flow in codegen>>");
}
return <Foo$6 a={a$9} b={b$10}></Foo$6>;
@@ -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);
("<<TODO: handle complex control flow in codegen>>");
}
return <Foo$6 a={a$9} b={b$10}></Foo$6>;
@@ -43,10 +43,8 @@ bb1:
```javascript
function And$0() {
if (f$1()) {
("<<TODO: handle complex control flow in codegen>>");
bb1: if (f$1()) {
} else {
("<<TODO: handle complex control flow in codegen>>");
}
return t8;
}
@@ -76,10 +74,8 @@ bb1:
```javascript
function Or$0() {
if (f$1()) {
("<<TODO: handle complex control flow in codegen>>");
bb1: if (f$1()) {
} else {
("<<TODO: handle complex control flow in codegen>>");
}
return t8;
}
@@ -111,10 +107,8 @@ bb1:
```javascript
function QuestionQuestion$0(props$8) {
if (f$2() != null) {
("<<TODO: handle complex control flow in codegen>>");
bb1: if (f$2() != null) {
} else {
("<<TODO: handle complex control flow in codegen>>");
}
return t14;
}
@@ -127,7 +127,6 @@ function Component$0(props$12) {
let b$14 = {};
let c$15 = {};
let d$16 = {};
("<<TODO: handle complex control flow in codegen>>");
}
```
@@ -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;
}
@@ -109,7 +109,6 @@ function Component$0(props$11) {
let b$13 = {};
let c$14 = {};
let d$15 = {};
("<<TODO: handle complex control flow in codegen>>");
}
```
@@ -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 = [];
("<<TODO: handle complex control flow in codegen>>");
}
let _$12 = <Component$0 x={x$11}></Component$0>;
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
break bb1;
}
case 1: {
x$12 = props$6.v1;
("<<TODO: handle complex control flow in codegen>>");
break bb1;
}
case 2: {
x$13 = props$6.v2;
("<<TODO: handle complex control flow in codegen>>");
}
default: {
x$13 = props$6.v2;
("<<TODO: handle complex control flow in codegen>>");
}
}
x$16;
return;
} else {
if (props$6.cond2) {
x$14 = props$6.b;
("<<TODO: handle complex control flow in codegen>>");
} else {
x$15 = props$6.c;
("<<TODO: handle complex control flow in codegen>>");
}
x$16;
return;
}
x$16;
@@ -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];
@@ -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 = <div a={a$11}></div>;
("<<TODO: handle complex control flow in codegen>>");
}
foo$4(a$11, b$12);
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
}
if (y$8 === 3) {
bb3: if (y$8 === 3) {
x$15 = 5;
("<<TODO: handle complex control flow in codegen>>");
}
y$18 = x$16;
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
}
y$11 = x$10;
@@ -42,7 +42,6 @@ bb2:
```javascript
function foo$0(cond$4) {
let items$5 = [];
("<<TODO: handle complex control flow in codegen>>");
}
```
@@ -39,7 +39,6 @@ bb2:
```javascript
function foo$0() {
let x$5 = 0;
("<<TODO: handle complex control flow in codegen>>");
}
```
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
} else {
let z$8 = x$5;
("<<TODO: handle complex control flow in codegen>>");
}
return;
@@ -46,7 +46,6 @@ bb2:
```javascript
function foo$0(a$6, b$7, c$8) {
let x$9 = 0;
("<<TODO: handle complex control flow in codegen>>");
}
```
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
} else {
y$11 = 3;
("<<TODO: handle complex control flow in codegen>>");
}
let t$14 = {
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
}
return x$8;
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
} else {
y$9 = 2;
("<<TODO: handle complex control flow in codegen>>");
}
let x$11 = y$10;
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
}
return;
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
break bb1;
}
case x$10 === 2: {
x$18 = x$10 + 2;
("<<TODO: handle complex control flow in codegen>>");
break bb1;
}
default: {
x$20 = x$10 + 3;
("<<TODO: handle complex control flow in codegen>>");
}
}
@@ -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;
("<<TODO: handle complex control flow in codegen>>");
}
throw x$8;
@@ -39,7 +39,6 @@ bb2:
```javascript
function foo$0() {
let x$5 = 1;
("<<TODO: handle complex control flow in codegen>>");
}
```
@@ -40,7 +40,6 @@ bb2:
```javascript
function foo$0() {
let x$5 = 1;
("<<TODO: handle complex control flow in codegen>>");
}
```
@@ -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 = [];
("<<TODO: handle complex control flow in codegen>>");
break bb1;
}
default:
break;
default: {
break bb1;
}
case false: {
y$16 = x$10;
("<<TODO: handle complex control flow in codegen>>");
}
}
@@ -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;
}
```
@@ -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;
}
}
}
@@ -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 = [];
("<<TODO: handle complex control flow in codegen>>");
}
case false: {
y$15 = x$9;
("<<TODO: handle complex control flow in codegen>>");
}
}