/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "dexanalyze_experiments.h"

#include <algorithm>
#include <stdint.h>
#include <inttypes.h>
#include <iostream>
#include <map>
#include <vector>

#include "android-base/stringprintf.h"
#include "dex/class_accessor-inl.h"
#include "dex/class_iterator.h"
#include "dex/code_item_accessors-inl.h"
#include "dex/dex_instruction-inl.h"
#include "dex/standard_dex_file.h"
#include "dex/utf-inl.h"

namespace art {
namespace dexanalyze {

bool IsRange(Instruction::Code code) {
  return code == Instruction::INVOKE_VIRTUAL_RANGE ||
      code == Instruction::INVOKE_DIRECT_RANGE ||
      code == Instruction::INVOKE_SUPER_RANGE ||
      code == Instruction::INVOKE_STATIC_RANGE ||
      code == Instruction::INVOKE_INTERFACE_RANGE;
}

uint16_t NumberOfArgs(const Instruction& inst) {
  return IsRange(inst.Opcode()) ? inst.VRegA_3rc() : inst.VRegA_35c();
}

uint16_t DexMethodIndex(const Instruction& inst) {
  return IsRange(inst.Opcode()) ? inst.VRegB_3rc() : inst.VRegB_35c();
}

std::string Percent(uint64_t value, uint64_t max) {
  if (max == 0) {
    return "0";
  }
  return android::base::StringPrintf(
      "%" PRId64 "(%.2f%%)",
      value,
      static_cast<double>(value * 100) / static_cast<double>(max));
}

std::string PercentDivide(uint64_t value, uint64_t max) {
  if (max == 0) {
    return "0";
  }
  return android::base::StringPrintf(
      "%" PRId64 "/%" PRId64 "(%.2f%%)",
      value,
      max,
      static_cast<double>(value * 100) / static_cast<double>(max));
}

size_t PrefixLen(const std::string& a, const std::string& b) {
  size_t len = 0;
  for (; len < a.length() && len < b.length() && a[len] == b[len]; ++len) {}
  return len;
}

void Experiment::ProcessDexFiles(const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
  for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
    ProcessDexFile(*dex_file);
  }
}

void AnalyzeDebugInfo::ProcessDexFiles(
    const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
  std::set<const uint8_t*> seen;
  std::vector<size_t> counts(256, 0u);
  std::vector<size_t> opcode_counts(256, 0u);
  std::set<std::vector<uint8_t>> unique_non_header;
  for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
    for (ClassAccessor accessor : dex_file->GetClasses()) {
      for (const ClassAccessor::Method& method : accessor.GetMethods()) {
        CodeItemDebugInfoAccessor code_item(*dex_file, method.GetCodeItem(), method.GetIndex());
        const uint8_t* debug_info = dex_file->GetDebugInfoStream(code_item.DebugInfoOffset());
        if (debug_info != nullptr && seen.insert(debug_info).second) {
          const uint8_t* stream = debug_info;
          DecodeUnsignedLeb128(&stream);  // line_start
          uint32_t parameters_size = DecodeUnsignedLeb128(&stream);
          for (uint32_t i = 0; i < parameters_size; ++i) {
            DecodeUnsignedLeb128P1(&stream);  // Parameter name.
          }
          bool done = false;
          const uint8_t* after_header_start = stream;
          while (!done) {
            const uint8_t* const op_start = stream;
            uint8_t opcode = *stream++;
            ++opcode_counts[opcode];
            ++total_opcode_bytes_;
            switch (opcode) {
              case DexFile::DBG_END_SEQUENCE:
                ++total_end_seq_bytes_;
                done = true;
                break;
              case DexFile::DBG_ADVANCE_PC:
                DecodeUnsignedLeb128(&stream);  // addr_diff
                total_advance_pc_bytes_ += stream - op_start;
                break;
              case DexFile::DBG_ADVANCE_LINE:
                DecodeSignedLeb128(&stream);  // line_diff
                total_advance_line_bytes_ += stream - op_start;
                break;
              case DexFile::DBG_START_LOCAL:
                DecodeUnsignedLeb128(&stream);  // register_num
                DecodeUnsignedLeb128P1(&stream);  // name_idx
                DecodeUnsignedLeb128P1(&stream);  // type_idx
                total_start_local_bytes_ += stream - op_start;
                break;
              case DexFile::DBG_START_LOCAL_EXTENDED:
                DecodeUnsignedLeb128(&stream);  // register_num
                DecodeUnsignedLeb128P1(&stream);  // name_idx
                DecodeUnsignedLeb128P1(&stream);  // type_idx
                DecodeUnsignedLeb128P1(&stream);  // sig_idx
                total_start_local_extended_bytes_ += stream - op_start;
                break;
              case DexFile::DBG_END_LOCAL:
                DecodeUnsignedLeb128(&stream);  // register_num
                total_end_local_bytes_ += stream - op_start;
                break;
              case DexFile::DBG_RESTART_LOCAL:
                DecodeUnsignedLeb128(&stream);  // register_num
                total_restart_local_bytes_ += stream - op_start;
                break;
              case DexFile::DBG_SET_PROLOGUE_END:
              case DexFile::DBG_SET_EPILOGUE_BEGIN:
                total_epilogue_bytes_ += stream - op_start;
                break;
              case DexFile::DBG_SET_FILE: {
                DecodeUnsignedLeb128P1(&stream);  // name_idx
                total_set_file_bytes_ += stream - op_start;
                break;
              }
              default: {
                total_other_bytes_ += stream - op_start;
                break;
              }
            }
          }
          const size_t bytes = stream - debug_info;
          total_bytes_ += bytes;
          total_non_header_bytes_ += stream - after_header_start;
          if (unique_non_header.insert(std::vector<uint8_t>(after_header_start, stream)).second) {
            total_unique_non_header_bytes_ += stream - after_header_start;
          }
          for (size_t i = 0; i < bytes; ++i) {
            ++counts[debug_info[i]];
          }
        }
      }
    }
  }
  auto calc_entropy = [](std::vector<size_t> data) {
    size_t total = std::accumulate(data.begin(), data.end(), 0u);
    double avg_entropy = 0.0;
    for (size_t c : data) {
      if (c > 0) {
        double ratio = static_cast<double>(c) / static_cast<double>(total);
        avg_entropy -= ratio * log(ratio) / log(256.0);
      }
    }
    return avg_entropy * total;
  };
  total_entropy_ += calc_entropy(counts);
  total_opcode_entropy_ += calc_entropy(opcode_counts);
}

