mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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.
This commit is contained in:
@@ -96,7 +96,10 @@ class Context {
|
|||||||
*/
|
*/
|
||||||
schedule(block: BlockId, type: "if" | "switch" | "case"): number {
|
schedule(block: BlockId, type: "if" | "switch" | "case"): number {
|
||||||
const id = this.#nextScheduleId++;
|
const id = this.#nextScheduleId++;
|
||||||
invariant(!this.#scheduled.has(block), "Block is already scheduled");
|
invariant(
|
||||||
|
!this.#scheduled.has(block),
|
||||||
|
`Break block is already scheduled: bb${block}`
|
||||||
|
);
|
||||||
this.#scheduled.add(block);
|
this.#scheduled.add(block);
|
||||||
this.#controlFlowStack.push({ block, id, type });
|
this.#controlFlowStack.push({ block, id, type });
|
||||||
return id;
|
return id;
|
||||||
@@ -112,11 +115,12 @@ class Context {
|
|||||||
this.#scheduled.add(fallthroughBlock);
|
this.#scheduled.add(fallthroughBlock);
|
||||||
invariant(
|
invariant(
|
||||||
!this.#scheduled.has(continueBlock),
|
!this.#scheduled.has(continueBlock),
|
||||||
"Block is already scheduled"
|
`Continue block is already scheduled: bb${continueBlock}`
|
||||||
);
|
);
|
||||||
this.#scheduled.add(continueBlock);
|
this.#scheduled.add(continueBlock);
|
||||||
|
let ownsLoop = false;
|
||||||
if (loopBlock !== null) {
|
if (loopBlock !== null) {
|
||||||
invariant(!this.#scheduled.has(loopBlock), "Block is already scheduled");
|
ownsLoop = !this.#scheduled.has(loopBlock);
|
||||||
this.#scheduled.add(loopBlock);
|
this.#scheduled.add(loopBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +131,7 @@ class Context {
|
|||||||
type: "loop",
|
type: "loop",
|
||||||
continueBlock,
|
continueBlock,
|
||||||
loopBlock,
|
loopBlock,
|
||||||
|
ownsLoop,
|
||||||
});
|
});
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -145,7 +150,7 @@ class Context {
|
|||||||
}
|
}
|
||||||
if (last.type === "loop") {
|
if (last.type === "loop") {
|
||||||
this.#scheduled.delete(last.continueBlock);
|
this.#scheduled.delete(last.continueBlock);
|
||||||
if (last.loopBlock !== null) {
|
if (last.ownsLoop && last.loopBlock !== null) {
|
||||||
this.#scheduled.delete(last.loopBlock);
|
this.#scheduled.delete(last.loopBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,21 +271,22 @@ type ControlFlowTarget =
|
|||||||
ownsBlock: boolean;
|
ownsBlock: boolean;
|
||||||
continueBlock: BlockId;
|
continueBlock: BlockId;
|
||||||
loopBlock: BlockId | null;
|
loopBlock: BlockId | null;
|
||||||
|
ownsLoop: boolean;
|
||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function codegenBlock(cx: Context, block: BasicBlock): t.BlockStatement {
|
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> = [];
|
const body: Array<t.Statement> = [];
|
||||||
writeBlock(cx, block, body);
|
writeBlock(cx, block, body);
|
||||||
return t.blockStatement(body);
|
return t.blockStatement(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
|
function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
|
||||||
|
invariant(
|
||||||
|
!cx.emitted.has(block.id),
|
||||||
|
`Cannot emit the same block twice: bb${block.id}`
|
||||||
|
);
|
||||||
|
cx.emitted.add(block.id);
|
||||||
for (const instr of block.instructions) {
|
for (const instr of block.instructions) {
|
||||||
writeInstr(cx, instr, body);
|
writeInstr(cx, instr, body);
|
||||||
}
|
}
|
||||||
@@ -440,6 +446,10 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
|
|||||||
terminal.fallthrough !== null && !cx.isScheduled(terminal.fallthrough)
|
terminal.fallthrough !== null && !cx.isScheduled(terminal.fallthrough)
|
||||||
? terminal.fallthrough
|
? terminal.fallthrough
|
||||||
: null;
|
: null;
|
||||||
|
const loopId =
|
||||||
|
!cx.isScheduled(terminal.loop) && terminal.loop !== terminal.fallthrough
|
||||||
|
? terminal.loop
|
||||||
|
: null;
|
||||||
const scheduleId = cx.scheduleLoop(
|
const scheduleId = cx.scheduleLoop(
|
||||||
terminal.fallthrough,
|
terminal.fallthrough,
|
||||||
terminal.test,
|
terminal.test,
|
||||||
@@ -448,10 +458,15 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array<t.Statement>) {
|
|||||||
scheduleIds.push(scheduleId);
|
scheduleIds.push(scheduleId);
|
||||||
|
|
||||||
let loopBody: t.Statement;
|
let loopBody: t.Statement;
|
||||||
if (terminal.loop !== null) {
|
if (loopId) {
|
||||||
loopBody = codegenBlock(cx, cx.ir.blocks.get(terminal.loop)!);
|
loopBody = codegenBlock(cx, cx.ir.blocks.get(loopId)!);
|
||||||
} else {
|
} else {
|
||||||
loopBody = t.blockStatement([]);
|
const break_ = codegenBreak(cx, terminal.loop);
|
||||||
|
invariant(
|
||||||
|
break_ !== null,
|
||||||
|
"If loop body is already scheduled it must be a break"
|
||||||
|
);
|
||||||
|
loopBody = t.blockStatement([break_]);
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.unscheduleAll(scheduleIds);
|
cx.unscheduleAll(scheduleIds);
|
||||||
|
|||||||
@@ -403,12 +403,6 @@ function shrink(func: HIR): HIR {
|
|||||||
block.terminal.fallthrough = null;
|
block.terminal.fallthrough = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (block.terminal.kind === "while") {
|
|
||||||
invariant(
|
|
||||||
resolveBlockTarget(block.terminal.loop) === block.terminal.loop,
|
|
||||||
`Expected while loop body to remain after shrinking`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return { blocks, entry: func.entry };
|
return { blocks, entry: func.entry };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function foo(a, b) {
|
||||||
|
while (a) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## HIR
|
||||||
|
|
||||||
|
```
|
||||||
|
bb0:
|
||||||
|
While test=bb1 loop=bb2 fallthrough=bb2
|
||||||
|
bb1:
|
||||||
|
predecessor blocks: bb0
|
||||||
|
If (read a$3) then:bb2 else:bb2
|
||||||
|
bb2:
|
||||||
|
predecessor blocks: bb1
|
||||||
|
Return read b$4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function foo$0(a$3, b$4) {
|
||||||
|
bb2: while (a$3) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return b$4;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
function foo(a, b) {
|
||||||
|
while (a) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user