1 /* 2 * Copyright (c) 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 #ifndef ECMASCRIPT_COMPILER_COMPILATION_DRIVER_H 17 #define ECMASCRIPT_COMPILER_COMPILATION_DRIVER_H 18 19 #include "ecmascript/compiler/bytecode_info_collector.h" 20 21 22 namespace panda::ecmascript::kungfu { 23 class AOTFileGenerator; 24 class CompilerLog; 25 struct LOptions; 26 class Module; 27 class CompilationDriver { 28 public: 29 CompilationDriver(PGOProfilerDecoder &profilerDecoder, 30 BytecodeInfoCollector* collector, 31 const std::string &compilemMethodsOption, 32 const std::string &compileSkipMethodsOption, 33 AOTFileGenerator *fileGenerator, 34 const std::string &fileName, 35 const std::string &triple, 36 LOptions *lOptions, 37 CompilerLog *log, 38 bool outputAsm, 39 size_t maxMethodsInModule); 40 ~CompilationDriver(); 41 42 NO_COPY_SEMANTIC(CompilationDriver); 43 NO_MOVE_SEMANTIC(CompilationDriver); 44 IsPGOLoaded()45 bool IsPGOLoaded() const 46 { 47 return pfDecoder_.IsLoaded(); 48 } 49 UpdateCompileQueue(const CString & recordName,EntityId resolvedMethod)50 void UpdateCompileQueue(const CString &recordName, EntityId resolvedMethod) 51 { 52 const auto &methodList = bytecodeInfo_.GetMethodList(); 53 auto &resolvedMethodInfo = methodList.at(resolvedMethod.GetOffset()); 54 if (pfDecoder_.Match(recordName, resolvedMethod) && !resolvedMethodInfo.IsTypeInferAbort()) { 55 return; 56 } 57 // update profile and update compile queue 58 std::unordered_set<EntityId> fullResolvedMethodSet; 59 auto dfs = [this, &fullResolvedMethodSet, resolvedMethod] (const CString &recordName, 60 [[maybe_unused]] const std::unordered_set<EntityId> &oldIds) -> std::unordered_set<EntityId> & { 61 fullResolvedMethodSet.clear(); 62 std::unordered_set<EntityId> currentResolvedMethodSet {resolvedMethod}; 63 uint32_t mainMethodOffset = jsPandaFile_->GetMainMethodIndex(recordName); 64 SearchForCompilation(recordName, currentResolvedMethodSet, 65 fullResolvedMethodSet, mainMethodOffset, true); 66 return fullResolvedMethodSet; 67 }; 68 69 pfDecoder_.Update(recordName, dfs); 70 71 if (fullResolvedMethodSet.size() > 0) { 72 bytecodeInfo_.AddRecordName(recordName); 73 } 74 for (auto &newMethod : fullResolvedMethodSet) { 75 bytecodeInfo_.EraseSkippedMethod(newMethod.GetOffset()); 76 } 77 } 78 79 template <class Callback> CompileMethod(const Callback & cb,uint32_t index,const std::string & methodName,MethodLiteral * methodLiteral,uint32_t methodOffset,const MethodPcInfo & methodPcInfo,MethodInfo & methodInfo)80 void CompileMethod(const Callback &cb, 81 uint32_t index, 82 const std::string &methodName, 83 MethodLiteral *methodLiteral, 84 uint32_t methodOffset, 85 const MethodPcInfo &methodPcInfo, 86 MethodInfo &methodInfo) 87 { 88 Module *module = GetModule(); 89 cb(bytecodeInfo_.GetRecordName(index), methodName, methodLiteral, methodOffset, 90 methodPcInfo, methodInfo, module); 91 if (methodInfo.IsTypeInferAbort()) { 92 return; 93 } 94 IncCompiledMethod(); 95 CompileModuleThenDestroyIfNeeded(); 96 } 97 98 template <class Callback> Run(const Callback & cb)99 void Run(const Callback &cb) 100 { 101 UpdatePGO(); 102 InitializeCompileQueue(); 103 uint32_t index = 0; 104 auto &methodList = bytecodeInfo_.GetMethodList(); 105 const auto &methodPcInfos = bytecodeInfo_.GetMethodPcInfos(); 106 while (!compileQueue_.empty()) { 107 std::queue<uint32_t> methodCompiledOrder; 108 methodCompiledOrder.push(compileQueue_.front()); 109 compileQueue_.pop_front(); 110 while (!methodCompiledOrder.empty()) { 111 auto compilingMethod = methodCompiledOrder.front(); 112 methodCompiledOrder.pop(); 113 bytecodeInfo_.AddMethodOffsetToRecordName(compilingMethod, bytecodeInfo_.GetRecordName(index)); 114 auto &methodInfo = methodList.at(compilingMethod); 115 auto &methodPcInfo = methodPcInfos[methodInfo.GetMethodPcInfoIndex()]; 116 auto methodLiteral = jsPandaFile_->FindMethodLiteral(compilingMethod); 117 const std::string methodName(MethodLiteral::GetMethodName(jsPandaFile_, methodLiteral->GetMethodId())); 118 if (FilterMethod(bytecodeInfo_.GetRecordName(index), methodLiteral, methodPcInfo, methodName)) { 119 bytecodeInfo_.AddSkippedMethod(compilingMethod); 120 } else { 121 if (!methodInfo.IsCompiled()) { 122 methodInfo.SetIsCompiled(true); 123 CompileMethod(cb, index, methodName, methodLiteral, compilingMethod, methodPcInfo, methodInfo); 124 } else if (NeedSecondaryCompile(methodInfo)) { 125 // if a method used to be not full compiled but now it's a deopt resolved method 126 // it should be full compiled again 127 CompileMethod(cb, index, methodName, methodLiteral, compilingMethod, methodPcInfo, methodInfo); 128 } 129 } 130 auto &innerMethods = methodInfo.GetInnerMethods(); 131 for (auto it : innerMethods) { 132 methodCompiledOrder.push(it); 133 } 134 } 135 index++; 136 } 137 CompileLastModuleThenDestroyIfNeeded(); 138 } 139 140 void FetchPGOMismatchResult(); 141 AddResolvedMethod(const CString & recordName,uint32_t classLiteralOffset)142 void AddResolvedMethod(const CString &recordName, uint32_t classLiteralOffset) 143 { 144 if (!IsPGOLoaded() || !bytecodeInfo_.HasClassDefMethod(classLiteralOffset)) { 145 return; 146 } 147 uint32_t resolvedMethod = bytecodeInfo_.GetDefineMethod(classLiteralOffset); 148 auto &methodList = bytecodeInfo_.GetMethodList(); 149 auto &methodInfo = methodList.at(resolvedMethod); 150 methodInfo.SetResolvedMethod(true); 151 panda_file::File::EntityId resolvedMethodId(resolvedMethod); 152 UpdateCompileQueue(recordName, resolvedMethodId); 153 } 154 private: 155 // add maxMethodsInModule_ functions in a module and when a module is 156 // full(maxMethodsInModule_ functions have been put into) or the module is the last module, 157 // compile it and the destroy it. 158 Module *GetModule(); 159 160 void IncCompiledMethod(); 161 162 bool IsCurModuleFull() const; 163 164 void CompileModuleThenDestroyIfNeeded(); 165 166 void CompileLastModuleThenDestroyIfNeeded(); 167 168 void TopologicalSortForRecords(); 169 170 void UpdatePGO(); 171 172 void InitializeCompileQueue(); 173 NeedSecondaryCompile(const MethodInfo & methodInfo)174 bool NeedSecondaryCompile(const MethodInfo &methodInfo) const 175 { 176 return methodInfo.IsTypeInferAbort() && methodInfo.IsResolvedMethod(); 177 } 178 AddDependList(const CString & recordName,uint32_t methodOffset,std::deque<CString> & dependList)179 void AddDependList(const CString &recordName, uint32_t methodOffset, 180 std::deque<CString> &dependList) 181 { 182 auto &methodList = bytecodeInfo_.GetMethodList(); 183 const auto &importRecordInfos = bytecodeInfo_.GetImportRecordsInfos(); 184 auto iter = importRecordInfos.find(recordName); 185 // if the resolved method don't have import records need-inferred, just return. 186 if (iter == importRecordInfos.end()) { 187 return; 188 } 189 auto &methodInfo = methodList.at(methodOffset); 190 // Get the import indexs collected in methodInfo. 191 auto &importIndexs = methodInfo.GetImportIndexes(); 192 // idInRecord is a map like "importIndex: (exportRecord: exportIndex)". 193 const auto &idInRecord = iter->second.GetImportIdToExportRecord(); 194 for (auto index : importIndexs) { 195 auto it = idInRecord.find(index); 196 if (it != idInRecord.end()) { 197 dependList.emplace_back(it->second.first); 198 } 199 } 200 } 201 VerifyAndMarkCurMethod(uint32_t methodOffset,std::unordered_set<EntityId> & newMethodSet)202 bool VerifyAndMarkCurMethod(uint32_t methodOffset, std::unordered_set<EntityId> &newMethodSet) 203 { 204 // if current method is at the boundary state, we should stop the define chain search. 205 if (methodOffset == MethodInfo::DEFAULT_OUTMETHOD_OFFSET) { 206 return false; 207 } 208 // if pgo profile is not matched with current abc file, methodOffset will be different. 209 auto &methodList = bytecodeInfo_.GetMethodList(); 210 auto iter = methodList.find(methodOffset); 211 if (iter == methodList.end()) { 212 LOG_COMPILER(ERROR) << "The correct aot profile has not been used"; 213 return false; 214 } 215 auto &methodInfo = iter->second; 216 // if current method has already been marked as PGO, stop searching upper layer of the define chain. 217 if (methodInfo.IsPGO() && !methodInfo.IsTypeInferAbort()) { 218 return false; 219 } 220 // we need to collect these new-marked PGO methods to update PGO profile 221 panda_file::File::EntityId methodId(methodOffset); 222 newMethodSet.insert(std::move(methodId)); 223 methodInfo.SetIsPGO(true); 224 return true; 225 } 226 UpdateResolveDepends(std::vector<CString> & dependNames,bool needUpdate)227 void UpdateResolveDepends(std::vector<CString> &dependNames, bool needUpdate) 228 { 229 if (needUpdate && !dependNames.empty()) { 230 // depend methods should keep the topological sorting rule 231 std::sort(dependNames.begin(), dependNames.end(), [this](auto first, auto second) { 232 return sortedRecords_.at(first) < sortedRecords_.at(second); 233 }); 234 auto resolvedMethod = compileQueue_.back(); 235 compileQueue_.pop_back(); 236 // it should be like "depended main method1, depended main method2, resolved method" in compileQueue. 237 for (const auto &ele : dependNames) { 238 bytecodeInfo_.AddRecordName(ele); 239 auto eleOffset = jsPandaFile_->GetMainMethodIndex(ele); 240 // these methods will be added to compile queue 241 bytecodeInfo_.EraseSkippedMethod(eleOffset); 242 compileQueue_.push_back(eleOffset); 243 } 244 compileQueue_.push_back(resolvedMethod); 245 } 246 } 247 SearchForCompilation(const CString & recordName,const std::unordered_set<EntityId> & methodSet,std::unordered_set<EntityId> & newMethodSet,uint32_t mainMethodOffset,bool needUpdateCompile)248 void SearchForCompilation(const CString &recordName, const std::unordered_set<EntityId> &methodSet, 249 std::unordered_set<EntityId> &newMethodSet, 250 uint32_t mainMethodOffset, bool needUpdateCompile) 251 { 252 auto &methodList = bytecodeInfo_.GetMethodList(); 253 std::unordered_set<EntityId> mainMethodSet; 254 auto getMainMethodSet = [this, &mainMethodSet](const CString &importRecord, 255 [[maybe_unused]] const std::unordered_set<EntityId> &oldIds) -> std::unordered_set<EntityId>& { 256 mainMethodSet.clear(); 257 auto mainMethodOffset = jsPandaFile_->GetMainMethodIndex(importRecord); 258 panda_file::File::EntityId mainMethodId(mainMethodOffset); 259 mainMethodSet.insert(mainMethodId); 260 return mainMethodSet; 261 }; 262 263 std::vector<CString> importNames{}; 264 std::function<void(EntityId)> importBfs = 265 [this, &methodList, &recordName, &importNames, &getMainMethodSet] 266 (EntityId methodId) -> void { 267 uint32_t methodOffset = methodId.GetOffset(); 268 std::deque<CString> importList{}; 269 AddDependList(recordName, methodOffset, importList); 270 while (!importList.empty()) { 271 auto importRecord = importList.front(); 272 importList.pop_front(); 273 // export syntax only exists in main method, so just judge and collect the main method. 274 auto mainMethodOffset = jsPandaFile_->GetMainMethodIndex(importRecord); 275 auto &mainMethodInfo = methodList.at(mainMethodOffset); 276 // mark the main method in other record as PGO. 277 if (mainMethodInfo.IsPGO()) { 278 continue; 279 } 280 importNames.emplace_back(importRecord); 281 mainMethodInfo.SetIsPGO(true); 282 pfDecoder_.Update(importRecord, getMainMethodSet); 283 AddDependList(importRecord, mainMethodOffset, importList); 284 } 285 }; 286 287 std::function<void(EntityId, bool)> dfs = 288 [this, &newMethodSet, &mainMethodOffset, &dfs, &methodList, &importBfs] 289 (EntityId methodId, bool needUpdateCompile) -> void { 290 uint32_t methodOffset = methodId.GetOffset(); 291 // verify whether we should stop the search for pgo methods or resolve methods. 292 if (!VerifyAndMarkCurMethod(methodOffset, newMethodSet)) { 293 return; 294 } 295 auto &methodInfo = methodList.at(methodOffset); 296 auto outMethodOffset = methodInfo.GetOutMethodOffset(); 297 // for pgo method, collect its import depends method 298 importBfs(methodId); 299 if (needUpdateCompile) { 300 // in deopt, we need to push the first un-marked method which is 301 // in upper layer of the deopt method's define chain (or maybe the deopt method itself) 302 // into the sharedCompiledQueue to trigger the compilation of the deopt method. 303 if (methodOffset != mainMethodOffset) { 304 // few methods which have the same bytecodes as other method can't find its outter method 305 if (outMethodOffset == MethodInfo::DEFAULT_OUTMETHOD_OFFSET) { 306 compileQueue_.push_back(methodOffset); 307 return; 308 } 309 // currentMethod whose outtermethod has been marked as pgo need to push into queue 310 auto outMethodInfo = methodList.at(outMethodOffset); 311 if (outMethodInfo.IsPGO()) { 312 compileQueue_.push_back(methodOffset); 313 return; 314 } 315 } else { 316 // if current searched method is an un-marked main method, just push it to compile queue 317 compileQueue_.push_back(methodOffset); 318 } 319 } 320 if (methodOffset == mainMethodOffset) { 321 return; 322 } 323 EntityId outMethod(outMethodOffset); 324 dfs(outMethod, needUpdateCompile); 325 }; 326 327 // search as link list, a one-way define chain 328 for (auto pgoMethod = methodSet.begin(); pgoMethod != methodSet.end(); pgoMethod++) { 329 dfs(*pgoMethod, needUpdateCompile); 330 } 331 // update compile queue only for resolve method when it depends on other record 332 UpdateResolveDepends(importNames, needUpdateCompile); 333 } 334 335 bool FilterMethod(const CString &recordName, const MethodLiteral *methodLiteral, 336 const MethodPcInfo &methodPCInfo, const std::string &methodName) const; 337 338 std::vector<std::string> SplitString(const std::string &str, const char ch) const; 339 340 void ParseOption(const std::string &option, std::map<std::string, std::vector<std::string>> &optionMap) const; 341 342 bool FilterOption(const std::map<std::string, std::vector<std::string>> &optionMap, const std::string &recordName, 343 const std::string &methodName) const; 344 345 EcmaVM *vm_ {nullptr}; 346 const JSPandaFile *jsPandaFile_ {nullptr}; 347 PGOProfilerDecoder &pfDecoder_; 348 BCInfo &bytecodeInfo_; 349 std::deque<uint32_t> compileQueue_ {}; 350 std::map<CString, uint32_t> sortedRecords_ {}; 351 std::map<std::string, std::vector<std::string>> optionSelectMethods_ {}; 352 std::map<std::string, std::vector<std::string>> optionSkipMethods_ {}; 353 uint32_t compiledMethodCnt_ {0}; 354 AOTFileGenerator *fileGenerator_ {nullptr}; 355 std::string fileName_ {}; 356 std::string triple_ {}; 357 LOptions *lOptions_ {nullptr}; 358 CompilerLog *log_ {nullptr}; 359 bool outputAsm_ {false}; 360 size_t maxMethodsInModule_ {0}; 361 }; 362 } // namespace panda::ecmascript::kungfu 363 #endif // ECMASCRIPT_COMPILER_COMPILATION_DRIVER_H 364