#include "ep.h"
#ifdef OS2
#define INCL_DOS
#include <os2.h>
#endif
#ifndef EP_API_EXPORT
#define EP_API_EXPORT(x) x
#endif
#ifndef EP_API_EXPORT_NONSTD
#define EP_API_EXPORT_NONSTD(x) x
#endif
#ifndef ep_inline
#define ep_inline
#endif
#define ep_block_alarms()
#define ep_unblock_alarms()
#ifndef BLOCK_MINFREE
#define BLOCK_MINFREE 4096
#endif
#ifndef BLOCK_MINALLOC
#define BLOCK_MINALLOC 8192
#endif
#ifndef DMALLOC
#undef malloc
#undef free
#undef fprintf
#undef exit
#endif
#ifdef POOL_DEBUG
#ifdef ALLOC_USE_MALLOC
# error "sorry, no support for ALLOC_USE_MALLOC and POOL_DEBUG at the same time"
#endif
#ifdef MULTITHREAD
# error "sorry, no support for MULTITHREAD and POOL_DEBUG at the same time"
#endif
#endif
#ifdef ALLOC_USE_MALLOC
#undef BLOCK_MINFREE
#undef BLOCK_MINALLOC
#define BLOCK_MINFREE 0
#define BLOCK_MINALLOC 0
#endif
union
align {
char
*cp;
void
(*f) (
void
);
long
l;
FILE
*fp;
double
d;
};
#define CLICK_SZ (sizeof(union align))
union
block_hdr {
union
align a;
struct
{
char
*endp;
union
block_hdr *next;
char
*first_avail;
#ifdef POOL_DEBUG
union
block_hdr *global_next;
struct
tMemPool *owning_pool;
#endif
} h;
};
static
union
block_hdr *block_freelist = NULL;
static
perl_mutex alloc_mutex ;
static
perl_mutex spawn_mutex ;
#ifdef POOL_DEBUG
static
char
*known_stack_point;
static
int
stack_direction;
static
union
block_hdr *global_block_list;
#define FREE_POOL ((struct tMemPool *)(-1))
#endif
#ifdef ALLOC_STATS
static
unsigned
long
long
num_free_blocks_calls;
static
unsigned
long
long
num_blocks_freed;
static
unsigned max_blocks_in_one_free;
static
unsigned num_malloc_calls;
static
unsigned num_malloc_bytes;
#endif
#ifdef ALLOC_DEBUG
#define FILL_BYTE ((char)(0xa5))
#define debug_fill(ptr,size) ((void)memset((ptr), FILL_BYTE, (size)))
static
ep_inline
void
debug_verify_filled(
const
char
*ptr,
const
char
*endp,
const
char
*error_msg)
{
for
(; ptr < endp; ++ptr) {
if
(*ptr != FILL_BYTE) {
fputs
(error_msg, stderr);
abort
();
exit
(1);
}
}
}
#else
#define debug_fill(a,b)
#define debug_verify_filled(a,b,c)
#endif
static
union
block_hdr *malloc_block(
int
size)
{
union
block_hdr *blok;
#ifdef ALLOC_DEBUG
size += CLICK_SZ;
#endif
#ifdef ALLOC_STATS
++num_malloc_calls;
num_malloc_bytes += size +
sizeof
(
union
block_hdr);
#endif
blok = (
union
block_hdr *)
malloc
(size +
sizeof
(
union
block_hdr));
if
(blok == NULL) {
printf
(
"Ouch! malloc failed in malloc_block()\n"
);
exit
(1);
}
debug_fill(blok, size +
sizeof
(
union
block_hdr));
blok->h.next = NULL;
blok->h.first_avail = (
char
*) (blok + 1);
blok->h.endp = size + blok->h.first_avail;
#ifdef ALLOC_DEBUG
blok->h.endp -= CLICK_SZ;
#endif
#ifdef POOL_DEBUG
blok->h.global_next = global_block_list;
global_block_list = blok;
blok->h.owning_pool = NULL;
#endif
return
blok;
}
#if defined(ALLOC_DEBUG) && !defined(ALLOC_USE_MALLOC)
static
void
chk_on_blk_list(
union
block_hdr *blok,
union
block_hdr *free_blk)
{
debug_verify_filled(blok->h.endp, blok->h.endp + CLICK_SZ,
"Ouch! Someone trounced the padding at the end of a block!\n"
);
while
(free_blk) {
if
(free_blk == blok) {
fprintf
(stderr,
"Ouch! Freeing free block\n"
);
abort
();
exit
(1);
}
free_blk = free_blk->h.next;
}
}
#else
#define chk_on_blk_list(_x, _y)
#endif
static
void
free_blocks(
union
block_hdr *blok)
{
#ifdef ALLOC_USE_MALLOC
union
block_hdr *next;
for
(; blok; blok = next) {
next = blok->h.next;
free
(blok);
}
#else
#ifdef ALLOC_STATS
unsigned num_blocks;
#endif
union
block_hdr *old_free_list;
if
(blok == NULL)
return
;
ep_acquire_mutex(alloc_mutex);
old_free_list = block_freelist;
block_freelist = blok;
#ifdef ALLOC_STATS
num_blocks = 1;
#endif
while
(blok->h.next != NULL) {
#ifdef ALLOC_STATS
++num_blocks;
#endif
chk_on_blk_list(blok, old_free_list);
blok->h.first_avail = (
char
*) (blok + 1);
debug_fill(blok->h.first_avail, blok->h.endp - blok->h.first_avail);
#ifdef POOL_DEBUG
blok->h.owning_pool = FREE_POOL;
#endif
blok = blok->h.next;
}
chk_on_blk_list(blok, old_free_list);
blok->h.first_avail = (
char
*) (blok + 1);
debug_fill(blok->h.first_avail, blok->h.endp - blok->h.first_avail);
#ifdef POOL_DEBUG
blok->h.owning_pool = FREE_POOL;
#endif
blok->h.next = old_free_list;
#ifdef ALLOC_STATS
if
(num_blocks > max_blocks_in_one_free) {
max_blocks_in_one_free = num_blocks;
}
++num_free_blocks_calls;
num_blocks_freed += num_blocks;
#endif
ep_release_mutex(alloc_mutex);
#endif
}
static
union
block_hdr *new_block(
int
min_size)
{
union
block_hdr **lastptr = &block_freelist;
union
block_hdr *blok = block_freelist;
while
(blok != NULL) {
if
(min_size + BLOCK_MINFREE <= blok->h.endp - blok->h.first_avail) {
*lastptr = blok->h.next;
blok->h.next = NULL;
debug_verify_filled(blok->h.first_avail, blok->h.endp,
"Ouch! Someone trounced a block on the free list!\n"
);
return
blok;
}
else
{
lastptr = &blok->h.next;
blok = blok->h.next;
}
}
min_size += BLOCK_MINFREE;
blok = malloc_block((min_size > BLOCK_MINALLOC) ? min_size : BLOCK_MINALLOC);
return
blok;
}
static
long
bytes_in_block_list(
union
block_hdr *blok)
{
long
size = 0;
while
(blok) {
size += blok->h.endp - (
char
*) (blok + 1);
blok = blok->h.next;
}
return
size;
}
struct
process_chain;
struct
cleanup;
#define run_cleanups(x)
#define free_proc_chain(x)
struct
tMemPool {
union
block_hdr *first;
union
block_hdr *last;
struct
cleanup *cleanups;
struct
process_chain *subprocesses;
struct
tMemPool *sub_pools;
struct
tMemPool *sub_next;
struct
tMemPool *sub_prev;
struct
tMemPool *parent;
char
*free_first_avail;
#ifdef ALLOC_USE_MALLOC
void
*allocation_list;
#endif
#ifdef POOL_DEBUG
struct
tMemPool *joined;
#endif
};
static
tMemPool *permanent_pool;
#define POOL_HDR_CLICKS (1 + ((sizeof(struct tMemPool) - 1) / CLICK_SZ))
#define POOL_HDR_BYTES (POOL_HDR_CLICKS * CLICK_SZ)
EP_API_EXPORT(
struct
tMemPool *) ep_make_sub_pool(
struct
tMemPool *p)
{
union
block_hdr *blok;
tMemPool *new_pool;
ep_block_alarms();
ep_acquire_mutex(alloc_mutex);
blok = new_block(POOL_HDR_BYTES);
new_pool = (tMemPool *) blok->h.first_avail;
blok->h.first_avail += POOL_HDR_BYTES;
#ifdef POOL_DEBUG
blok->h.owning_pool = new_pool;
#endif
memset
((
char
*) new_pool,
'\0'
,
sizeof
(
struct
tMemPool));
new_pool->free_first_avail = blok->h.first_avail;
new_pool->first = new_pool->last = blok;
if
(p) {
new_pool->parent = p;
new_pool->sub_next = p->sub_pools;
if
(new_pool->sub_next)
new_pool->sub_next->sub_prev = new_pool;
p->sub_pools = new_pool;
}
ep_release_mutex(alloc_mutex);
ep_unblock_alarms();
return
new_pool;
}
#ifdef POOL_DEBUG
static
void
stack_var_init(
char
*s)
{
char
t;
if
(s < &t) {
stack_direction = 1;
}
else
{
stack_direction = -1;
}
}
#endif
#ifdef ALLOC_STATS
static
void
dump_stats(
void
)
{
fprintf
(stderr,
"alloc_stats: [%d] #free_blocks %llu #blocks %llu max %u #malloc %u #bytes %u\n"
,
(
int
)getpid(),
num_free_blocks_calls,
num_blocks_freed,
max_blocks_in_one_free,
num_malloc_calls,
num_malloc_bytes);
}
#endif
tMemPool *ep_init_alloc(
void
)
{
#ifdef POOL_DEBUG
char
s;
known_stack_point = &s;
stack_var_init(&s);
#endif
ep_create_mutex(alloc_mutex);
ep_create_mutex(spawn_mutex );
permanent_pool = ep_make_sub_pool(NULL);
#ifdef ALLOC_STATS
atexit
(dump_stats);
#endif
return
permanent_pool;
}
void
ep_cleanup_alloc(
void
)
{
ep_destroy_mutex(alloc_mutex);
ep_destroy_mutex(spawn_mutex);
}
EP_API_EXPORT(
void
) ep_clear_pool(
struct
tMemPool *a)
{
ep_block_alarms();
ep_acquire_mutex(alloc_mutex);
while
(a->sub_pools)
ep_destroy_pool(a->sub_pools);
ep_release_mutex(alloc_mutex);
run_cleanups(a->cleanups);
a->cleanups = NULL;
free_proc_chain(a->subprocesses);
a->subprocesses = NULL;
free_blocks(a->first->h.next);
a->first->h.next = NULL;
a->last = a->first;
a->first->h.first_avail = a->free_first_avail;
debug_fill(a->first->h.first_avail,
a->first->h.endp - a->first->h.first_avail);
#ifdef ALLOC_USE_MALLOC
{
void
*c, *n;
for
(c = a->allocation_list; c; c = n) {
n = *(
void
**)c;
free
(c);
}
a->allocation_list = NULL;
}
#endif
ep_unblock_alarms();
}
EP_API_EXPORT(
void
) ep_destroy_pool(tMemPool *a)
{
ep_block_alarms();
ep_clear_pool(a);
ep_acquire_mutex(alloc_mutex);
if
(a->parent) {
if
(a->parent->sub_pools == a)
a->parent->sub_pools = a->sub_next;
if
(a->sub_prev)
a->sub_prev->sub_next = a->sub_next;
if
(a->sub_next)
a->sub_next->sub_prev = a->sub_prev;
}
ep_release_mutex(alloc_mutex);
free_blocks(a->first);
ep_unblock_alarms();
}
EP_API_EXPORT(
long
) ep_bytes_in_pool(tMemPool *p)
{
return
bytes_in_block_list(p->first);
}
EP_API_EXPORT(
long
) ep_bytes_in_free_blocks(
void
)
{
return
bytes_in_block_list(block_freelist);
}
#ifdef POOL_DEBUG
extern
char
_end;
#define is_ptr_in_range(ptr, lo, hi) \
(((unsigned
long
)(ptr) - (unsigned
long
)(lo)) \
< \
(unsigned
long
)(hi) - (unsigned
long
)(lo))
EP_API_EXPORT(tMemPool *) ep_find_pool(
const
void
*ts)
{
const
char
*s = ts;
union
block_hdr **pb;
union
block_hdr *b;
if
(is_ptr_in_range(s, 0, &_end)) {
return
NULL;
}
if
((stack_direction == -1 && is_ptr_in_range(s, &ts, known_stack_point))
|| (stack_direction == 1 && is_ptr_in_range(s, known_stack_point, &ts))) {
abort
();
return
NULL;
}
ep_block_alarms();
for
(pb = &global_block_list; *pb; pb = &b->h.global_next) {
b = *pb;
if
(is_ptr_in_range(s, b, b->h.endp)) {
if
(b->h.owning_pool == FREE_POOL) {
fprintf
(stderr,
"Ouch! find_pool() called on pointer in a free block\n"
);
abort
();
exit
(1);
}
if
(b != global_block_list) {
*pb = b->h.global_next;
b->h.global_next = global_block_list;
global_block_list = b;
}
ep_unblock_alarms();
return
b->h.owning_pool;
}
}
ep_unblock_alarms();
return
NULL;
}
EP_API_EXPORT(
int
) ep_pool_is_ancestor(tMemPool *a, tMemPool *b)
{
if
(a == NULL) {
return
1;
}
while
(a->joined) {
a = a->joined;
}
while
(b) {
if
(a == b) {
return
1;
}
b = b->parent;
}
return
0;
}
EP_API_EXPORT(
void
) ep_pool_join(tMemPool *p, tMemPool *sub)
{
union
block_hdr *b;
if
(sub->parent != p) {
fprintf
(stderr,
"pool_join: p is not parent of sub\n"
);
abort
();
}
ep_block_alarms();
while
(p->joined) {
p = p->joined;
}
sub->joined = p;
for
(b = global_block_list; b; b = b->h.global_next) {
if
(b->h.owning_pool == sub) {
b->h.owning_pool = p;
}
}
ep_unblock_alarms();
}
#endif
EP_API_EXPORT(
void
*) ep_palloc(
struct
tMemPool *a,
int
reqsize)
{
#ifdef ALLOC_USE_MALLOC
int
size = reqsize + CLICK_SZ;
void
*ptr;
ep_block_alarms();
ptr =
malloc
(size);
if
(ptr == NULL) {
fputs
(
"Ouch! Out of memory!\n"
, stderr);
exit
(1);
}
debug_fill(ptr, size);
*(
void
**)ptr = a->allocation_list;
a->allocation_list = ptr;
ep_unblock_alarms();
return
(
char
*)ptr + CLICK_SZ;
#else
int
nclicks = 1 + ((reqsize - 1) / CLICK_SZ);
int
size = nclicks * CLICK_SZ;
union
block_hdr *blok = a->last;
char
*first_avail = blok->h.first_avail;
char
*new_first_avail;
if
(reqsize <= 0)
return
NULL;
new_first_avail = first_avail + size;
if
(new_first_avail <= blok->h.endp) {
debug_verify_filled(first_avail, blok->h.endp,
"Ouch! Someone trounced past the end of their allocation!\n"
);
blok->h.first_avail = new_first_avail;
return
(
void
*) first_avail;
}
ep_block_alarms();
ep_acquire_mutex(alloc_mutex);
blok = new_block(size);
a->last->h.next = blok;
a->last = blok;
#ifdef POOL_DEBUG
blok->h.owning_pool = a;
#endif
ep_release_mutex(alloc_mutex);
ep_unblock_alarms();
first_avail = blok->h.first_avail;
blok->h.first_avail += size;
return
(
void
*) first_avail;
#endif
}
EP_API_EXPORT(
void
*) ep_pcalloc(
struct
tMemPool *a,
int
size)
{
void
*res = ep_palloc(a, size);
memset
(res,
'\0'
, size);
return
res;
}
EP_API_EXPORT(
char
*) ep_pstrdup(
struct
tMemPool *a,
const
char
*s)
{
char
*res;
size_t
len;
if
(s == NULL)
return
NULL;
len =
strlen
(s) + 1;
res = ep_palloc(a, len);
memcpy
(res, s, len);
return
res;
}
EP_API_EXPORT(
char
*) ep_pstrndup(
struct
tMemPool *a,
const
char
*s,
int
n)
{
char
*res;
if
(s == NULL)
return
NULL;
res = ep_palloc(a, n + 1);
memcpy
(res, s, n);
res[n] =
'\0'
;
return
res;
}
EP_API_EXPORT_NONSTD(
char
*) ep_pstrcat(tMemPool *a,...)
{
char
*cp, *argp, *res;
int
len = 0;
va_list
adummy;
va_start
(adummy, a);
while
((cp =
va_arg
(adummy,
char
*)) != NULL)
len +=
strlen
(cp);
va_end
(adummy);
res = (
char
*) ep_palloc(a, len + 1);
cp = res;
*cp =
'\0'
;
va_start
(adummy, a);
while
((argp =
va_arg
(adummy,
char
*)) != NULL) {
strcpy
(cp, argp);
cp +=
strlen
(argp);
}
va_end
(adummy);
return
res;
}
#ifdef EPSPRINTF
struct
psprintf_data {
ep_vformatter_buff vbuff;
#ifdef ALLOC_USE_MALLOC
char
*base;
#else
union
block_hdr *blok;
int
got_a_new_block;
#endif
};
static
int
psprintf_flush(ep_vformatter_buff *vbuff)
{
struct
psprintf_data *ps = (
struct
psprintf_data *)vbuff;
#ifdef ALLOC_USE_MALLOC
int
size;
char
*ptr;
size = (
char
*)ps->vbuff.curpos - ps->base;
ptr =
realloc
(ps->base, 2*size);
if
(ptr == NULL) {
fputs
(
"Ouch! Out of memory!\n"
, stderr);
exit
(1);
}
ps->base = ptr;
ps->vbuff.curpos = ptr + size;
ps->vbuff.endpos = ptr + 2*size - 1;
return
0;
#else
union
block_hdr *blok;
union
block_hdr *nblok;
size_t
cur_len;
char
*strp;
blok = ps->blok;
strp = ps->vbuff.curpos;
cur_len = strp - blok->h.first_avail;
(
void
) ep_acquire_mutex(alloc_mutex);
nblok = new_block(2 * cur_len);
(
void
) ep_release_mutex(alloc_mutex);
memcpy
(nblok->h.first_avail, blok->h.first_avail, cur_len);
ps->vbuff.curpos = nblok->h.first_avail + cur_len;
ps->vbuff.endpos = nblok->h.endp - 1;
if
(ps->got_a_new_block) {
debug_fill(blok->h.first_avail, blok->h.endp - blok->h.first_avail);
(
void
) ep_acquire_mutex(alloc_mutex);
blok->h.next = block_freelist;
block_freelist = blok;
(
void
) ep_release_mutex(alloc_mutex);
}
ps->blok = nblok;
ps->got_a_new_block = 1;
return
0;
#endif
}
EP_API_EXPORT(
char
*) ep_pvsprintf(tMemPool *p,
const
char
*fmt,
va_list
ap)
{
#ifdef ALLOC_USE_MALLOC
struct
psprintf_data ps;
void
*ptr;
ep_block_alarms();
ps.base =
malloc
(512);
if
(ps.base == NULL) {
fputs
(
"Ouch! Out of memory!\n"
, stderr);
exit
(1);
}
ps.vbuff.curpos = ps.base + CLICK_SZ;
ps.vbuff.endpos = ps.base + 511;
ep_vformatter(psprintf_flush, &ps.vbuff, fmt, ap);
*ps.vbuff.curpos++ =
'\0'
;
ptr = ps.base;
ptr =
realloc
(ptr, (
char
*)ps.vbuff.curpos - (
char
*)ptr);
if
(ptr == NULL) {
fputs
(
"Ouch! Out of memory!\n"
, stderr);
exit
(1);
}
*(
void
**)ptr = p->allocation_list;
p->allocation_list = ptr;
ep_unblock_alarms();
return
(
char
*)ptr + CLICK_SZ;
#else
struct
psprintf_data ps;
char
*strp;
int
size;
ep_block_alarms();
ps.blok = p->last;
ps.vbuff.curpos = ps.blok->h.first_avail;
ps.vbuff.endpos = ps.blok->h.endp - 1;
ps.got_a_new_block = 0;
ep_vformatter(psprintf_flush, &ps.vbuff, fmt, ap);
strp = ps.vbuff.curpos;
*strp++ =
'\0'
;
size = strp - ps.blok->h.first_avail;
size = (1 + ((size - 1) / CLICK_SZ)) * CLICK_SZ;
strp = ps.blok->h.first_avail;
ps.blok->h.first_avail += size;
if
(ps.got_a_new_block) {
p->last->h.next = ps.blok;
p->last = ps.blok;
#ifdef POOL_DEBUG
ps.blok->h.owning_pool = p;
#endif
}
ep_unblock_alarms();
return
strp;
#endif
}
EP_API_EXPORT_NONSTD(
char
*) ep_psprintf(tMemPool *p,
const
char
*fmt, ...)
{
va_list
ap;
char
*res;
va_start
(ap, fmt);
res = ep_pvsprintf(p, fmt, ap);
va_end
(ap);
return
res;
}
#endif