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