void AnalyzeDebugInfo::Dump(std::ostream& os, uint64_t total_size) const {
  os << "Debug info bytes " << Percent(total_bytes_, total_size) << "\n";

  os << "  DBG_END_SEQUENCE: " << Percent(total_end_seq_bytes_, total_size) << "\n";
  os << "  DBG_ADVANCE_PC: " << Percent(total_advance_pc_bytes_, total_size) << "\n";
  os << "  DBG_ADVANCE_LINE: " << Percent(total_advance_line_bytes_, total_size) << "\n";
  os << "  DBG_START_LOCAL: " << Percent(total_start_local_bytes_, total_size) << "\n";
  os << "  DBG_START_LOCAL_EXTENDED: "
     << Percent(total_start_local_extended_bytes_, total_size) << "\n";
  os << "  DBG_END_LOCAL: " << Percent(total_end_local_bytes_, total_size) << "\n";
  os << "  DBG_RESTART_LOCAL: " << Percent(total_restart_local_bytes_, total_size) << "\n";
  os << "  DBG_SET_PROLOGUE bytes " << Percent(total_epilogue_bytes_, total_size) << "\n";
  os << "  DBG_SET_FILE bytes " << Percent(total_set_file_bytes_, total_size) << "\n";
  os << "  special: "
      << Percent(total_other_bytes_, total_size) << "\n";
  os << "Debug info entropy " << Percent(total_entropy_, total_size) << "\n";
  os << "Debug info opcode bytes " << Percent(total_opcode_bytes_, total_size) << "\n";
  os << "Debug info opcode entropy " << Percent(total_opcode_entropy_, total_size) << "\n";
  os << "Debug info non header bytes " << Percent(total_non_header_bytes_, total_size) << "\n";
  os << "Debug info deduped non header bytes "
     << Percent(total_unique_non_header_bytes_, total_size) << "\n";
}

