diff --git a/static_core/compiler/CMakeLists.txt b/static_core/compiler/CMakeLists.txt index adb381af974feae00b703d53c1786a5919d3a14e..e8c46d9166f90b635bab6b06493ab246d38ea3ff 100644 --- a/static_core/compiler/CMakeLists.txt +++ b/static_core/compiler/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2024 Huawei Device Co., Ltd. +# Copyright (c) 2021-2025 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -368,6 +368,13 @@ set(PANDA_COMPILER_TESTS_SOURCES ) set_source_files_properties(tests/loop_unroll_test.cpp PROPERTIES COMPILE_FLAGS "-Wno-shadow -Wno-deprecated-declarations") +# Disable profiling_saver_test for AARCH32 and X86 +if(NOT (PANDA_TARGET_ARM32 OR PANDA_TARGET_X86)) + list(APPEND PANDA_COMPILER_TESTS_SOURCES + tests/profiling_saver_test.cpp + ) +endif() + # Distinguish 'PANDA_COMPILER_TARGET_..' and 'PANDA_TARGET_..' because for PANDA_TARGET_AMD64 tests # are being executed for Arch::AARCH64 if(PANDA_COMPILER_TARGET_AARCH64 OR PANDA_TARGET_ARM32) diff --git a/static_core/compiler/aot/aot_manager.h b/static_core/compiler/aot/aot_manager.h index 1e6d1fa1ffd3b96e8643eec0c3f2d714e277ed66..99215f762cb89b16519dbec6042bbe771ccab7fd 100644 --- a/static_core/compiler/aot/aot_manager.h +++ b/static_core/compiler/aot/aot_manager.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -21,6 +21,7 @@ #include "utils/arena_containers.h" #include "runtime/include/mem/panda_containers.h" #include "runtime/include/mem/panda_string.h" +#include "runtime/include/method.h" #include "utils/expected.h" namespace ark::compiler { @@ -49,9 +50,30 @@ public: return bootClassContext_; } + void ParseClassContextToFile(std::string_view context) + { + size_t start = 0; + size_t end; + size_t pathEnd; + if (context.empty()) { + profiledPandaFiles_.insert(""); + return; + } + while ((end = context.find(':', start)) != std::string_view::npos) { + pathEnd = context.find('*', start); + ASSERT(pathEnd != std::string_view::npos); + profiledPandaFiles_.insert(context.substr(start, pathEnd - start)); + start = end + 1; + } + pathEnd = context.find('*', start); + ASSERT(pathEnd != std::string_view::npos); + profiledPandaFiles_.insert(context.substr(start, pathEnd - start)); + } + void SetBootClassContext(PandaString context) { bootClassContext_ = std::move(context); + ParseClassContextToFile(bootClassContext_); } PandaString GetAppClassContext() const @@ -62,6 +84,7 @@ public: void SetAppClassContext(PandaString context) { appClassContext_ = std::move(context); + ParseClassContextToFile(appClassContext_); } void VerifyClassHierarchy(); @@ -149,12 +172,35 @@ public: return !aotFiles_.empty(); } + void TryAddMethodToProfile(const Method *method) + { + auto pfName = method->GetPandaFile()->GetFullFileName(); + if (profiledPandaFiles_.find(pfName) != profiledPandaFiles_.end()) { + os::memory::LockHolder lock {profiledMethodsLock_}; + profiledMethods_.push_back(method); + } + } + + PandaVector &GetProfiledMethods() + { + return profiledMethods_; + } + + PandaUnorderedSet &GetProfiledPandaFiles() + { + return profiledPandaFiles_; + } + private: PandaVector> aotFiles_; PandaUnorderedMap filesMap_; PandaString bootClassContext_; PandaString appClassContext_; + mutable os::memory::Mutex profiledMethodsLock_; + PandaVector profiledMethods_ GUARDED_BY(profiledMethodsLock_); + PandaUnorderedSet profiledPandaFiles_; + os::memory::RecursiveMutex aotStringRootsLock_; PandaVector aotStringGcRoots_; std::atomic_uint32_t aotStringGcRootsCount_ {0}; diff --git a/static_core/compiler/docs/aot_pgo.md b/static_core/compiler/docs/aot_pgo.md new file mode 100644 index 0000000000000000000000000000000000000000..997439e3b62394bc0e7a2464a05b22a7eb71ce45 --- /dev/null +++ b/static_core/compiler/docs/aot_pgo.md @@ -0,0 +1,68 @@ +# Profile guided optimization of AOT + +AOT compiler support profile guided optimization + +## PGO file format + +**Profiled Information:** abcfiles, classes, methods, virtual call caches, branch caches, throw caches + +``` +FileHeader + magic[] + version[] + versionProfileType // profile type supportted by this version + savedProfiledType // saved profile type in this file + headerSize // size of FileHeader + withCha: len(classCtxStr) | 0 + classCtxStr + +PandaFiles + numberOfFiles + sectionSize + PandaFileInfo: [type, filePathLen, filePath] + +SectionInfos + sectionNumber + sectionSize + sectionInfo: [checkSum, zippedSize, unzippedSize, sectionType, pandaFileIdx] + +Methods + numberOfMethods + pandaFileIdx + MethodData [methodIdx, classIdx, savedType, chunkSize, ProfileData[]] + profileData [] + profType + numberOfRecords + chunkSize + inlineCaches [pc, (classIdx, abcfileIdx)[4]] + profileData [] + profType + numberOfRecords + chunkSize + branches [pc, taken, notTaken] + profileData [] + profType + numberOfRecords + chunkSize + throws [pc, taken] +``` + +## Save profile during runtime destroy + +Note that we will only save profile information for abcfiles in **BOOT and INITIAL APP context**. + +![Profile collection and saving logic](images/aot_pgo.png) + +Now, we only dump profiling data to file when runtime destory to avoid big impact on VM. + +1. Record profiled method during runtime + +We record list of profiled methods and binded it to ClassLinker. + +2. Serialization + +Step1: Transform data in ProfilingData to AotProfilingData + +Step2: Build sections based on AotProfilingData + +Step3: Write sections to file \ No newline at end of file diff --git a/static_core/compiler/docs/images/aot_pgo.png b/static_core/compiler/docs/images/aot_pgo.png new file mode 100644 index 0000000000000000000000000000000000000000..3df1e624704ffbfc92a684f7808887d7c59fea3b Binary files /dev/null and b/static_core/compiler/docs/images/aot_pgo.png differ diff --git a/static_core/compiler/tests/expected_profile.ap.hex b/static_core/compiler/tests/expected_profile.ap.hex new file mode 100644 index 0000000000000000000000000000000000000000..4f43de33fa3f7323ede1207829ffc3e9aecaa1a3 --- /dev/null +++ b/static_core/compiler/tests/expected_profile.ap.hex @@ -0,0 +1,14 @@ +# Copyright (c) 2021-2025 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +2e 61 70 00 30 30 31 00 07 00 00 00 07 00 00 00 19 00 00 00 01 00 00 00 00 01 00 00 00 11 00 00 00 01 00 00 00 01 00 00 00 00 01 00 00 00 1c 00 00 00 c0 2a 6a b6 00 00 00 00 20 01 00 00 04 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 bd 00 00 00 b0 00 00 00 06 00 00 00 48 00 00 00 02 00 00 00 01 00 00 00 20 00 00 00 02 00 00 00 11 00 00 00 00 00 00 00 0f 00 00 00 00 00 00 00 04 00 00 00 01 00 00 00 18 00 00 00 0c 00 00 00 11 00 00 00 00 00 00 00 61 01 00 00 3a 01 00 00 03 00 00 00 d0 00 00 00 01 00 00 00 03 00 00 00 78 00 00 00 16 00 00 00 b0 00 00 00 00 00 00 00 00 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 1a 00 00 00 e5 00 00 00 00 00 00 00 00 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 1e 00 00 00 06 01 00 00 00 00 00 00 00 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 00 00 00 00 ff ff ff ff 02 00 00 00 03 00 00 00 48 00 00 00 13 00 00 00 01 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 26 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 diff --git a/static_core/compiler/tests/profiling_saver_test.cpp b/static_core/compiler/tests/profiling_saver_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9daf724b638823a9910c413629a6ee93ebf8342c --- /dev/null +++ b/static_core/compiler/tests/profiling_saver_test.cpp @@ -0,0 +1,184 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "unit_test.h" +#include "panda_runner.h" +#include "libpandabase/os/filesystem.h" +#include "utils/string_helpers.h" + +namespace ark::test { +// NOLINTBEGIN(readability-magic-numbers) +class ProfilingSaverTest : public ::testing::Test {}; + +static constexpr auto SOURCE = R"( +.record E {} +.record A {} +.record B {} +.record C {} + +.function i32 A.bar(A a0, i32 a1) { + ldai 0x10 + jge a1, jump_label_0 + lda a1 + return +jump_label_0: + newobj v0, E + throw v0 +} + +.function i32 A.foo(A a0) { + ldai 0x1 + return +} + +.function i32 B.foo(B a0) { + ldai 0x2 + return +} + +.function i32 C.foo(C a0) { + ldai 0x3 + return +} + +.function void test() { + movi v0, 0x20 + movi v1, 0x0 + newobj v4, A + newobj v5, B + newobj v6, C +jump_label_4: + lda v1 + jge v0, jump_label_3 + call.virt A.foo, v4 + call.virt B.foo, v5 + call.virt C.foo, v6 +try_begin: + call.short A.bar, v4, v1 +try_end: + jmp handler_begin_label_0_0 +handler_begin_label_0_0: + inci v1, 0x1 + jmp jump_label_4 +jump_label_3: + return.void + +.catchall try_begin, try_end, handler_begin_label_0_0 +} + +.function void main() { + call.short test + return.void +} +)"; + +std::vector HexStringToBytes(const std::string &hex) +{ + std::vector bytes; + for (size_t i = 0; i + 1 < hex.size(); i += 2) { // CC-OFF(G.CNS.02-CPP) step length is 2 + std::string byteStr = hex.substr(i, 2); + bytes.push_back(static_cast(std::stoul(byteStr, nullptr, 16))); // CC-OFF(G.CNS.02-CPP) hexadecimal + } + return bytes; +} + +std::string ReadHexFile(const std::string &path) +{ + std::ifstream file(path); + std::string hex; + std::string line; + while (std::getline(file, line)) { + if (line.empty() || line[0] == '#') { + continue; + } + for (size_t i = 0; i + 1 < line.size(); ++i) { + if (std::isxdigit(line[i]) != 0 && std::isxdigit(line[i + 1]) != 0) { + hex += line[i]; + hex += line[i + 1]; + ++i; // CC-OFF(G.EXP.41-CPP) necessary operation + } + } + } + return hex; +} + +std::vector ReadBinaryFile(const std::string &path) +{ + std::ifstream file(path, std::ios::binary); + return {std::istreambuf_iterator(file), {}}; +} + +bool CompareBinaryAndHexFiles(const std::string &binaryPath, const std::string &hexPath) +{ + auto binary = ReadBinaryFile(binaryPath); + auto hex = HexStringToBytes(ReadHexFile(hexPath)); + return binary == hex; +} + +TEST_F(ProfilingSaverTest, ProfilingFileSaveCpp) +{ + PandaRunner runner; + runner.GetRuntimeOptions().SetShouldLoadBootPandaFiles(false); + runner.GetRuntimeOptions().SetShouldInitializeIntrinsics(false); + runner.GetRuntimeOptions().SetCompilerProfilingThreshold(0U); + runner.GetRuntimeOptions().SetInterpreterType("cpp"); + + uint32_t tid = os::thread::GetCurrentThreadId(); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + std::string pgoFileName = helpers::string::Format("tmpfile1_%04x.ap", tid); + // CC-OFFNXT(G.NAM.03-CPP) project code style + static const std::string LOCATION = "/tmp"; + // CC-OFFNXT(G.NAM.03-CPP) project code style + static const std::string PGO_FILE_PATH = LOCATION + "/" + pgoFileName; + + runner.GetRuntimeOptions().SetProfilesaverEnabled(true); + runner.GetRuntimeOptions().SetProfileOutput(PGO_FILE_PATH); + + auto runtime = runner.CreateRuntime(); + runner.Run(runtime, SOURCE, std::vector {}); + Runtime::Destroy(); + ASSERT_TRUE(os::IsFileExists(PGO_FILE_PATH)); + std::filesystem::path currentFile = __FILE__; + static const std::string PGO_HEX_FILE_PATH = currentFile.parent_path() / "expected_profile.ap.hex"; + ASSERT_TRUE(CompareBinaryAndHexFiles(PGO_FILE_PATH, PGO_HEX_FILE_PATH)); +} + +TEST_F(ProfilingSaverTest, ProfilingFileSave) +{ + PandaRunner runner; + runner.GetRuntimeOptions().SetShouldLoadBootPandaFiles(false); + runner.GetRuntimeOptions().SetShouldInitializeIntrinsics(false); + runner.GetRuntimeOptions().SetCompilerProfilingThreshold(0U); + + uint32_t tid = os::thread::GetCurrentThreadId(); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + std::string pgoFileName = helpers::string::Format("tmpfile2_%04x.ap", tid); + // CC-OFFNXT(G.NAM.03-CPP) project code style + static const std::string LOCATION = "/tmp"; + // CC-OFFNXT(G.NAM.03-CPP) project code style + static const std::string PGO_FILE_PATH = LOCATION + "/" + pgoFileName; + runner.GetRuntimeOptions().SetProfilesaverEnabled(true); + runner.GetRuntimeOptions().SetProfileOutput(PGO_FILE_PATH); + + auto runtime = runner.CreateRuntime(); + runner.Run(runtime, SOURCE, std::vector {}); + Runtime::Destroy(); + ASSERT_TRUE(os::IsFileExists(PGO_FILE_PATH)); + std::filesystem::path currentFile = __FILE__; + static const std::string PGO_HEX_FILE_PATH = currentFile.parent_path() / "expected_profile.ap.hex"; + ASSERT_TRUE(CompareBinaryAndHexFiles(PGO_FILE_PATH, PGO_HEX_FILE_PATH)); +} +// NOLINTEND(readability-magic-numbers) +} // namespace ark::test diff --git a/static_core/plugins/ets/tests/CMakeLists.txt b/static_core/plugins/ets/tests/CMakeLists.txt index 2b09877afeb634a5886dba035dfda245f92d2514..502b3d32b4fde0f53c958d2f0cb314871748649e 100644 --- a/static_core/plugins/ets/tests/CMakeLists.txt +++ b/static_core/plugins/ets/tests/CMakeLists.txt @@ -193,6 +193,7 @@ function(run_int_ets_code TARGET WORK_DIR) set(arkopts_cpp --compiler-enable-jit=false --interpreter-type=cpp + --profilesaver-enabled=true ${ARG_RUNTIME_EXTRA_OPTIONS} ) run_ets_code_impl(${TARGET}-cpp ${WORK_DIR} "${ARG_SOURCES}" @@ -206,6 +207,7 @@ function(run_int_ets_code TARGET WORK_DIR) set(arkopts_irtoc --compiler-enable-jit=false --interpreter-type=irtoc + --profilesaver-enabled=true ${ARG_RUNTIME_EXTRA_OPTIONS} ) run_ets_code_impl(${TARGET}-irtoc ${WORK_DIR} "${ARG_SOURCES}" @@ -220,6 +222,7 @@ function(run_int_ets_code TARGET WORK_DIR) set(arkopts_llvm --compiler-enable-jit=false --interpreter-type=llvm + --profilesaver-enabled=true ${ARG_RUNTIME_EXTRA_OPTIONS} ) run_ets_code_impl(${TARGET}-llvmirtoc ${WORK_DIR} "${ARG_SOURCES}" diff --git a/static_core/runtime/BUILD.gn b/static_core/runtime/BUILD.gn index 81d006511386bb4e48754603c5f5f2f63a354554..bad51039243eb444914add879cdc09e5bcfc7475 100644 --- a/static_core/runtime/BUILD.gn +++ b/static_core/runtime/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2024 Huawei Device Co., Ltd. +# Copyright (c) 2021-2025 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -162,6 +162,8 @@ ohos_source_set("libarkruntime_set_static") { "interpreter/interpreter.cpp", "interpreter/runtime_interface.cpp", "intrinsics.cpp", + "jit/libprofile/pgo_file_builder.cpp", + "jit/profiling_saver.cpp", "language_context.cpp", "loadable_agent.cpp", "lock_order_graph.cpp", diff --git a/static_core/runtime/CMakeLists.txt b/static_core/runtime/CMakeLists.txt index d4fe62df33527bea214d79901b5823fad7ae787e..c28b5d6661a06c8a84852104aba96ca460f377dd 100644 --- a/static_core/runtime/CMakeLists.txt +++ b/static_core/runtime/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2024 Huawei Device Co., Ltd. +# Copyright (c) 2021-2025 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -129,6 +129,8 @@ set(SOURCES signal_handler.cpp profilesaver/profile_saver.cpp profilesaver/profile_dump_info.cpp + jit/libprofile/pgo_file_builder.cpp + jit/profiling_saver.cpp cha.cpp runtime_helpers.cpp handle_scope.cpp diff --git a/static_core/runtime/jit/libprofile/aot_profiling_data.h b/static_core/runtime/jit/libprofile/aot_profiling_data.h new file mode 100644 index 0000000000000000000000000000000000000000..791d0fdd7202480b92253a8ea4b5178910c361fb --- /dev/null +++ b/static_core/runtime/jit/libprofile/aot_profiling_data.h @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AOT_PROFILING_DATA_H +#define AOT_PROFILING_DATA_H + +#include "utils/span.h" +#include "runtime/include/mem/panda_string.h" +#include "runtime/include/mem/panda_containers.h" +#include "runtime/jit/profiling_data.h" + +#include +#include + +namespace ark::pgo { +using PandaFileIdxType = int32_t; // >= -1 + +class AotProfilingData { +public: + class AotCallSiteInlineCache; + class AotBranchData; + class AotThrowData; + class AotMethodProfilingData { // NOLINT(cppcoreguidelines-special-member-functions) + public: + AotMethodProfilingData(uint32_t methodIdx, uint32_t classIdx, uint32_t inlineCaches, uint32_t branchData, + uint32_t throwData) + : methodIdx_(methodIdx), + classIdx_(classIdx), + inlineCaches_(inlineCaches), + branchData_(branchData), + throwData_(throwData) + { + } + ~AotMethodProfilingData() = default; + + Span GetInlineCaches() + { + return Span(inlineCaches_.data(), inlineCaches_.size()); + } + + Span GetBranchData() + { + return Span(branchData_.data(), branchData_.size()); + } + + Span GetThrowData() + { + return Span(throwData_.data(), throwData_.size()); + } + + uint32_t GetMethodIdx() const + { + return methodIdx_; + } + + uint32_t GetClassIdx() const + { + return classIdx_; + } + + private: + uint32_t methodIdx_; + uint32_t classIdx_; + + PandaVector inlineCaches_; + PandaVector branchData_; + PandaVector throwData_; + }; + +#pragma pack(push, 4) + class AotCallSiteInlineCache { // NOLINT(cppcoreguidelines-pro-type-member-init) + public: + static constexpr size_t CLASSES_COUNT = 4; // CC-OFF(G.NAM.03-CPP) project code style + static constexpr int32_t MEGAMORPHIC_FLAG = 0xFFFFFFFF; // CC-OFF(G.NAM.03-CPP) project code style + + void SetBytecodePc(std::atomic_uintptr_t pcPtr) + { + pc_ = pcPtr; + } + + void InitClasses() + { + std::fill(classes_.begin(), classes_.end(), std::make_pair(0, -1)); + } + + uint32_t GetClassesSize() + { + return classes_.size(); + } + + std::pair GetInlineCache(uint32_t idx) + { + return classes_[idx]; + } + + void SetInlineCache(uint32_t idx, uint32_t classIdx, PandaFileIdxType fileIdx) + { + classes_[idx] = std::make_pair(classIdx, fileIdx); + } + + private: + uint32_t pc_; + std::array, CLASSES_COUNT> classes_; + }; + + class AotBranchData { + public: + void SetBranchData(uint32_t pc, uint64_t taken, uint64_t notTaken) + { + pc_ = pc; + taken_ = taken; + notTaken_ = notTaken; + } + + private: + uint32_t pc_; + uint64_t taken_; + uint64_t notTaken_; + }; + + class AotThrowData { + public: + void SetThrowData(uint32_t pc, uint64_t taken) + { + pc_ = pc; + taken_ = taken; + } + + private: + uint32_t pc_; + uint64_t taken_; + }; +#pragma pack(pop) + +public: + PandaMap &GetPandaFileMap() + { + return pandaFileMap_; + } + + PandaMap &GetPandaFileMapReverse() + { + return pandaFileMapRev_; + } + + using MethodsMap = PandaMap; + PandaMap &GetAllMethods() + { + return allMethodsMap_; + } + + int32_t GetPandaFileIdxByName(const std::string &pandaFileName) + { + auto pfIdx = pandaFileMap_.find(pandaFileName); + if (pfIdx == pandaFileMap_.end()) { + return -1; + } + return pfIdx->second; + } + + void AddPandaFiles(PandaUnorderedSet &profiledPandaFiles) + { + uint32_t count = 0; + for (auto &pandaFile : profiledPandaFiles) { + pandaFileMap_.insert(std::make_pair(pandaFile, count)); + pandaFileMapRev_.insert(std::make_pair(count, pandaFile)); + allMethodsMap_.insert(std::make_pair(count, MethodsMap())); + count++; + } + } + + void AddMethod(PandaFileIdxType pfIdx, uint32_t methodIdx, AotMethodProfilingData &&profData) + { + allMethodsMap_[pfIdx].insert(std::make_pair(methodIdx, profData)); + } + +private: + PandaMap pandaFileMap_; + PandaMap pandaFileMapRev_; + PandaMap allMethodsMap_; +}; +} // namespace ark::pgo + +#endif // AOT_PROFILING_DATA_H diff --git a/static_core/runtime/jit/libprofile/pgo_file_builder.cpp b/static_core/runtime/jit/libprofile/pgo_file_builder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c16d9242e5870f5e59ca7cdf16f95d9e661d0ff5 --- /dev/null +++ b/static_core/runtime/jit/libprofile/pgo_file_builder.cpp @@ -0,0 +1,410 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pgo_file_builder.h" +#include "zlib.h" +#include "utils/logger.h" + +#include + +namespace ark::pgo { +// NOLINTNEXTLINE(readability-magic-numbers) +static_assert(sizeof(PgoHeader) == 24, "PgoHeader is 24 bytes"); +// NOLINTNEXTLINE(readability-magic-numbers) +static_assert(sizeof(PandaFilesSectionHeader) == 8, "PandaFiles section header is 8 bytes"); +// NOLINTNEXTLINE(readability-magic-numbers) +static_assert(sizeof(PandaFileInfoHeader) == 8, + "Header of each item of PandaFileInfo in PandaFiles section is 8 bytes"); +// NOLINTNEXTLINE(readability-magic-numbers) +static_assert(sizeof(SectionsInfoSectionHeader) == 8, "Header of SectionsInfo section is 8 bytes"); +// NOLINTNEXTLINE(readability-magic-numbers) +static_assert(sizeof(SectionInfo) == 20, "Each item of section info in SectionsInfo section is 20 bytes"); +// NOLINTNEXTLINE(readability-magic-numbers) +static_assert(sizeof(MethodsHeader) == 8, "Header of Methods section is 8 bytes"); +// NOLINTNEXTLINE(readability-magic-numbers) +static_assert(sizeof(MethodDataHeader) == 16, "Header of one method data sub-section is 16 bytes"); +// NOLINTNEXTLINE(readability-magic-numbers) +static_assert(sizeof(AotProfileDataHeader) == 12, "Header of one profile data sub-section is 12 bytes"); + +uint32_t AotPgoFile::WritePandaFilesSection(std::ofstream &fd, PandaMap &pandaFileMap) +{ + PandaFilesSectionHeader sectionHeader = {static_cast(pandaFileMap.size()), + static_cast(sizeof(PandaFilesSectionHeader))}; + + for (auto &fileInfo : pandaFileMap) { + sectionHeader.sectionSize += sizeof(PandaFileInfoHeader) + fileInfo.second.size() + 1; + } + AotPgoFile::Buffer buffer(sectionHeader.sectionSize); + + uint32_t currPos = 0; + if (buffer.CopyToBuffer(§ionHeader, sizeof(sectionHeader), currPos) == 0) { + return 0; + } + currPos += sizeof(sectionHeader); + + for (auto &fileInfo : pandaFileMap) { + PandaFileInfoHeader fileInfoHeader = {FileType::BOOT, static_cast(fileInfo.second.size() + 1)}; + if (buffer.CopyToBuffer(&fileInfoHeader, sizeof(fileInfoHeader), currPos) == 0) { + return 0; + } + currPos += sizeof(fileInfoHeader); + if (buffer.CopyToBuffer(fileInfo.second.data(), fileInfoHeader.fileNameLen, currPos) == 0) { + return 0; + } + currPos += fileInfoHeader.fileNameLen; + } + + buffer.WriteBuffer(fd); + return sectionHeader.sectionSize; +} + +uint32_t AotPgoFile::WriteSectionInfosSection(std::ofstream &fd, PandaVector §ionInfos) +{ + SectionsInfoSectionHeader header = {static_cast(sectionInfos.size()), + static_cast(sizeof(SectionsInfoSectionHeader))}; + header.sectionSize += sectionInfos.size() * sizeof(SectionInfo); + + AotPgoFile::Buffer buffer(header.sectionSize); + + auto currPos = 0; + if (buffer.CopyToBuffer(&header, sizeof(SectionsInfoSectionHeader), currPos) == 0) { + return 0; + } + currPos += sizeof(SectionsInfoSectionHeader); + + for (auto &secInfo : sectionInfos) { + if (buffer.CopyToBuffer(&secInfo, sizeof(SectionInfo), currPos) == 0) { + return 0; + } + currPos += sizeof(SectionInfo); + } + + buffer.WriteBuffer(fd); + return header.sectionSize; +} + +uint32_t AotPgoFile::WriteAllMethodsSections(std::ofstream &fd, FileToMethodsMap &methods) +{ + uint32_t totalSize = 0; + for (auto §ion : methods) { + uint32_t checkSum = adler32(0L, Z_NULL, 0); + auto sectionSize = WriteMethodsSection(fd, section.first, &checkSum, section.second); + if (sectionSize == 0) { + continue; + } + + SectionInfo sectionInfo = {checkSum, 0, sectionSize, SectionType::METHODS, section.first}; + sectionInfos_.push_back(sectionInfo); + + totalSize += sectionSize; + } + return totalSize; +} + +uint32_t AotPgoFile::GetMaxMethodSectionSize(AotProfilingData::MethodsMap &methods) +{ + uint32_t size = 0; + for (auto &[methodIdx, methodProfData] : methods) { + bool empty = true; + auto inlineCaches = methodProfData.GetInlineCaches(); + auto branches = methodProfData.GetBranchData(); + auto throws = methodProfData.GetThrowData(); + if (!inlineCaches.empty()) { + size += sizeof(AotProfileDataHeader) + inlineCaches.SizeBytes(); + empty = false; + } + if (!branches.empty()) { + size += sizeof(AotProfileDataHeader) + branches.SizeBytes(); + empty = false; + } + if (!throws.empty()) { + size += sizeof(AotProfileDataHeader) + throws.SizeBytes(); + empty = false; + } + if (!empty) { + size += sizeof(MethodDataHeader); + } + } + if (size > 0) { + size += sizeof(MethodsHeader); + } + return size; +} + +uint32_t AotPgoFile::GetMethodSectionProf(AotProfilingData::MethodsMap &methods) +{ + uint32_t savedType = 0; + for (auto &[methodIdx, methodProfData] : methods) { + auto inlineCaches = methodProfData.GetInlineCaches(); + auto branches = methodProfData.GetBranchData(); + auto throws = methodProfData.GetThrowData(); + if (!inlineCaches.empty()) { + savedType |= ProfileType::VCALL; + } + if (!branches.empty()) { + savedType |= ProfileType::BRANCH; + } + if (!throws.empty()) { + savedType |= ProfileType::THROW; + } + } + return savedType; +} + +uint32_t AotPgoFile::WriteMethodsSection(std::ofstream &fd, int32_t pandaFileIdx, uint32_t *checkSum, + AotProfilingData::MethodsMap &methods) +{ + MethodsHeader methodsSectionHeader = {static_cast(methods.size()), pandaFileIdx}; + + uint32_t sectionSize = GetMaxMethodSectionSize(methods); + Buffer buffer(sectionSize); + + uint32_t writtenBytes = 0; + uint32_t currPos = 0; + currPos += sizeof(MethodsHeader); + + for (auto &[methodIdx, methodProfData] : methods) { + writtenBytes += WriteMethodSubSection(currPos, &buffer, methodIdx, methodProfData); + } + + if (writtenBytes == 0) { + return 0; + } + + if (buffer.CopyToBuffer(&methodsSectionHeader, sizeof(MethodsHeader), 0) == 0) { + return 0; + } + + buffer.WriteBuffer(fd); + writtenBytes += sizeof(MethodsHeader); + + *checkSum = adler32(*checkSum, buffer.GetBuffer().data(), writtenBytes); + + return writtenBytes; +} + +uint32_t AotPgoFile::WriteMethodSubSection(uint32_t &currPos, Buffer *buffer, uint32_t methodIdx, + AotProfilingData::AotMethodProfilingData &methodProfData) +{ + MethodDataHeader methodHeader = {methodIdx, methodProfData.GetClassIdx(), ProfileType::NO, + sizeof(MethodDataHeader)}; + + auto currMethodHeaderPos = currPos; + currPos += sizeof(MethodDataHeader); + uint32_t writtenBytes = 0; + + auto inlineCaches = methodProfData.GetInlineCaches(); + if (!inlineCaches.empty()) { + methodHeader.savedType |= ProfileType::VCALL; + auto icSize = WriteInlineCachesToStream(currPos, buffer, inlineCaches); + methodHeader.chunkSize += icSize; + writtenBytes += icSize; + currPos += icSize; + } + + auto branches = methodProfData.GetBranchData(); + if (!branches.empty()) { + methodHeader.savedType |= ProfileType::BRANCH; + auto brSize = WriteBranchDataToStream(currPos, buffer, branches); + methodHeader.chunkSize += brSize; + writtenBytes += brSize; + currPos += brSize; + } + + auto throws = methodProfData.GetThrowData(); + if (!throws.empty()) { + methodHeader.savedType |= ProfileType::THROW; + auto thSize = WriteThrowDataToStream(currPos, buffer, throws); + methodHeader.chunkSize += thSize; + writtenBytes += thSize; + currPos += thSize; + } + + if (methodHeader.chunkSize > sizeof(MethodDataHeader)) { + if (buffer->CopyToBuffer(&methodHeader, sizeof(MethodDataHeader), currMethodHeaderPos) == 0) { + return 0; + } + writtenBytes += sizeof(MethodDataHeader); + } else { + currPos -= sizeof(MethodDataHeader); + } + return writtenBytes; +} + +uint32_t AotPgoFile::WriteInlineCachesToStream(uint32_t streamBegin, Buffer *buffer, + Span inlineCaches) +{ + AotProfileDataHeader icHeader = {ProfileType::VCALL, static_cast(inlineCaches.size()), + static_cast(sizeof(AotProfileDataHeader) + inlineCaches.SizeBytes())}; + uint32_t writtenBytes = 0; + auto currPos = streamBegin; + + if (buffer->CopyToBuffer(&icHeader, sizeof(AotProfileDataHeader), currPos) == 0) { + return 0; + } + currPos += sizeof(AotProfileDataHeader); + writtenBytes += sizeof(AotProfileDataHeader); + + if (buffer->CopyToBuffer(inlineCaches.data(), inlineCaches.SizeBytes(), currPos) == 0) { + return 0; + } + writtenBytes += inlineCaches.SizeBytes(); + return writtenBytes; +} + +uint32_t AotPgoFile::WriteBranchDataToStream(uint32_t streamBegin, Buffer *buffer, + Span branches) +{ + AotProfileDataHeader brHeader = {ProfileType::BRANCH, static_cast(branches.size()), + static_cast(sizeof(AotProfileDataHeader) + branches.SizeBytes())}; + uint32_t writtenBytes = 0; + auto currPos = streamBegin; + + if (buffer->CopyToBuffer(&brHeader, sizeof(AotProfileDataHeader), currPos) == 0) { + return 0; + } + currPos += sizeof(AotProfileDataHeader); + writtenBytes += sizeof(AotProfileDataHeader); + + if (buffer->CopyToBuffer(branches.data(), branches.SizeBytes(), currPos) == 0) { + return 0; + } + writtenBytes += branches.SizeBytes(); + return writtenBytes; +} + +uint32_t AotPgoFile::WriteThrowDataToStream(uint32_t streamBegin, Buffer *buffer, + Span throws) +{ + AotProfileDataHeader thHeader = {ProfileType::THROW, static_cast(throws.size()), + static_cast(sizeof(AotProfileDataHeader) + throws.SizeBytes())}; + uint32_t writtenBytes = 0; + auto currPos = streamBegin; + + if (buffer->CopyToBuffer(&thHeader, sizeof(AotProfileDataHeader), currPos) == 0) { + return 0; + } + currPos += sizeof(AotProfileDataHeader); + writtenBytes += sizeof(AotProfileDataHeader); + + if (buffer->CopyToBuffer(throws.data(), throws.SizeBytes(), currPos) == 0) { + return 0; + } + writtenBytes += throws.SizeBytes(); + return writtenBytes; +} + +uint32_t AotPgoFile::GetSectionNumbers(FileToMethodsMap &methods) +{ + uint32_t count = 0; + for (auto &method : methods) { + if (GetMaxMethodSectionSize(method.second) > 0) { + count++; + } + } + return count; +} + +uint32_t AotPgoFile::GetSavedTypes(FileToMethodsMap &allMethodsMap) +{ + uint32_t savedType = 0; + for (auto &methodMap : allMethodsMap) { + savedType |= GetMethodSectionProf(methodMap.second); + if (savedType == PROFILE_TYPE) { + break; + } + } + return savedType; +} + +// CC-OFFNXT(G.FUN.01-CPP) Decreasing the number of arguments will decrease the clarity of the code. +uint32_t AotPgoFile::WriteFileHeader(std::ofstream &fd, const std::array &magic, + const std::array &version, uint32_t versionPType, + uint32_t savedPType, PandaString &classCtxStr) +{ + uint32_t cha = classCtxStr.size() + 1; + uint32_t headerSize = sizeof(PgoHeader) + cha; + PgoHeader header = {magic, version, versionPType, savedPType, headerSize, cha}; + Buffer buffer(headerSize); + + if (buffer.CopyToBuffer(&header, sizeof(PgoHeader), 0) == 0) { + return 0; + } + + if (buffer.CopyToBuffer(classCtxStr.c_str(), cha, sizeof(PgoHeader)) == 0) { + return 0; + } + + buffer.WriteBuffer(fd); + return header.headerSize; +} + +// CC-OFFNXT(G.PRE.06) code generation +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define CheckAndAddBytes(fd, filePath, checkBytes, writtenBytes) \ + { \ + /* CC-OFFNXT(G.PRE.02) part name */ \ + if (checkBytes == 0) { \ + /* CC-OFFNXT(G.PRE.02) part name */ \ + fd.close(); \ + /* CC-OFFNXT(G.PRE.02) part name */ \ + if (remove(filePath.data()) == -1) { \ + /* CC-OFFNXT(G.PRE.02) part name */ \ + LOG(ERROR, RUNTIME) << "Failed to remove file: " << filePath; \ + } \ + /* CC-OFFNXT(G.PRE.05) function gen */ \ + return 0; \ + } \ + /* CC-OFFNXT(G.PRE.02) part name */ \ + writtenBytes += checkBytes; \ + } + +uint32_t AotPgoFile::Save(const PandaString &fileName, AotProfilingData *profObject, PandaString &classCtxStr) +{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + std::ofstream fd(fileName.data(), std::ios::binary | std::ios::out); + if (!fd.is_open()) { + return 0; + } + uint32_t writtenBytes = 0; + + auto savedProf = GetSavedTypes(profObject->GetAllMethods()); + auto headerBytes = WriteFileHeader(fd, MAGIC, VERSION, PROFILE_TYPE, savedProf, classCtxStr); + CheckAndAddBytes(fd, fileName, headerBytes, writtenBytes); + + auto pandaFilesBytes = WritePandaFilesSection(fd, profObject->GetPandaFileMapReverse()); + CheckAndAddBytes(fd, fileName, pandaFilesBytes, writtenBytes); + + uint32_t offset = writtenBytes; + auto sectionNum = GetSectionNumbers(profObject->GetAllMethods()); + auto sectionInfosSize = GetSectionInfosSectionSize(sectionNum); + + fd.seekp(sectionInfosSize, std::ios::cur); + + auto methodsBytes = WriteAllMethodsSections(fd, profObject->GetAllMethods()); + CheckAndAddBytes(fd, fileName, methodsBytes, writtenBytes); + + fd.seekp(offset, std::ios::beg); + auto sectionInfoBytes = WriteSectionInfosSection(fd, sectionInfos_); + CheckAndAddBytes(fd, fileName, sectionInfoBytes, writtenBytes); + + if (chmod(fileName.data(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) { + return 0; + } + + return writtenBytes; +} +#undef CheckAndAddBytes +} // namespace ark::pgo diff --git a/static_core/runtime/jit/libprofile/pgo_file_builder.h b/static_core/runtime/jit/libprofile/pgo_file_builder.h new file mode 100644 index 0000000000000000000000000000000000000000..be87144397f9a11f4d771ebdae2143e86c76be36 --- /dev/null +++ b/static_core/runtime/jit/libprofile/pgo_file_builder.h @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PGO_FILE_BUILDER_H +#define PGO_FILE_BUILDER_H + +#include "pgo_header.h" +#include "aot_profiling_data.h" +#include "utils/span.h" +#include "runtime/include/mem/panda_string.h" +#include "runtime/include/mem/panda_containers.h" + +namespace ark::pgo { + +class AotPgoFile { +public: + class Buffer { + public: + Buffer() : buffer_(0), bufferSize_(0), currSize_(0) {} + + explicit Buffer(uint32_t size) + : buffer_(size > 1_MB ? LOG(FATAL, RUNTIME) << "Allocated of huge memory is not allowed" : size), + bufferSize_(size), + currSize_(0) + { + } + + Span GetBuffer() + { + Span buffer(buffer_.data(), bufferSize_); + return buffer; + } + + template + uint32_t CopyToBuffer(const T *from, size_t size, size_t beginIndex) + { + const char *data = reinterpret_cast(from); + if (beginIndex >= bufferSize_) { + return 0; + } + auto maxSize {bufferSize_ - beginIndex}; + errno_t res = memcpy_s(&buffer_[beginIndex], maxSize, data, size); + if (res != 0) { + return 0; + } + currSize_ += size; + return size; + } + + void WriteBuffer(std::ofstream &fd) + { + fd.write(reinterpret_cast(buffer_.data()), currSize_); + + if (!fd) { + return; + } + } + + private: + PandaVector buffer_; + uint32_t bufferSize_; + uint32_t currSize_; + }; + + uint32_t Save(const PandaString &fileName, AotProfilingData *profObject, PandaString &classCtxStr); + +private: + static constexpr std::array MAGIC = {'.', 'a', 'p', '\0'}; // CC-OFF(G.NAM.03-CPP) project code style + static constexpr std::array VERSION = {'0', '0', '1', '\0'}; // CC-OFF(G.NAM.03-CPP) project code style + static constexpr uint32_t MAGIC_SIZE = 4; // CC-OFF(G.NAM.03-CPP) project code style + static constexpr uint32_t VERSION_SIZE = 4; // CC-OFF(G.NAM.03-CPP) project code style + + enum ProfileType : uint32_t { NO = 0x00, VCALL = 0x01, BRANCH = 0x02, THROW = 0x04, ALL = 0xFFFFFFFF }; + // CC-OFFNXT(G.NAM.03-CPP) project code style + static constexpr uint32_t PROFILE_TYPE = ProfileType::VCALL | ProfileType::BRANCH | ProfileType::THROW; + + enum FileType : uint32_t { PFILE = 0, BOOT = 1, APP = 2 }; + + enum SectionType : uint32_t { + UNKNOW_SECTION = 0, + FILE_HEADER = 1, + PANDA_FILES = 2, + SECTIONS_INFO = 3, + METHODS = 4 + }; + + // CC-OFFNXT(G.FUN.01-CPP) Decreasing the number of arguments will decrease the clarity of the code. + static uint32_t WriteFileHeader(std::ofstream &fd, const std::array &magic, + const std::array &version, uint32_t versionPType, + uint32_t savedPType, PandaString &classCtxStr); + + static uint32_t WriteSectionInfosSection(std::ofstream &fd, PandaVector §ionInfos); + + static uint32_t GetSectionInfosSectionSize(uint32_t sectionNumer) + { + return sectionNumer * sizeof(SectionInfo) + sizeof(SectionsInfoSectionHeader); + } + + static uint32_t GetMaxMethodSectionSize(AotProfilingData::MethodsMap &methods); + static uint32_t GetMethodSectionProf(AotProfilingData::MethodsMap &methods); + static uint32_t WriteInlineCachesToStream(uint32_t streamBegin, Buffer *buffer, + Span inlineCaches); + static uint32_t WriteBranchDataToStream(uint32_t streamBegin, Buffer *buffer, + Span branches); + static uint32_t WriteThrowDataToStream(uint32_t streamBegin, Buffer *buffer, + Span throws); + uint32_t WriteMethodsSection(std::ofstream &fd, int32_t pandaFileIdx, uint32_t *checkSum, + AotProfilingData::MethodsMap &methods); + uint32_t WriteMethodSubSection(uint32_t &currPos, Buffer *buffer, uint32_t methodIdx, + AotProfilingData::AotMethodProfilingData &methodProfData); + + using FileToMethodsMap = PandaMap; + uint32_t WriteAllMethodsSections(std::ofstream &fd, FileToMethodsMap &methods); + uint32_t WritePandaFilesSection(std::ofstream &fd, PandaMap &pandaFileMap); + uint32_t GetSectionNumbers(FileToMethodsMap &methods); + uint32_t GetSavedTypes(FileToMethodsMap &allMethodsMap); + + PandaVector sectionInfos_; +}; + +} // namespace ark::pgo + +#endif // PGO_FILE_BUILDER_H diff --git a/static_core/runtime/jit/libprofile/pgo_header.h b/static_core/runtime/jit/libprofile/pgo_header.h new file mode 100644 index 0000000000000000000000000000000000000000..9f7a0127a63c5146de401ef9bb14842a9bdadf32 --- /dev/null +++ b/static_core/runtime/jit/libprofile/pgo_header.h @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PGO_HEADER_H +#define PGO_HEADER_H + +#include +#include + +namespace ark::pgo { + +constexpr uint32_t PGO_HEADER_MAGIC_SIZE = 4; +constexpr uint32_t PGO_HEADER_VERSION_SIZE = 4; + +struct PgoHeader { + alignas(alignof(uint32_t)) std::array magic; + alignas(alignof(uint32_t)) std::array version; + uint32_t versionProfileType; + uint32_t savedProfileType; + uint32_t headerSize; + uint32_t withCha; +}; + +static_assert((sizeof(PgoHeader) % sizeof(uint32_t)) == 0); +static_assert(alignof(PgoHeader) == alignof(uint32_t)); + +struct PandaFilesSectionHeader { + uint32_t numberOfFiles; + uint32_t sectionSize; +}; + +struct PandaFileInfoHeader { + uint32_t type; + uint32_t fileNameLen; +}; + +struct SectionsInfoSectionHeader { + uint32_t sectionNumber; + uint32_t sectionSize; +}; + +struct SectionInfo { + uint32_t checkSum; + uint32_t zippedSize; + uint32_t unzippedSize; + uint32_t sectionType; + int32_t pandaFileIdx; +}; + +struct MethodsHeader { + uint32_t numberOfMethods; + int32_t pandaFileIdx; +}; + +struct MethodDataHeader { + uint32_t methodIdx; + uint32_t classIdx; + uint32_t savedType; + uint32_t chunkSize; +}; + +struct AotProfileDataHeader { + uint32_t profType; + uint32_t numberOfRecords; + uint32_t chunkSize; +}; + +} // namespace ark::pgo + +#endif // PGO_HEADER_H diff --git a/static_core/runtime/jit/profiling_data.h b/static_core/runtime/jit/profiling_data.h index c62b5aad6863199d0cda9b98c097e1e96d7e1309..4c2a9e9eb272f460cf3e622b4457b4e667fbae41 100644 --- a/static_core/runtime/jit/profiling_data.h +++ b/static_core/runtime/jit/profiling_data.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -247,11 +247,21 @@ public: { } - Span GetInlineCaches() + Span GetInlineCaches() const { return inlineCaches_; } + Span GetBranchData() const + { + return branchData_; + } + + Span GetThrowData() const + { + return throwData_; + } + CallSiteInlineCache *FindInlineCache(uintptr_t pc) { auto ics = GetInlineCaches(); diff --git a/static_core/runtime/jit/profiling_saver.cpp b/static_core/runtime/jit/profiling_saver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e4c0eaae1b82289c6b1f7f668d259470fffc0974 --- /dev/null +++ b/static_core/runtime/jit/profiling_saver.cpp @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "profiling_saver.h" + +namespace ark { +void ProfilingSaver::UpdateInlineCaches(pgo::AotProfilingData::AotCallSiteInlineCache *ic, + std::vector &runtimeClasses, pgo::AotProfilingData *profileData) +{ + for (uint32_t i = 0; i < runtimeClasses.size();) { + auto storedClass = ic->GetInlineCache(i); + + auto runtimeCls = runtimeClasses[i]; + // Megamorphic Call, update first item, and return. + if (i == 0 && CallSiteInlineCache::IsMegamorphic(runtimeCls)) { + ic->SetInlineCache(i, ic->MEGAMORPHIC_FLAG, -1); + return; + } + + if (storedClass.first == runtimeCls->GetFileId().GetOffset()) { + continue; + } + + auto clsPandaFile = runtimeCls->GetPandaFile()->GetFullFileName(); + auto pfIdx = profileData->GetPandaFileIdxByName(clsPandaFile); + auto clsIdx = runtimeCls->GetFileId().GetOffset(); + + ic->SetInlineCache(i, clsIdx, pfIdx); + i++; + } +} + +void ProfilingSaver::CreateInlineCaches(pgo::AotProfilingData::AotMethodProfilingData *profilingData, + Span &runtimeICs, pgo::AotProfilingData *profileData) +{ + auto icCount = runtimeICs.size(); + auto aotICs = profilingData->GetInlineCaches(); + for (size_t i = 0; i < icCount; i++) { + aotICs[i].SetBytecodePc(runtimeICs[i].GetBytecodePc()); + aotICs[i].InitClasses(); + auto classes = runtimeICs[i].GetClassesCopy(); + UpdateInlineCaches(&aotICs[i], classes, profileData); + } +} + +void ProfilingSaver::CreateBranchData(pgo::AotProfilingData::AotMethodProfilingData *profilingData, + Span &runtimeBranch) +{ + auto brCount = runtimeBranch.size(); + auto aotBrs = profilingData->GetBranchData(); + + for (size_t i = 0; i < brCount; i++) { + aotBrs[i].SetBranchData(runtimeBranch[i].GetPc(), runtimeBranch[i].GetTakenCounter(), + runtimeBranch[i].GetNotTakenCounter()); + } +} + +void ProfilingSaver::CreateThrowData(pgo::AotProfilingData::AotMethodProfilingData *profilingData, + Span &runtimeThrow) +{ + auto thCount = runtimeThrow.size(); + auto aotThs = profilingData->GetThrowData(); + + for (size_t i = 0; i < thCount; i++) { + aotThs[i].SetThrowData(runtimeThrow[i].GetPc(), runtimeThrow[i].GetTakenCounter()); + } +} + +void ProfilingSaver::AddMethod(pgo::AotProfilingData *profileData, const Method *method, int32_t pandaFileIdx) +{ + auto *runtimeProfData = method->GetProfilingData(); + auto runtimeICs = runtimeProfData->GetInlineCaches(); + uint32_t vcallsCount = runtimeICs.size(); + + auto runtimeBrs = runtimeProfData->GetBranchData(); + uint32_t branchCount = runtimeBrs.size(); + + auto runtimeThs = runtimeProfData->GetThrowData(); + uint32_t throwCount = runtimeThs.size(); + + auto profilingData = pgo::AotProfilingData::AotMethodProfilingData(method->GetFileId().GetOffset(), + method->GetClass()->GetFileId().GetOffset(), + vcallsCount, branchCount, throwCount); + CreateInlineCaches(&profilingData, runtimeICs, profileData); + CreateBranchData(&profilingData, runtimeBrs); + CreateThrowData(&profilingData, runtimeThs); + + auto methodIdx = method->GetFileId().GetOffset(); + profileData->AddMethod(pandaFileIdx, methodIdx, std::move(profilingData)); +} + +void ProfilingSaver::AddProfiledMethods(pgo::AotProfilingData *profileData, + PandaVector &profiledMethods) +{ + auto pfMap = profileData->GetPandaFileMap(); + for (auto method : profiledMethods) { + auto pandaFileName = method->GetPandaFile()->GetFullFileName(); + if (pfMap.find(PandaString(pandaFileName)) == pfMap.end()) { + continue; + } + auto pandaFileIdx = profileData->GetPandaFileIdxByName(pandaFileName); + AddMethod(profileData, method, pandaFileIdx); + } +} + +void ProfilingSaver::SaveProfile(const PandaString &saveFilePath, PandaString &classCtxStr, + PandaVector profiledMethods, + PandaUnorderedSet &profiledPandaFiles) +{ + pgo::AotProfilingData profData; + profData.AddPandaFiles(profiledPandaFiles); + + AddProfiledMethods(&profData, profiledMethods); + + pgo::AotPgoFile pgoFile; + auto writtenBytes = pgoFile.Save(saveFilePath, &profData, classCtxStr); + if (writtenBytes > 0) { + LOG(INFO, RUNTIME) << "Profile data saved to " << saveFilePath << " with " << writtenBytes << " bytes."; + } else { + LOG(ERROR, RUNTIME) << "Failed to save profile data to " << saveFilePath << "."; + } +} +} // namespace ark \ No newline at end of file diff --git a/static_core/runtime/jit/profiling_saver.h b/static_core/runtime/jit/profiling_saver.h new file mode 100644 index 0000000000000000000000000000000000000000..5c78c2be1e2a67229abde10bb89334e7ad6ada15 --- /dev/null +++ b/static_core/runtime/jit/profiling_saver.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PROFILING_SAVE_H +#define PROFILING_SAVE_H + +#include +#include +#include +#include "libprofile/aot_profiling_data.h" +#include "runtime/include/mem/panda_containers.h" +#include "runtime/jit/profiling_data.h" +#include "runtime/include/class.h" +#include "runtime/include/class_linker.h" +#include "runtime/jit/libprofile/pgo_file_builder.h" + +namespace ark { +class ProfilingSaver { +public: + void UpdateInlineCaches(pgo::AotProfilingData::AotCallSiteInlineCache *ic, std::vector &runtimeClasses, + pgo::AotProfilingData *profileData); + void CreateInlineCaches(pgo::AotProfilingData::AotMethodProfilingData *profilingData, + Span &runtimeICs, pgo::AotProfilingData *profileData); + void CreateBranchData(pgo::AotProfilingData::AotMethodProfilingData *profilingData, + Span &runtimeBranch); + void CreateThrowData(pgo::AotProfilingData::AotMethodProfilingData *profilingData, Span &runtimeThrow); + void AddMethod(pgo::AotProfilingData *profileData, const Method *method, int32_t pandaFileIdx); + void AddProfiledMethods(pgo::AotProfilingData *profileData, PandaVector &profiledMethods); + void SaveProfile(const PandaString &saveFilePath, PandaString &classCtxStr, + PandaVector profiledMethods, + PandaUnorderedSet &profiledPandaFiles); +}; +} // namespace ark +#endif // PROFILING_SAVE_H diff --git a/static_core/runtime/method.cpp b/static_core/runtime/method.cpp index 75fc8d785e337ac1b9d83b892a1ded62f88de37c..7c50dcb4634e8938856c3cbdd56a52284416779e 100644 --- a/static_core/runtime/method.cpp +++ b/static_core/runtime/method.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -579,6 +579,7 @@ void Method::StartProfiling() } } EVENT_INTERP_PROFILING(events::InterpProfilingAction::START, GetFullName(), vcalls.size()); + Runtime::GetCurrent()->GetClassLinker()->GetAotManager()->TryAddMethodToProfile(this); } void Method::StopProfiling() diff --git a/static_core/runtime/runtime.cpp b/static_core/runtime/runtime.cpp index 2315dd2751e22238862093ce8d6edaa8ad67535b..4512aaeac4c13a23d540316c773820af86773362 100644 --- a/static_core/runtime/runtime.cpp +++ b/static_core/runtime/runtime.cpp @@ -73,6 +73,7 @@ #include "runtime/methodtrace/trace.h" #include "trace/trace.h" #include "runtime/tests/intrusive-tests/intrusive_test_option.h" +#include "runtime/jit/profiling_saver.h" namespace ark { @@ -419,6 +420,16 @@ bool Runtime::Destroy() trace::ScopedTrace scopedTrace("Runtime shutdown"); + if (instance_->SaveProfileInfo()) { + ProfilingSaver profileSaver; + auto isAotVerifyAbsPath = instance_->GetOptions().IsAotVerifyAbsPath(); + auto classCtxStr = instance_->GetClassLinker()->GetClassContextForAot(isAotVerifyAbsPath); + auto &profiledMethods = instance_->GetClassLinker()->GetAotManager()->GetProfiledMethods(); + auto savingPath = PandaString(instance_->GetOptions().GetProfileOutput()); + auto profiledPandaFiles = instance_->GetClassLinker()->GetAotManager()->GetProfiledPandaFiles(); + profileSaver.SaveProfile(savingPath, classCtxStr, profiledMethods, profiledPandaFiles); + } + if (GetOptions().ShouldLoadBootPandaFiles()) { // Performing some actions before Runtime destroy. // For example, traversing FinalizableWeakRefList @@ -435,8 +446,6 @@ bool Runtime::Destroy() Trace::StopTracing(); } - instance_->GetPandaVM()->SaveProfileInfo(); - instance_->GetNotificationManager()->VmDeathEvent(); // Stop compiler first to make sure compile memleak doesn't occur