/*  pool.c -- runtime memory pools
 *
 *  These functions implement an allocation pool.
 *
 *  - Pool mpd_makepool (name, el_size, el_max, init, re_init)
 *      initializes a pool of descriptors.  The name is used only to print
 *      error messages.  Each descriptor is of size el_size;  a maximum of
 *      el_max descriptors are allowed.
 *
 *      The functions "init" and "re_init" are supplied to initialize
 *      descriptors.  The "init" function is called only the first time
 *      a descriptor is returned; it should not expect any fields to have
 *      predictable values.  It must use INIT_LOCK to allocate every 
 *      field of type Mutex in the descriptor type.  The function "re_init"
 *      is called every time a descriptor is returned, thus it can assume 
 *      that values are the same as when the descriptor was released.  It 
 *      must use RESET_LOCK to initialize every field of type Mutex
 *	in the descriptor type.
 *
 *	Either "init" or "re_init" may be NULL if no such function is needed.
 *
 *  - Ptr mpd_addpool (Pool p) allocates a new descriptor from pool p and
 *      returns a pointer to it.
 *
 *  - void mpd_delpool (Pool p, Ptr el) returns el to the pool.
 *
 *  - void mpd_eachpool (Pool p, Func f) invokes the function *f on
 *      each currently allocated descriptor in pool p.  The argument
 *      to function f is a pointer to a descriptor.  *f must NOT
 *      allocate or deallocate items from p.
 *
 *  - void mpd_killpool (Pool p) deallocates all storage associated with pool p.
 *
 *  As far as users of a pool are concerned, type Pool is a magic cookie;
 *  there are no useful external operations on it.
 *
 *  Internally, pool allocates two extra pointers for every element.  The
 *  pointers are hidden; they are actually stored just before the address
 *  returned by mpd_addpool (). They are used to keep a doubly linked list of
 *  allocated elements, and one of them is used for linking the free list.
 *
 *  These pool functions access no mutexes except the pool mutexes.
 *  However, the function passed to mpd_eachpool can potentially access
 *  a mutex.
 */

#include "rts.h"

/*
 *  Links for pool elements
 */
typedef struct linkage {
    struct linkage *next;	/* next element in list */
    struct linkage *prev;	/* previous element in list */
} PoolLinks;

/*
 * Representation of a Pool.
 */
struct pool_str {
    char *name;			/* name of this pool */
    char *lockname;		/* name of this pool's lock (for debugging) */
    PoolLinks *free;		/* free list */
    PoolLinks *inuse;		/* in-use list */
    PoolLinks *blk;		/* next block allocated */
    int el_size;		/* element size */
    int el_max;			/* maximum number of elements allowed */
    int el_cur;			/* current number of elements allocated */
    Func init;			/* function to call on new elements */
    Func re_init;		/* function to call on used elements */
    Mutex pmutex;		/* protect table access.  Access to fields
				 * of individual element are protected by
				 * its own lock if necessary. */
};

/*
 * translate between pointers the user sees and pointers to PoolLinks
 */
#define user_addr(el) ((Ptr)(el) + sizeof (PoolLinks))
#define pool_addr(el) ((PoolLinks *)((Ptr)(el) - sizeof (PoolLinks)))



/*
 * initialize a pool
 */
Pool
mpd_makepool (name, el_size, el_max, init, re_init)
char *name;			/* name of pool */
int el_size;			/* size of elements */
int el_max;			/* max number of elements */
Func init;			/* function to initialize new elements */
Func re_init;			/* function to re-initialize elements */
{
    Pool pl;

    pl = (Pool) mpd_alloc (sizeof (struct pool_str));
    pl->name = name;			/* name */
    pl->lockname = (char *) mpd_alloc (strlen (name) + 12);
    sprintf (pl->lockname, "%s->pmutex", pl->name);
    pl->free = 0;			/* free list */
    pl->inuse = 0;			/* in-use list */
    pl->blk = 0;			/* allocated block */
    pl->el_size = el_size + sizeof (PoolLinks);  /* element size */
    pl->el_max = el_max;		/* max number of elements */
    pl->el_cur = 0;			/* current # of elements allocated */
    pl->init = init;
    pl->re_init = re_init;
    INIT_LOCK (pl->pmutex, pl->lockname);
    return pl;
}


/*
 * allocate an element from pool p
 */
