@samitouri / QOS-React / commits / ea1a18ec16

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!

Joe Savona committed Nov 8, 2022 at 21:14 UTC ea1a18ec169f459bc5b389c35af197db268cbe31
36 files changed +482 -305
compiler/forget/src/HIR/BuildHIR.ts
+20 -6
@@ -12,6 +12,7 @@ import { invariant } from "../CompilerError";
12 import {
13 Effect,
14 GeneratedSource,
15 + GotoVariant,
16 HIRFunction,
17 IfTerminal,
18 InstructionKind,
@@ -133,6 +134,7 @@ function lowerStatement(
134 return {
135 kind: "goto",
136 block: continuationBlock.id,
137 + variant: GotoVariant.Break,
138 };
139 });
140 // Block for the alternate (if the test is not truthy)
@@ -144,6 +146,7 @@ function lowerStatement(
146 return {
147 kind: "goto",
148 block: continuationBlock.id,
149 + variant: GotoVariant.Break,
150 };
151 });
152 } else {
@@ -172,6 +175,7 @@ function lowerStatement(
175 builder.terminate({
176 kind: "goto",
177 block,
178 + variant: GotoVariant.Break,
179 });
180 return;
181 }
@@ -181,6 +185,7 @@ function lowerStatement(
185 builder.terminate({
186 kind: "goto",
187 block,
188 + variant: GotoVariant.Continue,
189 });
190 return;
191 }
@@ -200,8 +205,7 @@ function lowerStatement(
205 return {
206 kind: "goto",
207 block: conditionalBlock.id,
203 - fallthrough: null,
204 - tests: null,
208 + variant: GotoVariant.Continue,
209 };
210 }
211 );
@@ -211,6 +215,7 @@ function lowerStatement(
215 {
216 kind: "goto",
217 block: conditionalBlock.id,
218 + variant: GotoVariant.Break,
219 },
220 conditionalBlock
221 );
@@ -250,8 +255,7 @@ function lowerStatement(
255 return {
256 kind: "goto",
257 block: conditionalBlock.id,
253 - fallthrough: null,
254 - tests: null,
258 + variant: GotoVariant.Continue,
259 };
260 }
261 );
@@ -261,6 +265,7 @@ function lowerStatement(
265 {
266 kind: "goto",
267 block: conditionalBlock.id,
268 + variant: GotoVariant.Break,
269 },
270 conditionalBlock
271 );
@@ -309,6 +314,7 @@ function lowerStatement(
314 builder.complete(updateBlock, {
315 kind: "goto",
316 block: conditionalBlock.id,
317 + variant: GotoVariant.Break,
318 });
319 /**
320 * Construct the loop itself: the loop body wraps around to the update block
@@ -320,7 +326,7 @@ function lowerStatement(
326 return {
327 kind: "goto",
328 block: updateBlock.id,
323 - fallthrough: null,
329 + variant: GotoVariant.Continue,
330 };
331 });
332 });
@@ -329,6 +335,7 @@ function lowerStatement(
335 {
336 kind: "goto",
337 block: conditionalBlock.id,
338 + variant: GotoVariant.Break,
339 },
340 conditionalBlock
341 );
@@ -355,6 +362,7 @@ function lowerStatement(
362 terminal = {
363 kind: "goto",
364 block: loopBlock,
365 + variant: GotoVariant.Break,
366 };
367 }
368 builder.terminateWithContinuation(terminal, continuationBlock);
@@ -385,7 +393,7 @@ function lowerStatement(
393 });
394 // do-while unconditionally enters the loop
395 builder.terminateWithContinuation(
388 - { kind: "goto", block: loopBlock },
396 + { kind: "goto", block: loopBlock, variant: GotoVariant.Break },
397 continuationBlock
398 );
399 return;
@@ -407,6 +415,7 @@ function lowerStatement(
415 return {
416 kind: "goto",
417 block: conditionalBlock.id,
418 + variant: GotoVariant.Continue,
419 };
420 }
421 );
@@ -419,6 +428,7 @@ function lowerStatement(
428 {
429 kind: "goto",
430 block: conditionalBlock.id,
431 + variant: GotoVariant.Break,
432 },
433 conditionalBlock
434 );
@@ -464,6 +474,7 @@ function lowerStatement(
474 {
475 kind: "goto",
476 block: continuationBlock.id,
477 + variant: GotoVariant.Break,
478 },
479 continuationBlock
480 );
@@ -509,6 +520,7 @@ function lowerStatement(
520 return {
521 kind: "goto",
522 block: fallthrough,
523 + variant: GotoVariant.Break,
524 };
525 });
526 });
@@ -1005,6 +1017,7 @@ function lowerConditional(
1017 return {
1018 kind: "goto",
1019 block: continuationBlock.id,
1020 + variant: GotoVariant.Break,
1021 };
1022 });
1023 // Block for the alternate (if the test is not truthy)
@@ -1019,6 +1032,7 @@ function lowerConditional(
1032 return {
1033 kind: "goto",
1034 block: continuationBlock.id,
1035 + variant: GotoVariant.Break,
1036 };
1037 });
1038 const terminal: IfTerminal = {
compiler/forget/src/HIR/Codegen.ts
+250 -49
@@ -10,6 +10,8 @@ import { assertExhaustive } from "../Common/utils";
10 import { invariant } from "../CompilerError";
11 import {
12 BasicBlock,
13 + BlockId,
14 + GotoVariant,
15 HIR,
16 HIRFunction,
17 Identifier,
@@ -43,7 +45,7 @@ import { todoInvariant } from "./todo";
45 */
46 export default function codegen(fn: HIRFunction): t.Function {
47 const entry = fn.body.blocks.get(fn.body.entry)!;
46 - const cx: Context = { ir: fn.body, temp: new Map() };
48 + const cx = new Context(fn.body);
49 const body = codegenBlock(cx, entry);
50 const params = fn.params.map((param) => convertIdentifier(param.identifier));
51 return t.functionDeclaration(
@@ -55,12 +57,112 @@ export default function codegen(fn: HIRFunction): t.Function {
57 );
58 }
59
58 -type Context = {
60 +class Context {
61 ir: HIR;
60 - temp: Map<IdentifierId, t.Expression>;
62 + temp: Map<IdentifierId, t.Expression> = new Map();
63 + #nextScheduleId: number = 0;
64 +
65 + /**
66 + * Used to track which blocks *have been* generated already in order to
67 + * abort if a block is generated a second time. This is an error catching
68 + * mechanism for debugging purposes, and is not used by the codegen algorithm
69 + * to drive decisions about how to emit blocks.
70 + */
71 + emitted: Set<BlockId> = new Set();
72 +
73 + /**
74 + * A set of blocks that are already scheduled to be emitted by eg a parent.
75 + * This allows child nodes to avoid re-emitting the same block and emit eg
76 + * a break instead.
77 + */
78 + #scheduled: Set<BlockId> = new Set();
79 +
80 + /**
81 + * A stack of blocks that are in scope, used to decide whether/how to emit
82 + * break and continue statements. All blocks in the stack must also be
83 + * in 'scheduled'.
84 + */
85 + #breakTargets: Array<BreakTarget> = [];
86 +
87 + constructor(ir: HIR) {
88 + this.ir = ir;
89 + }
90 +
91 + /**
92 + * Record that the given block will be emitted (eg by the codegen of a parent node)
93 + * so that child nodes can avoid re-emitting it.
94 + */
95 + schedule(block: BlockId, type: "if" | "switch" | "case"): number {
96 + const id = this.#nextScheduleId++;
97 + invariant(!this.#scheduled.has(block), "Block is already scheduled");
98 + this.#scheduled.add(block);
99 + this.#breakTargets.push({ block, id, type });
100 + return id;
101 + }
102 +
103 + /**
104 + * Removes a block that was scheduled; must be called after that block is emitted.
105 + */
106 + unschedule(scheduleId: number): void {
107 + const last = this.#breakTargets.pop();
108 + invariant(
109 + last !== undefined && last.id === scheduleId,
110 + "Can only unschedule the last target"
111 + );
112 + this.#scheduled.delete(last.block);
113 + }
114 +
115 + /**
116 + * Helper to unschedule multiple scheduled blocks. The ids should be in
117 + * the order in which they were scheduled, ie most recently scheduled last.
118 + */
119 + unscheduleAll(scheduleIds: Array<number>): void {
120 + for (let i = scheduleIds.length - 1; i >= 0; i--) {
121 + this.unschedule(scheduleIds[i]!);
122 + }
123 + }
124 +
125 + /**
126 + * Check if the given @param block is scheduled or not.
127 + */
128 + isScheduled(block: BlockId): boolean {
129 + return this.#scheduled.has(block);
130 + }
131 +
132 + /**
133 + * Lookup the break target for the given @param block. This will return non-null
134 + * if and only if isScheduled() returns true for the given @param block. Returns
135 + * the break target and whether this is the most recent target (which can be used
136 + * to elide unnecessary break statemetns).
137 + */
138 + getBreakTarget(
139 + block: BlockId
140 + ): { target: BreakTarget; last: boolean } | null {
141 + for (let i = this.#breakTargets.length - 1; i >= 0; i--) {
142 + const target = this.#breakTargets[i]!;
143 + if (target.block === block) {
144 + return {
145 + target,
146 + last: i === this.#breakTargets.length - 1,
147 + };
148 + }
149 + }
150 + return null;
151 + }
152 +}
153 +
154 +type BreakTarget = {
155 + block: BlockId;
156 + id: number;
157 + type: "if" | "switch" | "case";
158 };
159
160 function codegenBlock(cx: Context, block: BasicBlock): t.BlockStatement {
161 + invariant(
162 + !cx.emitted.has(block.id),
163 + `Cannot emit the same block twice: bb${block.id}`
164 + );
165 + cx.emitted.add(block.id);
166 const body: Array<t.Statement> = [];
167 writeBlock(cx, block, body);
168 return t.blockStatement(body);
@@ -71,6 +173,7 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
173 writeInstr(cx, instr, body);
174 }
175 const terminal = block.terminal;
176 + const scheduleIds = [];
177 switch (terminal.kind) {
178 case "return": {
179 const value =
@@ -85,64 +188,140 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
188 }
189 case "if": {
190 const test = codegenPlace(cx, terminal.test);
88 - const consequent = codegenBlock(
89 - cx,
90 - cx.ir.blocks.get(terminal.consequent)!
91 - );
92 - const fallthrough =
93 - terminal.fallthrough !== null &&
94 - terminal.fallthrough !== terminal.alternate
191 + const fallthroughId =
192 + terminal.fallthrough !== null && !cx.isScheduled(terminal.fallthrough)
193 ? terminal.fallthrough
194 : null;
97 - if (fallthrough !== null) {
98 - const alternate = codegenBlock(
99 - cx,
100 - cx.ir.blocks.get(terminal.alternate)!
101 - );
102 - body.push(t.ifStatement(test, consequent, alternate));
103 - const fallthroughBlock = cx.ir.blocks.get(fallthrough)!;
104 - writeBlock(cx, fallthroughBlock, body);
195 + const alternateId =
196 + terminal.alternate !== terminal.fallthrough ? terminal.alternate : null;
197 +
198 + if (fallthroughId !== null) {
199 + const scheduleId = cx.schedule(fallthroughId, "if");
200 + scheduleIds.push(scheduleId);
201 + }
202 +
203 + let consequent: t.Statement | null = null;
204 + if (cx.isScheduled(terminal.consequent)) {
205 + consequent = codegenBreak(cx, terminal.consequent);
206 } else {
106 - body.push(t.ifStatement(test, consequent));
107 - writeBlock(cx, cx.ir.blocks.get(terminal.alternate)!, body);
207 + consequent = codegenBlock(cx, cx.ir.blocks.get(terminal.consequent)!);
208 + }
209 +
210 + let alternate: t.Statement | null = null;
211 + if (alternateId !== null) {
212 + if (cx.isScheduled(alternateId)) {
213 + alternate = codegenBreak(cx, alternateId);
214 + } else {
215 + alternate = codegenBlock(cx, cx.ir.blocks.get(alternateId)!);
216 + }
217 + }
218 +
219 + if (fallthroughId !== null) {
220 + if (consequent === null && alternate === null) {
221 + body.push(t.expressionStatement(test));
222 + } else {
223 + body.push(
224 + t.labeledStatement(
225 + t.identifier(`bb${fallthroughId}`),
226 + t.ifStatement(test, consequent ?? t.blockStatement([]), alternate)
227 + )
228 + );
229 + }
230 + writeBlock(cx, cx.ir.blocks.get(fallthroughId)!, body);
231 + } else {
232 + if (consequent === null && alternate === null) {
233 + body.push(t.expressionStatement(test));
234 + } else {
235 + body.push(
236 + t.ifStatement(test, consequent ?? t.blockStatement([]), alternate)
237 + );
238 + }
239 }
109 - break;
110 - }
111 - case "goto": {
112 - body.push(
113 - t.expressionStatement(
114 - t.stringLiteral("<<TODO: handle complex control flow in codegen>>")
115 - )
116 - );
240 break;
241 }
242 case "switch": {
243 + const test = codegenPlace(cx, terminal.test);
244 + const fallthroughId =
245 + terminal.fallthrough !== null && !cx.isScheduled(terminal.fallthrough)
246 + ? terminal.fallthrough
247 + : null;
248 + if (fallthroughId !== null) {
249 + const scheduleId = cx.schedule(fallthroughId, "switch");
250 + scheduleIds.push(scheduleId);
251 + }
252 +
253 const cases: Array<t.SwitchCase> = [];
121 - terminal.cases.forEach((case_, index) => {
254 + [...terminal.cases].reverse().forEach((case_, index) => {
255 const test = case_.test !== null ? codegenPlace(cx, case_.test) : null;
123 - // If the final case is a `default` *and* points directly to the
124 - // fallthrough branch, then we can skip emitting `default: break`
125 - // since this implied. For a default in any other position, or
126 - // for a default pointing to a different block, emit a case
127 - // normally.
128 - if (
129 - index === terminal.cases.length - 1 &&
130 - test === null &&
131 - case_.block === terminal.fallthrough
132 - ) {
133 - return;
134 - } else if (case_.block === terminal.fallthrough) {
135 - // Otherwise for any block that points directly to the fallthrough,
136 - // emit a break instead
137 - cases.push(t.switchCase(test, [t.breakStatement()]));
256 +
257 + let consequent;
258 + if (cx.isScheduled(case_.block)) {
259 + // cases which are empty or contain only a `break` may point to blocks
260 + // that are already scheduled. emit as follows:
261 + // - if the block is for another case branch, don't emit a break and fall-through
262 + // - else, emit an explicit break.
263 + const break_ = codegenBreak(cx, case_.block);
264 + if (
265 + index === 0 &&
266 + break_ === null &&
267 + case_.block === terminal.fallthrough &&
268 + case_.test === null
269 + ) {
270 + // If the last case statement (first in reverse order) is a default that
271 + // jumps to the fallthrough, then we would emit a useless `default: {}`,
272 + // so instead skip this case.
273 + return;
274 + }
275 + const block = [];
276 + if (break_ !== null) {
277 + block.push(break_);
278 + }
279 + consequent = t.blockStatement(block);
280 } else {
139 - const consequent = codegenBlock(cx, cx.ir.blocks.get(case_.block)!);
140 - cases.push(t.switchCase(test, [consequent]));
281 + consequent = codegenBlock(cx, cx.ir.blocks.get(case_.block)!);
282 + const scheduleId = cx.schedule(case_.block, "case");
283 + scheduleIds.push(scheduleId);
284 }
285 + cases.push(t.switchCase(test, [consequent]));
286 });
143 - body.push(t.switchStatement(codegenPlace(cx, terminal.test), cases));
144 - if (terminal.fallthrough !== null) {
145 - writeBlock(cx, cx.ir.blocks.get(terminal.fallthrough)!, body);
287 + cases.reverse();
288 +
289 + if (fallthroughId !== null) {
290 + body.push(
291 + t.labeledStatement(
292 + t.identifier(`bb${fallthroughId}`),
293 + t.switchStatement(test, cases)
294 + )
295 + );
296 + writeBlock(cx, cx.ir.blocks.get(fallthroughId)!, body);
297 + } else {
298 + body.push(t.switchStatement(test, cases));
299 + }
300 + break;
301 + }
302 + case "goto": {
303 + switch (terminal.variant) {
304 + case GotoVariant.Break: {
305 + const break_ = codegenBreak(cx, terminal.block);
306 + if (break_ !== null) {
307 + body.push(break_);
308 + }
309 + break;
310 + }
311 + case GotoVariant.Continue: {
312 + invariant(
313 + cx.isScheduled(terminal.block),
314 + "Expected continue target to be scheduled"
315 + );
316 + body.push(t.continueStatement(t.identifier(`bb${terminal.block}`)));
317 + break;
318 + }
319 + default: {
320 + assertExhaustive(
321 + terminal.variant,
322 + `Unexpected goto variant '${terminal.variant}'`
323 + );
324 + }
325 }
326 break;
327 }
@@ -150,6 +329,28 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
329 assertExhaustive(terminal, "Unexpected terminal");
330 }
331 }
332 + cx.unscheduleAll(scheduleIds);
333 +}
334 +
335 +function codegenBreak(cx: Context, block: BlockId): t.Statement | null {
336 + const breakTarget = cx.getBreakTarget(block);
337 + if (breakTarget === null) {
338 + // TODO: we should always have a target
339 + return null;
340 + }
341 + const { target, last } = breakTarget;
342 + if (target.type === "case") {
343 + // This break is transitioning to the next case statement. JS doesn't allow
344 + // labeling cases, the only option is to emit a plain break.
345 + return null;
346 + } else if (last) {
347 + // This break is to the most recent break target. Control flow will naturally
348 + // transition to this target, so a break is not required.
349 + return null;
350 + } else {
351 + // We're trying to break somewhere else, emit a label
352 + return t.breakStatement(t.identifier(`bb${block}`));
353 + }
354 }
355
356 function writeInstr(cx: Context, instr: Instruction, body: Array<t.Statement>) {
compiler/forget/src/HIR/HIR.ts
+13 -1
@@ -121,8 +121,20 @@ export type Terminal =
121 | SwitchTerminal;
122
123 export type ThrowTerminal = { kind: "throw"; value: Place };
124 +
125 export type ReturnTerminal = { kind: "return"; value: Place | null };
125 -export type GotoTerminal = { kind: "goto"; block: BlockId };
126 +
127 +export type GotoTerminal = {
128 + kind: "goto";
129 + block: BlockId;
130 + variant: GotoVariant;
131 +};
132 +
133 +export enum GotoVariant {
134 + Break = "Break",
135 + Continue = "Continue",
136 +}
137 +
138 export type IfTerminal = {
139 kind: "if";
140 test: Place;
compiler/forget/src/HIR/visitors.ts
+1
@@ -142,6 +142,7 @@ export function mapTerminalSuccessors(
142 return {
143 kind: "goto",
144 block: target,
145 + variant: terminal.variant,
146 };
147 }
148 case "if": {
compiler/forget/src/__tests__/fixtures/hir/component.expect.md
-1
@@ -93,7 +93,6 @@ function Component$0(props$26) {
93 const renderedItems$29 = [];
94 const seen$30 = new Set$6();
95 const max$32 = Math$8.max(0, maxItems$28);
96 - ("<<TODO: handle complex control flow in codegen>>");
96 }
97
98 ```
compiler/forget/src/__tests__/fixtures/hir/conditional-break.expect.md
+5 -7
@@ -97,7 +97,7 @@ bb1:
97 function Component$0(props$4) {
98 const a_DEBUG$5 = [];
99 a_DEBUG$5.push(props$4.a);
100 - if (props$4.b) {
100 + bb1: if (props$4.b) {
101 return null;
102 }
103
@@ -129,9 +129,8 @@ bb1:
129 function Component$0(props$3) {
130 const a$4 = [];
131 a$4.push(props$3.a);
132 - if (props$3.b) {
132 + bb1: if (props$3.b) {
133 a$4.push(props$3.c);
134 - ("<<TODO: handle complex control flow in codegen>>");
134 }
135
136 a$4.push(props$3.d);
@@ -163,7 +162,7 @@ bb1:
162 function Component$0(props$4) {
163 const a$5 = [];
164 a$5.push(props$4.a);
166 - if (props$4.b) {
165 + bb1: if (props$4.b) {
166 a$5.push(props$4.c);
167 return null;
168 }
@@ -196,7 +195,7 @@ bb1:
195 function Component$0(props$3) {
196 const a$4 = [];
197 a$4.push(props$3.a);
199 - if (props$3.b) {
198 + bb1: if (props$3.b) {
199 a$4.push(props$3.c);
200 return a$4;
201 }
@@ -229,13 +228,12 @@ bb1:
228 function Component$0(props$3) {
229 const a$4 = [];
230 a$4.push(props$3.a);
232 - if (props$3.b) {
231 + bb2: if (props$3.b) {
232 a$4.push(props$3.d);
233 return a$4;
234 }
235
236 a$4.push(props$3.c);
238 - ("<<TODO: handle complex control flow in codegen>>");
237 }
238
239 ```
compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md
+4 -8
@@ -93,14 +93,12 @@ bb3:
93 function Component$0(props$6) {
94 const a$7 = [];
95 const b$8 = [];
96 - if (b$8) {
96 + bb1: if (b$8) {
97 a$7.push(props$6.p0);
98 - ("<<TODO: handle complex control flow in codegen>>");
98 }
99
101 - if (props$6.p1) {
100 + bb3: if (props$6.p1) {
101 b$8.push(props$6.p2);
103 - ("<<TODO: handle complex control flow in codegen>>");
102 }
103
104 return <Foo$4 a={a$7} b={b$8}></Foo$4>;
@@ -138,14 +136,12 @@ bb3:
136 function Component$0(props$8) {
137 const a$9 = [];
138 const b$10 = [];
141 - if (mayMutate$4(b$10)) {
139 + bb1: if (mayMutate$4(b$10)) {
140 a$9.push(props$8.p0);
143 - ("<<TODO: handle complex control flow in codegen>>");
141 }
142
146 - if (props$8.p1) {
143 + bb3: if (props$8.p1) {
144 b$10.push(props$8.p2);
148 - ("<<TODO: handle complex control flow in codegen>>");
145 }
146
147 return <Foo$6 a={a$9} b={b$10}></Foo$6>;
compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md
+1 -3
@@ -54,12 +54,10 @@ function Component$0(props$7) {
54 const cond$8 = props$7.cond;
55 const x$9 = props$7.x;
56 let a$10 = undefined;
57 - if (cond$8) {
57 + bb1: if (cond$8) {
58 a$11 = x$9;
59 - ("<<TODO: handle complex control flow in codegen>>");
59 } else {
60 a$12 = [];
62 - ("<<TODO: handle complex control flow in codegen>>");
61 }
62
63 useFreeze$5(a$14);
compiler/forget/src/__tests__/fixtures/hir/independent-across-if.expect.md
+1 -2
@@ -117,10 +117,9 @@ bb1:
117 function Component$0(props$8) {
118 const a$9 = compute$3(props$8.a);
119 const b$10 = compute$3(props$8.b);
120 - if (props$8.c) {
120 + bb1: if (props$8.c) {
121 mutate$5(a$9);
122 mutate$5(b$10);
123 - ("<<TODO: handle complex control flow in codegen>>");
123 }
124
125 return <Foo$6 a={a$9} b={b$10}></Foo$6>;
compiler/forget/src/__tests__/fixtures/hir/interdependent-across-if.expect.md
+1 -2
@@ -95,9 +95,8 @@ bb1:
95 function Component$0(props$8) {
96 const a$9 = compute$3(props$8.a);
97 const b$10 = compute$3(props$8.b);
98 - if (props$8.c) {
98 + bb1: if (props$8.c) {
99 foo$5(a$9, b$10);
100 - ("<<TODO: handle complex control flow in codegen>>");
100 }
101
102 return <Foo$6 a={a$9} b={b$10}></Foo$6>;
compiler/forget/src/__tests__/fixtures/hir/logical-expression.expect.md
+3 -9
@@ -43,10 +43,8 @@ bb1:
43
44 ```javascript
45 function And$0() {
46 - if (f$1()) {
47 - ("<<TODO: handle complex control flow in codegen>>");
46 + bb1: if (f$1()) {
47 } else {
49 - ("<<TODO: handle complex control flow in codegen>>");
48 }
49 return t8;
50 }
@@ -76,10 +74,8 @@ bb1:
74
75 ```javascript
76 function Or$0() {
79 - if (f$1()) {
80 - ("<<TODO: handle complex control flow in codegen>>");
77 + bb1: if (f$1()) {
78 } else {
82 - ("<<TODO: handle complex control flow in codegen>>");
79 }
80 return t8;
81 }
@@ -111,10 +107,8 @@ bb1:
107
108 ```javascript
109 function QuestionQuestion$0(props$8) {
114 - if (f$2() != null) {
115 - ("<<TODO: handle complex control flow in codegen>>");
110 + bb1: if (f$2() != null) {
111 } else {
117 - ("<<TODO: handle complex control flow in codegen>>");
112 }
113 return t14;
114 }
compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-loops.expect.md
-1
@@ -127,7 +127,6 @@ function Component$0(props$12) {
127 let b$14 = {};
128 let c$15 = {};
129 let d$16 = {};
130 - ("<<TODO: handle complex control flow in codegen>>");
130 }
131
132 ```
compiler/forget/src/__tests__/fixtures/hir/mutable-lifetime-with-aliasing.expect.md
+5 -155
@@ -94,161 +94,11 @@ function Component$0(props$10) {
94 const x$15 = {};
95 x$15 = b$12;
96 const y$16 = mutate$8(x$15, d$14);
97 - if (a$11) {
98 - if (b$12) {
99 - if (c$13) {
100 - if (d$14) {
101 - if (y$16) {
102 - mutate$8(x$15, null);
103 - return;
104 - }
105 -
106 - mutate$8(x$15, null);
107 - return;
108 - }
109 -
110 - if (y$16) {
111 - mutate$8(x$15, null);
112 - return;
113 - }
114 -
115 - mutate$8(x$15, null);
116 - return;
117 - }
118 -
119 - if (d$14) {
120 - if (y$16) {
121 - mutate$8(x$15, null);
122 - return;
123 - }
124 -
125 - mutate$8(x$15, null);
126 - return;
127 - }
128 -
129 - if (y$16) {
130 - mutate$8(x$15, null);
131 - return;
132 - }
133 -
134 - mutate$8(x$15, null);
135 - return;
136 - }
137 -
138 - if (c$13) {
139 - if (d$14) {
140 - if (y$16) {
141 - mutate$8(x$15, null);
142 - return;
143 - }
144 -
145 - mutate$8(x$15, null);
146 - return;
147 - }
148 -
149 - if (y$16) {
150 - mutate$8(x$15, null);
151 - return;
152 - }
153 -
154 - mutate$8(x$15, null);
155 - return;
156 - }
157 -
158 - if (d$14) {
159 - if (y$16) {
160 - mutate$8(x$15, null);
161 - return;
162 - }
163 -
164 - mutate$8(x$15, null);
165 - return;
166 - }
167 -
168 - if (y$16) {
169 - mutate$8(x$15, null);
170 - return;
171 - }
172 -
173 - mutate$8(x$15, null);
174 - return;
175 - }
176 -
177 - if (b$12) {
178 - if (c$13) {
179 - if (d$14) {
180 - if (y$16) {
181 - mutate$8(x$15, null);
182 - return;
183 - }
184 -
185 - mutate$8(x$15, null);
186 - return;
187 - }
188 -
189 - if (y$16) {
190 - mutate$8(x$15, null);
191 - return;
192 - }
193 -
194 - mutate$8(x$15, null);
195 - return;
196 - }
197 -
198 - if (d$14) {
199 - if (y$16) {
200 - mutate$8(x$15, null);
201 - return;
202 - }
203 -
204 - mutate$8(x$15, null);
205 - return;
206 - }
207 -
208 - if (y$16) {
209 - mutate$8(x$15, null);
210 - return;
211 - }
212 -
213 - mutate$8(x$15, null);
214 - return;
215 - }
216 -
217 - if (c$13) {
218 - if (d$14) {
219 - if (y$16) {
220 - mutate$8(x$15, null);
221 - return;
222 - }
223 -
224 - mutate$8(x$15, null);
225 - return;
226 - }
227 -
228 - if (y$16) {
229 - mutate$8(x$15, null);
230 - return;
231 - }
232 -
233 - mutate$8(x$15, null);
234 - return;
235 - }
236 -
237 - if (d$14) {
238 - if (y$16) {
239 - mutate$8(x$15, null);
240 - return;
241 - }
242 -
243 - mutate$8(x$15, null);
244 - return;
245 - }
246 -
247 - if (y$16) {
248 - mutate$8(x$15, null);
249 - return;
250 - }
251 -
97 + a$11;
98 + b$12;
99 + c$13;
100 + d$14;
101 + y$16;
102 mutate$8(x$15, null);
103 return;
104 }
compiler/forget/src/__tests__/fixtures/hir/mutable-liverange-loop.expect.md
-1
@@ -109,7 +109,6 @@ function Component$0(props$11) {
109 let b$13 = {};
110 let c$14 = {};
111 let d$15 = {};
112 - ("<<TODO: handle complex control flow in codegen>>");
112 }
113
114 ```
compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md
+1 -2
@@ -47,9 +47,8 @@ function Component$0(props$6) {
47 let x$7 = [];
48 x$7.push(props$6.p0);
49 let y$8 = x$7;
50 - if (props$6.p1) {
50 + bb1: if (props$6.p1) {
51 x$9 = [];
52 - ("<<TODO: handle complex control flow in codegen>>");
52 }
53
54 let _$12 = <Component$0 x={x$11}></Component$0>;
compiler/forget/src/__tests__/fixtures/hir/reverse-postorder.expect.md
+3 -14
@@ -83,42 +83,31 @@ bb1:
83 ```javascript
84 function Component$0(props$6) {
85 let x$7 = undefined;
86 - if (props$6.cond) {
86 + bb1: if (props$6.cond) {
87 switch (props$6.test) {
88 case 0: {
89 x$11 = props$6.v0;
90 - ("<<TODO: handle complex control flow in codegen>>");
90 + break bb1;
91 }
92
93 case 1: {
94 x$12 = props$6.v1;
95 - ("<<TODO: handle complex control flow in codegen>>");
95 + break bb1;
96 }
97
98 case 2: {
99 - x$13 = props$6.v2;
100 - ("<<TODO: handle complex control flow in codegen>>");
99 }
100
101 default: {
102 x$13 = props$6.v2;
105 - ("<<TODO: handle complex control flow in codegen>>");
103 }
104 }
108 -
109 - x$16;
110 - return;
105 } else {
106 if (props$6.cond2) {
107 x$14 = props$6.b;
114 - ("<<TODO: handle complex control flow in codegen>>");
108 } else {
109 x$15 = props$6.c;
117 - ("<<TODO: handle complex control flow in codegen>>");
110 }
119 -
120 - x$16;
121 - return;
111 }
112
113 x$16;
compiler/forget/src/__tests__/fixtures/hir/simple.expect.md
+1 -1
@@ -33,7 +33,7 @@ bb1:
33
34 ```javascript
35 function foo$0(x$8, y$9) {
36 - if (x$8) {
36 + bb1: if (x$8) {
37 return foo$0(false, y$9);
38 }
39 return [y$9 * 10];
compiler/forget/src/__tests__/fixtures/hir/ssa-call-jsx-2.expect.md
+1 -3
@@ -62,10 +62,8 @@ function Component$0(props$10) {
62 const a$11 = [];
63 const b$12 = {};
64 foo$4(a$11, b$12);
65 - if (foo$4()) {
65 + bb1: if (foo$4()) {
66 let _$15 = <div a={a$11}></div>;
67 -
68 - ("<<TODO: handle complex control flow in codegen>>");
67 }
68
69 foo$4(a$11, b$12);
compiler/forget/src/__tests__/fixtures/hir/ssa-complex-multiple-if.expect.md
+2 -4
@@ -53,14 +53,12 @@ bb3:
53 function foo$0() {
54 let x$7 = 1;
55 let y$8 = 2;
56 - if (y$8 === 2) {
56 + bb1: if (y$8 === 2) {
57 x$11 = 3;
58 - ("<<TODO: handle complex control flow in codegen>>");
58 }
59
61 - if (y$8 === 3) {
60 + bb3: if (y$8 === 3) {
61 x$15 = 5;
63 - ("<<TODO: handle complex control flow in codegen>>");
62 }
63
64 y$18 = x$16;
compiler/forget/src/__tests__/fixtures/hir/ssa-complex-single-if.expect.md
+1 -2
@@ -40,9 +40,8 @@ bb1:
40 function foo$0() {
41 let x$5 = 1;
42 let y$6 = 2;
43 - if (y$6 === 2) {
43 + bb1: if (y$6 === 2) {
44 x$9 = 3;
45 - ("<<TODO: handle complex control flow in codegen>>");
45 }
46
47 y$11 = x$10;
compiler/forget/src/__tests__/fixtures/hir/ssa-for-of.expect.md
-1
@@ -42,7 +42,6 @@ bb2:
42 ```javascript
43 function foo$0(cond$4) {
44 let items$5 = [];
45 - ("<<TODO: handle complex control flow in codegen>>");
45 }
46
47 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-for.expect.md
-1
@@ -39,7 +39,6 @@ bb2:
39 ```javascript
40 function foo$0() {
41 let x$5 = 0;
42 - ("<<TODO: handle complex control flow in codegen>>");
42 }
43
44 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-if-else.expect.md
+1 -3
@@ -41,12 +41,10 @@ bb1:
41 function foo$0() {
42 let x$5 = 1;
43 let y$6 = 2;
44 - if (y$6) {
44 + bb1: if (y$6) {
45 let z$7 = x$5 + y$6;
46 - ("<<TODO: handle complex control flow in codegen>>");
46 } else {
47 let z$8 = x$5;
49 - ("<<TODO: handle complex control flow in codegen>>");
48 }
49
50 return;
compiler/forget/src/__tests__/fixtures/hir/ssa-nested-loops-no-reassign.expect.md
-1
@@ -46,7 +46,6 @@ bb2:
46 ```javascript
47 function foo$0(a$6, b$7, c$8) {
48 let x$9 = 0;
49 - ("<<TODO: handle complex control flow in codegen>>");
49 }
50
51 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-objectexpression-phi.expect.md
+1 -3
@@ -49,12 +49,10 @@ bb1:
49 function foo$0() {
50 let x$6 = 1;
51 let y$7 = 2;
52 - if (x$6 > 1) {
52 + bb1: if (x$6 > 1) {
53 x$10 = 2;
54 - ("<<TODO: handle complex control flow in codegen>>");
54 } else {
55 y$11 = 3;
57 - ("<<TODO: handle complex control flow in codegen>>");
56 }
57
58 let t$14 = {
compiler/forget/src/__tests__/fixtures/hir/ssa-return.expect.md
+1 -2
@@ -36,9 +36,8 @@ bb1:
36 ```javascript
37 function foo$0() {
38 let x$4 = 1;
39 - if (x$4 === 1) {
39 + bb1: if (x$4 === 1) {
40 x$7 = 2;
41 - ("<<TODO: handle complex control flow in codegen>>");
41 }
42
43 return x$8;
compiler/forget/src/__tests__/fixtures/hir/ssa-simple-phi.expect.md
+1 -3
@@ -44,12 +44,10 @@ bb1:
44 ```javascript
45 function foo$0() {
46 let y$5 = 2;
47 - if (y$5 > 1) {
47 + bb1: if (y$5 > 1) {
48 y$8 = 1;
49 - ("<<TODO: handle complex control flow in codegen>>");
49 } else {
50 y$9 = 2;
52 - ("<<TODO: handle complex control flow in codegen>>");
51 }
52
53 let x$11 = y$10;
compiler/forget/src/__tests__/fixtures/hir/ssa-single-if.expect.md
+1 -2
@@ -35,9 +35,8 @@ bb1:
35 function foo$0() {
36 let x$4 = 1;
37 let y$5 = 2;
38 - if (y$5) {
38 + bb1: if (y$5) {
39 let z$6 = x$4 + y$5;
40 - ("<<TODO: handle complex control flow in codegen>>");
40 }
41
42 return;
compiler/forget/src/__tests__/fixtures/hir/ssa-switch.expect.md
+3 -4
@@ -64,20 +64,19 @@ bb1:
64 ```javascript
65 function foo$0() {
66 let x$10 = 1;
67 - switch (x$10) {
67 + bb1: switch (x$10) {
68 case x$10 === 1: {
69 x$16 = x$10 + 1;
70 - ("<<TODO: handle complex control flow in codegen>>");
70 + break bb1;
71 }
72
73 case x$10 === 2: {
74 x$18 = x$10 + 2;
75 - ("<<TODO: handle complex control flow in codegen>>");
75 + break bb1;
76 }
77
78 default: {
79 x$20 = x$10 + 3;
80 - ("<<TODO: handle complex control flow in codegen>>");
80 }
81 }
82
compiler/forget/src/__tests__/fixtures/hir/ssa-throw.expect.md
+1 -2
@@ -35,9 +35,8 @@ bb1:
35 ```javascript
36 function foo$0() {
37 let x$4 = 1;
38 - if (x$4 === 1) {
38 + bb1: if (x$4 === 1) {
39 x$7 = 2;
40 - ("<<TODO: handle complex control flow in codegen>>");
40 }
41
42 throw x$8;
compiler/forget/src/__tests__/fixtures/hir/ssa-while-no-reassign.expect.md
-1
@@ -39,7 +39,6 @@ bb2:
39 ```javascript
40 function foo$0() {
41 let x$5 = 1;
42 - ("<<TODO: handle complex control flow in codegen>>");
42 }
43
44 ```
compiler/forget/src/__tests__/fixtures/hir/ssa-while.expect.md
-1
@@ -40,7 +40,6 @@ bb2:
40 ```javascript
41 function foo$0() {
42 let x$5 = 1;
43 - ("<<TODO: handle complex control flow in codegen>>");
43 }
44
45 ```
compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md
+8 -7
@@ -66,22 +66,23 @@ bb1:
66 function Component$0(props$9) {
67 let x$10 = [];
68 let y$11 = undefined;
69 - switch (props$9.p0) {
70 - case 1:
71 - break;
69 + bb1: switch (props$9.p0) {
70 + case 1: {
71 + break bb1;
72 + }
73
74 case true: {
75 x$10.push(props$9.p2);
76 y$15 = [];
76 - ("<<TODO: handle complex control flow in codegen>>");
77 + break bb1;
78 }
79
79 - default:
80 - break;
80 + default: {
81 + break bb1;
82 + }
83
84 case false: {
85 y$16 = x$10;
84 - ("<<TODO: handle complex control flow in codegen>>");
86 }
87 }
88
compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.expect.md new
+124
@@ -0,0 +1,124 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo(x) {
6 + let y;
7 + switch (x) {
8 + case 0: {
9 + y = 0;
10 + }
11 + case 1: {
12 + y = 1;
13 + }
14 + case 2: {
15 + break;
16 + }
17 + case 3: {
18 + y = 3;
19 + break;
20 + }
21 + case 4: {
22 + y = 4;
23 + }
24 + case 5: {
25 + y = 5;
26 + }
27 + default: {
28 + y = 0;
29 + }
30 + }
31 +}
32 +
33 +```
34 +
35 +## HIR
36 +
37 +```
38 +bb0:
39 + [1] Let mutate y$10 = undefined
40 + [2] Const mutate $11 = 5
41 + [3] Const mutate $12 = 4
42 + [4] Const mutate $13 = 3
43 + [5] Const mutate $14 = 2
44 + [6] Const mutate $15 = 1
45 + [7] Const mutate $16 = 0
46 + Switch (read x$9)
47 + Case read $16: bb10
48 + Case read $15: bb9
49 + Case read $14: bb1
50 + Case read $13: bb5
51 + Case read $12: bb4
52 + Case read $11: bb3
53 + Default: bb2
54 +bb10:
55 + predecessor blocks: bb0
56 + [8] Reassign mutate y$17 = 0
57 + Goto bb9
58 +bb9:
59 + predecessor blocks: bb10 bb0
60 + [9] Reassign mutate y$18 = 1
61 + Goto bb1
62 +bb5:
63 + predecessor blocks: bb0
64 + [10] Reassign mutate y$19 = 3
65 + Goto bb1
66 +bb4:
67 + predecessor blocks: bb0
68 + [11] Reassign mutate y$20 = 4
69 + Goto bb3
70 +bb3:
71 + predecessor blocks: bb4 bb0
72 + [12] Reassign mutate y$21 = 5
73 + Goto bb2
74 +bb2:
75 + predecessor blocks: bb3 bb0
76 + [13] Reassign mutate y$22 = 0
77 + Goto bb1
78 +bb1:
79 + predecessor blocks: bb9 bb0 bb5 bb2
80 + Return
81 +```
82 +
83 +## Code
84 +
85 +```javascript
86 +function foo$0(x$9) {
87 + let y$10 = undefined;
88 + bb1: switch (x$9) {
89 + case 0: {
90 + y$17 = 0;
91 + }
92 +
93 + case 1: {
94 + y$18 = 1;
95 + break bb1;
96 + }
97 +
98 + case 2: {
99 + break bb1;
100 + }
101 +
102 + case 3: {
103 + y$19 = 3;
104 + break bb1;
105 + }
106 +
107 + case 4: {
108 + y$20 = 4;
109 + }
110 +
111 + case 5: {
112 + y$21 = 5;
113 + }
114 +
115 + default: {
116 + y$22 = 0;
117 + }
118 + }
119 +
120 + return;
121 +}
122 +
123 +```
124 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/switch-with-fallthrough.js new
+27
@@ -0,0 +1,27 @@
1 +function foo(x) {
2 + let y;
3 + switch (x) {
4 + case 0: {
5 + y = 0;
6 + }
7 + case 1: {
8 + y = 1;
9 + }
10 + case 2: {
11 + break;
12 + }
13 + case 3: {
14 + y = 3;
15 + break;
16 + }
17 + case 4: {
18 + y = 4;
19 + }
20 + case 5: {
21 + y = 5;
22 + }
23 + default: {
24 + y = 0;
25 + }
26 + }
27 +}
compiler/forget/src/__tests__/fixtures/hir/switch.expect.md
+1 -3
@@ -61,17 +61,15 @@ bb1:
61 function Component$0(props$8) {
62 let x$9 = [];
63 let y$10 = undefined;
64 - switch (props$8.p0) {
64 + bb1: switch (props$8.p0) {
65 case true: {
66 x$9.push(props$8.p2);
67 x$9.push(props$8.p3);
68 y$13 = [];
69 - ("<<TODO: handle complex control flow in codegen>>");
69 }
70
71 case false: {
72 y$15 = x$9;
74 - ("<<TODO: handle complex control flow in codegen>>");
73 }
74 }
75