Use verify when speed-profile gets an empty profile

Change the compiler filter to verify if we need to compile
speed-profile but we don't get a profile, or the profile is empty.
This will improve the clarity and the precision of the telemetry
data which usually expects speed-profile to outperform verify.

Test: gtest
Bug: 188655918
Change-Id: I215552e0001d56df0e0d676721f0a741ef2573be
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 4cf42cc..b19e41c 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -543,7 +543,8 @@
       force_determinism_(false),
       check_linkage_conditions_(false),
       crash_on_linkage_violation_(false),
-      compile_individually_(false)
+      compile_individually_(false),
+      profile_load_attempted_(false)
       {}
 
   ~Dex2Oat() {
@@ -1164,7 +1165,7 @@
     // before reading compiler options.
     static_assert(CompilerFilter::kDefaultCompilerFilter == CompilerFilter::kSpeed);
     DCHECK_EQ(compiler_options_->GetCompilerFilter(), CompilerFilter::kSpeed);
-    if (UseProfile()) {
+    if (HasProfileInput()) {
       compiler_options_->SetCompilerFilter(CompilerFilter::kSpeedProfile);
     }
 
@@ -1173,9 +1174,6 @@
     }
 
     ProcessOptions(parser_options.get());
-
-    // Insert some compiler things.
-    InsertCompileOptions(argc, argv);
   }
 
   // Check whether the oat output files are writable, and open them for later. Also open a swap
@@ -1398,7 +1396,7 @@
     if (!IsImage()) {
       return;
     }
