/*
// Full copyright information is available in the file ../doc/CREDITS
//
// Memory management.
//
// This code is not ANSI-conformant, because it plays some games with pointer
// conversions which ANSI does not allow.  It also assumes that a Long has the
// most restrictive alignment.  I do the pile and tray mallocs this way because
// they work on most systems and save space.
*/

#include "defs.h"

#include <sys/types.h>

/* This file supports a tray malloc and a pile malloc.  The tray malloc
 * enhances allocation efficiency by keeping trays for small pieces of
 * data.  Note that tfree() and trealloc() require the size of the old
 * block.  trealloc() handles ptr == NULL and newsize == 0 appropriately.
 *
 * The pile malloc enhances efficiency and convenience for applications
 * that allocate a lot of memory in small chunks and then free it all at
 * once.  We call new_pile() to get a handle on a 'pile' that we can
 * allocate memory from.  pile_malloc() accepts a pile in addition to the
 * size argument.  pile_free() frees all the used memory in a pile.  It
 * retains up to MAX_BLOCKS blocks of memory in the pile to avoid repeated
 * mallocs and frees of large blocks. */

#define MAX(a, b)	(((a) >= (b)) ? (a) : (b))

#define TRAY_INC	sizeof(Tlist)
#define NUM_TRAYS	4
#define MAX_USE_TRAY	(NUM_TRAYS * TRAY_INC)
#define TRAY_ELEM	508

#define PILE_BLOCK_SIZE 254
#define MAX_PILE_BLOCKS 8

typedef Long Align;
typedef union tlist Tlist;
typedef struct block Block;
typedef struct oversized Oversized;

union tlist {
    Tlist *next;
    Align a;
};

struct pile {
    Block *blocks;
    Oversized *over;
};

struct block {
    Align data[PILE_BLOCK_SIZE];
    Int pos;
    Block *next;
};

struct oversized {
    void *data;
    Oversized *next;
};

static Tlist *trays[NUM_TRAYS];

#ifdef DOFUNC_FREE
void efree(void *block)
{
    free(block);
}
#endif

void *emalloc(size_t size)
{
    void *ptr;

    ptr = malloc(size);
    if (!ptr)
        panic("emalloc(%lX) failed.", size);

    return ptr;
}

void *erealloc(void *ptr, size_t size)
{
    void *newptr;

    newptr = realloc(ptr, size);
    if (!newptr)
        panic("erealloc(%ld) failed.", size);
    return newptr;
}

void *tmalloc(size_t size)
{
    Int t, n, i;
    void *p;

    /* If the block isn't fairly small, fall back on malloc(). */
    if (size > MAX_USE_TRAY)
        return emalloc(size);

    /* Find the appropriate tray to use. */
    t = (size - 1) / TRAY_INC;

    /* If we're out of tray elements, make a new tray. */
    if (!trays[t]) {
        trays[t] = EMALLOC(Tlist, TRAY_ELEM);

        /* n is the number of Tlists we need for each tray element. */
        n = t + 1;

        /* Link up tray elements. */
        for (i = 0; i <= TRAY_ELEM - (2 * n); i += n)
            trays[t][i].next = &trays[t][i + n];
        trays[t][i].next = NULL;
    }

    /* Return the first tray element, and set trays[t] to the next tray
     * element. */
    p = (void *) trays[t];
    trays[t] = trays[t]->next;
    return p;
}

void tfree(void *ptr, size_t size)
{
    Int t;

    /* If the block size is greater than MAX_USE_TRAY, then tmalloc() didn't
     * pull it out of a tray, so just free it normally. */
    if (size > MAX_USE_TRAY) {
        efree(ptr);
        return;
    }

    /* Add this element to the appropriate tray. */
    t = (size - 1) / TRAY_INC;
    ((Tlist *) ptr)->next = trays[t];
    trays[t] = (Tlist *) ptr;
}

void *trealloc(void *ptr, size_t oldsize, size_t newsize)
{
    void *cnew;

    /* If neither the old block or the new block is fairly small, then just
     * fall back on realloc(). */
    if (oldsize > MAX_USE_TRAY && newsize > MAX_USE_TRAY)
        return erealloc(ptr, newsize);

    /* If sizes are such that we would be using the same tray for both blocks,
     * just return the old pointer. */
    if ((oldsize - 1) / TRAY_INC == (newsize - 1) / TRAY_INC)
        return ptr;

    /* Allocate a new tray, copy into it, and free the old tray. */
    cnew = tmalloc(newsize);
    memcpy(cnew, ptr, MAX(newsize, oldsize));
    tfree(ptr, oldsize);

    return cnew;
}

