/*-=-=-=-=-=-=-=-=-=-=-=- RCCL/RCI Notice of Copyright -=-=-=-=-=-=-=-=-=-=-*
 *									    *
 *	Copyright (C) 1991 by John E. Lloyd.				    *
 *									    *
 *	Information on copyright permissions, as well as a complete	    *
 *	disclaimer of warranty, is given in the file COPYRIGHT.		    *
 *									    *
 *-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*---------------------------------------------------------------------------
 *		
 * MODULE NAME:	firstAlloc
 *
 * DESCRIPTION:	Allocation package using circular first-fit stragtegy.
 *
 *		This has replaced the buddy allocator in 'buddyAlloc.c'
 *		for most internal RCI memory allocation applications.
 *
 * CAVEATS:	Optimized for LIFO operation.
 *
 * AUTHOR/DATE:	This really just the old Berkeley 4.1 malloc, (12/21/80)
 *		and forced into RCI shoes by John Lloyd, Oct 3, 1989
 *
 * MODIFICATION HISTORY:
 *
 *--------------------------------------------------------------------------*/

#include "../h/cdefs.h"

#include "../rci/firstAlloc.h"

/* ------------------------------------------------------------------------

	Comments brought over from the original Berkeley code:

	C storage allocator, using circular first-fit strategy.
	Works with noncontiguous, but monotonically linked, arena.
	Each block is preceded by a ptr to the (pointer of) 
	the next following block. Blocks are exact number of words long,
	aligned to the data type requirements of ALIGN. Pointers to blocks 
	must have BUSY bit 0. Bit in ptr is 1 for busy, 0 for idle
	Gaps in arena are merely noted as busy blocks. Last block of arena 
	(pointed to by alloct) is empty and has a pointer to first.
	Idle blocks are coalesced during space search

	A different implementation may need to redefine NALIGN, 
	BLOCK, BUSY, INT, where INT is integer type to which a pointer 
	can be cast.

  -------------------------------------------------------------------------*/

#define INT		long
#define WORD 		sizeof(union store)
#define BLOCK 		1024	/* a multiple of WORD*/
#define BUSY 		1
#define MIN_SIZE	23
#define testbusy(p)	((INT)(p)&BUSY)
#define setbusy(p)	(union store *)((INT)(p)|BUSY)
#define clearbusy(p)	(union store *)((INT)(p)&~BUSY)

firstAllocInit (fad, morecore, blkSize, pool, size)
FIRST_ALLOC_DESC *fad;
void *(*morecore)();
int blkSize;
char *pool;
int size;
{
	/* Initialization routine. Call to setup descriptor,
	   and also provide a function for getting more memory.
	 */
	fad->allocs[0].ptr = setbusy(&fad->allocs[1]);
	fad->allocs[1].ptr = setbusy(&fad->allocs[0]);
	fad->alloct = &fad->allocs[1];
	fad->allocp = &fad->allocs[0];
	fad->morecore = morecore;
	fad->morecoreBlkSize = blkSize;

	/* Align pool and size */

	if (pool)
	 { union store *q;
	   if ((INT)pool%sizeof(ALIGN))
	    { size -= sizeof(ALIGN) - ((INT)pool%sizeof(ALIGN));
	      pool += sizeof(ALIGN) - ((INT)pool%sizeof(ALIGN));
	    }
	   if (size%sizeof(ALIGN))
	    { size -= sizeof(ALIGN) - (size%sizeof(ALIGN));
	    }
	   q = (union store *)pool;
	   fad->alloct->ptr = q;
	   fad->alloct->ptr = setbusy(fad->alloct->ptr);
	   fad->alloct = q->ptr = q+size/WORD-1;
	   fad->alloct->ptr = setbusy(fad->allocs);
	 }
	return (1);
}

