Add android_mallopt M_GET_DECAY_TIME_ENABLED.

The bionic benchmarks set the decay time in various ways, but
don't necessarily restore it properly. Add a new method for
getting the current decay time and then a way to restore it.

Right now the assumption is that the decay time defaults to zero,
but in the near future that assumption might be incorrect. Therefore
using this method will future proof the code.

Bug: 302212507

Test: Unit tests pass for both static and dynamic executables.
Test: Ran bionic benchmarks that were modified.
Change-Id: Ia77ff9ffee3081c5c1c02cb4309880f33b284e82
diff --git a/benchmarks/Android.bp b/benchmarks/Android.bp
index ffb5921..75e607c 100644
--- a/benchmarks/Android.bp
+++ b/benchmarks/Android.bp
@@ -72,6 +72,7 @@
 
     target: {
         android: {
+            header_libs: ["bionic_libc_platform_headers"],
             static_libs: [
                 "libmeminfo",
                 "libprocinfo",
diff --git a/benchmarks/ScopedDecayTimeRestorer.h b/benchmarks/ScopedDecayTimeRestorer.h
new file mode 100644
index 0000000..5835b43
--- /dev/null
+++ b/benchmarks/ScopedDecayTimeRestorer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <malloc.h>
+
+#if defined(__BIONIC__)
+
+#include "platform/bionic/malloc.h"
+
+class ScopedDecayTimeRestorer {
+ public:
+  ScopedDecayTimeRestorer() {
+    bool value;
+    if (android_mallopt(M_GET_DECAY_TIME_ENABLED, &value, sizeof(value))) {
+      saved_value_ = value ? 1 : 0;
+    }
+  }
+
+  virtual ~ScopedDecayTimeRestorer() { mallopt(M_DECAY_TIME, saved_value_); }
+
+ private:
+  int saved_value_ = 0;
+};
+
+#endif
diff --git a/benchmarks/malloc_benchmark.cpp b/benchmarks/malloc_benchmark.cpp
index 258343f..8f467d2 100644
--- a/benchmarks/malloc_benchmark.cpp
+++ b/benchmarks/malloc_benchmark.cpp
@@ -36,11 +36,14 @@
 #include <vector>
 
 #include <benchmark/benchmark.h>
+#include "ScopedDecayTimeRestorer.h"
 #include "util.h"
 
 #if defined(__BIONIC__)
 
 static void RunMalloptPurge(benchmark::State& state, int purge_value) {
+  ScopedDecayTimeRestorer restorer;
+
   static size_t sizes[] = {8, 16, 32, 64, 128, 1024, 4096, 16384, 65536, 131072, 1048576};
   static int pagesize = getpagesize();
   mallopt(M_DECAY_TIME, 1);
@@ -69,7 +72,6 @@
 
     mallopt(purge_value, 0);
   }
-  mallopt(M_DECAY_TIME, 0);
 }
 
 static void RunThreadsThroughput(benchmark::State& state, size_t size, size_t num_threads) {
diff --git a/benchmarks/malloc_sql_benchmark.cpp b/benchmarks/malloc_sql_benchmark.cpp
index 383325c..d5b17f6 100644
--- a/benchmarks/malloc_sql_benchmark.cpp
+++ b/benchmarks/malloc_sql_benchmark.cpp
@@ -31,6 +31,7 @@
 #include <unistd.h>
 
 #include <benchmark/benchmark.h>
+#include "ScopedDecayTimeRestorer.h"
 #include "util.h"
 
 #if defined(__BIONIC__)
@@ -104,6 +105,8 @@
 #include "malloc_sql.h"
 
 static void BM_malloc_sql_trace_default(benchmark::State& state) {
+  ScopedDecayTimeRestorer restorer;
+
   // The default is expected to be a zero decay time.
   mallopt(M_DECAY_TIME, 0);
 
@@ -115,14 +118,14 @@
 BIONIC_BENCHMARK(BM_malloc_sql_trace_default);
 
 static void BM_malloc_sql_trace_decay1(benchmark::State& state) {
+  ScopedDecayTimeRestorer restorer;
+
   mallopt(M_DECAY_TIME, 1);
 
   for (auto _ : state) {
     BenchmarkMalloc(g_sql_entries, sizeof(g_sql_entries) / sizeof(MallocEntry),
                     kMaxSqlAllocSlots);
   }
-
-  mallopt(M_DECAY_TIME, 0);
 }
 BIONIC_BENCHMARK(BM_malloc_sql_trace_decay1);
 
diff --git a/benchmarks/stdlib_benchmark.cpp b/benchmarks/stdlib_benchmark.cpp
index 14b380a..9be72e7 100644
--- a/benchmarks/stdlib_benchmark.cpp
+++ b/benchmarks/stdlib_benchmark.cpp
@@ -22,6 +22,7 @@
 #include <unistd.h>
 
 #include <benchmark/benchmark.h>
+#include "ScopedDecayTimeRestorer.h"
 #include "util.h"
 
 static void MallocFree(benchmark::State& state) {
@@ -40,6 +41,8 @@
 
 static void BM_stdlib_malloc_free_default(benchmark::State& state) {
 #if defined(__BIONIC__)
+  ScopedDecayTimeRestorer restorer;
+
   // The default is expected to be a zero decay time.
   mallopt(M_DECAY_TIME, 0);
 #endif
@@ -50,11 +53,11 @@
 
 #if defined(__BIONIC__)
 static void BM_stdlib_malloc_free_decay1(benchmark::State& state) {
+  ScopedDecayTimeRestorer restorer;
+
   mallopt(M_DECAY_TIME, 1);
 
   MallocFree(state);
-
-  mallopt(M_DECAY_TIME, 0);
 }
 BIONIC_BENCHMARK_WITH_ARG(BM_stdlib_malloc_free_decay1, "AT_COMMON_SIZES");
 #endif
@@ -75,6 +78,8 @@
 
 static void BM_stdlib_calloc_free_default(benchmark::State& state) {
 #if defined(__BIONIC__)
+  ScopedDecayTimeRestorer restorer;
+
   // The default is expected to be a zero decay time.
   mallopt(M_DECAY_TIME, 0);
 #endif
@@ -113,8 +118,9 @@
 }
 
 void BM_stdlib_malloc_forty_default(benchmark::State& state) {
-
 #if defined(__BIONIC__)
+  ScopedDecayTimeRestorer restorer;
+
   // The default is expected to be a zero decay time.
   mallopt(M_DECAY_TIME, 0);
 #endif
@@ -125,17 +131,19 @@
 
 #if defined(__BIONIC__)
 void BM_stdlib_malloc_forty_decay1(benchmark::State& state) {
+  ScopedDecayTimeRestorer restorer;
+
   mallopt(M_DECAY_TIME, 1);
 
   MallocMultiple(state, state.range(0), 40);
-
-  mallopt(M_DECAY_TIME, 0);
 }
 BIONIC_BENCHMARK_WITH_ARG(BM_stdlib_malloc_forty_decay1, "AT_COMMON_SIZES");
 #endif
 
 void BM_stdlib_malloc_multiple_8192_allocs_default(benchmark::State& state) {
 #if defined(__BIONIC__)
+  ScopedDecayTimeRestorer restorer;
+
   // The default is expected to be a zero decay time.
   mallopt(M_DECAY_TIME, 0);
 #endif
@@ -146,11 +154,11 @@
 
 #if defined(__BIONIC__)
 void BM_stdlib_malloc_multiple_8192_allocs_decay1(benchmark::State& state) {
+  ScopedDecayTimeRestorer restorer;
+
   mallopt(M_DECAY_TIME, 1);
 
   MallocMultiple(state, 8192, state.range(0));
-
-  mallopt(M_DECAY_TIME, 0);
 }
 BIONIC_BENCHMARK_WITH_ARG(BM_stdlib_malloc_multiple_8192_allocs_decay1, "AT_SMALL_SIZES");
 #endif
diff --git a/libc/bionic/malloc_common.cpp b/libc/bionic/malloc_common.cpp
index e159fdc..3c4884b 100644
--- a/libc/bionic/malloc_common.cpp
+++ b/libc/bionic/malloc_common.cpp
@@ -110,12 +110,27 @@
   if (param == M_BIONIC_ZERO_INIT) {
     return SetHeapZeroInitialize(value);
   }
+
   // The rest we pass on...
+  int retval;
   auto dispatch_table = GetDispatchTable();
   if (__predict_false(dispatch_table != nullptr)) {
-    return dispatch_table->mallopt(param, value);
+    retval = dispatch_table->mallopt(param, value);
+  } else {
+    retval = Malloc(mallopt)(param, value);
   }
-  return Malloc(mallopt)(param, value);
+
+  // Track the M_DECAY_TIME mallopt calls.
+  if (param == M_DECAY_TIME && retval == 1) {
+    __libc_globals.mutate([value](libc_globals* globals) {
+      if (value == 0) {
+        atomic_store(&globals->decay_time_enabled, false);
+      } else {
+        atomic_store(&globals->decay_time_enabled, true);
+      }
+    });
+  }
+  return retval;
 }
 
 extern "C" void* malloc(size_t bytes) {
@@ -341,6 +356,14 @@
     *reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->memtag_stack);
     return true;
   }
+  if (opcode == M_GET_DECAY_TIME_ENABLED) {
+    if (arg == nullptr || arg_size != sizeof(bool)) {
+      errno = EINVAL;
+      return false;
+    }
+    *reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->decay_time_enabled);
+    return true;
+  }
   errno = ENOTSUP;
   return false;
 }
diff --git a/libc/bionic/malloc_common_dynamic.cpp b/libc/bionic/malloc_common_dynamic.cpp
index 802a94f..792a114 100644
--- a/libc/bionic/malloc_common_dynamic.cpp
+++ b/libc/bionic/malloc_common_dynamic.cpp
@@ -543,6 +543,14 @@
     *reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->memtag_stack);
     return true;
   }
+  if (opcode == M_GET_DECAY_TIME_ENABLED) {
+    if (arg == nullptr || arg_size != sizeof(bool)) {
+      errno = EINVAL;
+      return false;
+    }
+    *reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->decay_time_enabled);
+    return true;
+  }
   // Try heapprofd's mallopt, as it handles options not covered here.
   return HeapprofdMallopt(opcode, arg, arg_size);
 }
