[valueblocks] Convert logical terminal to ReactiveValue

Implements the conversion from LogicalTerminal into a ReactiveLogicalValue (and 
ReactiveSequenveValue if necessary). The implementation is a bit rough, i clean 
it up in subsequent PRs which revealed parts of the logic that could be shared w 
ternaries.
This commit is contained in:
Joe Savona
2023-01-31 13:39:32 -08:00
parent bb8ae86c24
commit d4acc7efa6
8 changed files with 261 additions and 73 deletions
+11 -1
View File
@@ -93,7 +93,10 @@ export type ReactiveInstruction = {
loc: SourceLocation;
};
export type ReactiveValue = InstructionValue | ReactiveLogicalValue;
export type ReactiveValue =
| InstructionValue
| ReactiveLogicalValue
| ReactiveSequenceValue;
export type ReactiveLogicalValue = {
kind: "LogicalExpression";
@@ -103,6 +106,13 @@ export type ReactiveLogicalValue = {
loc: SourceLocation;
};
export type ReactiveSequenceValue = {
kind: "SequenceExpression";
instructions: Array<ReactiveInstruction>;
value: ReactiveValue;
loc: SourceLocation;
};
export type ReactiveTerminal =
| ReactiveBreakTerminal
| ReactiveContinueTerminal
+10
View File
@@ -339,6 +339,16 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
} ${printInstructionValue(instrValue.right)}`;
break;
}
case "SequenceExpression": {
value = [
`Sequence`,
...instrValue.instructions.map(
(instr) => ` ${printInstruction(instr)}`
),
` ${printInstructionValue(instrValue.value)}`,
].join("\n");
break;
}
default: {
assertExhaustive(
instrValue,
@@ -19,11 +19,24 @@ import {
} from "../HIR";
import {
HIRFunction,
Instruction,
InstructionKind,
ReactiveBreakTerminal,
ReactiveContinueTerminal,
ReactiveFunction,
ReactiveInstruction,
ReactiveLogicalValue,
ReactiveSequenceValue,
ReactiveTerminalStatement,
ReactiveValue,
Terminal,
} from "../HIR/HIR";
import {
printInstructionValue,
printPlace,
printTerminal,
} from "../HIR/PrintHIR";
import { mapInstructionOperands } from "../HIR/visitors";
import { assertExhaustive } from "../Utils/utils";
/**
@@ -407,7 +420,16 @@ class Driver {
const scheduleId = this.cx.schedule(fallthroughId, "if");
scheduleIds.push(scheduleId);
this.visitBlock(this.cx.ir.blocks.get(terminal.test)!, blockValue);
const { place, value } = this.visitValueTerminal(terminal);
blockValue.push({
kind: "instruction",
instruction: {
id: terminal.id,
lvalue: { kind: InstructionKind.Const, place },
value,
loc: terminal.loc,
},
});
this.cx.unschedule(scheduleId);
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
@@ -447,6 +469,119 @@ class Driver {
}
}
visitValueTerminal(terminal: Terminal): {
value: ReactiveValue;
place: Place;
fallthrough: BlockId;
} {
switch (terminal.kind) {
case "logical": {
let testBlock: BasicBlock;
let leftValue: ReactiveValue | null = null;
let leftPlace: Place | null = null;
const defaultTestBlock = this.cx.ir.blocks.get(terminal.test)!;
if (defaultTestBlock.terminal.kind === "branch") {
testBlock = defaultTestBlock;
} else {
const leftResult = this.visitValueTerminal(defaultTestBlock.terminal);
testBlock = this.cx.ir.blocks.get(leftResult.fallthrough)!;
leftValue = leftResult.value;
leftPlace = leftResult.place;
}
invariant(
testBlock.terminal.kind === "branch",
"Unexpected terminal kind '%s' for logical test block",
testBlock.terminal.kind
);
const leftInstructions: Array<ReactiveInstruction> =
testBlock.instructions;
const leftBlock = this.cx.ir.blocks.get(testBlock.terminal.consequent)!;
leftInstructions.push(...leftBlock.instructions);
// TODO: If right block ends in a value terminal, recursively process with visitValueTerminal
// similar to handling for the compound lhs case.
const rightBlock = this.cx.ir.blocks.get(testBlock.terminal.alternate)!;
const rightInstructions: Array<ReactiveInstruction> =
rightBlock.instructions;
const place = leftInstructions.at(-1)!.lvalue!.place;
invariant(
place.identifier ===
rightInstructions.at(-1)!.lvalue!.place.identifier,
"Expected both branches of a logical expression to store to the same temporary"
);
if (leftPlace !== null) {
leftInstructions.forEach((instr) =>
mapInstructionOperands(instr as Instruction, (place) => {
return place.identifier === leftPlace!.identifier
? (leftValue! as Place)
: place;
})
);
rightInstructions.forEach((instr) =>
mapInstructionOperands(instr as Instruction, (place) => {
return place.identifier === leftPlace!.identifier
? (leftValue! as Place)
: place;
})
);
}
let left: ReactiveValue;
if (leftInstructions.length === 1) {
left = leftInstructions[0]!.value;
} else {
const sequence: ReactiveSequenceValue = {
kind: "SequenceExpression",
instructions: leftInstructions.slice(0, -1),
value: leftInstructions.at(-1)!.value,
loc: terminal.loc,
};
left = sequence;
}
let right: ReactiveValue;
if (rightInstructions.length === 1) {
right = rightInstructions[0]!.value;
} else {
const sequence: ReactiveSequenceValue = {
kind: "SequenceExpression",
instructions: rightInstructions.slice(0, -1),
value: rightInstructions.at(-1)!.value,
loc: terminal.loc,
};
right = sequence;
}
const value: ReactiveLogicalValue = {
kind: "LogicalExpression",
operator: terminal.operator,
left,
right,
loc: terminal.loc,
};
console.log(
printTerminal(terminal) +
" testBlock=" +
testBlock.id +
" " +
printPlace(place) +
"=" +
printInstructionValue(value)
);
return {
place: { ...place },
value,
fallthrough: terminal.fallthrough,
};
}
default: {
invariant(
false,
"Unexpected value block terminal kind '%s'",
terminal.kind
);
}
}
}
visitInitBlock(parent: ReactiveBlock, block: BasicBlock): ReactiveValueBlock {
const initBlock: ReactiveValueBlock = {
kind: "value-block",
@@ -93,7 +93,7 @@ function codegenBlock(cx: Context, block: ReactiveBlock): t.BlockStatement {
const statement = codegenInstructionNullable(
cx,
item.instruction,
codegenInstructionValue(cx.temp, item.instruction.value)
codegenInstructionValue(cx, item.instruction.value)
);
if (statement !== null) {
statements.push(statement);
@@ -272,7 +272,7 @@ function codegenTerminal(
}
case "if": {
return t.ifStatement(
codegenPlace(cx.temp, terminal.test),
codegenPlace(cx, terminal.test),
codegenBlock(cx, terminal.consequent),
terminal.alternate !== null
? codegenBlock(cx, terminal.alternate)
@@ -281,22 +281,22 @@ function codegenTerminal(
}
case "return": {
return t.returnStatement(
terminal.value !== null ? codegenPlace(cx.temp, terminal.value) : null
terminal.value !== null ? codegenPlace(cx, terminal.value) : null
);
}
case "switch": {
return t.switchStatement(
codegenPlace(cx.temp, terminal.test),
codegenPlace(cx, terminal.test),
terminal.cases.map((case_) => {
const test =
case_.test !== null ? codegenPlace(cx.temp, case_.test) : null;
case_.test !== null ? codegenPlace(cx, case_.test) : null;
const block = codegenBlock(cx, case_.block!);
return t.switchCase(test, [block]);
})
);
}
case "throw": {
return t.throwStatement(codegenPlace(cx.temp, terminal.value));
return t.throwStatement(codegenPlace(cx, terminal.value));
}
case "while": {
const test = codegenValueBlock(cx, terminal.test);
@@ -319,7 +319,7 @@ function codegenInstructionNullable(
let statement;
if (instr.lvalue !== null && cx.declared(instr.lvalue.place.identifier)) {
statement = codegenInstruction(
cx.temp,
cx,
{
...instr,
lvalue: {
@@ -330,7 +330,7 @@ function codegenInstructionNullable(
value
);
} else {
statement = codegenInstruction(cx.temp, instr, value);
statement = codegenInstruction(cx, instr, value);
}
if (statement.type === "EmptyStatement") {
return null;
@@ -348,7 +348,7 @@ function codegenForInit(
body.length === 0,
"Expected for init block to produce only temporaries"
);
return codegenInstructionValue(cx.temp, init.last.value);
return codegenInstructionValue(cx, init.last.value);
} else {
invariant(
body.length === 1,
@@ -376,7 +376,7 @@ function codegenValueBlock(
}
});
if (block.last !== null) {
const value = codegenInstructionValue(cx.temp, block.last.value);
const value = codegenInstructionValue(cx, block.last.value);
expressions.push(value);
}
invariant(
@@ -431,6 +431,7 @@ const createVariableDeclaration = withLoc(t.variableDeclaration);
const createWhileStatement = withLoc(t.whileStatement);
const createTaggedTemplateExpression = withLoc(t.taggedTemplateExpression);
const createLogicalExpression = withLoc(t.logicalExpression);
const createSequenceExpression = withLoc(t.sequenceExpression);
type Temporaries = Map<IdentifierId, t.Expression>;
@@ -439,7 +440,7 @@ function codegenLabel(id: BlockId): string {
}
function codegenInstruction(
temp: Temporaries,
cx: Context,
instr: ReactiveInstruction,
value: t.Expression
): t.Statement {
@@ -451,7 +452,7 @@ function codegenInstruction(
}
if (instr.lvalue.place.identifier.name === null) {
// temporary
temp.set(instr.lvalue.place.identifier.id, value);
cx.temp.set(instr.lvalue.place.identifier.id, value);
return t.emptyStatement();
} else {
switch (instr.lvalue.kind) {
@@ -482,21 +483,21 @@ function codegenInstruction(
}
function codegenInstructionValue(
temp: Temporaries,
cx: Context,
instrValue: ReactiveValue
): t.Expression {
let value: t.Expression;
switch (instrValue.kind) {
case "ArrayExpression": {
const elements = instrValue.elements.map((element) =>
codegenPlace(temp, element)
codegenPlace(cx, element)
);
value = t.arrayExpression(elements);
break;
}
case "BinaryExpression": {
const left = codegenPlace(temp, instrValue.left);
const right = codegenPlace(temp, instrValue.right);
const left = codegenPlace(cx, instrValue.left);
const right = codegenPlace(cx, instrValue.right);
value = createBinaryExpression(
instrValue.loc,
instrValue.operator,
@@ -508,41 +509,41 @@ function codegenInstructionValue(
case "UnaryExpression": {
value = t.unaryExpression(
instrValue.operator as "throw", // todo
codegenPlace(temp, instrValue.value)
codegenPlace(cx, instrValue.value)
);
break;
}
case "Primitive": {
value = codegenValue(temp, instrValue.value);
value = codegenValue(cx, instrValue.value);
break;
}
case "CallExpression": {
const callee = codegenPlace(temp, instrValue.callee);
const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
const callee = codegenPlace(cx, instrValue.callee);
const args = instrValue.args.map((arg) => codegenPlace(cx, arg));
value = createCallExpression(instrValue.loc, callee, args);
break;
}
case "PropertyCall": {
const receiver = codegenPlace(temp, instrValue.receiver);
const receiver = codegenPlace(cx, instrValue.receiver);
const callee = t.memberExpression(
receiver,
t.identifier(instrValue.property)
);
const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
const args = instrValue.args.map((arg) => codegenPlace(cx, arg));
value = createCallExpression(instrValue.loc, callee, args);
break;
}
case "ComputedCall": {
const receiver = codegenPlace(temp, instrValue.receiver);
const property = codegenPlace(temp, instrValue.property);
const receiver = codegenPlace(cx, instrValue.receiver);
const property = codegenPlace(cx, instrValue.property);
const callee = t.memberExpression(receiver, property, true);
const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
const args = instrValue.args.map((arg) => codegenPlace(cx, arg));
value = createCallExpression(instrValue.loc, callee, args);
break;
}
case "NewExpression": {
const callee = codegenPlace(temp, instrValue.callee);
const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
const callee = codegenPlace(cx, instrValue.callee);
const args = instrValue.args.map((arg) => codegenPlace(cx, arg));
value = t.newExpression(callee, args);
break;
}
@@ -551,10 +552,7 @@ function codegenInstructionValue(
if (instrValue.properties !== null) {
for (const [property, value] of instrValue.properties) {
properties.push(
t.objectProperty(
t.stringLiteral(property),
codegenPlace(temp, value)
)
t.objectProperty(t.stringLiteral(property), codegenPlace(cx, value))
);
}
}
@@ -571,11 +569,11 @@ function codegenInstructionValue(
attributes.push(
t.jsxAttribute(
t.jsxIdentifier(prop),
t.jsxExpressionContainer(codegenPlace(temp, value))
t.jsxExpressionContainer(codegenPlace(cx, value))
)
);
}
let tagValue = codegenPlace(temp, instrValue.tag);
let tagValue = codegenPlace(cx, instrValue.tag);
let tag: string;
if (tagValue.type === "Identifier") {
tag = tagValue.name;
@@ -588,7 +586,7 @@ function codegenInstructionValue(
}
const children =
instrValue.children !== null
? instrValue.children.map((child) => codegenJsxElement(temp, child))
? instrValue.children.map((child) => codegenJsxElement(cx, child))
: [];
value = t.jsxElement(
t.jsxOpeningElement(
@@ -608,7 +606,7 @@ function codegenInstructionValue(
value = t.jsxFragment(
t.jsxOpeningFragment(),
t.jsxClosingFragment(),
instrValue.children.map((child) => codegenJsxElement(temp, child))
instrValue.children.map((child) => codegenJsxElement(cx, child))
);
break;
}
@@ -624,24 +622,24 @@ function codegenInstructionValue(
value = t.assignmentExpression(
"=",
t.memberExpression(
codegenPlace(temp, instrValue.object),
codegenPlace(cx, instrValue.object),
t.identifier(instrValue.property)
),
codegenPlace(temp, instrValue.value)
codegenPlace(cx, instrValue.value)
);
break;
}
case "PropertyLoad": {
if (instrValue.optional) {
value = t.optionalMemberExpression(
codegenPlace(temp, instrValue.object),
codegenPlace(cx, instrValue.object),
t.identifier(instrValue.property),
undefined,
true
);
} else {
value = t.memberExpression(
codegenPlace(temp, instrValue.object),
codegenPlace(cx, instrValue.object),
t.identifier(instrValue.property)
);
}
@@ -651,24 +649,24 @@ function codegenInstructionValue(
value = t.assignmentExpression(
"=",
t.memberExpression(
codegenPlace(temp, instrValue.object),
codegenPlace(temp, instrValue.property),
codegenPlace(cx, instrValue.object),
codegenPlace(cx, instrValue.property),
true
),
codegenPlace(temp, instrValue.value)
codegenPlace(cx, instrValue.value)
);
break;
}
case "ComputedLoad": {
value = t.memberExpression(
codegenPlace(temp, instrValue.object),
codegenPlace(temp, instrValue.property),
codegenPlace(cx, instrValue.object),
codegenPlace(cx, instrValue.property),
true
);
break;
}
case "Identifier": {
value = codegenPlace(temp, instrValue);
value = codegenPlace(cx, instrValue);
break;
}
case "FunctionExpression": {
@@ -681,7 +679,7 @@ function codegenInstructionValue(
case "TaggedTemplateExpression": {
value = createTaggedTemplateExpression(
instrValue.loc,
codegenPlace(temp, instrValue.tag),
codegenPlace(cx, instrValue.tag),
t.templateLiteral([t.templateElement(instrValue.value)], [])
);
break;
@@ -690,11 +688,39 @@ function codegenInstructionValue(
value = createLogicalExpression(
instrValue.loc,
instrValue.operator,
codegenInstructionValue(temp, instrValue.left),
codegenInstructionValue(temp, instrValue.right)
codegenInstructionValue(cx, instrValue.left),
codegenInstructionValue(cx, instrValue.right)
);
break;
}
case "SequenceExpression": {
const body = codegenBlock(
cx,
instrValue.instructions.map((instruction) => ({
kind: "instruction",
instruction,
}))
).body;
const expressions = body.map((stmt) => {
if (stmt.type === "ExpressionStatement") {
return stmt.expression;
} else {
todoInvariant(
false,
`Handle conversion of ${stmt.type} to expression`
);
}
});
if (expressions.length === 0) {
value = codegenInstructionValue(cx, instrValue.value);
} else {
value = createSequenceExpression(instrValue.loc, [
...expressions,
codegenInstructionValue(cx, instrValue.value),
]);
}
break;
}
default: {
assertExhaustive(
instrValue,
@@ -706,7 +732,7 @@ function codegenInstructionValue(
}
function codegenJsxElement(
temp: Temporaries,
cx: Context,
place: Place
):
| t.JSXText
@@ -714,7 +740,7 @@ function codegenJsxElement(
| t.JSXSpreadChild
| t.JSXElement
| t.JSXFragment {
const value = codegenPlace(temp, place);
const value = codegenPlace(cx, place);
switch (value.type) {
case "StringLiteral": {
return t.jsxText(value.value);
@@ -730,7 +756,7 @@ function codegenLVal(lval: LValue): t.LVal {
}
function codegenValue(
temp: Temporaries,
cx: Context,
value: boolean | number | string | null | undefined
): t.Expression {
if (typeof value === "number") {
@@ -748,9 +774,9 @@ function codegenValue(
}
}
function codegenPlace(temp: Temporaries, place: Place): t.Expression {
function codegenPlace(cx: Context, place: Place): t.Expression {
todoInvariant(place.kind === "Identifier", "support scope values");
let tmp = temp.get(place.identifier.id);
let tmp = cx.temp.get(place.identifier.id);
if (tmp != null) {
return tmp;
}
@@ -91,6 +91,13 @@ export function* eachReactiveValueOperand(
yield* eachReactiveValueOperand(instrValue.right);
break;
}
case "SequenceExpression": {
for (const instr of instrValue.instructions) {
yield* eachReactiveValueOperand(instr.value);
}
yield* eachReactiveValueOperand(instrValue.value);
break;
}
default: {
yield* eachInstructionValueOperand(instrValue);
}
+5 -1
View File
@@ -123,7 +123,6 @@ export function leaveSSA(fn: HIRFunction) {
(terminal.kind === "if" ||
terminal.kind === "switch" ||
terminal.kind === "while" ||
terminal.kind === "logical" ||
terminal.kind === "for") &&
terminal.fallthrough !== null
) {
@@ -149,6 +148,11 @@ export function leaveSSA(fn: HIRFunction) {
pushPhis(rewritePhis, update);
update.phis.clear();
}
if (terminal.kind === "logical") {
const fallthrough = fn.body.blocks.get(terminal.fallthrough)!;
pushPhis(rewritePhis, fallthrough);
fallthrough.phis.clear();
}
for (const { phi, block: phiBlock } of reassignmentPhis) {
// In some cases one of the phi operands can be defined *before* the let binding
@@ -2,8 +2,9 @@
## Input
```javascript
// @only
function component(props) {
let a = props.a && props.b;
let a = (props.a && props.b && props.c) || props.d;
return a;
// let b = props.c || props.d;
// let c = props.e ?? props.f;
@@ -15,25 +16,19 @@ function component(props) {
## Code
```javascript
// @only
function component(props) {
const $ = React.useMemoCache();
const c_0 = $[0] !== props.a;
const c_1 = $[1] !== props.b;
let t2;
if (c_0 || c_1) {
t2 = undefined;
if (props.a) {
t2 = props.a;
} else {
t2 = props.b;
}
$[0] = props.a;
$[1] = props.b;
$[2] = t2;
const c_0 = $[0] !== props;
let t1;
if (c_0) {
t1 = (props.a && props.b && props.c) || props.d;
$[0] = props;
$[1] = t1;
} else {
t2 = $[2];
t1 = $[1];
}
const a = t2;
const a = t1;
return a;
}
@@ -1,5 +1,6 @@
// @only
function component(props) {
let a = props.a && props.b;
let a = (props.a && props.b && props.c) || props.d;
return a;
// let b = props.c || props.d;
// let c = props.e ?? props.f;