Add madvising of .odex, .vdex and .art files

1. When mmaping {.art, .odex, .vdex} files madvise them to MADV_WILLNEED
2. Add system properties to limit the madvise size/range

Test: presubmit
Bug: 178853586
Change-Id: I14afc7cc038ebbf6bba5a393ef222050284dd86d
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 838960c..7191525 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -111,6 +111,9 @@
     },
     {
       "name": "art-run-test-047-returns[com.google.android.art.apex]"
+    },
+    {
+      "name": "art-run-test-048-reflect-v8[com.google.android.art.apex]"
     }
   ],
   "presubmit": [
@@ -1261,6 +1264,9 @@
       "name": "art-run-test-818-clinit-nterp"
     },
     {
+      "name": "art-run-test-821-madvise-willneed"
+    },
+    {
       "name": "art-run-test-963-default-range-smali"
     }
   ]
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 1dee6a6..1627d78 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -964,6 +964,18 @@
         return MemMap::Invalid();
       }
 
+      Runtime* runtime = Runtime::Current();
+      // The runtime might not be available at this point if we're running
+      // dex2oat or oatdump.
+      if (runtime != nullptr) {
+        size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeArt();
+        Runtime::MadviseFileForRange(madvise_size_limit,
+                                     temp_map.Size(),
+                                     temp_map.Begin(),
+                                     temp_map.End(),
+                                     image_filename);
+      }
+
       if (is_compressed) {
         memcpy(map.Begin(), &image_header, sizeof(ImageHeader));
 
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 887c992..e33053f 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -1693,6 +1693,17 @@
                                                                  reservation,
                                                                  error_msg);
   if (with_dlopen != nullptr) {
+    Runtime* runtime = Runtime::Current();
+    // The runtime might not be available at this point if we're running
+    // dex2oat or oatdump.
+    if (runtime != nullptr) {
+      size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeOdex();
+      Runtime::MadviseFileForRange(madvise_size_limit,
+                                   with_dlopen->Size(),
+                                   with_dlopen->Begin(),
+                                   with_dlopen->End(),
+                                   oat_location);
+    }
     return with_dlopen;
   }
   if (kPrintDlOpenErrorMessage) {
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index 2a5bc9f..1cb8e9f 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -233,6 +233,15 @@
           .WithType<bool>()
           .WithValueMap({{"false", false}, {"true", true}})
           .IntoKey(M::MadviseRandomAccess)
+      .Define("-XMadviseWillNeedVdexFileSize:_")
+          .WithType<unsigned int>()
+          .IntoKey(M::MadviseWillNeedVdexFileSize)
+      .Define("-XMadviseWillNeedOdexFileSize:_")
+          .WithType<unsigned int>()
+          .IntoKey(M::MadviseWillNeedOdexFileSize)
+      .Define("-XMadviseWillNeedArtFileSize:_")
+          .WithType<unsigned int>()
+          .IntoKey(M::MadviseWillNeedArtFileSize)
       .Define("-Xusejit:_")
           .WithType<bool>()
           .WithValueMap({{"false", false}, {"true", true}})
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index fea03c8..8877c54 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -284,6 +284,9 @@
       experimental_flags_(ExperimentalFlags::kNone),
       oat_file_manager_(nullptr),
       is_low_memory_mode_(false),
+      madvise_willneed_vdex_filesize_(0),
+      madvise_willneed_odex_filesize_(0),
+      madvise_willneed_art_filesize_(0),
       safe_mode_(false),
       hidden_api_policy_(hiddenapi::EnforcementPolicy::kDisabled),
       core_platform_api_policy_(hiddenapi::EnforcementPolicy::kDisabled),
@@ -1387,6 +1390,9 @@
   experimental_flags_ = runtime_options.GetOrDefault(Opt::Experimental);
   is_low_memory_mode_ = runtime_options.Exists(Opt::LowMemoryMode);
   madvise_random_access_ = runtime_options.GetOrDefault(Opt::MadviseRandomAccess);
+  madvise_willneed_vdex_filesize_ = runtime_options.GetOrDefault(Opt::MadviseWillNeedVdexFileSize);
+  madvise_willneed_odex_filesize_ = runtime_options.GetOrDefault(Opt::MadviseWillNeedOdexFileSize);
+  madvise_willneed_art_filesize_ = runtime_options.GetOrDefault(Opt::MadviseWillNeedArtFileSize);
 
   jni_ids_indirection_ = runtime_options.GetOrDefault(Opt::OpaqueJniIds);
   automatically_set_jni_ids_indirection_ =
@@ -3128,4 +3134,50 @@
   }
 }
 
