/*
 * $Id: ptmalloc3.c,v 1.8 2006/03/31 15:57:28 wg Exp $
 * 

ptmalloc3 -- wrapper for Doug Lea's malloc-2.8.3 with concurrent
             allocations

Copyright (c) 2005, 2006 Wolfram Gloger  <ptmalloc@malloc.de>

Permission to use, copy, modify, distribute, and sell this software
and its documentation for any purpose is hereby granted without fee,
provided that (i) the above copyright notices and this permission
notice appear in all copies of the software and related documentation,
and (ii) the name of Wolfram Gloger may not be used in any advertising
or publicity relating to the software.

THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

IN NO EVENT SHALL WOLFRAM GLOGER BE LIABLE FOR ANY SPECIAL,
INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY
OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

 */

/*
 * TODO: optimization / better integration with malloc.c (partly done)
 *       malloc_{get,set}_state (probably hard to keep compatibility)
 *       debugging hooks
 *       better mstats
 */

#if (_MSC_VER > 1300)
#define _CRT_SECURE_NO_DEPRECATE
#endif

#ifndef WIN32
#include <sys/types.h>   /* For size_t */
#include <sys/mman.h>    /* for mmap */
#endif /* WIN32 */
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>      /* for memset */

#include <malloc-machine.h>

// CS specific options
#define USE_PT_PREFIX

#define MSPACES 1
#define ONLY_MSPACES 1

#include "malloc-2.8.3.h"

/* ----------------------------------------------------------------------- */

/* The following section is replicated from malloc.c */

#include "malloc-private.h"

/* end of definitions replicated from malloc.c */

#define munmap_chunk(mst, p) do {                         \
  size_t prevsize = (p)->prev_foot & ~IS_MMAPPED_BIT;     \
  size_t psize = chunksize(p) + prevsize + MMAP_FOOT_PAD; \
  if (CALL_MUNMAP((char*)(p) - prevsize, psize) == 0)     \
    ((struct malloc_state*)(mst))->footprint -= psize;    \
} while (0)

/* ---------------------------------------------------------------------- */

/* Minimum size for a newly created arena.  */
#ifndef ARENA_SIZE_MIN
# define ARENA_SIZE_MIN	   (128*1024)
#endif
#define HAVE_MEMCPY        1

/* If THREAD_STATS is non-zero, some statistics on mutex locking are
   computed.  */
#ifndef THREAD_STATS
# define THREAD_STATS 0
#endif

#ifndef MALLOC_DEBUG
# define MALLOC_DEBUG 0
#endif

#define my_powerof2(x) ((((x)-1)&(x))==0)

/* Already initialized? */
//static int __malloc_initialized = -1;

#ifndef RETURN_ADDRESS
# define RETURN_ADDRESS(X_) (NULL)
#endif

#if THREAD_STATS
# define THREAD_STAT(x) x
#else
# define THREAD_STAT(x) do ; while(0)
#endif

#ifdef _LIBC

/* Special defines for the GNU C library.  */
#define public_cALLOc    __libc_calloc
#define public_fREe      __libc_free
#define public_cFREe     __libc_cfree
#define public_mALLOc    __libc_malloc
#define public_mEMALIGn  __libc_memalign
#define public_rEALLOc   __libc_realloc
#define public_vALLOc    __libc_valloc
#define public_pVALLOc   __libc_pvalloc
#define public_pMEMALIGn __posix_memalign
#define public_mALLINFo  __libc_mallinfo
#define public_mALLOPt   __libc_mallopt
#define public_mTRIm     __malloc_trim
#define public_mSTATs    __malloc_stats
#define public_mUSABLe   __malloc_usable_size
#define public_iCALLOc   __libc_independent_calloc
#define public_iCOMALLOc __libc_independent_comalloc
#define public_gET_STATe __malloc_get_state
#define public_sET_STATe __malloc_set_state
#define malloc_getpagesize __getpagesize()
#define open             __open
#define mmap             __mmap
#define munmap           __munmap
#define mremap           __mremap
#define mprotect         __mprotect
#define MORECORE         (*__morecore)
#define MORECORE_FAILURE 0

void * __default_morecore (ptrdiff_t);
void *(*__morecore)(ptrdiff_t) = __default_morecore;

#elif defined(USE_PT_PREFIX)

#define public_cALLOc    ptcalloc
#define public_fREe      ptfree
#define public_cFREe     ptcfree
#define public_mALLOc    ptmalloc
#define public_mEMALIGn  ptmemalign
#define public_rEALLOc   ptrealloc
#define public_vALLOc    ptvalloc
#define public_pVALLOc   ptpvalloc
#define public_pMEMALIGn ptposix_memalign
#define public_mALLINFo  ptmallinfo
#define public_mALLOPt   ptmallopt
#define public_mTRIm     ptmalloc_trim
#define public_mSTATs    ptmalloc_stats
#define public_mUSABLe   ptmalloc_usable_size
#define public_iCALLOc   ptindependent_calloc
#define public_iCOMALLOc ptindependent_comalloc
#define public_gET_STATe ptmalloc_get_state
#define public_sET_STATe ptmalloc_set_state

