/* ==========================================================================
* libarena/src/arena.c - Custom Memory Allocator Interface
* --------------------------------------------------------------------------
* Copyright (c) 2006 William Ahern
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* ==========================================================================
*/
#include <stdlib.h> /* size_t getenv(3) */
#include <stddef.h> /* offsetof */
#include <stdarg.h> /* va_list */
#ifdef _WIN32
#include <stddef.h> /* ptrdiff_t */
#else
#include <inttypes.h> /* ptrdiff_t */
#endif
#include <limits.h> /* CHAR_BIT */
#include <string.h> /* memcpy(3) memmove(3) */
#include <assert.h> /* assert */
#include "align.h"
#include "proto.h"
#include "arena.h"
#include "rbits.h"
#include "util.h"
#include "queue.h" /* SLIST_HEAD SLIST_ENTRY SLIST_INIT SLIST_FIRST */
#ifndef MIN
#define MIN(a, b) (((a) < (b))? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b))? (a) : (b))
#endif
#ifndef roundup
#define roundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y))
#endif
static const struct arena_block {
size_t size;
struct {
unsigned char *next;
} pos;
SLIST_ENTRY(arena_block) sle;
unsigned char bytes[];
} arena_block_initializer;
const struct arena_options arena_defaults = {
ARENA_SYSTEM_ALIGNMENT,
1 << 15, /* 32768 */
};
static const struct arena {
struct arena_prototype interface; /* Exportable interface */
const struct arena_prototype *allocator; /* Internal "core" allocator */
SLIST_HEAD(,arena_block) blocks;
unsigned int nblocks;
struct arena_options options;
} arena_initializer;
static struct arena_block *arena_block_malloc(struct arena *a, size_t len, size_t align) {
struct arena_block *b;
size_t size;
if (align == 0)
align = a->options.alignment;
if (!ARENA_DEBUG)
size = MAX(a->options.blocklen,offsetof(struct arena_block, bytes) + len + align - 1 + RBITS_MAXLEN);
else
size = MAX(a->options.blocklen,offsetof(struct arena_block, bytes) + len + align - 1 + rbits_len(len));
if (!(b = a->allocator->malloc(a->allocator,size,0)))
return 0;
*b = arena_block_initializer;
b->size = size - offsetof(struct arena_block, bytes);
b->pos.next = b->bytes;
return b;
} /* arena_block_malloc() */
static int arena_block_regionof(struct arena_block *b, void *p) {
unsigned char *x = p;
return (x >= b->bytes && x < &b->bytes[b->size]);
} /* arena_block_regionof() */
struct arena *arena_open(const struct arena_options *opts, const struct arena_prototype *m) {
struct arena tmp, *a;
struct arena_block *b;
if (!opts)
opts = &arena_defaults;
if (!m)
m = ARENA_STDLIB;
tmp = arena_initializer;
tmp.allocator = m;
tmp.options = *opts;
if (ARENA_DEBUG)
tmp.options.blocklen = 0;
if (!(b = arena_block_malloc(&tmp,sizeof *a,0)))
return 0;
a = (void *)(b->pos.next + rbits_ptroffset(b->pos.next,sizeof *a,ARENA_SYSTEM_ALIGNMENT));
rbits_put(b->pos.next, (unsigned char *)a - b->pos.next, sizeof *a, 0);
b->pos.next = (void *)(a + 1);
*a = arena_initializer;
a->allocator = m;
SLIST_INIT(&a->blocks)
SLIST_INSERT_HEAD(&a->blocks,b,sle);
a->nblocks++;
a->options = *opts;
if (ARENA_DEBUG)
a->options.blocklen = 0;
return a;
} /* arena_open() */
void arena_close(struct arena *a) {
struct arena_block *b, *nxt;
if (a == 0)
return /* void */;
for (b = SLIST_FIRST(&a->blocks); b; b = nxt) {
nxt = SLIST_NEXT(b,sle);
a->allocator->free(a->allocator,b);
}
return /* void */;
} /* arena_close() */
void *arena_malloc(struct arena *a, size_t size, size_t align) {
struct arena_block *b = SLIST_FIRST(&a->blocks);
unsigned char *p;
if (size == 0)
return 0;
if (align == 0)
align = a->options.alignment;
/* printf("p getoffset(%p,%d,%d): %d\n",b->pos.next,size,align,getoffset(b->pos.next,size,align));*/
p = b->pos.next + rbits_ptroffset(b->pos.next,size,align);
if (!(p + size <= &b->bytes[b->size])) {
size_t want;
/*
* FIXME: This is sort of a hack. Think of a better way to
* implement this behavior, if not better behavior.
*
* If somebody is sequentially and repeatedly growing a
* buffer beyond the default block length, try to keep
* apace.
*/
if (size > a->options.blocklen)
want = roundup(2 * size, MAX(1, a->options.blocklen));
else
want = size;
if (!(b = arena_block_malloc(a,want,align)))
return 0;
SLIST_INSERT_HEAD(&a->blocks,b,sle);
a->nblocks++;
p = b->pos.next + rbits_ptroffset(b->pos.next,size,align);
}
rbits_put(b->pos.next,p - b->pos.next,size,0);
b->pos.next = p + size;
return p;
} /* arena_malloc() */
void *arena_realloc(struct arena *a, void *p_, size_t n, size_t align) {
struct { size_t old; size_t new; } len;
unsigned char *p = p_;
unsigned char *hp = 0; /* Head of pointer */
unsigned char *tp; /* Tail of pointer */
struct arena_block *top = SLIST_FIRST(&a->blocks);
size_t offset;
if (align == 0)
align = a->options.alignment;
if (p == 0)
return arena_malloc(a,n,align);
else if (n == 0)
return arena_free(a,p), (void *)0;
len.new = n;
assert(((len.old = rbits_get(p - 1,&hp)) > 0 && hp != 0));
tp = p + len.old;
/*
* NOTE: Realignment could make a shorter reallocation consume more
* space than the original allocation. The code follows from that
* observation.
*/
offset = rbits_ptroffset(hp,len.new,align);
if (hp + offset + len.new <= tp) {
ptrdiff_t diff = (p - hp) - offset;
if (diff)
p = memmove(hp + offset,p,MIN(len.new,len.old));
/* If the tail of the original allocation was the top of our
* stack, drop the stack pointer so the space can be reused.
* Otherwise, maintain the stored region size so a reverse
* order free will work as advertised (rather than losing
* track of previous allocations and leaking).
*/
if (top->pos.next == tp) {
top->pos.next = p + len.new;
rbits_put(hp,p - hp,len.new,0);
} else {
/* Store the full size of this region, not just the
* requested.
*/
rbits_put(hp,p - hp,tp - p,0);
}
} else {
/* Are we at the top of the stack and the requested
* allocation still fits? If so, shift the stack pointer and
* memmove the data. Otherwise, allocate a new region and
* copy the data, possibly leaking the old region.
*/
if (top->pos.next == tp && hp + offset + len.new <= &top->bytes[top->size]) {
p = memmove(hp + offset,p,MIN(len.new,len.old));
top->pos.next = p + len.new;
rbits_put(hp,p - hp,len.new,0);
} else if ((p = arena_malloc(a,len.new,align))) {
/* If we were top of stack shift the pointer down.
* This allows reallocations at the top of the stack
* to overflow a block, but still provide leak free
* reverse deallocation later on.
*/
if (top->pos.next == tp)
top->pos.next = hp;
(void)memcpy(p,p_,MIN(len.new,len.old));
}
}
return p;
} /* arena_realloc() */
void arena_free(struct arena *a, void *p_) {
unsigned char *hp, *tp, *p = p_;
struct arena_block *top;
size_t len;
if (!p)
return /* void */;
assert(top = SLIST_FIRST(&a->blocks));
assert(((len = rbits_get(p - 1,&hp)) > 0 && hp != 0));
tp = p + len;
if (top->pos.next != tp)
return /* void */;
/* If the block is empty, free it */
if (top->bytes == (top->pos.next = hp)) {
SLIST_REMOVE_HEAD(&a->blocks,sle);
a->nblocks--;
a->allocator->free(a->allocator,top);
}
return /* void */;
} /* arena_free() */
void arena_mark(struct arena *a, void **p) {
struct arena_block *top;
assert(top = SLIST_FIRST(&a->blocks));
*p = top->pos.next;
return /* void */;
} /* arena_mark() */
void arena_reset(struct arena *a, void *p_) {
unsigned char *p = p_;
struct arena_block *b;
while ((b = SLIST_FIRST(&a->blocks))) {
if (arena_block_regionof(b,p)
|| arena_block_regionof(b,p - 1)) {
b->pos.next = p;
return /* void */;
}
/* Don't free ourselves! */
assert(a->nblocks > 1);
SLIST_REMOVE_HEAD(&a->blocks,sle);
a->nblocks--;
a->allocator->free(a->allocator,b);
}
assert(0 && "Bad arena reset!");
return /* void */;
} /* arena_mark() */
static char arena_name[] = __FILE__;
const char *arena_instanceof(struct arena *a) {
return &arena_name[0];
} /* arena_instanceof() */
struct arena *arena_import(const struct arena_prototype *ap) {
return (ap->instanceof(ap) == &arena_name[0])? (struct arena *)ap : 0;
} /* arena_import() */
size_t arena_lengthof(struct arena *a, void *p) {
unsigned char *hp;
size_t len;
len = rbits_get((unsigned char *)p - 1,&hp);
/* printf("len=%d; p - hp=%d; p=%p\n",len,(int)((unsigned char *)p - hp),p);*/
return len;
} /* arena_lengthof() */
int arena_regionof(struct arena *a, void *p) {
struct arena_block *b;
SLIST_FOREACH(b,&a->blocks,sle) {
if (arena_block_regionof(b,p))
return 1;
}
return 0;
} /* arena_regionof() */
const char *arena_strerror(struct arena *a) {
return a->allocator->strerror(a->allocator);
} /* arena_strerror() */
void arena_clearerr(struct arena *a) {
(a->allocator->clearerr)(a->allocator);
return /* void */;
} /* arena_clearerr() */
const struct arena_prototype *arena_export(struct arena *a) {
if (!a->interface.malloc) {
a->interface.malloc = (void *(*)(const struct arena_prototype *, size_t, size_t))&arena_malloc;
a->interface.realloc = (void *(*)(const struct arena_prototype *, void *, size_t, size_t))&arena_realloc;
a->interface.free = (void (*)(const struct arena_prototype *, void *))&arena_free;
a->interface.instanceof = (const char *(*)(const struct arena_prototype *))&arena_instanceof;
a->interface.strerror = (const char *(*)(const struct arena_prototype *))&arena_strerror;
a->interface.clearerr = (void (*)(const struct arena_prototype *))&arena_clearerr;
}
return &a->interface;
} /* arena_export() */
char *arena_strdup(struct arena *P, const char *src) {
return arena_util_strdup(arena_export(P),src);
} /* arena_strdup() */
char *arena_strndup(struct arena *P, const char *src, size_t n) {
return arena_util_strndup(arena_export(P),src,n);
} /* arena_strndup() */
void *arena_memdup(struct arena *P, const void *p, size_t n) {
return arena_util_memdup(arena_export(P),p,n);
} /* arena_memdup() */
int arena_vasprintf(struct arena *P, char **dstp, const char *fmt, va_list ap) {
return arena_util_vasprintf(arena_export(P),dstp,fmt,ap);
} /* arena_vasprintf() */
int arena_asprintf(struct arena *P, char **dstp, const char *fmt, ...) {
va_list ap;
int n;
va_start(ap,fmt);
n = arena_util_vasprintf(arena_export(P),dstp,fmt,ap);
va_end(ap);
return n;
} /* arena_asprintf() */
char *arena_vsprintf(struct arena *P, const char *fmt, va_list ap) {
return arena_util_vsprintf(arena_export(P),fmt,ap);
} /* arena_vsprintf() */
char *arena_sprintf(struct arena *P, const char *fmt, ...) {
va_list ap;
char *s;
va_start(ap,fmt);
s = arena_util_vsprintf(arena_export(P),fmt,ap);
va_end(ap);
return s;
} /* arena_sprintf() */
#if ARENA_MAIN
#include <stdio.h>
void test0(int argc, char *argv[]) {
ARENA *a;
struct arena_options opts = arena_defaults;
char *list[256];
int i, n;
opts.blocklen = 64;
if (!(a = arena_open(&opts,ARENA_STDLIB)))
err(EXIT_FAILURE,"arena_open");
for (n = 0; n < 8; n++) {
for (i = 0; i < sizeof list / sizeof *list; i++) {
/*if (!(list[i] = arena_malloc(a,2,1)))
err(EXIT_FAILURE,"arena_malloc");
list[i][0] = '0' + i;
list[i][1] = '\0';*/
if (!(list[i] = arena_util_sprintf(arena_export(a),"%d",i)))
err(EXIT_FAILURE,"arena_util_sprintf");
}
for (i = 0; i < sizeof list / sizeof *list; i++) {
printf("list[%d] = %s\n",i,list[i]);
printf("regionof: %s\n",(arena_regionof(a,list[i]))? "yes" : "no");
printf("lengthof: %d\n",(int)arena_lengthof(a,list[i]));
}
printf("nblocks = %d\n",(int)a->nblocks);
for (i = (sizeof list / sizeof *list) - 1; i >= 0; i--) {
arena_free(a,list[i]);
}
printf("nblocks = %d\n",(int)a->nblocks);
}
arena_close(a);
} /* test0() */
int main(int argc, char *argv[]) {
int n;
unsigned char len[RBITS_MAXLEN];
size_t size;
unsigned char *last;
unsigned char *p;
#if 0
printf("RBITS_MAXLEN = %d\n",RBITS_MAXLEN);
size = strtoul(argv[1],0,0);
last = rbits_put(len,sizeof len,size,0);
printf("last - len: %d\n",(int)(last - len));
/*printf("n = %d\n",n);*/
p = &len[sizeof len - 1];
size = rbits_get(p,&p);
printf("size = %lu\n",(unsigned long)size);
printf("p - len: %d\n",(int)(p - len));
#endif
test0(argc,argv);
return 0;
}
#endif /* ARENA_MAIN */
syntax highlighted by Code2HTML, v. 0.9.1