
/*
 * This is a combined alloca/malloc package. It uses a classic algorithm
 * and so may be seen to be quite slow compared to more modern routines
 * with 'nasty' distributions.
 */

extern void *malloc();
extern void *calloc();
extern void *realloc();
extern void free();

#define MCHUNK		2048	/* Allocation unit in 'mem' elements */
#define XLAZY_FREE		/* If set frees can be infinitly defered */
#define XMINALLOC	32	/* Smallest chunk to alloc in 'mem's */
#define XVERBOSE		/* Lots of noise, debuging ? */

#ifdef VERBOSE
#define noise __noise
#else
#define noise(y,x)
#endif

typedef union mem_cell
{
   union mem_cell *next;	/* A pointer to the next mem */
   unsigned int size;		/* An int >= sizeof pointer */
   char *depth;			/* For the alloca hack */
}
mem;

#define m_size(p)  ((p) [0].size)	/* For malloc */
#define m_next(p)  ((p) [1].next)	/* For malloc and alloca */
#define m_deep(p)  ((p) [0].depth)	/* For alloca */

#ifdef L_free
/* Start the alloca with just the dumb version of malloc */

void *__more_memory();
void *(*__alloca_alloc) () = __more_memory;
mem  *__freed_list = 0;
#else

extern void *__more_memory();
extern void *(*__alloca_alloc) ();
extern mem *__freed_list;
#endif

#ifdef L_alloca
static mem *alloca_stack = 0;

void *
alloca(size)
unsigned size;
{
   auto char probe;		/* Probes stack depth: */
   register mem *hp;

   /*
    * Reclaim garbage, defined as all alloca'd storage that was allocated
    * from deeper in the stack than currently.
    */

   for (hp = alloca_stack; hp != 0;)
      if (m_deep(hp) < &probe)
      {
	 register mem *np = m_next(hp);
	 free((void *) hp);	/* Collect garbage.  */
	 hp = np;		/* -> next header.  */
      }
      else
	 break;			/* Rest are not deeper.  */

   alloca_stack = hp;		/* -> last valid storage.  */
   if (size == 0)
      return 0;			/* No allocation required.  */

   hp = (mem *) (*__alloca_alloc) (sizeof(mem) + size);
   if (hp == 0)
      return hp;

   m_next(hp) = alloca_stack;
   m_deep(hp) = &probe;
   alloca_stack = hp;

   /* User storage begins just after header.  */
   return (void *) (hp + 2);
}
#endif				/* L_alloca */

#ifdef L_free
void
free(ptr)
void *ptr;
{
   register mem *top;
   register mem *chk = (mem *) ptr;

   if (chk == 0)
      return;			/* free(NULL) - be nice */
   chk--;

 try_this:;
   /* First check see if we're running in minimal mode */
   top = (mem *) sbrk(0);
   if (chk + m_size(chk) == top)
   {
      noise("FREE sbrk", chk);
      sbrk(-m_size(chk) * sizeof(mem));
      /*
       * Adding this code allow free to release same order frees as well
       * as reverse order; probably not very useful.
       */
#if 1
      if (__freed_list)
      {
	 chk = __freed_list;
	 __freed_list = m_next(__freed_list);
	 goto try_this;
      }
#endif
   }
   else
   {				/* Nope, not sure where this goes, leave
				 * it for malloc to deal with */
      m_next(chk) = __freed_list;
      __freed_list = chk;
      noise("ADD LIST", chk);
   }
}

void *
__more_memory(size)
unsigned int size;
{
   register mem *ptr;
   register unsigned int sz;

   /* First time round this _might_ be odd, But we won't do that! */
   sz = (unsigned int) sbrk(0);
   if (sz & (sizeof(mem) - 1))
      sbrk(4 - (sz & (sizeof(mem) - 1)));

   if (size == 0)
      return 0;

   size += sizeof(mem) * 2 - 1;	/* Round up and leave space for size field */
   size /= sizeof(mem);

   ptr = (mem *) sbrk(size * sizeof(mem));
   if ((unsigned int) ptr == -1)
      return 0;

   m_size(ptr) = size;
   noise("CREATE", ptr);
   return ptr + 1;
}
#endif				/* L_free */

#ifdef L_malloc

/*
 * The chunk_list pointer is either NULL or points to a chunk in a
 * circular list of all the free blocks in memory
 */