#define __malloc_initialize_hook        __ptmalloc_initialize_hook
#define __free_hook                     __ptfree_hook
#define __malloc_hook                   __ptmalloc_hook
#define __realloc_hook                  __ptrealloc_hook
#define __memalign_hook                 __ptmemalign_hook

#else /* !_LIBC */

#define public_cALLOc    calloc
#define public_fREe      free
#define public_cFREe     cfree
#define public_mALLOc    malloc
#define public_mEMALIGn  memalign
#define public_rEALLOc   realloc
#define public_vALLOc    valloc
#define public_pVALLOc   pvalloc
#define public_pMEMALIGn posix_memalign
#define public_mALLINFo  mallinfo
#define public_mALLOPt   mallopt
#define public_mTRIm     malloc_trim
#define public_mSTATs    malloc_stats
#define public_mUSABLe   malloc_usable_size
#define public_iCALLOc   independent_calloc
#define public_iCOMALLOc independent_comalloc
#define public_gET_STATe malloc_get_state
#define public_sET_STATe malloc_set_state

#endif /* _LIBC */

#if !defined _LIBC && (!defined __GNUC__ || __GNUC__<3)
#define __builtin_expect(expr, val) (expr)
#endif

#if MALLOC_DEBUG
#include <assert.h>
#else
#undef assert
#define assert(x) ((void)0)
#endif

/* USE_STARTER determines if and when the special "starter" hook
   functions are used: not at all (0), during ptmalloc_init (first bit
   set), or from the beginning until an explicit call to ptmalloc_init
   (second bit set).  This is necessary if thread-related
   initialization functions (e.g.  pthread_key_create) require
   malloc() calls (set USE_STARTER=1), or if those functions initially
   cannot be used at all (set USE_STARTER=2 and perform an explicit
   ptmalloc_init() when the thread library is ready, typically at the
   start of main()). */

#ifndef USE_STARTER
# ifndef _LIBC
#  define USE_STARTER 1
# else
#  if USE___THREAD || (defined USE_TLS && !defined SHARED)
    /* These routines are never needed in this configuration.  */
#   define USE_STARTER 0
#  else
#   define USE_STARTER (USE_TLS ? 4 : 1)
#  endif
# endif
#endif

/*----------------------------------------------------------------------*/

/* Arenas */
//static tsd_key_t arena_key;
//static mutex_t list_lock;

/* Arena structure */
struct malloc_arena {
  /* Serialize access.  */
  mutex_t mutex;

  /* Statistics for locking.  Only used if THREAD_STATS is defined.  */
  long stat_lock_direct, stat_lock_loop, stat_lock_wait;
  long stat_starter;

  /* Linked list */
  struct malloc_arena *next;

  /* Space for mstate.  The size is just the minimum such that
     create_mspace_with_base can be successfully called.  */
  char buf_[pad_request(sizeof(struct malloc_state)) + TOP_FOOT_SIZE +
	    CHUNK_ALIGN_MASK + 1];
};
#define MSPACE_OFFSET (((offsetof(struct malloc_arena, buf_) \
			 + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK))
#define arena_to_mspace(a) ((void *)chunk2mem((char*)(a) + MSPACE_OFFSET))

/* check for chunk from non-main arena */
#define chunk_non_main_arena(p) ((p)->head & NON_MAIN_ARENA)

static struct malloc_arena* _int_new_arena(size_t size);

/* Buffer for the main arena. */
//static struct malloc_arena main_arena;
struct ptmalloc_state
{
  struct malloc_arena main_arena;

  int refcount;
  int __malloc_initialized;
  tsd_key_t arena_key;
  mutex_t list_lock;
};
static struct ptmalloc_state* state;

/* For now, store arena in footer.  This means typically 4bytes more
   overhead for each non-main-arena chunk, but is fast and easy to
   compute.  Note that the pointer stored in the extra footer must be
   properly aligned, though. */
#define FOOTER_OVERHEAD \
 (2*sizeof(struct malloc_arena*) - SIZE_T_SIZE)

#define arena_for_chunk(ptr) \
 (chunk_non_main_arena(ptr) ? *(struct malloc_arena**)              \
  ((char*)(ptr) + chunksize(ptr) - (FOOTER_OVERHEAD - SIZE_T_SIZE)) \
  : &state->main_arena)

/* special because of extra overhead */
#define arena_for_mmap_chunk(ptr) \
 (chunk_non_main_arena(ptr) ? *(struct malloc_arena**)             \
  ((char*)(ptr) + chunksize(ptr) - sizeof(struct malloc_arena*))   \
  : &state->main_arena)

#define set_non_main_arena(mem, ar_ptr) do {                   		      \
  mchunkptr P = mem2chunk(mem);                                               \
  size_t SZ = chunksize(P) - (is_mmapped(P) ? sizeof(struct malloc_arena*)    \
                              : (FOOTER_OVERHEAD - SIZE_T_SIZE));             \
  assert((unsigned long)((char*)(P) + SZ)%sizeof(struct malloc_arena*) == 0); \
  *(struct malloc_arena**)((char*)(P) + SZ) = (ar_ptr);                       \
  P->head |= NON_MAIN_ARENA;                                                  \
} while (0)