void CountDexIndices::ProcessDexFiles(
    const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
  std::set<std::string> unique_field_names;
  std::set<std::string> unique_method_names;
  std::set<std::string> unique_type_names;
  std::set<std::string> unique_mf_names;
  for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
    for (size_t i = 0; i < dex_file->NumTypeIds(); ++i) {
      unique_type_names.insert(
          dex_file->StringDataByIdx(dex_file->GetTypeId(dex::TypeIndex(i)).descriptor_idx_));
    }
    for (size_t i = 0; i < dex_file->NumFieldIds(); ++i) {
      unique_field_names.insert(dex_file->StringDataByIdx(dex_file->GetFieldId(i).name_idx_));
    }
    for (size_t i = 0; i < dex_file->NumMethodIds(); ++i) {
      unique_method_names.insert(dex_file->StringDataByIdx(dex_file->GetMethodId(i).name_idx_));
    }
    ProcessDexFile(*dex_file);
  }
  total_unique_method_names_ += unique_method_names.size();
  total_unique_field_names_ += unique_field_names.size();
  total_unique_type_names_ += unique_type_names.size();
  unique_mf_names = unique_field_names;
  unique_mf_names.insert(unique_method_names.begin(), unique_method_names.end());
  total_unique_mf_names_ += unique_mf_names.size();
}

