1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
-import * as t from "@babel/types";
9
-import { invariant } from "../Utils/CompilerError";
10
-import { todoInvariant } from "../Utils/todo";
11
-import { assertExhaustive } from "../Utils/utils";
12
-import {
13
- BlockId,
14
- GeneratedSource,
15
- HIRFunction,
16
- Identifier,
17
- IdentifierId,
18
- Instruction,
19
- InstructionId,
20
- InstructionKind,
21
- InstructionValue,
22
- LValue,
23
- Place,
24
- ReactiveInstruction,
25
- SourceLocation,
26
-} from "./HIR";
27
-import { BlockTerminal, Visitor, visitTree } from "./HIRTreeVisitor";
28
-
29
-function withLoc<TNode extends t.Node, T extends (...args: any[]) => TNode>(
30
- fn: T
31
-): (
32
- loc: SourceLocation | null | undefined,
33
- ...args: Parameters<T>
34
-) => ReturnType<T> {
35
- return (
36
- loc: SourceLocation | null | undefined,
37
- ...args: Parameters<T>
38
- ): ReturnType<T> => {
39
- const node = fn(...args);
40
- if (loc != null && loc != GeneratedSource) {
41
- node.loc = loc;
42
- }
43
- // @ts-ignore
44
- return node;
45
- };
46
-}
47
-
48
-export const createBinaryExpression = withLoc(t.binaryExpression);
49
-export const createCallExpression = withLoc(t.callExpression);
50
-export const createExpressionStatement = withLoc(t.expressionStatement);
51
-export const createFunctionDeclaration = withLoc(t.functionDeclaration);
52
-export const createLabelledStatement = withLoc(t.labeledStatement);
53
-export const createVariableDeclaration = withLoc(t.variableDeclaration);
54
-export const createWhileStatement = withLoc(t.whileStatement);
55
-
56
-/**
57
- * Converts HIR into Babel nodes, which can then be printed into source text.
58
- * Note that converting source to HIR and back is not guaranteed to produce
59
- * identicl source text: instead, it is guaranteed to produce semantically
60
- * equivalent JavaScript. Where possible the original shape of the source
61
- * code is preserved. Notably, temporary variables are only introduced
62
- * where strictly necessary such that in general the only variable declarations
63
- * that appear in the output are those that appeared in the input.
64
- *
65
- * However, it is expected that minor changes may occur, such as splitting
66
- * multiple variable declarations into one, converting `else` branches
67
- * into fallthrough branches, etc.
68
- *
69
- * Also, the *semantics* of variable resolution are preserved, but the exact
70
- * original block structure is *not* guaranteed to be preserved. As such,
71
- * variable names in the output may have a suffix attached to distinguish them.
72
- * It is expected that the output will be passed through a minifier which can
73
- * rename variables to reduce code size. In theory minification could be
74
- * performed as an HIR optimization pass, that is left todo for the time being.
75
- */
76
-export default function codegen(fn: HIRFunction): t.Function {
77
- const visitor = new CodegenVisitor();
78
- const body = visitTree(fn, visitor);
79
- invariant(t.isBlockStatement(body), "Expected a block statement");
80
- const params = fn.params.map((param) => convertIdentifier(param.identifier));
81
- return createFunctionDeclaration(
82
- fn.loc,
83
- fn.id !== null ? convertIdentifier(fn.id) : null,
84
- params,
85
- body,
86
- fn.generator,
87
- fn.async
88
- );
89
-}
90
-
91
-export type Temporaries = Map<IdentifierId, t.Expression>;
92
-
93
-class CodegenVisitor
94
- implements
95
- Visitor<
96
- Array<t.Statement>,
97
- t.Statement,
98
- Array<t.Statement>,
99
- Array<t.Statement>,
100
- t.Expression,
101
- t.Statement,
102
- t.SwitchCase
103
- >
104
-{
105
- depth: number = 0;
106
- temp: Map<IdentifierId, t.Expression> = new Map();
107
-
108
- enterBlock(): t.Statement[] {
109
- this.depth++;
110
- return [];
111
- }
112
- appendBlock(
113
- block: t.Statement[],
114
- item: t.Statement,
115
- blockId?: BlockId | undefined
116
- ): void {
117
- if (item.type === "EmptyStatement") {
118
- return;
119
- }
120
- if (blockId !== undefined) {
121
- block.push(
122
- createLabelledStatement(
123
- item.loc,
124
- t.identifier(codegenLabel(blockId)),
125
- item
126
- )
127
- );
128
- } else {
129
- block.push(item);
130
- }
131
- }
132
- leaveBlock(block: t.Statement[]): t.Statement {
133
- this.depth--;
134
- return t.blockStatement(block);
135
- }
136
- enterValueBlock(): t.Statement[] {
137
- return this.enterBlock();
138
- }
139
- appendValueBlock(block: t.Statement[], item: t.Statement): void {
140
- this.appendBlock(block, item);
141
- }
142
- leaveValueBlock(
143
- block: t.Statement[],
144
- place: { value: t.Expression; id: InstructionId } | null
145
- ): t.Expression {
146
- this.depth--;
147
- if (block.length === 0) {
148
- invariant(place !== null, "Unexpected empty value block");
149
- return place.value;
150
- }
151
- const expressions = block.map((stmt) => {
152
- switch (stmt.type) {
153
- case "ExpressionStatement":
154
- return stmt.expression;
155
- default:
156
- todoInvariant(
157
- false,
158
- `Handle conversion of ${stmt.type} to expression`
159
- );
160
- }
161
- });
162
- if (place !== null) {
163
- expressions.push(place.value);
164
- }
165
- return t.sequenceExpression(expressions);
166
- }
167
-
168
- enterInitBlock(block: t.Statement[]): t.Statement[] {
169
- return this.enterBlock();
170
- }
171
-
172
- appendInitBlock(block: t.Statement[], item: t.Statement): void {
173
- this.appendBlock(block, item);
174
- }
175
- leaveInitBlock(block: t.Statement[]): t.Statement[] {
176
- switch (block.length) {
177
- case 0: {
178
- return [t.emptyStatement()];
179
- }
180
- case 1: {
181
- return [block[0]];
182
- }
183
- default: {
184
- return [t.blockStatement(block)];
185
- }
186
- }
187
- }
188
-
189
- visitValue(value: InstructionValue): t.Expression {
190
- return codegenInstructionValue(this.temp, value);
191
- }
192
- visitInstruction(instr: Instruction, value: t.Expression): t.Statement {
193
- return codegenInstruction(this.temp, instr, value);
194
- }
195
- visitTerminalId(id: InstructionId): void {}
196
- visitImplicitTerminal(): t.Statement | null {
197
- return null;
198
- }
199
- visitTerminal(
200
- terminal: BlockTerminal<
201
- t.Statement[],
202
- t.Expression,
203
- t.Statement,
204
- t.SwitchCase
205
- >
206
- ): t.Statement {
207
- switch (terminal.kind) {
208
- case "break": {
209
- if (terminal.label) {
210
- return t.breakStatement(t.identifier(codegenLabel(terminal.label)));
211
- } else {
212
- return t.breakStatement();
213
- }
214
- }
215
- case "continue": {
216
- if (terminal.label) {
217
- return t.continueStatement(
218
- t.identifier(codegenLabel(terminal.label))
219
- );
220
- } else {
221
- return t.continueStatement();
222
- }
223
- }
224
- case "if": {
225
- return t.ifStatement(
226
- terminal.test,
227
- terminal.consequent,
228
- terminal.alternate
229
- );
230
- }
231
- case "switch": {
232
- return t.switchStatement(terminal.test, terminal.cases);
233
- }
234
- case "while": {
235
- return createWhileStatement(terminal.loc, terminal.test, terminal.loop);
236
- }
237
- case "for": {
238
- const initBlock = terminal.init;
239
- invariant(
240
- initBlock.length === 1,
241
- "Expected for init to be a single expression or statement"
242
- );
243
- const initStatement = initBlock[0]!;
244
- let init;
245
- if (initStatement.type === "VariableDeclaration") {
246
- init = initStatement;
247
- } else if (initStatement.type === "ExpressionStatement") {
248
- init = initStatement.expression;
249
- } else {
250
- invariant(
251
- false,
252
- `Expected 'for' init block to contain variable declaration or an expression, got '${initStatement.type}'.`
253
- );
254
- }
255
- return t.forStatement(
256
- init,
257
- terminal.test,
258
- terminal.update,
259
- terminal.loop
260
- );
261
- }
262
- case "return": {
263
- const createReturnStatement = withLoc(t.returnStatement);
264
- if (terminal.value !== null) {
265
- return createReturnStatement(terminal.loc, terminal.value);
266
- } else if (this.depth === 1) {
267
- // A return at the top-level of a function must be the last instruction,
268
- // and functions implicitly return after the last instruction of the top-level.
269
- // Elide the return.
270
- return t.emptyStatement();
271
- } else {
272
- return createReturnStatement(terminal.loc);
273
- }
274
- }
275
- case "throw": {
276
- return t.throwStatement(terminal.value);
277
- }
278
- default: {
279
- assertExhaustive(
280
- terminal,
281
- `Unexpected terminal kind '${(terminal as any).kind}'`
282
- );
283
- }
284
- }
285
- }
286
- visitCase(test: t.Expression | null, block: t.Statement): t.SwitchCase {
287
- return t.switchCase(test, [block]);
288
- }
289
-}
290
-
291
-export function codegenLabel(id: BlockId): string {
292
- return `bb${id}`;
293
-}
294
-
295
-export function codegenInstruction(
296
- temp: Temporaries,
297
- instr: ReactiveInstruction,
298
- value: t.Expression
299
-): t.Statement {
300
- if (t.isStatement(value)) {
301
- return value;
302
- }
303
- if (instr.lvalue === null) {
304
- return t.expressionStatement(value);
305
- }
306
- if (instr.lvalue.place.identifier.name === null) {
307
- // temporary
308
- temp.set(instr.lvalue.place.identifier.id, value);
309
- return t.emptyStatement();
310
- } else {
311
- switch (instr.lvalue.kind) {
312
- case InstructionKind.Const: {
313
- return createVariableDeclaration(instr.loc, "const", [
314
- t.variableDeclarator(codegenLVal(instr.lvalue), value),
315
- ]);
316
- }
317
- case InstructionKind.Let: {
318
- return createVariableDeclaration(instr.loc, "let", [
319
- t.variableDeclarator(codegenLVal(instr.lvalue), value),
320
- ]);
321
- }
322
- case InstructionKind.Reassign: {
323
- return createExpressionStatement(
324
- instr.loc,
325
- t.assignmentExpression("=", codegenLVal(instr.lvalue), value)
326
- );
327
- }
328
- default: {
329
- assertExhaustive(
330
- instr.lvalue.kind,
331
- `Unexpected instruction kind '${instr.lvalue.kind}'`
332
- );
333
- }
334
- }
335
- }
336
-}
337
-
338
-export function codegenInstructionValue(
339
- temp: Temporaries,
340
- instrValue: InstructionValue
341
-): t.Expression {
342
- let value: t.Expression;
343
- switch (instrValue.kind) {
344
- case "ArrayExpression": {
345
- const elements = instrValue.elements.map((element) =>
346
- codegenPlace(temp, element)
347
- );
348
- value = t.arrayExpression(elements);
349
- break;
350
- }
351
- case "BinaryExpression": {
352
- const left = codegenPlace(temp, instrValue.left);
353
- const right = codegenPlace(temp, instrValue.right);
354
- value = createBinaryExpression(
355
- instrValue.loc,
356
- instrValue.operator,
357
- left,
358
- right
359
- );
360
- break;
361
- }
362
- case "UnaryExpression": {
363
- value = t.unaryExpression(
364
- instrValue.operator as "throw", // todo
365
- codegenPlace(temp, instrValue.value)
366
- );
367
- break;
368
- }
369
- case "Primitive": {
370
- value = codegenValue(temp, instrValue.value);
371
- break;
372
- }
373
- case "CallExpression": {
374
- const callee = codegenPlace(temp, instrValue.callee);
375
- const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
376
- value = createCallExpression(instrValue.loc, callee, args);
377
- break;
378
- }
379
- case "NewExpression": {
380
- const callee = codegenPlace(temp, instrValue.callee);
381
- const args = instrValue.args.map((arg) => codegenPlace(temp, arg));
382
- value = t.newExpression(callee, args);
383
- break;
384
- }
385
- case "ObjectExpression": {
386
- const properties = [];
387
- if (instrValue.properties !== null) {
388
- for (const [property, value] of instrValue.properties) {
389
- properties.push(
390
- t.objectProperty(
391
- t.stringLiteral(property),
392
- codegenPlace(temp, value)
393
- )
394
- );
395
- }
396
- }
397
- value = t.objectExpression(properties);
398
- break;
399
- }
400
- case "JSXText": {
401
- value = t.stringLiteral(instrValue.value);
402
- break;
403
- }
404
- case "JsxExpression": {
405
- const attributes: Array<t.JSXAttribute> = [];
406
- for (const [prop, value] of instrValue.props) {
407
- attributes.push(
408
- t.jsxAttribute(
409
- t.jsxIdentifier(prop),
410
- t.jsxExpressionContainer(codegenPlace(temp, value))
411
- )
412
- );
413
- }
414
- let tagValue = codegenPlace(temp, instrValue.tag);
415
- let tag: string;
416
- if (tagValue.type === "Identifier") {
417
- tag = tagValue.name;
418
- } else {
419
- invariant(
420
- tagValue.type === "StringLiteral",
421
- "Expected JSX tag to be an identifier or string"
422
- );
423
- tag = tagValue.value;
424
- }
425
- const children =
426
- instrValue.children !== null
427
- ? instrValue.children.map((child) => codegenJsxElement(temp, child))
428
- : [];
429
- value = t.jsxElement(
430
- t.jsxOpeningElement(
431
- t.jsxIdentifier(tag),
432
- attributes,
433
- instrValue.children === null
434
- ),
435
- instrValue.children !== null
436
- ? t.jsxClosingElement(t.jsxIdentifier(tag))
437
- : null,
438
- children,
439
- instrValue.children === null
440
- );
441
- break;
442
- }
443
- case "JsxFragment": {
444
- value = t.jsxFragment(
445
- t.jsxOpeningFragment(),
446
- t.jsxClosingFragment(),
447
- instrValue.children.map((child) => codegenJsxElement(temp, child))
448
- );
449
- break;
450
- }
451
- case "OtherStatement": {
452
- const node = instrValue.node;
453
- if (!t.isExpression(node)) {
454
- return node as any; // TODO handle statements, jsx fragments
455
- }
456
- value = node;
457
- break;
458
- }
459
- case "PropertyStore": {
460
- value = t.assignmentExpression(
461
- "=",
462
- t.memberExpression(
463
- codegenPlace(temp, instrValue.object),
464
- t.identifier(instrValue.property)
465
- ),
466
- codegenPlace(temp, instrValue.value)
467
- );
468
- break;
469
- }
470
- case "PropertyLoad": {
471
- value = t.memberExpression(
472
- codegenPlace(temp, instrValue.object),
473
- t.identifier(instrValue.property)
474
- );
475
- break;
476
- }
477
- case "ComputedStore": {
478
- value = t.assignmentExpression(
479
- "=",
480
- t.memberExpression(
481
- codegenPlace(temp, instrValue.object),
482
- codegenPlace(temp, instrValue.property),
483
- true
484
- ),
485
- codegenPlace(temp, instrValue.value)
486
- );
487
- break;
488
- }
489
- case "ComputedLoad": {
490
- value = t.memberExpression(
491
- codegenPlace(temp, instrValue.object),
492
- codegenPlace(temp, instrValue.property),
493
- true
494
- );
495
- break;
496
- }
497
- case "Identifier": {
498
- value = codegenPlace(temp, instrValue);
499
- break;
500
- }
501
- case "FunctionExpression": {
502
- const id =
503
- instrValue.name !== null ? t.identifier(instrValue.name) : null;
504
- const params = instrValue.params.map((p) => t.identifier(p));
505
- value = t.functionExpression(id, params, instrValue.body);
506
- break;
507
- }
508
- default: {
509
- assertExhaustive(
510
- instrValue,
511
- `Unexpected instruction value kind '${(instrValue as any).kind}'`
512
- );
513
- }
514
- }
515
- return value;
516
-}
517
-
518
-function codegenJsxElement(
519
- temp: Temporaries,
520
- place: Place
521
-):
522
- | t.JSXText
523
- | t.JSXExpressionContainer
524
- | t.JSXSpreadChild
525
- | t.JSXElement
526
- | t.JSXFragment {
527
- const value = codegenPlace(temp, place);
528
- switch (value.type) {
529
- case "StringLiteral": {
530
- return t.jsxText(value.value);
531
- }
532
- default: {
533
- return t.jsxExpressionContainer(value);
534
- }
535
- }
536
-}
537
-
538
-export function codegenLVal(lval: LValue): t.LVal {
539
- return convertIdentifier(lval.place.identifier);
540
-}
541
-
542
-function codegenValue(
543
- temp: Temporaries,
544
- value: boolean | number | string | null | undefined
545
-): t.Expression {
546
- if (typeof value === "number") {
547
- return t.numericLiteral(value);
548
- } else if (typeof value === "boolean") {
549
- return t.booleanLiteral(value);
550
- } else if (typeof value === "string") {
551
- return t.stringLiteral(value);
552
- } else if (value === null) {
553
- return t.nullLiteral();
554
- } else if (value === undefined) {
555
- return t.identifier("undefined");
556
- } else {
557
- assertExhaustive(value, "Unexpected primitive value kind");
558
- }
559
-}
560
-
561
-export function codegenPlace(temp: Temporaries, place: Place): t.Expression {
562
- todoInvariant(place.kind === "Identifier", "support scope values");
563
- let tmp = temp.get(place.identifier.id);
564
- if (tmp != null) {
565
- return tmp;
566
- }
567
- return convertIdentifier(place.identifier);
568
-}
569
-
570
-export function convertIdentifier(identifier: Identifier): t.Identifier {
571
- if (identifier.name !== null) {
572
- return t.identifier(`${identifier.name}`);
573
- }
574
- return t.identifier(`t${identifier.id}`);
575
-}