/* arena_get() acquires an arena and locks the corresponding mutex.
   First, try the one last locked successfully by this thread.  (This
   is the common case and handled with a macro for speed.)  Then, loop
   once over the circularly linked list of arenas.  If no arena is
   readily available, create a new one.  In this latter case, `size'
   is just a hint as to how much memory will be required immediately
   in the new arena. */

#define arena_get(ptr, size) do { \
  void *vptr = NULL; \
  ptr = (struct malloc_arena*)tsd_getspecific(state->arena_key, vptr); \
  if(ptr && !mutex_trylock(&ptr->mutex)) { \
    THREAD_STAT(++(ptr->stat_lock_direct)); \
  } else \
    ptr = arena_get2(ptr, (size)); \
} while(0)

static struct malloc_arena*
arena_get2(struct malloc_arena* a_tsd, size_t size)
{
  struct malloc_arena* a;
  int err;

  if(!a_tsd)
    a = a_tsd = &state->main_arena;
  else {
    a = a_tsd->next;
    if(!a) {
      /* This can only happen while initializing the new arena. */
      (void)mutex_lock(&state->main_arena.mutex);
      THREAD_STAT(++(state->main_arena.stat_lock_wait));
      return &state->main_arena;
    }
  }

  /* Check the global, circularly linked list for available arenas. */
 repeat:
  do {
    if(!mutex_trylock(&a->mutex)) {
      THREAD_STAT(++(a->stat_lock_loop));
      tsd_setspecific(state->arena_key, (void *)a);
      return a;
    }
    a = a->next;
  } while(a != a_tsd);

  /* If not even the list_lock can be obtained, try again.  This can
     happen during `atfork', or for example on systems where thread
     creation makes it temporarily impossible to obtain _any_
     locks. */
  if(mutex_trylock(&state->list_lock)) {
    a = a_tsd;
    goto repeat;
  }
  (void)mutex_unlock(&state->list_lock);

  /* Nothing immediately available, so generate a new arena.  */
  a = _int_new_arena(size);
  if(!a)
    return 0;

  tsd_setspecific(state->arena_key, (void *)a);
  mutex_init(&a->mutex);
  err = mutex_lock(&a->mutex); /* remember result */

  /* Add the new arena to the global list.  */
  (void)mutex_lock(&state->list_lock);
  a->next = state->main_arena.next;
  atomic_write_barrier ();
  state->main_arena.next = a;
  (void)mutex_unlock(&state->list_lock);

  if(err) /* locking failed; keep arena for further attempts later */
    return 0;

  THREAD_STAT(++(a->stat_lock_loop));
  return a;
}

/* Create a new arena with room for a chunk of size "size".  */

static struct malloc_arena*
_int_new_arena(size_t size)
{
  struct malloc_arena* a;
  size_t mmap_sz = sizeof(*a) + pad_request(size);
  void *m;

  if (mmap_sz < ARENA_SIZE_MIN)
    mmap_sz = ARENA_SIZE_MIN;
  /* conservative estimate for page size */
  mmap_sz = (mmap_sz + 8191) & ~(size_t)8191;
  a = CALL_MMAP(mmap_sz);
  if ((char*)a == (char*)-1)
    return 0;

  m = create_mspace_with_base((char*)a + MSPACE_OFFSET,
			      mmap_sz - MSPACE_OFFSET,
			      0);

  if (!m) { 
    CALL_MUNMAP(a, mmap_sz);
    a = 0;
  } else {
    /*a->next = NULL;*/
    /*a->system_mem = a->max_system_mem = h->size;*/
  }

  return a;
}

/*------------------------------------------------------------------------*/

/* Hook mechanism for proper initialization and atfork support. */

/* Define and initialize the hook variables.  These weak definitions must
   appear before any use of the variables in a function.  */
#ifndef weak_variable
#ifndef _LIBC
#define weak_variable /**/
#else
/* In GNU libc we want the hook variables to be weak definitions to
   avoid a problem with Emacs.  */
#define weak_variable weak_function
#endif
#endif

#if !(USE_STARTER & 2)
# define free_hook_ini     NULL
/* Forward declarations.  */
static void* malloc_hook_ini (size_t sz, const void *caller);
static void* realloc_hook_ini (void* ptr, size_t sz, const void* caller);
static void* memalign_hook_ini (size_t alignment, size_t sz,
				const void* caller);
#else
static void* malloc_starter (size_t sz, const void *caller);
static void* realloc_starter (void* ptr, size_t sz, const void* caller);
static void free_starter (void * __ptr, const void *);
static void* memalign_starter (size_t alignment, size_t sz,
				const void* caller);
# define free_hook_ini     free_starter
# define malloc_hook_ini   malloc_starter
# define realloc_hook_ini  NULL
# define memalign_hook_ini memalign_starter
#endif

void weak_variable (*__malloc_initialize_hook) (void) = NULL;
void weak_variable (*__free_hook) (void * __ptr, const void *)
     = free_hook_ini;
void * weak_variable (*__malloc_hook) (size_t __size, const void *)
     = malloc_hook_ini;
void * weak_variable (*__realloc_hook)
     (void * __ptr, size_t __size, const void *) = realloc_hook_ini;