void CountDexIndices::ProcessDexFile(const DexFile& dex_file) {
  num_string_ids_ += dex_file.NumStringIds();
  num_method_ids_ += dex_file.NumMethodIds();
  num_field_ids_ += dex_file.NumFieldIds();
  num_type_ids_ += dex_file.NumTypeIds();
  num_class_defs_ += dex_file.NumClassDefs();
  std::set<size_t> unique_code_items;

  for (ClassAccessor accessor : dex_file.GetClasses()) {
    std::set<size_t> unique_method_ids;
    std::set<size_t> unique_string_ids;
    // Types accessed and count.
    std::map<size_t, size_t> types_accessed;

    // Maps from dex field index -> class field index (static or instance).
    std::map<uint32_t, uint32_t> static_field_index_map_;
    size_t current_idx = 0u;
    for (const ClassAccessor::Field& field : accessor.GetStaticFields()) {
      static_field_index_map_[field.GetIndex()] = current_idx++;
    }
    std::map<uint32_t, uint32_t> instance_field_index_map_;
    current_idx = 0u;
    for (const ClassAccessor::Field& field : accessor.GetInstanceFields()) {
      instance_field_index_map_[field.GetIndex()] = current_idx++;
    }
    auto ProcessFieldIndex = [&](uint32_t dex_field_idx,
                                 uint32_t inout,
                                 const std::map<uint32_t, uint32_t>& index_map,
                                 /*inout*/ FieldAccessStats* stats) {
      auto it = index_map.find(dex_field_idx);
      if (it != index_map.end()) {
        if (it->second < FieldAccessStats::kMaxFieldIndex) {
          ++stats->field_index_[it->second];
        } else {
          ++stats->field_index_other_;
        }
      } else {
        ++stats->field_index_other_class_;
      }
      if (it != index_map.end() &&
          it->second < FieldAccessStats::kShortBytecodeFieldIndexOutCutOff &&
          inout < FieldAccessStats::kShortBytecodeInOutCutOff) {
        ++stats->short_bytecode_;
      }
    };
    auto ProcessInstanceField = [&](const Instruction& inst,
                                    uint32_t first_arg_reg,
                                    const std::map<uint32_t, uint32_t>& index_map,
                                    /*inout*/ InstanceFieldAccessStats* stats) {
      const uint32_t dex_field_idx = inst.VRegC_22c();
      ++types_accessed[dex_file.GetFieldId(dex_field_idx).class_idx_.index_];
      uint32_t input = inst.VRegA_22c();
      ++stats->inout_[input];
      const uint32_t receiver = inst.VRegB_22c();
      // FIXME: This is weird if receiver < first_arg_reg.
      ++stats->receiver_[(receiver - first_arg_reg) & 0xF];
      if (first_arg_reg == receiver) {
        ProcessFieldIndex(dex_field_idx, input, index_map, stats);
      }
    };
    auto ProcessStaticField = [&](const Instruction& inst,
                                  const std::map<uint32_t, uint32_t>& index_map,
                                  /*inout*/ StaticFieldAccessStats* stats) {
      const uint32_t dex_field_idx = inst.VRegB_21c();
      ++types_accessed[dex_file.GetFieldId(dex_field_idx).class_idx_.index_];
      uint8_t output = inst.VRegA_21c();
      if (output < 16u) {
        ++stats->inout_[output];
      } else {
        ++stats->inout_other_;
      }
      ProcessFieldIndex(dex_field_idx, output, index_map, stats);
    };

    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
      CodeItemDataAccessor code_item(dex_file, method.GetCodeItem());
      const uint32_t first_arg_reg =
          ((method.GetAccessFlags() & kAccStatic) == 0)
          ? code_item.RegistersSize() - code_item.InsSize()
          : static_cast<uint32_t>(-1);

      dex_code_bytes_ += code_item.InsnsSizeInBytes();
      unique_code_items.insert(method.GetCodeItemOffset());
      for (const DexInstructionPcPair& inst : code_item) {
        switch (inst->Opcode()) {
          case Instruction::CONST_STRING: {
            const dex::StringIndex string_index(inst->VRegB_21c());
            unique_string_ids.insert(string_index.index_);
            ++num_string_ids_from_code_;
            break;
          }
          case Instruction::IGET:
          case Instruction::IGET_WIDE:
          case Instruction::IGET_OBJECT:
          case Instruction::IGET_BOOLEAN:
          case Instruction::IGET_BYTE:
          case Instruction::IGET_CHAR:
          case Instruction::IGET_SHORT: {
            ProcessInstanceField(
                inst.Inst(), first_arg_reg, instance_field_index_map_, &iget_stats_);
            break;
          }
          case Instruction::IPUT:
          case Instruction::IPUT_WIDE:
          case Instruction::IPUT_OBJECT:
          case Instruction::IPUT_BOOLEAN:
          case Instruction::IPUT_BYTE:
          case Instruction::IPUT_CHAR:
          case Instruction::IPUT_SHORT: {
            ProcessInstanceField(
                inst.Inst(), first_arg_reg, instance_field_index_map_, &iput_stats_);
            break;
          }
          case Instruction::SGET:
          case Instruction::SGET_WIDE:
          case Instruction::SGET_OBJECT:
          case Instruction::SGET_BOOLEAN:
          case Instruction::SGET_BYTE:
          case Instruction::SGET_CHAR:
          case Instruction::SGET_SHORT: {
            ProcessStaticField(inst.Inst(), static_field_index_map_, &sget_stats_);
            break;
          }
          case Instruction::SPUT:
          case Instruction::SPUT_WIDE:
          case Instruction::SPUT_OBJECT:
          case Instruction::SPUT_BOOLEAN:
          case Instruction::SPUT_BYTE:
          case Instruction::SPUT_CHAR:
          case Instruction::SPUT_SHORT: {
            ProcessStaticField(inst.Inst(), static_field_index_map_, &sput_stats_);
            break;
          }
          case Instruction::CONST_STRING_JUMBO: {
            const dex::StringIndex string_index(inst->VRegB_31c());
            unique_string_ids.insert(string_index.index_);
            ++num_string_ids_from_code_;
            break;
          }
          // Invoke cases.
          case Instruction::INVOKE_VIRTUAL:
          case Instruction::INVOKE_VIRTUAL_RANGE: {
            uint32_t method_idx = DexMethodIndex(inst.Inst());
            ++types_accessed[dex_file.GetMethodId(method_idx).class_idx_.index_];
            if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) {
              ++same_class_virtual_;
            }
            ++total_virtual_;
            unique_method_ids.insert(method_idx);
            break;
          }
          case Instruction::INVOKE_DIRECT:
          case Instruction::INVOKE_DIRECT_RANGE: {
            uint32_t method_idx = DexMethodIndex(inst.Inst());
            ++types_accessed[dex_file.GetMethodId(method_idx).class_idx_.index_];
            if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) {
              ++same_class_direct_;
            }
            ++total_direct_;
            unique_method_ids.insert(method_idx);
            break;
          }
          case Instruction::INVOKE_STATIC:
          case Instruction::INVOKE_STATIC_RANGE: {
            uint32_t method_idx = DexMethodIndex(inst.Inst());
            ++types_accessed[dex_file.GetMethodId(method_idx).class_idx_.index_];
            if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) {
              ++same_class_static_;
            }
            ++total_static_;
            unique_method_ids.insert(method_idx);
            break;
          }
          case Instruction::INVOKE_INTERFACE:
          case Instruction::INVOKE_INTERFACE_RANGE: {
            uint32_t method_idx = DexMethodIndex(inst.Inst());
            ++types_accessed[dex_file.GetMethodId(method_idx).class_idx_.index_];
            if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) {
              ++same_class_interface_;
            }
            ++total_interface_;
            unique_method_ids.insert(method_idx);
            break;
          }
          case Instruction::INVOKE_SUPER:
          case Instruction::INVOKE_SUPER_RANGE: {
            uint32_t method_idx = DexMethodIndex(inst.Inst());
            ++types_accessed[dex_file.GetMethodId(method_idx).class_idx_.index_];
            if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) {
              ++same_class_super_;
            }
            ++total_super_;
            unique_method_ids.insert(method_idx);
            break;
          }
          case Instruction::NEW_ARRAY: {
            ++types_accessed[inst->VRegC_22c()];
            break;
          }
          case Instruction::FILLED_NEW_ARRAY: {
            ++types_accessed[inst->VRegB_35c()];
            break;
          }
          case Instruction::FILLED_NEW_ARRAY_RANGE: {
            ++types_accessed[inst->VRegB_3rc()];
            break;
          }
          case Instruction::CONST_CLASS:
          case Instruction::CHECK_CAST:
          case Instruction::NEW_INSTANCE: {
            ++types_accessed[inst->VRegB_21c()];
            break;
          }
          case Instruction::INSTANCE_OF: {
            ++types_accessed[inst->VRegB_21c()];
            break;
          }
          default:
            break;
        }
      }
    }
    // Count uses of top 16n.
    std::vector<size_t> uses;
    for (auto&& p : types_accessed) {
      uses.push_back(p.second);
    }
    std::sort(uses.rbegin(), uses.rend());
    for (size_t i = 0; i < uses.size(); ++i) {
      if (i < 16) {
        uses_top_types_ += uses[i];
      }
      uses_all_types_ += uses[i];
    }
    total_unique_types_ += types_accessed.size();
    total_unique_method_ids_ += unique_method_ids.size();
    total_unique_string_ids_ += unique_string_ids.size();
  }
  total_unique_code_items_ += unique_code_items.size();
}

