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 static constexpr size_t kMaxMethodIds = 65535;
40
41 class ProfileCompilationInfoTest : public CommonRuntimeTest {
42 public:
PostRuntimeCreate()43 void PostRuntimeCreate() override {
44 allocator_.reset(new ArenaAllocator(Runtime::Current()->GetArenaPool()));
45 }
46
47 protected:
GetVirtualMethods(jobject class_loader,const std::string & clazz)48 std::vector<ArtMethod*> GetVirtualMethods(jobject class_loader,
49 const std::string& clazz) {
50 ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
51 Thread* self = Thread::Current();
52 ScopedObjectAccess soa(self);
53 StackHandleScope<1> hs(self);
54 Handle<mirror::ClassLoader> h_loader(
55 hs.NewHandle(self->DecodeJObject(class_loader)->AsClassLoader()));
56 ObjPtr<mirror::Class> klass = class_linker->FindClass(self, clazz.c_str(), h_loader);
57
58 const auto pointer_size = class_linker->GetImagePointerSize();
59 std::vector<ArtMethod*> methods;
60 for (auto& m : klass->GetVirtualMethods(pointer_size)) {
61 methods.push_back(&m);
62 }
63 return methods;
64 }
65
AddMethod(const std::string & dex_location,uint32_t checksum,uint16_t method_index,ProfileCompilationInfo * info)66 bool AddMethod(const std::string& dex_location,
67 uint32_t checksum,
68 uint16_t method_index,
69 ProfileCompilationInfo* info) {
70 return info->AddMethodIndex(Hotness::kFlagHot,
71 dex_location,
72 checksum,
73 method_index,
74 kMaxMethodIds);
75 }
76
AddMethod(const std::string & dex_location,uint32_t checksum,uint16_t method_index,const ProfileCompilationInfo::OfflineProfileMethodInfo & pmi,ProfileCompilationInfo * info)77 bool AddMethod(const std::string& dex_location,
78 uint32_t checksum,
79 uint16_t method_index,
80 const ProfileCompilationInfo::OfflineProfileMethodInfo& pmi,
81 ProfileCompilationInfo* info) {
82 return info->AddMethod(
83 dex_location, checksum, method_index, kMaxMethodIds, pmi, Hotness::kFlagPostStartup);
84 }
85
AddClass(const std::string & dex_location,uint32_t checksum,dex::TypeIndex type_index,ProfileCompilationInfo * info)86 bool AddClass(const std::string& dex_location,
87 uint32_t checksum,
88 dex::TypeIndex type_index,
89 ProfileCompilationInfo* info) {
90 DexCacheResolvedClasses classes(dex_location, dex_location, checksum, kMaxMethodIds);
91 classes.AddClass(type_index);
92 return info->AddClasses({classes});
93 }
94
GetFd(const ScratchFile & file)95 uint32_t GetFd(const ScratchFile& file) {
96 return static_cast<uint32_t>(file.GetFd());
97 }
98
SaveProfilingInfo(const std::string & filename,const std::vector<ArtMethod * > & methods,const std::set<DexCacheResolvedClasses> & resolved_classes,Hotness::Flag flags)99 bool SaveProfilingInfo(
100 const std::string& filename,
101 const std::vector<ArtMethod*>& methods,
102 const std::set<DexCacheResolvedClasses>& resolved_classes,
103 Hotness::Flag flags) {
104 ProfileCompilationInfo info;
105 std::vector<ProfileMethodInfo> profile_methods;
106 ScopedObjectAccess soa(Thread::Current());
107 for (ArtMethod* method : methods) {
108 profile_methods.emplace_back(
109 MethodReference(method->GetDexFile(), method->GetDexMethodIndex()));
110 }
111 if (!info.AddMethods(profile_methods, flags) || !info.AddClasses(resolved_classes)) {
112 return false;
113 }
114 if (info.GetNumberOfMethods() != profile_methods.size()) {
115 return false;
116 }
117 ProfileCompilationInfo file_profile;
118 if (!file_profile.Load(filename, false)) {
119 return false;
120 }
121 if (!info.MergeWith(file_profile)) {
122 return false;
123 }
124
125 return info.Save(filename, nullptr);
126 }
127
128 // Saves the given art methods to a profile backed by 'filename' and adds
129 // some fake inline caches to it. The added inline caches are returned in
130 // 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)131 bool SaveProfilingInfoWithFakeInlineCaches(
132 const std::string& filename,
133 const std::vector<ArtMethod*>& methods,
134 Hotness::Flag flags,
135 /*out*/ SafeMap<ArtMethod*, ProfileMethodInfo>* profile_methods_map) {
136 ProfileCompilationInfo info;
137 std::vector<ProfileMethodInfo> profile_methods;
138 ScopedObjectAccess soa(Thread::Current());
139 for (ArtMethod* method : methods) {
140 std::vector<ProfileMethodInfo::ProfileInlineCache> caches;
141 // Monomorphic
142 for (uint16_t dex_pc = 0; dex_pc < 11; dex_pc++) {
143 std::vector<TypeReference> classes;
144 classes.emplace_back(method->GetDexFile(), dex::TypeIndex(0));
145 caches.emplace_back(dex_pc, /*is_missing_types*/false, classes);
146 }
147 // Polymorphic
148 for (uint16_t dex_pc = 11; dex_pc < 22; dex_pc++) {
149 std::vector<TypeReference> classes;
150 for (uint16_t k = 0; k < InlineCache::kIndividualCacheSize / 2; k++) {
151 classes.emplace_back(method->GetDexFile(), dex::TypeIndex(k));
152 }
153 caches.emplace_back(dex_pc, /*is_missing_types*/false, classes);
154 }
155 // Megamorphic
156 for (uint16_t dex_pc = 22; dex_pc < 33; dex_pc++) {
157 std::vector<TypeReference> classes;
158 for (uint16_t k = 0; k < 2 * InlineCache::kIndividualCacheSize; k++) {
159 classes.emplace_back(method->GetDexFile(), dex::TypeIndex(k));
160 }
161 caches.emplace_back(dex_pc, /*is_missing_types*/false, classes);
162 }
163 // Missing types
164 for (uint16_t dex_pc = 33; dex_pc < 44; dex_pc++) {
165 std::vector<TypeReference> classes;
166 caches.emplace_back(dex_pc, /*is_missing_types*/true, classes);
167 }
168 ProfileMethodInfo pmi(MethodReference(method->GetDexFile(),
169 method->GetDexMethodIndex()),
170 caches);
171 profile_methods.push_back(pmi);
172 profile_methods_map->Put(method, pmi);
173 }
174
175 if (!info.AddMethods(profile_methods, flags)
176 || info.GetNumberOfMethods() != profile_methods.size()) {
177 return false;
178 }
179 return info.Save(filename, nullptr);
180 }
181
182 // Creates an inline cache which will be destructed at the end of the test.
CreateInlineCacheMap()183 ProfileCompilationInfo::InlineCacheMap* CreateInlineCacheMap() {
184 used_inline_caches.emplace_back(new ProfileCompilationInfo::InlineCacheMap(
185 std::less<uint16_t>(), allocator_->Adapter(kArenaAllocProfile)));
186 return used_inline_caches.back().get();
187 }
188
ConvertProfileMethodInfo(const ProfileMethodInfo & pmi)189 ProfileCompilationInfo::OfflineProfileMethodInfo ConvertProfileMethodInfo(
190 const ProfileMethodInfo& pmi) {
191 ProfileCompilationInfo::InlineCacheMap* ic_map = CreateInlineCacheMap();
192 ProfileCompilationInfo::OfflineProfileMethodInfo offline_pmi(ic_map);
193 SafeMap<DexFile*, uint8_t> dex_map; // dex files to profile index
194 for (const auto& inline_cache : pmi.inline_caches) {
195 ProfileCompilationInfo::DexPcData& dex_pc_data =
196 ic_map->FindOrAdd(
197 inline_cache.dex_pc, ProfileCompilationInfo::DexPcData(allocator_.get()))->second;
198 if (inline_cache.is_missing_types) {
199 dex_pc_data.SetIsMissingTypes();
200 }
201 for (const auto& class_ref : inline_cache.classes) {
202 uint8_t dex_profile_index = dex_map.FindOrAdd(const_cast<DexFile*>(class_ref.dex_file),
203 static_cast<uint8_t>(dex_map.size()))->second;
204 dex_pc_data.AddClass(dex_profile_index, class_ref.TypeIndex());
205 if (dex_profile_index >= offline_pmi.dex_references.size()) {
206 // This is a new dex.
207 const std::string& dex_key = ProfileCompilationInfo::GetProfileDexFileKey(
208 class_ref.dex_file->GetLocation());
209 offline_pmi.dex_references.emplace_back(dex_key,
210 class_ref.dex_file->GetLocationChecksum(),
211 class_ref.dex_file->NumMethodIds());
212 }
213 }
214 }
215 return offline_pmi;
216 }
217
218 // Cannot sizeof the actual arrays so hard code the values here.
219 // They should not change anyway.
220 static constexpr int kProfileMagicSize = 4;
221 static constexpr int kProfileVersionSize = 4;
222
223 std::unique_ptr<ArenaAllocator> allocator_;
224
225 // Cache of inline caches generated during tests.
226 // This makes it easier to pass data between different utilities and ensure that
227 // caches are destructed at the end of the test.
228 std::vector<std::unique_ptr<ProfileCompilationInfo::InlineCacheMap>> used_inline_caches;
229 };
230
TEST_F(ProfileCompilationInfoTest,SaveArtMethods)231 TEST_F(ProfileCompilationInfoTest, SaveArtMethods) {
232 ScratchFile profile;
233
234 Thread* self = Thread::Current();
235 jobject class_loader;
236 {
237 ScopedObjectAccess soa(self);
238 class_loader = LoadDex("ProfileTestMultiDex");
239 }
240 ASSERT_NE(class_loader, nullptr);
241
242 // Save virtual methods from Main.
243 std::set<DexCacheResolvedClasses> resolved_classes;
244 std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;");
245 ASSERT_TRUE(SaveProfilingInfo(
246 profile.GetFilename(), main_methods, resolved_classes, Hotness::kFlagPostStartup));
247
248 // Check that what we saved is in the profile.
249 ProfileCompilationInfo info1;
250 ASSERT_TRUE(info1.Load(GetFd(profile)));
251 ASSERT_EQ(info1.GetNumberOfMethods(), main_methods.size());
252 {
253 ScopedObjectAccess soa(self);
254 for (ArtMethod* m : main_methods) {
255 Hotness h = info1.GetMethodHotness(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()));
256 ASSERT_TRUE(h.IsHot());
257 ASSERT_TRUE(h.IsPostStartup());
258 }
259 }
260
261 // Save virtual methods from Second.
262 std::vector<ArtMethod*> second_methods = GetVirtualMethods(class_loader, "LSecond;");
263 ASSERT_TRUE(SaveProfilingInfo(
264 profile.GetFilename(), second_methods, resolved_classes, Hotness::kFlagStartup));
265
266 // Check that what we saved is in the profile (methods form Main and Second).
267 ProfileCompilationInfo info2;
268 ASSERT_TRUE(profile.GetFile()->ResetOffset());
269 ASSERT_TRUE(info2.Load(GetFd(profile)));
270 ASSERT_EQ(info2.GetNumberOfMethods(), main_methods.size() + second_methods.size());
271 {
272 ScopedObjectAccess soa(self);
273 for (ArtMethod* m : main_methods) {
274 Hotness h = info2.GetMethodHotness(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()));
275 ASSERT_TRUE(h.IsHot());
276 ASSERT_TRUE(h.IsPostStartup());
277 }
278 for (ArtMethod* m : second_methods) {
279 Hotness h = info2.GetMethodHotness(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()));
280 ASSERT_TRUE(h.IsHot());
281 ASSERT_TRUE(h.IsStartup());
282 }
283 }
284 }
285
TEST_F(ProfileCompilationInfoTest,SaveArtMethodsWithInlineCaches)286 TEST_F(ProfileCompilationInfoTest, SaveArtMethodsWithInlineCaches) {
287 ScratchFile profile;
288
289 Thread* self = Thread::Current();
290 jobject class_loader;
291 {
292 ScopedObjectAccess soa(self);
293 class_loader = LoadDex("ProfileTestMultiDex");
294 }
295 ASSERT_NE(class_loader, nullptr);
296
297 // Save virtual methods from Main.
298 std::set<DexCacheResolvedClasses> resolved_classes;
299 std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;");
300
301 SafeMap<ArtMethod*, ProfileMethodInfo> profile_methods_map;
302 ASSERT_TRUE(SaveProfilingInfoWithFakeInlineCaches(
303 profile.GetFilename(), main_methods, Hotness::kFlagStartup, &profile_methods_map));
304
305 // Check that what we saved is in the profile.
306 ProfileCompilationInfo info;
307 ASSERT_TRUE(info.Load(GetFd(profile)));
308 ASSERT_EQ(info.GetNumberOfMethods(), main_methods.size());
309 {
310 ScopedObjectAccess soa(self);
311 for (ArtMethod* m : main_methods) {
312 Hotness h = info.GetMethodHotness(MethodReference(m->GetDexFile(), m->GetDexMethodIndex()));
313 ASSERT_TRUE(h.IsHot());
314 ASSERT_TRUE(h.IsStartup());
315 const ProfileMethodInfo& pmi = profile_methods_map.find(m)->second;
316 std::unique_ptr<ProfileCompilationInfo::OfflineProfileMethodInfo> offline_pmi =
317 info.GetMethod(m->GetDexFile()->GetLocation(),
318 m->GetDexFile()->GetLocationChecksum(),
319 m->GetDexMethodIndex());
320 ASSERT_TRUE(offline_pmi != nullptr);
321 ProfileCompilationInfo::OfflineProfileMethodInfo converted_pmi =
322 ConvertProfileMethodInfo(pmi);
323 ASSERT_EQ(converted_pmi, *offline_pmi);
324 }
325 }
326 }
327
328 } // namespace art
329