diff --git a/libc/platform/bionic/malloc.h b/libc/platform/bionic/malloc.h
index 0a6546e..a06b8ee 100644
--- a/libc/platform/bionic/malloc.h
+++ b/libc/platform/bionic/malloc.h
@@ -104,6 +104,13 @@
   // Query whether memtag stack is enabled for this process.
   M_MEMTAG_STACK_IS_ON = 11,
 #define M_MEMTAG_STACK_IS_ON M_MEMTAG_STACK_IS_ON
+  // Query whether the current process has the decay time enabled so that
+  // the memory from allocations are not immediately released to the OS.
+  // Result is assigned to the arg pointer's destination.
+  //   arg = bool*
+  //   arg_size = sizeof(bool)
+  M_GET_DECAY_TIME_ENABLED = 12,
+#define M_GET_DECAY_TIME_ENABLED M_GET_DECAY_TIME_ENABLED
 };
 
 #pragma clang diagnostic push
diff --git a/libc/private/bionic_globals.h b/libc/private/bionic_globals.h
index d9c4234..15b570d 100644
--- a/libc/private/bionic_globals.h
+++ b/libc/private/bionic_globals.h
@@ -49,6 +49,7 @@
   long setjmp_cookie;
   uintptr_t heap_pointer_tag;
   _Atomic(bool) memtag_stack;
