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:
Joe Savona
2023-04-05 16:26:40 -07:00
parent b087635e5d
commit d243e748d0
18 changed files with 167 additions and 55 deletions
+14 -5
View File
@@ -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
);
+7 -1
View File
@@ -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
+2 -1
View File
@@ -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,
+2 -1
View File
@@ -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;
}
```
@@ -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
```
@@ -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;
}
}
```