#ifndef ICI_ALLOC_H
#define ICI_ALLOC_H

/*
 * The following portion of this file exports to ici.h. --ici.h-start--
 */

/*
 * If WHOALLOC is defined to be non-zero (i.e. 1), every allocation records
 * the C source file and line number it was made on and how much was allocated.
 * It is then possible to print a summary, after running for a while, of what
 * is still allocated. Mainly for finding leaks, but it can also be
 * used for other debugging. Changing this requires total recompilation.
 */
#define WHOALLOC    0   /* Define to 1 to track who is using memory. */
#if WHOALLOC
#define DBGMEM      0   /* Set this to track _every_ allocation/free */
#endif  /*WHOALLOC*/


/*
 * Most allocated things will fit within 24 bytes, many of the rest within
 * about 50.  We keep our own lists of things of these sizes, and use the
 * native system's mechanism for the remainder.  Each items on our lists
 * is also allocated directly from the system.  Clearly some time and space
 * advantage would be gained from allocating in larger chunks and then
 * splitting up ourselves, but there are disadvantages too.
 *
 * Raw malloc's will end up being done of the sizes given here plus
 * sizeof(int) plus futher alignment requirements.  We allow these to be
 * set from the configuration file so they can be fine tuned for a
 * particular malloc. (eg. 64 may be a non-optimal size form some mallocs).
 */

#ifndef LIST1Z
#define LIST1Z      24
#endif
#define FEW_OBJS    (LIST1Z / 4)

#ifndef LIST2Z
#define LIST2Z      56
#endif
#define FEW_MORE_OBJS   (LIST2Z / 4)

/*
 * To be sure that the area returned by ici_raw_alloc is properly aligned
 * we must add to the pointer originally returned by malloc a size
 * which will maintain the worst case alignment requirement.
 * Even though we only need one byte of space for our header.
 *
 * The following structure is used as an overlay to make sure we add
 * the right amount to start user data at a good alignment position.
 */
typedef struct
{
#if !WHOALLOC
    int         ga_list;    /* Which fast free list, 0 if none. */
#else
#if !DBGMEM
    short       ga_list;
    short       ga_who;
    int         ga_size;
#else   /* DBGMEM */
    int         ga_list;    /* which fast free list */
    int         ga_size;
#endif  /* DBGMEM */
#endif
    union
    {
        char    gau_data[1];    /* The first byte of user data when not free.*/
        char    *gau_next;      /* Next free block when on fast free list. */
        double  gau_align;      /* Only to force alignment. */
    }
    gau;
}
    gahead_t;

/*
 * The number of bytes between the prefix block header and the start
 * of the user data. This is the overhead. (offsetof() from stddef.h).
 */
#define GALLOC_HEADZ    offsetof(gahead_t, gau.gau_data)

/*
 * Return a pointer to the header, given the user pointer.
 */
#define gahead(p)   ((gahead_t *)((char *)p - GALLOC_HEADZ))
/*
 * Return the user pointer given a pointer to the header.
 */
#define gadata(p)   (((gahead_t *)p)->gau.gau_data)
/*
 * Given a size, which, if any, fast free list should it be on?
 * Note that this will be a constant expression if z is a constant.
 */
#define galist(z)   ((z) <= LIST1Z ? 1 : (z) <= LIST2Z ? 2 : 0)

/*
 * Allocate memory as if by ici_raw_alloc(). But if there is currently a block
 * of suitable size on one of the fast free lists, just use that. If there
 * isn't resort to the real ici_raw_alloc() function.
 *
 * Every block allocated by ici_raw_alloc() is prefixed by a header which
 * contains an int. This int is 0 in general, but if the block is of an
 * appropriate size for one of the fast free lists, it is a small integer
 * index of that free list.
 */
#define ici_alloc(z) \
        (ici_fflist[galist(z)] != NULL \
        ? ici_new_mem += (z), ici_fflist[galist(z)] \
            = *(char **)(ici_fftmp = ici_fflist[galist(z)]), ici_fftmp \
        : ici_raw_alloc(z))

/*
 * Free memory allocated with ici_raw_alloc/ici_alloc. If it belongs on one
 * of the fast free lists, just put it on that. Otherwise resort to the real
 * function.
 */
#define ici_free(p) \
    do\
        if (gahead(p)->ga_list != 0) \
        { \
            *(char **)(p) = ici_fflist[gahead(p)->ga_list]; \
            ici_fflist[gahead(p)->ga_list] = (char *)(p); \
        } \
        else \
            ici_raw_free(p);\
    while (0)

extern DLI char *ici_fflist[3]; /* The array of free lists heads. */
extern DLI char *ici_fftmp;     /* Temporary used in ici_alloc() above. */
extern DLI long ici_old_mem;    /* Num bytes in use after last collect. */
extern DLI long ici_new_mem;    /* Num bytes alloced since last collect. */
extern void ici_raw_free(void *);
#if !WHOALLOC
extern void *ici_raw_alloc(int);
#else
extern void *ici_raw_alloc(int, char *, int);
#undef  ici_alloc
#undef  ici_free
#define ici_alloc(z)    ici_raw_alloc((z), __FILE__, __LINE__)
#define ici_free(p)     ici_raw_free(p)
#endif

#ifdef  SMALL
/*
 * If SMALL is defined we ignore all those macros and always use functions
 * for allocation. No fast free lists are used. The address returned by
 * malloc is the address of our objects. Incompatible with WHOALLOC.
 * If your malloc() is even slightly slow this will significantly degrade
 * performance.
 */
#undef  GALLOC_HEADZ
#undef  gahead
#undef  gadata
#undef  galist
#undef  ici_alloc
#undef  ici_free

#define GALLOC_HEADZ    0
#define gahead(p)       p
#define gadata(p)       p
#define galist(z)       0
#define ici_alloc(z)    ici_raw_alloc(z)
#define ici_free(p)     ici_raw_free(p)
#endif /* SMALL */
/*
 * End of ici.h export. --ici.h-end--
 */

#endif /* ICI_ALLOC_H */