void * weak_variable (*__memalign_hook)
  (size_t __alignment, size_t __size, const void *) = memalign_hook_ini;
/*void weak_variable (*__after_morecore_hook) (void) = NULL;*/

/* The initial hooks just call the initialization routine, then do the
   normal work. */

#if !(USE_STARTER & 2)
static
#endif
void ptmalloc_init(void);

void* public_mALLOc(size_t sz);
void* public_rEALLOc(void* p, size_t sz);
void* public_mEMALIGn(size_t alignment, size_t sz);
int public_mALLOPt(int p, int v);

#if !(USE_STARTER & 2)

static void*
malloc_hook_ini(size_t sz, const void * caller)
{
  __malloc_hook = NULL;
  ptmalloc_init();
  return public_mALLOc(sz);
}

static void *
realloc_hook_ini(void *ptr, size_t sz, const void * caller)
{
  __malloc_hook = NULL;
  __realloc_hook = NULL;
  ptmalloc_init();
  return public_rEALLOc(ptr, sz);
}

static void*
memalign_hook_ini(size_t alignment, size_t sz, const void * caller)
{
  __memalign_hook = NULL;
  ptmalloc_init();
  return public_mEMALIGn(alignment, sz);
}

#endif /* !(USE_STARTER & 2) */

/*----------------------------------------------------------------------*/

#if !defined NO_THREADS && USE_STARTER

/* The following hooks are used when the global initialization in
   ptmalloc_init() hasn't completed yet. */

static void*
malloc_starter(size_t sz, const void *caller)
{
  void* victim;

  /*ptmalloc_init_minimal();*/
  victim = mspace_malloc(arena_to_mspace(&state->main_arena), sz);
  THREAD_STAT(++state->main_arena.stat_starter);

  return victim;
}

static void*
memalign_starter(size_t align, size_t sz, const void *caller)
{
  void* victim;

  /*ptmalloc_init_minimal();*/
  victim = mspace_memalign(arena_to_mspace(&state->main_arena), align, sz);
  THREAD_STAT(++state->main_arena.stat_starter);

  return victim;
}

static void
free_starter(void* mem, const void *caller)
{
  if (mem) {
    mchunkptr p = mem2chunk(mem);
    void *msp = arena_to_mspace(&state->main_arena);
    if (is_mmapped(p))
      munmap_chunk(msp, p);
    else
      mspace_free(msp, mem);
  }
  THREAD_STAT(++state->main_arena.stat_starter);
}

#endif /* !defined NO_THREADS && USE_STARTER */

/*----------------------------------------------------------------------*/

#if !defined(NO_THREADS)

/* atfork support.  */

static void * (*save_malloc_hook) (size_t __size, const void *);
# if !defined _LIBC || !defined USE_TLS || (defined SHARED && !USE___THREAD)
static void * (*save_memalign_hook) (size_t __align, size_t __size,
				     const void *);
# endif
static void   (*save_free_hook) (void * __ptr, const void *);

#if !defined(WIN32)
static void*  save_arena;

/* Magic value for the thread-specific arena pointer when
   malloc_atfork() is in use.  */

#define ATFORK_ARENA_PTR ((void*)-1)

/* The following hooks are used while the `atfork' handling mechanism
   is active. */

static void*
malloc_atfork(size_t sz, const void *caller)
{
  void *vptr = NULL;

  tsd_getspecific(state->arena_key, vptr);
  if(vptr == ATFORK_ARENA_PTR) {
    /* We are the only thread that may allocate at all.  */
    return mspace_malloc(arena_to_mspace(&state->main_arena), sz);
  } else {
    /* Suspend the thread until the `atfork' handlers have completed.
       By that time, the hooks will have been reset as well, so that
       mALLOc() can be used again. */
    (void)mutex_lock(&state->list_lock);
    (void)mutex_unlock(&state->list_lock);
    return public_mALLOc(sz);
  }
}

static void
free_atfork(void* mem, const void *caller)
{
  void *vptr = NULL;
  struct malloc_arena *ar_ptr;
  mchunkptr p;                          /* chunk corresponding to mem */

  if (mem == 0)                              /* free(0) has no effect */
    return;

  p = mem2chunk(mem);

  if (is_mmapped(p)) {                      /* release mmapped memory. */
    ar_ptr = arena_for_mmap_chunk(p);
    munmap_chunk(arena_to_mspace(ar_ptr), p);
    return;
  }

  ar_ptr = arena_for_chunk(p);
  tsd_getspecific(state->arena_key, vptr);
  if(vptr != ATFORK_ARENA_PTR)
    (void)mutex_lock(&ar_ptr->mutex);
  mspace_free(arena_to_mspace(ar_ptr), mem);
  if(vptr != ATFORK_ARENA_PTR)
    (void)mutex_unlock(&ar_ptr->mutex);
}

/* The following two functions are registered via thread_atfork() to
   make sure that the mutexes remain in a consistent state in the
   fork()ed version of a thread.  Also adapt the malloc and free hooks
   temporarily, because the `atfork' handler mechanism may use
   malloc/free internally (e.g. in LinuxThreads). */

