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