ART: Swap-space in the compiler

Introduce a swap-space and corresponding allocator to transparently
switch native allocations to memory backed by a file.

Bug: 18596910

(cherry picked from commit 62746d8d9c4400e4764f162b22bfb1a32be287a9)

Change-Id: I131448f3907115054a592af73db86d2b9257ea33
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 2cbfffa..63009bf 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -248,6 +248,12 @@
   UsageError("      Used to specify a pass specific option. The setting itself must be integer.");
   UsageError("      Separator used between options is a comma.");
   UsageError("");
+  UsageError("  --swap-file=<file-name>:  specifies a file to use for swap.");
+  UsageError("      Example: --swap-file=/data/tmp/swap.001");
+  UsageError("");
+  UsageError("  --swap-fd=<file-descriptor>:  specifies a file to use for swap (by descriptor).");
+  UsageError("      Example: --swap-fd=10");
+  UsageError("");
   std::cerr << "See log for usage error information\n";
   exit(EXIT_FAILURE);
 }
@@ -393,6 +399,25 @@
   *parsed_value = value;
 }
 
+static constexpr size_t kMinDexFilesForSwap = 2;
+static constexpr size_t kMinDexFileCumulativeSizeForSwap = 20 * MB;
+
+static bool UseSwap(bool is_image, std::vector<const DexFile*>& dex_files) {
+  if (is_image) {
+    // Don't use swap, we know generation should succeed, and we don't want to slow it down.
+    return false;
+  }
+  if (dex_files.size() < kMinDexFilesForSwap) {
+    // If there are less dex files than the threshold, assume it's gonna be fine.
+    return false;
+  }
+  size_t dex_files_size = 0;
+  for (const auto* dex_file : dex_files) {
+    dex_files_size += dex_file->GetHeader().file_size_;
+  }
+  return dex_files_size >= kMinDexFileCumulativeSizeForSwap;
+}
+
 class Dex2Oat FINAL {
  public:
   explicit Dex2Oat(TimingLogger* timings) :
@@ -416,6 +441,7 @@
       dump_passes_(false),
       dump_timing_(false),
       dump_slow_timing_(kIsDebugBuild),
+      swap_fd_(-1),
       timings_(timings) {}
 
   ~Dex2Oat() {
@@ -684,6 +710,16 @@
                      << "failures.";
           init_failure_output_.reset();
         }
+      } else if (option.starts_with("--swap-file=")) {
+        swap_file_name_ = option.substr(strlen("--swap-file=")).data();
+      } else if (option.starts_with("--swap-fd=")) {
+        const char* swap_fd_str = option.substr(strlen("--swap-fd=")).data();
+        if (!ParseInt(swap_fd_str, &swap_fd_)) {
+          Usage("Failed to parse --swap-fd argument '%s' as an integer", swap_fd_str);
+        }
+        if (swap_fd_ < 0) {
+          Usage("--swap-fd passed a negative value %d", swap_fd_);
+        }
       } else {
         Usage("Unknown argument %s", option.data());
       }
@@ -918,7 +954,8 @@
     }
   }
 
-  // Check whether the oat output file is writable, and open it for later.
+  // Check whether the oat output file is writable, and open it for later. Also open a swap file,
+  // if a name is given.
   bool OpenFile() {
     bool create_file = !oat_unstripped_.empty();  // as opposed to using open file descriptor
     if (create_file) {
@@ -942,6 +979,27 @@
       oat_file_->Erase();
       return false;
     }
+
+    // Swap file handling.
+    //
+    // If the swap fd is not -1, we assume this is the file descriptor of an open but unlinked file
+    // that we can use for swap.
+    //
+    // If the swap fd is -1 and we have a swap-file string, open the given file as a swap file. We
+    // will immediately unlink to satisfy the swap fd assumption.
+    if (swap_fd_ == -1 && !swap_file_name_.empty()) {
+      std::unique_ptr<File> swap_file(OS::CreateEmptyFile(swap_file_name_.c_str()));
+      if (swap_file.get() == nullptr) {
+        PLOG(ERROR) << "Failed to create swap file: " << swap_file_name_;
+        return false;
+      }
+      swap_fd_ = swap_file->Fd();
+      swap_file->MarkUnchecked();     // We don't we to track this, it will be unlinked immediately.
+      swap_file->DisableAutoClose();  // We'll handle it ourselves, the File object will be
+                                      // released immediately.
+      unlink(swap_file_name_.c_str());
+    }
+
     return true;
   }
 
@@ -1085,6 +1143,18 @@
       }
     }
 
+    // If we use a swap file, ensure we are above the threshold to make it necessary.
+    if (swap_fd_ != -1) {
+      if (!UseSwap(image_, dex_files_)) {
+        close(swap_fd_);
+        swap_fd_ = -1;
+        LOG(INFO) << "Decided to run without swap.";
+      } else {
+        LOG(INFO) << "Accepted running with swap.";
+      }
+    }
+    // Note that dex2oat won't close the swap_fd_. The compiler driver's swap space will do that.
+
     /*
      * If we're not in interpret-only or verify-none mode, go ahead and compile small applications.
      * Don't bother to check if we're doing the image.
@@ -1143,6 +1213,7 @@
                                      dump_stats_,
                                      dump_passes_,
                                      compiler_phases_timings_.get(),
+                                     swap_fd_,
                                      profile_file_));
 
     driver_->CompileAll(class_loader, dex_files_, timings_);
@@ -1591,6 +1662,8 @@
   bool dump_passes_;
   bool dump_timing_;
   bool dump_slow_timing_;
+  std::string swap_file_name_;
+  int swap_fd_;
   std::string profile_file_;  // Profile file to use
   TimingLogger* timings_;
   std::unique_ptr<CumulativeLogger> compiler_phases_timings_;