void *firstAlloc (fad, size)
register FIRST_ALLOC_DESC *fad;
int size;
{
	/* Tries to allocate 'size' bytes and returns a pointer to it.
	   Will return NULL in the event of failure.
	 */

	register union store *p, *q;
	register nw;
	int cyclesThroughList;
	int numNewWords;

	if (size < 0)
	 { return (NULL);
	 }
	nw = (size+WORD+WORD-1)/WORD;
	for (p=fad->allocp; ; )
	 {
	   for (cyclesThroughList=0; ; )
	    {
	      if(!testbusy(p->ptr))
	       { while(!testbusy((q=p->ptr)->ptr))
		  { p->ptr = q->ptr;
		  }
		 if(q>=p+nw && p+nw>=p)
		  { goto found;
		  }
	       }
	      q = p;
	      p = clearbusy(p->ptr);
	      /*if(p>q)
		 ;
	      else if (q!=fad->alloct || p!=fad->allocs)
	       { return(NULL);
	       }
	      else /*/ if (p == fad->allocs) /**/ if (++cyclesThroughList>1)
	       { break;
	       }
	    }
	
	   /* Check the internal pool first */

	   if (fad->morecore)
	    { int blkSize;
	      if (fad->morecoreBlkSize > 0)
	       { blkSize = fad->morecoreBlkSize;
	       }
	      else
	       { blkSize = BLOCK/WORD;
	       }
	      numNewWords = (nw+blkSize)/blkSize*blkSize;
	      q = (union store *)(*fad->morecore)(0);
	      if (q+numNewWords < q)
	       { return(NULL);
	       }
	      q = (union store *)(*fad->morecore)((unsigned)numNewWords*WORD);
	      if((INT)q == NULL || (INT)q == -1)
	       { return(NULL);
	       }
	    }
	   else
	    { return (NULL);
	    }
	   fad->alloct->ptr = q;
	   if(q!=fad->alloct+1)
	    { fad->alloct->ptr = setbusy(fad->alloct->ptr);
	    }
	   fad->alloct = q->ptr = q+numNewWords-1;
	   fad->alloct->ptr = setbusy(fad->allocs);
	 }
found:
	if (q > p + nw + MIN_SIZE)
	 { fad->allocp = p + nw;
	   fad->allocx = fad->allocp->ptr;
	   fad->allocp->ptr = p->ptr;
	 }
	else
	 { fad->allocp = q;
	 }
	p->ptr = setbusy(fad->allocp);
	return((void *)(p+1));
}

firstFree (fad, ap)
FIRST_ALLOC_DESC *fad;
void *ap;
{
	/* Frees the block pointed to by 'ap' */

	register union store *p = (union store *)ap;

	fad->allocp = --p;
	p->ptr = clearbusy(p->ptr);
}

#if TEST

#ifndef VXWORKS_CFG
#include <stdio.h>
#else
#include <vxWorks.h>
#include <stdioLib.h>
#endif

#include "../rci/link.h"
#include "../h/cputime.h"

#define BIGPOOL	   0x10000
#define LITTLEPOOL (0x1000+sizeof(ALIGN))

static FIRST_ALLOC_DESC _fad;
static char littlepool[LITTLEPOOL];
static char bigpool[BIGPOOL];

void *_morecore(nbytes)
int nbytes;
{
	static char *mark = bigpool;
	char *mem;

	mem = mark;
	if (mem + nbytes >= bigpool + BIGPOOL)
	 { return (NULL);
	 }
	else
	 { mark += nbytes;
	   return ((void*)mem);
	 }
}	

struct block {
	struct block *next;       
	struct block *prev;       
	void *mem;
  };

#define TESTCOUNT	10000

struct block blockPool[TESTCOUNT];

#define GET_BLOCK(bp, k)		\
	bp = &blockPool[k];		\
	LINK(bp, allocList);

#define FREE_BLOCK(bp)			\
	UNLINK(bp)

