/* Copyright 2006 The Android Open Source Project */

/* A wrapper file for dlmalloc.c that compiles in the
 * mspace_*() functions, which provide an interface for
 * creating multiple heaps.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/ioctl.h>

#include <cutils/ashmem.h>

/* It's a pain getting the mallinfo stuff to work
 * with Linux, OSX, and klibc, so just turn it off
 * for now.
 * TODO: make mallinfo work
 */
#define NO_MALLINFO 1

/* Allow setting the maximum heap footprint.
 */
#define USE_MAX_ALLOWED_FOOTPRINT 1

/* Don't try to trim memory.
 * TODO: support this.
 */
#define MORECORE_CANNOT_TRIM 1

/* Use mmap()d anonymous memory to guarantee
 * that an mspace is contiguous.
 *
 * create_mspace() won't work right if this is
 * defined, so hide the definition of it and
 * break any users at build time.
 */
#define USE_CONTIGUOUS_MSPACES 1
#if USE_CONTIGUOUS_MSPACES
/* This combination of settings forces sys_alloc()
 * to always use MORECORE().  It won't expect the
 * results to be contiguous, but we'll guarantee
 * that they are.
 */
#define HAVE_MMAP 0
#define HAVE_MORECORE 1
#define MORECORE_CONTIGUOUS 0
/* m is always the appropriate local when MORECORE() is called. */
#define MORECORE(S) contiguous_mspace_morecore(m, S)
#define create_mspace   HIDDEN_create_mspace_HIDDEN
#define destroy_mspace   HIDDEN_destroy_mspace_HIDDEN
typedef struct malloc_state *mstate0;
static void *contiguous_mspace_morecore(mstate0 m, ssize_t nb);
#endif

#define MSPACES 1
#define ONLY_MSPACES 1
#include "../../../bionic/libc/bionic/dlmalloc.c"

#ifndef PAGESIZE
#define PAGESIZE  mparams.page_size
#endif

#define ALIGN_UP(p, alignment) \
    (((uintptr_t)(p) + (alignment)-1) & ~((alignment)-1))

/* A direct copy of dlmalloc_usable_size(),
 * which isn't compiled in when ONLY_MSPACES is set.
 * The mspace parameter isn't actually necessary,
 * but we include it to be consistent with the
 * rest of the mspace_*() functions.
 */
size_t mspace_usable_size(mspace _unused, const void* mem) {
  if (mem != 0) {
    const mchunkptr p = mem2chunk(mem);
    if (cinuse(p))
      return chunksize(p) - overhead_for(p);
  }
  return 0;
}

#if USE_CONTIGUOUS_MSPACES
#include <sys/mman.h>
#include <limits.h>

#define CONTIG_STATE_MAGIC  0xf00dd00d
struct mspace_contig_state {
  unsigned int magic;
  char *brk;
  char *top;
  mspace m;
};

static void *contiguous_mspace_morecore(mstate m, ssize_t nb) {
  struct mspace_contig_state *cs;
  char *oldbrk;
  const unsigned int pagesize = PAGESIZE;

  cs = (struct mspace_contig_state *)((uintptr_t)m & ~(pagesize-1));
  assert(cs->magic == CONTIG_STATE_MAGIC);
  assert(cs->m == m);
assert(nb >= 0);  //xxx deal with the trim case

  oldbrk = cs->brk;
  if (nb > 0) {
    /* Break to the first page boundary that satisfies the request.
     */
    char *newbrk = (char *)ALIGN_UP(oldbrk + nb, pagesize);
    if (newbrk > cs->top)
      return CMFAIL;

    /* Update the protection on the underlying memory.
     * Pages we've given to dlmalloc are read/write, and
     * pages we haven't are not accessable (read or write
     * will cause a seg fault).
     */
    if (mprotect(cs, newbrk - (char *)cs, PROT_READ | PROT_WRITE) < 0)
      return CMFAIL;
    if (newbrk != cs->top) {
      if (mprotect(newbrk, cs->top - newbrk, PROT_NONE) < 0)
        return CMFAIL;
    }

    cs->brk = newbrk;

    /* Make sure that dlmalloc will merge this block with the
     * initial block that was passed to create_mspace_with_base().
     * We don't care about extern vs. non-extern, so just clear it.
     */
    m->seg.sflags &= ~EXTERN_BIT;
  }

  return oldbrk;
}

