1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <gtest/gtest.h>
18 #include <stdio.h>
19
20 #include "art_method-inl.h"
21 #include "base/unix_file/fd_file.h"
22 #include "class_linker-inl.h"
23 #include "common_runtime_test.h"
24 #include "dex/dex_file.h"
25 #include "dex/dex_file_loader.h"
26 #include "dex/method_reference.h"
27 #include "dex/type_reference.h"
28 #include "handle_scope-inl.h"
29 #include "linear_alloc.h"
30 #include "mirror/class-inl.h"
31 #include "mirror/class_loader.h"
32 #include "profile/profile_compilation_info.h"
33 #include "scoped_thread_state_change-inl.h"
34
35 namespace art {
36
37 using Hotness = ProfileCompilationInfo::MethodHotness;
38
39 class ProfileCompilationInfoTest : public CommonRuntimeTest {
40 public:
PostRuntimeCreate()41 void PostRuntimeCreate() override {
42 allocator_.reset(new ArenaAllocator(Runtime::Current()->GetArenaPool()));
43 }
44
45 protected:
GetVirtualMethods(jobject class_loader,const std::string & clazz)46 std::vector<ArtMethod*> GetVirtualMethods(jobject class_loader,
47 const std::string& clazz) {
48 ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
49 Thread* self = Thread::Current();
50 ScopedObjectAccess soa(self);
51 StackHandleScope<1> hs(self);
52 Handle<mirror::ClassLoader> h_loader(
53 hs.NewHandle(self->DecodeJObject(class_loader)->AsClassLoader()));
54 ObjPtr<mirror::Class> klass = class_linker->FindClass(self, clazz.c_str(), h_loader);
55
56 const auto pointer_size = class_linker->GetImagePointerSize();
57 std::vector<ArtMethod*> methods;
58 for (auto& m : klass->GetVirtualMethods(pointer_size)) {
59 methods.push_back(&m);
60 }
61 return methods;
62 }
63
GetFd(const ScratchFile & file)64 uint32_t GetFd(const ScratchFile& file) {
65 return static_cast<uint32_t>(file.GetFd());
66 }
67
SaveProfilingInfo(const std::string & filename,const std::vector<ArtMethod * > & methods,Hotness::Flag flags)68 bool SaveProfilingInfo(
69 const std::string& filename,
70 const std::vector<ArtMethod*>& methods,
71 Hotness::Flag flags) {
72 ProfileCompilationInfo info;
73 std::vector<ProfileMethodInfo> profile_methods;
74 ScopedObjectAccess soa(Thread::Current());
75 for (ArtMethod* method : methods) {
76 profile_methods.emplace_back(
77 MethodReference(method->GetDexFile(), method->GetDexMethodIndex()));
78 }
79 if (!info.AddMethods(profile_methods, flags)) {
80 return false;
81 }
82 if (info.GetNumberOfMethods() != profile_methods.size()) {
83 return false;
84 }
85 ProfileCompilationInfo file_profile;
86 if (!file_profile.Load(filename, false)) {
87 return false;
88 }
89 if (!info.MergeWith(file_profile)) {
90 return false;
91 }
92
93 return info.Save(filename, nullptr);
94 }
95
96 // Saves the given art methods to a profile backed by 'filename' and adds
97 // some fake inline caches to it. The added inline caches are returned in
98 // the out map `profile_methods_map`.
SaveProfilingInfoWithFakeInlineCaches(const std::string & filename,const std::vector<ArtMethod * > & methods,Hotness::Flag flags,SafeMap<ArtMethod *,ProfileMethodInfo> * profile_methods_map)99 bool SaveProfilingInfoWithFakeInlineCaches(
100 const std::string& filename,
101 const std::vector<ArtMethod*>& methods,
102 Hotness::Flag flags,
103 /*out*/ SafeMap<ArtMethod*, ProfileMethodInfo>* profile_methods_map) {
104 ProfileCompilationInfo info;
105 std::vector<ProfileMethodInfo> profile_methods;
106 ScopedObjectAccess soa(Thread::Current());
107 for (ArtMethod* method : methods) {
108 std::vector<ProfileMethodInfo::ProfileInlineCache> caches;
109 // Monomorphic
110 for (uint16_t dex_pc = 0; dex_pc < 11; dex_pc++) {
111 std::vector<TypeReference> classes;
112 classes.emplace_back(method->GetDexFile(), dex::TypeIndex(0));
113 caches.emplace_back(dex_pc, /*is_missing_types*/false, classes);
114 }
115 // Polymorphic
116 for (uint16_t dex_pc = 11; dex_pc < 22; dex_pc++) {
117 std::vector<TypeReference> classes;
118 for (uint16_t k = 0; k < InlineCache::kIndividualCacheSize / 2; k++) {
119 classes.emplace_back(method->GetDexFile(), dex::TypeIndex(k));
120 }
121 caches.emplace_back(dex_pc, /*is_missing_types*/false, classes);
122 }
123 // Megamorphic
124 for (uint16_t dex_pc = 22; dex_pc < 33; dex_pc++) {
125 std::vector<TypeReference> classes;
126 for (uint16_t k = 0; k < 2 * InlineCache::kIndividualCacheSize; k++) {
127 classes.emplace_back(method->GetDexFile(), dex::TypeIndex(k));
128 }
129 caches.emplace_back(dex_pc, /*is_missing_types*/false, classes);
130 }
131 // Missing types
132 for (uint16_t dex_pc = 33; dex_pc < 44; dex_pc++) {
133 std::vector<TypeReference> classes;
134 caches.emplace_back(dex_pc, /*is_missing_types*/true, classes);
135 }
136 ProfileMethodInfo pmi(MethodReference(method->GetDexFile(),
137 method->GetDexMethodIndex()),
138 caches);
139 profile_methods.push_back(pmi);
140 profile_methods_map->Put(method, pmi);
141 }
142
143 if (!info.AddMethods(profile_methods, flags)
144 || info.GetNumberOfMethods() != profile_methods.size()) {
145 return false;
146 }
147 return info.Save(filename, nullptr);
148 }
149
150 // Creates an inline cache which will be destructed at the end of the test.
CreateInlineCacheMap()151 ProfileCompilationInfo::InlineCacheMap* CreateInlineCacheMap() {
152 used_inline_caches.emplace_back(new ProfileCompilationInfo::InlineCacheMap(
153 std::less<uint16_t>(), allocator_->Adapter(kArenaAllocProfile)));
154 return used_inline_caches.back().get();
155 }
156
ConvertProfileMethodInfo(const ProfileMethodInfo & pmi)157 ProfileCompilationInfo::OfflineProfileMethodInfo ConvertProfileMethodInfo(
158 const ProfileMethodInfo& pmi) {
159 ProfileCompilationInfo::InlineCacheMap* ic_map = CreateInlineCacheMap();
160 ProfileCompilationInfo::OfflineProfileMethodInfo offline_pmi(ic_map);
161 SafeMap<DexFile*, uint8_t> dex_map; // dex files to profile index
162 for (const auto& inline_cache : pmi.inline_caches) {
163 ProfileCompilationInfo::DexPcData& dex_pc_data =
164 ic_map->FindOrAdd(
165 inline_cache.dex_pc, ProfileCompilationInfo::DexPcData(allocator_.get()))->second;
166 if (inline_cache.is_missing_types) {
167 dex_pc_data.SetIsMissingTypes();
168 }
169 for (const auto& class_ref : inline_cache.classes) {
170 uint8_t dex_profile_index = dex_map.FindOrAdd(const_cast<DexFile*>(class_ref.dex_file),
171 static_cast<uint8_t>(dex_map.size()))->second;
172 dex_pc_data.AddClass(dex_profile_index, class_ref.TypeIndex());
173 if (dex_profile_index >= offline_pmi.dex_references.size()) {
174 // This is a new dex.
175 const std::string& dex_key = ProfileCompilationInfo::GetProfileDexFileBaseKey(
176 class_ref.dex_file->GetLocation());
177 offline_pmi.dex_references.emplace_back(dex_key,
178 class_ref.dex_file->GetLocationChecksum(),
179 class_ref.dex_file->NumMethodIds());
180 }
181 }
182 }
183 return offline_pmi;
184 }
185
186 // Cannot sizeof the actual arrays so hard code the values here.
187 // They should not change anyway.
188 static constexpr int kProfileMagicSize = 4;
189 static constexpr int kProfileVersionSize = 4;
190
191 std::unique_ptr<ArenaAllocator> allocator_;
192
193 // Cache of inline caches generated during tests.
194 // This makes it easier to pass data between different utilities and ensure that
195 // caches are destructed at the end of the test.
196 std::vector<std::unique_ptr<ProfileCompilationInfo::InlineCacheMap>> used_inline_caches;
197 };
198
TEST_F(ProfileCompilationInfoTest,SaveArtMethods)199 TEST_F(ProfileCompilationInfoTest, SaveArtMethods) {
200 ScratchFile profile;
201
202 Thread* self = Thread::Current();
203 jobject class_loader;
204 {
205 ScopedObjectAccess soa(self);
206 class_loader = LoadDex("ProfileTestMultiDex");
207 }
208 ASSERT_NE(class_loader, nullptr);
209
210 // Save virtual methods from Main.
211 std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;");
212 ASSERT_TRUE(SaveProfilingInfo(
213 profile.GetFilename(),
214 main_methods,
215 static_cast<Hotness::Flag>(Hotness::kFlagHot | Hotness::kFlagPostStartup)));
216
217 // Check that what we saved is in the profile.
218 ProfileCompilationInfo info1;
219 ASSERT_TRUE(info1.Load(GetFd(profile)));
220 ASSERT_EQ(info1.GetNumberOfMethods(), main_methods.size());
221 {
222 ScopedObjectAccess soa(self);
223 for (ArtMethod* m : main_methods) {
224 Hotness h = info1.GetMethodHotness(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()));
225 ASSERT_TRUE(h.IsHot());
226 ASSERT_TRUE(h.IsPostStartup());
227 }
228 }
229
230 // Save virtual methods from Second.
231 std::vector<ArtMethod*> second_methods = GetVirtualMethods(class_loader, "LSecond;");
232 ASSERT_TRUE(SaveProfilingInfo(
233 profile.GetFilename(),
234 second_methods,
235 static_cast<Hotness::Flag>(Hotness::kFlagHot | Hotness::kFlagStartup)));
236
237 // Check that what we saved is in the profile (methods form Main and Second).
238 ProfileCompilationInfo info2;
239 ASSERT_TRUE(profile.GetFile()->ResetOffset());
240 ASSERT_TRUE(info2.Load(GetFd(profile)));
241 ASSERT_EQ(info2.GetNumberOfMethods(), main_methods.size() + second_methods.size());
242 {
243 ScopedObjectAccess soa(self);
244 for (ArtMethod* m : main_methods) {
245 Hotness h = info2.GetMethodHotness(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()));
246 ASSERT_TRUE(h.IsHot());
247 ASSERT_TRUE(h.IsPostStartup());
248 }
249 for (ArtMethod* m : second_methods) {
250 Hotness h = info2.GetMethodHotness(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()));
251 ASSERT_TRUE(h.IsHot());
252 ASSERT_TRUE(h.IsStartup());
253 }
254 }
255 }
256
TEST_F(ProfileCompilationInfoTest,SaveArtMethodsWithInlineCaches)257 TEST_F(ProfileCompilationInfoTest, SaveArtMethodsWithInlineCaches) {
258 ScratchFile profile;
259
260 Thread* self = Thread::Current();
261 jobject class_loader;
262 {
263 ScopedObjectAccess soa(self);
264 class_loader = LoadDex("ProfileTestMultiDex");
265 }
266 ASSERT_NE(class_loader, nullptr);
267
268 // Save virtual methods from Main.
269 std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;");
270
271 SafeMap<ArtMethod*, ProfileMethodInfo> profile_methods_map;
272 ASSERT_TRUE(SaveProfilingInfoWithFakeInlineCaches(
273 profile.GetFilename(),
274 main_methods,
275 static_cast<Hotness::Flag>(Hotness::kFlagHot | Hotness::kFlagStartup),
276 &profile_methods_map));
277
278 // Check that what we saved is in the profile.
279 ProfileCompilationInfo info;
280 ASSERT_TRUE(info.Load(GetFd(profile)));
281 ASSERT_EQ(info.GetNumberOfMethods(), main_methods.size());
282 {
283 ScopedObjectAccess soa(self);
284 for (ArtMethod* m : main_methods) {
285 MethodReference method_ref(m->GetDexFile(), m->GetDexMethodIndex());
286 Hotness h = info.GetMethodHotness(method_ref);
287 ASSERT_TRUE(h.IsHot());
288 ASSERT_TRUE(h.IsStartup());
289 const ProfileMethodInfo& pmi = profile_methods_map.find(m)->second;
290 std::unique_ptr<ProfileCompilationInfo::OfflineProfileMethodInfo> offline_pmi =
291 info.GetHotMethodInfo(method_ref);
292 ASSERT_TRUE(offline_pmi != nullptr);
293 ProfileCompilationInfo::OfflineProfileMethodInfo converted_pmi =
294 ConvertProfileMethodInfo(pmi);
295 ASSERT_EQ(converted_pmi, *offline_pmi);
296 }
297 }
298 }
299
300 } // namespace art
301