/* Duplicate a string, using tray memory. */
char *tstrdup(char *s)
{
    Int len = strlen(s);
    char *cnew;

    cnew = TMALLOC(char, len + 1);
    if (cnew)
        memcpy(cnew, s, len + 1);
    else
        panic("tstrdup(): malloc(%ld) failed.", len);

    return cnew;
}

char *tstrndup(char *s, Int len)
{
    char *cnew;

    cnew = TMALLOC(char, len + 1);
    memcpy(cnew, s, len);
    cnew[len] = '\0';
    return cnew;
}

/* Frees a tray-allocated string, assuming we
   allocated exactly enough memory for it. */
void tfree_chars(char *s)
{
    if (s)
        tfree(s, strlen(s) + 1);
}

#if DISABLED
Pile *new_pile(void)
{
    Pile *cnew;

    cnew = TMALLOC(Pile, 1);

    /* Start with one block and no oversized blocks. */
    cnew->blocks = EMALLOC(Block, 1);
    cnew->blocks->pos = 0;
    cnew->blocks->next = NULL;
    cnew->over = NULL;
    return cnew;
}

void *pmalloc(Pile * pile, size_t size)
{
    Block *b;
    Int aligns = (size - 1) / sizeof(Align) + 1;

    /* If the size is larger than a block, then make an oversized block and
     * link it in. */
    if (aligns > PILE_BLOCK_SIZE) {
        Oversized *o;

        o = TMALLOC(Oversized, 1);
        o->data = emalloc(size);
        o->next = pile->over;
        pile->over = o;
        return o->data;
    }

    /* Look for a block with enough space. */
    for (b = pile->blocks; b; b = b->next) {
        if (b->pos + aligns <= PILE_BLOCK_SIZE) {
            b->pos += aligns;
            return (void *) (&b->data[b->pos - aligns]);
        }
    }

    /* There weren't any.  Make a new block. */
    b = EMALLOC(Block, 1);
    b->pos = aligns;
    b->next = pile->blocks;
    pile->blocks = b;
    return (void *) (&b->data[0]);
}

/* Free all the memory allocated from a pile. */
void pfree(Pile * pile)
{
    Oversized *o, *nexto;
    Block *b, *nextb;
    Int count;

    /* Free all the oversized blocks. */
    for (o = pile->over; o; o = nexto) {
        nexto = o->next;
        efree(o->data);
        TFREE(o, 1);
    }
    pile->over = NULL;

    /* Reset positions of blocks up to MAX_PILE_BLOCKS - 1. */
    b = pile->blocks;
    count = 0;
    while (b && count < MAX_PILE_BLOCKS - 1) {
        b->pos = 0;
        b = b->next;
        count++;
    }

    /* If we didn't run out of blocks, then we're at the last block.  Reset its
     * position and set its ->next pointer to null.  Then free the rest of the
     * blocks. */
    if (b) {
        b->pos = 0;
        nextb = b->next;
        b->next = NULL;
        while (nextb) {
            b = nextb->next;
            efree(nextb);
            nextb = b;
        }
    }
}
#endif

/* new routines courtesy of Dancer */
Pile *new_pile(void)
{
    Pile *tmp;
    /* static Int pile_counter=0; */

    tmp = emalloc(sizeof(Pile));
    tmp->blocks = NULL;

    return tmp;
}

typedef struct blink_s blink_t;

struct blink_s {
    void *data;
    size_t sz;
    blink_t *next;
};

void *pmalloc(Pile * p, size_t s)
{
    /* void    * newblock; */
    blink_t *blink;

    if (p->blocks == NULL) {
        blink = emalloc(sizeof(blink_t));
        blink->data = tmalloc(s);
        blink->sz = s;
        blink->next = NULL;
        p->blocks = (Block *) blink;
        return (blink->data);
    }
    blink = emalloc(sizeof(blink_t));
    blink->data = tmalloc(s);
    blink->sz = s;
    blink->next = (blink_t *) p->blocks;
    p->blocks = (Block *) blink;
    return blink->data;
}

void pfree(Pile * p)
{
    blink_t *roam, *ahead;

    roam = (blink_t *) p->blocks;
    while (roam) {
        ahead = roam->next;
        tfree(roam->data, roam->sz);
        efree(roam);
        roam = ahead;
    }
    p->blocks = NULL;
}