mspace create_contiguous_mspace_with_base(size_t starting_capacity,
    size_t max_capacity, int locked, void *base) {
  struct mspace_contig_state *cs;
  unsigned int pagesize;
  mstate m;

  init_mparams();
  pagesize = PAGESIZE;
  assert(starting_capacity <= max_capacity);
  assert(((uintptr_t)base & (pagesize-1)) == 0);
  assert(((uintptr_t)max_capacity & (pagesize-1)) == 0);
  starting_capacity = (size_t)ALIGN_UP(starting_capacity, pagesize);

  /* Make the first page read/write. dlmalloc needs to use that page.
   */
  if (mprotect(base, starting_capacity, PROT_READ | PROT_WRITE) < 0) {
    goto error;
  }

  /* Create the mspace, pointing to the memory given.
   */
  m = create_mspace_with_base((char *)base + sizeof(*cs), starting_capacity,
                              locked);
  if (m == (mspace)0) {
    goto error;
  }
  /* Make sure that m is in the same page as base.
   */
  assert(((uintptr_t)m & (uintptr_t)~(pagesize-1)) == (uintptr_t)base);
  /* Use some space for the information that our MORECORE needs.
   */
  cs = (struct mspace_contig_state *)base;

  /* Find out exactly how much of the memory the mspace
   * is using.
   */
  cs->brk = m->seg.base + m->seg.size;
  cs->top = (char *)base + max_capacity;

  assert((char *)base <= cs->brk);
  assert(cs->brk <= cs->top);
  /* Prevent access to the memory we haven't handed out yet.
   */
  if (cs->brk != cs->top) {
    /* mprotect() requires page-aligned arguments, but it's possible
     * for cs->brk not to be page-aligned at this point.
     */
    char *prot_brk = (char *)ALIGN_UP(cs->brk, pagesize);
    if ((mprotect(base, prot_brk - (char *)base, PROT_READ | PROT_WRITE) < 0) ||
        (mprotect(prot_brk, cs->top - prot_brk, PROT_NONE) < 0)) {
      goto error;
    }
  }

  cs->m = m;
  cs->magic = CONTIG_STATE_MAGIC;

  return (mspace)m;

error:
  return (mspace)0;
}


mspace create_contiguous_mspace_with_name(size_t starting_capacity,
    size_t max_capacity, int locked, char const *name) {
  int fd, ret;
  char buf[ASHMEM_NAME_LEN] = "mspace";
  void *base;
  unsigned int pagesize;
  mstate m;

  if (starting_capacity > max_capacity)
    return (mspace)0;

  init_mparams();
  pagesize = PAGESIZE;

  /* Create the anonymous memory that will back the mspace.
   * This reserves all of the virtual address space we could
   * ever need.  Physical pages will be mapped as the memory
   * is touched.
   *
   * Align max_capacity to a whole page.
   */
  max_capacity = (size_t)ALIGN_UP(max_capacity, pagesize);

  if (name)
    snprintf(buf, sizeof(buf), "mspace/%s", name);
  fd = ashmem_create_region(buf, max_capacity);
  if (fd < 0)
    return (mspace)0;

  base = mmap(NULL, max_capacity, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
  close(fd);
  if (base == MAP_FAILED)
    return (mspace)0;

  /* Make sure that base is at the beginning of a page.
   */
  assert(((uintptr_t)base & (pagesize-1)) == 0);

  m = create_contiguous_mspace_with_base(starting_capacity, max_capacity,
                                         locked, base);
  if (m == 0) {
    munmap(base, max_capacity);
  }
  return m;
}

mspace create_contiguous_mspace(size_t starting_capacity,
    size_t max_capacity, int locked) {
  return create_contiguous_mspace_with_name(starting_capacity,
      max_capacity, locked, NULL);
}

size_t destroy_contiguous_mspace(mspace msp) {
  mstate ms = (mstate)msp;

  if (ok_magic(ms)) {
    struct mspace_contig_state *cs;
    size_t length;
    const unsigned int pagesize = PAGESIZE;

    cs = (struct mspace_contig_state *)((uintptr_t)ms & ~(pagesize-1));
    assert(cs->magic == CONTIG_STATE_MAGIC);
    assert(cs->m == ms);

    length = cs->top - (char *)cs;
    if (munmap((char *)cs, length) != 0)
      return length;
  }
  else {
    USAGE_ERROR_ACTION(ms, ms);
  }
  return 0;
}
#endif