Ptr
mpd_addpool (pl)
Pool pl;
{
    PoolLinks *res, *el;
    int i;

    LOCK (pl->pmutex, "mpd_addpool");

    if (pl->free != 0) {
	res = pl->free;
	pl->free = res->next;
    } else if (pl->el_cur < pl->el_max) {
	PoolLinks *blk;
	int how_many;
	
	/* allocate a block of elements */
	if (pl->el_cur + ALCPOOL > pl->el_max)
	    how_many = pl->el_max - pl->el_cur;
	else
	    how_many = ALCPOOL;
	blk = (PoolLinks *) mpd_alloc ((how_many + 1) * pl->el_size);

	/* link blk into block list */
	/* first element in blk is reserved for use as a block link */
	blk->next = pl->blk;
	pl->blk = blk;
	
	/* call init on each element and link into the free list */
	/* (Caution: slightly funky pointer arithmetic) */
	el = pl->free = (PoolLinks *) ((char *) blk + pl->el_size);
	for (i = 0; i < how_many - 1; i += 1) {
	    PoolLinks *next_el;

	    if (pl->init)		/* initialize this element */
		(*pl->init) (user_addr (el));

	    /* link it into the free list */
	    next_el = (PoolLinks *) ((char *) el + pl->el_size);
	    el->next = next_el;
	    el = next_el;
	}
	if (pl->init)			/* initialize the last element */
	    (*pl->init) (user_addr (el));
	el->next = 0;
	pl->el_cur += how_many;
	
	/* finally, get res from free list */
	res = pl->free;
	pl->free = res->next;
    } else {
	char buf[100];
	sprintf (buf, "too many %s", pl->name);
	mpd_abort (buf);
    }

    /* link res into inuse chain */
    res->prev = 0;
    res->next = pl->inuse;
    if (pl->inuse != 0)
	pl->inuse->prev = res;
    pl->inuse = res;
    if (pl->re_init)
	(*pl->re_init) (user_addr (res));

    DEBUG (D_ALLOC, "addpool  @%06lX %s", user_addr (res), pl->name, 0);
    UNLOCK (pl->pmutex, "mpd_addpool");
    return user_addr (res);
}


/*
 *  put el back in pool pl.
 */
void
mpd_delpool (pl, el_ptr)
Pool pl;
Ptr el_ptr;
{
    PoolLinks * el;

    LOCK (pl->pmutex, "mpd_delpool");

    el = pool_addr (el_ptr);
    DEBUG (D_ALLOC, "delpool  @%06lX %s", el_ptr, pl->name, 0);

    /* extract el from the inuse list */
    if (el->prev == 0) {	/* first element */
	pl->inuse = el->next;
	if (pl->inuse)
	    pl->inuse->prev = 0;
    } else if (el->next == 0) {	/* last element, but not only one */
	el->prev->next = 0;
    } else {			/* in the middle */
	el->prev->next = el->next;
	el->next->prev = el->prev;
    }

    /* put element on the free list, which is singly linked */
    if (!DBFLAGS (D_NOFREE)) {		/* can be inhibited by debug flag */
	el->next = pl->free;
	pl->free = el;
    }

    UNLOCK (pl->pmutex, "mpd_delpool");
}


/*
 * invoke f once for each element in pl->inuse.
 * f must NOT alter the inuse list.  And nothing
 * that f calls may cause pmutex to be grabbed
 * again (this problem goes away with a generalized
 * locking facility that allows nesting, as in
 * the special case of mpd_queue_mutex).
 */
void
mpd_eachpool (pl, f)
Pool pl;
Func f;
{
    PoolLinks *el;

    LOCK (pl->pmutex, "mpd_eachpool");

    for (el = pl->inuse; el != 0; el = el->next)
	(*f) (user_addr (el));

    UNLOCK (pl->pmutex, "mpd_eachpool");
}


#ifdef NEVER	/* can re-enable if this is ever needed */
/*
 * deallocate all storage associated with a pool
 */
void
mpd_killpool (p)
Pool p;
{
    PoolLinks *el;

    for (el = p->blk; el != 0;) {
	PoolLinks *next_blk = el->next;
	UNMALLOC ((Ptr) el);
	/*  we really could use a un_init function here to deallocate
	 *  anything that the init function allocated. */
	el = next_blk;
    }
    FREE_LOCK (pl->pmutex, pl->lockname, 0);
    UNMALLOC ((Ptr) p->lockname);
    UNMALLOC ((Ptr) p);
}
#endif
