1 /*
2 * Copyright (c) 2025 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 <gtest/gtest.h>
17 #include <cstddef>
18 #include <cstdint>
19 #include <filesystem>
20 #include <iostream>
21 #include <string>
22 #include "include/mem/panda_containers.h"
23 #include "include/method.h"
24 #include "include/runtime.h"
25 #include "jit/libprofile/aot_profiling_data.h"
26 #include "jit/profiling_loader.h"
27 #include "jit/profiling_saver.h"
28 #include "macros.h"
29 #include "unit_test.h"
30 #include "panda_runner.h"
31 #include "runtime/jit/profiling_data.h"
32 namespace ark::test {
33
34 static constexpr auto SOURCE = R"(
35 .function void main() <static> {
36 movi v0, 0x2
37 movi v1, 0x0
38 jump_label_1: lda v1
39 jge v0, jump_label_0
40 call.short foo
41 inci v1, 0x1
42 jmp jump_label_1
43 jump_label_0: return.void
44 }
45
46 .function i32 foo() <static> {
47 movi v0, 0x64
48 movi v1, 0x0
49 mov v2, v1
50 jump_label_3: lda v2
51 jge v0, jump_label_0
52 lda v2
53 modi 0x3
54 jnez jump_label_1
55 lda v1
56 addi 0x2
57 sta v3
58 mov v1, v3
59 jmp jump_label_2
60 jump_label_1: lda v1
61 addi 0x3
62 sta v3
63 mov v1, v3
64 jump_label_2: inci v2, 0x1
65 jmp jump_label_3
66 jump_label_0: lda v1
67 return
68 }
69 )";
70
FindClass(const std::string & name)71 Class *FindClass(const std::string &name)
72 {
73 PandaString descriptor;
74 auto *thread = MTManagedThread::GetCurrent();
75 thread->ManagedCodeBegin();
76 auto cls = Runtime::GetCurrent()
77 ->GetClassLinker()
78 ->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY)
79 ->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8(name.data()), &descriptor));
80 thread->ManagedCodeEnd();
81 return cls;
82 }
83
ToTuple(const BranchData & data)84 auto ToTuple(const BranchData &data)
85 {
86 return std::make_tuple(data.GetPc(), data.GetTakenCounter(), data.GetNotTakenCounter());
87 }
88
ToTuple(const ThrowData & data)89 auto ToTuple(const ThrowData &data)
90 {
91 return std::make_tuple(data.GetPc(), data.GetTakenCounter());
92 }
93
ToTuple(const CallSiteInlineCache & data)94 auto ToTuple(const CallSiteInlineCache &data)
95 {
96 auto classIdsVec = data.GetClassesCopy();
97 std::set<ark::Class *> classIds(classIdsVec.begin(), classIdsVec.end());
98 return std::make_tuple(data.GetBytecodePc(), classIds);
99 }
100
Equals(const BranchData & lhs,const BranchData & rhs)101 bool Equals(const BranchData &lhs, const BranchData &rhs)
102 {
103 return ToTuple(lhs) == ToTuple(rhs);
104 }
105
Equals(const ThrowData & lhs,const ThrowData & rhs)106 bool Equals(const ThrowData &lhs, const ThrowData &rhs)
107 {
108 return ToTuple(lhs) == ToTuple(rhs);
109 }
110
Equals(const CallSiteInlineCache & lhs,const CallSiteInlineCache & rhs)111 bool Equals(const CallSiteInlineCache &lhs, const CallSiteInlineCache &rhs)
112 {
113 return ToTuple(lhs) == ToTuple(rhs);
114 }
115
116 template <typename T>
Equals(const Span<T> & lhs,const Span<T> & rhs)117 bool Equals(const Span<T> &lhs, const Span<T> &rhs)
118 {
119 return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(),
120 [](const T &l, const T &r) { return Equals(l, r); });
121 }
122
Equals(const ProfilingData & lhs,const ProfilingData & rhs)123 bool Equals(const ProfilingData &lhs, const ProfilingData &rhs)
124 {
125 return Equals(lhs.GetBranchData(), rhs.GetBranchData()) && Equals(lhs.GetThrowData(), rhs.GetThrowData()) &&
126 Equals(lhs.GetInlineCaches(), rhs.GetInlineCaches());
127 }
128
129 class ProfilingMergerTest : public ::testing::Test {
130 public:
CollectProfile()131 void CollectProfile()
132 {
133 auto runtime = runner_.CreateRuntime();
134 runner_.Run(runtime, SOURCE, std::vector<std::string> {});
135 }
136
SaveProfile()137 void SaveProfile()
138 {
139 auto *runtime = Runtime::GetCurrent();
140 if (!runtime->GetClassLinker()->GetAotManager()->HasProfiledMethods()) {
141 return;
142 }
143 ProfilingSaver profileSaver;
144 classCtxStr_ = runtime->GetClassLinker()->GetClassContextForAot(true);
145 auto &writtenMethods = runtime->GetClassLinker()->GetAotManager()->GetProfiledMethods();
146 auto writtenMethodsFinal = runtime->GetClassLinker()->GetAotManager()->GetProfiledMethodsFinal();
147 auto profiledPandaFiles = runtime->GetClassLinker()->GetAotManager()->GetProfiledPandaFiles();
148 profileSaver.SaveProfile(PandaString(pgoFilePath_), PandaString(classCtxStr_), writtenMethods,
149 writtenMethodsFinal, profiledPandaFiles);
150 }
151
LoadProfile(ProfilingLoader & profilingLoader)152 void LoadProfile(ProfilingLoader &profilingLoader)
153 {
154 auto profileCtxOrError = profilingLoader.LoadProfile(PandaString(pgoFilePath_));
155 if (!profileCtxOrError) {
156 std::cerr << profileCtxOrError.Error();
157 }
158 ASSERT_TRUE(profileCtxOrError.HasValue());
159 ASSERT_EQ(*profileCtxOrError, PandaString(classCtxStr_));
160 }
161
ClassResolver(uint32_t classIdx,size_t fileIdx)162 ark::Class *ClassResolver([[maybe_unused]] uint32_t classIdx, [[maybe_unused]] size_t fileIdx)
163 {
164 return FindClass("_GLOBAL");
165 }
166
InitPGOFilePath(const std::string & path)167 void InitPGOFilePath(const std::string &path)
168 {
169 pgoFilePath_ = path;
170 runner_.GetRuntimeOptions().SetProfileOutput(path);
171 if (std::filesystem::exists(path)) {
172 std::filesystem::remove(path);
173 }
174 }
175
SetCompilerProfilingThreshold(uint32_t value)176 void SetCompilerProfilingThreshold(uint32_t value)
177 {
178 runner_.GetRuntimeOptions().SetCompilerProfilingThreshold(value);
179 }
180
DestoryRuntime()181 void DestoryRuntime()
182 {
183 Runtime::Destroy();
184 }
185
186 private:
187 PandaRunner runner_;
188 std::map<std::string, ark::Class *> classMap_;
189 std::string pgoFilePath_;
190 std::string classCtxStr_;
191 };
192
TEST_F(ProfilingMergerTest,ProfileAddAndOverwriteTest)193 TEST_F(ProfilingMergerTest, ProfileAddAndOverwriteTest)
194 {
195 InitPGOFilePath("/tmp/profileaddandoverwrite.ap");
196
197 // first run
198 const uint32_t highThreshold = 10U;
199 SetCompilerProfilingThreshold(highThreshold);
200 CollectProfile();
201 SaveProfile();
202 Runtime::Destroy();
203
204 // second run
205 SetCompilerProfilingThreshold(0U);
206 CollectProfile();
207 {
208 ProfilingLoader profilingLoader0;
209 LoadProfile(profilingLoader0);
210 auto &methodsProfile0 = profilingLoader0.GetAotProfilingData().GetAllMethods().begin()->second;
211 EXPECT_EQ(methodsProfile0.size(), 1U);
212 SaveProfile();
213 ProfilingLoader profilingLoader1;
214 LoadProfile(profilingLoader1);
215 auto &methodsProfile1 = profilingLoader1.GetAotProfilingData().GetAllMethods().begin()->second;
216 EXPECT_EQ(methodsProfile1.size(), 2U);
217
218 // add profile data of main (new profiled)
219 // overwrite profile data of foo
220 auto globalCls = FindClass("_GLOBAL");
221 auto mainMtd = globalCls->GetClassMethod(utf::CStringAsMutf8("main"));
222 auto fooMtd = globalCls->GetClassMethod(utf::CStringAsMutf8("foo"));
223 EXPECT_FALSE(mainMtd->GetProfilingData()->IsUpdateSinceLastSave());
224 EXPECT_FALSE(fooMtd->GetProfilingData()->IsUpdateSinceLastSave());
225
226 for (auto &[methodId, methodProfile] : methodsProfile1) {
227 EXPECT_EQ(methodId, methodProfile.GetMethodIdx());
228 if (methodId == mainMtd->GetFileId().GetOffset()) {
229 // check data of main, same as second profiled
230 auto profilingData = profilingLoader1.CreateProfilingData(
231 methodProfile, Runtime::GetCurrent()->GetInternalAllocator(),
232 [this](uint32_t classIdx, size_t fileIdx) { return ClassResolver(classIdx, fileIdx); });
233 EXPECT_TRUE(Equals(*mainMtd->GetProfilingData(), *profilingData));
234 Runtime::GetCurrent()->GetInternalAllocator()->Free(profilingData);
235 } else if (methodId == fooMtd->GetFileId().GetOffset()) {
236 // check data of foo, same as second profiled
237 auto profilingData = profilingLoader1.CreateProfilingData(
238 methodProfile, Runtime::GetCurrent()->GetInternalAllocator(),
239 [this](uint32_t classIdx, size_t fileIdx) { return ClassResolver(classIdx, fileIdx); });
240 auto methodProfile0 = methodsProfile0.begin()->second;
241 EXPECT_TRUE(Equals(*fooMtd->GetProfilingData(), *profilingData));
242 Runtime::GetCurrent()->GetInternalAllocator()->Free(profilingData);
243 }
244 }
245 }
246 Runtime::Destroy();
247 }
248
TEST_F(ProfilingMergerTest,ProfileKeepAndOverwriteTest)249 TEST_F(ProfilingMergerTest, ProfileKeepAndOverwriteTest)
250 {
251 InitPGOFilePath("/tmp/profilekeepandoverwrite.ap");
252
253 // first run
254 SetCompilerProfilingThreshold(0U);
255 CollectProfile();
256 SaveProfile();
257 Runtime::Destroy();
258
259 // second run
260 const uint32_t highThreshold = 10U;
261 SetCompilerProfilingThreshold(highThreshold);
262 CollectProfile();
263 {
264 ProfilingLoader profilingLoader0;
265 LoadProfile(profilingLoader0);
266 auto &methodsProfile0 = profilingLoader0.GetAotProfilingData().GetAllMethods().begin()->second;
267 EXPECT_EQ(methodsProfile0.size(), 2U);
268 SaveProfile();
269 ProfilingLoader profilingLoader1;
270 LoadProfile(profilingLoader1);
271 auto &methodsProfile1 = profilingLoader1.GetAotProfilingData().GetAllMethods().begin()->second;
272 EXPECT_EQ(methodsProfile1.size(), 2U);
273
274 // keep profile data of main (old profield)
275 // overwrite profile data of foo
276 auto globalCls = FindClass("_GLOBAL");
277 auto mainMtd = globalCls->GetClassMethod(utf::CStringAsMutf8("main"));
278 auto fooMtd = globalCls->GetClassMethod(utf::CStringAsMutf8("foo"));
279 EXPECT_FALSE(fooMtd->GetProfilingData()->IsUpdateSinceLastSave());
280
281 for (auto &[methodId, methodProfile] : methodsProfile1) {
282 EXPECT_EQ(methodId, methodProfile.GetMethodIdx());
283 if (methodId == mainMtd->GetFileId().GetOffset()) {
284 // check data of main, same as first profiled
285 auto profilingData = profilingLoader1.CreateProfilingData(
286 methodProfile, Runtime::GetCurrent()->GetInternalAllocator(),
287 [this](uint32_t classIdx, size_t fileIdx) { return ClassResolver(classIdx, fileIdx); });
288 auto methodProfile0 = methodsProfile0.begin()->first == mainMtd->GetFileId().GetOffset()
289 ? methodsProfile0.begin()->second
290 : methodsProfile0.rbegin()->second;
291 auto profilingData0 = profilingLoader0.CreateProfilingData(
292 methodProfile0, Runtime::GetCurrent()->GetInternalAllocator(),
293 [this](uint32_t classIdx, size_t fileIdx) { return ClassResolver(classIdx, fileIdx); });
294 EXPECT_TRUE(Equals(*profilingData0, *profilingData));
295 Runtime::GetCurrent()->GetInternalAllocator()->Free(profilingData);
296 Runtime::GetCurrent()->GetInternalAllocator()->Free(profilingData0);
297 } else if (methodId == fooMtd->GetFileId().GetOffset()) {
298 // check data of foo, same as second profiled
299 auto profilingData = profilingLoader1.CreateProfilingData(
300 methodProfile, Runtime::GetCurrent()->GetInternalAllocator(),
301 [this](uint32_t classIdx, size_t fileIdx) { return ClassResolver(classIdx, fileIdx); });
302 EXPECT_TRUE(Equals(*fooMtd->GetProfilingData(), *profilingData));
303 Runtime::GetCurrent()->GetInternalAllocator()->Free(profilingData);
304 }
305 }
306 }
307 Runtime::Destroy();
308 }
309 } // namespace ark::test
310