static void
ptmalloc_lock_all (void)
{
  struct malloc_arena* ar_ptr;

  if((state == NULL) || (state->__malloc_initialized < 1))
    return;
  (void)mutex_lock(&state->list_lock);
  for(ar_ptr = &state->main_arena;;) {
    (void)mutex_lock(&ar_ptr->mutex);
    ar_ptr = ar_ptr->next;
    if(ar_ptr == &state->main_arena)
      break;
  }
  save_malloc_hook = __malloc_hook;
  save_free_hook = __free_hook;
  __malloc_hook = malloc_atfork;
  __free_hook = free_atfork;
  /* Only the current thread may perform malloc/free calls now. */
  tsd_getspecific(state->arena_key, save_arena);
  tsd_setspecific(state->arena_key, ATFORK_ARENA_PTR);
}

static void
ptmalloc_unlock_all (void)
{
  struct malloc_arena *ar_ptr;

  if((state == NULL) || (state->__malloc_initialized < 1))
    return;
  tsd_setspecific(state->arena_key, save_arena);
  __malloc_hook = save_malloc_hook;
  __free_hook = save_free_hook;
  for(ar_ptr = &state->main_arena;;) {
    (void)mutex_unlock(&ar_ptr->mutex);
    ar_ptr = ar_ptr->next;
    if(ar_ptr == &state->main_arena) break;
  }
  (void)mutex_unlock(&state->list_lock);
}

#ifdef __linux__

/* In LinuxThreads, unlocking a mutex in the child process after a
   fork() is currently unsafe, whereas re-initializing it is safe and
   does not leak resources.  Therefore, a special atfork handler is
   installed for the child. */

static void
ptmalloc_unlock_all2(void)
{
  struct malloc_arena *ar_ptr;

  if((state == NULL) || (state->__malloc_initialized < 1))
    return;
#if defined _LIBC || 1 /*defined MALLOC_HOOKS*/
  tsd_setspecific(state->arena_key, save_arena);
  __malloc_hook = save_malloc_hook;
  __free_hook = save_free_hook;
#endif
  for(ar_ptr = &state->main_arena;;) {
    (void)mutex_init(&ar_ptr->mutex);
    ar_ptr = ar_ptr->next;
    if(ar_ptr == &state->main_arena) break;
  }
  (void)mutex_init(&state->list_lock);
}

#else

#define ptmalloc_unlock_all2 ptmalloc_unlock_all

#endif

#endif /* !defined WIN32 */
#endif /* !defined NO_THREADS */

/*---------------------------------------------------------------------*/

static void ptmalloc_finis (void)
{
  if((state == NULL) || (state->__malloc_initialized < 1))
    return;
  
  state->refcount--;
  if (state->refcount > 0) return;
  CALL_MUNMAP(state, sizeof (struct ptmalloc_state));
  sharemem_destroy ();
}

#if !(USE_STARTER & 2)
static
#endif
void
ptmalloc_init(void)
{
  const char* s;
  int secure = 0;
  void *mspace;
  struct ptmalloc_state** state_ptr;
  int created = 0;

  if(state != NULL) return;
  state_ptr = sharemem_init (sizeof (struct ptmalloc_state*), &created);
  atexit (ptmalloc_finis);
  if (created)
  {
    state = CALL_MMAP(sizeof (struct ptmalloc_state));
    *state_ptr = state;
    state->refcount = 1;
    state->__malloc_initialized = 0;
    sharemem_close (state_ptr, sizeof (struct ptmalloc_state*));
  }
  else 
  {
    state = *state_ptr;
    sharemem_close (state_ptr, sizeof (struct ptmalloc_state*));
    state->refcount++;
    /* hack: force dlmalloc() to initialize it's (static) mparams struct */
    mspace_mallopt (0xdeadbeef, 0x0f00);
    if(state->__malloc_initialized >= 0) return;
  }

  /*if (mp_.pagesize == 0)
    ptmalloc_init_minimal();*/

#ifndef NO_THREADS
# if USE_STARTER & 1
  /* With some threads implementations, creating thread-specific data
     or initializing a mutex may call malloc() itself.  Provide a
     simple starter version (realloc() won't work). */
  save_malloc_hook = __malloc_hook;
  save_memalign_hook = __memalign_hook;
  save_free_hook = __free_hook;
  __malloc_hook = malloc_starter;
  __memalign_hook = memalign_starter;
  __free_hook = free_starter;
#  ifdef _LIBC
  /* Initialize the pthreads interface. */
  if (__pthread_initialize != NULL)
    __pthread_initialize();
#  endif /* !defined _LIBC */
# endif /* USE_STARTER & 1 */
#endif /* !defined NO_THREADS */
  mutex_init(&state->main_arena.mutex);
  state->main_arena.next = &state->main_arena;
  mspace = create_mspace_with_base((char*)&state->main_arena + MSPACE_OFFSET,
				   sizeof(state->main_arena) - MSPACE_OFFSET,
				   0);
  assert(mspace == arena_to_mspace(&state->main_arena));

  mutex_init(&state->list_lock);
  tsd_key_create(&state->arena_key, NULL);
  tsd_setspecific(state->arena_key, (void *)&state->main_arena);
  thread_atfork(ptmalloc_lock_all, ptmalloc_unlock_all, ptmalloc_unlock_all2);
#ifndef NO_THREADS
# if USE_STARTER & 1
  __malloc_hook = save_malloc_hook;
  __memalign_hook = save_memalign_hook;
  __free_hook = save_free_hook;
# endif
# if USE_STARTER & 2
  __malloc_hook = 0;
  __memalign_hook = 0;
  __free_hook = 0;
# endif
#endif
#ifdef _LIBC
  secure = __libc_enable_secure;
#else
  if (! secure) {
    if ((s = getenv("MALLOC_TRIM_THRESHOLD_")))
      public_mALLOPt(M_TRIM_THRESHOLD, atoi(s));
    if ((s = getenv("MALLOC_TOP_PAD_")) ||
	(s = getenv("MALLOC_GRANULARITY_")))
      public_mALLOPt(M_GRANULARITY, atoi(s));
    if ((s = getenv("MALLOC_MMAP_THRESHOLD_")))
      public_mALLOPt(M_MMAP_THRESHOLD, atoi(s));
    /*if ((s = getenv("MALLOC_MMAP_MAX_"))) this is no longer available
      public_mALLOPt(M_MMAP_MAX, atoi(s));*/
  }
  s = getenv("MALLOC_CHECK_");
#endif
  if (s) {
    /*if(s[0]) mALLOPt(M_CHECK_ACTION, (int)(s[0] - '0'));
      __malloc_check_init();*/
  }
  if (__malloc_initialize_hook != NULL)
    (*__malloc_initialize_hook)();
  state->__malloc_initialized = 1;
}