void CountDexIndices::Dump(std::ostream& os, uint64_t total_size) const {
  auto DumpFieldIndexes = [&](const FieldAccessStats& stats) {
    const uint64_t fields_idx_total = std::accumulate(
        stats.field_index_,
        stats.field_index_ + FieldAccessStats::kMaxFieldIndex,
        stats.field_index_other_ + stats.field_index_other_class_);
    for (size_t i = 0; i < FieldAccessStats::kMaxFieldIndex; ++i) {
      os << "  field_idx=" << i << ": " << Percent(stats.field_index_[i], fields_idx_total) << "\n";
    }
    os << "  field_idx=other: " << Percent(stats.field_index_other_, fields_idx_total) << "\n";
    os << "  field_idx=other_class: " << Percent(stats.field_index_other_class_, fields_idx_total)
       << "\n";
  };
  auto DumpInstanceFieldStats = [&](const char* tag, const InstanceFieldAccessStats& stats) {
    const uint64_t fields_total = std::accumulate(stats.inout_, stats.inout_ + 16u, 0u);
    os << tag << "\n";
    for (size_t i = 0; i < 16; ++i) {
      os << "  receiver_reg=" << i << ": " << Percent(stats.receiver_[i], fields_total) << "\n";
    }
    DCHECK(tag[1] == 'G' || tag[1] == 'P');
    const char* inout_tag = (tag[1] == 'G') ? "output_reg" : "input_reg";
    for (size_t i = 0; i < 16; ++i) {
      os << "  " << inout_tag << "=" << i << ": " << Percent(stats.inout_[i], fields_total) << "\n";
    }
    DumpFieldIndexes(stats);
    os << "  short_bytecode: " << Percent(stats.short_bytecode_, fields_total) << "\n";
    os << "  short_bytecode_savings=" << Percent(stats.short_bytecode_ * 2, total_size) << "\n";
  };
  DumpInstanceFieldStats("IGET", iget_stats_);
  DumpInstanceFieldStats("IPUT", iput_stats_);

  auto DumpStaticFieldStats = [&](const char* tag, const StaticFieldAccessStats& stats) {
    const uint64_t fields_total =
        std::accumulate(stats.inout_, stats.inout_ + 16u, stats.inout_other_);
    os << tag << "\n";
    DCHECK(tag[1] == 'G' || tag[1] == 'P');
    const char* inout_tag = (tag[1] == 'G') ? "output_reg" : "input_reg";
    for (size_t i = 0; i < 16; ++i) {
      os << "  " << inout_tag << "=" << i << ": " << Percent(stats.inout_[i], fields_total) << "\n";
    }
    os << "  " << inout_tag << "=other: " << Percent(stats.inout_other_, fields_total) << "\n";
    DumpFieldIndexes(stats);
    os << "  short_bytecode: " << Percent(stats.short_bytecode_, fields_total) << "\n";
    os << "  short_bytecode_savings=" << Percent(stats.short_bytecode_ * 2, total_size) << "\n";
  };
  DumpStaticFieldStats("SGET", sget_stats_);
  DumpStaticFieldStats("SPUT", sput_stats_);

  os << "Num string ids: " << num_string_ids_ << "\n";
  os << "Num method ids: " << num_method_ids_ << "\n";
  os << "Num field ids: " << num_field_ids_ << "\n";
  os << "Num type ids: " << num_type_ids_ << "\n";
  os << "Num class defs: " << num_class_defs_ << "\n";
  os << "Direct same class: " << PercentDivide(same_class_direct_, total_direct_) << "\n";
  os << "Virtual same class: " << PercentDivide(same_class_virtual_, total_virtual_) << "\n";
  os << "Static same class: " << PercentDivide(same_class_static_, total_static_) << "\n";
  os << "Interface same class: " << PercentDivide(same_class_interface_, total_interface_) << "\n";
  os << "Super same class: " << PercentDivide(same_class_super_, total_super_) << "\n";
  os << "Num strings accessed from code: " << num_string_ids_from_code_ << "\n";
  os << "Avg unique methods accessed per class: "
     << static_cast<double>(total_unique_method_ids_) / static_cast<double>(num_class_defs_) << "\n";
  os << "Avg unique strings accessed per class: "
     << static_cast<double>(total_unique_string_ids_) / static_cast<double>(num_class_defs_) << "\n";
  os << "Avg unique types accessed per class " <<
        static_cast<double>(total_unique_types_) / static_cast<double>(num_class_defs_) << "\n";
  os << "Total unique methods accessed per class: "
     << Percent(total_unique_method_ids_, total_size) << "\n";
  os << "Total unique strings accessed per class: "
     << Percent(total_unique_string_ids_, total_size) << "\n";
  os << "Total unique types accessed per class: "
     << Percent(total_unique_types_, total_size) << "\n";
  const size_t same_class_total =
      same_class_direct_ +
      same_class_virtual_ +
      same_class_static_ +
      same_class_interface_ +
      same_class_super_;
  const size_t other_class_total =
      total_direct_ +
      total_virtual_ +
      total_static_ +
      total_interface_ +
      total_super_;
  os << "Unique method names: " << Percent(total_unique_method_names_, num_field_ids_) << "\n";
  os << "Unique field names: " << Percent(total_unique_field_names_, num_method_ids_) << "\n";
  os << "Unique type names: " << Percent(total_unique_type_names_, num_type_ids_) << "\n";
  os << "Unique method/field names: "
     << Percent(total_unique_mf_names_, num_field_ids_ + num_method_ids_) << "\n";
  os << "Same class invokes: " << PercentDivide(same_class_total, other_class_total) << "\n";
  os << "Invokes from code: " << (same_class_total + other_class_total) << "\n";
  os << "Type uses on top types: " << PercentDivide(uses_top_types_, uses_all_types_) << "\n";
  os << "Type uses 1b savings: " << PercentDivide(uses_top_types_, total_size) << "\n";
  os << "Total Dex code bytes: " << Percent(dex_code_bytes_, total_size) << "\n";
  os << "Total unique code items: " << total_unique_code_items_ << "\n";
  os << "Total Dex size: " << total_size << "\n";
}