static mem *chunk_list = 0;
static void insert_chunk();
static mem *search_chunk();

void *
malloc(size)
unsigned int size;
{
   register mem *ptr = 0;
   register unsigned int sz;

   if (size == 0)
      return 0;			/* ANSI STD */

   sz = size + sizeof(mem) * 2 - 1;
   sz /= sizeof(mem);

#ifdef MINALLOC
   if (sz < MINALLOC)
      sz = MINALLOC;
#endif

#ifdef VERBOSE
   {
      static mem arr[2];
      m_size(arr) = sz;
      noise("WANTED", arr);
   }
#endif

   __alloca_alloc = malloc;	/* We'll be messing with the heap now TVM */

#ifdef LAZY_FREE
   ptr = search_chunk(sz);
   if (ptr == 0)
   {
#endif

      /* First deal with the freed list */
      if (__freed_list)
      {
	 while (__freed_list)
	 {
	    ptr = __freed_list;
	    __freed_list = m_next(__freed_list);

	    if (m_size(ptr) == sz)	/* Oh! Well that's lucky ain't it
					 * :-) */
	    {
	       noise("LUCKY MALLOC", ptr);
	       return ptr + 1;
	    }

	    insert_chunk(ptr);
	 }
	 ptr = m_next(chunk_list);
	 if (ptr + m_size(ptr) == (void *) sbrk(0))
	 {
	    /* Time to free for real */
	    m_next(chunk_list) = m_next(ptr);
	    if (ptr == m_next(ptr))
	       chunk_list = 0;
	    free(ptr + 1);
	 }
#ifdef LAZY_FREE
	 ptr = search_chunk(sz);
#endif
      }
#ifndef LAZY_FREE
      ptr = search_chunk(sz);
#endif
      if (ptr == 0)
      {
#ifdef MCHUNK
	 ptr = __more_memory(sizeof(mem) * (MCHUNK * ((sz + MCHUNK - 1) / MCHUNK) - 1));
	 if (ptr)
	 {
	    insert_chunk(ptr - 1);
	    ptr = search_chunk(sz);
	 }
	 if (ptr == 0)
#endif
	 {
	    ptr = __more_memory(size);
	    noise("MALLOC NOW", ptr - 1);
	    return ptr;
	 }
      }
#ifdef LAZY_FREE
   }
#endif

#ifdef VERBOSE
   ptr[1].size = 0x55555555;
#endif
   noise("MALLOC RET", ptr);
   return ptr + 1;
}

/*
 * This function takes a pointer to a block of memory and inserts it into
 * the chain of memory chunks
 */

static void
insert_chunk(mem_chunk)
mem  *mem_chunk;
{
   register mem *p1, *p2;
   if (chunk_list == 0)		/* Simple case first */
   {
      m_next(mem_chunk) = chunk_list = mem_chunk;
      noise("FIRST CHUNK", mem_chunk);
      return;
   }
   p1 = mem_chunk;
   p2 = chunk_list;

   do
   {
      if (p1 > p2)
      {
	 if (m_next(p2) <= p2)
	 {			/* We're at the top of the chain, p1 is
				 * higher */

	    if (p2 + m_size(p2) == p1)
	    {			/* Good, stick 'em together */
	       noise("INSERT CHUNK", mem_chunk);
	       m_size(p2) += m_size(p1);
	       noise("JOIN 1", p2);
	    }
	    else
	    {
	       m_next(p1) = m_next(p2);
	       m_next(p2) = p1;
	       noise("INSERT CHUNK", mem_chunk);
	       noise("FROM", p2);
	    }
	    return;
	 }
	 if (m_next(p2) > p1)
	 {
	    /* In chain, p1 between p2 and next */

	    m_next(p1) = m_next(p2);
	    m_next(p2) = p1;
	    noise("INSERT CHUNK", mem_chunk);
	    noise("FROM", p2);

	    /* Try to join above */
	    if (p1 + m_size(p1) == m_next(p1))
	    {
	       m_size(p1) += m_size(m_next(p1));
	       m_next(p1) = m_next(m_next(p1));
	       noise("JOIN 2", p1);
	    }
	    /* Try to join below */
	    if (p2 + m_size(p2) == p1)
	    {
	       m_size(p2) += m_size(p1);
	       m_next(p2) = m_next(p1);
	       noise("JOIN 3", p2);
	    }
	    chunk_list = p2;	/* Make sure it's valid */
	    return;
	 }
      }
      else if (p1 < p2)
      {
	 if (m_next(p2) <= p2 && p1 < m_next(p2))
	 {
	    /* At top of chain, next is bottom of chain, p1 is below next */

	    m_next(p1) = m_next(p2);
	    m_next(p2) = p1;
	    noise("INSERT CHUNK", mem_chunk);
	    noise("FROM", p2);
	    chunk_list = p2;

	    if (p1 + m_size(p1) == m_next(p1))
	    {
	       if (p2 == m_next(p1))
		  chunk_list = p1;
	       m_size(p1) += m_size(m_next(p1));
	       m_next(p1) = m_next(m_next(p1));
	       noise("JOIN 4", p1);
	    }
	    return;
	 }
      }
      chunk_list = p2;		/* Save for search */
      p2 = m_next(p2);
   }
   while (p2 != chunk_list);

   /* If we get here we have a problem, ignore it, maybe it'll go away */
   noise("DROPPED CHUNK", mem_chunk);
}

