1 /*
2 * Copyright (C) 2017 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 "boot_image_profile.h"
18
19 #include <memory>
20 #include <set>
21
22 #include "android-base/file.h"
23 #include "dex/class_accessor-inl.h"
24 #include "dex/descriptors_names.h"
25 #include "dex/dex_file-inl.h"
26 #include "dex/method_reference.h"
27 #include "dex/type_reference.h"
28 #include "profile/profile_compilation_info.h"
29 #include "inline_cache_format_util.h"
30
31 namespace art {
32
33 using Hotness = ProfileCompilationInfo::MethodHotness;
34
35 static const std::string kMethodSep = "->"; // NOLINT [runtime/string] [4]
36 static const std::string kPackageUseDelim = "@"; // NOLINT [runtime/string] [4]
37 static constexpr char kMethodFlagStringHot = 'H';
38 static constexpr char kMethodFlagStringStartup = 'S';
39 static constexpr char kMethodFlagStringPostStartup = 'P';
40
41 // Returns the type descriptor of the given reference.
GetTypeDescriptor(const TypeReference & ref)42 static std::string GetTypeDescriptor(const TypeReference& ref) {
43 const dex::TypeId& type_id = ref.dex_file->GetTypeId(ref.TypeIndex());
44 return ref.dex_file->GetTypeDescriptor(type_id);
45 }
46
47 // Returns the method representation used in the text format of the boot image profile.
BootImageRepresentation(const MethodReference & ref)48 static std::string BootImageRepresentation(const MethodReference& ref) {
49 const DexFile* dex_file = ref.dex_file;
50 const dex::MethodId& id = ref.GetMethodId();
51 std::string signature_string(dex_file->GetMethodSignature(id).ToString());
52 std::string type_string(dex_file->GetTypeDescriptor(dex_file->GetTypeId(id.class_idx_)));
53 std::string method_name(dex_file->GetMethodName(id));
54 return type_string +
55 kMethodSep +
56 method_name +
57 signature_string;
58 }
59
60 // Returns the class representation used in the text format of the boot image profile.
BootImageRepresentation(const TypeReference & ref)61 static std::string BootImageRepresentation(const TypeReference& ref) {
62 return GetTypeDescriptor(ref);
63 }
64
65 // Returns the class representation used in preloaded classes.
PreloadedClassesRepresentation(const TypeReference & ref)66 static std::string PreloadedClassesRepresentation(const TypeReference& ref) {
67 std::string descriptor = GetTypeDescriptor(ref);
68 return DescriptorToDot(descriptor.c_str());
69 }
70
71 // Formats the list of packages from the item metadata as a debug string.
GetPackageUseString(const FlattenProfileData::ItemMetadata & metadata)72 static std::string GetPackageUseString(const FlattenProfileData::ItemMetadata& metadata) {
73 std::string result;
74 for (const auto& it : metadata.GetAnnotations()) {
75 result += it.GetOriginPackageName() + ",";
76 }
77
78 return metadata.GetAnnotations().empty()
79 ? result
80 : result.substr(0, result.size() - 1);
81 }
82
83 // Converts a method representation to its final profile format.
MethodToProfileFormat(const std::string & method,const FlattenProfileData::ItemMetadata & metadata,bool output_package_use)84 static std::string MethodToProfileFormat(
85 const std::string& method,
86 const FlattenProfileData::ItemMetadata& metadata,
87 bool output_package_use) {
88 std::string flags_string;
89 if (metadata.HasFlagSet(Hotness::kFlagHot)) {
90 flags_string += kMethodFlagStringHot;
91 }
92 if (metadata.HasFlagSet(Hotness::kFlagStartup)) {
93 flags_string += kMethodFlagStringStartup;
94 }
95 if (metadata.HasFlagSet(Hotness::kFlagPostStartup)) {
96 flags_string += kMethodFlagStringPostStartup;
97 }
98 std::string extra;
99 if (output_package_use) {
100 extra = kPackageUseDelim + GetPackageUseString(metadata);
101 }
102
103 std::string inline_cache_string = GetInlineCacheLine(metadata.GetInlineCache());
104 return flags_string + method + extra + inline_cache_string;
105 }
106
107 // Converts a class representation to its final profile or preloaded classes format.
ClassToProfileFormat(const std::string & classString,const FlattenProfileData::ItemMetadata & metadata,bool output_package_use)108 static std::string ClassToProfileFormat(
109 const std::string& classString,
110 const FlattenProfileData::ItemMetadata& metadata,
111 bool output_package_use) {
112 std::string extra;
113 if (output_package_use) {
114 extra = kPackageUseDelim + GetPackageUseString(metadata);
115 }
116
117 return classString + extra;
118 }
119
120 // Tries to asses if the given type reference is a clean class.
MaybeIsClassClean(const TypeReference & ref)121 static bool MaybeIsClassClean(const TypeReference& ref) {
122 const dex::ClassDef* class_def = ref.dex_file->FindClassDef(ref.TypeIndex());
123 if (class_def == nullptr) {
124 return false;
125 }
126
127 ClassAccessor accessor(*ref.dex_file, *class_def);
128 for (auto& it : accessor.GetStaticFields()) {
129 if (!it.IsFinal()) {
130 // Not final static field will probably dirty the class.
131 return false;
132 }
133 }
134 for (auto& it : accessor.GetMethods()) {
135 uint32_t flags = it.GetAccessFlags();
136 if ((flags & kAccNative) != 0) {
137 // Native method will get dirtied.
138 return false;
139 }
140 if ((flags & kAccConstructor) != 0 && (flags & kAccStatic) != 0) {
141 // Class initializer, may get dirtied (not sure).
142 return false;
143 }
144 }
145
146 return true;
147 }
148
149 // Returns true iff the item should be included in the profile.
150 // (i.e. it passes the given aggregation thresholds)
IncludeItemInProfile(uint32_t max_aggregation_count,uint32_t item_threshold,const FlattenProfileData::ItemMetadata & metadata,const BootImageOptions & options)151 static bool IncludeItemInProfile(uint32_t max_aggregation_count,
152 uint32_t item_threshold,
153 const FlattenProfileData::ItemMetadata& metadata,
154 const BootImageOptions& options) {
155 CHECK_NE(max_aggregation_count, 0u);
156 float item_percent = metadata.GetAnnotations().size() / static_cast<float>(max_aggregation_count);
157 for (const auto& annotIt : metadata.GetAnnotations()) {
158 const auto&thresholdIt =
159 options.special_packages_thresholds.find(annotIt.GetOriginPackageName());
160 if (thresholdIt != options.special_packages_thresholds.end()) {
161 if (item_percent >= (thresholdIt->second / 100.f)) {
162 return true;
163 }
164 }
165 }
166 return item_percent >= (item_threshold / 100.f);
167 }
168
169 // Returns true iff a method with the given metada should be included in the profile.
IncludeMethodInProfile(uint32_t max_aggregation_count,const FlattenProfileData::ItemMetadata & metadata,const BootImageOptions & options)170 static bool IncludeMethodInProfile(uint32_t max_aggregation_count,
171 const FlattenProfileData::ItemMetadata& metadata,
172 const BootImageOptions& options) {
173 return IncludeItemInProfile(max_aggregation_count, options.method_threshold, metadata, options);
174 }
175
176 // Returns true iff a class with the given metada should be included in the profile.
IncludeClassInProfile(const TypeReference & type_ref,uint32_t max_aggregation_count,const FlattenProfileData::ItemMetadata & metadata,const BootImageOptions & options)177 static bool IncludeClassInProfile(const TypeReference& type_ref,
178 uint32_t max_aggregation_count,
179 const FlattenProfileData::ItemMetadata& metadata,
180 const BootImageOptions& options) {
181 uint32_t threshold = MaybeIsClassClean(type_ref)
182 ? options.image_class_clean_threshold
183 : options.image_class_threshold;
184 return IncludeItemInProfile(max_aggregation_count, threshold, metadata, options);
185 }
186
187 // Returns true iff a class with the given metada should be included in the list of
188 // prelaoded classes.
IncludeInPreloadedClasses(const std::string & class_name,uint32_t max_aggregation_count,const FlattenProfileData::ItemMetadata & metadata,const BootImageOptions & options)189 static bool IncludeInPreloadedClasses(const std::string& class_name,
190 uint32_t max_aggregation_count,
191 const FlattenProfileData::ItemMetadata& metadata,
192 const BootImageOptions& options) {
193 bool denylisted = options.preloaded_classes_denylist.find(class_name) !=
194 options.preloaded_classes_denylist.end();
195 return !denylisted && IncludeItemInProfile(
196 max_aggregation_count, options.preloaded_class_threshold, metadata, options);
197 }
198
GenerateBootImageProfile(const std::vector<std::unique_ptr<const DexFile>> & dex_files,const std::vector<std::string> & profile_files,const BootImageOptions & options,const std::string & boot_profile_out_path,const std::string & preloaded_classes_out_path)199 bool GenerateBootImageProfile(
200 const std::vector<std::unique_ptr<const DexFile>>& dex_files,
201 const std::vector<std::string>& profile_files,
202 const BootImageOptions& options,
203 const std::string& boot_profile_out_path,
204 const std::string& preloaded_classes_out_path) {
205 if (boot_profile_out_path.empty()) {
206 LOG(ERROR) << "No output file specified";
207 return false;
208 }
209
210 bool generate_preloaded_classes = !preloaded_classes_out_path.empty();
211
212 std::unique_ptr<FlattenProfileData> flattend_data(new FlattenProfileData());
213 for (const std::string& profile_file : profile_files) {
214 ProfileCompilationInfo profile(/*for_boot_image=*/ true);
215 if (!profile.Load(profile_file, /*clear_if_invalid=*/ false)) {
216 LOG(ERROR) << "Profile is not a valid: " << profile_file;
217 return false;
218 }
219 std::unique_ptr<FlattenProfileData> currentData = profile.ExtractProfileData(dex_files);
220 flattend_data->MergeData(*currentData);
221 }
222
223 // We want the output sorted by the method/class name.
224 // So we use an intermediate map for that.
225 // There's no attempt to optimize this as it's not part of any critical path,
226 // and mostly executed on hosts.
227 SafeMap<std::string, FlattenProfileData::ItemMetadata> profile_methods;
228 SafeMap<std::string, FlattenProfileData::ItemMetadata> profile_classes;
229 SafeMap<std::string, FlattenProfileData::ItemMetadata> preloaded_classes;
230
231 for (const auto& it : flattend_data->GetMethodData()) {
232 if (IncludeMethodInProfile(flattend_data->GetMaxAggregationForMethods(), it.second, options)) {
233 FlattenProfileData::ItemMetadata metadata(it.second);
234 if (options.upgrade_startup_to_hot
235 && ((metadata.GetFlags() & Hotness::Flag::kFlagStartup) != 0)) {
236 metadata.AddFlag(Hotness::Flag::kFlagHot);
237 }
238 profile_methods.Put(BootImageRepresentation(it.first), metadata);
239 }
240 }
241
242 for (const auto& it : flattend_data->GetClassData()) {
243 const TypeReference& type_ref = it.first;
244 const FlattenProfileData::ItemMetadata& metadata = it.second;
245 if (IncludeClassInProfile(type_ref,
246 flattend_data->GetMaxAggregationForClasses(),
247 metadata,
248 options)) {
249 profile_classes.Put(BootImageRepresentation(it.first), it.second);
250 }
251 std::string preloaded_class_representation = PreloadedClassesRepresentation(it.first);
252 if (generate_preloaded_classes && IncludeInPreloadedClasses(
253 preloaded_class_representation,
254 flattend_data->GetMaxAggregationForClasses(),
255 metadata,
256 options)) {
257 preloaded_classes.Put(preloaded_class_representation, it.second);
258 }
259 }
260
261 // Create the output content
262 std::string profile_content;
263 std::string preloaded_content;
264 for (const auto& it : profile_classes) {
265 profile_content += ClassToProfileFormat(it.first, it.second, options.append_package_use_list)
266 + "\n";
267 }
268 for (const auto& it : profile_methods) {
269 profile_content += MethodToProfileFormat(it.first, it.second, options.append_package_use_list)
270 + "\n";
271 }
272
273 if (generate_preloaded_classes) {
274 for (const auto& it : preloaded_classes) {
275 preloaded_content +=
276 ClassToProfileFormat(it.first, it.second, options.append_package_use_list) + "\n";
277 }
278 }
279
280 return android::base::WriteStringToFile(profile_content, boot_profile_out_path)
281 && (!generate_preloaded_classes
282 || android::base::WriteStringToFile(preloaded_content, preloaded_classes_out_path));
283 }
284
285 } // namespace art
286