/*------------------------ Public wrappers. --------------------------------*/

void*
public_mALLOc(size_t bytes)
{
  struct malloc_arena* ar_ptr;
  void *victim;

  void * (*hook) (size_t, const void *) = __malloc_hook;
  if (hook != NULL)
    return (*hook)(bytes, RETURN_ADDRESS (0));
  
  // Can happen with dynamically loaded shared libs
  if (!state) return malloc_hook_ini (bytes, RETURN_ADDRESS (0));

  arena_get(ar_ptr, bytes + FOOTER_OVERHEAD);
  if (!ar_ptr)
    return 0;
  if (ar_ptr != &state->main_arena)
    bytes += FOOTER_OVERHEAD;
  victim = mspace_malloc(arena_to_mspace(ar_ptr), bytes);
  if (victim && ar_ptr != &state->main_arena)
    set_non_main_arena(victim, ar_ptr);
  (void)mutex_unlock(&ar_ptr->mutex);
  assert(!victim || is_mmapped(mem2chunk(victim)) ||
	 ar_ptr == arena_for_chunk(mem2chunk(victim)));
  return victim;
}
#ifdef libc_hidden_def
libc_hidden_def(public_mALLOc)
#endif

void
public_fREe(void* mem)
{
  struct malloc_arena* ar_ptr;
  mchunkptr p;                          /* chunk corresponding to mem */

  void (*hook) (void *, const void *) = __free_hook;
  if (hook != NULL) {
    (*hook)(mem, RETURN_ADDRESS (0));
    return;
  }

  if (mem == 0)                              /* free(0) has no effect */
    return;

  p = mem2chunk(mem);

  if (is_mmapped(p)) {                      /* release mmapped memory. */
    ar_ptr = arena_for_mmap_chunk(p);
    munmap_chunk(arena_to_mspace(ar_ptr), p);
    return;
  }

  ar_ptr = arena_for_chunk(p);
#if THREAD_STATS
  if(!mutex_trylock(&ar_ptr->mutex))
    ++(ar_ptr->stat_lock_direct);
  else {
    (void)mutex_lock(&ar_ptr->mutex);
    ++(ar_ptr->stat_lock_wait);
  }
#else
  (void)mutex_lock(&ar_ptr->mutex);
#endif
  mspace_free(arena_to_mspace(ar_ptr), mem);
  (void)mutex_unlock(&ar_ptr->mutex);
}
#ifdef libc_hidden_def
libc_hidden_def (public_fREe)
#endif

void*
public_rEALLOc(void* oldmem, size_t bytes)
{
  struct malloc_arena* ar_ptr;

  mchunkptr oldp;             /* chunk corresponding to oldmem */

  void* newp;             /* chunk to return */

  void * (*hook) (void *, size_t, const void *) = __realloc_hook;
  if (hook != NULL)
    return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));

#if REALLOC_ZERO_BYTES_FREES
  if (bytes == 0 && oldmem != NULL) { public_fREe(oldmem); return 0; }
#endif

  /* realloc of null is supposed to be same as malloc */
  if (oldmem == 0)
    return public_mALLOc(bytes);

  oldp    = mem2chunk(oldmem);
  if (is_mmapped(oldp))
    ar_ptr = arena_for_mmap_chunk(oldp); /* FIXME: use mmap_resize */
  else
    ar_ptr = arena_for_chunk(oldp);
#if THREAD_STATS
  if(!mutex_trylock(&ar_ptr->mutex))
    ++(ar_ptr->stat_lock_direct);
  else {
    (void)mutex_lock(&ar_ptr->mutex);
    ++(ar_ptr->stat_lock_wait);
  }
#else
  (void)mutex_lock(&ar_ptr->mutex);
#endif

