mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Build -> Codegen for LabelTerminal
NOTE: See background in #1476. Updates BuildHIR to use the new LabelTerminal for LabeledStatements, and adds support for HIR->ReactiveFunction transformation and codegen. Note that we sometimes produce an extraneous block wrapper if it turns out the label wasn't necessary, that seems...fine?
This commit is contained in:
@@ -450,15 +450,24 @@ function lowerStatement(
|
||||
// All other statements create a continuation block to allow `break`,
|
||||
// explicitly *don't* pass the label down
|
||||
const continuationBlock = builder.reserve("block");
|
||||
builder.label(label, continuationBlock.id, () => {
|
||||
lowerStatement(builder, stmt.get("body"));
|
||||
});
|
||||
builder.terminateWithContinuation(
|
||||
{
|
||||
const block = builder.enter("block", () => {
|
||||
builder.label(label, continuationBlock.id, () => {
|
||||
lowerStatement(builder, stmt.get("body"));
|
||||
});
|
||||
return {
|
||||
kind: "goto",
|
||||
block: continuationBlock.id,
|
||||
variant: GotoVariant.Break,
|
||||
id: makeInstructionId(0),
|
||||
};
|
||||
});
|
||||
builder.terminateWithContinuation(
|
||||
{
|
||||
kind: "label",
|
||||
block,
|
||||
fallthrough: continuationBlock.id,
|
||||
id: makeInstructionId(0),
|
||||
loc: stmt.node.loc ?? GeneratedSource,
|
||||
},
|
||||
continuationBlock
|
||||
);
|
||||
|
||||
@@ -135,7 +135,8 @@ export type ReactiveTerminal =
|
||||
| ReactiveWhileTerminal
|
||||
| ReactiveForTerminal
|
||||
| ReactiveForOfTerminal
|
||||
| ReactiveIfTerminal;
|
||||
| ReactiveIfTerminal
|
||||
| ReactiveLabelTerminal;
|
||||
|
||||
export type ReactiveBreakTerminal = {
|
||||
kind: "break";
|
||||
@@ -201,6 +202,11 @@ export type ReactiveIfTerminal = {
|
||||
alternate: ReactiveBlock | null;
|
||||
id: InstructionId;
|
||||
};
|
||||
export type ReactiveLabelTerminal = {
|
||||
kind: "label";
|
||||
block: ReactiveBlock;
|
||||
id: InstructionId;
|
||||
};
|
||||
|
||||
/**
|
||||
* A function lowered to HIR form, ie where its body is lowered to an HIR control-flow graph
|
||||
|
||||
@@ -541,7 +541,8 @@ export function removeUnreachableFallthroughs(func: HIR): void {
|
||||
if (
|
||||
block.terminal.kind === "if" ||
|
||||
block.terminal.kind === "switch" ||
|
||||
block.terminal.kind === "while"
|
||||
block.terminal.kind === "while" ||
|
||||
block.terminal.kind === "label"
|
||||
) {
|
||||
if (
|
||||
block.terminal.fallthrough !== null &&
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
ReactiveValue,
|
||||
Terminal,
|
||||
} from "../HIR/HIR";
|
||||
import todo from "../Utils/todo";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
|
||||
/**
|
||||
@@ -531,7 +530,35 @@ class Driver {
|
||||
break;
|
||||
}
|
||||
case "label": {
|
||||
todo("Support label terminals");
|
||||
const fallthroughId =
|
||||
terminal.fallthrough !== null &&
|
||||
!this.cx.isScheduled(terminal.fallthrough)
|
||||
? terminal.fallthrough
|
||||
: null;
|
||||
if (fallthroughId !== null) {
|
||||
const scheduleId = this.cx.schedule(fallthroughId, "if");
|
||||
scheduleIds.push(scheduleId);
|
||||
}
|
||||
|
||||
const block = this.traverseBlock(
|
||||
this.cx.ir.blocks.get(terminal.block)!
|
||||
);
|
||||
|
||||
this.cx.unscheduleAll(scheduleIds);
|
||||
blockValue.push({
|
||||
kind: "terminal",
|
||||
terminal: {
|
||||
kind: "label",
|
||||
block,
|
||||
id: terminal.id,
|
||||
},
|
||||
label: fallthroughId,
|
||||
});
|
||||
if (fallthroughId !== null) {
|
||||
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "optional-call":
|
||||
case "ternary":
|
||||
|
||||
@@ -409,6 +409,9 @@ function codegenTerminal(
|
||||
const test = codegenInstructionValue(cx, terminal.test);
|
||||
return t.whileStatement(test, codegenBlock(cx, terminal.loop));
|
||||
}
|
||||
case "label": {
|
||||
return codegenBlock(cx, terminal.block);
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal,
|
||||
|
||||
@@ -54,6 +54,7 @@ class Transform extends ReactiveFunctionTransform<boolean> {
|
||||
break;
|
||||
}
|
||||
// Non-loop terminals passthrough is contextual, inherits the parent isWithinScope
|
||||
case "label":
|
||||
case "break":
|
||||
case "continue":
|
||||
case "if":
|
||||
|
||||
@@ -258,6 +258,12 @@ function printTerminal(writer: Writer, terminal: ReactiveTerminal): void {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "label": {
|
||||
writer.writeLine("{");
|
||||
printReactiveInstructions(writer, terminal.block);
|
||||
writer.writeLine("}");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assertExhaustive(terminal, `Unhandled terminal ${terminal}`);
|
||||
}
|
||||
|
||||
@@ -443,6 +443,10 @@ function visit(context: Context, block: ReactiveBlock): void {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "label": {
|
||||
visit(context, terminal.block);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal,
|
||||
|
||||
@@ -149,6 +149,10 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "label": {
|
||||
this.visitBlock(terminal.block, state);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal,
|
||||
@@ -355,6 +359,10 @@ export function mapTerminalBlocks(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "label": {
|
||||
terminal.block = fn(terminal.block);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal,
|
||||
|
||||
@@ -296,7 +296,8 @@ export function leaveSSA(fn: HIRFunction): void {
|
||||
terminal.kind === "while" ||
|
||||
terminal.kind === "do-while" ||
|
||||
terminal.kind === "for" ||
|
||||
terminal.kind === "for-of") &&
|
||||
terminal.kind === "for-of" ||
|
||||
terminal.kind === "label") &&
|
||||
terminal.fallthrough !== null
|
||||
) {
|
||||
const fallthrough = fn.body.blocks.get(terminal.fallthrough)!;
|
||||
|
||||
@@ -19,10 +19,12 @@ function foo(a, b, c) {
|
||||
|
||||
```javascript
|
||||
function foo(a, b, c) {
|
||||
if (a) {
|
||||
while (b) {
|
||||
if (c) {
|
||||
break;
|
||||
{
|
||||
if (a) {
|
||||
while (b) {
|
||||
if (c) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* props.b *does* influence `a`
|
||||
*/
|
||||
function Component(props) {
|
||||
const a = [];
|
||||
a.push(props.a);
|
||||
label: {
|
||||
if (props.b) {
|
||||
break label;
|
||||
}
|
||||
a.push(props.c);
|
||||
}
|
||||
a.push(props.d);
|
||||
return a;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* props.b *does* influence `a`
|
||||
*/
|
||||
function Component(props) {
|
||||
const $ = React.unstable_useMemoCache(5);
|
||||
const c_0 = $[0] !== props.a;
|
||||
const c_1 = $[1] !== props.b;
|
||||
const c_2 = $[2] !== props.c;
|
||||
const c_3 = $[3] !== props.d;
|
||||
let a;
|
||||
if (c_0 || c_1 || c_2 || c_3) {
|
||||
a = [];
|
||||
a.push(props.a);
|
||||
bb1: {
|
||||
if (props.b) {
|
||||
break bb1;
|
||||
}
|
||||
|
||||
a.push(props.c);
|
||||
}
|
||||
|
||||
a.push(props.d);
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.c;
|
||||
$[3] = props.d;
|
||||
$[4] = a;
|
||||
} else {
|
||||
a = $[4];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* props.b *does* influence `a`
|
||||
*/
|
||||
function Component(props) {
|
||||
const a = [];
|
||||
a.push(props.a);
|
||||
label: {
|
||||
if (props.b) {
|
||||
break label;
|
||||
}
|
||||
a.push(props.c);
|
||||
}
|
||||
a.push(props.d);
|
||||
return a;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Expected a break target
|
||||
```
|
||||
|
||||
|
||||
+14
-4
@@ -16,11 +16,21 @@ function foo(a, b, c) {
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
## Error
|
||||
```javascript
|
||||
function foo(a, b, c) {
|
||||
let x = undefined;
|
||||
bb1: {
|
||||
if (a) {
|
||||
x = b;
|
||||
break bb1;
|
||||
}
|
||||
|
||||
x = c;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
Expected a break target
|
||||
```
|
||||
|
||||
|
||||
@@ -28,13 +28,15 @@ function foo(a, b, c, d) {
|
||||
let y;
|
||||
if (c_0 || c_1 || c_2 || c_3) {
|
||||
y = [];
|
||||
bb1: if (a) {
|
||||
if (b) {
|
||||
y.push(c);
|
||||
break bb1;
|
||||
}
|
||||
bb1: {
|
||||
if (a) {
|
||||
if (b) {
|
||||
y.push(c);
|
||||
break bb1;
|
||||
}
|
||||
|
||||
y.push(d);
|
||||
y.push(d);
|
||||
}
|
||||
}
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
|
||||
@@ -17,7 +17,9 @@ function foo(a) {
|
||||
|
||||
```javascript
|
||||
function foo(a) {
|
||||
return a + 1;
|
||||
{
|
||||
return a + 1;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user