summaryrefslogtreecommitdiff
path: root/src/malloc
diff options
context:
space:
mode:
Diffstat (limited to 'src/malloc')
-rw-r--r--src/malloc/DESIGN22
-rw-r--r--src/malloc/aligned_alloc.c7
-rw-r--r--src/malloc/calloc.c45
-rw-r--r--src/malloc/expand_heap.c71
-rw-r--r--src/malloc/free.c6
-rw-r--r--src/malloc/libc_calloc.c4
-rw-r--r--src/malloc/lite_malloc.c111
-rw-r--r--src/malloc/mallocng/aligned_alloc.c60
-rw-r--r--src/malloc/mallocng/donate.c39
-rw-r--r--src/malloc/mallocng/free.c151
-rw-r--r--src/malloc/mallocng/glue.h95
-rw-r--r--src/malloc/mallocng/malloc.c387
-rw-r--r--src/malloc/mallocng/malloc_usable_size.c13
-rw-r--r--src/malloc/mallocng/meta.h288
-rw-r--r--src/malloc/mallocng/realloc.c51
-rw-r--r--src/malloc/memalign.c53
-rw-r--r--src/malloc/oldmalloc/aligned_alloc.c53
-rw-r--r--src/malloc/oldmalloc/malloc.c (renamed from src/malloc/malloc.c)384
-rw-r--r--src/malloc/oldmalloc/malloc_impl.h39
-rw-r--r--src/malloc/oldmalloc/malloc_usable_size.c (renamed from src/malloc/malloc_usable_size.c)0
-rw-r--r--src/malloc/posix_memalign.c3
-rw-r--r--src/malloc/realloc.c6
-rw-r--r--src/malloc/reallocarray.c13
-rw-r--r--src/malloc/replaced.c4
24 files changed, 1539 insertions, 366 deletions
diff --git a/src/malloc/DESIGN b/src/malloc/DESIGN
deleted file mode 100644
index 58b0523f..00000000
--- a/src/malloc/DESIGN
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-In principle, this memory allocator is roughly equivalent to Doug
-Lea's dlmalloc with fine-grained locking.
-
-
-
-malloc:
-
-Uses a freelist binned by chunk size, with a bitmap to optimize
-searching for the smallest non-empty bin which can satisfy an
-allocation. If no free chunks are available, it creates a new chunk of
-the requested size and attempts to merge it with any existing free
-chunk immediately below the newly created chunk.
-
-Whether the chunk was obtained from a bin or newly created, it's
-likely to be larger than the requested allocation. malloc always
-finishes its work by passing the new chunk to realloc, which will
-split it into two chunks and free the tail portion.
-
-
-
diff --git a/src/malloc/aligned_alloc.c b/src/malloc/aligned_alloc.c
deleted file mode 100644
index b6143f30..00000000
--- a/src/malloc/aligned_alloc.c
+++ /dev/null
@@ -1,7 +0,0 @@
-#include <stdlib.h>
-#include "malloc_impl.h"
-
-void *aligned_alloc(size_t align, size_t len)
-{
- return __memalign(align, len);
-}
diff --git a/src/malloc/calloc.c b/src/malloc/calloc.c
new file mode 100644
index 00000000..bf6bddca
--- /dev/null
+++ b/src/malloc/calloc.c
@@ -0,0 +1,45 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include "dynlink.h"
+
+static size_t mal0_clear(char *p, size_t n)
+{
+ const size_t pagesz = 4096; /* arbitrary */
+ if (n < pagesz) return n;
+#ifdef __GNUC__
+ typedef uint64_t __attribute__((__may_alias__)) T;
+#else
+ typedef unsigned char T;
+#endif
+ char *pp = p + n;
+ size_t i = (uintptr_t)pp & (pagesz - 1);
+ for (;;) {
+ pp = memset(pp - i, 0, i);
+ if (pp - p < pagesz) return pp - p;
+ for (i = pagesz; i; i -= 2*sizeof(T), pp -= 2*sizeof(T))
+ if (((T *)pp)[-1] | ((T *)pp)[-2])
+ break;
+ }
+}
+
+static int allzerop(void *p)
+{
+ return 0;
+}
+weak_alias(allzerop, __malloc_allzerop);
+
+void *calloc(size_t m, size_t n)
+{
+ if (n && m > (size_t)-1/n) {
+ errno = ENOMEM;
+ return 0;
+ }
+ n *= m;
+ void *p = malloc(n);
+ if (!p || (!__malloc_replaced && __malloc_allzerop(p)))
+ return p;
+ n = mal0_clear(p, n);
+ return memset(p, 0, n);
+}
diff --git a/src/malloc/expand_heap.c b/src/malloc/expand_heap.c
deleted file mode 100644
index e6a3d7a0..00000000
--- a/src/malloc/expand_heap.c
+++ /dev/null
@@ -1,71 +0,0 @@
-#include <limits.h>
-#include <stdint.h>
-#include <errno.h>
-#include <sys/mman.h>
-#include "libc.h"
-#include "syscall.h"
-#include "malloc_impl.h"
-
-/* This function returns true if the interval [old,new]
- * intersects the 'len'-sized interval below &libc.auxv
- * (interpreted as the main-thread stack) or below &b
- * (the current stack). It is used to defend against
- * buggy brk implementations that can cross the stack. */
-
-static int traverses_stack_p(uintptr_t old, uintptr_t new)
-{
- const uintptr_t len = 8<<20;
- uintptr_t a, b;
-
- b = (uintptr_t)libc.auxv;
- a = b > len ? b-len : 0;
- if (new>a && old<b) return 1;
-
- b = (uintptr_t)&b;
- a = b > len ? b-len : 0;
- if (new>a && old<b) return 1;
-
- return 0;
-}
-
-/* Expand the heap in-place if brk can be used, or otherwise via mmap,
- * using an exponential lower bound on growth by mmap to make
- * fragmentation asymptotically irrelevant. The size argument is both
- * an input and an output, since the caller needs to know the size
- * allocated, which will be larger than requested due to page alignment
- * and mmap minimum size rules. The caller is responsible for locking
- * to prevent concurrent calls. */
-
-void *__expand_heap(size_t *pn)
-{
- static uintptr_t brk;
- static unsigned mmap_step;
- size_t n = *pn;
-
- if (n > SIZE_MAX/2 - PAGE_SIZE) {
- errno = ENOMEM;
- return 0;
- }
- n += -n & PAGE_SIZE-1;
-
- if (!brk) {
- brk = __syscall(SYS_brk, 0);
- brk += -brk & PAGE_SIZE-1;
- }
-
- if (n < SIZE_MAX-brk && !traverses_stack_p(brk, brk+n)
- && __syscall(SYS_brk, brk+n)==brk+n) {
- *pn = n;
- brk += n;
- return (void *)(brk-n);
- }
-
- size_t min = (size_t)PAGE_SIZE << mmap_step/2;
- if (n < min) n = min;
- void *area = __mmap(0, n, PROT_READ|PROT_WRITE,
- MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
- if (area == MAP_FAILED) return 0;
- *pn = n;
- mmap_step++;
- return area;
-}
diff --git a/src/malloc/free.c b/src/malloc/free.c
new file mode 100644
index 00000000..3944f7b2
--- /dev/null
+++ b/src/malloc/free.c
@@ -0,0 +1,6 @@
+#include <stdlib.h>
+
+void free(void *p)
+{
+ __libc_free(p);
+}
diff --git a/src/malloc/libc_calloc.c b/src/malloc/libc_calloc.c
new file mode 100644
index 00000000..d25eabea
--- /dev/null
+++ b/src/malloc/libc_calloc.c
@@ -0,0 +1,4 @@
+#define calloc __libc_calloc
+#define malloc __libc_malloc
+
+#include "calloc.c"
diff --git a/src/malloc/lite_malloc.c b/src/malloc/lite_malloc.c
index 050d84f6..43a988fb 100644
--- a/src/malloc/lite_malloc.c
+++ b/src/malloc/lite_malloc.c
@@ -2,58 +2,117 @@
#include <stdint.h>
#include <limits.h>
#include <errno.h>
+#include <sys/mman.h>
+#include "libc.h"
#include "lock.h"
-#include "malloc_impl.h"
+#include "syscall.h"
+#include "fork_impl.h"
#define ALIGN 16
+/* This function returns true if the interval [old,new]
+ * intersects the 'len'-sized interval below &libc.auxv
+ * (interpreted as the main-thread stack) or below &b
+ * (the current stack). It is used to defend against
+ * buggy brk implementations that can cross the stack. */
+
+static int traverses_stack_p(uintptr_t old, uintptr_t new)
+{
+ const uintptr_t len = 8<<20;
+ uintptr_t a, b;
+
+ b = (uintptr_t)libc.auxv;
+ a = b > len ? b-len : 0;
+ if (new>a && old<b) return 1;
+
+ b = (uintptr_t)&b;
+ a = b > len ? b-len : 0;
+ if (new>a && old<b) return 1;
+
+ return 0;
+}
+
+static volatile int lock[1];
+volatile int *const __bump_lockptr = lock;
+
static void *__simple_malloc(size_t n)
{
- static char *cur, *end;
- static volatile int lock[1];
- size_t align=1, pad;
+ static uintptr_t brk, cur, end;
+ static unsigned mmap_step;
+ size_t align=1;
void *p;
+ if (n > SIZE_MAX/2) {
+ errno = ENOMEM;
+ return 0;
+ }
+
if (!n) n++;
while (align<n && align<ALIGN)
align += align;
LOCK(lock);
- pad = -(uintptr_t)cur & align-1;
-
- if (n <= SIZE_MAX/2 + ALIGN) n += pad;
+ cur += -cur & align-1;
if (n > end-cur) {
- size_t m = n;
- char *new = __expand_heap(&m);
- if (!new) {
- UNLOCK(lock);
- return 0;
+ size_t req = n - (end-cur) + PAGE_SIZE-1 & -PAGE_SIZE;
+
+ if (!cur) {
+ brk = __syscall(SYS_brk, 0);
+ brk += -brk & PAGE_SIZE-1;
+ cur = end = brk;
}
- if (new != end) {
- cur = new;
- n -= pad;
- pad = 0;
+
+ if (brk == end && req < SIZE_MAX-brk
+ && !traverses_stack_p(brk, brk+req)
+ && __syscall(SYS_brk, brk+req)==brk+req) {
+ brk = end += req;
+ } else {
+ int new_area = 0;
+ req = n + PAGE_SIZE-1 & -PAGE_SIZE;
+ /* Only make a new area rather than individual mmap
+ * if wasted space would be over 1/8 of the map. */
+ if (req-n > req/8) {
+ /* Geometric area size growth up to 64 pages,
+ * bounding waste by 1/8 of the area. */
+ size_t min = PAGE_SIZE<<(mmap_step/2);
+ if (min-n > end-cur) {
+ if (req < min) {
+ req = min;
+ if (mmap_step < 12)
+ mmap_step++;
+ }
+ new_area = 1;
+ }
+ }
+ void *mem = __mmap(0, req, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+ if (mem == MAP_FAILED || !new_area) {
+ UNLOCK(lock);
+ return mem==MAP_FAILED ? 0 : mem;
+ }
+ cur = (uintptr_t)mem;
+ end = cur + req;
}
- end = new + m;
}
- p = cur + pad;
+ p = (void *)cur;
cur += n;
UNLOCK(lock);
return p;
}
-weak_alias(__simple_malloc, malloc);
+weak_alias(__simple_malloc, __libc_malloc_impl);
-static void *__simple_calloc(size_t m, size_t n)
+void *__libc_malloc(size_t n)
{
- if (n && m > (size_t)-1/n) {
- errno = ENOMEM;
- return 0;
- }
- return __simple_malloc(n * m);
+ return __libc_malloc_impl(n);
+}
+
+static void *default_malloc(size_t n)
+{
+ return __libc_malloc_impl(n);
}
-weak_alias(__simple_calloc, calloc);
+weak_alias(default_malloc, malloc);
diff --git a/src/malloc/mallocng/aligned_alloc.c b/src/malloc/mallocng/aligned_alloc.c
new file mode 100644
index 00000000..e0862a83
--- /dev/null
+++ b/src/malloc/mallocng/aligned_alloc.c
@@ -0,0 +1,60 @@
+#include <stdlib.h>
+#include <errno.h>
+#include "meta.h"
+
+void *aligned_alloc(size_t align, size_t len)
+{
+ if ((align & -align) != align) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ if (len > SIZE_MAX - align || align >= (1ULL<<31)*UNIT) {
+ errno = ENOMEM;
+ return 0;
+ }
+
+ if (DISABLE_ALIGNED_ALLOC) {
+ errno = ENOMEM;
+ return 0;
+ }
+
+ if (align <= UNIT) align = UNIT;
+
+ unsigned char *p = malloc(len + align - UNIT);
+ if (!p)
+ return 0;
+
+ struct meta *g = get_meta(p);
+ int idx = get_slot_index(p);
+ size_t stride = get_stride(g);
+ unsigned char *start = g->mem->storage + stride*idx;
+ unsigned char *end = g->mem->storage + stride*(idx+1) - IB;
+ size_t adj = -(uintptr_t)p & (align-1);
+
+ if (!adj) {
+ set_size(p, end, len);
+ return p;
+ }
+ p += adj;
+ uint32_t offset = (size_t)(p-g->mem->storage)/UNIT;
+ if (offset <= 0xffff) {
+ *(uint16_t *)(p-2) = offset;
+ p[-4] = 0;
+ } else {
+ // use a 32-bit offset if 16-bit doesn't fit. for this,
+ // 16-bit field must be zero, [-4] byte nonzero.
+ *(uint16_t *)(p-2) = 0;
+ *(uint32_t *)(p-8) = offset;
+ p[-4] = 1;
+ }
+ p[-3] = idx;
+ set_size(p, end, len);
+ // store offset to aligned enframing. this facilitates cycling
+ // offset and also iteration of heap for debugging/measurement.
+ // for extreme overalignment it won't fit but these are classless
+ // allocations anyway.
+ *(uint16_t *)(start - 2) = (size_t)(p-start)/UNIT;
+ start[-3] = 7<<5;
+ return p;
+}
diff --git a/src/malloc/mallocng/donate.c b/src/malloc/mallocng/donate.c
new file mode 100644
index 00000000..41d850f3
--- /dev/null
+++ b/src/malloc/mallocng/donate.c
@@ -0,0 +1,39 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+#include "meta.h"
+
+static void donate(unsigned char *base, size_t len)
+{
+ uintptr_t a = (uintptr_t)base;
+ uintptr_t b = a + len;
+ a += -a & (UNIT-1);
+ b -= b & (UNIT-1);
+ memset(base, 0, len);
+ for (int sc=47; sc>0 && b>a; sc-=4) {
+ if (b-a < (size_classes[sc]+1)*UNIT) continue;
+ struct meta *m = alloc_meta();
+ m->avail_mask = 0;
+ m->freed_mask = 1;
+ m->mem = (void *)a;
+ m->mem->meta = m;
+ m->last_idx = 0;
+ m->freeable = 0;
+ m->sizeclass = sc;
+ m->maplen = 0;
+ *((unsigned char *)m->mem+UNIT-4) = 0;
+ *((unsigned char *)m->mem+UNIT-3) = 255;
+ m->mem->storage[size_classes[sc]*UNIT-4] = 0;
+ queue(&ctx.active[sc], m);
+ a += (size_classes[sc]+1)*UNIT;
+ }
+}
+
+void __malloc_donate(char *start, char *end)
+{
+ donate((void *)start, end-start);
+}
diff --git a/src/malloc/mallocng/free.c b/src/malloc/mallocng/free.c
new file mode 100644
index 00000000..43f32aad
--- /dev/null
+++ b/src/malloc/mallocng/free.c
@@ -0,0 +1,151 @@
+#define _BSD_SOURCE
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#include "meta.h"
+
+struct mapinfo {
+ void *base;
+ size_t len;
+};
+
+static struct mapinfo nontrivial_free(struct meta *, int);
+
+static struct mapinfo free_group(struct meta *g)
+{
+ struct mapinfo mi = { 0 };
+ int sc = g->sizeclass;
+ if (sc < 48) {
+ ctx.usage_by_class[sc] -= g->last_idx+1;
+ }
+ if (g->maplen) {
+ step_seq();
+ record_seq(sc);
+ mi.base = g->mem;
+ mi.len = g->maplen*4096UL;
+ } else {
+ void *p = g->mem;
+ struct meta *m = get_meta(p);
+ int idx = get_slot_index(p);
+ g->mem->meta = 0;
+ // not checking size/reserved here; it's intentionally invalid
+ mi = nontrivial_free(m, idx);
+ }
+ free_meta(g);
+ return mi;
+}
+
+static int okay_to_free(struct meta *g)
+{
+ int sc = g->sizeclass;
+
+ if (!g->freeable) return 0;
+
+ // always free individual mmaps not suitable for reuse
+ if (sc >= 48 || get_stride(g) < UNIT*size_classes[sc])
+ return 1;
+
+ // always free groups allocated inside another group's slot
+ // since recreating them should not be expensive and they
+ // might be blocking freeing of a much larger group.
+ if (!g->maplen) return 1;
+
+ // if there is another non-full group, free this one to
+ // consolidate future allocations, reduce fragmentation.
+ if (g->next != g) return 1;
+
+ // free any group in a size class that's not bouncing
+ if (!is_bouncing(sc)) return 1;
+
+ size_t cnt = g->last_idx+1;
+ size_t usage = ctx.usage_by_class[sc];
+
+ // if usage is high enough that a larger count should be
+ // used, free the low-count group so a new one will be made.
+ if (9*cnt <= usage && cnt < 20)
+ return 1;
+
+ // otherwise, keep the last group in a bouncing class.
+ return 0;
+}
+
+static struct mapinfo nontrivial_free(struct meta *g, int i)
+{
+ uint32_t self = 1u<<i;
+ int sc = g->sizeclass;
+ uint32_t mask = g->freed_mask | g->avail_mask;
+
+ if (mask+self == (2u<<g->last_idx)-1 && okay_to_free(g)) {
+ // any multi-slot group is necessarily on an active list
+ // here, but single-slot groups might or might not be.
+ if (g->next) {
+ assert(sc < 48);
+ int activate_new = (ctx.active[sc]==g);
+ dequeue(&ctx.active[sc], g);
+ if (activate_new && ctx.active[sc])
+ activate_group(ctx.active[sc]);
+ }
+ return free_group(g);
+ } else if (!mask) {
+ assert(sc < 48);
+ // might still be active if there were no allocations
+ // after last available slot was taken.
+ if (ctx.active[sc] != g) {
+ queue(&ctx.active[sc], g);
+ }
+ }
+ a_or(&g->freed_mask, self);
+ return (struct mapinfo){ 0 };
+}
+
+void free(void *p)
+{
+ if (!p) return;
+
+ struct meta *g = get_meta(p);
+ int idx = get_slot_index(p);
+ size_t stride = get_stride(g);
+ unsigned char *start = g->mem->storage + stride*idx;
+ unsigned char *end = start + stride - IB;
+ get_nominal_size(p, end);
+ uint32_t self = 1u<<idx, all = (2u<<g->last_idx)-1;
+ ((unsigned char *)p)[-3] = 255;
+ // invalidate offset to group header, and cycle offset of
+ // used region within slot if current offset is zero.
+ *(uint16_t *)((char *)p-2) = 0;
+
+ // release any whole pages contained in the slot to be freed
+ // unless it's a single-slot group that will be unmapped.
+ if (((uintptr_t)(start-1) ^ (uintptr_t)end) >= 2*PGSZ && g->last_idx) {
+ unsigned char *base = start + (-(uintptr_t)start & (PGSZ-1));
+ size_t len = (end-base) & -PGSZ;
+ if (len && USE_MADV_FREE) {
+ int e = errno;
+ madvise(base, len, MADV_FREE);
+ errno = e;
+ }
+ }
+
+ // atomic free without locking if this is neither first or last slot
+ for (;;) {
+ uint32_t freed = g->freed_mask;
+ uint32_t avail = g->avail_mask;
+ uint32_t mask = freed | avail;
+ assert(!(mask&self));
+ if (!freed || mask+self==all) break;
+ if (!MT)
+ g->freed_mask = freed+self;
+ else if (a_cas(&g->freed_mask, freed, freed+self)!=freed)
+ continue;
+ return;
+ }
+
+ wrlock();
+ struct mapinfo mi = nontrivial_free(g, idx);
+ unlock();
+ if (mi.len) {
+ int e = errno;
+ munmap(mi.base, mi.len);
+ errno = e;
+ }
+}
diff --git a/src/malloc/mallocng/glue.h b/src/malloc/mallocng/glue.h
new file mode 100644
index 00000000..77f4c812
--- /dev/null
+++ b/src/malloc/mallocng/glue.h
@@ -0,0 +1,95 @@
+#ifndef MALLOC_GLUE_H
+#define MALLOC_GLUE_H
+
+#include <stdint.h>
+#include <sys/mman.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <elf.h>
+#include <string.h>
+#include "atomic.h"
+#include "syscall.h"
+#include "libc.h"
+#include "lock.h"
+#include "dynlink.h"
+
+// use macros to appropriately namespace these.
+#define size_classes __malloc_size_classes
+#define ctx __malloc_context
+#define alloc_meta __malloc_alloc_meta
+#define is_allzero __malloc_allzerop
+#define dump_heap __dump_heap
+
+#define malloc __libc_malloc_impl
+#define realloc __libc_realloc
+#define free __libc_free
+
+#define USE_MADV_FREE 0
+
+#if USE_REAL_ASSERT
+#include <assert.h>
+#else
+#undef assert
+#define assert(x) do { if (!(x)) a_crash(); } while(0)
+#endif
+
+#define brk(p) ((uintptr_t)__syscall(SYS_brk, p))
+
+#define mmap __mmap
+#define madvise __madvise
+#define mremap __mremap
+
+#define DISABLE_ALIGNED_ALLOC (__malloc_replaced && !__aligned_alloc_replaced)
+
+static inline uint64_t get_random_secret()
+{
+ uint64_t secret = (uintptr_t)&secret * 1103515245;
+ for (size_t i=0; libc.auxv[i]; i+=2)
+ if (libc.auxv[i]==AT_RANDOM)
+ memcpy(&secret, (char *)libc.auxv[i+1]+8, sizeof secret);
+ return secret;
+}
+
+#ifndef PAGESIZE
+#define PAGESIZE PAGE_SIZE
+#endif
+
+#define MT (libc.need_locks)
+
+#define RDLOCK_IS_EXCLUSIVE 1
+
+__attribute__((__visibility__("hidden")))
+extern int __malloc_lock[1];
+
+#define LOCK_OBJ_DEF \
+int __malloc_lock[1]; \
+void __malloc_atfork(int who) { malloc_atfork(who); }
+
+static inline void rdlock()
+{
+ if (MT) LOCK(__malloc_lock);
+}
+static inline void wrlock()
+{
+ if (MT) LOCK(__malloc_lock);
+}
+static inline void unlock()
+{
+ UNLOCK(__malloc_lock);
+}
+static inline void upgradelock()
+{
+}
+static inline void resetlock()
+{
+ __malloc_lock[0] = 0;
+}
+
+static inline void malloc_atfork(int who)
+{
+ if (who<0) rdlock();
+ else if (who>0) resetlock();
+ else unlock();
+}
+
+#endif
diff --git a/src/malloc/mallocng/malloc.c b/src/malloc/mallocng/malloc.c
new file mode 100644
index 00000000..d695ab8e
--- /dev/null
+++ b/src/malloc/mallocng/malloc.c
@@ -0,0 +1,387 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+#include "meta.h"
+
+LOCK_OBJ_DEF;
+
+const uint16_t size_classes[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 12, 15,
+ 18, 20, 25, 31,
+ 36, 42, 50, 63,
+ 72, 84, 102, 127,
+ 146, 170, 204, 255,
+ 292, 340, 409, 511,
+ 584, 682, 818, 1023,
+ 1169, 1364, 1637, 2047,
+ 2340, 2730, 3276, 4095,
+ 4680, 5460, 6552, 8191,
+};
+
+static const uint8_t small_cnt_tab[][3] = {
+ { 30, 30, 30 },
+ { 31, 15, 15 },
+ { 20, 10, 10 },
+ { 31, 15, 7 },
+ { 25, 12, 6 },
+ { 21, 10, 5 },
+ { 18, 8, 4 },
+ { 31, 15, 7 },
+ { 28, 14, 6 },
+};
+
+static const uint8_t med_cnt_tab[4] = { 28, 24, 20, 32 };
+
+struct malloc_context ctx = { 0 };
+
+struct meta *alloc_meta(void)
+{
+ struct meta *m;
+ unsigned char *p;
+ if (!ctx.init_done) {
+#ifndef PAGESIZE
+ ctx.pagesize = get_page_size();
+#endif
+ ctx.secret = get_random_secret();
+ ctx.init_done = 1;
+ }
+ size_t pagesize = PGSZ;
+ if (pagesize < 4096) pagesize = 4096;
+ if ((m = dequeue_head(&ctx.free_meta_head))) return m;
+ if (!ctx.avail_meta_count) {
+ int need_unprotect = 1;
+ if (!ctx.avail_meta_area_count && ctx.brk!=-1) {
+ uintptr_t new = ctx.brk + pagesize;
+ int need_guard = 0;
+ if (!ctx.brk) {
+ need_guard = 1;
+ ctx.brk = brk(0);
+ // some ancient kernels returned _ebss
+ // instead of next page as initial brk.
+ ctx.brk += -ctx.brk & (pagesize-1);
+ new = ctx.brk + 2*pagesize;
+ }
+ if (brk(new) != new) {
+ ctx.brk = -1;
+ } else {
+ if (need_guard) mmap((void *)ctx.brk, pagesize,
+ PROT_NONE, MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 0);
+ ctx.brk = new;
+ ctx.avail_meta_areas = (void *)(new - pagesize);
+ ctx.avail_meta_area_count = pagesize>>12;
+ need_unprotect = 0;
+ }
+ }
+ if (!ctx.avail_meta_area_count) {
+ size_t n = 2UL << ctx.meta_alloc_shift;
+ p = mmap(0, n*pagesize, PROT_NONE,
+ MAP_PRIVATE|MAP_ANON, -1, 0);
+ if (p==MAP_FAILED) return 0;
+ ctx.avail_meta_areas = p + pagesize;
+ ctx.avail_meta_area_count = (n-1)*(pagesize>>12);
+ ctx.meta_alloc_shift++;
+ }
+ p = ctx.avail_meta_areas;
+ if ((uintptr_t)p & (pagesize-1)) need_unprotect = 0;
+ if (need_unprotect)
+ if (mprotect(p, pagesize, PROT_READ|PROT_WRITE)
+ && errno != ENOSYS)
+ return 0;
+ ctx.avail_meta_area_count--;
+ ctx.avail_meta_areas = p + 4096;
+ if (ctx.meta_area_tail) {
+ ctx.meta_area_tail->next = (void *)p;
+ } else {
+ ctx.meta_area_head = (void *)p;
+ }
+ ctx.meta_area_tail = (void *)p;
+ ctx.meta_area_tail->check = ctx.secret;
+ ctx.avail_meta_count = ctx.meta_area_tail->nslots
+ = (4096-sizeof(struct meta_area))/sizeof *m;
+ ctx.avail_meta = ctx.meta_area_tail->slots;
+ }
+ ctx.avail_meta_count--;
+ m = ctx.avail_meta++;
+ m->prev = m->next = 0;
+ return m;
+}
+
+static uint32_t try_avail(struct meta **pm)
+{
+ struct meta *m = *pm;
+ uint32_t first;
+ if (!m) return 0;
+ uint32_t mask = m->avail_mask;
+ if (!mask) {
+ if (!m) return 0;
+ if (!m->freed_mask) {
+ dequeue(pm, m);
+ m = *pm;
+ if (!m) return 0;
+ } else {
+ m = m->next;
+ *pm = m;
+ }
+
+ mask = m->freed_mask;
+
+ // skip fully-free group unless it's the only one
+ // or it's a permanently non-freeable group
+ if (mask == (2u<<m->last_idx)-1 && m->freeable) {
+ m = m->next;
+ *pm = m;
+ mask = m->freed_mask;
+ }
+
+ // activate more slots in a not-fully-active group
+ // if needed, but only as a last resort. prefer using
+ // any other group with free slots. this avoids
+ // touching & dirtying as-yet-unused pages.
+ if (!(mask & ((2u<<m->mem->active_idx)-1))) {
+ if (m->next != m) {
+ m = m->next;
+ *pm = m;
+ } else {
+ int cnt = m->mem->active_idx + 2;
+ int size = size_classes[m->sizeclass]*UNIT;
+ int span = UNIT + size*cnt;
+ // activate up to next 4k boundary
+ while ((span^(span+size-1)) < 4096) {
+ cnt++;
+ span += size;
+ }
+ if (cnt > m->last_idx+1)
+ cnt = m->last_idx+1;
+ m->mem->active_idx = cnt-1;
+ }
+ }
+ mask = activate_group(m);
+ assert(mask);
+ decay_bounces(m->sizeclass);
+ }
+ first = mask&-mask;
+ m->avail_mask = mask-first;
+ return first;
+}
+
+static int alloc_slot(int, size_t);
+
+static struct meta *alloc_group(int sc, size_t req)
+{
+ size_t size = UNIT*size_classes[sc];
+ int i = 0, cnt;
+ unsigned char *p;
+ struct meta *m = alloc_meta();
+ if (!m) return 0;
+ size_t usage = ctx.usage_by_class[sc];
+ size_t pagesize = PGSZ;
+ int active_idx;
+ if (sc < 9) {
+ while (i<2 && 4*small_cnt_tab[sc][i] > usage)
+ i++;
+ cnt = small_cnt_tab[sc][i];
+ } else {
+ // lookup max number of slots fitting in power-of-two size
+ // from a table, along with number of factors of two we
+ // can divide out without a remainder or reaching 1.
+ cnt = med_cnt_tab[sc&3];
+
+ // reduce cnt to avoid excessive eagar allocation.
+ while (!(cnt&1) && 4*cnt > usage)
+ cnt >>= 1;
+
+ // data structures don't support groups whose slot offsets
+ // in units don't fit in 16 bits.
+ while (size*cnt >= 65536*UNIT)
+ cnt >>= 1;
+ }
+
+ // If we selected a count of 1 above but it's not sufficient to use
+ // mmap, increase to 2. Then it might be; if not it will nest.
+ if (cnt==1 && size*cnt+UNIT <= pagesize/2) cnt = 2;
+
+ // All choices of size*cnt are "just below" a power of two, so anything
+ // larger than half the page size should be allocated as whole pages.
+ if (size*cnt+UNIT > pagesize/2) {
+ // check/update bounce counter to start/increase retention
+ // of freed maps, and inhibit use of low-count, odd-size
+ // small mappings and single-slot groups if activated.
+ int nosmall = is_bouncing(sc);
+ account_bounce(sc);
+ step_seq();
+
+ // since the following count reduction opportunities have
+ // an absolute memory usage cost, don't overdo them. count
+ // coarse usage as part of usage.
+ if (!(sc&1) && sc<32) usage += ctx.usage_by_class[sc+1];
+
+ // try to drop to a lower count if the one found above
+ // increases usage by more than 25%. these reduced counts
+ // roughly fill an integral number of pages, just not a
+ // power of two, limiting amount of unusable space.
+ if (4*cnt > usage && !nosmall) {
+ if (0);
+ else if ((sc&3)==1 && size*cnt>8*pagesize) cnt = 2;
+ else if ((sc&3)==2 && size*cnt>4*pagesize) cnt = 3;
+ else if ((sc&3)==0 && size*cnt>8*pagesize) cnt = 3;
+ else if ((sc&3)==0 && size*cnt>2*pagesize) cnt = 5;
+ }
+ size_t needed = size*cnt + UNIT;
+ needed += -needed & (pagesize-1);
+
+ // produce an individually-mmapped allocation if usage is low,
+ // bounce counter hasn't triggered, and either it saves memory
+ // or it avoids eagar slot allocation without wasting too much.
+ if (!nosmall && cnt<=7) {
+ req += IB + UNIT;
+ req += -req & (pagesize-1);
+ if (req<size+UNIT || (req>=4*pagesize && 2*cnt>usage)) {
+ cnt = 1;
+ needed = req;
+ }
+ }
+
+ p = mmap(0, needed, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
+ if (p==MAP_FAILED) {
+ free_meta(m);
+ return 0;
+ }
+ m->maplen = needed>>12;
+ ctx.mmap_counter++;
+ active_idx = (4096-UNIT)/size-1;
+ if (active_idx > cnt-1) active_idx = cnt-1;
+ if (active_idx < 0) active_idx = 0;
+ } else {
+ int j = size_to_class(UNIT+cnt*size-IB);
+ int idx = alloc_slot(j, UNIT+cnt*size-IB);
+ if (idx < 0) {
+ free_meta(m);
+ return 0;
+ }
+ struct meta *g = ctx.active[j];
+ p = enframe(g, idx, UNIT*size_classes[j]-IB, ctx.mmap_counter);
+ m->maplen = 0;
+ p[-3] = (p[-3]&31) | (6<<5);
+ for (int i=0; i<=cnt; i++)
+ p[UNIT+i*size-4] = 0;
+ active_idx = cnt-1;
+ }
+ ctx.usage_by_class[sc] += cnt;
+ m->avail_mask = (2u<<active_idx)-1;
+ m->freed_mask = (2u<<(cnt-1))-1 - m->avail_mask;
+ m->mem = (void *)p;
+ m->mem->meta = m;
+ m->mem->active_idx = active_idx;
+ m->last_idx = cnt-1;
+ m->freeable = 1;
+ m->sizeclass = sc;
+ return m;
+}
+
+static int alloc_slot(int sc, size_t req)
+{
+ uint32_t first = try_avail(&ctx.active[sc]);
+ if (first) return a_ctz_32(first);
+
+ struct meta *g = alloc_group(sc, req);
+ if (!g) return -1;
+
+ g->avail_mask--;
+ queue(&ctx.active[sc], g);
+ return 0;
+}
+
+void *malloc(size_t n)
+{
+ if (size_overflows(n)) return 0;
+ struct meta *g;
+ uint32_t mask, first;
+ int sc;
+ int idx;
+ int ctr;
+
+ if (n >= MMAP_THRESHOLD) {
+ size_t needed = n + IB + UNIT;
+ void *p = mmap(0, needed, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_ANON, -1, 0);
+ if (p==MAP_FAILED) return 0;
+ wrlock();
+ step_seq();
+ g = alloc_meta();
+ if (!g) {
+ unlock();
+ munmap(p, needed);
+ return 0;
+ }
+ g->mem = p;
+ g->mem->meta = g;
+ g->last_idx = 0;
+ g->freeable = 1;
+ g->sizeclass = 63;
+ g->maplen = (needed+4095)/4096;
+ g->avail_mask = g->freed_mask = 0;
+ // use a global counter to cycle offset in
+ // individually-mmapped allocations.
+ ctx.mmap_counter++;
+ idx = 0;
+ goto success;
+ }
+
+ sc = size_to_class(n);
+
+ rdlock();
+ g = ctx.active[sc];
+
+ // use coarse size classes initially when there are not yet
+ // any groups of desired size. this allows counts of 2 or 3
+ // to be allocated at first rather than having to start with
+ // 7 or 5, the min counts for even size classes.
+ if (!g && sc>=4 && sc<32 && sc!=6 && !(sc&1) && !ctx.usage_by_class[sc]) {
+ size_t usage = ctx.usage_by_class[sc|1];
+ // if a new group may be allocated, count it toward
+ // usage in deciding if we can use coarse class.
+ if (!ctx.active[sc|1] || (!ctx.active[sc|1]->avail_mask
+ && !ctx.active[sc|1]->freed_mask))
+ usage += 3;
+ if (usage <= 12)
+ sc |= 1;
+ g = ctx.active[sc];
+ }
+
+ for (;;) {
+ mask = g ? g->avail_mask : 0;
+ first = mask&-mask;
+ if (!first) break;
+ if (RDLOCK_IS_EXCLUSIVE || !MT)
+ g->avail_mask = mask-first;
+ else if (a_cas(&g->avail_mask, mask, mask-first)!=mask)
+ continue;
+ idx = a_ctz_32(first);
+ goto success;
+ }
+ upgradelock();
+
+ idx = alloc_slot(sc, n);
+ if (idx < 0) {
+ unlock();
+ return 0;
+ }
+ g = ctx.active[sc];
+
+success:
+ ctr = ctx.mmap_counter;
+ unlock();
+ return enframe(g, idx, n, ctr);
+}
+
+int is_allzero(void *p)
+{
+ struct meta *g = get_meta(p);
+ return g->sizeclass >= 48 ||
+ get_stride(g) < UNIT*size_classes[g->sizeclass];
+}
diff --git a/src/malloc/mallocng/malloc_usable_size.c b/src/malloc/mallocng/malloc_usable_size.c
new file mode 100644
index 00000000..ce6a960c
--- /dev/null
+++ b/src/malloc/mallocng/malloc_usable_size.c
@@ -0,0 +1,13 @@
+#include <stdlib.h>
+#include "meta.h"
+
+size_t malloc_usable_size(void *p)
+{
+ if (!p) return 0;
+ struct meta *g = get_meta(p);
+ int idx = get_slot_index(p);
+ size_t stride = get_stride(g);
+ unsigned char *start = g->mem->storage + stride*idx;
+ unsigned char *end = start + stride - IB;
+ return get_nominal_size(p, end);
+}
diff --git a/src/malloc/mallocng/meta.h b/src/malloc/mallocng/meta.h
new file mode 100644
index 00000000..61ec53f9
--- /dev/null
+++ b/src/malloc/mallocng/meta.h
@@ -0,0 +1,288 @@
+#ifndef MALLOC_META_H
+#define MALLOC_META_H
+
+#include <stdint.h>
+#include <errno.h>
+#include <limits.h>
+#include "glue.h"
+
+__attribute__((__visibility__("hidden")))
+extern const uint16_t size_classes[];
+
+#define MMAP_THRESHOLD 131052
+
+#define UNIT 16
+#define IB 4
+
+struct group {
+ struct meta *meta;
+ unsigned char active_idx:5;
+ char pad[UNIT - sizeof(struct meta *) - 1];
+ unsigned char storage[];
+};
+
+struct meta {
+ struct meta *prev, *next;
+ struct group *mem;
+ volatile int avail_mask, freed_mask;
+ uintptr_t last_idx:5;
+ uintptr_t freeable:1;
+ uintptr_t sizeclass:6;
+ uintptr_t maplen:8*sizeof(uintptr_t)-12;
+};
+
+struct meta_area {
+ uint64_t check;
+ struct meta_area *next;
+ int nslots;
+ struct meta slots[];
+};
+
+struct malloc_context {
+ uint64_t secret;
+#ifndef PAGESIZE
+ size_t pagesize;
+#endif
+ int init_done;
+ unsigned mmap_counter;
+ struct meta *free_meta_head;
+ struct meta *avail_meta;
+ size_t avail_meta_count, avail_meta_area_count, meta_alloc_shift;
+ struct meta_area *meta_area_head, *meta_area_tail;
+ unsigned char *avail_meta_areas;
+ struct meta *active[48];
+ size_t usage_by_class[48];
+ uint8_t unmap_seq[32], bounces[32];
+ uint8_t seq;
+ uintptr_t brk;
+};
+
+__attribute__((__visibility__("hidden")))
+extern struct malloc_context ctx;
+
+#ifdef PAGESIZE
+#define PGSZ PAGESIZE
+#else
+#define PGSZ ctx.pagesize
+#endif
+
+__attribute__((__visibility__("hidden")))
+struct meta *alloc_meta(void);
+
+__attribute__((__visibility__("hidden")))
+int is_allzero(void *);
+
+static inline void queue(struct meta **phead, struct meta *m)
+{
+ assert(!m->next);
+ assert(!m->prev);
+ if (*phead) {
+ struct meta *head = *phead;
+ m->next = head;
+ m->prev = head->prev;
+ m->next->prev = m->prev->next = m;
+ } else {
+ m->prev = m->next = m;
+ *phead = m;
+ }
+}
+
+static inline void dequeue(struct meta **phead, struct meta *m)
+{
+ if (m->next != m) {
+ m->prev->next = m->next;
+ m->next->prev = m->prev;
+ if (*phead == m) *phead = m->next;
+ } else {
+ *phead = 0;
+ }
+ m->prev = m->next = 0;
+}
+
+static inline struct meta *dequeue_head(struct meta **phead)
+{
+ struct meta *m = *phead;
+ if (m) dequeue(phead, m);
+ return m;
+}
+
+static inline void free_meta(struct meta *m)
+{
+ *m = (struct meta){0};
+ queue(&ctx.free_meta_head, m);
+}
+
+static inline uint32_t activate_group(struct meta *m)
+{
+ assert(!m->avail_mask);
+ uint32_t mask, act = (2u<<m->mem->active_idx)-1;
+ do mask = m->freed_mask;
+ while (a_cas(&m->freed_mask, mask, mask&~act)!=mask);
+ return m->avail_mask = mask & act;
+}
+
+static inline int get_slot_index(const unsigned char *p)
+{
+ return p[-3] & 31;
+}
+
+static inline struct meta *get_meta(const unsigned char *p)
+{
+ assert(!((uintptr_t)p & 15));
+ int offset = *(const uint16_t *)(p - 2);
+ int index = get_slot_index(p);
+ if (p[-4]) {
+ assert(!offset);
+ offset = *(uint32_t *)(p - 8);
+ assert(offset > 0xffff);
+ }
+ const struct group *base = (const void *)(p - UNIT*offset - UNIT);
+ const struct meta *meta = base->meta;
+ assert(meta->mem == base);
+ assert(index <= meta->last_idx);
+ assert(!(meta->avail_mask & (1u<<index)));
+ assert(!(meta->freed_mask & (1u<<index)));
+ const struct meta_area *area = (void *)((uintptr_t)meta & -4096);
+ assert(area->check == ctx.secret);
+ if (meta->sizeclass < 48) {
+ assert(offset >= size_classes[meta->sizeclass]*index);
+ assert(offset < size_classes[meta->sizeclass]*(index+1));
+ } else {
+ assert(meta->sizeclass == 63);
+ }
+ if (meta->maplen) {
+ assert(offset <= meta->maplen*4096UL/UNIT - 1);
+ }
+ return (struct meta *)meta;
+}
+
+static inline size_t get_nominal_size(const unsigned char *p, const unsigned char *end)
+{
+ size_t reserved = p[-3] >> 5;
+ if (reserved >= 5) {
+ assert(reserved == 5);
+ reserved = *(const uint32_t *)(end-4);
+ assert(reserved >= 5);
+ assert(!end[-5]);
+ }
+ assert(reserved <= end-p);
+ assert(!*(end-reserved));
+ // also check the slot's overflow byte
+ assert(!*end);
+ return end-reserved-p;
+}
+
+static inline size_t get_stride(const struct meta *g)
+{
+ if (!g->last_idx && g->maplen) {
+ return g->maplen*4096UL - UNIT;
+ } else {
+ return UNIT*size_classes[g->sizeclass];
+ }
+}
+
+static inline void set_size(unsigned char *p, unsigned char *end, size_t n)
+{
+ int reserved = end-p-n;
+ if (reserved) end[-reserved] = 0;
+ if (reserved >= 5) {
+ *(uint32_t *)(end-4) = reserved;
+ end[-5] = 0;
+ reserved = 5;
+ }
+ p[-3] = (p[-3]&31) + (reserved<<5);
+}
+
+static inline void *enframe(struct meta *g, int idx, size_t n, int ctr)
+{
+ size_t stride = get_stride(g);
+ size_t slack = (stride-IB-n)/UNIT;
+ unsigned char *p = g->mem->storage + stride*idx;
+ unsigned char *end = p+stride-IB;
+ // cycle offset within slot to increase interval to address
+ // reuse, facilitate trapping double-free.
+ int off = (p[-3] ? *(uint16_t *)(p-2) + 1 : ctr) & 255;
+ assert(!p[-4]);
+ if (off > slack) {
+ size_t m = slack;
+ m |= m>>1; m |= m>>2; m |= m>>4;
+ off &= m;
+ if (off > slack) off -= slack+1;
+ assert(off <= slack);
+ }
+ if (off) {
+ // store offset in unused header at offset zero
+ // if enframing at non-zero offset.
+ *(uint16_t *)(p-2) = off;
+ p[-3] = 7<<5;
+ p += UNIT*off;
+ // for nonzero offset there is no permanent check
+ // byte, so make one.
+ p[-4] = 0;
+ }
+ *(uint16_t *)(p-2) = (size_t)(p-g->mem->storage)/UNIT;
+ p[-3] = idx;
+ set_size(p, end, n);
+ return p;
+}
+
+static inline int size_to_class(size_t n)
+{
+ n = (n+IB-1)>>4;
+ if (n<10) return n;
+ n++;
+ int i = (28-a_clz_32(n))*4 + 8;
+ if (n>size_classes[i+1]) i+=2;
+ if (n>size_classes[i]) i++;
+ return i;
+}
+
+static inline int size_overflows(size_t n)
+{
+ if (n >= SIZE_MAX/2 - 4096) {
+ errno = ENOMEM;
+ return 1;
+ }
+ return 0;
+}
+
+static inline void step_seq(void)
+{
+ if (ctx.seq==255) {
+ for (int i=0; i<32; i++) ctx.unmap_seq[i] = 0;
+ ctx.seq = 1;
+ } else {
+ ctx.seq++;
+ }
+}
+
+static inline void record_seq(int sc)
+{
+ if (sc-7U < 32) ctx.unmap_seq[sc-7] = ctx.seq;
+}
+
+static inline void account_bounce(int sc)
+{
+ if (sc-7U < 32) {
+ int seq = ctx.unmap_seq[sc-7];
+ if (seq && ctx.seq-seq < 10) {
+ if (ctx.bounces[sc-7]+1 < 100)
+ ctx.bounces[sc-7]++;
+ else
+ ctx.bounces[sc-7] = 150;
+ }
+ }
+}
+
+static inline void decay_bounces(int sc)
+{
+ if (sc-7U < 32 && ctx.bounces[sc-7])
+ ctx.bounces[sc-7]--;
+}
+
+static inline int is_bouncing(int sc)
+{
+ return (sc-7U < 32 && ctx.bounces[sc-7] >= 100);
+}
+
+#endif
diff --git a/src/malloc/mallocng/realloc.c b/src/malloc/mallocng/realloc.c
new file mode 100644
index 00000000..18769f42
--- /dev/null
+++ b/src/malloc/mallocng/realloc.c
@@ -0,0 +1,51 @@
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <string.h>
+#include "meta.h"
+
+void *realloc(void *p, size_t n)
+{
+ if (!p) return malloc(n);
+ if (size_overflows(n)) return 0;
+
+ struct meta *g = get_meta(p);
+ int idx = get_slot_index(p);
+ size_t stride = get_stride(g);
+ unsigned char *start = g->mem->storage + stride*idx;
+ unsigned char *end = start + stride - IB;
+ size_t old_size = get_nominal_size(p, end);
+ size_t avail_size = end-(unsigned char *)p;
+ void *new;
+
+ // only resize in-place if size class matches
+ if (n <= avail_size && n<MMAP_THRESHOLD
+ && size_to_class(n)+1 >= g->sizeclass) {
+ set_size(p, end, n);
+ return p;
+ }
+
+ // use mremap if old and new size are both mmap-worthy
+ if (g->sizeclass>=48 && n>=MMAP_THRESHOLD) {
+ assert(g->sizeclass==63);
+ size_t base = (unsigned char *)p-start;
+ size_t needed = (n + base + UNIT + IB + 4095) & -4096;
+ new = g->maplen*4096UL == needed ? g->mem :
+ mremap(g->mem, g->maplen*4096UL, needed, MREMAP_MAYMOVE);
+ if (new!=MAP_FAILED) {
+ g->mem = new;
+ g->maplen = needed/4096;
+ p = g->mem->storage + base;
+ end = g->mem->storage + (needed - UNIT) - IB;
+ *end = 0;
+ set_size(p, end, n);
+ return p;
+ }
+ }
+
+ new = malloc(n);
+ if (!new) return 0;
+ memcpy(new, p, n < old_size ? n : old_size);
+ free(p);
+ return new;
+}
diff --git a/src/malloc/memalign.c b/src/malloc/memalign.c
index cf9dfbda..32cd87d8 100644
--- a/src/malloc/memalign.c
+++ b/src/malloc/memalign.c
@@ -1,54 +1,7 @@
+#define _BSD_SOURCE
#include <stdlib.h>
-#include <stdint.h>
-#include <errno.h>
-#include "malloc_impl.h"
-void *__memalign(size_t align, size_t len)
+void *memalign(size_t align, size_t len)
{
- unsigned char *mem, *new;
-
- if ((align & -align) != align) {
- errno = EINVAL;
- return 0;
- }
-
- if (len > SIZE_MAX - align || __malloc_replaced) {
- errno = ENOMEM;
- return 0;
- }
-
- if (align <= SIZE_ALIGN)
- return malloc(len);
-
- if (!(mem = malloc(len + align-1)))
- return 0;
-
- new = (void *)((uintptr_t)mem + align-1 & -align);
- if (new == mem) return mem;
-
- struct chunk *c = MEM_TO_CHUNK(mem);
- struct chunk *n = MEM_TO_CHUNK(new);
-
- if (IS_MMAPPED(c)) {
- /* Apply difference between aligned and original
- * address to the "extra" field of mmapped chunk. */
- n->psize = c->psize + (new-mem);
- n->csize = c->csize - (new-mem);
- return new;
- }
-
- struct chunk *t = NEXT_CHUNK(c);
-
- /* Split the allocated chunk into two chunks. The aligned part
- * that will be used has the size in its footer reduced by the
- * difference between the aligned and original addresses, and
- * the resulting size copied to its header. A new header and
- * footer are written for the split-off part to be freed. */
- n->psize = c->csize = C_INUSE | (new-mem);
- n->csize = t->psize -= new-mem;
-
- __bin_chunk(c);
- return new;
+ return aligned_alloc(align, len);
}
-
-weak_alias(__memalign, memalign);
diff --git a/src/malloc/oldmalloc/aligned_alloc.c b/src/malloc/oldmalloc/aligned_alloc.c
new file mode 100644
index 00000000..4adca3b4
--- /dev/null
+++ b/src/malloc/oldmalloc/aligned_alloc.c
@@ -0,0 +1,53 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include "malloc_impl.h"
+
+void *aligned_alloc(size_t align, size_t len)
+{
+ unsigned char *mem, *new;
+
+ if ((align & -align) != align) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ if (len > SIZE_MAX - align ||
+ (__malloc_replaced && !__aligned_alloc_replaced)) {
+ errno = ENOMEM;
+ return 0;
+ }
+
+ if (align <= SIZE_ALIGN)
+ return malloc(len);
+
+ if (!(mem = malloc(len + align-1)))
+ return 0;
+
+ new = (void *)((uintptr_t)mem + align-1 & -align);
+ if (new == mem) return mem;
+
+ struct chunk *c = MEM_TO_CHUNK(mem);
+ struct chunk *n = MEM_TO_CHUNK(new);
+
+ if (IS_MMAPPED(c)) {
+ /* Apply difference between aligned and original
+ * address to the "extra" field of mmapped chunk. */
+ n->psize = c->psize + (new-mem);
+ n->csize = c->csize - (new-mem);
+ return new;
+ }
+
+ struct chunk *t = NEXT_CHUNK(c);
+
+ /* Split the allocated chunk into two chunks. The aligned part
+ * that will be used has the size in its footer reduced by the
+ * difference between the aligned and original addresses, and
+ * the resulting size copied to its header. A new header and
+ * footer are written for the split-off part to be freed. */
+ n->psize = c->csize = C_INUSE | (new-mem);
+ n->csize = t->psize -= new-mem;
+
+ __bin_chunk(c);
+ return new;
+}
diff --git a/src/malloc/malloc.c b/src/malloc/oldmalloc/malloc.c
index 96982596..25d00d44 100644
--- a/src/malloc/malloc.c
+++ b/src/malloc/oldmalloc/malloc.c
@@ -9,6 +9,11 @@
#include "atomic.h"
#include "pthread_impl.h"
#include "malloc_impl.h"
+#include "fork_impl.h"
+
+#define malloc __libc_malloc_impl
+#define realloc __libc_realloc
+#define free __libc_free
#if defined(__GNUC__) && defined(__PIC__)
#define inline inline __attribute__((always_inline))
@@ -17,17 +22,18 @@
static struct {
volatile uint64_t binmap;
struct bin bins[64];
- volatile int free_lock[2];
+ volatile int split_merge_lock[2];
} mal;
-int __malloc_replaced;
-
/* Synchronization tools */
static inline void lock(volatile int *lk)
{
- if (libc.threads_minus_1)
+ int need_locks = libc.need_locks;
+ if (need_locks) {
while(a_swap(lk, 1)) __wait(lk, lk+1, 1, 1);
+ if (need_locks < 0) libc.need_locks = 0;
+ }
}
static inline void unlock(volatile int *lk)
@@ -123,9 +129,72 @@ void __dump_heap(int x)
}
#endif
+/* This function returns true if the interval [old,new]
+ * intersects the 'len'-sized interval below &libc.auxv
+ * (interpreted as the main-thread stack) or below &b
+ * (the current stack). It is used to defend against
+ * buggy brk implementations that can cross the stack. */
+
+static int traverses_stack_p(uintptr_t old, uintptr_t new)
+{
+ const uintptr_t len = 8<<20;
+ uintptr_t a, b;
+
+ b = (uintptr_t)libc.auxv;
+ a = b > len ? b-len : 0;
+ if (new>a && old<b) return 1;
+
+ b = (uintptr_t)&b;
+ a = b > len ? b-len : 0;
+ if (new>a && old<b) return 1;
+
+ return 0;
+}
+
+/* Expand the heap in-place if brk can be used, or otherwise via mmap,
+ * using an exponential lower bound on growth by mmap to make
+ * fragmentation asymptotically irrelevant. The size argument is both
+ * an input and an output, since the caller needs to know the size
+ * allocated, which will be larger than requested due to page alignment
+ * and mmap minimum size rules. The caller is responsible for locking
+ * to prevent concurrent calls. */
+
+static void *__expand_heap(size_t *pn)
+{
+ static uintptr_t brk;
+ static unsigned mmap_step;
+ size_t n = *pn;
+
+ if (n > SIZE_MAX/2 - PAGE_SIZE) {
+ errno = ENOMEM;
+ return 0;
+ }
+ n += -n & PAGE_SIZE-1;
+
+ if (!brk) {
+ brk = __syscall(SYS_brk, 0);
+ brk += -brk & PAGE_SIZE-1;
+ }
+
+ if (n < SIZE_MAX-brk && !traverses_stack_p(brk, brk+n)
+ && __syscall(SYS_brk, brk+n)==brk+n) {
+ *pn = n;
+ brk += n;
+ return (void *)(brk-n);
+ }
+
+ size_t min = (size_t)PAGE_SIZE << mmap_step/2;
+ if (n < min) n = min;
+ void *area = __mmap(0, n, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+ if (area == MAP_FAILED) return 0;
+ *pn = n;
+ mmap_step++;
+ return area;
+}
+
static struct chunk *expand_heap(size_t n)
{
- static int heap_lock[2];
static void *end;
void *p;
struct chunk *w;
@@ -135,13 +204,8 @@ static struct chunk *expand_heap(size_t n)
* we need room for an extra zero-sized sentinel chunk. */
n += SIZE_ALIGN;
- lock(heap_lock);
-
p = __expand_heap(&n);
- if (!p) {
- unlock(heap_lock);
- return 0;
- }
+ if (!p) return 0;
/* If not just expanding existing space, we need to make a
* new sentinel chunk below the allocated space. */
@@ -164,8 +228,6 @@ static struct chunk *expand_heap(size_t n)
w = MEM_TO_CHUNK(p);
w->csize = n | C_INUSE;
- unlock(heap_lock);
-
return w;
}
@@ -195,96 +257,44 @@ static void unbin(struct chunk *c, int i)
NEXT_CHUNK(c)->psize |= C_INUSE;
}
-static int alloc_fwd(struct chunk *c)
+static void bin_chunk(struct chunk *self, int i)
{
- int i;
- size_t k;
- while (!((k=c->csize) & C_INUSE)) {
- i = bin_index(k);
- lock_bin(i);
- if (c->csize == k) {
- unbin(c, i);
- unlock_bin(i);
- return 1;
- }
- unlock_bin(i);
- }
- return 0;
-}
-
-static int alloc_rev(struct chunk *c)
-{
- int i;
- size_t k;
- while (!((k=c->psize) & C_INUSE)) {
- i = bin_index(k);
- lock_bin(i);
- if (c->psize == k) {
- unbin(PREV_CHUNK(c), i);
- unlock_bin(i);
- return 1;
- }
- unlock_bin(i);
- }
- return 0;
+ self->next = BIN_TO_CHUNK(i);
+ self->prev = mal.bins[i].tail;
+ self->next->prev = self;
+ self->prev->next = self;
+ if (self->prev == BIN_TO_CHUNK(i))
+ a_or_64(&mal.binmap, 1ULL<<i);
}
-
-/* pretrim - trims a chunk _prior_ to removing it from its bin.
- * Must be called with i as the ideal bin for size n, j the bin
- * for the _free_ chunk self, and bin j locked. */
-static int pretrim(struct chunk *self, size_t n, int i, int j)
+static void trim(struct chunk *self, size_t n)
{
- size_t n1;
+ size_t n1 = CHUNK_SIZE(self);
struct chunk *next, *split;
- /* We cannot pretrim if it would require re-binning. */
- if (j < 40) return 0;
- if (j < i+3) {
- if (j != 63) return 0;
- n1 = CHUNK_SIZE(self);
- if (n1-n <= MMAP_THRESHOLD) return 0;
- } else {
- n1 = CHUNK_SIZE(self);
- }
- if (bin_index(n1-n) != j) return 0;
+ if (n >= n1 - DONTCARE) return;
next = NEXT_CHUNK(self);
split = (void *)((char *)self + n);
- split->prev = self->prev;
- split->next = self->next;
- split->prev->next = split;
- split->next->prev = split;
split->psize = n | C_INUSE;
split->csize = n1-n;
next->psize = n1-n;
self->csize = n | C_INUSE;
- return 1;
-}
-static void trim(struct chunk *self, size_t n)
-{
- size_t n1 = CHUNK_SIZE(self);
- struct chunk *next, *split;
+ int i = bin_index(n1-n);
+ lock_bin(i);
- if (n >= n1 - DONTCARE) return;
+ bin_chunk(split, i);
- next = NEXT_CHUNK(self);
- split = (void *)((char *)self + n);
-
- split->psize = n | C_INUSE;
- split->csize = n1-n | C_INUSE;
- next->psize = n1-n | C_INUSE;
- self->csize = n | C_INUSE;
-
- __bin_chunk(split);
+ unlock_bin(i);
}
void *malloc(size_t n)
{
struct chunk *c;
int i, j;
+ uint64_t mask;
if (adjust_size(&n) < 0) return 0;
@@ -300,70 +310,43 @@ void *malloc(size_t n)
}
i = bin_index_up(n);
- for (;;) {
- uint64_t mask = mal.binmap & -(1ULL<<i);
- if (!mask) {
- c = expand_heap(n);
- if (!c) return 0;
- if (alloc_rev(c)) {
- struct chunk *x = c;
- c = PREV_CHUNK(c);
- NEXT_CHUNK(x)->psize = c->csize =
- x->csize + CHUNK_SIZE(c);
- }
- break;
+ if (i<63 && (mal.binmap & (1ULL<<i))) {
+ lock_bin(i);
+ c = mal.bins[i].head;
+ if (c != BIN_TO_CHUNK(i) && CHUNK_SIZE(c)-n <= DONTCARE) {
+ unbin(c, i);
+ unlock_bin(i);
+ return CHUNK_TO_MEM(c);
}
+ unlock_bin(i);
+ }
+ lock(mal.split_merge_lock);
+ for (mask = mal.binmap & -(1ULL<<i); mask; mask -= (mask&-mask)) {
j = first_set(mask);
lock_bin(j);
c = mal.bins[j].head;
if (c != BIN_TO_CHUNK(j)) {
- if (!pretrim(c, n, i, j)) unbin(c, j);
+ unbin(c, j);
unlock_bin(j);
break;
}
unlock_bin(j);
}
-
- /* Now patch up in case we over-allocated */
+ if (!mask) {
+ c = expand_heap(n);
+ if (!c) {
+ unlock(mal.split_merge_lock);
+ return 0;
+ }
+ }
trim(c, n);
-
+ unlock(mal.split_merge_lock);
return CHUNK_TO_MEM(c);
}
-static size_t mal0_clear(char *p, size_t pagesz, size_t n)
+int __malloc_allzerop(void *p)
{
-#ifdef __GNUC__
- typedef uint64_t __attribute__((__may_alias__)) T;
-#else
- typedef unsigned char T;
-#endif
- char *pp = p + n;
- size_t i = (uintptr_t)pp & (pagesz - 1);
- for (;;) {
- pp = memset(pp - i, 0, i);
- if (pp - p < pagesz) return pp - p;
- for (i = pagesz; i; i -= 2*sizeof(T), pp -= 2*sizeof(T))
- if (((T *)pp)[-1] | ((T *)pp)[-2])
- break;
- }
-}
-
-void *calloc(size_t m, size_t n)
-{
- if (n && m > (size_t)-1/n) {
- errno = ENOMEM;
- return 0;
- }
- n *= m;
- void *p = malloc(n);
- if (!p) return p;
- if (!__malloc_replaced) {
- if (IS_MMAPPED(MEM_TO_CHUNK(p)))
- return p;
- if (n >= PAGE_SIZE)
- n = mal0_clear(p, PAGE_SIZE, n);
- }
- return memset(p, 0, n);
+ return IS_MMAPPED(MEM_TO_CHUNK(p));
}
void *realloc(void *p, size_t n)
@@ -379,6 +362,8 @@ void *realloc(void *p, size_t n)
self = MEM_TO_CHUNK(p);
n1 = n0 = CHUNK_SIZE(self);
+ if (n<=n0 && n0-n<=DONTCARE) return p;
+
if (IS_MMAPPED(self)) {
size_t extra = self->psize;
char *base = (char *)self - extra;
@@ -405,34 +390,43 @@ void *realloc(void *p, size_t n)
/* Crash on corrupted footer (likely from buffer overflow) */
if (next->psize != self->csize) a_crash();
- /* Merge adjacent chunks if we need more space. This is not
- * a waste of time even if we fail to get enough space, because our
- * subsequent call to free would otherwise have to do the merge. */
- if (n > n1 && alloc_fwd(next)) {
- n1 += CHUNK_SIZE(next);
- next = NEXT_CHUNK(next);
- }
- /* FIXME: find what's wrong here and reenable it..? */
- if (0 && n > n1 && alloc_rev(self)) {
- self = PREV_CHUNK(self);
- n1 += CHUNK_SIZE(self);
+ if (n < n0) {
+ int i = bin_index_up(n);
+ int j = bin_index(n0);
+ if (i<j && (mal.binmap & (1ULL << i)))
+ goto copy_realloc;
+ struct chunk *split = (void *)((char *)self + n);
+ self->csize = split->psize = n | C_INUSE;
+ split->csize = next->psize = n0-n | C_INUSE;
+ __bin_chunk(split);
+ return CHUNK_TO_MEM(self);
}
- self->csize = n1 | C_INUSE;
- next->psize = n1 | C_INUSE;
- /* If we got enough space, split off the excess and return */
- if (n <= n1) {
- //memmove(CHUNK_TO_MEM(self), p, n0-OVERHEAD);
- trim(self, n);
- return CHUNK_TO_MEM(self);
+ lock(mal.split_merge_lock);
+
+ size_t nsize = next->csize & C_INUSE ? 0 : CHUNK_SIZE(next);
+ if (n0+nsize >= n) {
+ int i = bin_index(nsize);
+ lock_bin(i);
+ if (!(next->csize & C_INUSE)) {
+ unbin(next, i);
+ unlock_bin(i);
+ next = NEXT_CHUNK(next);
+ self->csize = next->psize = n0+nsize | C_INUSE;
+ trim(self, n);
+ unlock(mal.split_merge_lock);
+ return CHUNK_TO_MEM(self);
+ }
+ unlock_bin(i);
}
+ unlock(mal.split_merge_lock);
copy_realloc:
/* As a last resort, allocate a new chunk and copy to it. */
new = malloc(n-OVERHEAD);
if (!new) return 0;
copy_free_ret:
- memcpy(new, p, n0-OVERHEAD);
+ memcpy(new, p, (n<n0 ? n : n0) - OVERHEAD);
free(CHUNK_TO_MEM(self));
return new;
}
@@ -440,67 +434,61 @@ copy_free_ret:
void __bin_chunk(struct chunk *self)
{
struct chunk *next = NEXT_CHUNK(self);
- size_t final_size, new_size, size;
- int reclaim=0;
- int i;
-
- final_size = new_size = CHUNK_SIZE(self);
/* Crash on corrupted footer (likely from buffer overflow) */
if (next->psize != self->csize) a_crash();
- for (;;) {
- if (self->psize & next->csize & C_INUSE) {
- self->csize = final_size | C_INUSE;
- next->psize = final_size | C_INUSE;
- i = bin_index(final_size);
- lock_bin(i);
- lock(mal.free_lock);
- if (self->psize & next->csize & C_INUSE)
- break;
- unlock(mal.free_lock);
- unlock_bin(i);
- }
+ lock(mal.split_merge_lock);
- if (alloc_rev(self)) {
- self = PREV_CHUNK(self);
- size = CHUNK_SIZE(self);
- final_size += size;
- if (new_size+size > RECLAIM && (new_size+size^size) > size)
- reclaim = 1;
- }
+ size_t osize = CHUNK_SIZE(self), size = osize;
+
+ /* Since we hold split_merge_lock, only transition from free to
+ * in-use can race; in-use to free is impossible */
+ size_t psize = self->psize & C_INUSE ? 0 : CHUNK_PSIZE(self);
+ size_t nsize = next->csize & C_INUSE ? 0 : CHUNK_SIZE(next);
- if (alloc_fwd(next)) {
- size = CHUNK_SIZE(next);
- final_size += size;
- if (new_size+size > RECLAIM && (new_size+size^size) > size)
- reclaim = 1;
+ if (psize) {
+ int i = bin_index(psize);
+ lock_bin(i);
+ if (!(self->psize & C_INUSE)) {
+ struct chunk *prev = PREV_CHUNK(self);
+ unbin(prev, i);
+ self = prev;
+ size += psize;
+ }
+ unlock_bin(i);
+ }
+ if (nsize) {
+ int i = bin_index(nsize);
+ lock_bin(i);
+ if (!(next->csize & C_INUSE)) {
+ unbin(next, i);
next = NEXT_CHUNK(next);
+ size += nsize;
}
+ unlock_bin(i);
}
- if (!(mal.binmap & 1ULL<<i))
- a_or_64(&mal.binmap, 1ULL<<i);
+ int i = bin_index(size);
+ lock_bin(i);
- self->csize = final_size;
- next->psize = final_size;
- unlock(mal.free_lock);
-
- self->next = BIN_TO_CHUNK(i);
- self->prev = mal.bins[i].tail;
- self->next->prev = self;
- self->prev->next = self;
+ self->csize = size;
+ next->psize = size;
+ bin_chunk(self, i);
+ unlock(mal.split_merge_lock);
/* Replace middle of large chunks with fresh zero pages */
- if (reclaim) {
+ if (size > RECLAIM && (size^(size-osize)) > size-osize) {
uintptr_t a = (uintptr_t)self + SIZE_ALIGN+PAGE_SIZE-1 & -PAGE_SIZE;
uintptr_t b = (uintptr_t)next - SIZE_ALIGN & -PAGE_SIZE;
+ int e = errno;
#if 1
__madvise((void *)a, b-a, MADV_DONTNEED);
#else
__mmap((void *)a, b-a, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
#endif
+ errno = e;
}
unlock_bin(i);
@@ -513,7 +501,9 @@ static void unmap_chunk(struct chunk *self)
size_t len = CHUNK_SIZE(self) + extra;
/* Crash on double free */
if (extra & 1) a_crash();
+ int e = errno;
__munmap(base, len);
+ errno = e;
}
void free(void *p)
@@ -546,3 +536,21 @@ void __malloc_donate(char *start, char *end)
c->csize = n->psize = C_INUSE | (end-start);
__bin_chunk(c);
}
+
+void __malloc_atfork(int who)
+{
+ if (who<0) {
+ lock(mal.split_merge_lock);
+ for (int i=0; i<64; i++)
+ lock(mal.bins[i].lock);
+ } else if (!who) {
+ for (int i=0; i<64; i++)
+ unlock(mal.bins[i].lock);
+ unlock(mal.split_merge_lock);
+ } else {
+ for (int i=0; i<64; i++)
+ mal.bins[i].lock[0] = mal.bins[i].lock[1] = 0;
+ mal.split_merge_lock[1] = 0;
+ mal.split_merge_lock[0] = 0;
+ }
+}
diff --git a/src/malloc/oldmalloc/malloc_impl.h b/src/malloc/oldmalloc/malloc_impl.h
new file mode 100644
index 00000000..e1cf4774
--- /dev/null
+++ b/src/malloc/oldmalloc/malloc_impl.h
@@ -0,0 +1,39 @@
+#ifndef MALLOC_IMPL_H
+#define MALLOC_IMPL_H
+
+#include <sys/mman.h>
+#include "dynlink.h"
+
+struct chunk {
+ size_t psize, csize;
+ struct chunk *next, *prev;
+};
+
+struct bin {
+ volatile int lock[2];
+ struct chunk *head;
+ struct chunk *tail;
+};
+
+#define SIZE_ALIGN (4*sizeof(size_t))
+#define SIZE_MASK (-SIZE_ALIGN)
+#define OVERHEAD (2*sizeof(size_t))
+#define MMAP_THRESHOLD (0x1c00*SIZE_ALIGN)
+#define DONTCARE 16
+#define RECLAIM 163840
+
+#define CHUNK_SIZE(c) ((c)->csize & -2)
+#define CHUNK_PSIZE(c) ((c)->psize & -2)
+#define PREV_CHUNK(c) ((struct chunk *)((char *)(c) - CHUNK_PSIZE(c)))
+#define NEXT_CHUNK(c) ((struct chunk *)((char *)(c) + CHUNK_SIZE(c)))
+#define MEM_TO_CHUNK(p) (struct chunk *)((char *)(p) - OVERHEAD)
+#define CHUNK_TO_MEM(c) (void *)((char *)(c) + OVERHEAD)
+#define BIN_TO_CHUNK(i) (MEM_TO_CHUNK(&mal.bins[i].head))
+
+#define C_INUSE ((size_t)1)
+
+#define IS_MMAPPED(c) !((c)->csize & (C_INUSE))
+
+hidden void __bin_chunk(struct chunk *);
+
+#endif
diff --git a/src/malloc/malloc_usable_size.c b/src/malloc/oldmalloc/malloc_usable_size.c
index 672b518a..672b518a 100644
--- a/src/malloc/malloc_usable_size.c
+++ b/src/malloc/oldmalloc/malloc_usable_size.c
diff --git a/src/malloc/posix_memalign.c b/src/malloc/posix_memalign.c
index 2ea8bd8a..ad4d8f47 100644
--- a/src/malloc/posix_memalign.c
+++ b/src/malloc/posix_memalign.c
@@ -1,11 +1,10 @@
#include <stdlib.h>
#include <errno.h>
-#include "malloc_impl.h"
int posix_memalign(void **res, size_t align, size_t len)
{
if (align < sizeof(void *)) return EINVAL;
- void *mem = __memalign(align, len);
+ void *mem = aligned_alloc(align, len);
if (!mem) return errno;
*res = mem;
return 0;
diff --git a/src/malloc/realloc.c b/src/malloc/realloc.c
new file mode 100644
index 00000000..fb0e8b7c
--- /dev/null
+++ b/src/malloc/realloc.c
@@ -0,0 +1,6 @@
+#include <stdlib.h>
+
+void *realloc(void *p, size_t n)
+{
+ return __libc_realloc(p, n);
+}
diff --git a/src/malloc/reallocarray.c b/src/malloc/reallocarray.c
new file mode 100644
index 00000000..4a6ebe46
--- /dev/null
+++ b/src/malloc/reallocarray.c
@@ -0,0 +1,13 @@
+#define _BSD_SOURCE
+#include <errno.h>
+#include <stdlib.h>
+
+void *reallocarray(void *ptr, size_t m, size_t n)
+{
+ if (n && m > -1 / n) {
+ errno = ENOMEM;
+ return 0;
+ }
+
+ return realloc(ptr, m * n);
+}
diff --git a/src/malloc/replaced.c b/src/malloc/replaced.c
new file mode 100644
index 00000000..07fce61e
--- /dev/null
+++ b/src/malloc/replaced.c
@@ -0,0 +1,4 @@
+#include "dynlink.h"
+
+int __malloc_replaced;
+int __aligned_alloc_replaced;