Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2018 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 "dexanalyze_experiments.h" |
Mathieu Chartier | 35ddc6f | 2018-05-09 11:34:07 -0700 | [diff] [blame] | 18 | |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 19 | #include <algorithm> |
Mathieu Chartier | 35ddc6f | 2018-05-09 11:34:07 -0700 | [diff] [blame] | 20 | #include <stdint.h> |
| 21 | #include <inttypes.h> |
| 22 | #include <iostream> |
| 23 | #include <map> |
| 24 | #include <vector> |
| 25 | |
| 26 | #include "android-base/stringprintf.h" |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 27 | #include "dex/class_accessor-inl.h" |
Mathieu Chartier | 05dc23e | 2018-05-22 11:56:14 -0700 | [diff] [blame] | 28 | #include "dex/class_iterator.h" |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 29 | #include "dex/code_item_accessors-inl.h" |
| 30 | #include "dex/dex_instruction-inl.h" |
| 31 | #include "dex/standard_dex_file.h" |
Mathieu Chartier | f275979 | 2018-05-18 13:16:54 -0700 | [diff] [blame] | 32 | #include "dex/utf-inl.h" |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 33 | |
| 34 | namespace art { |
Mathieu Chartier | 12dd8a9 | 2018-06-12 11:17:22 -0700 | [diff] [blame] | 35 | namespace dexanalyze { |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 36 | |
Mathieu Chartier | 12dd8a9 | 2018-06-12 11:17:22 -0700 | [diff] [blame] | 37 | bool IsRange(Instruction::Code code) { |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 38 | return code == Instruction::INVOKE_VIRTUAL_RANGE || |
| 39 | code == Instruction::INVOKE_DIRECT_RANGE || |
| 40 | code == Instruction::INVOKE_SUPER_RANGE || |
| 41 | code == Instruction::INVOKE_STATIC_RANGE || |
| 42 | code == Instruction::INVOKE_INTERFACE_RANGE; |
| 43 | } |
| 44 | |
Mathieu Chartier | 12dd8a9 | 2018-06-12 11:17:22 -0700 | [diff] [blame] | 45 | uint16_t NumberOfArgs(const Instruction& inst) { |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 46 | return IsRange(inst.Opcode()) ? inst.VRegA_3rc() : inst.VRegA_35c(); |
| 47 | } |
| 48 | |
Mathieu Chartier | 12dd8a9 | 2018-06-12 11:17:22 -0700 | [diff] [blame] | 49 | uint16_t DexMethodIndex(const Instruction& inst) { |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 50 | return IsRange(inst.Opcode()) ? inst.VRegB_3rc() : inst.VRegB_35c(); |
| 51 | } |
| 52 | |
Mathieu Chartier | 35ddc6f | 2018-05-09 11:34:07 -0700 | [diff] [blame] | 53 | std::string Percent(uint64_t value, uint64_t max) { |
| 54 | if (max == 0) { |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 55 | return "0"; |
Mathieu Chartier | 35ddc6f | 2018-05-09 11:34:07 -0700 | [diff] [blame] | 56 | } |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 57 | return android::base::StringPrintf( |
| 58 | "%" PRId64 "(%.2f%%)", |
| 59 | value, |
| 60 | static_cast<double>(value * 100) / static_cast<double>(max)); |
| 61 | } |
| 62 | |
| 63 | std::string PercentDivide(uint64_t value, uint64_t max) { |
| 64 | if (max == 0) { |
| 65 | return "0"; |
| 66 | } |
| 67 | return android::base::StringPrintf( |
| 68 | "%" PRId64 "/%" PRId64 "(%.2f%%)", |
| 69 | value, |
| 70 | max, |
| 71 | static_cast<double>(value * 100) / static_cast<double>(max)); |
Mathieu Chartier | 35ddc6f | 2018-05-09 11:34:07 -0700 | [diff] [blame] | 72 | } |
| 73 | |
Mathieu Chartier | 12dd8a9 | 2018-06-12 11:17:22 -0700 | [diff] [blame] | 74 | size_t PrefixLen(const std::string& a, const std::string& b) { |
Mathieu Chartier | 35ddc6f | 2018-05-09 11:34:07 -0700 | [diff] [blame] | 75 | size_t len = 0; |
| 76 | for (; len < a.length() && len < b.length() && a[len] == b[len]; ++len) {} |
| 77 | return len; |
| 78 | } |
| 79 | |
Mathieu Chartier | 48de723 | 2018-06-01 17:30:29 -0700 | [diff] [blame] | 80 | void Experiment::ProcessDexFiles(const std::vector<std::unique_ptr<const DexFile>>& dex_files) { |
| 81 | for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { |
| 82 | ProcessDexFile(*dex_file); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | void AnalyzeDebugInfo::ProcessDexFiles( |
| 87 | const std::vector<std::unique_ptr<const DexFile>>& dex_files) { |
Mathieu Chartier | 31380c7 | 2018-05-31 18:12:27 -0700 | [diff] [blame] | 88 | std::set<const uint8_t*> seen; |
| 89 | std::vector<size_t> counts(256, 0u); |
| 90 | std::vector<size_t> opcode_counts(256, 0u); |
| 91 | std::set<std::vector<uint8_t>> unique_non_header; |
Mathieu Chartier | 48de723 | 2018-06-01 17:30:29 -0700 | [diff] [blame] | 92 | for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { |
| 93 | for (ClassAccessor accessor : dex_file->GetClasses()) { |
| 94 | for (const ClassAccessor::Method& method : accessor.GetMethods()) { |
| 95 | CodeItemDebugInfoAccessor code_item(*dex_file, method.GetCodeItem(), method.GetIndex()); |
| 96 | const uint8_t* debug_info = dex_file->GetDebugInfoStream(code_item.DebugInfoOffset()); |
| 97 | if (debug_info != nullptr && seen.insert(debug_info).second) { |
| 98 | const uint8_t* stream = debug_info; |
| 99 | DecodeUnsignedLeb128(&stream); // line_start |
| 100 | uint32_t parameters_size = DecodeUnsignedLeb128(&stream); |
| 101 | for (uint32_t i = 0; i < parameters_size; ++i) { |
| 102 | DecodeUnsignedLeb128P1(&stream); // Parameter name. |
| 103 | } |
| 104 | bool done = false; |
| 105 | const uint8_t* after_header_start = stream; |
| 106 | while (!done) { |
| 107 | const uint8_t* const op_start = stream; |
| 108 | uint8_t opcode = *stream++; |
| 109 | ++opcode_counts[opcode]; |
| 110 | ++total_opcode_bytes_; |
| 111 | switch (opcode) { |
| 112 | case DexFile::DBG_END_SEQUENCE: |
| 113 | ++total_end_seq_bytes_; |
| 114 | done = true; |
| 115 | break; |
| 116 | case DexFile::DBG_ADVANCE_PC: |
| 117 | DecodeUnsignedLeb128(&stream); // addr_diff |
| 118 | total_advance_pc_bytes_ += stream - op_start; |
| 119 | break; |
| 120 | case DexFile::DBG_ADVANCE_LINE: |
| 121 | DecodeSignedLeb128(&stream); // line_diff |
| 122 | total_advance_line_bytes_ += stream - op_start; |
| 123 | break; |
| 124 | case DexFile::DBG_START_LOCAL: |
| 125 | DecodeUnsignedLeb128(&stream); // register_num |
| 126 | DecodeUnsignedLeb128P1(&stream); // name_idx |
| 127 | DecodeUnsignedLeb128P1(&stream); // type_idx |
| 128 | total_start_local_bytes_ += stream - op_start; |
| 129 | break; |
| 130 | case DexFile::DBG_START_LOCAL_EXTENDED: |
| 131 | DecodeUnsignedLeb128(&stream); // register_num |
| 132 | DecodeUnsignedLeb128P1(&stream); // name_idx |
| 133 | DecodeUnsignedLeb128P1(&stream); // type_idx |
| 134 | DecodeUnsignedLeb128P1(&stream); // sig_idx |
| 135 | total_start_local_extended_bytes_ += stream - op_start; |
| 136 | break; |
| 137 | case DexFile::DBG_END_LOCAL: |
| 138 | DecodeUnsignedLeb128(&stream); // register_num |
| 139 | total_end_local_bytes_ += stream - op_start; |
| 140 | break; |
| 141 | case DexFile::DBG_RESTART_LOCAL: |
| 142 | DecodeUnsignedLeb128(&stream); // register_num |
| 143 | total_restart_local_bytes_ += stream - op_start; |
| 144 | break; |
| 145 | case DexFile::DBG_SET_PROLOGUE_END: |
| 146 | case DexFile::DBG_SET_EPILOGUE_BEGIN: |
| 147 | total_epilogue_bytes_ += stream - op_start; |
| 148 | break; |
| 149 | case DexFile::DBG_SET_FILE: { |
| 150 | DecodeUnsignedLeb128P1(&stream); // name_idx |
| 151 | total_set_file_bytes_ += stream - op_start; |
| 152 | break; |
| 153 | } |
| 154 | default: { |
| 155 | total_other_bytes_ += stream - op_start; |
| 156 | break; |
| 157 | } |
Mathieu Chartier | 31380c7 | 2018-05-31 18:12:27 -0700 | [diff] [blame] | 158 | } |
| 159 | } |
Mathieu Chartier | 48de723 | 2018-06-01 17:30:29 -0700 | [diff] [blame] | 160 | const size_t bytes = stream - debug_info; |
| 161 | total_bytes_ += bytes; |
| 162 | total_non_header_bytes_ += stream - after_header_start; |
| 163 | if (unique_non_header.insert(std::vector<uint8_t>(after_header_start, stream)).second) { |
| 164 | total_unique_non_header_bytes_ += stream - after_header_start; |
| 165 | } |
| 166 | for (size_t i = 0; i < bytes; ++i) { |
| 167 | ++counts[debug_info[i]]; |
| 168 | } |
Mathieu Chartier | 31380c7 | 2018-05-31 18:12:27 -0700 | [diff] [blame] | 169 | } |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | auto calc_entropy = [](std::vector<size_t> data) { |
| 174 | size_t total = std::accumulate(data.begin(), data.end(), 0u); |
| 175 | double avg_entropy = 0.0; |
| 176 | for (size_t c : data) { |
| 177 | if (c > 0) { |
| 178 | double ratio = static_cast<double>(c) / static_cast<double>(total); |
| 179 | avg_entropy -= ratio * log(ratio) / log(256.0); |
| 180 | } |
| 181 | } |
| 182 | return avg_entropy * total; |
| 183 | }; |
| 184 | total_entropy_ += calc_entropy(counts); |
| 185 | total_opcode_entropy_ += calc_entropy(opcode_counts); |
| 186 | } |
| 187 | |
| 188 | void AnalyzeDebugInfo::Dump(std::ostream& os, uint64_t total_size) const { |
| 189 | os << "Debug info bytes " << Percent(total_bytes_, total_size) << "\n"; |
| 190 | |
| 191 | os << " DBG_END_SEQUENCE: " << Percent(total_end_seq_bytes_, total_size) << "\n"; |
| 192 | os << " DBG_ADVANCE_PC: " << Percent(total_advance_pc_bytes_, total_size) << "\n"; |
| 193 | os << " DBG_ADVANCE_LINE: " << Percent(total_advance_line_bytes_, total_size) << "\n"; |
| 194 | os << " DBG_START_LOCAL: " << Percent(total_start_local_bytes_, total_size) << "\n"; |
| 195 | os << " DBG_START_LOCAL_EXTENDED: " |
| 196 | << Percent(total_start_local_extended_bytes_, total_size) << "\n"; |
| 197 | os << " DBG_END_LOCAL: " << Percent(total_end_local_bytes_, total_size) << "\n"; |
| 198 | os << " DBG_RESTART_LOCAL: " << Percent(total_restart_local_bytes_, total_size) << "\n"; |
| 199 | os << " DBG_SET_PROLOGUE bytes " << Percent(total_epilogue_bytes_, total_size) << "\n"; |
| 200 | os << " DBG_SET_FILE bytes " << Percent(total_set_file_bytes_, total_size) << "\n"; |
| 201 | os << " special: " |
| 202 | << Percent(total_other_bytes_, total_size) << "\n"; |
| 203 | os << "Debug info entropy " << Percent(total_entropy_, total_size) << "\n"; |
| 204 | os << "Debug info opcode bytes " << Percent(total_opcode_bytes_, total_size) << "\n"; |
| 205 | os << "Debug info opcode entropy " << Percent(total_opcode_entropy_, total_size) << "\n"; |
| 206 | os << "Debug info non header bytes " << Percent(total_non_header_bytes_, total_size) << "\n"; |
| 207 | os << "Debug info deduped non header bytes " |
| 208 | << Percent(total_unique_non_header_bytes_, total_size) << "\n"; |
| 209 | } |
| 210 | |
Mathieu Chartier | 5840c84 | 2018-06-15 09:07:05 -0700 | [diff] [blame] | 211 | void CountDexIndices::ProcessDexFiles( |
| 212 | const std::vector<std::unique_ptr<const DexFile>>& dex_files) { |
| 213 | std::set<std::string> unique_field_names; |
| 214 | std::set<std::string> unique_method_names; |
| 215 | std::set<std::string> unique_type_names; |
| 216 | std::set<std::string> unique_mf_names; |
| 217 | for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { |
| 218 | for (size_t i = 0; i < dex_file->NumTypeIds(); ++i) { |
| 219 | unique_type_names.insert( |
| 220 | dex_file->StringDataByIdx(dex_file->GetTypeId(dex::TypeIndex(i)).descriptor_idx_)); |
| 221 | } |
| 222 | for (size_t i = 0; i < dex_file->NumFieldIds(); ++i) { |
| 223 | unique_field_names.insert(dex_file->StringDataByIdx(dex_file->GetFieldId(i).name_idx_)); |
| 224 | } |
| 225 | for (size_t i = 0; i < dex_file->NumMethodIds(); ++i) { |
| 226 | unique_method_names.insert(dex_file->StringDataByIdx(dex_file->GetMethodId(i).name_idx_)); |
| 227 | } |
| 228 | ProcessDexFile(*dex_file); |
| 229 | } |
| 230 | total_unique_method_names_ += unique_method_names.size(); |
| 231 | total_unique_field_names_ += unique_field_names.size(); |
| 232 | total_unique_type_names_ += unique_type_names.size(); |
| 233 | unique_mf_names = unique_field_names; |
| 234 | unique_mf_names.insert(unique_method_names.begin(), unique_method_names.end()); |
| 235 | total_unique_mf_names_ += unique_mf_names.size(); |
| 236 | } |
| 237 | |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 238 | void CountDexIndices::ProcessDexFile(const DexFile& dex_file) { |
| 239 | num_string_ids_ += dex_file.NumStringIds(); |
| 240 | num_method_ids_ += dex_file.NumMethodIds(); |
| 241 | num_field_ids_ += dex_file.NumFieldIds(); |
| 242 | num_type_ids_ += dex_file.NumTypeIds(); |
| 243 | num_class_defs_ += dex_file.NumClassDefs(); |
Mathieu Chartier | 7c3a8c1 | 2018-05-23 18:09:45 -0700 | [diff] [blame] | 244 | std::set<size_t> unique_code_items; |
Mathieu Chartier | 5840c84 | 2018-06-15 09:07:05 -0700 | [diff] [blame] | 245 | |
Mathieu Chartier | 05dc23e | 2018-05-22 11:56:14 -0700 | [diff] [blame] | 246 | for (ClassAccessor accessor : dex_file.GetClasses()) { |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 247 | std::set<size_t> unique_method_ids; |
| 248 | std::set<size_t> unique_string_ids; |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 249 | // Types accessed and count. |
| 250 | std::map<size_t, size_t> types_accessed; |
| 251 | |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 252 | // Maps from dex field index -> class field index (static or instance). |
| 253 | std::map<uint32_t, uint32_t> static_field_index_map_; |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 254 | size_t current_idx = 0u; |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 255 | for (const ClassAccessor::Field& field : accessor.GetStaticFields()) { |
| 256 | static_field_index_map_[field.GetIndex()] = current_idx++; |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 257 | } |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 258 | std::map<uint32_t, uint32_t> instance_field_index_map_; |
| 259 | current_idx = 0u; |
| 260 | for (const ClassAccessor::Field& field : accessor.GetInstanceFields()) { |
| 261 | instance_field_index_map_[field.GetIndex()] = current_idx++; |
| 262 | } |
Vladimir Marko | 6e75bee | 2018-06-27 10:53:29 +0100 | [diff] [blame] | 263 | auto ProcessFieldIndex = [&](uint32_t dex_field_idx, |
| 264 | uint32_t inout, |
| 265 | const std::map<uint32_t, uint32_t>& index_map, |
| 266 | /*inout*/ FieldAccessStats* stats) { |
| 267 | auto it = index_map.find(dex_field_idx); |
| 268 | if (it != index_map.end()) { |
| 269 | if (it->second < FieldAccessStats::kMaxFieldIndex) { |
| 270 | ++stats->field_index_[it->second]; |
| 271 | } else { |
| 272 | ++stats->field_index_other_; |
| 273 | } |
| 274 | } else { |
| 275 | ++stats->field_index_other_class_; |
| 276 | } |
| 277 | if (it != index_map.end() && |
| 278 | it->second < FieldAccessStats::kShortBytecodeFieldIndexOutCutOff && |
| 279 | inout < FieldAccessStats::kShortBytecodeInOutCutOff) { |
| 280 | ++stats->short_bytecode_; |
| 281 | } |
| 282 | }; |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 283 | auto ProcessInstanceField = [&](const Instruction& inst, |
| 284 | uint32_t first_arg_reg, |
| 285 | const std::map<uint32_t, uint32_t>& index_map, |
| 286 | /*inout*/ InstanceFieldAccessStats* stats) { |
| 287 | const uint32_t dex_field_idx = inst.VRegC_22c(); |
| 288 | ++types_accessed[dex_file.GetFieldId(dex_field_idx).class_idx_.index_]; |
| 289 | uint32_t input = inst.VRegA_22c(); |
| 290 | ++stats->inout_[input]; |
| 291 | const uint32_t receiver = inst.VRegB_22c(); |
| 292 | // FIXME: This is weird if receiver < first_arg_reg. |
| 293 | ++stats->receiver_[(receiver - first_arg_reg) & 0xF]; |
| 294 | if (first_arg_reg == receiver) { |
Vladimir Marko | 6e75bee | 2018-06-27 10:53:29 +0100 | [diff] [blame] | 295 | ProcessFieldIndex(dex_field_idx, input, index_map, stats); |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 296 | } |
| 297 | }; |
| 298 | auto ProcessStaticField = [&](const Instruction& inst, |
| 299 | const std::map<uint32_t, uint32_t>& index_map, |
| 300 | /*inout*/ StaticFieldAccessStats* stats) { |
| 301 | const uint32_t dex_field_idx = inst.VRegB_21c(); |
| 302 | ++types_accessed[dex_file.GetFieldId(dex_field_idx).class_idx_.index_]; |
| 303 | uint8_t output = inst.VRegA_21c(); |
| 304 | if (output < 16u) { |
| 305 | ++stats->inout_[output]; |
| 306 | } else { |
| 307 | ++stats->inout_other_; |
| 308 | } |
Vladimir Marko | 6e75bee | 2018-06-27 10:53:29 +0100 | [diff] [blame] | 309 | ProcessFieldIndex(dex_field_idx, output, index_map, stats); |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 310 | }; |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 311 | |
Mathieu Chartier | 0d896bd | 2018-05-25 00:20:27 -0700 | [diff] [blame] | 312 | for (const ClassAccessor::Method& method : accessor.GetMethods()) { |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 313 | CodeItemDataAccessor code_item(dex_file, method.GetCodeItem()); |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 314 | const uint32_t first_arg_reg = |
| 315 | ((method.GetAccessFlags() & kAccStatic) == 0) |
| 316 | ? code_item.RegistersSize() - code_item.InsSize() |
| 317 | : static_cast<uint32_t>(-1); |
| 318 | |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 319 | dex_code_bytes_ += code_item.InsnsSizeInBytes(); |
Mathieu Chartier | 7c3a8c1 | 2018-05-23 18:09:45 -0700 | [diff] [blame] | 320 | unique_code_items.insert(method.GetCodeItemOffset()); |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 321 | for (const DexInstructionPcPair& inst : code_item) { |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 322 | switch (inst->Opcode()) { |
| 323 | case Instruction::CONST_STRING: { |
| 324 | const dex::StringIndex string_index(inst->VRegB_21c()); |
| 325 | unique_string_ids.insert(string_index.index_); |
| 326 | ++num_string_ids_from_code_; |
| 327 | break; |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 328 | } |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 329 | case Instruction::IGET: |
| 330 | case Instruction::IGET_WIDE: |
| 331 | case Instruction::IGET_OBJECT: |
| 332 | case Instruction::IGET_BOOLEAN: |
| 333 | case Instruction::IGET_BYTE: |
| 334 | case Instruction::IGET_CHAR: |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 335 | case Instruction::IGET_SHORT: { |
| 336 | ProcessInstanceField( |
| 337 | inst.Inst(), first_arg_reg, instance_field_index_map_, &iget_stats_); |
| 338 | break; |
| 339 | } |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 340 | case Instruction::IPUT: |
| 341 | case Instruction::IPUT_WIDE: |
| 342 | case Instruction::IPUT_OBJECT: |
| 343 | case Instruction::IPUT_BOOLEAN: |
| 344 | case Instruction::IPUT_BYTE: |
| 345 | case Instruction::IPUT_CHAR: |
| 346 | case Instruction::IPUT_SHORT: { |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 347 | ProcessInstanceField( |
| 348 | inst.Inst(), first_arg_reg, instance_field_index_map_, &iput_stats_); |
| 349 | break; |
| 350 | } |
| 351 | case Instruction::SGET: |
| 352 | case Instruction::SGET_WIDE: |
| 353 | case Instruction::SGET_OBJECT: |
| 354 | case Instruction::SGET_BOOLEAN: |
| 355 | case Instruction::SGET_BYTE: |
| 356 | case Instruction::SGET_CHAR: |
| 357 | case Instruction::SGET_SHORT: { |
| 358 | ProcessStaticField(inst.Inst(), static_field_index_map_, &sget_stats_); |
| 359 | break; |
| 360 | } |
| 361 | case Instruction::SPUT: |
| 362 | case Instruction::SPUT_WIDE: |
| 363 | case Instruction::SPUT_OBJECT: |
| 364 | case Instruction::SPUT_BOOLEAN: |
| 365 | case Instruction::SPUT_BYTE: |
| 366 | case Instruction::SPUT_CHAR: |
| 367 | case Instruction::SPUT_SHORT: { |
| 368 | ProcessStaticField(inst.Inst(), static_field_index_map_, &sput_stats_); |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 369 | break; |
| 370 | } |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 371 | case Instruction::CONST_STRING_JUMBO: { |
| 372 | const dex::StringIndex string_index(inst->VRegB_31c()); |
| 373 | unique_string_ids.insert(string_index.index_); |
| 374 | ++num_string_ids_from_code_; |
| 375 | break; |
| 376 | } |
| 377 | // Invoke cases. |
| 378 | case Instruction::INVOKE_VIRTUAL: |
| 379 | case Instruction::INVOKE_VIRTUAL_RANGE: { |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 380 | uint32_t method_idx = DexMethodIndex(inst.Inst()); |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 381 | ++types_accessed[dex_file.GetMethodId(method_idx).class_idx_.index_]; |
Mathieu Chartier | c8c8d5f | 2018-05-22 11:56:14 -0700 | [diff] [blame] | 382 | if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) { |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 383 | ++same_class_virtual_; |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 384 | } |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 385 | ++total_virtual_; |
| 386 | unique_method_ids.insert(method_idx); |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 387 | break; |
| 388 | } |
| 389 | case Instruction::INVOKE_DIRECT: |
| 390 | case Instruction::INVOKE_DIRECT_RANGE: { |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 391 | uint32_t method_idx = DexMethodIndex(inst.Inst()); |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 392 | ++types_accessed[dex_file.GetMethodId(method_idx).class_idx_.index_]; |
Mathieu Chartier | c8c8d5f | 2018-05-22 11:56:14 -0700 | [diff] [blame] | 393 | if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) { |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 394 | ++same_class_direct_; |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 395 | } |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 396 | ++total_direct_; |
| 397 | unique_method_ids.insert(method_idx); |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 398 | break; |
| 399 | } |
| 400 | case Instruction::INVOKE_STATIC: |
| 401 | case Instruction::INVOKE_STATIC_RANGE: { |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 402 | uint32_t method_idx = DexMethodIndex(inst.Inst()); |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 403 | ++types_accessed[dex_file.GetMethodId(method_idx).class_idx_.index_]; |
Mathieu Chartier | c8c8d5f | 2018-05-22 11:56:14 -0700 | [diff] [blame] | 404 | if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) { |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 405 | ++same_class_static_; |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 406 | } |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 407 | ++total_static_; |
| 408 | unique_method_ids.insert(method_idx); |
| 409 | break; |
| 410 | } |
| 411 | case Instruction::INVOKE_INTERFACE: |
| 412 | case Instruction::INVOKE_INTERFACE_RANGE: { |
| 413 | uint32_t method_idx = DexMethodIndex(inst.Inst()); |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 414 | ++types_accessed[dex_file.GetMethodId(method_idx).class_idx_.index_]; |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 415 | if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) { |
| 416 | ++same_class_interface_; |
| 417 | } |
| 418 | ++total_interface_; |
| 419 | unique_method_ids.insert(method_idx); |
| 420 | break; |
| 421 | } |
| 422 | case Instruction::INVOKE_SUPER: |
| 423 | case Instruction::INVOKE_SUPER_RANGE: { |
| 424 | uint32_t method_idx = DexMethodIndex(inst.Inst()); |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 425 | ++types_accessed[dex_file.GetMethodId(method_idx).class_idx_.index_]; |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 426 | if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) { |
| 427 | ++same_class_super_; |
| 428 | } |
| 429 | ++total_super_; |
| 430 | unique_method_ids.insert(method_idx); |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 431 | break; |
| 432 | } |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 433 | case Instruction::NEW_ARRAY: { |
| 434 | ++types_accessed[inst->VRegC_22c()]; |
| 435 | break; |
| 436 | } |
| 437 | case Instruction::FILLED_NEW_ARRAY: { |
| 438 | ++types_accessed[inst->VRegB_35c()]; |
| 439 | break; |
| 440 | } |
| 441 | case Instruction::FILLED_NEW_ARRAY_RANGE: { |
| 442 | ++types_accessed[inst->VRegB_3rc()]; |
| 443 | break; |
| 444 | } |
| 445 | case Instruction::CONST_CLASS: |
| 446 | case Instruction::CHECK_CAST: |
| 447 | case Instruction::NEW_INSTANCE: { |
| 448 | ++types_accessed[inst->VRegB_21c()]; |
| 449 | break; |
| 450 | } |
| 451 | case Instruction::INSTANCE_OF: { |
| 452 | ++types_accessed[inst->VRegB_21c()]; |
| 453 | break; |
| 454 | } |
Mathieu Chartier | c2b4db6 | 2018-05-18 13:58:12 -0700 | [diff] [blame] | 455 | default: |
| 456 | break; |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 457 | } |
| 458 | } |
Mathieu Chartier | 0d896bd | 2018-05-25 00:20:27 -0700 | [diff] [blame] | 459 | } |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 460 | // Count uses of top 16n. |
| 461 | std::vector<size_t> uses; |
| 462 | for (auto&& p : types_accessed) { |
| 463 | uses.push_back(p.second); |
| 464 | } |
| 465 | std::sort(uses.rbegin(), uses.rend()); |
| 466 | for (size_t i = 0; i < uses.size(); ++i) { |
| 467 | if (i < 16) { |
| 468 | uses_top_types_ += uses[i]; |
| 469 | } |
| 470 | uses_all_types_ += uses[i]; |
| 471 | } |
| 472 | total_unique_types_ += types_accessed.size(); |
| 473 | total_unique_method_ids_ += unique_method_ids.size(); |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 474 | total_unique_string_ids_ += unique_string_ids.size(); |
| 475 | } |
Mathieu Chartier | 7c3a8c1 | 2018-05-23 18:09:45 -0700 | [diff] [blame] | 476 | total_unique_code_items_ += unique_code_items.size(); |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 477 | } |
| 478 | |
Mathieu Chartier | 35ddc6f | 2018-05-09 11:34:07 -0700 | [diff] [blame] | 479 | void CountDexIndices::Dump(std::ostream& os, uint64_t total_size) const { |
Vladimir Marko | 6e75bee | 2018-06-27 10:53:29 +0100 | [diff] [blame] | 480 | auto DumpFieldIndexes = [&](const FieldAccessStats& stats) { |
| 481 | const uint64_t fields_idx_total = std::accumulate( |
| 482 | stats.field_index_, |
| 483 | stats.field_index_ + FieldAccessStats::kMaxFieldIndex, |
| 484 | stats.field_index_other_ + stats.field_index_other_class_); |
| 485 | for (size_t i = 0; i < FieldAccessStats::kMaxFieldIndex; ++i) { |
| 486 | os << " field_idx=" << i << ": " << Percent(stats.field_index_[i], fields_idx_total) << "\n"; |
| 487 | } |
| 488 | os << " field_idx=other: " << Percent(stats.field_index_other_, fields_idx_total) << "\n"; |
| 489 | os << " field_idx=other_class: " << Percent(stats.field_index_other_class_, fields_idx_total) |
| 490 | << "\n"; |
| 491 | }; |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 492 | auto DumpInstanceFieldStats = [&](const char* tag, const InstanceFieldAccessStats& stats) { |
| 493 | const uint64_t fields_total = std::accumulate(stats.inout_, stats.inout_ + 16u, 0u); |
| 494 | os << tag << "\n"; |
| 495 | for (size_t i = 0; i < 16; ++i) { |
| 496 | os << " receiver_reg=" << i << ": " << Percent(stats.receiver_[i], fields_total) << "\n"; |
| 497 | } |
| 498 | DCHECK(tag[1] == 'G' || tag[1] == 'P'); |
| 499 | const char* inout_tag = (tag[1] == 'G') ? "output_reg" : "input_reg"; |
| 500 | for (size_t i = 0; i < 16; ++i) { |
| 501 | os << " " << inout_tag << "=" << i << ": " << Percent(stats.inout_[i], fields_total) << "\n"; |
| 502 | } |
Vladimir Marko | 6e75bee | 2018-06-27 10:53:29 +0100 | [diff] [blame] | 503 | DumpFieldIndexes(stats); |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 504 | os << " short_bytecode: " << Percent(stats.short_bytecode_, fields_total) << "\n"; |
| 505 | os << " short_bytecode_savings=" << Percent(stats.short_bytecode_ * 2, total_size) << "\n"; |
| 506 | }; |
| 507 | DumpInstanceFieldStats("IGET", iget_stats_); |
| 508 | DumpInstanceFieldStats("IPUT", iput_stats_); |
| 509 | |
| 510 | auto DumpStaticFieldStats = [&](const char* tag, const StaticFieldAccessStats& stats) { |
| 511 | const uint64_t fields_total = |
| 512 | std::accumulate(stats.inout_, stats.inout_ + 16u, stats.inout_other_); |
| 513 | os << tag << "\n"; |
| 514 | DCHECK(tag[1] == 'G' || tag[1] == 'P'); |
| 515 | const char* inout_tag = (tag[1] == 'G') ? "output_reg" : "input_reg"; |
| 516 | for (size_t i = 0; i < 16; ++i) { |
| 517 | os << " " << inout_tag << "=" << i << ": " << Percent(stats.inout_[i], fields_total) << "\n"; |
| 518 | } |
| 519 | os << " " << inout_tag << "=other: " << Percent(stats.inout_other_, fields_total) << "\n"; |
Vladimir Marko | 6e75bee | 2018-06-27 10:53:29 +0100 | [diff] [blame] | 520 | DumpFieldIndexes(stats); |
Vladimir Marko | 93bffcc | 2018-06-26 15:49:16 +0100 | [diff] [blame] | 521 | os << " short_bytecode: " << Percent(stats.short_bytecode_, fields_total) << "\n"; |
| 522 | os << " short_bytecode_savings=" << Percent(stats.short_bytecode_ * 2, total_size) << "\n"; |
| 523 | }; |
| 524 | DumpStaticFieldStats("SGET", sget_stats_); |
| 525 | DumpStaticFieldStats("SPUT", sput_stats_); |
| 526 | |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 527 | os << "Num string ids: " << num_string_ids_ << "\n"; |
| 528 | os << "Num method ids: " << num_method_ids_ << "\n"; |
| 529 | os << "Num field ids: " << num_field_ids_ << "\n"; |
| 530 | os << "Num type ids: " << num_type_ids_ << "\n"; |
| 531 | os << "Num class defs: " << num_class_defs_ << "\n"; |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 532 | os << "Direct same class: " << PercentDivide(same_class_direct_, total_direct_) << "\n"; |
| 533 | os << "Virtual same class: " << PercentDivide(same_class_virtual_, total_virtual_) << "\n"; |
| 534 | os << "Static same class: " << PercentDivide(same_class_static_, total_static_) << "\n"; |
| 535 | os << "Interface same class: " << PercentDivide(same_class_interface_, total_interface_) << "\n"; |
| 536 | os << "Super same class: " << PercentDivide(same_class_super_, total_super_) << "\n"; |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 537 | os << "Num strings accessed from code: " << num_string_ids_from_code_ << "\n"; |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 538 | os << "Avg unique methods accessed per class: " |
Mathieu Chartier | 12dd8a9 | 2018-06-12 11:17:22 -0700 | [diff] [blame] | 539 | << static_cast<double>(total_unique_method_ids_) / static_cast<double>(num_class_defs_) << "\n"; |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 540 | os << "Avg unique strings accessed per class: " |
Mathieu Chartier | 12dd8a9 | 2018-06-12 11:17:22 -0700 | [diff] [blame] | 541 | << static_cast<double>(total_unique_string_ids_) / static_cast<double>(num_class_defs_) << "\n"; |
| 542 | os << "Avg unique types accessed per class " << |
| 543 | static_cast<double>(total_unique_types_) / static_cast<double>(num_class_defs_) << "\n"; |
| 544 | os << "Total unique methods accessed per class: " |
| 545 | << Percent(total_unique_method_ids_, total_size) << "\n"; |
| 546 | os << "Total unique strings accessed per class: " |
| 547 | << Percent(total_unique_string_ids_, total_size) << "\n"; |
| 548 | os << "Total unique types accessed per class: " |
| 549 | << Percent(total_unique_types_, total_size) << "\n"; |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 550 | const size_t same_class_total = |
| 551 | same_class_direct_ + |
| 552 | same_class_virtual_ + |
| 553 | same_class_static_ + |
| 554 | same_class_interface_ + |
| 555 | same_class_super_; |
| 556 | const size_t other_class_total = |
| 557 | total_direct_ + |
| 558 | total_virtual_ + |
| 559 | total_static_ + |
| 560 | total_interface_ + |
| 561 | total_super_; |
Mathieu Chartier | 5840c84 | 2018-06-15 09:07:05 -0700 | [diff] [blame] | 562 | os << "Unique method names: " << Percent(total_unique_method_names_, num_field_ids_) << "\n"; |
| 563 | os << "Unique field names: " << Percent(total_unique_field_names_, num_method_ids_) << "\n"; |
| 564 | os << "Unique type names: " << Percent(total_unique_type_names_, num_type_ids_) << "\n"; |
| 565 | os << "Unique method/field names: " |
| 566 | << Percent(total_unique_mf_names_, num_field_ids_ + num_method_ids_) << "\n"; |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 567 | os << "Same class invokes: " << PercentDivide(same_class_total, other_class_total) << "\n"; |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 568 | os << "Invokes from code: " << (same_class_total + other_class_total) << "\n"; |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 569 | os << "Type uses on top types: " << PercentDivide(uses_top_types_, uses_all_types_) << "\n"; |
| 570 | os << "Type uses 1b savings: " << PercentDivide(uses_top_types_, total_size) << "\n"; |
Mathieu Chartier | 7c3a8c1 | 2018-05-23 18:09:45 -0700 | [diff] [blame] | 571 | os << "Total Dex code bytes: " << Percent(dex_code_bytes_, total_size) << "\n"; |
| 572 | os << "Total unique code items: " << total_unique_code_items_ << "\n"; |
| 573 | os << "Total Dex size: " << total_size << "\n"; |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 574 | } |
| 575 | |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 576 | void CodeMetrics::ProcessDexFile(const DexFile& dex_file) { |
| 577 | for (ClassAccessor accessor : dex_file.GetClasses()) { |
| 578 | for (const ClassAccessor::Method& method : accessor.GetMethods()) { |
| 579 | bool space_for_out_arg = false; |
| 580 | for (const DexInstructionPcPair& inst : method.GetInstructions()) { |
| 581 | switch (inst->Opcode()) { |
| 582 | case Instruction::INVOKE_VIRTUAL: |
| 583 | case Instruction::INVOKE_DIRECT: |
| 584 | case Instruction::INVOKE_SUPER: |
| 585 | case Instruction::INVOKE_INTERFACE: |
| 586 | case Instruction::INVOKE_STATIC: { |
| 587 | const uint32_t args = NumberOfArgs(inst.Inst()); |
| 588 | CHECK_LT(args, kMaxArgCount); |
| 589 | ++arg_counts_[args]; |
| 590 | space_for_out_arg = args < kMaxArgCount - 1; |
| 591 | break; |
| 592 | } |
| 593 | case Instruction::MOVE_RESULT: |
| 594 | case Instruction::MOVE_RESULT_OBJECT: { |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 595 | if (space_for_out_arg && inst->VRegA_11x() < 16) { |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 596 | move_result_savings_ += inst->SizeInCodeUnits() * 2; |
| 597 | } |
| 598 | break; |
| 599 | } |
| 600 | default: |
| 601 | space_for_out_arg = false; |
| 602 | break; |
| 603 | } |
| 604 | } |
| 605 | } |
| 606 | } |
| 607 | } |
Mathieu Chartier | ef2fa26 | 2018-04-12 15:14:07 -0700 | [diff] [blame] | 608 | |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 609 | void CodeMetrics::Dump(std::ostream& os, uint64_t total_size) const { |
| 610 | const uint64_t total = std::accumulate(arg_counts_, arg_counts_ + kMaxArgCount, 0u); |
| 611 | for (size_t i = 0; i < kMaxArgCount; ++i) { |
| 612 | os << "args=" << i << ": " << Percent(arg_counts_[i], total) << "\n"; |
| 613 | } |
| 614 | os << "Move result savings: " << Percent(move_result_savings_, total_size) << "\n"; |
| 615 | os << "One byte invoke savings: " << Percent(total, total_size) << "\n"; |
Mathieu Chartier | 5d16154 | 2018-05-31 11:02:39 -0700 | [diff] [blame] | 616 | const uint64_t low_arg_total = std::accumulate(arg_counts_, arg_counts_ + 2, 0u); |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 617 | os << "Low arg savings: " << Percent(low_arg_total * 2, total_size) << "\n"; |
| 618 | } |
| 619 | |
Mathieu Chartier | 12dd8a9 | 2018-06-12 11:17:22 -0700 | [diff] [blame] | 620 | } // namespace dexanalyze |
Mathieu Chartier | b7ae0b1 | 2018-05-29 09:25:57 -0700 | [diff] [blame] | 621 | } // namespace art |