// RUN: %clangxx %s -o %t
// RUN: env %tool_options=allocator_release_to_os_interval_ms=-1 %run %t

// Temporarily disable test
// UNSUPPORTED: tsan
// UNSUPPORTED: target={{(powerpc64|loongarch64).*}}

// Not needed, no allocator.
// UNSUPPORTED: ubsan

// FIXME: This mode uses 32bit allocator without purge.
// UNSUPPORTED: hwasan-aliasing

// Page size is hardcoded below, but test still fails even if not hardcoded.
// REQUIRES: page-size-4096

#include <algorithm>
#include <assert.h>
#include <fcntl.h>
#include <random>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <sanitizer/allocator_interface.h>

const size_t kPageSize = 4096;

void sync_rss() {
  char *page =
      (char *)mmap((void *)&sync_rss, kPageSize, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
  // Linux kernel updates RSS counters after a set number of page faults.
  for (int i = 0; i < 10000; ++i) {
    page[0] = 42;
    madvise(page, kPageSize, MADV_DONTNEED);
  }
  munmap(page, kPageSize);
}

size_t current_rss() {
  sync_rss();
  int statm_fd = open("/proc/self/statm", O_RDONLY);
  assert(statm_fd >= 0);

  char buf[100];
  assert(read(statm_fd, &buf, sizeof(buf)) > 0);
  size_t size, rss;
  assert(sscanf(buf, "%zu %zu", &size, &rss) == 2);

  close(statm_fd);
  return rss;
}

size_t MallocReleaseStress() {
  const size_t kNumChunks = 10000;
  const size_t kAllocSize = 100;
  const size_t kNumIter = 100;
  uintptr_t *chunks[kNumChunks] = {0};
  std::mt19937 r;

  for (size_t iter = 0; iter < kNumIter; iter++) {
    std::shuffle(chunks, chunks + kNumChunks, r);
    size_t to_replace = rand() % kNumChunks;
    for (size_t i = 0; i < kNumChunks; i++) {
      if (chunks[i])
        assert(chunks[i][0] == (uintptr_t)chunks[i]);
      if (i < to_replace) {
        delete[] chunks[i];
        chunks[i] = new uintptr_t[kAllocSize];
        chunks[i][0] = (uintptr_t)chunks[i];
      }
    }
  }
  fprintf(stderr, "before delete: %zu\n", current_rss());
  for (auto p : chunks)
    delete[] p;
  return kNumChunks * kAllocSize * sizeof(uintptr_t);
}

int main(int argc, char **argv) {
  // 32bit asan allocator is unsupported.
  if (sizeof(void *) < 8)
    return 0;
  auto a = current_rss();
  auto total = MallocReleaseStress() >> 10;
  auto b = current_rss();
  __sanitizer_purge_allocator();
  auto c = current_rss();
  fprintf(stderr, "a:%zu b:%zu c:%zu total:%zu\n", a, b, c, total);
  assert(a + total / 8 < b);
  assert(c + total / 8 < b);
}