void CodeMetrics::ProcessDexFile(const DexFile& dex_file) {
  for (ClassAccessor accessor : dex_file.GetClasses()) {
    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
      bool space_for_out_arg = false;
      for (const DexInstructionPcPair& inst : method.GetInstructions()) {
        switch (inst->Opcode()) {
          case Instruction::INVOKE_VIRTUAL:
          case Instruction::INVOKE_DIRECT:
          case Instruction::INVOKE_SUPER:
          case Instruction::INVOKE_INTERFACE:
          case Instruction::INVOKE_STATIC: {
            const uint32_t args = NumberOfArgs(inst.Inst());
            CHECK_LT(args, kMaxArgCount);
            ++arg_counts_[args];
            space_for_out_arg = args < kMaxArgCount - 1;
            break;
          }
          case Instruction::MOVE_RESULT:
          case Instruction::MOVE_RESULT_OBJECT: {
            if (space_for_out_arg && inst->VRegA_11x() < 16) {
              move_result_savings_ += inst->SizeInCodeUnits() * 2;
            }
            break;
          }
          default:
            space_for_out_arg = false;
            break;
        }
      }
    }
  }
}

void CodeMetrics::Dump(std::ostream& os, uint64_t total_size) const {
  const uint64_t total = std::accumulate(arg_counts_, arg_counts_ + kMaxArgCount, 0u);
  for (size_t i = 0; i < kMaxArgCount; ++i) {
    os << "args=" << i << ": " << Percent(arg_counts_[i], total) << "\n";
  }
  os << "Move result savings: " << Percent(move_result_savings_, total_size) << "\n";
  os << "One byte invoke savings: " << Percent(total, total_size) << "\n";
  const uint64_t low_arg_total = std::accumulate(arg_counts_, arg_counts_ + 2, 0u);
  os << "Low arg savings: " << Percent(low_arg_total * 2, total_size) << "\n";
}

}  // namespace dexanalyze
}  // namespace art