+void Runtime::MadviseFileForRange(size_t madvise_size_limit_bytes,
+                                  size_t map_size_bytes,
+                                  const uint8_t* map_begin,
+                                  const uint8_t* map_end,
+                                  const std::string& file_name) {
+  // Ideal blockTransferSize for madvising files (128KiB)
+  static constexpr size_t kIdealIoTransferSizeBytes = 128*1024;
+
+  size_t target_size_bytes = std::min<size_t>(map_size_bytes, madvise_size_limit_bytes);
+
+  if (target_size_bytes > 0) {
+    ScopedTrace madvising_trace("madvising "
+                                + file_name
+                                + " size="
+                                + std::to_string(target_size_bytes));
+
+    // Based on requested size (target_size_bytes)
+    const uint8_t* target_pos = map_begin + target_size_bytes;
+
+    // Clamp endOfFile if its past map_end
+    if (target_pos < map_end) {
+        target_pos = map_end;
+    }
+
+    // Madvise the whole file up to target_pos in chunks of
+    // kIdealIoTransferSizeBytes (to MADV_WILLNEED)
+    // Note:
+    // madvise(MADV_WILLNEED) will prefetch max(fd readahead size, optimal
+    // block size for device) per call, hence the need for chunks. (128KB is a
+    // good default.)
+    for (const uint8_t* madvise_start = map_begin;
+         madvise_start < target_pos;
+         madvise_start += kIdealIoTransferSizeBytes) {
+      void* madvise_addr = const_cast<void*>(reinterpret_cast<const void*>(madvise_start));
+      size_t madvise_length = std::min(kIdealIoTransferSizeBytes,
+                                       static_cast<size_t>(target_pos - madvise_start));
+      int status = madvise(madvise_addr, madvise_length, MADV_WILLNEED);
+      // In case of error we stop madvising rest of the file
+      if (status < 0) {
+        LOG(ERROR) << "Failed to madvise file:" << file_name << " for size:" << map_size_bytes;
+        break;
+      }
+    }
+  }
+}
+
 }  // namespace art
diff --git a/runtime/runtime.h b/runtime/runtime.h
index c9142f6..43a8454 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -885,6 +885,18 @@
     return madvise_random_access_;
   }
 
+  size_t GetMadviseWillNeedSizeVdex() const {
+    return madvise_willneed_vdex_filesize_;
+  }
+
+  size_t GetMadviseWillNeedSizeOdex() const {
+    return madvise_willneed_odex_filesize_;
+  }
+
+  size_t GetMadviseWillNeedSizeArt() const {
+    return madvise_willneed_art_filesize_;
+  }
+
   const std::string& GetJdwpOptions() {
     return jdwp_options_;
   }
@@ -969,6 +981,12 @@
 
   void RequestMetricsReport(bool synchronous = true);
 
+  static void MadviseFileForRange(size_t madvise_size_limit_bytes,
+                                  size_t map_size_bytes,
+                                  const uint8_t* map_begin,
+                                  const uint8_t* map_end,
+                                  const std::string& file_name);
+
  private:
   static void InitPlatformSignalHandlers();
 
@@ -1232,6 +1250,18 @@
   // This is beneficial for low RAM devices since it reduces page cache thrashing.
   bool madvise_random_access_;
 
+  // Limiting size (in bytes) for applying MADV_WILLNEED on vdex files
+  // A 0 for this will turn off madvising to MADV_WILLNEED
+  size_t madvise_willneed_vdex_filesize_;
+
+  // Limiting size (in bytes) for applying MADV_WILLNEED on odex files
+  // A 0 for this will turn off madvising to MADV_WILLNEED
+  size_t madvise_willneed_odex_filesize_;
+
+  // Limiting size (in bytes) for applying MADV_WILLNEED on art files
+  // A 0 for this will turn off madvising to MADV_WILLNEED
+  size_t madvise_willneed_art_filesize_;
+
   // Whether the application should run in safe mode, that is, interpreter only.
   bool safe_mode_;
 
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index b58d924..ea6b6e1 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -79,6 +79,9 @@
 RUNTIME_OPTIONS_KEY (bool,                UseProfiledJitCompilation,      false)
 RUNTIME_OPTIONS_KEY (bool,                DumpNativeStackOnSigQuit,       true)
 RUNTIME_OPTIONS_KEY (bool,                MadviseRandomAccess,            false)
