Blame src/atomic_ops_malloc.c

Packit fe9d6e
/*
Packit fe9d6e
 * Copyright (c) 2005 Hewlett-Packard Development Company, L.P.
Packit fe9d6e
 *
Packit fe9d6e
 * This file may be redistributed and/or modified under the
Packit fe9d6e
 * terms of the GNU General Public License as published by the Free Software
Packit fe9d6e
 * Foundation; either version 2, or (at your option) any later version.
Packit fe9d6e
 *
Packit fe9d6e
 * It is distributed in the hope that it will be useful, but WITHOUT ANY
Packit fe9d6e
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
Packit fe9d6e
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License in the
Packit fe9d6e
 * file COPYING for more details.
Packit fe9d6e
 */
Packit fe9d6e
Packit fe9d6e
#if defined(HAVE_CONFIG_H)
Packit fe9d6e
# include "config.h"
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
#ifdef DONT_USE_MMAP /* for testing */
Packit fe9d6e
# undef HAVE_MMAP
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
#define AO_REQUIRE_CAS
Packit fe9d6e
#include "atomic_ops_malloc.h"
Packit fe9d6e
Packit fe9d6e
#include <string.h>     /* for ffs, which is assumed reentrant. */
Packit fe9d6e
#include <stdlib.h>
Packit fe9d6e
#include <assert.h>
Packit fe9d6e
Packit fe9d6e
#ifdef AO_TRACE_MALLOC
Packit fe9d6e
# include <stdio.h>
Packit fe9d6e
# include <pthread.h>
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
#if defined(AO_ADDRESS_SANITIZER) && !defined(AO_NO_MALLOC_POISON)
Packit fe9d6e
  /* #include "sanitizer/asan_interface.h" */
Packit fe9d6e
  void __asan_poison_memory_region(void *, size_t);
Packit fe9d6e
  void __asan_unpoison_memory_region(void *, size_t);
Packit fe9d6e
# define ASAN_POISON_MEMORY_REGION(addr, size) \
Packit fe9d6e
                __asan_poison_memory_region(addr, size)
Packit fe9d6e
# define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
Packit fe9d6e
                __asan_unpoison_memory_region(addr, size)
Packit fe9d6e
#else
Packit fe9d6e
# define ASAN_POISON_MEMORY_REGION(addr, size) (void)0
Packit fe9d6e
# define ASAN_UNPOISON_MEMORY_REGION(addr, size) (void)0
Packit fe9d6e
#endif /* !AO_ADDRESS_SANITIZER */
Packit fe9d6e
Packit fe9d6e
#if (defined(_WIN32_WCE) || defined(__MINGW32CE__)) && !defined(AO_HAVE_abort)
Packit fe9d6e
# define abort() _exit(-1) /* there is no abort() in WinCE */
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
/*
Packit fe9d6e
 * We round up each allocation request to the next power of two
Packit fe9d6e
 * minus one word.
Packit fe9d6e
 * We keep one stack of free objects for each size.  Each object
Packit fe9d6e
 * has an initial word (offset -sizeof(AO_t) from the visible pointer)
Packit fe9d6e
 * which contains either
Packit fe9d6e
 *      The binary log of the object size in bytes (small objects)
Packit fe9d6e
 *      The object size (a multiple of CHUNK_SIZE) for large objects.
Packit fe9d6e
 * The second case only arises if mmap-based allocation is supported.
Packit fe9d6e
 * We align the user-visible part of each object on a GRANULARITY
Packit fe9d6e
 * byte boundary.  That means that the actual (hidden) start of
Packit fe9d6e
 * the object starts a word before this boundary.
Packit fe9d6e
 */
