Extend profman to generate profiles with inline caches

Extend profman logic to generate profiles based on a simple textual
respresentation. This will help writing tests for profile guided
compilation.

Before this CL, profman was able to generate profiles based on a list of
classes like:
java.lang.Comparable
java.lang.Math
java.lang.Object

This CL, enables profman to understand methods and classes alike. The
new format is:

# Classes
Ljava/lang/Comparable;
Ljava/lang/Math;
# Methods with inline caches
LTestInline;->inlinePolymorhic(LSuper;)I+LSubA;,LSubB;,LSubC;
LTestInline;->noInlineCache(LSuper;)I

"LTestInline;->inlinePolymorhic(LSuper;)I+LSubA;,LSubB;,LSubC;"
means that method `int inlineMonomorphicSubA(Super)` from class Main
will be added to the profile with the inline cache (SubA,SubB) for its
one and only invoke virtual.

@Main#noInlineCache:(LSuper;)I+;
meaning that method `int noInlineCache' from class Main will be added
to the profile with no inline cache.

Note that the methods are allowed to have a single invoke virtual in
their dex bytecode. That is to keep the parsing the file format
simple and easy to use.

Also, add a few more tests for profiles and fix an issue caused by
writing the dex files in a possibly wrong order.

Test: m run-test-host-gtest-profile_assistant_test
Bug: 32434870
Change-Id: I6b7340cf613007117d9818be206ccb3a27b815bf
diff --git a/profman/profman.cc b/profman/profman.cc
index a42e4f1..a99a0ea 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -36,6 +36,7 @@
 #include "base/stringpiece.h"
 #include "base/time_utils.h"
 #include "base/unix_file/fd_file.h"
+#include "bytecode_utils.h"
 #include "dex_file.h"
 #include "jit/profile_compilation_info.h"
 #include "runtime.h"
@@ -136,6 +137,14 @@
 static constexpr uint16_t kDefaultTestProfileMethodRatio = 5;
 static constexpr uint16_t kDefaultTestProfileClassRatio = 5;
 
+// Separators used when parsing human friendly representation of profiles.
+static const std::string kMethodSep = "->";
+static constexpr char kProfileParsingInlineChacheSep = '+';
+static constexpr char kProfileParsingTypeSep = ',';
+static constexpr char kProfileParsingFirstCharInSignature = '(';
+
+// TODO(calin): This class has grown too much from its initial design. Split the functionality
+// into smaller, more contained pieces.
 class ProfMan FINAL {
  public:
   ProfMan() :
@@ -522,6 +531,180 @@
     return output.release();
   }
 
+  // Find class klass_descriptor in the given dex_files and store its reference
+  // in the out parameter class_ref.
+  // Return true if the definition of the class was found in any of the dex_files.
+  bool FindClass(const std::vector<std::unique_ptr<const DexFile>>& dex_files,
+                 const std::string& klass_descriptor,
+                 /*out*/ProfileMethodInfo::ProfileClassReference* class_ref) {
+    for (const std::unique_ptr<const DexFile>& dex_file_ptr : dex_files) {
+      const DexFile* dex_file = dex_file_ptr.get();
+      const DexFile::TypeId* type_id = dex_file->FindTypeId(klass_descriptor.c_str());
+      if (type_id == nullptr) {
+        continue;
+      }
+      dex::TypeIndex type_index = dex_file->GetIndexForTypeId(*type_id);
+      if (dex_file->FindClassDef(type_index) == nullptr) {
+        // Class is only referenced in the current dex file but not defined in it.
+        continue;
+      }
+      class_ref->dex_file = dex_file;
+      class_ref->type_index = type_index;
+      return true;
+    }
+    return false;
+  }
+
+  // Find the method specified by method_spec in the class class_ref. The method
+  // must have a single INVOKE_VIRTUAL in its byte code.
+  // Upon success it returns true and stores the method index and the invoke dex pc
+  // in the output parameters.
+  // The format of the method spec is "inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;".
+  //
+  // TODO(calin): support INVOKE_INTERFACE and the range variants.
+  bool FindMethodWithSingleInvoke(const ProfileMethodInfo::ProfileClassReference& class_ref,
+                                  const std::string& method_spec,
+                                  /*out*/uint16_t* method_index,
+                                  /*out*/uint32_t* dex_pc) {
+    std::vector<std::string> name_and_signature;
+    Split(method_spec, kProfileParsingFirstCharInSignature, &name_and_signature);
+    if (name_and_signature.size() != 2) {
+      LOG(ERROR) << "Invalid method name and signature " << method_spec;
+    }
+    const std::string& name = name_and_signature[0];
+    const std::string& signature = kProfileParsingFirstCharInSignature + name_and_signature[1];
+    const DexFile* dex_file = class_ref.dex_file;
+
+    const DexFile::StringId* name_id = dex_file->FindStringId(name.c_str());
+    if (name_id == nullptr) {
+      LOG(ERROR) << "Could not find name: "  << name;
+      return false;
+    }
+    dex::TypeIndex return_type_idx;
+    std::vector<dex::TypeIndex> param_type_idxs;
+    if (!dex_file->CreateTypeList(signature, &return_type_idx, &param_type_idxs)) {
+      LOG(ERROR) << "Could not create type list" << signature;
+      return false;
+    }
+    const DexFile::ProtoId* proto_id = dex_file->FindProtoId(return_type_idx, param_type_idxs);
+    if (proto_id == nullptr) {
+      LOG(ERROR) << "Could not find proto_id: " << name;
+      return false;
+    }
+    const DexFile::MethodId* method_id = dex_file->FindMethodId(
+        dex_file->GetTypeId(class_ref.type_index), *name_id, *proto_id);
+    if (method_id == nullptr) {
+      LOG(ERROR) << "Could not find method_id: " << name;
+      return false;
+    }
+
+    *method_index = dex_file->GetIndexForMethodId(*method_id);
+
+    uint32_t offset = dex_file->FindCodeItemOffset(
+        *dex_file->FindClassDef(class_ref.type_index),
+        *method_index);
+    const DexFile::CodeItem* code_item = dex_file->GetCodeItem(offset);
+
+    bool found_invoke = false;
+    for (CodeItemIterator it(*code_item); !it.Done(); it.Advance()) {
+      if (it.CurrentInstruction().Opcode() == Instruction::INVOKE_VIRTUAL) {
+        if (found_invoke) {
+          LOG(ERROR) << "Multiple invoke INVOKE_VIRTUAL found: " << name;
+          return false;
+        }
+        found_invoke = true;
+        *dex_pc = it.CurrentDexPc();
+      }
+    }
+    if (!found_invoke) {
+      LOG(ERROR) << "Could not find any INVOKE_VIRTUAL: " << name;
+    }
+    return found_invoke;
+  }
+
+  // Process a line defining a class or a method and its inline caches.
+  // Upon success return true and add the class or the method info to profile.
+  // The format of the method line is:
+  // "LTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;".
+  // The method and classes are searched only in the given dex files.
+  bool ProcessLine(const std::vector<std::unique_ptr<const DexFile>>& dex_files,
+                   const std::string& line,
+                   /*out*/ProfileCompilationInfo* profile) {
+    std::string klass;
+    std::string method_str;
+    size_t method_sep_index = line.find(kMethodSep);
+    if (method_sep_index == std::string::npos) {
+      klass = line;
+    } else {
+      klass = line.substr(0, method_sep_index);
+      method_str = line.substr(method_sep_index + kMethodSep.size());
+    }
+
+    ProfileMethodInfo::ProfileClassReference class_ref;
+    if (!FindClass(dex_files, klass, &class_ref)) {
+      LOG(ERROR) << "Could not find class: " << klass;
+      return false;
+    }
+
+    if (method_str.empty()) {
+      // No method to add. Just add the class.
+      std::set<DexCacheResolvedClasses> resolved_class_set;
+      const DexFile* dex_file = class_ref.dex_file;
+      const auto& dex_resolved_classes = resolved_class_set.emplace(
+            dex_file->GetLocation(),
+            dex_file->GetBaseLocation(),
+            dex_file->GetLocationChecksum());
+      dex_resolved_classes.first->AddClass(class_ref.type_index);
+      profile->AddMethodsAndClasses(std::vector<ProfileMethodInfo>(), resolved_class_set);
+      return true;
+    }
+
+    // Process the method.
+    std::string method_spec;
+    std::vector<std::string> inline_cache_elems;
+
+    std::vector<std::string> method_elems;
+    Split(method_str, kProfileParsingInlineChacheSep, &method_elems);
+    if (method_elems.size() == 2) {
+      method_spec = method_elems[0];
+      Split(method_elems[1], kProfileParsingTypeSep, &inline_cache_elems);
+    } else if (method_elems.size() == 1) {
+      method_spec = method_elems[0];
+    } else {
+      LOG(ERROR) << "Invalid method line: " << line;
+      return false;
+    }
+
+    uint16_t method_index;
+    uint32_t dex_pc;
+    if (!FindMethodWithSingleInvoke(class_ref, method_spec, &method_index, &dex_pc)) {
+      return false;
+    }
+    std::vector<ProfileMethodInfo::ProfileClassReference> classes(inline_cache_elems.size());
+    size_t class_it = 0;
+    for (const std::string ic_class : inline_cache_elems) {
+      if (!FindClass(dex_files, ic_class, &(classes[class_it++]))) {
+        LOG(ERROR) << "Could not find class: " << ic_class;
+        return false;
+      }
+    }
+    std::vector<ProfileMethodInfo::ProfileInlineCache> inline_caches;
+    inline_caches.emplace_back(dex_pc, classes);
+    std::vector<ProfileMethodInfo> pmi;
+    pmi.emplace_back(class_ref.dex_file, method_index, inline_caches);
+
+    profile->AddMethodsAndClasses(pmi, std::set<DexCacheResolvedClasses>());
+    return true;
+  }
+
+  // Creates a profile from a human friendly textual representation.
+  // The expected input format is:
+  //   # Classes
+  //   Ljava/lang/Comparable;
+  //   Ljava/lang/Math;
+  //   # Methods with inline caches
+  //   LTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;
+  //   LTestInline;->noInlineCache(LSuper;)I
   int CreateProfile() {
     // Validate parameters for this command.
     if (apk_files_.empty() && apks_fd_.empty()) {
@@ -550,51 +733,22 @@
         return -1;
       }
     }
-    // Read the user-specified list of classes (dot notation rather than descriptors).
+    // Read the user-specified list of classes and methods.
     std::unique_ptr<std::unordered_set<std::string>>
-        user_class_list(ReadCommentedInputFromFile<std::unordered_set<std::string>>(
+        user_lines(ReadCommentedInputFromFile<std::unordered_set<std::string>>(
             create_profile_from_file_.c_str(), nullptr));  // No post-processing.
-    std::unordered_set<std::string> matched_user_classes;
-    // Open the dex files to look up class names.
+
+    // Open the dex files to look up classes and methods.
     std::vector<std::unique_ptr<const DexFile>> dex_files;
     OpenApkFilesFromLocations(&dex_files);
-    // Iterate over the dex files looking for class names in the input stream.
-    std::set<DexCacheResolvedClasses> resolved_class_set;
-    for (auto& dex_file : dex_files) {
-      // Compute the set of classes to be added for this dex file first.  This
-      // avoids creating an entry in the profile information for dex files that
-      // contribute no classes.
-      std::unordered_set<dex::TypeIndex> classes_to_be_added;
-      for (const auto& klass : *user_class_list) {
-        std::string descriptor = DotToDescriptor(klass.c_str());
-        const DexFile::TypeId* type_id = dex_file->FindTypeId(descriptor.c_str());
-        if (type_id == nullptr) {
-          continue;
-        }
-        classes_to_be_added.insert(dex_file->GetIndexForTypeId(*type_id));
-        matched_user_classes.insert(klass);
-      }
-      if (classes_to_be_added.empty()) {
-        continue;
-      }
-      // Insert the DexCacheResolved Classes into the set expected for
-      // AddMethodsAndClasses.
-      std::set<DexCacheResolvedClasses>::iterator dex_resolved_classes =
-          resolved_class_set.emplace(dex_file->GetLocation(),
-                                     dex_file->GetBaseLocation(),
-                                     dex_file->GetLocationChecksum()).first;
-      dex_resolved_classes->AddClasses(classes_to_be_added.begin(), classes_to_be_added.end());
-    }
-    // Warn the user if we didn't find matches for every class.
-    for (const auto& klass : *user_class_list) {
-      if (matched_user_classes.find(klass) == matched_user_classes.end()) {
-        LOG(WARNING) << "requested class '" << klass << "' was not matched in any dex file";
-      }
-    }
-    // Generate the profile data structure.
+
+    // Process the lines one by one and add the successful ones to the profile.
     ProfileCompilationInfo info;
-    std::vector<ProfileMethodInfo> methods;  // No methods for now.
-    info.AddMethodsAndClasses(methods, resolved_class_set);
+
+    for (const auto& line : *user_lines) {
+      ProcessLine(dex_files, line, &info);
+    }
+
     // Write the profile file.
     CHECK(info.Save(fd));
     if (close(fd) < 0) {