#ifndef NO_THREADS
  /* As in malloc(), remember this arena for the next allocation. */
  tsd_setspecific(state->arena_key, (void *)ar_ptr);
#endif

  if (ar_ptr != &state->main_arena)
    bytes += FOOTER_OVERHEAD;
  newp = mspace_realloc(arena_to_mspace(ar_ptr), oldmem, bytes);

  if (newp && ar_ptr != &state->main_arena)
    set_non_main_arena(newp, ar_ptr);
  (void)mutex_unlock(&ar_ptr->mutex);

  assert(!newp || is_mmapped(mem2chunk(newp)) ||
	 ar_ptr == arena_for_chunk(mem2chunk(newp)));
  return newp;
}
#ifdef libc_hidden_def
libc_hidden_def (public_rEALLOc)
#endif

void*
public_mEMALIGn(size_t alignment, size_t bytes)
{
  struct malloc_arena* ar_ptr;
  void *p;

  void * (*hook) (size_t, size_t, const void *) = __memalign_hook;
  if (hook != NULL)
    return (*hook)(alignment, bytes, RETURN_ADDRESS (0));

  /* If need less alignment than we give anyway, just relay to malloc */
  if (alignment <= MALLOC_ALIGNMENT) return public_mALLOc(bytes);

  /* Otherwise, ensure that it is at least a minimum chunk size */
  if (alignment <  MIN_CHUNK_SIZE)
    alignment = MIN_CHUNK_SIZE;

  arena_get(ar_ptr,
	    bytes + FOOTER_OVERHEAD + alignment + MIN_CHUNK_SIZE);
  if(!ar_ptr)
    return 0;

  if (ar_ptr != &state->main_arena)
    bytes += FOOTER_OVERHEAD;
  p = mspace_memalign(arena_to_mspace(ar_ptr), alignment, bytes);

  if (p && ar_ptr != &state->main_arena)
    set_non_main_arena(p, ar_ptr);
  (void)mutex_unlock(&ar_ptr->mutex);

  assert(!p || is_mmapped(mem2chunk(p)) ||
	 ar_ptr == arena_for_chunk(mem2chunk(p)));
  return p;
}
#ifdef libc_hidden_def
libc_hidden_def (public_mEMALIGn)
#endif

void*
public_vALLOc(size_t bytes)
{
  struct malloc_arena* ar_ptr;
  void *p;

  if((state == NULL) || (state->__malloc_initialized < 0))
    ptmalloc_init ();
  arena_get(ar_ptr, bytes + FOOTER_OVERHEAD + MIN_CHUNK_SIZE);
  if(!ar_ptr)
    return 0;
  if (ar_ptr != &state->main_arena)
    bytes += FOOTER_OVERHEAD;
  p = mspace_memalign(arena_to_mspace(ar_ptr), 4096, bytes);

  if (p && ar_ptr != &state->main_arena)
    set_non_main_arena(p, ar_ptr);
  (void)mutex_unlock(&ar_ptr->mutex);
  return p;
}

int
public_pMEMALIGn (void **memptr, size_t alignment, size_t size)
{
  void *mem;

  /* Test whether the SIZE argument is valid.  It must be a power of
     two multiple of sizeof (void *).  */
  if (alignment % sizeof (void *) != 0
      || !my_powerof2 (alignment / sizeof (void *)) != 0
      || alignment == 0)
    return EINVAL;

  mem = public_mEMALIGn (alignment, size);

  if (mem != NULL) {
    *memptr = mem;
    return 0;
  }

  return ENOMEM;
}

void*
public_cALLOc(size_t n_elements, size_t elem_size)
{
  struct malloc_arena* ar_ptr;
  size_t bytes, sz;
  void* mem;
  void * (*hook) (size_t, const void *) = __malloc_hook;

  /* size_t is unsigned so the behavior on overflow is defined.  */
  bytes = n_elements * elem_size;
#define HALF_INTERNAL_SIZE_T \
  (((size_t) 1) << (8 * sizeof (size_t) / 2))
  if (__builtin_expect ((n_elements | elem_size) >= HALF_INTERNAL_SIZE_T, 0)) {
    if (elem_size != 0 && bytes / elem_size != n_elements) {
      /*MALLOC_FAILURE_ACTION;*/
      return 0;
    }
  }

  if (hook != NULL) {
    sz = bytes;
    mem = (*hook)(sz, RETURN_ADDRESS (0));
    if(mem == 0)
      return 0;
#ifdef HAVE_MEMCPY
    return memset(mem, 0, sz);
#else
    while(sz > 0) ((char*)mem)[--sz] = 0; /* rather inefficient */
    return mem;
#endif
  }

  arena_get(ar_ptr, bytes + FOOTER_OVERHEAD);
  if(!ar_ptr)
    return 0;

  if (ar_ptr != &state->main_arena)
    bytes += FOOTER_OVERHEAD;
  mem = mspace_calloc(arena_to_mspace(ar_ptr), bytes, 1);

  if (mem && ar_ptr != &state->main_arena)
    set_non_main_arena(mem, ar_ptr);
  (void)mutex_unlock(&ar_ptr->mutex);
  
  assert(!mem || is_mmapped(mem2chunk(mem)) ||
	 ar_ptr == arena_for_chunk(mem2chunk(mem)));

  return mem;
}