-    if (profile_compilation_info_ != nullptr) {
+    if (DoProfileGuidedOptimizations()) {
       // TODO: The following comment looks outdated or misplaced.
       // Filter out class path classes since we don't want to include these in the image.
       HashSet<std::string> image_classes = profile_compilation_info_->GetClassDescriptors(
@@ -2330,12 +2328,16 @@
     return is_host_;
   }
 
-  bool UseProfile() const {
+  bool HasProfileInput() const {
     return profile_file_fd_ != -1 || !profile_file_.empty();
   }
 
+  // Must be called after the profile is loaded.
   bool DoProfileGuidedOptimizations() const {
-    return UseProfile();
+    DCHECK(!HasProfileInput() || profile_load_attempted_)
+        << "The profile has to be loaded before we can decided "
+        << "if we do profile guided optimizations";
+    return profile_compilation_info_ != nullptr && !profile_compilation_info_->IsEmpty();
   }
 
   bool DoGenerateCompactDex() const {
@@ -2361,7 +2363,8 @@
   }
 
   bool LoadProfile() {
-    DCHECK(UseProfile());
+    DCHECK(HasProfileInput());
+    profile_load_attempted_ = true;
     // TODO(calin): We should be using the runtime arena pool (instead of the
     // default profile arena). However the setup logic is messy and needs
     // cleaning up before that (e.g. the oat writers are created before the
@@ -2394,6 +2397,29 @@
     return true;
   }
 
+  // If we're asked to speed-profile the app but we have no profile, or the profile
+  // is empty, change the filter to verify, and the image_type to none.
+  // A speed-profile compilation without profile data is equivalent to verify and
+  // this change will increase the precision of the telemetry data.
+  void UpdateCompilerOptionsBasedOnProfile() {
+    if (!DoProfileGuidedOptimizations() &&
+        compiler_options_->GetCompilerFilter() == CompilerFilter::kSpeedProfile) {
+      VLOG(compiler) << "Changing compiler filter to verify from speed-profile "
+          << "because of empty or non existing profile";
+
+      compiler_options_->SetCompilerFilter(CompilerFilter::kVerify);
+
+      // Note that we could reset the image_type to CompilerOptions::ImageType::kNone
+      // to prevent an app image generation.
+      // However, if we were pass an image file we would essentially leave the image
+      // file empty (possibly triggering some harmless errors when we try to load it).
+      //
+      // Letting the image_type_ be determined by whether or not we passed an image
+      // file will at least write the appropriate header making it an empty but valid
+      // image.
+    }
+  }
+
   class ScopedDex2oatReporting {
    public:
     explicit ScopedDex2oatReporting(const Dex2Oat& dex2oat) {
@@ -2615,9 +2641,6 @@
       elf_writers_.emplace_back(linker::CreateElfWriterQuick(*compiler_options_, oat_file.get()));
       elf_writers_.back()->Start();
       bool do_oat_writer_layout = DoDexLayoutOptimizations() || DoOatLayoutOptimizations();
-      if (profile_compilation_info_ != nullptr && profile_compilation_info_->IsEmpty()) {
-        do_oat_writer_layout = false;
-      }
       oat_writers_.emplace_back(new linker::OatWriter(
           *compiler_options_,
           timings_,
@@ -2986,6 +3009,9 @@
   // argument.
   std::string apex_versions_argument_;
 
+  // Whether or we attempted to load the profile (if given).
+  bool profile_load_attempted_;
+
   DISALLOW_IMPLICIT_CONSTRUCTORS(Dex2Oat);
 };
 
@@ -3088,13 +3114,22 @@
 
   // If needed, process profile information for profile guided compilation.
   // This operation involves I/O.
-  if (dex2oat->UseProfile()) {
+  if (dex2oat->HasProfileInput()) {
     if (!dex2oat->LoadProfile()) {
       LOG(ERROR) << "Failed to process profile file";
       return dex2oat::ReturnCode::kOther;
     }
   }
 
+  // Check if we need to update any of the compiler options (such as the filter)
+  // and do it before anything else (so that the other operations have a true
+  // view of the state).
+  dex2oat->UpdateCompilerOptionsBasedOnProfile();
+
+  // Insert the compiler options in the key value store.
+  // We have to do this after we altered any incoming arguments
+  // (such as the compiler filter).
+  dex2oat->InsertCompileOptions(argc, argv);
 
   // Check early that the result of compilation can be written
   if (!dex2oat->OpenFile()) {
@@ -3140,7 +3175,7 @@
   // Note: If dex2oat fails, installd will remove the oat files causing the app
   // to fallback to apk with possible in-memory extraction. We want to avoid
   // that, and thus we're lenient towards profile corruptions.
-  if (dex2oat->UseProfile()) {
+  if (dex2oat->DoProfileGuidedOptimizations()) {
     dex2oat->VerifyProfileData();
   }
 
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index c4c39c2..cc29b0f 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -208,6 +208,20 @@
   std::string error_msg_ = "";
 };
 
+// This test class provides an easy way to validate an expected filter which is different
+// then the one pass to generate the odex file (compared to adding yet another argument
+// to what's already huge test methods).
+class Dex2oatWithExpectedFilterTest : public Dex2oatTest {
+ protected:
+  void CheckFilter(
+        CompilerFilter::Filter expected ATTRIBUTE_UNUSED,
+        CompilerFilter::Filter actual) override {
+    EXPECT_EQ(expected_filter_, actual);
+  }
+
+  CompilerFilter::Filter expected_filter_;
+};
+
 class Dex2oatSwapTest : public Dex2oatTest {
  protected:
   void RunTest(bool use_fd, bool expect_use, const std::vector<std::string>& extra_args = {}) {
@@ -443,6 +457,14 @@
                bool expect_large,
                bool expect_downgrade,
                const std::vector<std::string>& extra_args = {}) {
+    RunTest(filter, filter, expect_large, expect_downgrade, extra_args);
+  }
+
+  void RunTest(CompilerFilter::Filter filter,
+               CompilerFilter::Filter expected_filter,
+               bool expect_large,
+               bool expect_downgrade,
+               const std::vector<std::string>& extra_args = {}) {
     std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
     std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex";
     std::string app_image_file = GetScratchDir() + "/Test.art";
@@ -458,6 +480,7 @@
                 odex_location,
                 app_image_file,
                 filter,
+                expected_filter,
                 expect_large,
                 expect_downgrade);
   }
@@ -466,6 +489,7 @@
                    const std::string& odex_location,
                    const std::string& app_image_file,
                    CompilerFilter::Filter filter,
+                   CompilerFilter::Filter expected_filter,
                    bool expect_large,
                    bool expect_downgrade) {
     if (expect_downgrade) {
@@ -503,7 +527,7 @@
 
       // If the input filter was "below," it should have been used.
       if (!CompilerFilter::IsAsGoodAs(CompilerFilter::kExtract, filter)) {
-        EXPECT_EQ(odex_file->GetCompilerFilter(), filter);
+        EXPECT_EQ(odex_file->GetCompilerFilter(), expected_filter);
       }
 
       // If expect large, make sure the app image isn't generated or is empty.
@@ -511,7 +535,7 @@
         EXPECT_EQ(file->GetLength(), 0u);
       }
     } else {
-      EXPECT_EQ(odex_file->GetCompilerFilter(), filter);
+      EXPECT_EQ(odex_file->GetCompilerFilter(), expected_filter);
       ASSERT_TRUE(file != nullptr) << app_image_file;
       EXPECT_GT(file->GetLength(), 0u);
     }
@@ -576,7 +600,7 @@
 // Regressin test for b/35665292.
 TEST_F(Dex2oatVeryLargeTest, SpeedProfileNoProfile) {
   // Test that dex2oat doesn't crash with speed-profile but no input profile.
-  RunTest(CompilerFilter::kSpeedProfile, false, false);
+  RunTest(CompilerFilter::kSpeedProfile, CompilerFilter::kVerify, false, false);
 }
 
 class Dex2oatLayoutTest : public Dex2oatTest {
@@ -675,6 +699,7 @@
       // Don't check the result since CheckResult relies on the class being in the profile.
       image_file_empty_profile = GetImageObjectSectionSize(app_image_file);
       EXPECT_GT(image_file_empty_profile, 0u);
+      CheckCompilerFilter(dex_location, odex_location, CompilerFilter::Filter::kVerify);
     }
 
     // Small profile.
@@ -685,6 +710,7 @@
                        /*num_profile_classes=*/ 1);
     CheckValidity();
     CheckResult(dex_location, odex_location, app_image_file);
+    CheckCompilerFilter(dex_location, odex_location, CompilerFilter::Filter::kSpeedProfile);
 
     if (app_image) {
       // Test that the profile made a difference by adding more classes.
@@ -693,6 +719,21 @@
     }
   }
 
+  void CheckCompilerFilter(
+      const std::string& dex_location,
+      const std::string& odex_location,
+      CompilerFilter::Filter expected_filter) {
+    std::string error_msg;
+    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
+                                                     odex_location.c_str(),
+                                                     odex_location.c_str(),
+                                                     /*executable=*/ false,
+                                                     /*low_4gb=*/ false,
+                                                     dex_location,
+                                                     &error_msg));
+    EXPECT_EQ(odex_file->GetCompilerFilter(), expected_filter);
+  }
+
   void RunTestVDex() {
     std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
     std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex";
@@ -1841,7 +1882,10 @@
   ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) != 0) << status << " " << output_;
 }
 
-TEST_F(Dex2oatTest, AppImageNoProfile) {
+TEST_F(Dex2oatWithExpectedFilterTest, AppImageNoProfile) {
+  // Set the expected filter.
+  expected_filter_ = CompilerFilter::Filter::kVerify;
+
   ScratchFile app_image_file;
   const std::string out_dir = GetScratchDir();
   const std::string odex_location = out_dir + "/base.odex";
@@ -1890,7 +1934,10 @@
                                   /*use_zip_fd=*/ true));
 }
 
