summaryrefslogtreecommitdiff
path: root/src/common/mm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/mm.c')
-rw-r--r--src/common/mm.c1414
1 files changed, 1414 insertions, 0 deletions
diff --git a/src/common/mm.c b/src/common/mm.c
new file mode 100644
index 0000000..b461241
--- /dev/null
+++ b/src/common/mm.c
@@ -0,0 +1,1414 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <execinfo.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/hashtbl.h>
+
+
+#define DEFAULT_DEPTH 8 /* default backtrace depth */
+#define MAX_DEPTH 128 /* max. backtrace depth */
+
+/*
+ * memory allocator state
+ */
+
+typedef struct {
+ mrp_list_hook_t blocks; /* list of allocated blocks */
+ size_t hdrsize; /* header size */
+ int depth; /* backtrace depth */
+ uint32_t cur_blocks; /* currently allocated blocks */
+ uint32_t max_blocks; /* max allocated blocks */
+ uint64_t cur_alloc; /* currently allocated memory */
+ uint64_t max_alloc; /* max allocated memory */
+ int poison; /* poisoning pattern */
+ size_t chunk_size; /* object pool chunk size */
+ mrp_mm_type_t mode; /* passthru/debug mode */
+
+ void *(*alloc)(size_t size, const char *file, int line, const char *func);
+ void *(*realloc)(void *ptr, size_t size, const char *file,
+ int line, const char *func);
+ int (*memalign)(void **ptr, size_t align, size_t size,
+ const char *file, int line, const char *func);
+ void (*free)(void *ptr, const char *file, int line, const char *func);
+} mm_t;
+
+
+/*
+ * memory block
+ */
+
+typedef struct {
+ mrp_list_hook_t hook; /* to allocated blocks */
+ mrp_list_hook_t more; /* with the same backtrace */
+ const char *file; /* file of immediate caller */
+ int line; /* line of immediate caller */
+ const char *func; /* name of immediate caller */
+ size_t size; /* requested size */
+ void *bt[]; /* for accessing backtrace */
+} memblk_t;
+
+
+
+static mm_t __mm = { /* allocator state */
+ .hdrsize = MRP_ALIGN(MRP_OFFSET(memblk_t, bt[DEFAULT_DEPTH]),
+ MRP_MM_ALIGN),
+ .depth = DEFAULT_DEPTH,
+ .poison = 0xdeadbeef,
+};
+
+
+static const char *get_config_key(const char *config, const char *key)
+{
+ const char *beg;
+ int len;
+
+ if (config != NULL) {
+ len = strlen(key);
+
+ beg = config;
+ while (beg != NULL) {
+ beg = strstr(beg, key);
+
+ if (beg != NULL) {
+ if ((beg == config || beg[-1] == ':') &&
+ (beg[len] == '=' || beg[len] == ':' || beg[len] == '\0'))
+ return (beg[len] == '=' ? beg + len + 1 : "");
+ else
+ beg++;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+static int32_t get_config_int32(const char *cfg, const char *key,
+ int32_t defval)
+{
+ const char *v;
+ char *end;
+ int i;
+
+ v = get_config_key(cfg, key);
+
+ if (v != NULL) {
+ if (*v) {
+ i = strtol(v, &end, 10);
+
+ if (end && (!*end || *end == ':'))
+ return i;
+ }
+ }
+
+ return defval;
+}
+
+
+static uint32_t get_config_uint32(const char *cfg, const char *key,
+ uint32_t defval)
+{
+ const char *v;
+ char *end;
+ int i;
+
+ v = get_config_key(cfg, key);
+
+ if (v != NULL) {
+ if (*v) {
+ i = strtol(v, &end, 10);
+
+ if (end && (!*end || *end == ':'))
+ return i;
+ }
+ }
+
+ return defval;
+}
+
+
+static int get_config_bool(const char *config, const char *key, int defval)
+{
+ const char *v;
+
+ v = get_config_key(config, key);
+
+ if (v != NULL) {
+ if (*v) {
+
+ if ((!strncasecmp(v, "false", 5) && (v[5] == ':' || !v[5])) ||
+ (!strncasecmp(v, "true" , 4) && (v[4] == ':' || !v[4])))
+ return (v[0] == 't' || v[0] == 'T');
+ }
+ else if (*v == '\0')
+ return TRUE;
+ }
+
+ return defval;
+}
+
+
+static int get_config_string(const char *cfg, const char *key,
+ const char *defval, char *buf, size_t size)
+{
+ const char *v;
+ char *end;
+ int len;
+
+ v = get_config_key(cfg, key);
+
+ if (v == NULL)
+ v = defval;
+
+ end = strchr(v, ':');
+
+ if (end != NULL)
+ len = end - v;
+ else
+ len = strlen(v);
+
+ len = snprintf(buf, size, "%*.*s", len, len, v);
+
+ if (len >= (int)size - 1)
+ buf[size - 1] = '\0';
+
+ return len;
+}
+
+
+
+MRP_INIT_AT(101) static void setup(void)
+{
+ char *config = getenv(MRP_MM_CONFIG_ENVVAR);
+
+ mrp_list_init(&__mm.blocks);
+
+ __mm.depth = get_config_int32(config, "depth", DEFAULT_DEPTH);
+
+ if (__mm.depth > MAX_DEPTH)
+ __mm.depth = MAX_DEPTH;
+
+ __mm.hdrsize = MRP_ALIGN(MRP_OFFSET(memblk_t, bt[__mm.depth]),
+ MRP_MM_ALIGN);
+
+ __mm.cur_blocks = 0;
+ __mm.max_blocks = 0;
+ __mm.cur_alloc = 0;
+ __mm.max_alloc = 0;
+
+ __mm.poison = get_config_uint32(config, "poison", 0xdeadbeef);
+ __mm.chunk_size = sysconf(_SC_PAGESIZE) * 2;
+
+ if (config == NULL || !get_config_bool(config, "debug", FALSE))
+ mrp_mm_config(MRP_MM_PASSTHRU);
+ else
+ mrp_mm_config(MRP_MM_DEBUG);
+}
+
+
+static void __attribute__((destructor)) cleanup(void)
+{
+ if (__mm.mode == MRP_MM_DEBUG) {
+ mrp_mm_dump(stdout);
+ /*mrp_mm_check(stdout);*/
+ }
+}
+
+
+
+int32_t mrp_mm_config_int32(const char *key, int32_t defval)
+{
+ return get_config_int32(getenv(MRP_MM_CONFIG_ENVVAR), key, defval);
+}
+
+
+uint32_t mrp_mm_config_uint32(const char *key, uint32_t defval)
+{
+ return get_config_uint32(getenv(MRP_MM_CONFIG_ENVVAR), key, defval);
+}
+
+
+int mrp_mm_config_bool(const char *key, int defval)
+{
+ return get_config_bool(getenv(MRP_MM_CONFIG_ENVVAR), key, defval);
+}
+
+
+int mrp_mm_config_string(const char *key, const char *defval,
+ char *buf, size_t size)
+{
+ return get_config_string(getenv(MRP_MM_CONFIG_ENVVAR), key, defval,
+ buf, size);
+}
+
+
+/*
+ * memblk handling
+ */
+
+static memblk_t *memblk_alloc(size_t size, const char *file, int line,
+ const char *func, void **bt)
+{
+ memblk_t *blk;
+
+ if (MRP_UNLIKELY(size == 0))
+ blk = NULL;
+ else {
+ if ((blk = malloc(__mm.hdrsize + size)) != NULL) {
+ mrp_list_init(&blk->hook);
+ mrp_list_init(&blk->more);
+ mrp_list_append(&__mm.blocks, &blk->hook);
+
+ blk->file = file;
+ blk->line = line;
+ blk->func = func;
+ blk->size = size;
+
+ memcpy(blk->bt, bt, __mm.depth * sizeof(*bt));
+
+ __mm.cur_blocks++;
+ __mm.cur_alloc += size;
+
+ __mm.max_blocks = MRP_MAX(__mm.max_blocks, __mm.cur_blocks);
+ __mm.max_alloc = MRP_MAX(__mm.max_alloc , __mm.cur_alloc);
+ }
+ }
+
+ return blk;
+}
+
+
+static void memblk_free(memblk_t *blk, const char *file, int line,
+ const char *func, void **bt)
+{
+ MRP_UNUSED(file);
+ MRP_UNUSED(line);
+ MRP_UNUSED(func);
+ MRP_UNUSED(bt);
+
+ if (blk != NULL) {
+ mrp_list_delete(&blk->hook);
+
+ __mm.cur_blocks--;
+ __mm.cur_alloc -= blk->size;
+
+ if (__mm.poison != 0)
+ memset(&blk->bt[__mm.depth], __mm.poison, blk->size);
+
+ free(blk);
+ }
+}
+
+
+static memblk_t *memblk_resize(memblk_t *blk, size_t size, const char *file,
+ int line, const char *func, void **bt)
+{
+ memblk_t *resized;
+
+ if (blk != NULL) {
+ mrp_list_delete(&blk->hook);
+
+ if (size != 0) {
+ resized = realloc(blk, __mm.hdrsize + size);
+
+ if (resized != NULL) {
+ mrp_list_init(&resized->hook);
+
+ mrp_list_append(&__mm.blocks, &resized->hook);
+
+ __mm.cur_alloc -= resized->size;
+ __mm.cur_alloc += size;
+ __mm.max_alloc = MRP_MAX(__mm.max_alloc, __mm.cur_alloc);
+
+ resized->file = file;
+ resized->line = line;
+ resized->func = func;
+
+ memcpy(resized->bt, bt, __mm.depth * sizeof(*bt));
+
+ resized->size = size;
+ }
+ else
+ mrp_list_append(&__mm.blocks, &blk->hook);
+ }
+ else {
+ resized = NULL;
+ memblk_free(blk, file, line, func, bt);
+ }
+
+ return resized;
+ }
+ else
+ return memblk_alloc(size, file, line, func, bt);
+}
+
+
+static inline void *memblk_to_ptr(memblk_t *blk)
+{
+ if (blk != NULL)
+ return (void *)&blk->bt[__mm.depth];
+ else
+ return NULL;
+}
+
+
+static inline memblk_t *ptr_to_memblk(void *ptr)
+{
+ /*
+ * XXX Hmm... maybe we should also add pre- and post-sentinels
+ * and check them here to have minimal protection/detection of
+ * trivial buffer overflow bugs when running in debug mode.
+ */
+
+ if (ptr != NULL)
+ return ptr - MRP_OFFSET(memblk_t, bt[__mm.depth]);
+ else
+ return NULL;
+}
+
+
+/*
+ * debugging allocator
+ */
+
+static inline int __mm_backtrace(void **bt, size_t size)
+{
+ int i, n;
+
+ n = backtrace(bt, (int)size);
+ for (i = n; i < (int)size; i++)
+ bt[i] = NULL;
+
+ return n;
+}
+
+
+static void *__mm_alloc(size_t size, const char *file, int line,
+ const char *func)
+{
+ memblk_t *blk;
+ void *bt[__mm.depth + 1];
+
+ __mm_backtrace(bt, MRP_ARRAY_SIZE(bt));
+ blk = memblk_alloc(size, file, line, func, bt + 1);
+
+ return memblk_to_ptr(blk);
+}
+
+
+static void *__mm_realloc(void *ptr, size_t size, const char *file,
+ int line, const char *func)
+{
+ memblk_t *blk;
+ void *bt[__mm.depth + 1];
+
+ __mm_backtrace(bt, MRP_ARRAY_SIZE(bt));
+ blk = ptr_to_memblk(ptr);
+
+ if (blk != NULL)
+ blk = memblk_resize(blk, size, file, line, func, bt + 1);
+ else
+ blk = memblk_alloc(size, file, line, func, bt + 1);
+
+ return memblk_to_ptr(blk);
+}
+
+
+static int __mm_memalign(void **ptr, size_t align, size_t size,
+ const char *file, int line, const char *func)
+{
+ MRP_UNUSED(align);
+ MRP_UNUSED(size);
+ MRP_UNUSED(file);
+ MRP_UNUSED(line);
+ MRP_UNUSED(func);
+
+ *ptr = NULL;
+ errno = ENOSYS;
+
+ mrp_log_error("XXX %s not implemented!!!", __FUNCTION__);
+ return -1;
+}
+
+
+static void __mm_free(void *ptr, const char *file, int line,
+ const char *func)
+{
+ memblk_t *blk;
+ void *bt[__mm.depth + 1];
+
+ if (ptr != NULL) {
+ __mm_backtrace(bt, MRP_ARRAY_SIZE(bt));
+ blk = ptr_to_memblk(ptr);
+
+ if (blk != NULL)
+ memblk_free(blk, file, line, func, bt + 1);
+ }
+}
+
+
+/*
+ * passthru allocator
+ */
+
+static void *__passthru_alloc(size_t size, const char *file, int line,
+ const char *func)
+{
+ MRP_UNUSED(file);
+ MRP_UNUSED(line);
+ MRP_UNUSED(func);
+
+ if (MRP_UNLIKELY(size == 0))
+ return NULL;
+ else
+ return malloc(size);
+}
+
+
+static void *__passthru_realloc(void *ptr, size_t size, const char *file,
+ int line, const char *func)
+{
+ MRP_UNUSED(file);
+ MRP_UNUSED(line);
+ MRP_UNUSED(func);
+
+ return realloc(ptr, size);
+}
+
+
+static int __passthru_memalign(void **ptr, size_t align, size_t size,
+ const char *file, int line, const char *func)
+{
+ MRP_UNUSED(file);
+ MRP_UNUSED(line);
+ MRP_UNUSED(func);
+
+ return posix_memalign(ptr, align, size);
+}
+
+
+static void __passthru_free(void *ptr, const char *file, int line,
+ const char *func)
+{
+ MRP_UNUSED(file);
+ MRP_UNUSED(line);
+ MRP_UNUSED(func);
+
+ free(ptr);
+}
+
+
+/*
+ * common public interface - uses either passthru or debugging
+ */
+
+void *mrp_mm_alloc(size_t size, const char *file, int line, const char *func)
+{
+ return __mm.alloc(size, file, line, func);
+}
+
+
+void *mrp_mm_realloc(void *ptr, size_t size, const char *file, int line,
+ const char *func)
+{
+ return __mm.realloc(ptr, size, file, line, func);
+}
+
+
+char *mrp_mm_strdup(const char *s, const char *file, int line, const char *func)
+{
+ char *p;
+ size_t size;
+
+ if (s != NULL) {
+ size = strlen(s) + 1;
+ p = mrp_mm_alloc(size, file, line, func);
+
+ if (p != NULL)
+ strcpy(p, s);
+ }
+ else
+ p = NULL;
+
+ return p;
+}
+
+
+int mrp_mm_memalign(void **ptr, size_t align, size_t size, const char *file,
+ int line, const char *func)
+{
+ return __mm.memalign(ptr, align, size, file, line, func);
+}
+
+
+void mrp_mm_free(void *ptr, const char *file, int line, const char *func)
+{
+ return __mm.free(ptr, file, line, func);
+}
+
+
+int mrp_mm_config(mrp_mm_type_t type)
+{
+ if (__mm.cur_blocks != 0)
+ return FALSE;
+
+ switch (type) {
+ case MRP_MM_PASSTHRU:
+ __mm.alloc = __passthru_alloc;
+ __mm.realloc = __passthru_realloc;
+ __mm.memalign = __passthru_memalign;
+ __mm.free = __passthru_free;
+ __mm.mode = MRP_MM_PASSTHRU;
+ return TRUE;
+
+ case MRP_MM_DEBUG:
+ __mm.alloc = __mm_alloc;
+ __mm.realloc = __mm_realloc;
+ __mm.memalign = __mm_memalign;
+ __mm.free = __mm_free;
+ __mm.mode = MRP_MM_DEBUG;
+ return TRUE;
+
+ default:
+ mrp_log_error("Invalid memory allocator type 0x%x requested.", type);
+ return FALSE;
+ }
+}
+
+
+#define NBUCKET 1024
+
+static int btcmp(void **bt1, void **bt2)
+{
+ ptrdiff_t diff;
+ int i;
+
+ for (i = 0; i < __mm.depth; i++) {
+ diff = bt1[i] - bt2[i];
+
+ if (diff < 0)
+ return -1;
+ else if (diff > 0)
+ return +1;
+ }
+
+ return 0;
+}
+
+
+static uint32_t blkhash(memblk_t *blk)
+{
+ uint32_t h;
+ int i;
+
+ h = 0;
+ for (i = 0; i < __mm.depth; i++)
+ h ^= (blk->bt[i] - NULL) & 0xffffffffUL;
+
+ return h % NBUCKET;
+}
+
+
+static memblk_t *blkfind(mrp_list_hook_t *buckets, memblk_t *blk)
+{
+ uint32_t h = blkhash(blk);
+ mrp_list_hook_t *head = buckets + h;
+ mrp_list_hook_t *p, *n;
+ memblk_t *b;
+
+ mrp_list_foreach(head, p, n) {
+ b = mrp_list_entry(p, typeof(*b), hook);
+ if (!btcmp(&b->bt[0], &blk->bt[0]))
+ return b;
+ }
+
+ return NULL;
+}
+
+
+static void collect_blocks(mrp_list_hook_t *buckets)
+{
+ mrp_list_hook_t *p, *n;
+ memblk_t *head, *blk;
+ uint32_t h;
+ int i;
+
+ for (i = 0; i < NBUCKET; i++)
+ mrp_list_init(buckets + i);
+
+ mrp_list_foreach(&__mm.blocks, p, n) {
+ blk = mrp_list_entry(p, typeof(*blk), hook);
+
+ mrp_list_init(&blk->more);
+ head = blkfind(buckets, blk);
+
+ if (head != NULL) {
+ mrp_list_append(&head->more, &blk->more);
+ head->size += blk->size;
+ }
+ else {
+ h = blkhash(blk);
+ mrp_list_delete(&blk->hook);
+ mrp_list_append(buckets + h, &blk->hook);
+ }
+ }
+}
+
+
+static uint32_t group_usage(memblk_t *head, int exclude_head)
+{
+ mrp_list_hook_t *p, *n;
+ memblk_t *blk;
+ uint32_t total;
+
+ total = exclude_head ? 0 : head->size;
+ mrp_list_foreach(&head->more, p, n) {
+ blk = mrp_list_entry(p, typeof(*blk), more);
+ total += blk->size;
+ }
+
+ return total;
+}
+
+
+static void dump_group(FILE *fp, memblk_t *head)
+{
+ mrp_list_hook_t *p, *n;
+ memblk_t *blk;
+ char **syms, *sym;
+ uint32_t total;
+ int nblk, i;
+
+ fprintf(fp, "Allocations with call stack fingerprint:\n");
+ syms = backtrace_symbols(head->bt, __mm.depth);
+ for (i = 0; i < __mm.depth && head->bt[i]; i++) {
+ sym = syms && syms[i] ? strrchr(syms[i], '/') : NULL;
+ fprintf(fp, " %p (%s)\n", head->bt[i], sym ? sym + 1 : "<unknown>");
+ }
+ free(syms);
+
+ total = head->size - group_usage(head, TRUE);
+ nblk = 1;
+
+ fprintf(fp, " %lu bytes at %p\n", (unsigned long)total,
+ memblk_to_ptr(head));
+
+ mrp_list_foreach(&head->more, p, n) {
+ blk = mrp_list_entry(p, typeof(*blk), more);
+
+ total += blk->size;
+ nblk++;
+
+ fprintf(fp, " %zd bytes at %p\n", blk->size, memblk_to_ptr(blk));
+ }
+
+ if (nblk > 1)
+ fprintf(fp, " total %lu bytes in %d blocks\n",
+ (unsigned long)total, nblk);
+}
+
+
+static void sort_blocks(mrp_list_hook_t *buckets, mrp_list_hook_t *sorted)
+{
+ mrp_list_hook_t *bp, *bn, *sp, *sn;
+ memblk_t *head, *entry, *next;
+ int i;
+
+ mrp_list_init(sorted);
+
+ for (i = 0; i < NBUCKET; i++) {
+ mrp_list_foreach(buckets + i, bp, bn) {
+ head = mrp_list_entry(bp, typeof(*head), hook);
+
+ next = NULL;
+ mrp_list_foreach(sorted, sp, sn) {
+ entry = mrp_list_entry(sp, typeof(*entry), hook);
+
+ if (head->size <= entry->size) {
+ next = entry;
+ break;
+ }
+ }
+
+ mrp_list_delete(&head->hook);
+
+ if (next != NULL)
+ mrp_list_insert_before(&next->hook, &head->hook);
+ else
+ mrp_list_append(sorted, &head->hook);
+ }
+ }
+}
+
+
+static void dump_blocks(FILE *fp, mrp_list_hook_t *sorted)
+{
+ mrp_list_hook_t *p, *n;
+ memblk_t *head;
+
+ mrp_list_foreach(sorted, p, n) {
+ head = mrp_list_entry(p, typeof(*head), hook);
+ dump_group(fp, head);
+ }
+}
+
+
+static void relink_blocks(mrp_list_hook_t *sorted)
+{
+ mrp_list_hook_t *p, *n;
+ memblk_t *head;
+ uint32_t rest;
+
+ mrp_list_foreach(sorted, p, n) {
+ head = mrp_list_entry(p, typeof(*head), hook);
+ mrp_list_delete(&head->hook);
+ mrp_list_append(&__mm.blocks, &head->hook);
+
+ rest = group_usage(head, TRUE);
+ head->size -= rest;
+ }
+}
+
+
+void mrp_mm_dump(FILE *fp)
+{
+ mrp_list_hook_t buckets[NBUCKET];
+ mrp_list_hook_t sorted;
+
+ mrp_list_init(&sorted);
+
+ collect_blocks(buckets);
+ sort_blocks(buckets, &sorted);
+ dump_blocks(fp, &sorted);
+ relink_blocks(&sorted);
+
+ fprintf(fp, "Max: %llu bytes (%.2f M, %.2f G), %ld blocks\n",
+ (unsigned long long)__mm.max_alloc,
+ 1.0 * __mm.max_alloc / (1024 * 1024),
+ 1.0 * __mm.max_alloc / (1024 * 1024 * 1024),
+ (unsigned long)__mm.max_blocks);
+ fprintf(fp, "Current: %llu bytes (%.2f M, %.2f G) in %ld blocks.\n",
+ (unsigned long long)__mm.cur_alloc,
+ 1.0 * __mm.cur_alloc / (1024 * 1024),
+ 1.0 * __mm.cur_alloc / (1024 * 1024 * 1024),
+ (unsigned long)__mm.cur_blocks);
+}
+
+
+void mrp_mm_check(FILE *fp)
+{
+ mrp_mm_dump(fp);
+}
+
+
+
+
+
+/*
+ * object pool interface
+ */
+
+typedef unsigned int mask_t;
+
+#define W sizeof(mask_t)
+#define B (W * 8)
+#define MASK_BYTES (sizeof(mask_t))
+#define MASK_BITS (MASK_BYTES * 8)
+#define MASK_EMPTY ((mask_t)-1)
+#define MASK_FULL ((mask_t) 0)
+
+typedef struct pool_chunk_s pool_chunk_t;
+
+static int pool_calc_sizes(mrp_objpool_t *pool);
+static int pool_grow(mrp_objpool_t *pool, int nobj);
+static int pool_shrink(mrp_objpool_t *pool, int nobj);
+static pool_chunk_t *chunk_alloc(int nperchunk);
+static void chunk_free(pool_chunk_t *chunk);
+static inline int chunk_empty(pool_chunk_t *chunk);
+static void pool_foreach_object(mrp_objpool_t *pool,
+ void (*cb)(void *obj, void *user_data),
+ void *user_data);
+static void chunk_foreach_object(pool_chunk_t *chunk,
+ void (*cb)(void *obj, void *user_data),
+ void *user_data);
+
+
+/*
+ * an object pool
+ */
+
+struct mrp_objpool_s {
+ char *name; /* verbose pool name */
+ size_t limit; /* max. number of objects */
+ size_t objsize; /* size of a single object */
+ size_t prealloc; /* preallocate this many */
+ size_t nobj; /* currently allocated */
+ int (*setup)(void *); /* object setup callback */
+ void (*cleanup)(void *); /* object cleanup callback */
+ uint32_t flags; /* pool flags */
+ int poison; /* poisoning pattern */
+
+ size_t nperchunk; /* objects per chunk */
+ size_t dataidx; /* data */
+ mrp_list_hook_t space; /* chunk with frees slots */
+ size_t nspace; /* number of such chunks */
+ mrp_list_hook_t full; /* fully allocated chunks */
+ size_t nfull; /* number of such chunks */
+};
+
+
+/*
+ * a chunk of memory allocated to an object pool
+ */
+
+struct pool_chunk_s {
+ mrp_objpool_t *pool; /* pool we're alloced to */
+ mrp_list_hook_t hook; /* hook to chunk list */
+ mask_t cache; /* cache bits */
+ mask_t used[]; /* allocation mask */
+};
+
+
+
+mrp_objpool_t *mrp_objpool_create(mrp_objpool_config_t *cfg)
+{
+ mrp_objpool_t *pool;
+
+ if ((pool = mrp_allocz(sizeof(*pool))) != NULL) {
+ if ((pool->name = mrp_strdup(cfg->name)) == NULL)
+ goto fail;
+
+ pool->limit = cfg->limit;
+ pool->objsize = MRP_MAX(cfg->objsize, (size_t)MRP_MM_OBJSIZE_MIN);
+ pool->prealloc = cfg->prealloc;
+ pool->setup = cfg->setup;
+ pool->cleanup = cfg->cleanup;
+ pool->flags = cfg->flags;
+ pool->poison = cfg->poison;
+
+ mrp_list_init(&pool->space);
+ mrp_list_init(&pool->full);
+ pool->nspace = 0;
+ pool->nfull = 0;
+
+ if (!pool_calc_sizes(pool))
+ goto fail;
+
+ if (!mrp_objpool_grow(pool, pool->prealloc))
+ goto fail;
+
+ mrp_debug("pool <%s> created, with %zd/%zd objects.", pool->name,
+ pool->prealloc, pool->limit);
+
+ return pool;
+ }
+
+
+ fail:
+ mrp_objpool_destroy(pool);
+ return NULL;
+}
+
+
+static void free_object(void *obj, void *user_data)
+{
+ mrp_objpool_t *pool = (mrp_objpool_t *)user_data;
+
+ printf("Releasing unfreed object %p from pool <%s>.\n", obj, pool->name);
+ mrp_objpool_free(obj);
+}
+
+
+void mrp_objpool_destroy(mrp_objpool_t *pool)
+{
+ if (pool != NULL) {
+ if (pool->cleanup != NULL)
+ pool_foreach_object(pool, free_object, pool);
+
+ mrp_free(pool->name);
+ mrp_free(pool);
+ }
+}
+
+
+void *mrp_objpool_alloc(mrp_objpool_t *pool)
+{
+ pool_chunk_t *chunk;
+ void *obj;
+ unsigned int cidx, uidx, sidx;
+
+ if (pool->limit && pool->nobj >= pool->limit)
+ return NULL;
+
+ if (mrp_list_empty(&pool->space)) {
+ if (!pool_grow(pool, 1))
+ return NULL;
+ }
+
+ chunk = mrp_list_entry(pool->space.next, pool_chunk_t, hook);
+ cidx = ffs(chunk->cache);
+
+ if (!cidx) {
+ mrp_log_error("object pool bug: no free slots in cache mask.");
+ return NULL;
+ }
+ else
+ cidx--;
+
+ uidx = ffs(chunk->used[cidx]);
+
+ if (!uidx) {
+ mrp_log_error("object pool bug: no free slots in used mask.");
+ return NULL;
+ }
+ else
+ uidx--;
+
+ sidx = cidx * MASK_BITS + uidx;
+ obj = ((void *)&chunk->used[pool->dataidx]) + (sidx * pool->objsize);
+
+ mrp_debug("%p: %u/%u: %u, offs %zd\n", obj, cidx, uidx, sidx,
+ sidx * pool->objsize);
+
+ chunk->used[cidx] &= ~(1 << uidx);
+
+ if (chunk->used[cidx] == MASK_FULL) {
+ chunk->cache &= ~(1 << cidx);
+
+ if (chunk->cache == MASK_FULL) { /* chunk exhausted */
+ mrp_list_delete(&chunk->hook);
+ pool->nspace--;
+ mrp_list_append(&pool->full, &chunk->hook);
+ pool->nfull++;
+ }
+ }
+
+ if (pool->setup == NULL || pool->setup(obj)) {
+ pool->nobj++;
+ return obj;
+ }
+ else {
+ mrp_objpool_free(obj);
+ return NULL;
+ }
+}
+
+
+void mrp_objpool_free(void *obj)
+{
+ pool_chunk_t *chunk;
+ mrp_objpool_t *pool;
+ unsigned int cidx, uidx, sidx;
+ mask_t cache, used;
+ void *base;
+
+ if (obj == NULL)
+ return;
+
+ chunk = (pool_chunk_t *)(((ptrdiff_t)obj) & ~(__mm.chunk_size - 1));
+ pool = chunk->pool;
+
+ base = (void *)&chunk->used[pool->dataidx];
+ sidx = (obj - base) / pool->objsize;
+ cidx = sidx / MASK_BITS;
+ uidx = sidx & (MASK_BITS - 1);
+
+ mrp_debug("%p: %u/%u: %u, offs %zd\n", obj, cidx, uidx, sidx,
+ sidx * pool->objsize);
+
+ cache = chunk->cache;
+ used = chunk->used[cidx];
+
+ if (used & (1 << uidx)) {
+ mrp_log_error("Trying to free unallocated object %p of pool <%s>.",
+ obj, pool->name);
+ return;
+ }
+
+ if (pool->cleanup != NULL)
+ pool->cleanup(obj);
+
+ if (pool->flags & MRP_OBJPOOL_FLAG_POISON)
+ memset(obj, pool->poison, pool->objsize);
+
+ chunk->used[cidx] |= (1 << uidx);
+ chunk->cache |= (1 << cidx);
+
+ if (cache == MASK_FULL) { /* chunk was full */
+ mrp_list_delete(&chunk->hook);
+ pool->nfull--;
+ mrp_list_append(&pool->space, &chunk->hook);
+ pool->nspace++;
+ }
+
+ pool->nobj--;
+}
+
+
+int mrp_objpool_grow(mrp_objpool_t *pool, int nobj)
+{
+ int nchunk = (nobj + pool->nperchunk - 1) / pool->nperchunk;
+
+ return pool_grow(pool, nchunk) == nchunk;
+}
+
+
+int mrp_objpool_shrink(mrp_objpool_t *pool, int nobj)
+{
+ int nchunk = (nobj + pool->nperchunk - 1) / pool->nperchunk;
+
+ return pool_shrink(pool, nchunk) == nchunk;
+}
+
+
+static int pool_calc_sizes(mrp_objpool_t *pool)
+{
+ size_t S, C, Hf, Hv, P;
+ size_t n, T;
+
+ if (!pool->objsize)
+ return FALSE;
+
+ pool->objsize = MRP_ALIGN(pool->objsize, MRP_MM_ALIGN);
+
+ /*
+ * Pool chunks consist of an administrative header followed by object
+ * slots each of which can be either claimed/allocated or free. The
+ * header contains a back pointer to the pool, a hook to one of the
+ * chunk lists and a two-level bit-mask for slot allocation status.
+ * The two-level mask consists of a 32-bit cache word and actual slot
+ * status words. The nth bit of the cache word caches whether there are
+ * any free among the nth - (n + 31)th slots. The slot status words keep
+ * the status of the actual slots. To find a free slot we find the idx
+ * of the 1st word with a free slot from the cache and then the free
+ * slot index in that word. To be able to use FFS we use inverted bit
+ * semantics (0=allocated, 1=free) and we populate the words starting
+ * at the LSB.
+ *
+ * Here we calculate how many objects we'll be able to squeeze into a
+ * single pool chunk and how many mask bits we'll need to administer
+ * the status of these. To do this we use the following equations:
+ *
+ * 1) Hf + Hv + n * S = C
+ * 2) Hv = W + (n + B - 1) / B * W
+ * where
+ * C: chunk size
+ * S: object size (aligned to our minimum alignment)
+ * Hf: header size, fixed part
+ * Hv: Header size, variable part (allocation mask)
+ * n: number of objects / chunk
+ * W: bitmask word size in bytes
+ * B: bitmask word size in bits
+ *
+ * Solving the equations for n gives us
+ * n = (B*C - B*Hf - W*(2*B - 1)) / (B*S + W)
+ *
+ * If any, the only non-obvious thing below is that instead of trying
+ * to express padding as part of the equation system (which seems to be
+ * way beyond my abilities in math nowadays), we initally assume no
+ * padding then check and compensate for it in the end if necessary.
+ */
+
+ Hf = sizeof(pool_chunk_t);
+ C = __mm.chunk_size;
+ P = 0;
+
+ S = MRP_ALIGN(pool->objsize, MRP_MM_ALIGN);
+ n = (B * C - B * Hf - W * (2*B - 1)) / (B * S + W);
+ Hv = W + W * (n + B - 1) / B;
+
+ P = (Hf + Hv) % sizeof(void *);
+ if (P != 0) {
+ P = sizeof(void *) - P;
+
+ if (Hv + Hf + P + n * S > C) {
+ n--;
+ Hv = W + W * (n + B - 1) / B;
+ }
+ }
+
+ T = Hf + Hv + P + n * S;
+
+ if (T > C) {
+ mrp_log_error("Could not size pool '%s' properly.", pool->name);
+ return FALSE;
+ }
+
+ pool->nperchunk = n;
+ pool->dataidx = (n + B - 1) / B;
+
+ if (pool->limit && (pool->limit % pool->nperchunk) != 0)
+ pool->limit += (pool->nperchunk - (pool->limit % pool->nperchunk));
+
+ return TRUE;
+}
+
+
+static int pool_grow(mrp_objpool_t *pool, int nchunk)
+{
+ pool_chunk_t *chunk;
+ int cnt;
+
+ for (cnt = 0; cnt < nchunk; cnt++) {
+ chunk = chunk_alloc(pool->nperchunk);
+
+ if (chunk != NULL) {
+ chunk->pool = pool;
+ mrp_list_append(&pool->space, &chunk->hook);
+ pool->nspace++;
+ }
+ else
+ break;
+ }
+
+ return cnt;
+}
+
+
+static int pool_shrink(mrp_objpool_t *pool, int nchunk)
+{
+ mrp_list_hook_t *p, *n;
+ pool_chunk_t *chunk;
+ int cnt;
+
+ cnt = 0;
+ mrp_list_foreach(&pool->space, p, n) {
+ chunk = mrp_list_entry(p, pool_chunk_t, hook);
+
+ if (chunk_empty(chunk)) {
+ mrp_list_delete(&chunk->hook);
+ chunk_free(chunk);
+ pool->nspace--;
+ cnt++;
+ }
+
+ if (cnt >= nchunk)
+ break;
+ }
+
+ return cnt;
+}
+
+
+static void pool_foreach_object(mrp_objpool_t *pool,
+ void (*cb)(void *obj, void *user_data),
+ void *user_data)
+{
+ mrp_list_hook_t *p, *n;
+ pool_chunk_t *chunk;
+
+ mrp_list_foreach(&pool->full, p, n) {
+ chunk = mrp_list_entry(p, pool_chunk_t, hook);
+ chunk_foreach_object(chunk, cb, user_data);
+ }
+
+ mrp_list_foreach(&pool->space, p, n) {
+ chunk = mrp_list_entry(p, pool_chunk_t, hook);
+ chunk_foreach_object(chunk, cb, user_data);
+ }
+}
+
+
+static void chunk_foreach_object(pool_chunk_t *chunk,
+ void (*cb)(void *obj, void *user_data),
+ void *user_data)
+{
+ mrp_objpool_t *pool = chunk->pool;
+ void *obj;
+ int sidx, cidx, uidx;
+ mask_t used;
+
+ sidx = 0;
+ while (sidx < (int)pool->nperchunk) {
+ cidx = sidx / MASK_BITS;
+ uidx = sidx & (MASK_BITS - 1);
+ used = chunk->used[cidx];
+
+ if (!(used & (1 << uidx))) {
+ obj = ((void *)&chunk->used[pool->dataidx]) + (sidx*pool->objsize);
+ cb(obj, user_data);
+ sidx++;
+ }
+ else {
+ if (used == MASK_EMPTY)
+ sidx = (sidx + MASK_BITS) & ~(MASK_BITS - 1);
+ else
+ sidx++;
+ }
+ }
+}
+
+
+static inline int chunk_empty(pool_chunk_t *chunk)
+{
+ mask_t mask;
+ int i, n;
+
+ if (chunk->cache != (MASK_EMPTY & 0xffff))
+ return FALSE;
+ else {
+ for (n = chunk->pool->nperchunk, i = 0; n > 0; n -= MASK_BITS, i++) {
+ if (n >= (int)MASK_BITS)
+ mask = MASK_EMPTY;
+ else
+ mask = (1 << n) - 1;
+
+ if ((chunk->used[i] & mask) != mask)
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+}
+
+
+static void chunk_init(pool_chunk_t *chunk, int nperchunk)
+{
+ int nword, left, i;
+
+ mrp_list_init(&chunk->hook);
+
+ left = nperchunk;
+ nword = (nperchunk + MASK_BITS - 1) / MASK_BITS;
+
+ /*
+ * initialize allocation bitmask
+ *
+ * Note that every bit that corresponds to a non-allocatable slots
+ * we mark as reserved. This keeps both the allocation and freeing
+ * code paths simpler.
+ */
+
+ chunk->cache = (1 << nword) - 1;
+
+ for (i = 0; left > 0; i++) {
+ if (left >= (int)MASK_BITS)
+ chunk->used[i] = MASK_EMPTY;
+ else
+ chunk->used[i] = (((mask_t)1) << left) - 1;
+
+ left -= B;
+ }
+}
+
+
+static pool_chunk_t *chunk_alloc(int nperchunk)
+{
+ void *chunk;
+ int err;
+
+ err = posix_memalign(&chunk, __mm.chunk_size, __mm.chunk_size);
+
+ if (err == 0) {
+ memset(chunk, 0, __mm.chunk_size);
+ chunk_init((pool_chunk_t *)chunk, nperchunk);
+ }
+ else
+ chunk = NULL;
+
+ return chunk;
+}
+
+
+static void chunk_free(pool_chunk_t *chunk)
+{
+ free(chunk);
+}
+
+
+#if 0
+static void test_sizes(void)
+{
+ size_t S, C, Hf, Hv, P;
+ size_t i, n, T, Hv1, n1, T1;
+ int ok, ok1;
+
+ Hf = sizeof(pool_chunk_t);
+ C = __mm.chunk_size;
+ P = 0;
+
+ printf(" C: %zd\n", C);
+ printf("Hf: %zd\n", Hf);
+
+ for (i = 1; i < __mm.chunk_size / 8; i++) {
+ S = MRP_ALIGN(i, MRP_MM_ALIGN);
+ n = (B * C - B * Hf - W * (2*B - 1)) / (B * S + W);
+ Hv = W + W * (n + B - 1) / B;
+
+ P = (Hf + Hv) % sizeof(void *);
+ if (P != 0) {
+ P = sizeof(void *) - P;
+
+ if (Hv + Hf + P + n * S > C) {
+ n--;
+ Hv = W + W * (n + B - 1) / B;
+ }
+ }
+
+ T = Hf + Hv + P + n * S;
+ ok = T <= C;
+
+ n1 = n + 1;
+ Hv1 = W + W * (n1 + B - 1) / B;
+ T1 = Hf + Hv1 + P + n1 * S;
+ ok1 = T1 > C;
+
+ printf(" i = %zd: %zd * %zd + %zd (%zd: %s, %zd: %s)\n", i, n, S, P,
+ T , ok ? "OK" : "FAIL",
+ T1, ok1 ? "OK" : "FAIL");
+ {
+ size_t hs, us;
+
+ us = sizeof(uint32_t);
+ hs = (Hf + Hv + P) / us;
+
+ printf(" H+P: %zd (%zd * %zd = %zd)\n", Hf + Hv + P,
+ hs, us, hs * us);
+
+ if (((Hf + Hv + P) % sizeof(void *)) != 0) {
+ printf("Padding error!\n");
+ exit(1);
+ }
+ }
+
+ if (!ok || !ok1)
+ exit(1);
+ }
+}
+#endif