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 delete cacheAbcProgramsInfo;
110 cacheAbcProgramsInfo = nullptr;
111 return true;
112 }
113 delete cacheAbcProgramsInfo;
114 cacheAbcProgramsInfo = nullptr;
115 }
116 }
117 return false;
118 }
119
InsertAbcCachePrograms(uint32_t hashCode,std::map<std::string,panda::es2panda::util::ProgramCache * > & abcProgramsInfo)120 void CompileFileJob::InsertAbcCachePrograms(uint32_t hashCode,
121 std::map<std::string, panda::es2panda::util::ProgramCache *> &abcProgramsInfo)
122 {
123 std::unique_lock<std::mutex> lock(globalMutex_);
124 Compiler::SetExpectedProgsCount(Compiler::GetExpectedProgsCount() + abcProgramsInfo.size() - 1);
125 for (auto pair : abcProgramsInfo) {
126 ASSERT(progsInfo_.find(pair.first) == progsInfo_.end());
127 pair.second->program.isGeneratedFromMergedAbc = true;
128 auto *cache = allocator_->New<util::ProgramCache>(hashCode, std::move(pair.second->program), false);
129 progsInfo_.insert({pair.first, cache});
130 }
131 }
132
Run()133 void CompileFileJob::Run()
134 {
135 std::stringstream ss;
136 std::string buffer;
137 panda::Timer::timerStart(panda::EVENT_READ_INPUT_AND_CACHE, src_->fileName);
138 if (!src_->fileName.empty()) {
139 if (!util::Helpers::ReadFileToBuffer(src_->fileName, ss)) {
140 return;
141 }
142 buffer = ss.str();
143 src_->source = buffer;
144 if (RetrieveProgramFromCacheFiles(buffer, !src_->isSourceMode)) {
145 panda::Timer::timerEnd(panda::EVENT_READ_INPUT_AND_CACHE, src_->fileName);
146 return;
147 }
148 }
149 panda::Timer::timerEnd(panda::EVENT_READ_INPUT_AND_CACHE, src_->fileName);
150
151 CompileProgram();
152 }
153
CompileProgram()154 void CompileFileJob::CompileProgram()
155 {
156 es2panda::Compiler compiler(src_->scriptExtension, options_->functionThreadCount);
157 panda::pandasm::Program *prog = nullptr;
158
159 if (src_->isSourceMode) {
160 panda::Timer::timerStart(panda::EVENT_COMPILE_FILE, src_->fileName);
161 prog = compiler.CompileFile(*options_, src_, symbolTable_);
162 panda::Timer::timerEnd(panda::EVENT_COMPILE_FILE, src_->fileName);
163 } else if (!options_->mergeAbc) {
164 // If input is an abc file, in non merge-abc mode, compile classes one by one.
165 panda::Timer::timerStart(panda::EVENT_COMPILE_ABC_FILE, src_->fileName);
166 prog = compiler.CompileAbcFile(src_->fileName, *options_);
167 panda::Timer::timerEnd(panda::EVENT_COMPILE_ABC_FILE, src_->fileName);
168 } else {
169 // If input is an abc file, in merge-abc mode, compile each class parallelly.
170 CompileAbcFileJobInParallel(compiler);
171 return;
172 }
173
174 if (prog == nullptr) {
175 return;
176 }
177
178 OptimizeAndCacheProgram(prog);
179 }
180
CompileAbcFileJobInParallel(es2panda::Compiler & compiler)181 void CompileFileJob::CompileAbcFileJobInParallel(es2panda::Compiler &compiler)
182 {
183 std::map<std::string, panda::es2panda::util::ProgramCache *> abcProgramsInfo {};
184
185 panda::Timer::timerStart(panda::EVENT_COMPILE_ABC_FILE, src_->fileName);
186 compiler.CompileAbcFileInParallel(src_, *options_, abcProgramsInfo, allocator_);
187 panda::Timer::timerEnd(panda::EVENT_COMPILE_ABC_FILE, src_->fileName);
188
189 panda::Timer::timerStart(panda::EVENT_UPDATE_ABC_PROG_CACHE, src_->fileName);
190 auto outputCacheIter = options_->cacheFiles.find(src_->fileName);
191 if (!options_->requireGlobalOptimization && outputCacheIter != options_->cacheFiles.end()) {
192 auto *cache = new panda::es2panda::util::AbcProgramsCache(src_->hash, abcProgramsInfo);
193 CHECK_NOT_NULL(cache);
194 panda::proto::ProtobufSnapshotGenerator::UpdateAbcCacheFile(cache, outputCacheIter->second);
195 delete cache;
196 cache = nullptr;
197 }
198 InsertAbcCachePrograms(src_->hash, abcProgramsInfo);
199 panda::Timer::timerEnd(panda::EVENT_UPDATE_ABC_PROG_CACHE, src_->fileName);
200 }
201
OptimizeAndCacheProgram(panda::pandasm::Program * prog)202 void CompileFileJob::OptimizeAndCacheProgram(panda::pandasm::Program *prog)
203 {
204 bool requireOptimizationAfterAnalysis = false;
205 // When cross-program optimizations are required, skip program-local optimization at this stage
206 // and perform it later after the analysis of all programs has been completed
207 if (src_->isSourceMode && options_->transformLib.empty()) {
208 if (options_->requireGlobalOptimization) {
209 panda::Timer::timerStart(panda::EVENT_OPTIMIZE_PROGRAM, src_->fileName);
210 util::Helpers::AnalysisProgram(prog, src_->fileName);
211 requireOptimizationAfterAnalysis = true;
212 } else if (options_->optLevel != 0) {
213 panda::Timer::timerStart(panda::EVENT_OPTIMIZE_PROGRAM, src_->fileName);
214 util::Helpers::OptimizeProgram(prog, src_->fileName);
215 panda::Timer::timerEnd(panda::EVENT_OPTIMIZE_PROGRAM, src_->fileName);
216 }
217 }
218
219 {
220 std::unique_lock<std::mutex> lock(globalMutex_);
221 auto *cache = allocator_->New<util::ProgramCache>(src_->hash, std::move(*prog), src_->isSourceMode);
222 progsInfo_.insert({src_->fileName, cache});
223 if (requireOptimizationAfterAnalysis) {
224 optimizationPendingProgs_.insert(src_->fileName);
225 }
226 }
227 }
228
Run()229 void CompileAbcClassJob::Run()
230 {
231 panda_file::File::EntityId recordId(classId_);
232 auto *program = new panda::pandasm::Program();
233 std::string record_name = "";
234 compiler_.CompileAbcClass(recordId, *program, record_name);
235 if (program->record_table.size() == 0) {
236 delete program;
237 return;
238 }
239 program->isGeneratedFromMergedAbc = true;
240
241 if (!options_.modifiedPkgName.empty()) {
242 UpdatePkgNameOfImportOhmurl(program, options_);
243 }
244 // Update ohmurl for abc input when needed
245 if (options_.compileContextInfo.needModifyRecord ||
246 (options_.updatePkgVersionForAbcInput && pkgVersionUpdateRequiredInAbc_)) {
247 panda::Timer::timerStart(panda::EVENT_UPDATE_ABC_PKG_VERSION, record_name);
248 UpdateImportOhmurl(program, options_);
249 panda::Timer::timerEnd(panda::EVENT_UPDATE_ABC_PKG_VERSION, record_name);
250 // Remove redundant strings created due to version replacement
251 panda::Timer::timerStart(panda::EVENT_UPDATE_ABC_PROGRAM_STRING, record_name);
252 if (options_.removeRedundantFile && hasOhmurlBeenChanged_) {
253 program->strings.clear();
254 for (const auto &[_, function] : program->function_table) {
255 const auto &funcStringSet = function.CollectStringsFromFunctionInsns();
256 program->strings.insert(funcStringSet.begin(), funcStringSet.end());
257 }
258 }
259 panda::Timer::timerEnd(panda::EVENT_UPDATE_ABC_PROGRAM_STRING, record_name);
260 }
261
262 panda::Timer::timerStart(panda::EVENT_UPDATE_ABC_PROG_CACHE, record_name);
263 {
264 std::unique_lock<std::mutex> lock(CompileFileJob::globalMutex_);
265 ASSERT(compiler_.GetAbcFile().GetFilename().find(util::CHAR_VERTICAL_LINE) == std::string::npos);
266 ASSERT(program->record_table.size() == 1);
267 ASSERT(util::RecordNotGeneratedFromBytecode(program->record_table.begin()->first));
268 auto name = compiler_.GetAbcFile().GetFilename();
269 name += util::CHAR_VERTICAL_LINE + program->record_table.begin()->first;
270 auto *cache = allocator_->New<util::ProgramCache>(src_->hash, std::move(*program), true);
271 ASSERT(!progsInfo_.count(name));
272 progsInfo_.emplace(name, cache);
273 }
274 panda::Timer::timerEnd(panda::EVENT_UPDATE_ABC_PROG_CACHE, record_name);
275
276 delete program;
277 program = nullptr;
278 }
279
UpdateBundleNameOfOhmurl(std::string & ohmurl)280 void CompileAbcClassJob::UpdateBundleNameOfOhmurl(std::string &ohmurl)
281 {
282 const auto &newOhmurl = util::UpdateBundleNameIfNeeded(ohmurl, options_.compileContextInfo.bundleName,
283 options_.compileContextInfo.externalPkgNames);
284 if (newOhmurl == ohmurl) {
285 return;
286 }
287 SetOhmurlBeenChanged(true);
288 ohmurl = newOhmurl;
289 }
290
UpdateDynamicImport(panda::pandasm::Program * prog,const std::map<std::string,panda::es2panda::PkgInfo> & pkgContextInfo)291 void CompileAbcClassJob::UpdateDynamicImport(panda::pandasm::Program *prog,
292 const std::map<std::string, panda::es2panda::PkgInfo> &pkgContextInfo)
293 {
294 for (auto &[name, function] : prog->function_table) {
295 util::VisitDyanmicImports<false>(function, [this, &prog, pkgContextInfo](std::string &ohmurl) {
296 if (this->options_.compileContextInfo.needModifyRecord) {
297 this->UpdateBundleNameOfOhmurl(ohmurl);
298 }
299 const auto &newOhmurl = util::UpdatePackageVersionIfNeeded(ohmurl, pkgContextInfo);
300 if (newOhmurl == ohmurl) {
301 return;
302 }
303 prog->strings.insert(newOhmurl);
304 this->SetOhmurlBeenChanged(true);
305 ohmurl = newOhmurl;
306 });
307 }
308 }
309
UpdateStaticImport(panda::pandasm::Program * prog,const std::map<std::string,panda::es2panda::PkgInfo> & pkgContextInfo)310 void CompileAbcClassJob::UpdateStaticImport(panda::pandasm::Program *prog,
311 const std::map<std::string, panda::es2panda::PkgInfo> &pkgContextInfo)
312 {
313 for (auto &[recordName, record] : prog->record_table) {
314 util::VisitStaticImports<false>(*prog, record, [this, pkgContextInfo](std::string &ohmurl) {
315 if (this->options_.compileContextInfo.needModifyRecord) {
316 this->UpdateBundleNameOfOhmurl(ohmurl);
317 }
318
319 const auto &newOhmurl = util::UpdatePackageVersionIfNeeded(ohmurl, pkgContextInfo);
320 if (newOhmurl == ohmurl) {
321 return;
322 }
323 this->SetOhmurlBeenChanged(true);
324 ohmurl = newOhmurl;
325 });
326 }
327 }
328
UpdateImportOhmurl(panda::pandasm::Program * prog,const panda::es2panda::CompilerOptions & options)329 void CompileAbcClassJob::UpdateImportOhmurl(panda::pandasm::Program *prog,
330 const panda::es2panda::CompilerOptions &options)
331 {
332 bool isAccurateUpdateVersion = !options.compileContextInfo.updateVersionInfo.empty();
333 const std::map<std::string, panda::es2panda::PkgInfo> &pkgContextInfo = isAccurateUpdateVersion ?
334 options.compileContextInfo.updateVersionInfo.at(abcPkgName_) : options.compileContextInfo.pkgContextInfo;
335 // Replace for esm module static import
336 UpdateStaticImport(prog, pkgContextInfo);
337 // Replace for dynamic import
338 UpdateDynamicImport(prog, pkgContextInfo);
339 }
340 /**
341 * Need to modify the package name of the original package to the package name of the target package when
342 * you merging two packages.
343 */
UpdatePkgNameOfImportOhmurl(panda::pandasm::Program * prog,const panda::es2panda::CompilerOptions & options)344 void CompileAbcClassJob::UpdatePkgNameOfImportOhmurl(panda::pandasm::Program *prog,
345 const panda::es2panda::CompilerOptions &options)
346 {
347 for (auto &[recordName, record] : prog->record_table) {
348 util::VisitStaticImports<false>(*prog, record, [this, options](std::string &ohmurl) {
349 const auto &newOhmurl = util::UpdatePackageNameIfNeeded(ohmurl, options.modifiedPkgName);
350 if (newOhmurl == ohmurl) {
351 return;
352 }
353 this->SetOhmurlBeenChanged(true);
354 ohmurl = newOhmurl;
355 });
356 }
357 for (auto &[name, function] : prog->function_table) {
358 util::VisitDyanmicImports<false>(function, [this, options](std::string &ohmurl) {
359 const auto &newOhmurl = util::UpdatePackageNameIfNeeded(ohmurl, options.modifiedPkgName);
360 if (newOhmurl == ohmurl) {
361 return;
362 }
363 this->SetOhmurlBeenChanged(true);
364 ohmurl = newOhmurl;
365 });
366 }
367 if (hasOhmurlBeenChanged_) {
368 prog->strings.clear();
369 for (const auto &[_, function] : prog->function_table) {
370 const auto &funcStringSet = function.CollectStringsFromFunctionInsns();
371 prog->strings.insert(funcStringSet.begin(), funcStringSet.end());
372 }
373 }
374 }
375
Run()376 void PostAnalysisOptimizeFileJob::Run()
377 {
378 util::Helpers::OptimizeProgram(program_, fileName_);
379 panda::Timer::timerEnd(panda::EVENT_OPTIMIZE_PROGRAM, fileName_);
380 }
381
Schedule()382 void CompileFuncQueue::Schedule()
383 {
384 ASSERT(jobsCount_ == 0);
385 std::unique_lock<std::mutex> lock(m_);
386 const auto &functions = context_->Binder()->Functions();
387
388 for (auto *function : functions) {
389 auto *funcJob = new CompileFunctionJob(context_);
390 funcJob->SetFunctionScope(function);
391 jobs_.push_back(funcJob);
392 jobsCount_++;
393 }
394
395 if (context_->Binder()->Program()->Kind() == parser::ScriptKind::MODULE) {
396 auto *moduleRecordJob = new CompileModuleRecordJob(context_);
397 jobs_.push_back(moduleRecordJob);
398 jobsCount_++;
399 }
400
401 lock.unlock();
402 jobsAvailable_.notify_all();
403 }
404
Schedule()405 void CompileFileQueue::Schedule()
406 {
407 ASSERT(jobsCount_ == 0);
408 std::unique_lock<std::mutex> lock(m_);
409
410 for (auto &input: options_->sourceFiles) {
411 auto *fileJob = new CompileFileJob(&input, options_, progsInfo_, optimizationPendingProgs_,
412 symbolTable_, allocator_);
413 jobs_.push_back(fileJob);
414 jobsCount_++;
415 }
416
417 lock.unlock();
418 jobsAvailable_.notify_all();
419 }
420
NeedUpdateVersion()421 bool CompileAbcClassQueue::NeedUpdateVersion()
422 {
423 std::unordered_map<std::string, std::map<std::string, panda::es2panda::PkgInfo>> updateVersionInfo =
424 options_.compileContextInfo.updateVersionInfo;
425 auto iter = updateVersionInfo.find(src_->pkgName);
426 return updateVersionInfo.empty() || (iter != updateVersionInfo.end() && !iter->second.empty());
427 }
428
Schedule()429 void CompileAbcClassQueue::Schedule()
430 {
431 std::unique_lock<std::mutex> lock(m_);
432
433 auto classIds = compiler_.GetAbcFile().GetClasses();
434 bool needUpdateVersion = NeedUpdateVersion();
435 for (size_t i = 0; i != classIds.size(); ++i) {
436 if (!compiler_.CheckClassId(classIds[i], i)) {
437 continue;
438 }
439
440 auto *abcClassJob = new CompileAbcClassJob(src_, classIds[i], options_, compiler_, progsInfo_, allocator_,
441 src_->pkgName, needUpdateVersion);
442
443 jobs_.push_back(abcClassJob);
444 jobsCount_++;
445 }
446
447 lock.unlock();
448 jobsAvailable_.notify_all();
449 }
450
Schedule()451 void PostAnalysisOptimizeFileQueue::Schedule()
452 {
453 ASSERT(jobsCount_ == 0);
454 std::unique_lock<std::mutex> lock(m_);
455
456 for (const auto &optimizationPendingProgName : optimizationPendingProgs_) {
457 auto progInfo = progsInfo_.find(optimizationPendingProgName);
458 if (progInfo == progsInfo_.end()) {
459 continue;
460 }
461 auto *optimizeJob = new PostAnalysisOptimizeFileJob(progInfo->first, &progInfo->second->program);
462 jobs_.push_back(optimizeJob);
463 jobsCount_++;
464 }
465 }
466
467 } // namespace panda::es2panda::compiler
468