Packit fe9d6e
Packit fe9d6e
#ifndef LOG_MAX_SIZE
Packit fe9d6e
# define LOG_MAX_SIZE 16
Packit fe9d6e
        /* We assume that 2**LOG_MAX_SIZE is a multiple of page size. */
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
#ifndef ALIGNMENT
Packit fe9d6e
# define ALIGNMENT 16
Packit fe9d6e
        /* Assumed to be at least sizeof(AO_t).         */
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
#define CHUNK_SIZE (1 << LOG_MAX_SIZE)
Packit fe9d6e
Packit fe9d6e
#ifndef AO_INITIAL_HEAP_SIZE
Packit fe9d6e
#  define AO_INITIAL_HEAP_SIZE (2*(LOG_MAX_SIZE+1)*CHUNK_SIZE)
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
char AO_initial_heap[AO_INITIAL_HEAP_SIZE];
Packit fe9d6e
Packit fe9d6e
static volatile AO_t initial_heap_ptr = (AO_t)AO_initial_heap;
Packit fe9d6e
Packit fe9d6e
#if defined(HAVE_MMAP)
Packit fe9d6e
Packit fe9d6e
#include <sys/types.h>
Packit fe9d6e
#include <sys/stat.h>
Packit fe9d6e
#include <fcntl.h>
Packit fe9d6e
#include <sys/mman.h>
Packit fe9d6e
Packit fe9d6e
#if defined(MAP_ANONYMOUS) || defined(MAP_ANON)
Packit fe9d6e
# define USE_MMAP_ANON
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
#ifdef USE_MMAP_FIXED
Packit fe9d6e
# define GC_MMAP_FLAGS (MAP_FIXED | MAP_PRIVATE)
Packit fe9d6e
        /* Seems to yield better performance on Solaris 2, but can      */
Packit fe9d6e
        /* be unreliable if something is already mapped at the address. */
Packit fe9d6e
#else
Packit fe9d6e
# define GC_MMAP_FLAGS MAP_PRIVATE
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
#ifdef USE_MMAP_ANON
Packit fe9d6e
# if defined(CPPCHECK)
Packit fe9d6e
#   define OPT_MAP_ANON 0x20 /* taken from linux */
Packit fe9d6e
# elif defined(MAP_ANONYMOUS)
Packit fe9d6e
#   define OPT_MAP_ANON MAP_ANONYMOUS
Packit fe9d6e
# else
Packit fe9d6e
#   define OPT_MAP_ANON MAP_ANON
Packit fe9d6e
# endif
Packit fe9d6e
#else
Packit fe9d6e
# include <unistd.h> /* for close() */
Packit fe9d6e
# define OPT_MAP_ANON 0
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
static volatile AO_t mmap_enabled = 0;
Packit fe9d6e
Packit fe9d6e
void
Packit fe9d6e
AO_malloc_enable_mmap(void)
Packit fe9d6e
{
Packit fe9d6e
# if defined(__sun)
Packit fe9d6e
    AO_store_release(&mmap_enabled, 1);
Packit fe9d6e
            /* Workaround for Sun CC */
Packit fe9d6e
# else
Packit fe9d6e
    AO_store(&mmap_enabled, 1);
Packit fe9d6e
# endif
Packit fe9d6e
}
Packit fe9d6e
Packit fe9d6e
static char *get_mmaped(size_t sz)
Packit fe9d6e
{
Packit fe9d6e
  char * result;
Packit fe9d6e
# ifdef USE_MMAP_ANON
Packit fe9d6e
#   define zero_fd -1
Packit fe9d6e
# else
Packit fe9d6e
    int zero_fd;
Packit fe9d6e
# endif
Packit fe9d6e
Packit fe9d6e
  assert(!(sz & (CHUNK_SIZE - 1)));
Packit fe9d6e
  if (!mmap_enabled)
Packit fe9d6e
    return 0;
Packit fe9d6e
Packit fe9d6e
# ifndef USE_MMAP_ANON
Packit fe9d6e
    zero_fd = open("/dev/zero", O_RDONLY);
Packit fe9d6e
    if (zero_fd == -1)
Packit fe9d6e
      return 0;
Packit fe9d6e
# endif
Packit fe9d6e
  result = mmap(0, sz, PROT_READ | PROT_WRITE,
Packit fe9d6e
                GC_MMAP_FLAGS | OPT_MAP_ANON, zero_fd, 0/* offset */);
Packit fe9d6e
# ifndef USE_MMAP_ANON
Packit fe9d6e
    close(zero_fd);
Packit fe9d6e
# endif
Packit fe9d6e
  if (AO_EXPECT_FALSE(result == MAP_FAILED))
Packit fe9d6e
    result = NULL;
Packit fe9d6e
  return result;
Packit fe9d6e
}
Packit fe9d6e
Packit fe9d6e
#ifndef SIZE_MAX
Packit fe9d6e
# include <limits.h>
Packit fe9d6e
#endif
Packit fe9d6e
#if defined(SIZE_MAX) && !defined(CPPCHECK)
Packit fe9d6e
# define AO_SIZE_MAX ((size_t)SIZE_MAX)
Packit fe9d6e
            /* Extra cast to workaround some buggy SIZE_MAX definitions. */
