• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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