Add experiments for Dex bytecode analysis

Count how many move-result instructions are directly following
invokes with less than 5 args.

Bug: 77721545
Test: test-art-host

Change-Id: Icd3be78260b9117660d734f50303a8e3bc030325
diff --git a/tools/dexanalyze/dexanalyze.cc b/tools/dexanalyze/dexanalyze.cc
index 083de70..38725d4 100644
--- a/tools/dexanalyze/dexanalyze.cc
+++ b/tools/dexanalyze/dexanalyze.cc
@@ -88,6 +88,7 @@
     bool run_dex_file_verifier_ = true;
     bool dump_per_input_dex_ = false;
     bool exp_count_indices_ = false;
+    bool exp_code_metrics_ = false;
     bool exp_analyze_strings_ = false;
     bool run_all_experiments_ = false;
     std::vector<std::string> filenames_;
@@ -102,6 +103,9 @@
       if (options->run_all_experiments_ || options->exp_analyze_strings_) {
         experiments_.emplace_back(new AnalyzeStrings);
       }
+      if (options->run_all_experiments_ || options->exp_code_metrics_) {
+        experiments_.emplace_back(new CodeMetrics);
+      }
     }
 
     bool ProcessDexFile(const DexFile& dex_file) {
diff --git a/tools/dexanalyze/dexanalyze_experiments.cc b/tools/dexanalyze/dexanalyze_experiments.cc
index 0f20a99..7006370 100644
--- a/tools/dexanalyze/dexanalyze_experiments.cc
+++ b/tools/dexanalyze/dexanalyze_experiments.cc
@@ -32,13 +32,41 @@
 
 namespace art {
 
+static inline 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;
+}
+
+static inline uint16_t NumberOfArgs(const Instruction& inst) {
+  return IsRange(inst.Opcode()) ? inst.VRegA_3rc() : inst.VRegA_35c();
+}
+
+static inline 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) {
-    ++max;
+    return "0";
   }
-  return android::base::StringPrintf("%" PRId64 "(%.2f%%)",
-                                     value,
-                                     static_cast<double>(value * 100) / static_cast<double>(max));
+  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));
 }
 
 static size_t PrefixLen(const std::string& a, const std::string& b) {
@@ -150,38 +178,52 @@
           // Invoke cases.
           case Instruction::INVOKE_VIRTUAL:
           case Instruction::INVOKE_VIRTUAL_RANGE: {
-            bool is_range = (inst->Opcode() == Instruction::INVOKE_VIRTUAL_RANGE);
-            uint32_t method_idx = is_range ? inst->VRegB_3rc() : inst->VRegB_35c();
+            uint32_t method_idx = DexMethodIndex(inst.Inst());
             if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) {
               ++same_class_virtual_;
-            } else {
-              ++other_class_virtual_;
-              unique_method_ids.insert(method_idx);
             }
+            ++total_virtual_;
+            unique_method_ids.insert(method_idx);
             break;
           }
           case Instruction::INVOKE_DIRECT:
           case Instruction::INVOKE_DIRECT_RANGE: {
-            bool is_range = (inst->Opcode() == Instruction::INVOKE_DIRECT_RANGE);
-            uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
+            uint32_t method_idx = DexMethodIndex(inst.Inst());
             if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) {
               ++same_class_direct_;
-            } else {
-              ++other_class_direct_;
-              unique_method_ids.insert(method_idx);
             }
+            ++total_direct_;
+            unique_method_ids.insert(method_idx);
             break;
           }
           case Instruction::INVOKE_STATIC:
           case Instruction::INVOKE_STATIC_RANGE: {
-            bool is_range = (inst->Opcode() == Instruction::INVOKE_STATIC_RANGE);
-            uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
+            uint32_t method_idx = DexMethodIndex(inst.Inst());
             if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) {
               ++same_class_static_;
-            } else {
-              ++other_class_static_;
-              unique_method_ids.insert(method_idx);
             }
+            ++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());
+            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());
+            if (dex_file.GetMethodId(method_idx).class_idx_ == accessor.GetClassIdx()) {
+              ++same_class_super_;
+            }
+            ++total_super_;
+            unique_method_ids.insert(method_idx);
             break;
           }
           default:
@@ -201,24 +243,75 @@
   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 << "Same class direct: " << same_class_direct_ << "\n";
-  os << "Other class direct: " << other_class_direct_ << "\n";
-  os << "Same class virtual: " << same_class_virtual_ << "\n";
-  os << "Other class virtual: " << other_class_virtual_ << "\n";
-  os << "Same class static: " << same_class_static_ << "\n";
-  os << "Other class static: " << other_class_static_ << "\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 << "Unique(per class) method ids accessed from code: " << total_unique_method_idx_ << "\n";
   os << "Unique(per class) string ids accessed from code: " << total_unique_string_ids_ << "\n";
-  size_t same_class_total = same_class_direct_ + same_class_virtual_ + same_class_static_;
-  size_t other_class_total = other_class_direct_ + other_class_virtual_ + other_class_static_;
-  os << "Same class invoke: " << same_class_total << "\n";
-  os << "Other class invoke: " << other_class_total << "\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 << "Same class invokes: " << PercentDivide(same_class_total, other_class_total) << "\n";
   os << "Invokes from code: " << (same_class_total + other_class_total) << "\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";
 }
 
-}  // namespace art
+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) {
+              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_ + 3, 0u);
+  os << "Low arg savings: " << Percent(low_arg_total * 2, total_size) << "\n";
+}
+
+}  // namespace art
diff --git a/tools/dexanalyze/dexanalyze_experiments.h b/tools/dexanalyze/dexanalyze_experiments.h
index c84b082..7ba2a49 100644
--- a/tools/dexanalyze/dexanalyze_experiments.h
+++ b/tools/dexanalyze/dexanalyze_experiments.h
@@ -75,11 +75,28 @@
 
   // Invokes
   size_t same_class_direct_ = 0;
-  size_t other_class_direct_ = 0;
+  size_t total_direct_ = 0;
   size_t same_class_virtual_ = 0;
-  size_t other_class_virtual_ = 0;
+  size_t total_virtual_ = 0;
   size_t same_class_static_ = 0;
-  size_t other_class_static_ = 0;
+  size_t total_static_ = 0;
+  size_t same_class_interface_ = 0;
+  size_t total_interface_ = 0;
+  size_t same_class_super_ = 0;
+  size_t total_super_ = 0;
+};
+
+// Measure various code metrics including args per invoke-virtual, fill/spill move paterns.
+class CodeMetrics : public Experiment {
+ public:
+  void ProcessDexFile(const DexFile& dex_file);
+
+  void Dump(std::ostream& os, uint64_t total_size) const;
+
+ private:
+  static constexpr size_t kMaxArgCount = 6;
+  uint64_t arg_counts_[kMaxArgCount] = {};
+  uint64_t move_result_savings_ = 0u;
 };
 
 }  // namespace art