#include "namespace.h"
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/queue.h>
#include <sys/ktrace.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stddef.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <machine/atomic.h>
#include "un-namespace.h"
#include "libc_private.h"
#include "spinlock.h"
void __free(void *);
void *__malloc(size_t);
void *__calloc(size_t, size_t);
void *__realloc(void *, size_t);
void *__aligned_alloc(size_t, size_t);
size_t __malloc_usable_size(const void *ptr);
int __posix_memalign(void **, size_t, size_t);
typedef struct bigalloc {
struct bigalloc *next;
void *base;
u_long active;
u_long bytes;
} *bigalloc_t;
#define MAX_SLAB_PAGEALIGN (2 * PAGE_SIZE)
#define ZALLOC_ZONE_LIMIT (16 * 1024)
#define ZALLOC_ZONE_SIZE (64 * 1024)
#define ZALLOC_SLAB_MAGIC 0x736c6162
#if ZALLOC_ZONE_LIMIT == 16384
#define NZONES 72
#elif ZALLOC_ZONE_LIMIT == 32768
#define NZONES 80
#else
#error "I couldn't figure out NZONES"
#endif
typedef struct slchunk {
struct slchunk *c_Next;
} *slchunk_t;
struct slglobaldata;
typedef struct slzone {
int32_t z_Magic;
int z_NFree;
struct slzone *z_Next;
int z_NMax;
char *z_BasePtr;
int z_UIndex;
int z_UEndIndex;
int z_ChunkSize;
int z_FirstFreePg;
int z_ZoneIndex;
int z_Flags;
struct slchunk *z_PageAry[ZALLOC_ZONE_SIZE / PAGE_SIZE];
} *slzone_t;
typedef struct slglobaldata {
spinlock_t Spinlock;
slzone_t ZoneAry[NZONES];
} *slglobaldata_t;
#define SLZF_UNOTZEROD 0x0001
#define FASTSLABREALLOC 0x02
#define MIN_CHUNK_SIZE 8
#define MIN_CHUNK_MASK (MIN_CHUNK_SIZE - 1)
#define IN_SAME_PAGE_MASK (~(intptr_t)PAGE_MASK | MIN_CHUNK_MASK)
#define BIGHSHIFT 10
#define BIGHSIZE (1 << BIGHSHIFT)
#define BIGHMASK (BIGHSIZE - 1)
#define BIGXSIZE (BIGHSIZE / 16)
#define BIGXMASK (BIGXSIZE - 1)
#define BIGCACHE 16
#define BIGCACHE_MASK (BIGCACHE - 1)
#define BIGCACHE_LIMIT (1024 * 1024)
#define BIGCACHE_EXCESS (16 * 1024 * 1024)
#define CACHE_CHUNKS 32
#define SAFLAG_ZERO 0x0001
#define SAFLAG_PASSIVE 0x0002
#define SAFLAG_MAGS 0x0004
#define arysize(ary) (sizeof(ary)/sizeof((ary)[0]))
#define MASSERT(exp) \
do { if (__predict_false(!(exp))) \
_mpanic("assertion: %s in %s", \
#exp, __func__); \
} while (0)
#define MASSERT_WTHUNLK(exp, unlk) \
do { if (__predict_false(!(exp))) { \
unlk; \
_mpanic("assertion: %s in %s", \
#exp, __func__); \
} \
} while (0)
#define M_MAX_ROUNDS (512 - 3)
#define M_MIN_ROUNDS 16
#define M_ZONE_INIT_ROUNDS 64
#define M_ZONE_HYSTERESIS 32
struct magazine {
SLIST_ENTRY(magazine) nextmagazine;
int flags;
int capacity;
int rounds;
int unused01;
void *objects[M_MAX_ROUNDS];
};
SLIST_HEAD(magazinelist, magazine);
static spinlock_t zone_mag_lock;
static spinlock_t depot_spinlock;
static struct magazine zone_magazine = {
.flags = 0,
.capacity = M_ZONE_INIT_ROUNDS,
.rounds = 0,
};
#define MAGAZINE_FULL(mp) (mp->rounds == mp->capacity)
#define MAGAZINE_NOTFULL(mp) (mp->rounds < mp->capacity)
#define MAGAZINE_EMPTY(mp) (mp->rounds == 0)
#define MAGAZINE_NOTEMPTY(mp) (mp->rounds != 0)
typedef struct magazine_pair {
struct magazine *loaded;
struct magazine *prev;
} magazine_pair;
typedef struct magazine_depot {
struct magazinelist full;
struct magazinelist empty;
spinlock_t lock;
} magazine_depot;
typedef struct thr_mags {
magazine_pair mags[NZONES];
struct magazine *newmag;
int init;
} thr_mags;
static __thread thr_mags thread_mags TLS_ATTRIBUTE;
static pthread_key_t thread_mags_key;
static pthread_once_t thread_mags_once = PTHREAD_ONCE_INIT;
static magazine_depot depots[NZONES];
static const int ZoneSize = ZALLOC_ZONE_SIZE;
static const int ZoneLimit = ZALLOC_ZONE_LIMIT;
static const int ZonePageCount = ZALLOC_ZONE_SIZE / PAGE_SIZE;
static const int ZoneMask = ZALLOC_ZONE_SIZE - 1;
static int opt_madvise = 0;
static int opt_utrace = 0;
static int g_malloc_flags = 0;
static struct slglobaldata SLGlobalData;
static bigalloc_t bigalloc_array[BIGHSIZE];
static spinlock_t bigspin_array[BIGXSIZE];
static volatile void *bigcache_array[BIGCACHE];
static volatile size_t bigcache_size_array[BIGCACHE];
static volatile int bigcache_index;
static int malloc_panic;
static size_t excess_alloc;
static void *_slaballoc(size_t size, int flags);
static void *_slabrealloc(void *ptr, size_t size);
static size_t _slabusablesize(const void *ptr);
static void _slabfree(void *ptr, int, bigalloc_t *);
static int _slabmemalign(void **memptr, size_t alignment, size_t size);
static void *_vmem_alloc(size_t bytes, size_t align, int flags);
static void _vmem_free(void *ptr, size_t bytes);
static void *magazine_alloc(struct magazine *);
static int magazine_free(struct magazine *, void *);
static void *mtmagazine_alloc(int zi, int flags);
static int mtmagazine_free(int zi, void *);
static void mtmagazine_init(void);
static void mtmagazine_destructor(void *);
static slzone_t zone_alloc(int flags);
static void zone_free(void *z);
static void _mpanic(const char *ctl, ...) __printflike(1, 2);
static void malloc_init(void) __constructor(101);
struct nmalloc_utrace {
void *p;
size_t s;
void *r;
};
#define UTRACE(a, b, c) \
if (opt_utrace) { \
struct nmalloc_utrace ut = { \
.p = (a), \
.s = (b), \
.r = (c) \
}; \
utrace(&ut, sizeof(ut)); \
}
static void
malloc_init(void)
{
const char *p = NULL;
if (issetugid() == 0)
p = getenv("MALLOC_OPTIONS");
for (; p != NULL && *p != '\0'; p++) {
switch(*p) {
case 'u': opt_utrace = 0; break;
case 'U': opt_utrace = 1; break;
case 'h': opt_madvise = 0; break;
case 'H': opt_madvise = 1; break;
case 'z': g_malloc_flags = 0; break;
case 'Z': g_malloc_flags = SAFLAG_ZERO; break;
default:
break;
}
}
UTRACE((void *) -1, 0, NULL);
}
void
_nmalloc_thr_init(void)
{
thr_mags *tp;
tp = &thread_mags;
tp->init = -1;
_pthread_once(&thread_mags_once, mtmagazine_init);
_pthread_setspecific(thread_mags_key, tp);
tp->init = 1;
}
void
_nmalloc_thr_prepfork(void)
{
if (__isthreaded) {
_SPINLOCK(&zone_mag_lock);
_SPINLOCK(&depot_spinlock);
}
}
void
_nmalloc_thr_parentfork(void)
{
if (__isthreaded) {
_SPINUNLOCK(&depot_spinlock);
_SPINUNLOCK(&zone_mag_lock);
}
}
void
_nmalloc_thr_childfork(void)
{
if (__isthreaded) {
_SPINUNLOCK(&depot_spinlock);
_SPINUNLOCK(&zone_mag_lock);
}
}
static __inline void
nmalloc_sigblockall(void)
{
sigblockall();
}
static __inline void
nmalloc_sigunblockall(void)
{
sigunblockall();
}
static __inline void
slgd_lock(slglobaldata_t slgd)
{
if (__isthreaded)
_SPINLOCK(&slgd->Spinlock);
}
static __inline void
slgd_unlock(slglobaldata_t slgd)
{
if (__isthreaded)
_SPINUNLOCK(&slgd->Spinlock);
}
static __inline void
depot_lock(magazine_depot *dp __unused)
{
if (__isthreaded)
_SPINLOCK(&depot_spinlock);
}
static __inline void
depot_unlock(magazine_depot *dp __unused)
{
if (__isthreaded)
_SPINUNLOCK(&depot_spinlock);
}
static __inline void
zone_magazine_lock(void)
{
if (__isthreaded)
_SPINLOCK(&zone_mag_lock);
}
static __inline void
zone_magazine_unlock(void)
{
if (__isthreaded)
_SPINUNLOCK(&zone_mag_lock);
}
static __inline void
swap_mags(magazine_pair *mp)
{
struct magazine *tmp;
tmp = mp->loaded;
mp->loaded = mp->prev;
mp->prev = tmp;
}
static __inline int
_bigalloc_hash(const void *ptr)
{
int hv;
hv = ((int)(intptr_t)ptr >> PAGE_SHIFT) ^
((int)(intptr_t)ptr >> (PAGE_SHIFT + BIGHSHIFT));
return(hv);
}
static __inline bigalloc_t *
bigalloc_lock(void *ptr)
{
int hv = _bigalloc_hash(ptr);
bigalloc_t *bigp;
bigp = &bigalloc_array[hv & BIGHMASK];
if (__isthreaded)
_SPINLOCK(&bigspin_array[hv & BIGXMASK]);
return(bigp);
}
static __inline bigalloc_t *
bigalloc_check_and_lock(const void *ptr)
{
int hv = _bigalloc_hash(ptr);
bigalloc_t *bigp;
bigp = &bigalloc_array[hv & BIGHMASK];
if (*bigp == NULL)
return(NULL);
if (__isthreaded) {
_SPINLOCK(&bigspin_array[hv & BIGXMASK]);
}
return(bigp);
}
static __inline void
bigalloc_unlock(const void *ptr)
{
int hv;
if (__isthreaded) {
hv = _bigalloc_hash(ptr);
_SPINUNLOCK(&bigspin_array[hv & BIGXMASK]);
}
}
static __inline
bigalloc_t
bigcache_find_alloc(size_t bytes)
{
bigalloc_t big = NULL;
size_t test;
int i;
for (i = 0; i < BIGCACHE; ++i) {
test = bigcache_size_array[i];
if (bytes <= test) {
bigcache_size_array[i] = 0;
big = atomic_swap_ptr(&bigcache_array[i], NULL);
break;
}
}
return big;
}
static __inline
bigalloc_t
bigcache_find_free(bigalloc_t big)
{
int i;
int j;
int b;
b = ++bigcache_index;
for (i = 0; i < BIGCACHE; ++i) {
j = (b + i) & BIGCACHE_MASK;
if (bigcache_size_array[j] < big->bytes) {
bigcache_size_array[j] = big->bytes;
big = atomic_swap_ptr(&bigcache_array[j], big);
break;
}
}
return big;
}
static __inline
void
handle_excess_big(void)
{
int i;
bigalloc_t big;
bigalloc_t *bigp;
if (excess_alloc <= BIGCACHE_EXCESS)
return;
for (i = 0; i < BIGHSIZE; ++i) {
bigp = &bigalloc_array[i];
if (*bigp == NULL)
continue;
if (__isthreaded)
_SPINLOCK(&bigspin_array[i & BIGXMASK]);
for (big = *bigp; big; big = big->next) {
if (big->active < big->bytes) {
MASSERT_WTHUNLK((big->active & PAGE_MASK) == 0,
_SPINUNLOCK(&bigspin_array[i & BIGXMASK]));
MASSERT_WTHUNLK((big->bytes & PAGE_MASK) == 0,
_SPINUNLOCK(&bigspin_array[i & BIGXMASK]));
munmap((char *)big->base + big->active,
big->bytes - big->active);
atomic_add_long(&excess_alloc,
big->active - big->bytes);
big->bytes = big->active;
}
}
if (__isthreaded)
_SPINUNLOCK(&bigspin_array[i & BIGXMASK]);
}
}
static __inline int
zoneindex(size_t *bytes, size_t *chunking)
{
size_t n = (unsigned int)*bytes;
if (n < 16) {
*bytes = n = (n + 7) & ~7;
*chunking = 8;
return(n / 8 - 1);
}
if (n < 128) {
*bytes = n = (n + 15) & ~15;
*chunking = 16;
return(n / 16 + 2);
}
if (n < 256) {
*bytes = n = (n + 15) & ~15;
*chunking = 16;
return(n / 16 + 7);
}
if (n < 8192) {
if (n < 512) {
*bytes = n = (n + 31) & ~31;
*chunking = 32;
return(n / 32 + 15);
}
if (n < 1024) {
*bytes = n = (n + 63) & ~63;
*chunking = 64;
return(n / 64 + 23);
}
if (n < 2048) {
*bytes = n = (n + 127) & ~127;
*chunking = 128;
return(n / 128 + 31);
}
if (n < 4096) {
*bytes = n = (n + 255) & ~255;
*chunking = 256;
return(n / 256 + 39);
}
*bytes = n = (n + 511) & ~511;
*chunking = 512;
return(n / 512 + 47);
}
#if ZALLOC_ZONE_LIMIT > 8192
if (n < 16384) {
*bytes = n = (n + 1023) & ~1023;
*chunking = 1024;
return(n / 1024 + 55);
}
#endif
#if ZALLOC_ZONE_LIMIT > 16384
if (n < 32768) {
*bytes = n = (n + 2047) & ~2047;
*chunking = 2048;
return(n / 2048 + 63);
}
#endif
_mpanic("Unexpected byte count %zu", n);
return(0);
}
static __inline int
zonecapacity(int zi)
{
int cap;
cap = (NZONES - zi) * (M_MAX_ROUNDS - M_MIN_ROUNDS) / NZONES +
M_MIN_ROUNDS;
return cap;
}
void *
__malloc(size_t size)
{
void *ptr;
nmalloc_sigblockall();
ptr = _slaballoc(size, 0);
if (ptr == NULL)
errno = ENOMEM;
else
UTRACE(0, size, ptr);
nmalloc_sigunblockall();
return(ptr);
}
#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4))
void *
__calloc(size_t number, size_t size)
{
void *ptr;
if ((number >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
number > 0 && SIZE_MAX / number < size) {
errno = ENOMEM;
return(NULL);
}
nmalloc_sigblockall();
ptr = _slaballoc(number * size, SAFLAG_ZERO);
if (ptr == NULL)
errno = ENOMEM;
else
UTRACE(0, number * size, ptr);
nmalloc_sigunblockall();
return(ptr);
}
void *
__realloc(void *ptr, size_t size)
{
void *ret;
nmalloc_sigblockall();
ret = _slabrealloc(ptr, size);
if (ret == NULL)
errno = ENOMEM;
else
UTRACE(ptr, size, ret);
nmalloc_sigunblockall();
return(ret);
}
size_t
__malloc_usable_size(const void *ptr)
{
return _slabusablesize(ptr);
}
void *
__aligned_alloc(size_t alignment, size_t size)
{
void *ptr;
int rc;
nmalloc_sigblockall();
ptr = NULL;
rc = _slabmemalign(&ptr, alignment, size);
if (rc)
errno = rc;
nmalloc_sigunblockall();
return (ptr);
}
int
__posix_memalign(void **memptr, size_t alignment, size_t size)
{
int rc;
if (alignment < sizeof(void *)) {
*memptr = NULL;
return(EINVAL);
}
nmalloc_sigblockall();
rc = _slabmemalign(memptr, alignment, size);
nmalloc_sigunblockall();
return (rc);
}
static int
_slabmemalign(void **memptr, size_t alignment, size_t size)
{
bigalloc_t *bigp;
bigalloc_t big;
size_t chunking;
int zi __unused;
if (alignment < 1) {
*memptr = NULL;
return(EINVAL);
}
if ((alignment | (alignment - 1)) + 1 != (alignment << 1)) {
*memptr = NULL;
return(EINVAL);
}
if (size <= alignment)
size = alignment;
else
size = (size + alignment - 1) & ~(size_t)(alignment - 1);
if (size == 0)
return(ENOMEM);
if (size <= MAX_SLAB_PAGEALIGN &&
(size | (size - 1)) + 1 == (size << 1)) {
*memptr = _slaballoc(size, 0);
return(*memptr ? 0 : ENOMEM);
}
if (size < PAGE_SIZE) {
zi = zoneindex(&size, &chunking);
if (chunking >= alignment) {
*memptr = _slaballoc(size, 0);
return(*memptr ? 0 : ENOMEM);
}
if (size >= 1024)
alignment = 1024;
if (size >= 16384)
alignment = 16384;
while (alignment < size)
alignment <<= 1;
*memptr = _slaballoc(alignment, 0);
return(*memptr ? 0 : ENOMEM);
}
if (alignment < PAGE_SIZE)
alignment = PAGE_SIZE;
if (size < alignment)
size = alignment;
size = (size + PAGE_MASK) & ~(size_t)PAGE_MASK;
if (alignment == PAGE_SIZE && size <= BIGCACHE_LIMIT) {
big = bigcache_find_alloc(size);
if (big && big->bytes < size) {
_slabfree(big->base, FASTSLABREALLOC, &big);
big = NULL;
}
if (big) {
*memptr = big->base;
big->active = size;
if (big->active < big->bytes) {
atomic_add_long(&excess_alloc,
big->bytes - big->active);
}
bigp = bigalloc_lock(*memptr);
big->next = *bigp;
*bigp = big;
bigalloc_unlock(*memptr);
handle_excess_big();
return(0);
}
}
*memptr = _vmem_alloc(size, alignment, 0);
if (*memptr == NULL)
return(ENOMEM);
big = _slaballoc(sizeof(struct bigalloc), 0);
if (big == NULL) {
_vmem_free(*memptr, size);
*memptr = NULL;
return(ENOMEM);
}
bigp = bigalloc_lock(*memptr);
big->base = *memptr;
big->active = size;
big->bytes = size;
big->next = *bigp;
*bigp = big;
bigalloc_unlock(*memptr);
return(0);
}
void
__free(void *ptr)
{
UTRACE(ptr, 0, 0);
nmalloc_sigblockall();
_slabfree(ptr, 0, NULL);
nmalloc_sigunblockall();
}
static void *
_slaballoc(size_t size, int flags)
{
slzone_t z;
slchunk_t chunk;
slglobaldata_t slgd;
size_t chunking;
thr_mags *tp;
struct magazine *mp;
int count;
int zi;
int off;
void *obj;
if (size == 0)
size = 1;
flags |= g_malloc_flags;
if (size >= ZoneLimit ||
((size & PAGE_MASK) == 0 && size > MAX_SLAB_PAGEALIGN)) {
bigalloc_t big;
bigalloc_t *bigp;
size = (size + PAGE_MASK) & ~(size_t)PAGE_MASK;
if (size == 0)
return (NULL);
if ((size & (PAGE_SIZE * 2 - 1)) == 0)
size += PAGE_SIZE;
big = NULL;
if (size <= BIGCACHE_LIMIT) {
big = bigcache_find_alloc(size);
if (big && big->bytes < size) {
_slabfree(big->base, FASTSLABREALLOC, &big);
big = NULL;
}
}
if (big) {
chunk = big->base;
if (flags & SAFLAG_ZERO)
bzero(chunk, size);
} else {
chunk = _vmem_alloc(size, PAGE_SIZE, flags);
if (chunk == NULL)
return(NULL);
big = _slaballoc(sizeof(struct bigalloc), 0);
if (big == NULL) {
_vmem_free(chunk, size);
return(NULL);
}
big->base = chunk;
big->bytes = size;
}
big->active = size;
bigp = bigalloc_lock(chunk);
if (big->active < big->bytes) {
atomic_add_long(&excess_alloc,
big->bytes - big->active);
}
big->next = *bigp;
*bigp = big;
bigalloc_unlock(chunk);
handle_excess_big();
return(chunk);
}
zi = zoneindex(&size, &chunking);
MASSERT(zi < NZONES);
obj = mtmagazine_alloc(zi, flags);
if (obj != NULL) {
if (flags & SAFLAG_ZERO)
bzero(obj, size);
return (obj);
}
slgd = &SLGlobalData;
again:
if (slgd->ZoneAry[zi] == NULL) {
z = zone_alloc(flags);
if (z == NULL)
goto fail;
off = sizeof(struct slzone);
if ((size | (size - 1)) + 1 == (size << 1))
off = roundup2(off, size);
else
off = roundup2(off, chunking);
z->z_Magic = ZALLOC_SLAB_MAGIC;
z->z_ZoneIndex = zi;
z->z_NMax = (ZoneSize - off) / size;
z->z_NFree = z->z_NMax;
z->z_BasePtr = (char *)z + off;
z->z_UIndex = z->z_UEndIndex = 0;
z->z_ChunkSize = size;
z->z_FirstFreePg = ZonePageCount;
if ((z->z_Flags & SLZF_UNOTZEROD) == 0) {
flags &= ~SAFLAG_ZERO;
flags |= SAFLAG_PASSIVE;
}
slgd_lock(slgd);
z->z_Next = slgd->ZoneAry[zi];
slgd->ZoneAry[zi] = z;
} else {
slgd_lock(slgd);
z = slgd->ZoneAry[zi];
if (z == NULL) {
slgd_unlock(slgd);
goto again;
}
}
MASSERT_WTHUNLK(z->z_NFree > 0, slgd_unlock(slgd));
tp = &thread_mags;
mp = tp->mags[zi].loaded;
count = 0;
if (mp && tp->init >= 0) {
count = mp->capacity - mp->rounds;
if (count >= z->z_NFree)
count = z->z_NFree - 1;
if (count > CACHE_CHUNKS)
count = CACHE_CHUNKS;
}
while (z->z_FirstFreePg < ZonePageCount) {
if ((chunk = z->z_PageAry[z->z_FirstFreePg]) != NULL) {
if (((uintptr_t)chunk & ZoneMask) == 0) {
slgd_unlock(slgd);
_mpanic("assertion: corrupt malloc zone");
}
z->z_PageAry[z->z_FirstFreePg] = chunk->c_Next;
--z->z_NFree;
if (count == 0)
goto done;
mp->objects[mp->rounds++] = chunk;
--count;
continue;
}
++z->z_FirstFreePg;
}
for (;;) {
chunk = (slchunk_t)(z->z_BasePtr + z->z_UIndex * size);
--z->z_NFree;
if (++z->z_UIndex == z->z_NMax)
z->z_UIndex = 0;
if (z->z_UIndex == z->z_UEndIndex) {
if (z->z_NFree != 0) {
slgd_unlock(slgd);
_mpanic("slaballoc: corrupted zone");
}
}
if (count == 0)
break;
mp->objects[mp->rounds++] = chunk;
--count;
}
if ((z->z_Flags & SLZF_UNOTZEROD) == 0) {
flags &= ~SAFLAG_ZERO;
flags |= SAFLAG_PASSIVE;
}
done:
if (z->z_NFree == 0) {
slgd->ZoneAry[zi] = z->z_Next;
z->z_Next = NULL;
}
slgd_unlock(slgd);
if (flags & SAFLAG_ZERO)
bzero(chunk, size);
return(chunk);
fail:
return(NULL);
}
static void *
_slabrealloc(void *ptr, size_t size)
{
bigalloc_t *bigp;
void *nptr;
slzone_t z;
size_t chunking;
if (ptr == NULL) {
return(_slaballoc(size, 0));
}
if (size == 0)
size = 1;
if ((bigp = bigalloc_check_and_lock(ptr)) != NULL) {
bigalloc_t big;
size_t bigbytes;
while ((big = *bigp) != NULL) {
if (big->base == ptr) {
size = (size + PAGE_MASK) & ~(size_t)PAGE_MASK;
bigbytes = big->bytes;
if (size >= (bigbytes >> 1) &&
size <= bigbytes) {
if (big->active != size) {
atomic_add_long(&excess_alloc,
big->active -
size);
}
big->active = size;
bigalloc_unlock(ptr);
return(ptr);
}
chunking = size + (size >> 3);
chunking = (chunking + PAGE_MASK) &
~(size_t)PAGE_MASK;
if (chunking > bigbytes) {
char *addr;
int errno_save = errno;
addr = mmap((char *)ptr + bigbytes,
chunking - bigbytes,
PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANON|
MAP_TRYFIXED,
-1, 0);
errno = errno_save;
if (addr == (char *)ptr + bigbytes) {
atomic_add_long(&excess_alloc,
big->active -
big->bytes +
chunking -
size);
big->bytes = chunking;
big->active = size;
bigalloc_unlock(ptr);
return(ptr);
}
MASSERT_WTHUNLK(
(void *)addr == MAP_FAILED,
bigalloc_unlock(ptr));
}
*bigp = big->next;
bigalloc_unlock(ptr);
if ((nptr = _slaballoc(size, 0)) == NULL) {
bigp = bigalloc_lock(ptr);
big->next = *bigp;
*bigp = big;
bigalloc_unlock(ptr);
return(NULL);
}
if (size > bigbytes)
size = bigbytes;
bcopy(ptr, nptr, size);
atomic_add_long(&excess_alloc, big->active -
big->bytes);
_slabfree(ptr, FASTSLABREALLOC, &big);
return(nptr);
}
bigp = &big->next;
}
bigalloc_unlock(ptr);
handle_excess_big();
}
z = (slzone_t)((uintptr_t)ptr & ~(uintptr_t)ZoneMask);
MASSERT(z->z_Magic == ZALLOC_SLAB_MAGIC);
if (size < ZoneLimit) {
zoneindex(&size, &chunking);
if (z->z_ChunkSize == size) {
return(ptr);
}
}
if ((nptr = _slaballoc(size, 0)) != NULL) {
if (size > z->z_ChunkSize)
size = z->z_ChunkSize;
bcopy(ptr, nptr, size);
_slabfree(ptr, 0, NULL);
}
return(nptr);
}
static size_t
_slabusablesize(const void *ptr)
{
size_t size;
bigalloc_t *bigp;
slzone_t z;
if (ptr == NULL)
return 0;
if ((bigp = bigalloc_check_and_lock(ptr)) != NULL) {
bigalloc_t big;
while ((big = *bigp) != NULL) {
const char *base = big->base;
if ((const char *)ptr >= base &&
(const char *)ptr < base + big->bytes)
{
size = base + big->bytes - (const char *)ptr;
bigalloc_unlock(ptr);
return size;
}
bigp = &big->next;
}
bigalloc_unlock(ptr);
handle_excess_big();
}
z = (slzone_t)((uintptr_t)ptr & ~(uintptr_t)ZoneMask);
MASSERT(z->z_Magic == ZALLOC_SLAB_MAGIC);
size = z->z_ChunkSize -
((const char *)ptr - (const char *)z->z_BasePtr) %
z->z_ChunkSize;
return size;
}
static void
_slabfree(void *ptr, int flags, bigalloc_t *rbigp)
{
slzone_t z;
slchunk_t chunk;
bigalloc_t big;
bigalloc_t *bigp;
slglobaldata_t slgd;
size_t size;
int zi;
int pgno;
if (flags & FASTSLABREALLOC) {
big = *rbigp;
goto fastslabrealloc;
}
if (ptr == NULL)
return;
if ((bigp = bigalloc_check_and_lock(ptr)) != NULL) {
while ((big = *bigp) != NULL) {
if (big->base == ptr) {
*bigp = big->next;
atomic_add_long(&excess_alloc, big->active -
big->bytes);
bigalloc_unlock(ptr);
fastslabrealloc:
if (big->bytes <= BIGCACHE_LIMIT) {
big = bigcache_find_free(big);
if (big == NULL)
return;
}
ptr = big->base;
size = big->bytes;
_slabfree(big, 0, NULL);
_vmem_free(ptr, size);
return;
}
bigp = &big->next;
}
bigalloc_unlock(ptr);
handle_excess_big();
}
z = (slzone_t)((uintptr_t)ptr & ~(uintptr_t)ZoneMask);
MASSERT(z->z_Magic == ZALLOC_SLAB_MAGIC);
size = z->z_ChunkSize;
zi = z->z_ZoneIndex;
if (g_malloc_flags & SAFLAG_ZERO)
bzero(ptr, size);
if (mtmagazine_free(zi, ptr) == 0)
return;
pgno = ((char *)ptr - (char *)z) >> PAGE_SHIFT;
chunk = ptr;
slgd = &SLGlobalData;
slgd_lock(slgd);
chunk->c_Next = z->z_PageAry[pgno];
z->z_PageAry[pgno] = chunk;
if (z->z_FirstFreePg > pgno)
z->z_FirstFreePg = pgno;
if (z->z_NFree++ == 0) {
z->z_Next = slgd->ZoneAry[z->z_ZoneIndex];
slgd->ZoneAry[z->z_ZoneIndex] = z;
}
if (z->z_NFree == z->z_NMax) {
slzone_t *pz;
pz = &slgd->ZoneAry[z->z_ZoneIndex];
while (z != *pz)
pz = &(*pz)->z_Next;
*pz = z->z_Next;
z->z_Magic = -1;
z->z_Next = NULL;
slgd_unlock(slgd);
zone_free(z);
} else {
slgd_unlock(slgd);
}
}
static __inline void *
magazine_alloc(struct magazine *mp)
{
void *obj;
if (mp && MAGAZINE_NOTEMPTY(mp)) {
obj = mp->objects[--mp->rounds];
} else {
obj = NULL;
}
return (obj);
}
static __inline int
magazine_free(struct magazine *mp, void *p)
{
if (mp != NULL && MAGAZINE_NOTFULL(mp)) {
mp->objects[mp->rounds++] = p;
return 0;
}
return -1;
}
static void *
mtmagazine_alloc(int zi, int flags)
{
thr_mags *tp;
struct magazine *mp, *emptymag;
magazine_depot *d;
void *obj;
tp = &thread_mags;
if (tp->init < 0)
return(NULL);
for (;;) {
if (tp->newmag == NULL && (flags & SAFLAG_MAGS) == 0) {
mp = _slaballoc(sizeof(struct magazine),
SAFLAG_ZERO | SAFLAG_MAGS);
if (mp == NULL) {
obj = NULL;
break;
}
if (tp->newmag) {
_slabfree(mp, 0, NULL);
} else {
tp->newmag = mp;
}
}
mp = tp->mags[zi].loaded;
obj = magazine_alloc(mp);
if (obj)
break;
mp = tp->mags[zi].prev;
if (mp && MAGAZINE_FULL(mp)) {
MASSERT(mp->rounds != 0);
swap_mags(&tp->mags[zi]);
continue;
}
d = &depots[zi];
if (SLIST_EMPTY(&d->full)) {
mp = tp->mags[zi].loaded;
if (mp == NULL && tp->newmag) {
mp = tp->newmag;
tp->newmag = NULL;
mp->capacity = zonecapacity(zi);
mp->rounds = 0;
mp->flags = 0;
tp->mags[zi].loaded = mp;
}
break;
}
depot_lock(d);
mp = SLIST_FIRST(&d->full);
if (mp) {
SLIST_REMOVE_HEAD(&d->full, nextmagazine);
emptymag = tp->mags[zi].prev;
if (emptymag) {
SLIST_INSERT_HEAD(&d->empty, emptymag,
nextmagazine);
}
tp->mags[zi].prev = tp->mags[zi].loaded;
tp->mags[zi].loaded = mp;
MASSERT(MAGAZINE_NOTEMPTY(mp));
}
depot_unlock(d);
continue;
}
return (obj);
}
static int
mtmagazine_free(int zi, void *ptr)
{
thr_mags *tp;
struct magazine *mp, *loadedmag;
magazine_depot *d;
int rc = -1;
tp = &thread_mags;
if (tp->init < 0)
return(-1);
for (;;) {
if (tp->newmag == NULL) {
mp = _slaballoc(sizeof(struct magazine),
SAFLAG_ZERO | SAFLAG_MAGS);
if (tp->newmag && mp)
_slabfree(mp, 0, NULL);
else
tp->newmag = mp;
if (tp->newmag == NULL) {
rc = -1;
break;
}
}
rc = magazine_free(tp->mags[zi].loaded, ptr);
if (rc == 0)
break;
mp = tp->mags[zi].prev;
if (mp && MAGAZINE_EMPTY(mp)) {
MASSERT(mp->rounds == 0);
swap_mags(&tp->mags[zi]);
continue;
}
d = &depots[zi];
depot_lock(d);
if ((loadedmag = tp->mags[zi].prev) != NULL)
SLIST_INSERT_HEAD(&d->full, loadedmag, nextmagazine);
tp->mags[zi].prev = tp->mags[zi].loaded;
mp = SLIST_FIRST(&d->empty);
if (mp) {
tp->mags[zi].loaded = mp;
SLIST_REMOVE_HEAD(&d->empty, nextmagazine);
depot_unlock(d);
MASSERT(MAGAZINE_NOTFULL(mp));
} else {
mp = tp->newmag;
tp->newmag = NULL;
mp->capacity = zonecapacity(zi);
mp->rounds = 0;
mp->flags = 0;
tp->mags[zi].loaded = mp;
depot_unlock(d);
}
}
return rc;
}
static void
mtmagazine_init(void)
{
_pthread_key_create(&thread_mags_key, mtmagazine_destructor);
}
static void
mtmagazine_drain(struct magazine *mp)
{
void *obj;
nmalloc_sigblockall();
while (MAGAZINE_NOTEMPTY(mp)) {
obj = magazine_alloc(mp);
_slabfree(obj, 0, NULL);
}
nmalloc_sigunblockall();
}
static void
mtmagazine_destructor(void *thrp)
{
thr_mags *tp = thrp;
struct magazine *mp;
int i;
if (__isexiting)
return;
tp->init = -1;
nmalloc_sigblockall();
for (i = 0; i < NZONES; i++) {
mp = tp->mags[i].loaded;
tp->mags[i].loaded = NULL;
if (mp) {
if (MAGAZINE_NOTEMPTY(mp))
mtmagazine_drain(mp);
_slabfree(mp, 0, NULL);
}
mp = tp->mags[i].prev;
tp->mags[i].prev = NULL;
if (mp) {
if (MAGAZINE_NOTEMPTY(mp))
mtmagazine_drain(mp);
_slabfree(mp, 0, NULL);
}
}
if (tp->newmag) {
mp = tp->newmag;
tp->newmag = NULL;
_slabfree(mp, 0, NULL);
}
nmalloc_sigunblockall();
}
static slzone_t
zone_alloc(int flags)
{
slzone_t z;
zone_magazine_lock();
z = magazine_alloc(&zone_magazine);
if (z == NULL) {
zone_magazine_unlock();
z = _vmem_alloc(ZoneSize, ZoneSize, flags);
} else {
z->z_Flags |= SLZF_UNOTZEROD;
zone_magazine_unlock();
}
return z;
}
static void
zone_free(void *z)
{
void *excess[M_ZONE_HYSTERESIS];
int i;
zone_magazine_lock();
bzero(z, sizeof(struct slzone));
if (opt_madvise)
madvise(z, ZoneSize, MADV_FREE);
i = magazine_free(&zone_magazine, z);
if (i == -1) {
for (i = 0; i < M_ZONE_HYSTERESIS; ++i) {
excess[i] = magazine_alloc(&zone_magazine);
MASSERT_WTHUNLK(excess[i] != NULL,
zone_magazine_unlock());
}
zone_magazine_unlock();
for (i = 0; i < M_ZONE_HYSTERESIS; ++i)
_vmem_free(excess[i], ZoneSize);
_vmem_free(z, ZoneSize);
} else {
zone_magazine_unlock();
}
}
static void *
_vmem_alloc(size_t size, size_t align, int flags)
{
static char *addr_hint;
static int reset_hint = 16;
char *addr;
char *save;
if (--reset_hint <= 0) {
addr_hint = NULL;
reset_hint = 16;
}
save = mmap(addr_hint, size, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANON, -1, 0);
if (save == MAP_FAILED)
goto worst_case;
if (((uintptr_t)save & (align - 1)) == 0)
return((void *)save);
addr_hint = (char *)(((size_t)save + (align - 1)) & ~(align - 1));
munmap(save, size);
save = mmap(addr_hint, size, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANON, -1, 0);
if (save == MAP_FAILED)
goto worst_case;
if (((size_t)save & (align - 1)) == 0)
return((void *)save);
munmap(save, size);
worst_case:
save = mmap(NULL, size + align, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANON, -1, 0);
if (save == MAP_FAILED)
return NULL;
addr = (char *)(((size_t)save + (align - 1)) & ~(align - 1));
if (save != addr)
munmap(save, addr - save);
if (addr + size != save + size + align)
munmap(addr + size, save + align - addr);
addr_hint = addr + size;
return ((void *)addr);
}
static void
_vmem_free(void *ptr, size_t size)
{
munmap(ptr, size);
}
static void
_mpanic(const char *ctl, ...)
{
va_list va;
if (malloc_panic == 0) {
malloc_panic = 1;
va_start(va, ctl);
vfprintf(stderr, ctl, va);
fprintf(stderr, "\n");
fflush(stderr);
va_end(va);
}
abort();
}
__weak_reference(__aligned_alloc, aligned_alloc);
__weak_reference(__malloc, malloc);
__weak_reference(__calloc, calloc);
__weak_reference(__posix_memalign, posix_memalign);
__weak_reference(__realloc, realloc);
__weak_reference(__free, free);
__weak_reference(__malloc_usable_size, malloc_usable_size);