/*
 * This function will search for a chunk in memory of at least 'mem_size'
 * when found, if the chunk is too big it'll be split, and pointer to the
 * chunk returned. If none is found NULL is returned.
 */

static mem *
search_chunk(mem_size)
unsigned int mem_size;
{
   register mem *p1, *p2;
   if (chunk_list == 0)		/* Simple case first */
      return 0;

   /* Search for a block >= the size we want */
   p1 = m_next(chunk_list);
   p2 = chunk_list;
   do
   {
      noise("CHECKED", p1);
      if (m_size(p1) >= mem_size)
	 break;

      p2 = p1;
      p1 = m_next(p1);
   }
   while (p2 != chunk_list);

   /* None found, exit */
   if (m_size(p1) < mem_size)
      return 0;

   /* If it's exactly right remove it */
   if (m_size(p1) < mem_size + 2)
   {
      noise("FOUND RIGHT", p1);
      chunk_list = m_next(p2) = m_next(p1);
      if (chunk_list == p1)
	 chunk_list = 0;
      return p1;
   }

   noise("SPLIT", p1);
   /* Otherwise split it */
   m_next(p2) = p1 + mem_size;
   chunk_list = p2;

   p2 = m_next(p2);
   m_size(p2) = m_size(p1) - mem_size;
   m_next(p2) = m_next(p1);
   m_size(p1) = mem_size;
   if (chunk_list == p1)
      chunk_list = p2;
#ifdef VERBOSE
   p1[1].size = 0xAAAAAAAA;
#endif
   noise("INSERT CHUNK", p2);
   noise("FOUND CHUNK", p1);
   noise("LIST IS", chunk_list);
   return p1;
}

#ifdef VERBOSE
static
phex(val)
{
   static char hex[] = "0123456789ABCDEF";
   int   i;
   for (i = 28; i >= 0; i -= 4)
      write(1, hex + ((val >> i) & 0xF), 1);
}

noise(y, x)
char *y;
mem  *x;
{
   write(1, "Malloc ", 7);
   phex(x);
   write(1, " sz ", 4);
   phex(m_size(x) * sizeof(mem));
   write(1, " nxt ", 5);
   phex(m_next(x));
   write(1, " is ", 4);
   write(1, y, strlen(y));
   write(1, "\n", 1);
}

#endif

#endif				/* L_malloc */

#ifdef L_calloc
void *
calloc(elm, sz)
unsigned int elm, sz;
{
   register unsigned int v;
   register void *ptr;
   ptr = malloc(v = elm * sz);
   if (ptr)
      memset(ptr, 0, v);
   return ptr;
}
#endif				/* L_calloc */

#ifdef L_realloc
void *
realloc(ptr, size)
void *ptr;
unsigned int size;
{
   void *nptr;
   unsigned int osize;
   if (ptr == 0)
      return malloc(size);

   osize = (m_size(((mem *) ptr) - 1) - 1) * sizeof(mem);
   if (size <= osize)
   {
      return ptr;
   }

   nptr = malloc(size);

   if (nptr == 0)
      return 0;

   memcpy(nptr, ptr, osize);
   free(ptr);

   return nptr;
}
#endif				/* L_calloc */