Packit fe9d6e
#else
Packit fe9d6e
# define AO_SIZE_MAX (~(size_t)0)
Packit fe9d6e
#endif
Packit fe9d6e
Packit fe9d6e
/* Saturated addition of size_t values.  Used to avoid value wrap       */
Packit fe9d6e
/* around on overflow.  The arguments should have no side effects.      */
Packit fe9d6e
#define SIZET_SAT_ADD(a, b) \
Packit fe9d6e
    (AO_EXPECT_FALSE((a) >= AO_SIZE_MAX - (b)) ? AO_SIZE_MAX : (a) + (b))
Packit fe9d6e
Packit fe9d6e
/* Allocate an object of size (incl. header) of size > CHUNK_SIZE.      */
Packit fe9d6e
/* sz includes space for an AO_t-sized header.                          */
Packit fe9d6e
static char *
Packit fe9d6e
AO_malloc_large(size_t sz)
Packit fe9d6e
{
Packit fe9d6e
  char *result;
Packit fe9d6e
  /* The header will force us to waste ALIGNMENT bytes, incl. header.   */
Packit fe9d6e
  /* Round to multiple of CHUNK_SIZE.                                   */
Packit fe9d6e
  sz = SIZET_SAT_ADD(sz, ALIGNMENT + CHUNK_SIZE - 1) & ~(CHUNK_SIZE - 1);
Packit fe9d6e
  assert(sz > LOG_MAX_SIZE);
Packit fe9d6e
  result = get_mmaped(sz);
Packit fe9d6e
  if (AO_EXPECT_FALSE(NULL == result))
Packit fe9d6e
    return NULL;
Packit fe9d6e
  result += ALIGNMENT;
Packit fe9d6e
  ((AO_t *)result)[-1] = (AO_t)sz;
Packit fe9d6e
  return result;
Packit fe9d6e
}
Packit fe9d6e
Packit fe9d6e
static void
Packit fe9d6e
AO_free_large(char * p)
Packit fe9d6e
{
Packit fe9d6e
  AO_t sz = ((AO_t *)p)[-1];
Packit fe9d6e
  if (munmap(p - ALIGNMENT, (size_t)sz) != 0)
Packit fe9d6e
    abort();  /* Programmer error.  Not really async-signal-safe, but ... */
Packit fe9d6e
}
Packit fe9d6e
Packit fe9d6e
Packit fe9d6e
#else /*  No MMAP */
Packit fe9d6e
Packit fe9d6e
void
Packit fe9d6e
AO_malloc_enable_mmap(void)
Packit fe9d6e
{
Packit fe9d6e
}
Packit fe9d6e
Packit fe9d6e
#define get_mmaped(sz) ((char*)0)
Packit fe9d6e
#define AO_malloc_large(sz) ((char*)0)
Packit fe9d6e
#define AO_free_large(p) abort()
Packit fe9d6e
                /* Programmer error.  Not really async-signal-safe, but ... */
