[net.sources] New malloc subroutine

thomas (12/16/82)

This malloc works much better in a VAX (paging) environment.  I doubt it would
work very well on a PDP-11 (in fact, the copymem routine uses an asm, so it
will work only on a VAX without modification).  Defining MSTATS causes
some statistics to be kept, and the routine mstats(string) can be called
to print them out.  Defining rcheck causes more careful checking to be done,
may help find bugs in code (I haven't used it).  Note that the layout of
the arena is QUITE different from the old malloc, so anything that depends
on this will need to be rewritten.  I make no guarantees, and it's not my
code to begin with (I asked the author, whose name I have forgotten now,
for permission to redistribute).

-Spencer
================================================================
/* @(#)nmalloc.c 1 (Caltech) 2/21/82
 *  This is a very fast storage allocator.  It allocates blocks of a small 
 *  number of different sizes, and keeps free lists of each size.  Blocks that
 *  don't exactly fit are passed up to the next larger size.  In this 
 *  implementation, the available sizes are (2^n)-4 (or -12) bytes long.
 *  This is designed for use in a program that uses vast quantities of memory,
 *  but bombs when it runs out.  To make it a little better, it warns the
 *  user when he starts to get near the end.
 */

/* nextf[i] is the pointer to the next free block of size 2^(i+3).  The
 * smallest allocatable block is 8 bytes.  The overhead information will
 * go in the first int of the block, and the returned pointer will point
 * to the second.
 *
#ifdef MSTATS
 * nmalloc[i] is the difference between the number of mallocs and frees
 * for a given block size.
#endif MSTATS
 */

static unsigned int *nextf[30];

#define MSTATS
#ifdef MSTATS
static unsigned int nmalloc[30];
#include "stdio.h"
#endif MSTATS

#include <sys/vlimit.h>     /* warn the user when near the end */
#ifdef debug
#define ASSERT(p)   if (!(p))botch("p"); else
#else
#define ASSERT(p)
#endif

extern char etext;           /* end of the program */
#define NULL 0

#ifdef rcheck
#define MAGIC 0x55555555
#endif

/*      The overhead on a block will be four bytes long.  When free, it will
 *  contain a pointer to the next free block, and the bottom two bits must
 *  be zero.  When in use, the first byte will be set to 0xFF, and the second
 *  byte will be the size index.  The other two bytes are only used for
 *  alignment.  If you are range checking, and the size of the block will fit
 *  into two bytes, then the top two bytes hold the size of the requested block
 *  plus the range checking words, and the header word MINUS ONE.
 */

static *morecore(nu)    /* ask system for more memory */
register int nu;        /* size index to get more of  */
{   char *sbrk();
    register unsigned int *cp;
    register int rnu;       /* 2^rnu bytes will be requested */
    register int nblks;     /* that becomes nblks blocks of the desired size */
    register int siz;       /* size in ints, not bytes */
    static int warnlevel=0;
    register int used;

    if (nextf[nu]!=NULL)
	return;

    siz=vlimit(LIM_DATA,-1);        /* find out how much we can get */
    cp=((unsigned int *)sbrk(0));
    used=(int)cp;
    used-= (int)&etext;
    switch (warnlevel){
    case 0:
	if (used>(siz/4)*3){
	    write(2,"warning: past 75% of memory limit\7\n",35);
	    warnlevel=1;}
	break;
    case 1:
	if (used>(siz/20)*17){
	    write(2,"warning: past 85% of memory limit\7\n",35);
	    warnlevel=2;}
	break;
    case 2:
	if (used>(siz/20)*19){
	    write(2,"warning: past 95% of memory limit\7\n",35);
	    warnlevel=3;}
	break;
    }       /* end of warning switch */
    if ((((int)cp)&0x3ff) != 0)       /* land on 1K boundaries */
	sbrk(1024-(((int)cp)&0x3ff));

    rnu=(nu<=8)?11:nu+3;    /* take 2k unless the block is bigger than that */
    nblks=1<<(rnu-(nu+3));  /* how many blocks to get */
    if (rnu<nu) rnu=nu;
    if ((int)(cp=(unsigned int*)sbrk(1<<rnu)) == -1)      /* no more room! */
	return;
    if ((((int)cp) & 7)!=0){
	cp=(unsigned int*)((((int)cp)+8)&~7);
	nblks--;}
    nextf[nu]=cp;
    siz= 1<<(nu+1);
    while (--nblks>0){
	((unsigned int**)cp)[0]= &cp[siz];
	cp= (unsigned int*)&cp[siz];}
}

char *malloc(nbytes)    /* get a block */
register unsigned nbytes;
{
    register unsigned char *p;
    register int nunits=0;
    register unsigned shiftr;

#ifdef rcheck
    nbytes+=12;     /* make sure the range checkers will fit */
#else
    nbytes+=4;      /* add on for the overhead */
#endif
    nbytes=(nbytes+3)&~3;       /* round up, but still measure in bytes */
    shiftr=(nbytes-1)>>2;
    while ((shiftr>>=1)!=0)     /* apart from this loop, this is O(1) */
	nunits++;
    if (nextf[nunits]==NULL)    /* needed block, nunits is the size index */
	morecore(nunits);
    if ((p=(unsigned char*)(nextf[nunits]))==NULL)
	return(NULL);
    nextf[nunits]= (unsigned int*)*nextf[nunits];
    p[0]=0xff;
    p[1]=nunits;
#ifdef MSTATS
    nmalloc[nunits]++;
#endif MSTATS
#ifdef rcheck
    if (nbytes<=0x10000)
	((unsigned short*)p)[1]=(unsigned short)nbytes-1;
    *((int*)(p+4))=MAGIC;
    *((int*)(p+nbytes-4))=MAGIC;
    return((char*)(p+8));
#else
    return((char*)(p+4));
#endif
}

free(ap)
register unsigned char *ap;
{   register int si;

    if (ap==NULL)
	return;
#ifdef rcheck
    ap-=4;
    ASSERT(*(int*)ap==MAGIC);
#endif
    ap-=4;                              /* point back to overhead word */
#ifdef debug
    ASSERT(ap[0]==0xff);                /* make sure it was in use */
#else
    if (ap[0]!=0xff)
	return;
#endif
#ifdef rcheck
    if (ap[1]<=13){
	si=((unsigned short *)ap)[1]-11;  /* get the size of the data */
	ASSERT(*((int*)(ap+si+8))==MAGIC);  /* check for overflow */
    }
#endif
    ASSERT(ap[1]<=29);
    si=ap[1];
    *((unsigned int**)ap)=nextf[si];
    nextf[si]=(unsigned int*)ap;
#ifdef MSTATS
    nmalloc[si]--;
#endif MSTATS
}

char *realloc(p, nbytes)
register char *p; register unsigned nbytes;
{   register char *res;
    register unsigned int onb;

    if (p==NULL)
	return(malloc(nbytes));
#ifdef rcheck
    if (p[-7]<13)
	onb= ((unsigned short*)p)[-3]-11;  /* old number of data bytes only */
    else
	onb=(1<<(p[-7]+3))-12;
#else
    onb=(1<<(p[-3]+3))-4;
#endif
    if ((res=malloc(nbytes))==NULL)
	return(NULL);
    copymem((nbytes<onb)?nbytes:onb,p,res);
    free(p);
    return(res);
}

copymem(n, from, to)
int n;
register char * from, * to;
{
    register int i;

    while (n > 0)
    {
	i = n > 65535L ? 65535L : n;
	asm("	movc3	r9,(r11),(r10)");	/* glug! */
	n -= i;
	from += i;
	to += i;
    }
}

#ifdef MSTATS
/* ****************************************************************
 * mstats - print out statistics about malloc
 * 
 * Prints two lines of numbers, one showing the length of the free list
 * for each size category, the second showing the number of mallocs -
 * frees for each size category.
 */

mstats(s)
char *s;
{
    register int i, j;
    register unsigned int * p;
    int totfree = 0,
        totused = 0;

    fprintf(stderr, "Memory allocation statistics %s\nfree:\t", s);
    for (i=0; i<30; i++)
    {
	for (j=0, p=nextf[i]; p; p = (unsigned int *)*p, j++)
	    ;
	fprintf(stderr, " %d", j);
	totfree += j * (1 << (i+3));
    }
    fprintf(stderr, "\nused:\t");
    for (i=0; i<30; i++)
    {
	fprintf(stderr, " %d", nmalloc[i]);
	totused += nmalloc[i] * (1 << (i+3));
    }
    fprintf(stderr, "\n\tTotal in use: %d, total free: %d\n");
}
#else
mstats()
{					/* dummy to keep people happy */
}
#endif