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
+}