+  _Atomic(bool) decay_time_enabled;
 
   // In order to allow a complete switch between dispatch tables without
   // the need for copying each function by function in the structure,
diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp
index 2411753..14a426f 100644
--- a/tests/malloc_test.cpp
+++ b/tests/malloc_test.cpp
@@ -1734,3 +1734,36 @@
     }
   }
 }
+
+TEST(android_mallopt, get_decay_time_enabled_errors) {
+#if defined(__BIONIC__)
+  errno = 0;
+  EXPECT_FALSE(android_mallopt(M_GET_DECAY_TIME_ENABLED, nullptr, sizeof(bool)));
+  EXPECT_ERRNO(EINVAL);
+
+  errno = 0;
+  int value;
+  EXPECT_FALSE(android_mallopt(M_GET_DECAY_TIME_ENABLED, &value, sizeof(value)));
+  EXPECT_ERRNO(EINVAL);
+#else
+  GTEST_SKIP() << "bionic-only test";
+#endif
+}
+
+TEST(android_mallopt, get_decay_time_enabled) {
+#if defined(__BIONIC__)
+  SKIP_WITH_HWASAN << "hwasan does not implement mallopt";
+
+  EXPECT_EQ(1, mallopt(M_DECAY_TIME, 0));
+
+  bool value;
+  EXPECT_TRUE(android_mallopt(M_GET_DECAY_TIME_ENABLED, &value, sizeof(value)));
+  EXPECT_FALSE(value);
+
+  EXPECT_EQ(1, mallopt(M_DECAY_TIME, 1));
+  EXPECT_TRUE(android_mallopt(M_GET_DECAY_TIME_ENABLED, &value, sizeof(value)));
+  EXPECT_TRUE(value);
+#else
+  GTEST_SKIP() << "bionic-only test";
+#endif
+}