diff --git a/compiler/forget/src/HIR/Codegen.ts b/compiler/forget/src/HIR/Codegen.ts deleted file mode 100644 index 4004871607..0000000000 --- a/compiler/forget/src/HIR/Codegen.ts +++ /dev/null @@ -1,575 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import * as t from "@babel/types"; -import { invariant } from "../Utils/CompilerError"; -import { todoInvariant } from "../Utils/todo"; -import { assertExhaustive } from "../Utils/utils"; -import { - BlockId, - GeneratedSource, - HIRFunction, - Identifier, - IdentifierId, - Instruction, - InstructionId, - InstructionKind, - InstructionValue, - LValue, - Place, - ReactiveInstruction, - SourceLocation, -} from "./HIR"; -import { BlockTerminal, Visitor, visitTree } from "./HIRTreeVisitor"; - -function withLoc TNode>( - fn: T -): ( - loc: SourceLocation | null | undefined, - ...args: Parameters -) => ReturnType { - return ( - loc: SourceLocation | null | undefined, - ...args: Parameters - ): ReturnType => { - const node = fn(...args); - if (loc != null && loc != GeneratedSource) { - node.loc = loc; - } - // @ts-ignore - return node; - }; -} - -export const createBinaryExpression = withLoc(t.binaryExpression); -export const createCallExpression = withLoc(t.callExpression); -export const createExpressionStatement = withLoc(t.expressionStatement); -export const createFunctionDeclaration = withLoc(t.functionDeclaration); -export const createLabelledStatement = withLoc(t.labeledStatement); -export const createVariableDeclaration = withLoc(t.variableDeclaration); -export const createWhileStatement = withLoc(t.whileStatement); - -/** - * Converts HIR into Babel nodes, which can then be printed into source text. - * Note that converting source to HIR and back is not guaranteed to produce - * identicl source text: instead, it is guaranteed to produce semantically - * equivalent JavaScript. Where possible the original shape of the source - * code is preserved. Notably, temporary variables are only introduced - * where strictly necessary such that in general the only variable declarations - * that appear in the output are those that appeared in the input. - * - * However, it is expected that minor changes may occur, such as splitting - * multiple variable declarations into one, converting `else` branches - * into fallthrough branches, etc. - * - * Also, the *semantics* of variable resolution are preserved, but the exact - * original block structure is *not* guaranteed to be preserved. As such, - * variable names in the output may have a suffix attached to distinguish them. - * It is expected that the output will be passed through a minifier which can - * rename variables to reduce code size. In theory minification could be - * performed as an HIR optimization pass, that is left todo for the time being. - */ -export default function codegen(fn: HIRFunction): t.Function { - const visitor = new CodegenVisitor(); - const body = visitTree(fn, visitor); - invariant(t.isBlockStatement(body), "Expected a block statement"); - const params = fn.params.map((param) => convertIdentifier(param.identifier)); - return createFunctionDeclaration( - fn.loc, - fn.id !== null ? convertIdentifier(fn.id) : null, - params, - body, - fn.generator, - fn.async - ); -} - -export type Temporaries = Map; - -class CodegenVisitor - implements - Visitor< - Array, - t.Statement, - Array, - Array, - t.Expression, - t.Statement, - t.SwitchCase - > -{ - depth: number = 0; - temp: Map = new Map(); - - enterBlock(): t.Statement[] { - this.depth++; - return []; - } - appendBlock( - block: t.Statement[], - item: t.Statement, - blockId?: BlockId | undefined - ): void { - if (item.type === "EmptyStatement") { - return; - } - if (blockId !== undefined) { - block.push( - createLabelledStatement( - item.loc, - t.identifier(codegenLabel(blockId)), - item - ) - ); - } else { - block.push(item); - } - } - leaveBlock(block: t.Statement[]): t.Statement { - this.depth--; - return t.blockStatement(block); - } - enterValueBlock(): t.Statement[] { - return this.enterBlock(); - } - appendValueBlock(block: t.Statement[], item: t.Statement): void { - this.appendBlock(block, item); - } - leaveValueBlock( - block: t.Statement[], - place: { value: t.Expression; id: InstructionId } | null - ): t.Expression { - this.depth--; - if (block.length === 0) { - invariant(place !== null, "Unexpected empty value block"); - return place.value; - } - const expressions = block.map((stmt) => { - switch (stmt.type) { - case "ExpressionStatement": - return stmt.expression; - default: - todoInvariant( - false, - `Handle conversion of ${stmt.type} to expression` - ); - } - }); - if (place !== null) { - expressions.push(place.value); - } - return t.sequenceExpression(expressions); - } - - enterInitBlock(block: t.Statement[]): t.Statement[] { - return this.enterBlock(); - } - - appendInitBlock(block: t.Statement[], item: t.Statement): void { - this.appendBlock(block, item); - } - leaveInitBlock(block: t.Statement[]): t.Statement[] { - switch (block.length) { - case 0: { - return [t.emptyStatement()]; - } - case 1: { - return [block[0]]; - } - default: { - return [t.blockStatement(block)]; - } - } - } - - visitValue(value: InstructionValue): t.Expression { - return codegenInstructionValue(this.temp, value); - } - visitInstruction(instr: Instruction, value: t.Expression): t.Statement { - return codegenInstruction(this.temp, instr, value); - } - visitTerminalId(id: InstructionId): void {} - visitImplicitTerminal(): t.Statement | null { - return null; - } - visitTerminal( - terminal: BlockTerminal< - t.Statement[], - t.Expression, - t.Statement, - t.SwitchCase - > - ): t.Statement { - switch (terminal.kind) { - case "break": { - if (terminal.label) { - return t.breakStatement(t.identifier(codegenLabel(terminal.label))); - } else { - return t.breakStatement(); - } - } - case "continue": { - if (terminal.label) { - return t.continueStatement( - t.identifier(codegenLabel(terminal.label)) - ); - } else { - return t.continueStatement(); - } - } - case "if": { - return t.ifStatement( - terminal.test, - terminal.consequent, - terminal.alternate - ); - } - case "switch": { - return t.switchStatement(terminal.test, terminal.cases); - } - case "while": { - return createWhileStatement(terminal.loc, terminal.test, terminal.loop); - } - case "for": { - const initBlock = terminal.init; - invariant( - initBlock.length === 1, - "Expected for init to be a single expression or statement" - ); - const initStatement = initBlock[0]!; - let init; - if (initStatement.type === "VariableDeclaration") { - init = initStatement; - } else if (initStatement.type === "ExpressionStatement") { - init = initStatement.expression; - } else { - invariant( - false, - `Expected 'for' init block to contain variable declaration or an expression, got '${initStatement.type}'.` - ); - } - return t.forStatement( - init, - terminal.test, - terminal.update, - terminal.loop - ); - } - case "return": { - const createReturnStatement = withLoc(t.returnStatement); - if (terminal.value !== null) { - return createReturnStatement(terminal.loc, terminal.value); - } else if (this.depth === 1) { - // A return at the top-level of a function must be the last instruction, - // and functions implicitly return after the last instruction of the top-level. - // Elide the return. - return t.emptyStatement(); - } else { - return createReturnStatement(terminal.loc); - } - } - case "throw": { - return t.throwStatement(terminal.value); - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind '${(terminal as any).kind}'` - ); - } - } - } - visitCase(test: t.Expression | null, block: t.Statement): t.SwitchCase { - return t.switchCase(test, [block]); - } -} - -export function codegenLabel(id: BlockId): string { - return `bb${id}`; -} - -export function codegenInstruction( - temp: Temporaries, - instr: ReactiveInstruction, - value: t.Expression -): t.Statement { - if (t.isStatement(value)) { - return value; - } - if (instr.lvalue === null) { - return t.expressionStatement(value); - } - if (instr.lvalue.place.identifier.name === null) { - // temporary - temp.set(instr.lvalue.place.identifier.id, value); - return t.emptyStatement(); - } else { - switch (instr.lvalue.kind) { - case InstructionKind.Const: { - return createVariableDeclaration(instr.loc, "const", [ - t.variableDeclarator(codegenLVal(instr.lvalue), value), - ]); - } - case InstructionKind.Let: { - return createVariableDeclaration(instr.loc, "let", [ - t.variableDeclarator(codegenLVal(instr.lvalue), value), - ]); - } - case InstructionKind.Reassign: { - return createExpressionStatement( - instr.loc, - t.assignmentExpression("=", codegenLVal(instr.lvalue), value) - ); - } - default: { - assertExhaustive( - instr.lvalue.kind, - `Unexpected instruction kind '${instr.lvalue.kind}'` - ); - } - } - } -} - -export function codegenInstructionValue( - temp: Temporaries, - instrValue: InstructionValue -): t.Expression { - let value: t.Expression; - switch (instrValue.kind) { - case "ArrayExpression": { - const elements = instrValue.elements.map((element) => - codegenPlace(temp, element) - ); - value = t.arrayExpression(elements); - break; - } - case "BinaryExpression": { - const left = codegenPlace(temp, instrValue.left); - const right = codegenPlace(temp, instrValue.right); - value = createBinaryExpression( - instrValue.loc, - instrValue.operator, - left, - right - ); - break; - } - case "UnaryExpression": { - value = t.unaryExpression( - instrValue.operator as "throw", // todo - codegenPlace(temp, instrValue.value) - ); - break; - } - case "Primitive": { - value = codegenValue(temp, instrValue.value); - break; - } - case "CallExpression": { - const callee = codegenPlace(temp, instrValue.callee); - const args = instrValue.args.map((arg) => codegenPlace(temp, 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)); - value = t.newExpression(callee, args); - break; - } - case "ObjectExpression": { - const properties = []; - if (instrValue.properties !== null) { - for (const [property, value] of instrValue.properties) { - properties.push( - t.objectProperty( - t.stringLiteral(property), - codegenPlace(temp, value) - ) - ); - } - } - value = t.objectExpression(properties); - break; - } - case "JSXText": { - value = t.stringLiteral(instrValue.value); - break; - } - case "JsxExpression": { - const attributes: Array = []; - for (const [prop, value] of instrValue.props) { - attributes.push( - t.jsxAttribute( - t.jsxIdentifier(prop), - t.jsxExpressionContainer(codegenPlace(temp, value)) - ) - ); - } - let tagValue = codegenPlace(temp, instrValue.tag); - let tag: string; - if (tagValue.type === "Identifier") { - tag = tagValue.name; - } else { - invariant( - tagValue.type === "StringLiteral", - "Expected JSX tag to be an identifier or string" - ); - tag = tagValue.value; - } - const children = - instrValue.children !== null - ? instrValue.children.map((child) => codegenJsxElement(temp, child)) - : []; - value = t.jsxElement( - t.jsxOpeningElement( - t.jsxIdentifier(tag), - attributes, - instrValue.children === null - ), - instrValue.children !== null - ? t.jsxClosingElement(t.jsxIdentifier(tag)) - : null, - children, - instrValue.children === null - ); - break; - } - case "JsxFragment": { - value = t.jsxFragment( - t.jsxOpeningFragment(), - t.jsxClosingFragment(), - instrValue.children.map((child) => codegenJsxElement(temp, child)) - ); - break; - } - case "OtherStatement": { - const node = instrValue.node; - if (!t.isExpression(node)) { - return node as any; // TODO handle statements, jsx fragments - } - value = node; - break; - } - case "PropertyStore": { - value = t.assignmentExpression( - "=", - t.memberExpression( - codegenPlace(temp, instrValue.object), - t.identifier(instrValue.property) - ), - codegenPlace(temp, instrValue.value) - ); - break; - } - case "PropertyLoad": { - value = t.memberExpression( - codegenPlace(temp, instrValue.object), - t.identifier(instrValue.property) - ); - break; - } - case "ComputedStore": { - value = t.assignmentExpression( - "=", - t.memberExpression( - codegenPlace(temp, instrValue.object), - codegenPlace(temp, instrValue.property), - true - ), - codegenPlace(temp, instrValue.value) - ); - break; - } - case "ComputedLoad": { - value = t.memberExpression( - codegenPlace(temp, instrValue.object), - codegenPlace(temp, instrValue.property), - true - ); - break; - } - case "Identifier": { - value = codegenPlace(temp, instrValue); - break; - } - case "FunctionExpression": { - const id = - instrValue.name !== null ? t.identifier(instrValue.name) : null; - const params = instrValue.params.map((p) => t.identifier(p)); - value = t.functionExpression(id, params, instrValue.body); - break; - } - default: { - assertExhaustive( - instrValue, - `Unexpected instruction value kind '${(instrValue as any).kind}'` - ); - } - } - return value; -} - -function codegenJsxElement( - temp: Temporaries, - place: Place -): - | t.JSXText - | t.JSXExpressionContainer - | t.JSXSpreadChild - | t.JSXElement - | t.JSXFragment { - const value = codegenPlace(temp, place); - switch (value.type) { - case "StringLiteral": { - return t.jsxText(value.value); - } - default: { - return t.jsxExpressionContainer(value); - } - } -} - -export function codegenLVal(lval: LValue): t.LVal { - return convertIdentifier(lval.place.identifier); -} - -function codegenValue( - temp: Temporaries, - value: boolean | number | string | null | undefined -): t.Expression { - if (typeof value === "number") { - return t.numericLiteral(value); - } else if (typeof value === "boolean") { - return t.booleanLiteral(value); - } else if (typeof value === "string") { - return t.stringLiteral(value); - } else if (value === null) { - return t.nullLiteral(); - } else if (value === undefined) { - return t.identifier("undefined"); - } else { - assertExhaustive(value, "Unexpected primitive value kind"); - } -} - -export function codegenPlace(temp: Temporaries, place: Place): t.Expression { - todoInvariant(place.kind === "Identifier", "support scope values"); - let tmp = temp.get(place.identifier.id); - if (tmp != null) { - return tmp; - } - return convertIdentifier(place.identifier); -} - -export function convertIdentifier(identifier: Identifier): t.Identifier { - if (identifier.name !== null) { - return t.identifier(`${identifier.name}`); - } - return t.identifier(`t${identifier.id}`); -} diff --git a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts index 9cd3c19995..aeff29922d 100644 --- a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -8,17 +8,14 @@ import * as t from "@babel/types"; import invariant from "invariant"; import { - codegenInstruction, - codegenInstructionValue, - codegenLabel, - codegenPlace, - convertIdentifier, - createFunctionDeclaration, - Temporaries, -} from "../HIR/Codegen"; -import { + BlockId, + GeneratedSource, Identifier, + IdentifierId, InstructionKind, + InstructionValue, + LValue, + Place, ReactiveBlock, ReactiveFunction, ReactiveInstruction, @@ -26,6 +23,7 @@ import { ReactiveScopeDependency, ReactiveTerminal, ReactiveValueBlock, + SourceLocation, } from "../HIR/HIR"; import { todoInvariant } from "../Utils/todo"; import { assertExhaustive } from "../Utils/utils"; @@ -299,7 +297,7 @@ function codegenTerminal(cx: Context, terminal: ReactiveTerminal): t.Statement { } } -export function codegenInstructionNullable( +function codegenInstructionNullable( cx: Context, instr: ReactiveInstruction, value: t.Expression @@ -390,3 +388,318 @@ function codegenDependency( } return object; } + +function withLoc TNode>( + fn: T +): ( + loc: SourceLocation | null | undefined, + ...args: Parameters +) => ReturnType { + return ( + loc: SourceLocation | null | undefined, + ...args: Parameters + ): ReturnType => { + const node = fn(...args); + if (loc != null && loc != GeneratedSource) { + node.loc = loc; + } + // @ts-ignore + return node; + }; +} + +const createBinaryExpression = withLoc(t.binaryExpression); +const createCallExpression = withLoc(t.callExpression); +const createExpressionStatement = withLoc(t.expressionStatement); +const createFunctionDeclaration = withLoc(t.functionDeclaration); +const createLabelledStatement = withLoc(t.labeledStatement); +const createVariableDeclaration = withLoc(t.variableDeclaration); +const createWhileStatement = withLoc(t.whileStatement); + +type Temporaries = Map; + +function codegenLabel(id: BlockId): string { + return `bb${id}`; +} + +function codegenInstruction( + temp: Temporaries, + instr: ReactiveInstruction, + value: t.Expression +): t.Statement { + if (t.isStatement(value)) { + return value; + } + if (instr.lvalue === null) { + return t.expressionStatement(value); + } + if (instr.lvalue.place.identifier.name === null) { + // temporary + temp.set(instr.lvalue.place.identifier.id, value); + return t.emptyStatement(); + } else { + switch (instr.lvalue.kind) { + case InstructionKind.Const: { + return createVariableDeclaration(instr.loc, "const", [ + t.variableDeclarator(codegenLVal(instr.lvalue), value), + ]); + } + case InstructionKind.Let: { + return createVariableDeclaration(instr.loc, "let", [ + t.variableDeclarator(codegenLVal(instr.lvalue), value), + ]); + } + case InstructionKind.Reassign: { + return createExpressionStatement( + instr.loc, + t.assignmentExpression("=", codegenLVal(instr.lvalue), value) + ); + } + default: { + assertExhaustive( + instr.lvalue.kind, + `Unexpected instruction kind '${instr.lvalue.kind}'` + ); + } + } + } +} + +function codegenInstructionValue( + temp: Temporaries, + instrValue: InstructionValue +): t.Expression { + let value: t.Expression; + switch (instrValue.kind) { + case "ArrayExpression": { + const elements = instrValue.elements.map((element) => + codegenPlace(temp, element) + ); + value = t.arrayExpression(elements); + break; + } + case "BinaryExpression": { + const left = codegenPlace(temp, instrValue.left); + const right = codegenPlace(temp, instrValue.right); + value = createBinaryExpression( + instrValue.loc, + instrValue.operator, + left, + right + ); + break; + } + case "UnaryExpression": { + value = t.unaryExpression( + instrValue.operator as "throw", // todo + codegenPlace(temp, instrValue.value) + ); + break; + } + case "Primitive": { + value = codegenValue(temp, instrValue.value); + break; + } + case "CallExpression": { + const callee = codegenPlace(temp, instrValue.callee); + const args = instrValue.args.map((arg) => codegenPlace(temp, 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)); + value = t.newExpression(callee, args); + break; + } + case "ObjectExpression": { + const properties = []; + if (instrValue.properties !== null) { + for (const [property, value] of instrValue.properties) { + properties.push( + t.objectProperty( + t.stringLiteral(property), + codegenPlace(temp, value) + ) + ); + } + } + value = t.objectExpression(properties); + break; + } + case "JSXText": { + value = t.stringLiteral(instrValue.value); + break; + } + case "JsxExpression": { + const attributes: Array = []; + for (const [prop, value] of instrValue.props) { + attributes.push( + t.jsxAttribute( + t.jsxIdentifier(prop), + t.jsxExpressionContainer(codegenPlace(temp, value)) + ) + ); + } + let tagValue = codegenPlace(temp, instrValue.tag); + let tag: string; + if (tagValue.type === "Identifier") { + tag = tagValue.name; + } else { + invariant( + tagValue.type === "StringLiteral", + "Expected JSX tag to be an identifier or string" + ); + tag = tagValue.value; + } + const children = + instrValue.children !== null + ? instrValue.children.map((child) => codegenJsxElement(temp, child)) + : []; + value = t.jsxElement( + t.jsxOpeningElement( + t.jsxIdentifier(tag), + attributes, + instrValue.children === null + ), + instrValue.children !== null + ? t.jsxClosingElement(t.jsxIdentifier(tag)) + : null, + children, + instrValue.children === null + ); + break; + } + case "JsxFragment": { + value = t.jsxFragment( + t.jsxOpeningFragment(), + t.jsxClosingFragment(), + instrValue.children.map((child) => codegenJsxElement(temp, child)) + ); + break; + } + case "OtherStatement": { + const node = instrValue.node; + if (!t.isExpression(node)) { + return node as any; // TODO handle statements, jsx fragments + } + value = node; + break; + } + case "PropertyStore": { + value = t.assignmentExpression( + "=", + t.memberExpression( + codegenPlace(temp, instrValue.object), + t.identifier(instrValue.property) + ), + codegenPlace(temp, instrValue.value) + ); + break; + } + case "PropertyLoad": { + value = t.memberExpression( + codegenPlace(temp, instrValue.object), + t.identifier(instrValue.property) + ); + break; + } + case "ComputedStore": { + value = t.assignmentExpression( + "=", + t.memberExpression( + codegenPlace(temp, instrValue.object), + codegenPlace(temp, instrValue.property), + true + ), + codegenPlace(temp, instrValue.value) + ); + break; + } + case "ComputedLoad": { + value = t.memberExpression( + codegenPlace(temp, instrValue.object), + codegenPlace(temp, instrValue.property), + true + ); + break; + } + case "Identifier": { + value = codegenPlace(temp, instrValue); + break; + } + case "FunctionExpression": { + const id = + instrValue.name !== null ? t.identifier(instrValue.name) : null; + const params = instrValue.params.map((p) => t.identifier(p)); + value = t.functionExpression(id, params, instrValue.body); + break; + } + default: { + assertExhaustive( + instrValue, + `Unexpected instruction value kind '${(instrValue as any).kind}'` + ); + } + } + return value; +} + +function codegenJsxElement( + temp: Temporaries, + place: Place +): + | t.JSXText + | t.JSXExpressionContainer + | t.JSXSpreadChild + | t.JSXElement + | t.JSXFragment { + const value = codegenPlace(temp, place); + switch (value.type) { + case "StringLiteral": { + return t.jsxText(value.value); + } + default: { + return t.jsxExpressionContainer(value); + } + } +} + +function codegenLVal(lval: LValue): t.LVal { + return convertIdentifier(lval.place.identifier); +} + +function codegenValue( + temp: Temporaries, + value: boolean | number | string | null | undefined +): t.Expression { + if (typeof value === "number") { + return t.numericLiteral(value); + } else if (typeof value === "boolean") { + return t.booleanLiteral(value); + } else if (typeof value === "string") { + return t.stringLiteral(value); + } else if (value === null) { + return t.nullLiteral(); + } else if (value === undefined) { + return t.identifier("undefined"); + } else { + assertExhaustive(value, "Unexpected primitive value kind"); + } +} + +function codegenPlace(temp: Temporaries, place: Place): t.Expression { + todoInvariant(place.kind === "Identifier", "support scope values"); + let tmp = temp.get(place.identifier.id); + if (tmp != null) { + return tmp; + } + return convertIdentifier(place.identifier); +} + +function convertIdentifier(identifier: Identifier): t.Identifier { + if (identifier.name !== null) { + return t.identifier(`${identifier.name}`); + } + return t.identifier(`t${identifier.id}`); +} diff --git a/compiler/forget/src/index.ts b/compiler/forget/src/index.ts index ebff2493c9..d95cfd528b 100644 --- a/compiler/forget/src/index.ts +++ b/compiler/forget/src/index.ts @@ -16,7 +16,6 @@ import traverse, { NodePath } from "@babel/traverse"; import * as t from "@babel/types"; import { compile, run } from "./CompilerPipeline"; import { lower } from "./HIR/BuildHIR"; -import codegen from "./HIR/Codegen"; import { Environment } from "./HIR/HIRBuilder"; import printHIR, { printFunction } from "./HIR/PrintHIR"; import { inferMutableRanges, inferReferenceEffects } from "./Inference"; @@ -56,7 +55,6 @@ function parseFunctions( export const HIR = { buildReactiveFunction, - codegen, codegenReactiveFunction, compile, eliminateRedundantPhi,