Merge "Fix shadowstack init crash on 16K page system" into main
diff --git a/benchmarks/malloc_benchmark.cpp b/benchmarks/malloc_benchmark.cpp
index e733cd0..258343f 100644
--- a/benchmarks/malloc_benchmark.cpp
+++ b/benchmarks/malloc_benchmark.cpp
@@ -29,6 +29,10 @@
 #include <malloc.h>
 #include <unistd.h>
 
+#include <condition_variable>
+#include <mutex>
+#include <random>
+#include <thread>
 #include <vector>
 
 #include <benchmark/benchmark.h>
@@ -68,6 +72,89 @@
   mallopt(M_DECAY_TIME, 0);
 }
 
+static void RunThreadsThroughput(benchmark::State& state, size_t size, size_t num_threads) {
+  constexpr size_t kMaxBytes = 1 << 24;
+  constexpr size_t kMaxThreads = 8;
+  constexpr size_t kMinRounds = 4;
+  const size_t MaxAllocCounts = kMaxBytes / size;
+  std::mutex m;
+  bool ready = false;
+  std::condition_variable cv;
+  std::thread* threads[kMaxThreads];
+
+  // The goal is to create malloc/free interleaving patterns across threads.
+  // The bytes processed by each thread will be the same. The difference is the
+  // patterns. Here's an example:
+  //
+  // A: Allocation
+  // D: Deallocation
+  //
+  //   T1    T2    T3
+  //   A     A     A
+  //   A     A     D
+  //   A     D     A
+  //   A     D     D
+  //   D     A     A
+  //   D     A     D
+  //   D     D     A
+  //   D     D     D
+  //
+  // To do this, `AllocCounts` and `AllocRounds` will be adjusted according to the
+  // thread id.
+  auto thread_task = [&](size_t id) {
+    {
+      std::unique_lock lock(m);
+      // Wait until all threads are created.
+      cv.wait(lock, [&] { return ready; });
+    }
+
+    void** MemPool;
+    const size_t AllocCounts = (MaxAllocCounts >> id);
+    const size_t AllocRounds = (kMinRounds << id);
+    MemPool = new void*[AllocCounts];
+
+    for (size_t i = 0; i < AllocRounds; ++i) {
+      for (size_t j = 0; j < AllocCounts; ++j) {
+        void* ptr = malloc(size);
+        MemPool[j] = ptr;
+      }
+
+      // Use a fix seed to reduce the noise of different round of benchmark.
+      const unsigned seed = 33529;
+      std::shuffle(MemPool, &MemPool[AllocCounts], std::default_random_engine(seed));
+
+      for (size_t j = 0; j < AllocCounts; ++j) free(MemPool[j]);
+    }
+
+    delete[] MemPool;
+  };
+
+  for (auto _ : state) {
+    state.PauseTiming();
+    // Don't need to acquire the lock because no thread is created.
+    ready = false;
+
+    for (size_t i = 0; i < num_threads; ++i) threads[i] = new std::thread(thread_task, i);
+
+    state.ResumeTiming();
+
+    {
+      std::unique_lock lock(m);
+      ready = true;
+    }
+
+    cv.notify_all();
+
+    for (size_t i = 0; i < num_threads; ++i) {
+      threads[i]->join();
+      delete threads[i];
+    }
+  }
+
+  const size_t ThreadsBytesProcessed = kMaxBytes * kMinRounds * num_threads;
+  state.SetBytesProcessed(ThreadsBytesProcessed * static_cast<size_t>(state.iterations()));
+}
+
 static void BM_mallopt_purge(benchmark::State& state) {
   RunMalloptPurge(state, M_PURGE);
 }
@@ -78,4 +165,23 @@
 }
 BIONIC_BENCHMARK(BM_mallopt_purge_all);
 
+// Note that this will only test a single size class at a time so that we can
+// observe the impact of contention more often.
+#define BM_MALLOC_THREADS_THROUGHPUT(SIZE, NUM_THREADS)                                      \
+  static void BM_malloc_threads_throughput_##SIZE##_##NUM_THREADS(benchmark::State& state) { \
+    RunThreadsThroughput(state, SIZE, NUM_THREADS);                                          \
+  }                                                                                          \
+  BIONIC_BENCHMARK(BM_malloc_threads_throughput_##SIZE##_##NUM_THREADS);
+
+// There are three block categories in Scudo, we choose 1 from each category.
+BM_MALLOC_THREADS_THROUGHPUT(64, 2);
+BM_MALLOC_THREADS_THROUGHPUT(64, 4);
+BM_MALLOC_THREADS_THROUGHPUT(64, 8);
+BM_MALLOC_THREADS_THROUGHPUT(512, 2);
+BM_MALLOC_THREADS_THROUGHPUT(512, 4);
+BM_MALLOC_THREADS_THROUGHPUT(512, 8);
+BM_MALLOC_THREADS_THROUGHPUT(8192, 2);
+BM_MALLOC_THREADS_THROUGHPUT(8192, 4);
+BM_MALLOC_THREADS_THROUGHPUT(8192, 8);
+
 #endif
diff --git a/libc/include/sys/ifunc.h b/libc/include/sys/ifunc.h
index 7fbca4a..d35600e 100644
--- a/libc/include/sys/ifunc.h
+++ b/libc/include/sys/ifunc.h
@@ -40,13 +40,15 @@
 #if defined(__aarch64__)
 
 /**
- * Provides information about hardware capabilities to ifunc resolvers.
+ * Provides information about hardware capabilities to arm64 ifunc resolvers.
  *
- * Starting with API level 30, ifunc resolvers on arm64 are passed two arguments. The first is a
- * uint64_t whose value is equal to getauxval(AT_HWCAP) | _IFUNC_ARG_HWCAP. The second is a pointer
- * to a data structure of this type. Prior to API level 30, no arguments are passed to ifunc
- * resolvers. Code that wishes to be compatible with prior API levels should not accept any
- * arguments in the resolver.
+ * Prior to API level 30, arm64 ifunc resolvers are passed no arguments.
+ *
+ * Starting with API level 30, arm64 ifunc resolvers are passed two arguments.
+ * The first is a uint64_t whose value is equal to getauxval(AT_HWCAP) | _IFUNC_ARG_HWCAP.
+ * The second is a pointer to a data structure of this type.
+ *
+ * Code that wishes to be compatible with API levels before 30 must call getauxval() itself.
  */
 typedef struct __ifunc_arg_t {
   /** Set to sizeof(__ifunc_arg_t). */
@@ -60,9 +62,14 @@
 } __ifunc_arg_t;
 
 /**
- * If this bit is set in the first argument to an ifunc resolver, indicates that the second argument
- * is a pointer to a data structure of type __ifunc_arg_t. This bit is always set on Android
- * starting with API level 30.
+ * If this bit is set in the first argument to an ifunc resolver, the second argument
+ * is a pointer to a data structure of type __ifunc_arg_t.
+ *
+ * This bit is always set on Android starting with API level 30.
+ * This bit is meaningless before API level 30 because ifunc resolvers are not passed any arguments.
+ * This bit has no real use on Android, but is included for glibc source compatibility;
+ * glibc used this bit to distinguish the case where the ifunc resolver received a single argument,
+ * which was an evolutionary stage Android never went through.
  */
 #define _IFUNC_ARG_HWCAP (1ULL << 62)