@samitouri / QOS-React-2 / commits / f57bcacced

fix for while (...) { break } edge case

Small adjustment to the previous PR for a special case: ```javascript while (cond) { break; } ``` The loop body is an indirection to the fallthrough, so shrink() collapses that and makes the while.loop === while.fallthrough. We now detect that this is the case in codegen and correctly emit a `break` rather than trying to write the fallthrough block inside the loop.

Joe Savona committed Nov 9, 2022 at 20:52 UTC f57bcacced0cd532cfd94dea00cf9d50023e86a4
4 files changed +71 -18
compiler/forget/src/HIR/Codegen.ts
+27 -12
@@ -96,7 +96,10 @@ class Context {
96 */
97 schedule(block: BlockId, type: "if" | "switch" | "case"): number {
98 const id = this.#nextScheduleId++;
99 - invariant(!this.#scheduled.has(block), "Block is already scheduled");
99 + invariant(
100 + !this.#scheduled.has(block),
101 + `Break block is already scheduled: bb${block}`
102 + );
103 this.#scheduled.add(block);
104 this.#controlFlowStack.push({ block, id, type });
105 return id;
@@ -112,11 +115,12 @@ class Context {
115 this.#scheduled.add(fallthroughBlock);
116 invariant(
117 !this.#scheduled.has(continueBlock),
115 - "Block is already scheduled"
118 + `Continue block is already scheduled: bb${continueBlock}`
119 );
120 this.#scheduled.add(continueBlock);
121 + let ownsLoop = false;
122 if (loopBlock !== null) {
119 - invariant(!this.#scheduled.has(loopBlock), "Block is already scheduled");
123 + ownsLoop = !this.#scheduled.has(loopBlock);
124 this.#scheduled.add(loopBlock);
125 }
126
@@ -127,6 +131,7 @@ class Context {
131 type: "loop",
132 continueBlock,
133 loopBlock,
134 + ownsLoop,
135 });
136 return id;
137 }
@@ -145,7 +150,7 @@ class Context {
150 }
151 if (last.type === "loop") {
152 this.#scheduled.delete(last.continueBlock);
148 - if (last.loopBlock !== null) {
153 + if (last.ownsLoop && last.loopBlock !== null) {
154 this.#scheduled.delete(last.loopBlock);
155 }
156 }
@@ -266,21 +271,22 @@ type ControlFlowTarget =
271 ownsBlock: boolean;
272 continueBlock: BlockId;
273 loopBlock: BlockId | null;
274 + ownsLoop: boolean;
275 id: number;
276 };
277
278 function codegenBlock(cx: Context, block: BasicBlock): t.BlockStatement {
273 - invariant(
274 - !cx.emitted.has(block.id),
275 - `Cannot emit the same block twice: bb${block.id}`
276 - );
277 - cx.emitted.add(block.id);
279 const body: Array<t.Statement> = [];
280 writeBlock(cx, block, body);
281 return t.blockStatement(body);
282 }
283
284 function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
285 + invariant(
286 + !cx.emitted.has(block.id),
287 + `Cannot emit the same block twice: bb${block.id}`
288 + );
289 + cx.emitted.add(block.id);
290 for (const instr of block.instructions) {
291 writeInstr(cx, instr, body);
292 }
@@ -440,6 +446,10 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
446 terminal.fallthrough !== null && !cx.isScheduled(terminal.fallthrough)
447 ? terminal.fallthrough
448 : null;
449 + const loopId =
450 + !cx.isScheduled(terminal.loop) && terminal.loop !== terminal.fallthrough
451 + ? terminal.loop
452 + : null;
453 const scheduleId = cx.scheduleLoop(
454 terminal.fallthrough,
455 terminal.test,
@@ -448,10 +458,15 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
458 scheduleIds.push(scheduleId);
459
460 let loopBody: t.Statement;
451 - if (terminal.loop !== null) {
452 - loopBody = codegenBlock(cx, cx.ir.blocks.get(terminal.loop)!);
461 + if (loopId) {
462 + loopBody = codegenBlock(cx, cx.ir.blocks.get(loopId)!);
463 } else {
454 - loopBody = t.blockStatement([]);
464 + const break_ = codegenBreak(cx, terminal.loop);
465 + invariant(
466 + break_ !== null,
467 + "If loop body is already scheduled it must be a break"
468 + );
469 + loopBody = t.blockStatement([break_]);
470 }
471
472 cx.unscheduleAll(scheduleIds);
compiler/forget/src/HIR/HIRBuilder.ts
-6
@@ -403,12 +403,6 @@ function shrink(func: HIR): HIR {
403 block.terminal.fallthrough = null;
404 }
405 }
406 - if (block.terminal.kind === "while") {
407 - invariant(
408 - resolveBlockTarget(block.terminal.loop) === block.terminal.loop,
409 - `Expected while loop body to remain after shrinking`
410 - );
411 - }
406 }
407 return { blocks, entry: func.entry };
408 }
compiler/forget/src/__tests__/fixtures/hir/while-break.expect.md new
+38
@@ -0,0 +1,38 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo(a, b) {
6 + while (a) {
7 + break;
8 + }
9 + return b;
10 +}
11 +
12 +```
13 +
14 +## HIR
15 +
16 +```
17 +bb0:
18 + While test=bb1 loop=bb2 fallthrough=bb2
19 +bb1:
20 + predecessor blocks: bb0
21 + If (read a$3) then:bb2 else:bb2
22 +bb2:
23 + predecessor blocks: bb1
24 + Return read b$4
25 +```
26 +
27 +## Code
28 +
29 +```javascript
30 +function foo$0(a$3, b$4) {
31 + bb2: while (a$3) {
32 + break;
33 + }
34 + return b$4;
35 +}
36 +
37 +```
38 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/while-break.js new
+6
@@ -0,0 +1,6 @@
1 +function foo(a, b) {
2 + while (a) {
3 + break;
4 + }
5 + return b;
6 +}