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
16 #include "compileQueue.h"
17
18 #include "utils/timers.h"
19
20 #include <compiler/core/compilerContext.h>
21 #include <compiler/core/emitter/emitter.h>
22 #include <compiler/core/function.h>
23 #include <compiler/core/pandagen.h>
24 #include <protobufSnapshotGenerator.h>
25 #include <util/commonUtil.h>
26
27 namespace panda::es2panda::compiler {
28
29 std::mutex CompileFileJob::globalMutex_;
30 std::mutex CompileAbcClassQueue::globalMutex_;
31
Run()32 void CompileFunctionJob::Run()
33 {
34 std::unique_lock<std::mutex> lock(m_);
35 cond_.wait(lock, [this] { return dependencies_ == 0; });
36
37 ArenaAllocator allocator(SpaceType::SPACE_TYPE_COMPILER, nullptr, true);
38 PandaGen pg(&allocator, context_, scope_);
39
40 Function::Compile(&pg);
41
42 FunctionEmitter funcEmitter(&allocator, &pg);
43 funcEmitter.Generate(context_->PatchFixHelper());
44
45 context_->GetEmitter()->AddFunction(&funcEmitter, context_);
46
47 for (auto *dependant : dependants_) {
48 dependant->Signal();
49 }
50 }
51
Run()52 void CompileModuleRecordJob::Run()
53 {
54 std::unique_lock<std::mutex> lock(m_);
55 cond_.wait(lock, [this] { return dependencies_ == 0; });
56
57 bool hasLazyImport = context_->Binder()->Program()->ModuleRecord()->HasLazyImport();
58 ModuleRecordEmitter moduleEmitter(context_->Binder()->Program()->ModuleRecord(), context_->NewLiteralIndex(),
59 hasLazyImport ? context_->NewLiteralIndex() : -1);
60 moduleEmitter.Generate();
61
62 context_->GetEmitter()->AddSourceTextModuleRecord(&moduleEmitter, context_);
63
64 for (auto *dependant : dependants_) {
65 dependant->Signal();
66 }
67 }
68
RetrieveProgramFromCacheFiles(const std::string & buffer,bool isAbcFile)69 bool CompileFileJob::RetrieveProgramFromCacheFiles(const std::string &buffer, bool isAbcFile)
70 {
71 if (options_->requireGlobalOptimization) {
72 return false;
73 }
74 auto cacheFileIter = options_->cacheFiles.find(src_->fileName);
75 // Disable the use of file caching when cross-program optimization is required, to prevent cached files from
76 // not being invalidated when their dependencies change, or from not being reanalyzed when their dependents
77 // are updated
78 if (cacheFileIter != options_->cacheFiles.end()) {
79 // cache is invalid when any one of source file infos being changed
80 auto bufToHash = buffer + src_->fileName + src_->recordName + src_->sourcefile + src_->pkgName;
81 ArenaAllocator allocator(SpaceType::SPACE_TYPE_COMPILER, nullptr, true);
82 if (!isAbcFile) {
83 src_->hash = GetHash32String(reinterpret_cast<const uint8_t *>(bufToHash.c_str()));
84 auto *cacheProgramInfo = proto::ProtobufSnapshotGenerator::GetCacheContext(cacheFileIter->second,
85 &allocator);
86 if (cacheProgramInfo != nullptr && cacheProgramInfo->hashCode == src_->hash) {
87 std::unique_lock<std::mutex> lock(globalMutex_);
88 auto *cache = allocator_->New<util::ProgramCache>(src_->hash, std::move(cacheProgramInfo->program));
89 progsInfo_.insert({src_->fileName, cache});
90 return true;
91 }
92 } else {
93 std::map<std::string, PkgInfo> updateVersionInfo =
94 options_->compileContextInfo.updateVersionInfo[src_->pkgName];
95 auto abcBufToHash = bufToHash;
96 for (auto &[pkgName, pkgInfo]: updateVersionInfo) {
97 /**
98 * When the bytecode har dependency package version changes, it needs to be recompiled, so the hash
99 * value needs to change
100 */
101 abcBufToHash = abcBufToHash + pkgName + ":" + pkgInfo.version;
102 }
103 // An ABC file starts with '\0', but the 'GetHash32String' method does not support this format.
104 src_->hash = GetHash32(reinterpret_cast<const uint8_t *>(abcBufToHash.c_str()), abcBufToHash.size());
105 auto *cacheAbcProgramsInfo = proto::ProtobufSnapshotGenerator::GetAbcInputCacheContext(
106 cacheFileIter->second, &allocator);
107 if (cacheAbcProgramsInfo != nullptr && cacheAbcProgramsInfo->hashCode == src_->hash) {
108 InsertAbcCachePrograms(src_->hash, cacheAbcProgramsInfo->programsCache);
109 return true;
110 }
111 }
112 }
113 return false;
114 }
115
InsertAbcCachePrograms(uint32_t hashCode,std::map<std::string,panda::es2panda::util::ProgramCache * > & abcProgramsInfo)116 void CompileFileJob::InsertAbcCachePrograms(uint32_t hashCode,
117 std::map<std::string, panda::es2panda::util::ProgramCache *> &abcProgramsInfo)
118 {
119 std::unique_lock<std::mutex> lock(globalMutex_);
120 Compiler::SetExpectedProgsCount(Compiler::GetExpectedProgsCount() + abcProgramsInfo.size() - 1);
121 for (auto pair : abcProgramsInfo) {
122 ASSERT(progsInfo_.find(pair.first) == progsInfo_.end());
123 pair.second->program.isGeneratedFromMergedAbc = true;
124 auto *cache = allocator_->New<util::ProgramCache>(hashCode, std::move(pair.second->program), false);
125 progsInfo_.insert({pair.first, cache});
126 }
127 }
128
Run()129 void CompileFileJob::Run()
130 {
131 std::stringstream ss;
132 std::string buffer;
133 panda::Timer::timerStart(panda::EVENT_READ_INPUT_AND_CACHE, src_->fileName);
134 if (!src_->fileName.empty()) {
135 if (!util::Helpers::ReadFileToBuffer(src_->fileName, ss)) {
136 return;
137 }
138 buffer = ss.str();
139 src_->source = buffer;
140 if (RetrieveProgramFromCacheFiles(buffer, !src_->isSourceMode)) {
141 panda::Timer::timerEnd(panda::EVENT_READ_INPUT_AND_CACHE, src_->fileName);
142 return;
143 }
144 }
145 panda::Timer::timerEnd(panda::EVENT_READ_INPUT_AND_CACHE, src_->fileName);
146
147 CompileProgram();
148 }
149
CompileProgram()150 void CompileFileJob::CompileProgram()
151 {
152 es2panda::Compiler compiler(src_->scriptExtension, options_->functionThreadCount);
153 panda::pandasm::Program *prog = nullptr;
154
155 if (src_->isSourceMode) {
156 panda::Timer::timerStart(panda::EVENT_COMPILE_FILE, src_->fileName);
157 prog = compiler.CompileFile(*options_, src_, symbolTable_);
158 panda::Timer::timerEnd(panda::EVENT_COMPILE_FILE, src_->fileName);
159 } else if (!options_->mergeAbc) {
160 // If input is an abc file, in non merge-abc mode, compile classes one by one.
161 panda::Timer::timerStart(panda::EVENT_COMPILE_ABC_FILE, src_->fileName);
162 prog = compiler.CompileAbcFile(src_->fileName, *options_);
163 panda::Timer::timerEnd(panda::EVENT_COMPILE_ABC_FILE, src_->fileName);
164 } else {
165 // If input is an abc file, in merge-abc mode, compile each class parallelly.
166 CompileAbcFileJobInParallel(compiler);
167 return;
168 }
169
170 if (prog == nullptr) {
171 return;
172 }
173
174 OptimizeAndCacheProgram(prog);
175 }
176
CompileAbcFileJobInParallel(es2panda::Compiler & compiler)177 void CompileFileJob::CompileAbcFileJobInParallel(es2panda::Compiler &compiler)
178 {
179 std::map<std::string, panda::es2panda::util::ProgramCache *> abcProgramsInfo {};
180
181 panda::Timer::timerStart(panda::EVENT_COMPILE_ABC_FILE, src_->fileName);
182 compiler.CompileAbcFileInParallel(src_, *options_, abcProgramsInfo, allocator_);
183 panda::Timer::timerEnd(panda::EVENT_COMPILE_ABC_FILE, src_->fileName);
184
185 panda::Timer::timerStart(panda::EVENT_UPDATE_ABC_PROG_CACHE, src_->fileName);
186 auto outputCacheIter = options_->cacheFiles.find(src_->fileName);
187 if (!options_->requireGlobalOptimization && outputCacheIter != options_->cacheFiles.end()) {
188 auto *cache = allocator_->New<panda::es2panda::util::AbcProgramsCache>(src_->hash, abcProgramsInfo);
189 CHECK_NOT_NULL(cache);
190 panda::proto::ProtobufSnapshotGenerator::UpdateAbcCacheFile(cache, outputCacheIter->second);
191 }
192 InsertAbcCachePrograms(src_->hash, abcProgramsInfo);
193 panda::Timer::timerEnd(panda::EVENT_UPDATE_ABC_PROG_CACHE, src_->fileName);
194 }
195
OptimizeAndCacheProgram(panda::pandasm::Program * prog)196 void CompileFileJob::OptimizeAndCacheProgram(panda::pandasm::Program *prog)
197 {
198 bool requireOptimizationAfterAnalysis = false;
199 // When cross-program optimizations are required, skip program-local optimization at this stage
200 // and perform it later after the analysis of all programs has been completed
201 if (src_->isSourceMode && options_->transformLib.empty()) {
202 if (options_->requireGlobalOptimization) {
203 panda::Timer::timerStart(panda::EVENT_OPTIMIZE_PROGRAM, src_->fileName);
204 util::Helpers::AnalysisProgram(prog, src_->fileName);
205 requireOptimizationAfterAnalysis = true;
206 } else if (options_->optLevel != 0) {
207 panda::Timer::timerStart(panda::EVENT_OPTIMIZE_PROGRAM, src_->fileName);
208 util::Helpers::OptimizeProgram(prog, src_->fileName);
209 panda::Timer::timerEnd(panda::EVENT_OPTIMIZE_PROGRAM, src_->fileName);
210 }
211 }
212
213 {
214 std::unique_lock<std::mutex> lock(globalMutex_);
215 auto *cache = allocator_->New<util::ProgramCache>(src_->hash, std::move(*prog), src_->isSourceMode);
216 progsInfo_.insert({src_->fileName, cache});
217 if (requireOptimizationAfterAnalysis) {
218 optimizationPendingProgs_.insert(src_->fileName);
219 }
220 }
221 }
222
Run()223 void CompileAbcClassJob::Run()
224 {
225 panda_file::File::EntityId recordId(classId_);
226 auto *program = new panda::pandasm::Program();
227 std::string record_name = "";
228 compiler_.CompileAbcClass(recordId, *program, record_name);
229 program->isGeneratedFromMergedAbc = true;
230
231 if (!options_.modifiedPkgName.empty()) {
232 UpdatePkgNameOfImportOhmurl(program, options_);
233 }
234 // Update ohmurl for abc input when needed
235 if (options_.compileContextInfo.needModifyRecord ||
236 (options_.updatePkgVersionForAbcInput && pkgVersionUpdateRequiredInAbc_)) {
237 panda::Timer::timerStart(panda::EVENT_UPDATE_ABC_PKG_VERSION, record_name);
238 UpdateImportOhmurl(program, options_);
239 panda::Timer::timerEnd(panda::EVENT_UPDATE_ABC_PKG_VERSION, record_name);
240 // Remove redundant strings created due to version replacement
241 panda::Timer::timerStart(panda::EVENT_UPDATE_ABC_PROGRAM_STRING, record_name);
242 if (options_.removeRedundantFile && hasOhmurlBeenChanged_) {
243 program->strings.clear();
244 for (const auto &[_, function] : program->function_table) {
245 const auto &funcStringSet = function.CollectStringsFromFunctionInsns();
246 program->strings.insert(funcStringSet.begin(), funcStringSet.end());
247 }
248 }
249 panda::Timer::timerEnd(panda::EVENT_UPDATE_ABC_PROGRAM_STRING, record_name);
250 }
251
252 panda::Timer::timerStart(panda::EVENT_UPDATE_ABC_PROG_CACHE, record_name);
253 {
254 std::unique_lock<std::mutex> lock(CompileFileJob::globalMutex_);
255 ASSERT(compiler_.GetAbcFile().GetFilename().find(util::CHAR_VERTICAL_LINE) == std::string::npos);
256 ASSERT(program->record_table.size() == 1);
257 ASSERT(util::RecordNotGeneratedFromBytecode(program->record_table.begin()->first));
258 auto name = compiler_.GetAbcFile().GetFilename();
259 name += util::CHAR_VERTICAL_LINE + program->record_table.begin()->first;
260 auto *cache = allocator_->New<util::ProgramCache>(src_->hash, std::move(*program), true);
261 progsInfo_.emplace(name, cache);
262 }
263 panda::Timer::timerEnd(panda::EVENT_UPDATE_ABC_PROG_CACHE, record_name);
264
265 delete program;
266 program = nullptr;
267 }
268
UpdateBundleNameOfOhmurl(std::string & ohmurl)269 void CompileAbcClassJob::UpdateBundleNameOfOhmurl(std::string &ohmurl)
270 {
271 const auto &newOhmurl = util::UpdateBundleNameIfNeeded(ohmurl, options_.compileContextInfo.bundleName,
272 options_.compileContextInfo.externalPkgNames);
273 if (newOhmurl == ohmurl) {
274 return;
275 }
276 SetOhmurlBeenChanged(true);
277 ohmurl = newOhmurl;
278 }
279
UpdateDynamicImport(panda::pandasm::Program * prog,const std::map<std::string,panda::es2panda::PkgInfo> & pkgContextInfo)280 void CompileAbcClassJob::UpdateDynamicImport(panda::pandasm::Program *prog,
281 const std::map<std::string, panda::es2panda::PkgInfo> &pkgContextInfo)
282 {
283 for (auto &[name, function] : prog->function_table) {
284 util::VisitDyanmicImports<false>(function, [this, &prog, pkgContextInfo](std::string &ohmurl) {
285 if (this->options_.compileContextInfo.needModifyRecord) {
286 this->UpdateBundleNameOfOhmurl(ohmurl);
287 }
288 const auto &newOhmurl = util::UpdatePackageVersionIfNeeded(ohmurl, pkgContextInfo);
289 if (newOhmurl == ohmurl) {
290 return;
291 }
292 prog->strings.insert(newOhmurl);
293 this->SetOhmurlBeenChanged(true);
294 ohmurl = newOhmurl;
295 });
296 }
297 }
298
UpdateStaticImport(panda::pandasm::Program * prog,const std::map<std::string,panda::es2panda::PkgInfo> & pkgContextInfo)299 void CompileAbcClassJob::UpdateStaticImport(panda::pandasm::Program *prog,
300 const std::map<std::string, panda::es2panda::PkgInfo> &pkgContextInfo)
301 {
302 for (auto &[recordName, record] : prog->record_table) {
303 util::VisitStaticImports<false>(*prog, record, [this, pkgContextInfo](std::string &ohmurl) {
304 if (this->options_.compileContextInfo.needModifyRecord) {
305 this->UpdateBundleNameOfOhmurl(ohmurl);
306 }
307
308 const auto &newOhmurl = util::UpdatePackageVersionIfNeeded(ohmurl, pkgContextInfo);
309 if (newOhmurl == ohmurl) {
310 return;
311 }
312 this->SetOhmurlBeenChanged(true);
313 ohmurl = newOhmurl;
314 });
315 }
316 }
317
UpdateImportOhmurl(panda::pandasm::Program * prog,const panda::es2panda::CompilerOptions & options)318 void CompileAbcClassJob::UpdateImportOhmurl(panda::pandasm::Program *prog,
319 const panda::es2panda::CompilerOptions &options)
320 {
321 bool isAccurateUpdateVersion = !options.compileContextInfo.updateVersionInfo.empty();
322 const std::map<std::string, panda::es2panda::PkgInfo> &pkgContextInfo = isAccurateUpdateVersion ?
323 options.compileContextInfo.updateVersionInfo.at(abcPkgName_) : options.compileContextInfo.pkgContextInfo;
324 // Replace for esm module static import
325 UpdateStaticImport(prog, pkgContextInfo);
326 // Replace for dynamic import
327 UpdateDynamicImport(prog, pkgContextInfo);
328 }
329 /**
330 * Need to modify the package name of the original package to the package name of the target package when
331 * you merging two packages.
332 */
UpdatePkgNameOfImportOhmurl(panda::pandasm::Program * prog,const panda::es2panda::CompilerOptions & options)333 void CompileAbcClassJob::UpdatePkgNameOfImportOhmurl(panda::pandasm::Program *prog,
334 const panda::es2panda::CompilerOptions &options)
335 {
336 for (auto &[recordName, record] : prog->record_table) {
337 util::VisitStaticImports<false>(*prog, record, [this, options](std::string &ohmurl) {
338 const auto &newOhmurl = util::UpdatePackageNameIfNeeded(ohmurl, options.modifiedPkgName);
339 if (newOhmurl == ohmurl) {
340 return;
341 }
342 this->SetOhmurlBeenChanged(true);
343 ohmurl = newOhmurl;
344 });
345 }
346 for (auto &[name, function] : prog->function_table) {
347 util::VisitDyanmicImports<false>(function, [this, options](std::string &ohmurl) {
348 const auto &newOhmurl = util::UpdatePackageNameIfNeeded(ohmurl, options.modifiedPkgName);
349 if (newOhmurl == ohmurl) {
350 return;
351 }
352 this->SetOhmurlBeenChanged(true);
353 ohmurl = newOhmurl;
354 });
355 }
356 if (hasOhmurlBeenChanged_) {
357 prog->strings.clear();
358 for (const auto &[_, function] : prog->function_table) {
359 const auto &funcStringSet = function.CollectStringsFromFunctionInsns();
360 prog->strings.insert(funcStringSet.begin(), funcStringSet.end());
361 }
362 }
363 }
364
Run()365 void PostAnalysisOptimizeFileJob::Run()
366 {
367 util::Helpers::OptimizeProgram(program_, fileName_);
368 panda::Timer::timerEnd(panda::EVENT_OPTIMIZE_PROGRAM, fileName_);
369 }
370
Schedule()371 void CompileFuncQueue::Schedule()
372 {
373 ASSERT(jobsCount_ == 0);
374 std::unique_lock<std::mutex> lock(m_);
375 const auto &functions = context_->Binder()->Functions();
376
377 for (auto *function : functions) {
378 auto *funcJob = new CompileFunctionJob(context_);
379 funcJob->SetFunctionScope(function);
380 jobs_.push_back(funcJob);
381 jobsCount_++;
382 }
383
384 if (context_->Binder()->Program()->Kind() == parser::ScriptKind::MODULE) {
385 auto *moduleRecordJob = new CompileModuleRecordJob(context_);
386 jobs_.push_back(moduleRecordJob);
387 jobsCount_++;
388 }
389
390 lock.unlock();
391 jobsAvailable_.notify_all();
392 }
393
Schedule()394 void CompileFileQueue::Schedule()
395 {
396 ASSERT(jobsCount_ == 0);
397 std::unique_lock<std::mutex> lock(m_);
398
399 for (auto &input: options_->sourceFiles) {
400 auto *fileJob = new CompileFileJob(&input, options_, progsInfo_, optimizationPendingProgs_,
401 symbolTable_, allocator_);
402 jobs_.push_back(fileJob);
403 jobsCount_++;
404 }
405
406 lock.unlock();
407 jobsAvailable_.notify_all();
408 }
409
NeedUpdateVersion()410 bool CompileAbcClassQueue::NeedUpdateVersion()
411 {
412 std::unordered_map<std::string, std::map<std::string, panda::es2panda::PkgInfo>> updateVersionInfo =
413 options_.compileContextInfo.updateVersionInfo;
414 auto iter = updateVersionInfo.find(src_->pkgName);
415 return updateVersionInfo.empty() || (iter != updateVersionInfo.end() && !iter->second.empty());
416 }
417
Schedule()418 void CompileAbcClassQueue::Schedule()
419 {
420 std::unique_lock<std::mutex> lock(m_);
421
422 auto classIds = compiler_.GetAbcFile().GetClasses();
423 bool needUpdateVersion = NeedUpdateVersion();
424 for (size_t i = 0; i != classIds.size(); ++i) {
425 if (!compiler_.CheckClassId(classIds[i], i)) {
426 continue;
427 }
428
429 auto *abcClassJob = new CompileAbcClassJob(src_, classIds[i], options_, compiler_, progsInfo_, allocator_,
430 src_->pkgName, needUpdateVersion);
431
432 jobs_.push_back(abcClassJob);
433 jobsCount_++;
434 }
435
436 lock.unlock();
437 jobsAvailable_.notify_all();
438 }
439
Schedule()440 void PostAnalysisOptimizeFileQueue::Schedule()
441 {
442 ASSERT(jobsCount_ == 0);
443 std::unique_lock<std::mutex> lock(m_);
444
445 for (const auto &optimizationPendingProgName : optimizationPendingProgs_) {
446 auto progInfo = progsInfo_.find(optimizationPendingProgName);
447 if (progInfo == progsInfo_.end()) {
448 continue;
449 }
450 auto *optimizeJob = new PostAnalysisOptimizeFileJob(progInfo->first, &progInfo->second->program);
451 jobs_.push_back(optimizeJob);
452 jobsCount_++;
453 }
454 }
455
456 } // namespace panda::es2panda::compiler
457