• 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 { writeFileSync } from "fs";
17import * as ts from "typescript";
18import { addVariableToScope } from "./addVariable2Scope";
19import { AssemblyDumper } from "./assemblyDumper";
20import { initiateTs2abc, listenChildExit, listenErrorEvent, terminateWritePipe } from "./base/util";
21import { CmdOptions } from "./cmdOptions";
22import {
23    Compiler
24} from "./compiler";
25import { CompilerStatistics } from "./compilerStatistics";
26import { DebugInfo } from "./debuginfo";
27import { hoisting } from "./hoisting";
28import { LOGD } from "./log";
29import { setExportBinding, setImport } from "./modules";
30import { PandaGen } from "./pandagen";
31import { Pass } from "./pass";
32import { CacheExpander } from "./pass/cacheExpander";
33import { Recorder } from "./recorder";
34import { RegAlloc } from "./regAllocator";
35import {
36    FunctionScope,
37    GlobalScope,
38    ModuleScope,
39    Scope,
40    VariableScope
41} from "./scope";
42import { getClassNameForConstructor } from "./statement/classStatement";
43import { checkDuplicateDeclaration, checkExportEntries } from "./syntaxChecker";
44import { Ts2Panda } from "./ts2panda";
45import { TypeRecorder } from "./typeRecorder";
46import { LiteralBuffer } from "./base/literal";
47
48export class PendingCompilationUnit {
49    constructor(
50        readonly decl: ts.FunctionLikeDeclaration,
51        readonly scope: Scope,
52        readonly internalName: string
53    ) { }
54}
55
56/**
57 * The class which drives the compilation process.
58 * It handles all dependencies and run passes.
59 */
60export class CompilerDriver {
61    static isTsFile: boolean = false;
62    private fileName: string;
63    private passes: Pass[] = [];
64    private compilationUnits: PandaGen[];
65    pendingCompilationUnits: PendingCompilationUnit[];
66    private functionId: number = 1; // 0 reserved for main
67    private funcIdMap: Map<ts.Node, number> = new Map<ts.Node, number>();
68    private statistics: CompilerStatistics | undefined;
69    private needDumpHeader: boolean = true;
70    private ts2abcProcess: any = undefined;
71
72    constructor(fileName: string) {
73        this.fileName = fileName;
74        // register passes here
75        this.passes = [
76            new CacheExpander(),
77            new RegAlloc()
78        ];
79        this.compilationUnits = [];
80        this.pendingCompilationUnits = [];
81        if (CmdOptions.showHistogramStatistics() || CmdOptions.showHoistingStatistics()) {
82            this.statistics = new CompilerStatistics();
83        }
84    }
85
86    initiateTs2abcChildProcess() {
87        this.ts2abcProcess = initiateTs2abc([this.fileName]);
88    }
89
90    getTs2abcProcess(): any {
91        if (this.ts2abcProcess === undefined) {
92            throw new Error("ts2abc hasn't been initiated")
93        }
94        return this.ts2abcProcess;
95    }
96
97    getStatistics() {
98        return this.statistics;
99    }
100
101    setCustomPasses(passes: Pass[]) {
102        this.passes = passes;
103    }
104
105    addCompilationUnit(decl: ts.FunctionLikeDeclaration, scope: Scope, recorder: Recorder): string {
106        let internalName = this.getFuncInternalName(decl, recorder);
107        this.pendingCompilationUnits.push(
108            new PendingCompilationUnit(decl, scope, internalName)
109        );
110        return internalName;
111    }
112
113    getCompilationUnits() {
114        return this.compilationUnits;
115    }
116
117    kind2String(kind: ts.SyntaxKind) {
118        return ts.SyntaxKind[kind];
119    }
120
121    getASTStatistics(node: ts.Node, statics: number[]) {
122        node.forEachChild(childNode => {
123            statics[<number>childNode.kind] = statics[<number>childNode.kind] + 1;
124            this.getASTStatistics(childNode, statics);
125        })
126    }
127
128    // sort all function in post order
129    postOrderAnalysis(scope: GlobalScope): VariableScope[] {
130        let spArray: VariableScope[] = [];
131        let stack: VariableScope[] = [];
132
133        stack.push(scope);
134        while (stack.length > 0) {
135            let temp: VariableScope | undefined = stack.pop();
136            if (temp == undefined) {
137                break;
138            }
139            spArray.push(temp);
140
141            for (let childVariableScope of temp.getChildVariableScope()) {
142                stack.push(childVariableScope);
143            }
144        }
145
146        return spArray.reverse();
147    }
148
149    compileForSyntaxCheck(node: ts.SourceFile): void {
150       let recorder = this.compilePrologue(node, false, true);
151       checkDuplicateDeclaration(recorder);
152       checkExportEntries(recorder);
153    }
154
155    compile(node: ts.SourceFile): void {
156        CompilerDriver.isTsFile = CompilerDriver.isTypeScriptSourceFile(node);
157        if (CmdOptions.showASTStatistics()) {
158            let statics: number[] = new Array(ts.SyntaxKind.Count).fill(0);
159
160            this.getASTStatistics(node, statics);
161            statics.forEach((element, idx) => {
162                if (element > 0) {
163                    LOGD(this.kind2String(idx) + " = " + element);
164                }
165            });
166        }
167
168        let recorder = this.compilePrologue(node, true, false);
169
170        // initiate ts2abc
171        if (!CmdOptions.isAssemblyMode()) {
172            this.initiateTs2abcChildProcess();
173            let ts2abcProc = this.getTs2abcProcess();
174            listenChildExit(ts2abcProc);
175            listenErrorEvent(ts2abcProc);
176
177            try {
178                Ts2Panda.dumpCmdOptions(ts2abcProc);
179
180                for (let i = 0; i < this.pendingCompilationUnits.length; i++) {
181                    let unit: PendingCompilationUnit = this.pendingCompilationUnits[i];
182                    this.compileImpl(unit.decl, unit.scope, unit.internalName, recorder);
183                }
184
185                Ts2Panda.dumpStringsArray(ts2abcProc);
186                Ts2Panda.dumpConstantPool(ts2abcProc);
187
188                terminateWritePipe(ts2abcProc);
189                if (CmdOptions.isEnableDebugLog()) {
190                    let jsonFileName = this.fileName.substring(0, this.fileName.lastIndexOf(".")).concat(".json");
191                    writeFileSync(jsonFileName, Ts2Panda.jsonString);
192                    LOGD("Successfully generate ", `${jsonFileName}`);
193                }
194                if (CmdOptions.isOutputType()) {
195                    let typeFileName = this.fileName.substring(0, this.fileName.lastIndexOf(".")).concat(".txt");
196                    writeFileSync(typeFileName, Ts2Panda.dumpTypeLiteralArrayBuffer());
197                }
198
199                Ts2Panda.clearDumpData();
200            } catch (err) {
201                terminateWritePipe(ts2abcProc);
202                throw err;
203            }
204        } else {
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
211        PandaGen.clearLiteralArrayBuffer();
212    }
213
214    private compileImpl(node: ts.SourceFile | ts.FunctionLikeDeclaration, scope: Scope,
215        internalName: string, recorder: Recorder): void {
216        let pandaGen = new PandaGen(internalName, this.getParametersCount(node), scope);
217        // for debug info
218        DebugInfo.addDebugIns(scope, pandaGen, true);
219
220        let compiler = new Compiler(node, pandaGen, this, recorder);
221
222        if (CmdOptions.isModules() && ts.isSourceFile(node) && scope instanceof ModuleScope) {
223            setImport(recorder.getImportStmts(), scope, pandaGen);
224            setExportBinding(recorder.getExportStmts(), scope, pandaGen);
225        }
226
227        // because of para vreg, don't change hosting's position
228        hoisting(node, pandaGen, recorder, compiler);
229        compiler.compile();
230
231        this.passes.forEach((pass) => pass.run(pandaGen));
232
233        // for debug info
234        DebugInfo.addDebugIns(scope, pandaGen, false);
235        DebugInfo.setDebugInfo(pandaGen);
236        DebugInfo.setSourceFileDebugInfo(pandaGen, node);
237
238        if (CmdOptions.isAssemblyMode()) {
239            this.writeBinaryFile(pandaGen);
240        } else {
241            Ts2Panda.dumpPandaGen(pandaGen, this.getTs2abcProcess(), recorder.recordType);
242        }
243
244        if (CmdOptions.showHistogramStatistics()) {
245            this.statistics!.getInsHistogramStatistics(pandaGen);
246        }
247    }
248
249    compileUnitTest(node: ts.SourceFile, literalBufferArray?: Array<LiteralBuffer>): void {
250        CompilerDriver.isTsFile = CompilerDriver.isTypeScriptSourceFile(node);
251        let recorder = this.compilePrologue(node, true, true);
252
253        for (let i = 0; i < this.pendingCompilationUnits.length; i++) {
254            let unit: PendingCompilationUnit = this.pendingCompilationUnits[i];
255            this.compileUnitTestImpl(unit.decl, unit.scope, unit.internalName, recorder);
256        }
257        if (literalBufferArray) {
258            PandaGen.getLiteralArrayBuffer().forEach(val => literalBufferArray.push(val));
259        }
260
261        PandaGen.clearLiteralArrayBuffer();
262    }
263
264    private compileUnitTestImpl(node: ts.SourceFile | ts.FunctionLikeDeclaration, scope: Scope,
265        internalName: string, recorder: Recorder) {
266        let pandaGen = new PandaGen(internalName, this.getParametersCount(node), scope);
267        let compiler = new Compiler(node, pandaGen, this, recorder);
268
269        if (CmdOptions.isModules() && ts.isSourceFile(node) && scope instanceof ModuleScope) {
270            setImport(recorder.getImportStmts(), scope, pandaGen);
271            setExportBinding(recorder.getExportStmts(), scope, pandaGen);
272        }
273
274        hoisting(node, pandaGen, recorder, compiler);
275        compiler.compile();
276
277        this.passes.forEach((pass) => pass.run(pandaGen));
278
279        this.compilationUnits.push(pandaGen);
280    }
281
282    static isTypeScriptSourceFile(node: ts.SourceFile) {
283        let fileName = node.fileName;
284        if (fileName && fileName.endsWith(".ts")) {
285            return true;
286        } else {
287            return false;
288        }
289    }
290
291    private compilePrologue(node: ts.SourceFile, recordType: boolean, syntaxCheckStatus: boolean) {
292        let topLevelScope: GlobalScope | ModuleScope;
293        if (CmdOptions.isModules()) {
294            topLevelScope = new ModuleScope(node);
295        } else {
296            topLevelScope = new GlobalScope(node);
297        }
298
299        let enableTypeRecord = recordType && CmdOptions.needRecordType() && CompilerDriver.isTsFile;
300        if (enableTypeRecord) {
301            TypeRecorder.createInstance();
302        }
303        let recorder = new Recorder(node, topLevelScope, this, enableTypeRecord, CompilerDriver.isTsFile, syntaxCheckStatus);
304        recorder.record();
305
306        addVariableToScope(recorder, enableTypeRecord);
307        let postOrderVariableScopes = this.postOrderAnalysis(topLevelScope);
308
309        for (let variableScope of postOrderVariableScopes) {
310            this.addCompilationUnit(<ts.FunctionLikeDeclaration>variableScope.getBindingNode(), variableScope, recorder);
311        }
312
313        return recorder;
314    }
315
316    showStatistics(): void {
317        if (CmdOptions.showHistogramStatistics()) {
318            this.statistics!.printHistogram(false);
319        }
320
321        if (CmdOptions.showHoistingStatistics()) {
322            this.statistics!.printHoistStatistics();
323        }
324    }
325
326    getFuncId(node: ts.SourceFile | ts.FunctionLikeDeclaration | ts.ClassLikeDeclaration): number {
327        if (this.funcIdMap.has(node)) {
328            return this.funcIdMap.get(node)!;
329        }
330
331        if (ts.isSourceFile(node)) {
332            this.funcIdMap.set(node, 0);
333            return 0;
334        }
335
336        let idx = this.functionId++;
337
338        this.funcIdMap.set(node, idx);
339        return idx;
340    }
341
342    /**
343     * Internal name is used to indentify a function in panda file
344     * Runtime uses this name to bind code and a Function object
345     */
346    getFuncInternalName(node: ts.SourceFile | ts.FunctionLikeDeclaration, recorder: Recorder): string {
347        let name: string;
348        if (ts.isSourceFile(node)) {
349            name = "func_main_0";
350        } else if (ts.isConstructorDeclaration(node)) {
351            let classNode = node.parent;
352            name = this.getInternalNameForCtor(classNode, node);
353        } else {
354            let funcNode = <ts.FunctionLikeDeclaration>node;
355            name = (<FunctionScope>recorder.getScopeOfNode(funcNode)).getFuncName();
356            if (name == '') {
357                return `#${this.getFuncId(funcNode)}#`;
358            }
359
360            if (name == "func_main_0") {
361                return `#${this.getFuncId(funcNode)}#${name}`;
362            }
363
364            let funcNameMap = recorder.getFuncNameMap();
365            if (funcNameMap.has(name)) {
366                let freq = <number>funcNameMap.get(name);
367                if (freq > 1) {
368                    name = `#${this.getFuncId(funcNode)}#${name}`;
369                }
370            } else {
371                throw new Error("the function name is missing from the name map");
372            }
373
374            if (name.lastIndexOf(".") != -1 || name.lastIndexOf("\\") != -1) {
375                name = `#${this.getFuncId(funcNode)}#`
376            }
377        }
378        return name;
379    }
380
381    getInternalNameForCtor(node: ts.ClassLikeDeclaration, ctor: ts.ConstructorDeclaration) {
382        let name = getClassNameForConstructor(node);
383        name = `#${this.getFuncId(ctor)}#${name}`
384        if (name.lastIndexOf(".") != -1) {
385            name = `#${this.getFuncId(ctor)}#`
386        }
387        return name;
388    }
389
390    writeBinaryFile(pandaGen: PandaGen) {
391        if (this.needDumpHeader) {
392            AssemblyDumper.dumpHeader();
393            this.needDumpHeader = false;
394        }
395        new AssemblyDumper(pandaGen).dump();
396    }
397
398    private getParametersCount(node: ts.SourceFile | ts.FunctionLikeDeclaration): number {
399        // each function and global scope accepts three parameters - funcObj + newTarget + this.
400        // the runtime passes these to global scope when calls it
401        let parametersCount = 3;
402        if (node.kind == ts.SyntaxKind.SourceFile) {
403            return parametersCount;
404        }
405        let decl = <ts.FunctionLikeDeclaration>node;
406        parametersCount += decl.parameters.length;
407        return parametersCount;
408    }
409}
410