Packit fe9d6e
Packit fe9d6e
#endif /* No MMAP */
Packit fe9d6e
Packit fe9d6e
static char *
Packit fe9d6e
get_chunk(void)
Packit fe9d6e
{
Packit fe9d6e
  char *my_chunk_ptr;
Packit fe9d6e
Packit fe9d6e
  for (;;) {
Packit fe9d6e
    char *initial_ptr = (char *)AO_load(&initial_heap_ptr);
Packit fe9d6e
Packit fe9d6e
    my_chunk_ptr = (char *)(((AO_t)initial_ptr + (ALIGNMENT - 1))
Packit fe9d6e
                            & ~(ALIGNMENT - 1));
Packit fe9d6e
    if (initial_ptr != my_chunk_ptr)
Packit fe9d6e
      {
Packit fe9d6e
        /* Align correctly.  If this fails, someone else did it for us. */
Packit fe9d6e
        (void)AO_compare_and_swap_acquire(&initial_heap_ptr,
Packit fe9d6e
                                    (AO_t)initial_ptr, (AO_t)my_chunk_ptr);
Packit fe9d6e
      }
Packit fe9d6e
Packit fe9d6e
    if (AO_EXPECT_FALSE(my_chunk_ptr - AO_initial_heap
Packit fe9d6e
                        > AO_INITIAL_HEAP_SIZE - CHUNK_SIZE))
Packit fe9d6e
      break;
Packit fe9d6e
    if (AO_compare_and_swap(&initial_heap_ptr, (AO_t)my_chunk_ptr,
Packit fe9d6e
                            (AO_t)(my_chunk_ptr + CHUNK_SIZE))) {
Packit fe9d6e
      return my_chunk_ptr;
Packit fe9d6e
    }
Packit fe9d6e
  }
Packit fe9d6e
Packit fe9d6e
  /* We failed.  The initial heap is used up.   */
Packit fe9d6e
  my_chunk_ptr = get_mmaped(CHUNK_SIZE);
Packit fe9d6e
  assert (!((AO_t)my_chunk_ptr & (ALIGNMENT-1)));
Packit fe9d6e
  return my_chunk_ptr;
Packit fe9d6e
}
Packit fe9d6e
Packit fe9d6e
/* Object free lists.  Ith entry corresponds to objects         */
Packit fe9d6e
/* of total size 2**i bytes.                                    */
Packit fe9d6e
AO_stack_t AO_free_list[LOG_MAX_SIZE+1];
Packit fe9d6e
Packit fe9d6e
/* Break up the chunk, and add it to the object free list for   */
Packit fe9d6e
/* the given size.  We have exclusive access to chunk.          */
Packit fe9d6e
static void add_chunk_as(void * chunk, unsigned log_sz)
Packit fe9d6e
{
Packit fe9d6e
  size_t ofs, limit;
Packit fe9d6e
  size_t sz = (size_t)1 << log_sz;
Packit fe9d6e
Packit fe9d6e
  assert (CHUNK_SIZE >= sz);
Packit fe9d6e
  limit = (size_t)CHUNK_SIZE - sz;
Packit fe9d6e
  for (ofs = ALIGNMENT - sizeof(AO_t); ofs <= limit; ofs += sz) {
Packit fe9d6e
    ASAN_POISON_MEMORY_REGION((char *)chunk + ofs + sizeof(AO_t),
Packit fe9d6e
                              sz - sizeof(AO_t));
Packit fe9d6e
    AO_stack_push(&AO_free_list[log_sz], (AO_t *)((char *)chunk + ofs));
Packit fe9d6e
  }
Packit fe9d6e
}
Packit fe9d6e
Packit fe9d6e
static const unsigned char msbs[16] = {
Packit fe9d6e
  0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4
Packit fe9d6e
};
Packit fe9d6e
Packit fe9d6e
/* Return the position of the most significant set bit in the   */
Packit fe9d6e
/* argument.                                                    */
Packit fe9d6e
/* We follow the conventions of ffs(), i.e. the least           */
Packit fe9d6e
/* significant bit is number one.                               */
Packit fe9d6e
static unsigned msb(size_t s)
Packit fe9d6e
{
Packit fe9d6e
  unsigned result = 0;
Packit fe9d6e
  if ((s & 0xff) != s) {
Packit fe9d6e
#   if (__SIZEOF_SIZE_T__ == 8) && !defined(CPPCHECK)
Packit fe9d6e
      unsigned v = (unsigned)(s >> 32);
Packit fe9d6e
      if (AO_EXPECT_FALSE(v != 0))
Packit fe9d6e
        {
Packit fe9d6e
          s = v;
Packit fe9d6e
          result += 32;
Packit fe9d6e
        }
Packit fe9d6e
#   elif __SIZEOF_SIZE_T__ == 4
Packit fe9d6e
      /* No op. */
Packit fe9d6e
#   else
Packit fe9d6e
      unsigned v;
Packit fe9d6e
      /* The following is a tricky code ought to be equivalent to       */
Packit fe9d6e
      /* "(v = s >> 32) != 0" but suppresses warnings on 32-bit arch's. */
Packit fe9d6e
#     define SIZEOF_SIZE_T_GT_4 (sizeof(size_t) > 4)
Packit fe9d6e
      if (SIZEOF_SIZE_T_GT_4
Packit fe9d6e
          && (v = (unsigned)(s >> (SIZEOF_SIZE_T_GT_4 ? 32 : 0))) != 0)
Packit fe9d6e
        {
Packit fe9d6e
          s = v;
Packit fe9d6e
          result += 32;
Packit fe9d6e
        }
Packit fe9d6e
#   endif /* !defined(__SIZEOF_SIZE_T__) */
Packit fe9d6e
    if (AO_EXPECT_FALSE((s >> 16) != 0))
Packit fe9d6e
      {
Packit fe9d6e
        s >>= 16;
Packit fe9d6e
        result += 16;
Packit fe9d6e
      }
Packit fe9d6e
    if ((s >> 8) != 0)
Packit fe9d6e
      {
Packit fe9d6e
        s >>= 8;
Packit fe9d6e
        result += 8;
Packit fe9d6e
      }
Packit fe9d6e
  }
Packit fe9d6e
  if (s > 15)
Packit fe9d6e
    {
Packit fe9d6e
      s >>= 4;
Packit fe9d6e
      result += 4;
Packit fe9d6e
    }
Packit fe9d6e
  result += msbs[s];
Packit fe9d6e
  return result;
Packit fe9d6e
}
Packit fe9d6e
Packit fe9d6e
void *
Packit fe9d6e
AO_malloc(size_t sz)
Packit fe9d6e
{
Packit fe9d6e
  AO_t *result;
Packit fe9d6e
  unsigned log_sz;
Packit fe9d6e
Packit fe9d6e
  if (AO_EXPECT_FALSE(sz > CHUNK_SIZE - sizeof(AO_t)))
Packit fe9d6e
    return AO_malloc_large(sz);
Packit fe9d6e
  log_sz = msb(sz + (sizeof(AO_t) - 1));
Packit fe9d6e
  assert(log_sz <= LOG_MAX_SIZE);
Packit fe9d6e
  assert(((size_t)1 << log_sz) >= sz + sizeof(AO_t));
Packit fe9d6e
  result = AO_stack_pop(AO_free_list+log_sz);
Packit fe9d6e
  while (AO_EXPECT_FALSE(NULL == result)) {
Packit fe9d6e
    void * chunk = get_chunk();
Packit fe9d6e
Packit fe9d6e
    if (AO_EXPECT_FALSE(NULL == chunk))
Packit fe9d6e
      return NULL;
Packit fe9d6e
    add_chunk_as(chunk, log_sz);
Packit fe9d6e
    result = AO_stack_pop(AO_free_list+log_sz);
Packit fe9d6e
  }
Packit fe9d6e
  *result = log_sz;
Packit fe9d6e
# ifdef AO_TRACE_MALLOC
Packit fe9d6e
    fprintf(stderr, "%p: AO_malloc(%lu) = %p\n",
Packit fe9d6e
            (void *)pthread_self(), (unsigned long)sz, (void *)(result + 1));
Packit fe9d6e
# endif
Packit fe9d6e
  ASAN_UNPOISON_MEMORY_REGION(result + 1, sz);
Packit fe9d6e
  return result + 1;
Packit fe9d6e
}
Packit fe9d6e
Packit fe9d6e
void
Packit fe9d6e
AO_free(void *p)
Packit fe9d6e
{
Packit fe9d6e
  AO_t *base;
Packit fe9d6e
  int log_sz;
Packit fe9d6e
Packit fe9d6e
  if (AO_EXPECT_FALSE(NULL == p))
Packit fe9d6e
    return;
Packit fe9d6e
Packit fe9d6e
  base = (AO_t *)p - 1;
Packit fe9d6e
  log_sz = (int)(*base);
Packit fe9d6e
# ifdef AO_TRACE_MALLOC
Packit fe9d6e
    fprintf(stderr, "%p: AO_free(%p sz:%lu)\n", (void *)pthread_self(), p,
Packit fe9d6e
            log_sz > LOG_MAX_SIZE ? (unsigned)log_sz : 1UL << log_sz);
Packit fe9d6e
# endif
Packit fe9d6e
  if (AO_EXPECT_FALSE(log_sz > LOG_MAX_SIZE)) {
Packit fe9d6e
    AO_free_large(p);
Packit fe9d6e
  } else {
Packit fe9d6e
    ASAN_POISON_MEMORY_REGION(base + 1, ((size_t)1 << log_sz) - sizeof(AO_t));
Packit fe9d6e
    AO_stack_push(AO_free_list + log_sz, base);
Packit fe9d6e
  }
Packit fe9d6e
}