+RUNTIME_OPTIONS_KEY (unsigned int,        MadviseWillNeedVdexFileSize,    0)
+RUNTIME_OPTIONS_KEY (unsigned int,        MadviseWillNeedOdexFileSize,    0)
+RUNTIME_OPTIONS_KEY (unsigned int,        MadviseWillNeedArtFileSize,     0)
 RUNTIME_OPTIONS_KEY (JniIdType,           OpaqueJniIds,                   JniIdType::kDefault)  // -Xopaque-jni-ids:{true, false, swapable}
 RUNTIME_OPTIONS_KEY (bool,                AutoPromoteOpaqueJniIds,        true)  // testing use only. -Xauto-promote-opaque-jni-ids:{true, false}
 RUNTIME_OPTIONS_KEY (unsigned int,        JITCompileThreshold)
diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc
index 70fde27..1f2d4e5 100644
--- a/runtime/vdex_file.cc
+++ b/runtime/vdex_file.cc
@@ -198,8 +198,20 @@
 
   if (!writable) {
     vdex->AllowWriting(false);
+    Runtime* runtime = Runtime::Current();
+    // The runtime might not be available at this point if we're running
+    // dex2oat or oatdump.
+    if (runtime != nullptr) {
+      size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeVdex();
+      Runtime::MadviseFileForRange(madvise_size_limit,
+                                   vdex->Size(),
+                                   vdex->Begin(),
+                                   vdex->End(),
+                                   vdex_filename);
+    }
   }
 
+
   return vdex;
 }
 
diff --git a/test/821-madvise-willneed/Android.bp b/test/821-madvise-willneed/Android.bp
new file mode 100644
index 0000000..86e883c
--- /dev/null
+++ b/test/821-madvise-willneed/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `821-madvise-willneed`.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+    name: "art-run-test-821-madvise-willneed",
+    defaults: ["art-run-test-defaults"],
+    test_config_template: ":art-run-test-target-template",
+    srcs: ["src/**/*.java"],
+    data: [
+        ":art-run-test-821-madvise-willneed-expected-stdout",
+        ":art-run-test-821-madvise-willneed-expected-stderr",
+    ],
+}
+
+// Test's expected standard output.
+genrule {
+    name: "art-run-test-821-madvise-willneed-expected-stdout",
+    out: ["art-run-test-821-madvise-willneed-expected-stdout.txt"],
+    srcs: ["expected-stdout.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+    name: "art-run-test-821-madvise-willneed-expected-stderr",
+    out: ["art-run-test-821-madvise-willneed-expected-stderr.txt"],
+    srcs: ["expected-stderr.txt"],
+    cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/821-madvise-willneed/expected-stderr.txt b/test/821-madvise-willneed/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/821-madvise-willneed/expected-stderr.txt
diff --git a/test/821-madvise-willneed/expected-stdout.txt b/test/821-madvise-willneed/expected-stdout.txt
new file mode 100644
index 0000000..af5626b
--- /dev/null
+++ b/test/821-madvise-willneed/expected-stdout.txt
@@ -0,0 +1 @@
+Hello, world!
diff --git a/test/821-madvise-willneed/info.txt b/test/821-madvise-willneed/info.txt
new file mode 100644
index 0000000..cea36a0
--- /dev/null
+++ b/test/821-madvise-willneed/info.txt
@@ -0,0 +1 @@
+Verify that passing madvise size limits argument to ART does not cause a crash
diff --git a/test/821-madvise-willneed/run b/test/821-madvise-willneed/run
new file mode 100644
index 0000000..2c3917f
--- /dev/null
+++ b/test/821-madvise-willneed/run
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 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.i
+
+# Load and run HelloWorld after madvising odex, vdex, art files to 100MB size
+# limit
+exec ${RUN} "${@}" --runtime-option -XMadviseWillNeedVdexFileSize:104857600 \
+  --runtime-option -XMadviseWillNeedOdexFileSize:104857600 \
+  --runtime-option -XMadviseWillNeedArtFileSize:104857600
diff --git a/test/821-madvise-willneed/src/Main.java b/test/821-madvise-willneed/src/Main.java
new file mode 100644
index 0000000..cc8c521
--- /dev/null
+++ b/test/821-madvise-willneed/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+public class Main {
+  public static void main(String[] args) {
+    System.out.println("Hello, world!");
+  }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 9564041..ce959ce 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1380,5 +1380,10 @@
         "bug": "b/175435088",
         "description": ["Test is time-sensitive and fails on gcstress and",
                         "interpreter-access-checks configurations."]
+    },
+    {
+        "tests": ["821-madvise-willneed"],
+        "variant": "jvm",
+        "description": ["Adding custom madvise flags. Not to be tested on default jvm"]
     }
 ]