• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { readFileSync, writeFileSync } from "fs";
17import * as ts from "typescript";
18import { addVariableToScope } from "./addVariable2Scope";
19import { AssemblyDumper } from "./assemblyDumper";
20import {
21    hasDefaultKeywordModifier, hasExportKeywordModifier, initiateTs2abc, listenChildExit,
22    listenErrorEvent, terminateWritePipe, getRecordTypeFlag
23} from "./base/util";
24import { CmdOptions } from "./cmdOptions";
25import {
26    Compiler
27} from "./compiler";
28import { CompilerStatistics } from "./compilerStatistics";
29import { DebugInfo } from "./debuginfo";
30import { hoisting } from "./hoisting";
31import { LOGD } from "./log";
32import { setModuleNamespaceImports, assignIndexToModuleVariable } from "./ecmaModule";
33import { PandaGen } from "./pandagen";
34import { Pass } from "./pass";
35import { CacheExpander } from "./pass/cacheExpander";
36import { Recorder } from "./recorder";
37import { RegAlloc } from "./regAllocator";
38import {
39    FunctionScope,
40    GlobalScope,
41    ModuleScope,
42    Scope,
43    VariableScope
44} from "./scope";
45import { getClassNameForConstructor } from "./statement/classStatement";
46import { checkDuplicateDeclaration } from "./syntaxChecker";
47import { Ts2Panda } from "./ts2panda";
48import { TypeRecorder } from "./typeRecorder";
49import { LiteralBuffer } from "./base/literal";
50import { findOuterNodeOfParenthesis } from "./expression/parenthesizedExpression";
51import {
52    Label,
53    IRNode
54} from "./irnodes";
55import { LexicalBinder } from "./lexicalBinder";
56
57export class PendingCompilationUnit {
58    constructor(
59        readonly decl: ts.FunctionLikeDeclaration,
60        readonly scope: Scope,
61        readonly internalName: string
62    ) { }
63}
64
65/**
66 * The class which drives the compilation process.
67 * It handles all dependencies and run passes.
68 */
69export class CompilerDriver {
70    static srcNode: ts.SourceFile | undefined = undefined;
71    static isTsFile: boolean = false;
72    private fileName: string;
73    private recordName: string;
74    private passes: Pass[] = [];
75    private compilationUnits: PandaGen[];
76    pendingCompilationUnits: PendingCompilationUnit[];
77    private functionId: number = 1; // 0 reserved for main
78    private funcIdMap: Map<ts.Node, number> = new Map<ts.Node, number>();
79    private statistics: CompilerStatistics | undefined;
80    private needDumpHeader: boolean = true;
81    private ts2abcProcess: any = undefined;
82
83    constructor(fileName: string, recordName: string) {
84        this.fileName = fileName;
85        this.recordName = recordName;
86        // register passes here
87        this.passes = [
88            new CacheExpander(),
89            new RegAlloc()
90        ];
91        this.compilationUnits = [];
92        this.pendingCompilationUnits = [];
93        if (CmdOptions.showHistogramStatistics() || CmdOptions.showHoistingStatistics()) {
94            this.statistics = new CompilerStatistics();
95        }
96    }
97
98    initiateTs2abcChildProcess(args: Array<string>): void {
99        this.ts2abcProcess = initiateTs2abc(args);
100        listenChildExit(this.getTs2abcProcess());
101        listenErrorEvent(this.getTs2abcProcess());
102    }
103
104    getTs2abcProcess(): any {
105        if (this.ts2abcProcess === undefined) {
106            throw new Error("ts2abc hasn't been initiated");
107        }
108        return this.ts2abcProcess;
109    }
110
111    getStatistics(): CompilerStatistics {
112        return this.statistics;
113    }
114
115    setCustomPasses(passes: Pass[]): void {
116        this.passes = passes;
117    }
118
119    addCompilationUnit(decl: ts.FunctionLikeDeclaration, scope: Scope, recorder: Recorder): string {
120        let internalName = this.getFuncInternalName(decl, recorder);
121        this.pendingCompilationUnits.push(
122            new PendingCompilationUnit(decl, scope, internalName)
123        );
124        return internalName;
125    }
126
127    getCompilationUnits(): PandaGen[] {
128        return this.compilationUnits;
129    }
130
131    kind2String(kind: ts.SyntaxKind): string {
132        return ts.SyntaxKind[kind];
133    }
134
135    getASTStatistics(node: ts.Node, statics: number[]): void {
136        node.forEachChild(childNode => {
137            statics[<number>childNode.kind] = statics[<number>childNode.kind] + 1;
138            this.getASTStatistics(childNode, statics);
139        })
140    }
141
142    // sort all function in post order
143    postOrderAnalysis(scope: GlobalScope): VariableScope[] {
144        let spArray: VariableScope[] = [];
145        let stack: VariableScope[] = [];
146
147        stack.push(scope);
148        while (stack.length > 0) {
149            let temp: VariableScope | undefined = stack.pop();
150            if (temp === undefined) {
151                break;
152            }
153            spArray.push(temp);
154
155            for (let childVariableScope of temp.getChildVariableScope()) {
156                stack.push(childVariableScope);
157            }
158        }
159
160        return spArray.reverse();
161    }
162
163    compileForSyntaxCheck(node: ts.SourceFile): void {
164        if (CompilerDriver.isTypeScriptSourceFile(node)) {
165            return;
166        }
167        let recorder = this.compilePrologue(node, false, true);
168        checkDuplicateDeclaration(recorder);
169    }
170
171    compile(node: ts.SourceFile, ts2abcProcess?: any | undefined): void {
172        CompilerDriver.isTsFile = CompilerDriver.isTypeScriptSourceFile(node);
173        if (CmdOptions.showASTStatistics()) {
174            let statics: number[] = new Array(ts.SyntaxKind.Count).fill(0);
175
176            this.getASTStatistics(node, statics);
177            statics.forEach((element, idx) => {
178                if (element > 0) {
179                    LOGD(this.kind2String(idx) + " = " + element);
180                }
181            });
182        }
183        Label.resetGlobalId();
184
185        let recorder = this.compilePrologue(node, true, false);
186        let lexBinder = new LexicalBinder(node, recorder);
187        lexBinder.resolve();
188
189        // initiate ts2abc
190        if (!CmdOptions.isAssemblyMode()) {
191            if (ts2abcProcess) {
192                this.ts2abcProcess = ts2abcProcess;
193            } else {
194                this.initiateTs2abcChildProcess([this.fileName]);
195            }
196            let ts2abcProc = this.getTs2abcProcess();
197
198            try {
199                if (CmdOptions.isMergeAbc()) {
200                    // must keep [dumpRecord] at first
201                    Ts2Panda.dumpRecord(ts2abcProc, this.recordName);
202                }
203                Ts2Panda.dumpCmdOptions(ts2abcProc);
204
205                for (let i = 0; i < this.pendingCompilationUnits.length; i++) {
206                    let unit: PendingCompilationUnit = this.pendingCompilationUnits[i];
207                    this.compileImpl(unit.decl, unit.scope, unit.internalName, recorder);
208                }
209
210                Ts2Panda.dumpStringsArray(ts2abcProc);
211                Ts2Panda.dumpConstantPool(ts2abcProc);
212                Ts2Panda.dumpModuleRecords(ts2abcProc);
213                Ts2Panda.dumpTypeInfoRecord(ts2abcProc, true);
214
215                if (ts2abcProcess) {
216                    Ts2Panda.dumpOutputFileName(ts2abcProc, this.fileName);
217                } else {
218                    terminateWritePipe(ts2abcProc);
219                }
220
221                if (CmdOptions.isEnableDebugLog()) {
222                    let jsonFileName = this.fileName.substring(0, this.fileName.lastIndexOf(".")).concat(".json");
223                    writeFileSync(jsonFileName, Ts2Panda.jsonString);
224                    LOGD("Successfully generate ", `${jsonFileName}`);
225                }
226                if (CmdOptions.isOutputType()) {
227                    let typeFileName = this.fileName.substring(0, this.fileName.lastIndexOf(".")).concat(".txt");
228                    writeFileSync(typeFileName, Ts2Panda.dumpTypeLiteralArrayBuffer());
229                }
230
231                Ts2Panda.clearDumpData();
232            } catch (err) {
233                terminateWritePipe(ts2abcProc);
234                throw err;
235            }
236        } else {
237            for (let i = 0; i < this.pendingCompilationUnits.length; i++) {
238                let unit: PendingCompilationUnit = this.pendingCompilationUnits[i];
239                this.compileImpl(unit.decl, unit.scope, unit.internalName, recorder);
240            }
241        }
242
243        PandaGen.clearLiteralArrayBuffer();
244    }
245
246    dumpInputJsonFileContent(ts2abcProc: any, jsonFileName: string, recordName: string): void {
247        Ts2Panda.dumpRecord(ts2abcProc, recordName);
248        Ts2Panda.dumpCmdOptions(ts2abcProc);
249        Ts2Panda.dumpInputJsonFileContent(ts2abcProc, readFileSync(jsonFileName).toString());
250        Ts2Panda.dumpOutputFileName(ts2abcProc, jsonFileName.substring(0, jsonFileName.lastIndexOf(".")).concat(".abc"));
251    }
252
253    private compileImpl(node: ts.SourceFile | ts.FunctionLikeDeclaration, scope: Scope,
254        internalName: string, recorder: Recorder): void {
255
256        let pandaGen = new PandaGen(internalName, node, this.getParametersCount(node), scope);
257        IRNode.pg = pandaGen;
258
259        if (CmdOptions.needRecordSourceCode() && !ts.isSourceFile(node)) {
260            // souceCode of [ts.sourceFile] will be record in debugInfo later.
261            pandaGen.setSourceCode(node.getText());
262        }
263        // for debug info
264        DebugInfo.addDebugIns(scope, pandaGen, true);
265
266        let compiler = new Compiler(node, pandaGen, this, recorder);
267
268        // because of para vreg, don't change hosting's position
269        hoisting(node, pandaGen, recorder, compiler);
270        setModuleNamespaceImports(compiler, scope, pandaGen);
271        compiler.compile();
272
273        this.passes.forEach((pass) => pass.run(pandaGen));
274
275        // for debug info
276        DebugInfo.addDebugIns(scope, pandaGen, false);
277        DebugInfo.setDebugInfo(pandaGen);
278        DebugInfo.setSourceFileDebugInfo(pandaGen, node);
279
280        if (CmdOptions.isAssemblyMode()) {
281            this.writeBinaryFile(pandaGen);
282        } else {
283            Ts2Panda.dumpPandaGen(pandaGen, this.getTs2abcProcess(), recorder.recordType);
284        }
285
286        if (CmdOptions.showHistogramStatistics()) {
287            this.statistics!.getInsHistogramStatistics(pandaGen);
288        }
289    }
290
291    compileUnitTest(node: ts.SourceFile, literalBufferArray?: Array<LiteralBuffer>): void {
292        CompilerDriver.isTsFile = CompilerDriver.isTypeScriptSourceFile(node);
293        let recorder = this.compilePrologue(node, true, true);
294        let lexBinder = new LexicalBinder(node, recorder);
295        lexBinder.resolve();
296
297        for (let i = 0; i < this.pendingCompilationUnits.length; i++) {
298            let unit: PendingCompilationUnit = this.pendingCompilationUnits[i];
299            this.compileUnitTestImpl(unit.decl, unit.scope, unit.internalName, recorder);
300        }
301        if (literalBufferArray) {
302            PandaGen.getLiteralArrayBuffer().forEach(val => literalBufferArray.push(val));
303        }
304
305        PandaGen.clearLiteralArrayBuffer();
306    }
307
308    private compileUnitTestImpl(node: ts.SourceFile | ts.FunctionLikeDeclaration, scope: Scope,
309        internalName: string, recorder: Recorder): void {
310        let pandaGen = new PandaGen(internalName, node, this.getParametersCount(node), scope);
311        IRNode.pg = pandaGen;
312        if (CmdOptions.needRecordSourceCode() && !ts.isSourceFile(node)) {
313            pandaGen.setSourceCode(node.getText());
314        }
315        let compiler = new Compiler(node, pandaGen, this, recorder);
316
317        hoisting(node, pandaGen, recorder, compiler);
318        setModuleNamespaceImports(compiler, scope, pandaGen);
319        compiler.compile();
320
321        this.passes.forEach((pass) => pass.run(pandaGen));
322
323        this.compilationUnits.push(pandaGen);
324    }
325
326    static isTypeScriptSourceFile(node: ts.SourceFile): boolean {
327        let fileName = node.fileName;
328        if (fileName && fileName.endsWith(".ts")) {
329            return true;
330        } else {
331            return false;
332        }
333    }
334
335    private compilePrologue(node: ts.SourceFile, recordType: boolean, syntaxCheckStatus: boolean): Recorder {
336        let topLevelScope: GlobalScope | ModuleScope;
337        if (CmdOptions.isModules()) {
338            topLevelScope = new ModuleScope(node);
339        } else {
340            topLevelScope = new GlobalScope(node);
341        }
342
343        let enableTypeRecord = getRecordTypeFlag(recordType);
344        this.setTypeInfoBeforeRecord(enableTypeRecord);
345        let recorder = new Recorder(node, topLevelScope, this, enableTypeRecord, CompilerDriver.isTsFile, syntaxCheckStatus);
346        recorder.record();
347        this.setTypeInfoAfterRecord(enableTypeRecord);
348        if (topLevelScope instanceof ModuleScope) {
349            topLevelScope.module().setModuleEnvironment(topLevelScope);
350        }
351        addVariableToScope(recorder, enableTypeRecord);
352        assignIndexToModuleVariable(topLevelScope);
353
354        let postOrderVariableScopes = this.postOrderAnalysis(topLevelScope);
355
356        for (let variableScope of postOrderVariableScopes) {
357            this.addCompilationUnit(<ts.FunctionLikeDeclaration>variableScope.getBindingNode(), variableScope, recorder);
358        }
359
360        return recorder;
361    }
362
363    showStatistics(): void {
364        if (CmdOptions.showHistogramStatistics()) {
365            this.statistics!.printHistogram(false);
366        }
367
368        if (CmdOptions.showHoistingStatistics()) {
369            this.statistics!.printHoistStatistics();
370        }
371    }
372
373    getFuncId(node: ts.SourceFile | ts.FunctionLikeDeclaration | ts.ClassLikeDeclaration): number {
374        if (this.funcIdMap.has(node)) {
375            return this.funcIdMap.get(node)!;
376        }
377
378        if (ts.isSourceFile(node)) {
379            this.funcIdMap.set(node, 0);
380            return 0;
381        }
382
383        let idx = this.functionId++;
384
385        this.funcIdMap.set(node, idx);
386        return idx;
387    }
388
389    getFormatedRecordName(): string {
390        let formatedRecordName: string = '';
391        if (CmdOptions.isMergeAbc()) {
392            formatedRecordName = this.recordName + '.';
393        }
394        return formatedRecordName;
395    }
396
397    /**
398     * Internal name is used to indentify a function in panda file
399     * Runtime uses this name to bind code and a Function object
400     */
401    getFuncInternalName(node: ts.SourceFile | ts.FunctionLikeDeclaration, recorder: Recorder): string {
402        let name: string;
403        if (ts.isSourceFile(node)) {
404            name = "func_main_0";
405        } else if (ts.isConstructorDeclaration(node)) {
406            let classNode = node.parent;
407            return this.getInternalNameForCtor(classNode, node);
408        } else {
409            let funcNode = <ts.FunctionLikeDeclaration>node;
410            name = (<FunctionScope>recorder.getScopeOfNode(funcNode)).getFuncName();
411            if (name === '') {
412                if ((ts.isFunctionDeclaration(node) && hasExportKeywordModifier(node) && hasDefaultKeywordModifier(node)) ||
413                    ts.isExportAssignment(findOuterNodeOfParenthesis(node))) {
414                    return `${this.getFormatedRecordName()}default`;
415                }
416                return `${this.getFormatedRecordName()}#${this.getFuncId(funcNode)}#`;
417            }
418
419            if (name === "func_main_0") {
420                return `${this.getFormatedRecordName()}#${this.getFuncId(funcNode)}#${name}`;
421            }
422
423            let funcNameMap = recorder.getFuncNameMap();
424            if (funcNameMap.has(name)) {
425                let freq = <number>funcNameMap.get(name);
426                if (freq > 1) {
427                    name = `#${this.getFuncId(funcNode)}#${name}`;
428                }
429            } else {
430                throw new Error("the function name is missing from the name map");
431            }
432
433            if (name.lastIndexOf(".") != -1 || name.lastIndexOf("\\") != -1) {
434                name = `#${this.getFuncId(funcNode)}#`;
435            }
436        }
437        return `${this.getFormatedRecordName()}${name}`;
438    }
439
440    getInternalNameForCtor(node: ts.ClassLikeDeclaration, ctor: ts.ConstructorDeclaration): string {
441        let name = getClassNameForConstructor(node);
442        name = `#${this.getFuncId(ctor)}#${name}`;
443        if (name.lastIndexOf(".") != -1) {
444            name = `#${this.getFuncId(ctor)}#`;
445        }
446        return `${this.getFormatedRecordName()}${name}`;
447    }
448
449    writeBinaryFile(pandaGen: PandaGen): void {
450        if (this.needDumpHeader) {
451            AssemblyDumper.dumpHeader();
452            this.needDumpHeader = false;
453        }
454        new AssemblyDumper(pandaGen).dump();
455    }
456
457    private getParametersCount(node: ts.SourceFile | ts.FunctionLikeDeclaration): number {
458        // each function and global scope accepts three parameters - funcObj + newTarget + this.
459        // the runtime passes these to global scope when calls it
460        let parametersCount = 3;
461        if (node.kind === ts.SyntaxKind.SourceFile) {
462            if (CmdOptions.isCommonJs()) {
463                // global scope accepts 5 additional parameters:
464                // "exports", "require", "module", "__filename","__dirname"
465                // when compiled as commonjs
466                parametersCount += 5;
467            }
468            return parametersCount;
469        }
470        let decl = <ts.FunctionLikeDeclaration>node;
471        parametersCount += decl.parameters.length;
472        return parametersCount;
473    }
474
475    private setTypeInfoBeforeRecord(enableTypeRecord: boolean): void {
476        if (enableTypeRecord) {
477            TypeRecorder.createInstance();
478        }
479    }
480
481    private setTypeInfoAfterRecord(enableTypeRecord: boolean): void {
482        if (enableTypeRecord) {
483            TypeRecorder.getInstance().setTypeSummary();
484            if (CmdOptions.enableTypeLog()) {
485                TypeRecorder.getInstance().getLog();
486            }
487        } else {
488            PandaGen.clearLiteralArrayBuffer();
489        }
490
491    }
492}
493