-TEST_F(Dex2oatTest, AppImageEmptyDex) {
+TEST_F(Dex2oatWithExpectedFilterTest, AppImageEmptyDex) {
+  // Set the expected filter.
+  expected_filter_ = CompilerFilter::Filter::kVerify;
+
   // Create a profile with the startup method marked.
   ScratchFile profile_file;
   ScratchFile temp_dex;
diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc
index f32c122..5cbdced 100644
--- a/libprofile/profile/profile_compilation_info.cc
+++ b/libprofile/profile/profile_compilation_info.cc
@@ -2214,7 +2214,10 @@
 
 bool ProfileCompilationInfo::IsEmpty() const {
   DCHECK_EQ(info_.size(), profile_key_map_.size());
-  return info_.empty();
+  // Note that this doesn't look at the bitmap region, so we will return true
+  // when the profile contains only non-hot methods. This is generally ok
+  // as for speed-profile to be useful we do need hot methods and resolved classes.
+  return GetNumberOfMethods() == 0 && GetNumberOfResolvedClasses() == 0;
 }
 
 ProfileCompilationInfo::InlineCacheMap*
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
index 9b5b473..abde17c 100644
--- a/runtime/dexopt_test.cc
+++ b/runtime/dexopt_test.cc
@@ -26,11 +26,14 @@
 #include "base/mem_map.h"
 #include "common_runtime_test.h"
 #include "compiler_callbacks.h"
+#include "dex/art_dex_file_loader.h"
+#include "dex/dex_file_loader.h"
 #include "dex2oat_environment_test.h"
 #include "dexopt_test.h"
 #include "gc/space/image_space.h"
 #include "hidden_api.h"
 #include "oat.h"
+#include "profile/profile_compilation_info.h"
 
 namespace art {
 void DexoptTest::SetUp() {
@@ -110,6 +113,24 @@
 
   ScratchFile profile_file;
   if (CompilerFilter::DependsOnProfile(filter)) {
+    // Create a profile with some basic content so that dex2oat
+    // doesn't get an empty profile and changes the filter to verify.
+    std::string error_msg;
+    std::vector<std::unique_ptr<const DexFile>> dex_files;
+    const ArtDexFileLoader dex_file_loader;
+    ASSERT_TRUE(dex_file_loader.Open(
+        dex_location.c_str(), dex_location.c_str(), /*verify=*/ false, /*verify_checksum=*/ false,
+        &error_msg, &dex_files));
+    EXPECT_GE(dex_files.size(), 1U);
+    std::unique_ptr<const DexFile>& dex_file = dex_files[0];
+    ProfileCompilationInfo info;
+
+    info.AddClass(*dex_file, dex::TypeIndex(0));
+
+    ASSERT_TRUE(info.Save(profile_file.GetFd()));
+    ASSERT_EQ(0, profile_file.GetFile()->Flush());
+
+    // Set the argument
     args.push_back("--profile-file=" + profile_file.GetFilename());
   }