void**
public_iCALLOc(size_t n, size_t elem_size, void* chunks[])
{
  struct malloc_arena* ar_ptr;
  void** m;

  arena_get(ar_ptr, n*(elem_size + FOOTER_OVERHEAD));
  if (!ar_ptr)
    return 0;

  if (ar_ptr != &state->main_arena)
    elem_size += FOOTER_OVERHEAD;
  m = mspace_independent_calloc(arena_to_mspace(ar_ptr), n, elem_size, chunks);

  if (m && ar_ptr != &state->main_arena) {
    while (n > 0)
      set_non_main_arena(m[--n], ar_ptr);
  }
  (void)mutex_unlock(&ar_ptr->mutex);
  return m;
}

void**
public_iCOMALLOc(size_t n, size_t sizes[], void* chunks[])
{
  struct malloc_arena* ar_ptr;
  size_t* m_sizes;
  size_t i;
  void** m;

  arena_get(ar_ptr, n*sizeof(size_t));
  if (!ar_ptr)
    return 0;

  if (ar_ptr != &state->main_arena) {
    /* Temporary m_sizes[] array is ugly but it would be surprising to
       change the original sizes[]... */
    m_sizes = mspace_malloc(arena_to_mspace(ar_ptr), n*sizeof(size_t));
    if (!m_sizes) {
      (void)mutex_unlock(&ar_ptr->mutex);
      return 0;
    }
    for (i=0; i<n; ++i)
      m_sizes[i] = sizes[i] + FOOTER_OVERHEAD;
    if (!chunks) {
      chunks = mspace_malloc(arena_to_mspace(ar_ptr),
			     n*sizeof(void*)+FOOTER_OVERHEAD);
      if (!chunks) {
	mspace_free(arena_to_mspace(ar_ptr), m_sizes);
	(void)mutex_unlock(&ar_ptr->mutex);
	return 0;
      }
      set_non_main_arena(chunks, ar_ptr);
    }
  } else
    m_sizes = sizes;

  m = mspace_independent_comalloc(arena_to_mspace(ar_ptr), n, m_sizes, chunks);

  if (ar_ptr != &state->main_arena) {
    mspace_free(arena_to_mspace(ar_ptr), m_sizes);
    if (m)
      for (i=0; i<n; ++i)
	set_non_main_arena(m[i], ar_ptr);
  }
  (void)mutex_unlock(&ar_ptr->mutex);
  return m;
}

#if 0 && !defined _LIBC

void
public_cFREe(void* m)
{
  public_fREe(m);
}

#endif /* _LIBC */

int
public_mTRIm(size_t s)
{
  int result;

  (void)mutex_lock(&state->main_arena.mutex);
  result = mspace_trim(arena_to_mspace(&state->main_arena), s);
  (void)mutex_unlock(&state->main_arena.mutex);
  return result;
}

size_t
public_mUSABLe(void* mem)
{
  if (mem != 0) {
    mchunkptr p = mem2chunk(mem);
    if (cinuse(p))
      return chunksize(p) - overhead_for(p);
  }
  return 0;
}

int
public_mALLOPt(int p, int v)
{
  int result;
  result = mspace_mallopt(p, v);
  return result;
}

void
public_mSTATs(void)
{
  int i;
  struct malloc_arena* ar_ptr;
  /*unsigned long in_use_b, system_b, avail_b;*/
#if THREAD_STATS
  long stat_lock_direct = 0, stat_lock_loop = 0, stat_lock_wait = 0;
#endif

  if((state == NULL) || (state->__malloc_initialized < 0))
    ptmalloc_init ();
  for (i=0, ar_ptr = &state->main_arena;; ++i) {
    struct malloc_state* msp = arena_to_mspace(ar_ptr);

    fprintf(stderr, "Arena %d:\n", i);
    mspace_malloc_stats(msp);
#if THREAD_STATS
    stat_lock_direct += ar_ptr->stat_lock_direct;
    stat_lock_loop += ar_ptr->stat_lock_loop;
    stat_lock_wait += ar_ptr->stat_lock_wait;
#endif
    if (MALLOC_DEBUG > 1) {
      struct malloc_segment* mseg = &msp->seg;
      while (mseg) {
	fprintf(stderr, " seg %p-%p\n", mseg->base,
		(mseg->base + mseg->size));
	mseg = mseg->next;
      }
    }
    ar_ptr = ar_ptr->next;
    if (ar_ptr == &state->main_arena)
      break;
  }
#if THREAD_STATS
  fprintf(stderr, "locked directly  = %10ld\n", stat_lock_direct);
  fprintf(stderr, "locked in loop   = %10ld\n", stat_lock_loop);
  fprintf(stderr, "locked waiting   = %10ld\n", stat_lock_wait);
  fprintf(stderr, "locked total     = %10ld\n",
          stat_lock_direct + stat_lock_loop + stat_lock_wait);
  if (main_arena.stat_starter > 0)
    fprintf(stderr, "starter hooks    = %10ld\n", main_arena.stat_starter);
#endif
}

/*
 * Local variables:
 * c-basic-offset: 2
 * End:
 */