main()
{
	static int sizes[] = 
	 { 0, 11, 30, 50, 101, 100, 200, 400, 1000, 5000
	 };
	struct block _allocList;
	struct block *allocList = &_allocList;
	struct block *freeBlocks = NULL;
	struct block *bp;

	FIRST_ALLOC_DESC *fad = &_fad;
	char *mem1;
	char *mem2;
	int size, j, k;
	int numFrees;
	int numAllocs;
	int maxAllocStreak;
	int allocStreak;

	double time1, time2, totaltime, overhead;

	/* Check it returns NULL when it has no memory */

	firstAllocInit (fad, NULLF, 0, 0, 0);
	if ((mem1 = firstAlloc (fad, 2)) != NULL)
	 { printf ("ERROR(1): allocated with no buffer given!\n");
	 }

	/* Now do some simple boundary case checks */

	firstAllocInit (fad, NULLF, LITTLEPOOL, littlepool, LITTLEPOOL);
	for (k=0; k<3; k++)
	 { if ((mem1 = firstAlloc (fad, LITTLEPOOL/4)) == NULL)
	    { printf ("ERROR(2): can't allocate\n");
	    }
	 }
	if (firstAlloc (fad, LITTLEPOOL/4) != NULL)
	 { printf ("ERROR(3): allocated when it shouldn't\n");
	 }
	firstFree (fad, mem1);
	mem2 = firstAlloc (fad, LITTLEPOOL/4);
	if (mem1 != mem2)
	 { printf ("ERROR(4): should have returned old memory\n");
	 }
	firstAllocInit (fad, _morecore, LITTLEPOOL, littlepool, LITTLEPOOL);
	for (k=0; k<3; k++)
	 { if ((mem1 = firstAlloc (fad, LITTLEPOOL/4)) == NULL)
	    { printf ("ERROR(5): can't allocate\n");
	    }
	 }

	/* now get serious. Do random allocation, and when we are
	   done, free everything and make sure we are back to
	   where we started from.
	 */

	allocList->prev = allocList->next = allocList;
	firstAllocInit (fad, _morecore, LITTLEPOOL, littlepool, LITTLEPOOL);
	numFrees = 0;
	numAllocs = 0;
	maxAllocStreak = 0;
	allocStreak = 0;

	for (k=0; k<TESTCOUNT; k++)
	 { if (irandom(0,1))
	    { size = sizes[irandom(0, ARRAY_SIZE(sizes)-1)];
	      if ((mem1 = firstAlloc (fad, size)) != NULL)
	       { GET_BLOCK (bp, k);
		 for (j=0; j<size; j++)
		  { mem1[j] = k;
		  }
		 bp->mem = mem1;
		 numAllocs++;
		 allocStreak++;
	       }
	    }
	   else
	    { if ((bp = allocList->next) != allocList)
	       { firstFree (fad, bp->mem);
		 FREE_BLOCK (bp);
		 numFrees++;
		 if (allocStreak > maxAllocStreak)
		  { maxAllocStreak = allocStreak;
		  }
		 allocStreak = 0;
	       }
	    }
	 }
	for (bp = allocList->next; bp != allocList; bp = bp->next)
	 { firstFree (fad, bp->mem);
	   numFrees++;
	 }
	if (allocStreak > maxAllocStreak)
	 { maxAllocStreak = allocStreak;
	 }

	if (numFrees != numAllocs)
	 { printf ("ERROR(6): numFrees = %d, numAllocs = %d\n",
			      numFrees, numAllocs);
	 }
	printf ("%d allocations, max %d consecutive\n", 
		 numAllocs, maxAllocStreak);

	/* Now time the blasted things */

	allocList->prev = allocList->next = allocList;
	firstAllocInit (fad, _morecore, LITTLEPOOL, littlepool, LITTLEPOOL);
	frandom_seed(1);
	numAllocs = 0;
	time1 = cputime(User);
	for (k=0; k<TESTCOUNT; k++)
	 { if (irandom(0,1))
	    { size = sizes[irandom(0, ARRAY_SIZE(sizes)-1)];
	      if ((mem1 = firstAlloc (fad, size)) != NULL)
	       { GET_BLOCK (bp, k);
		 bp->mem = mem1;
		 numAllocs++;
	       }
	    }
	   else
	    { if ((bp = allocList->next) != allocList)
	       { firstFree (fad, bp->mem);
		 FREE_BLOCK (bp);
	       }
	    }
	 }
	for (bp = allocList->next; bp != allocList; bp = bp->next)
	 { firstFree (fad, bp->mem);
	 }
	time2 = cputime(User);

	totaltime = time2-time1;

	allocList->prev = allocList->next = allocList;
	firstAllocInit (fad, _morecore, LITTLEPOOL, littlepool, LITTLEPOOL);
	frandom_seed(1);
	numAllocs = 0;
	time1 = cputime(User);
	for (k=0; k<TESTCOUNT; k++)
	 { if (irandom(0,1))
	    { size = sizes[irandom(0, ARRAY_SIZE(sizes)-1)];
	      if ((mem1 = blockPool[k].mem) != NULL)
	       { GET_BLOCK (bp, k);
		 bp->mem = mem1;
		 numAllocs++;
	       }
	    }
	   else
	    { if ((bp = allocList->next) != allocList)
	       { FREE_BLOCK (bp);
	       }
	    }
	 }
	for (bp = allocList->next; bp != allocList; bp = bp->next)
	   ;
	time2 = cputime(User);

	overhead = time2-time1;

	printf ("typical alloc/free time is %g usec. for %d allocations\n",
		 (totaltime - overhead)*1.0e6/numAllocs, numAllocs);
}

#endif
