/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident "$Header: shmalloc.c 1.3 89/05/13 $"

/* modified for shared memory, simpler logic, and more sizes GLG Sequent 8/86 */ 

/*
 * malloc.c (Caltech) 2/21/82
 * Chris Kingsley, kingsley@cit-20.
 *
 * 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 indicated in the table "sizes".
 * This is designed for use in a program that uses vast quantities of memory,
 * but bombs when it runs out. 
 */
#define SHARED

#include <sys/types.h>

#include "parallel.h"
#ifdef SHARED
#define sbrk shsbrk
#define malloc shmalloc
#define mstats shmstats
#define free shfree
#define realloc shrealloc
#define CHUNK 17
#else
#define CHUNK 1
#endif


#ifdef	LIBSANITY
# define RCHECK
# define SANITY
#endif

/*
 * The overhead on a block is at least 4 bytes.  When free, this space
 * contains a pointer to the next free block, and the bottom two bits must
 * be zero.  When in use, the first byte is set to MAGIC, and the second
 * byte is the size index.  The remaining bytes are for alignment.
 * If range checking is enabled and the size of the block fits
 * in 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.
 */
union	overhead {
	union	overhead *ov_next;	/* when free */
	struct {
#ifndef RCHECK
		unsigned char	ovu_magic;	/* magic number */
		unsigned char	ovu_index;	/* bucket # */
#else
		unsigned int	ovu_size;	/* actual block size */
		unsigned char	ovu_magic;	/* magic number */
		unsigned char	ovu_index;	/* bucket # */
		unsigned char	ovu_rmagic;	/* range magic number */
#endif
	} ovu;
#define	ov_magic	ovu.ovu_magic
#define	ov_index	ovu.ovu_index
#define	ov_rmagic	ovu.ovu_rmagic
#define	ov_size		ovu.ovu_size
};

#define	MAGIC		0xef		/* magic # on accounting info */
#define RMAGIC		0x5555		/* magic # on range info */

#ifdef RCHECK
#define	RSLOP		sizeof (u_short)
#else
#define	RSLOP		0
#endif

/*
 * nextf[i] is the pointer to the next free block sizes[i].  The
 * smallest allocatable block is sizes[0] bytes.  The overhead information
 * precedes the data area returned to the user.
 */
#define	NBUCKETS 51
#define MEG	(1024*1024)
#ifdef SHARED
shared static	union overhead *nextf[NBUCKETS];
shared static unsigned int sizes[NBUCKETS] = {
	20, 36, 52, 68, 100, 132,
	196, 260, 388, 516, 772, 
	1024,2048, 3072,4096, 5120,
	7168, 9216,13312, 17408, 25600,
	33792,50176, 66560,99328, 132096,197632,
	263168,394240, 525312,787456, 1049600,1573888,
	2098176,3146752, 4195328,6292480, 8389632,12583936,
	0xfff000, 24*MEG, 32*MEG, 48*MEG, 64*MEG, 96*MEG, 128*MEG,
	256*MEG, 384*MEG, 512*MEG, 768*MEG, 0xFFFFFFFF
};
extern	char *sbrk();

shared static	int pagesz;			/* page size */

shared static slock_t lock;
#else
static	union overhead *nextf[NBUCKETS];
static unsigned int sizes[NBUCKETS] = {
	20, 36, 52, 68, 100, 132,
	196, 260, 388, 516, 772, 
	1024,2048, 3072,4096, 5120,
	7168, 9216,13312, 17408, 25600,
	33792,50176, 66560,99328, 132096,197632,
	263168,394240, 525312,787456, 1049600,1573888,
	2098176,3146752, 4195328,6292480, 8389632,12583936,
	0xfff000, 24*MEG, 32*MEG, 48*MEG, 64*MEG, 96*MEG, 128*MEG,
	256*MEG, 384*MEG, 512*MEG, 768*MEG, 0xFFFFFFFF
};
extern	char *sbrk();

static	int pagesz;			/* page size */
#endif

#ifdef MSTATS
/*
 * nmalloc[i] is the difference between the number of mallocs and frees
 * for a given block size.
 */
#ifdef SHARED
shared static	unsigned int nmalloc[NBUCKETS];
#else
static	unsigned int nmalloc[NBUCKETS];
#endif
#include <stdio.h>
#endif

#ifdef SANITY
#define	ASSERT(p, m)   { if (!(p)) botch(__FILE__, __LINE__, "p", m); }
static
botch(f, l, s, m)
	char *f, *s, *m;
{

	printf("%s(%d) in subroutine %s(), sanity condition (%s) failed\n", f, l, m, s);
	abort();
}
#else
#define	ASSERT(p, m)
#endif

char *
malloc(nbytes)
	unsigned nbytes;
{
  	register union overhead *op;
  	register int bucket;
	register unsigned amt, n;

	/*
	 * First time malloc is called, setup page size and
	 * align break pointer so all data will be page aligned.
	 */
	if (pagesz == 0) {
#ifdef SHARED
	    S_LOCK(&lock);
	    if (pagesz == 0) {
#endif
		pagesz = n = getpagesize();
		op = (union overhead *)sbrk(0);
  		n = n - sizeof (*op) - ((int)op & (n - 1));
		if (n < 0)
			n += pagesz;
  		if (n) {
  			if (sbrk(n) == (char *)-1) {
#ifdef SHARED
	    			S_UNLOCK(&lock);
#endif /* SHARED */
				return (NULL);
			}
		}
#ifdef SHARED
	    }
	    S_UNLOCK(&lock);
#endif
	}
	/*
	 * Convert amount of memory requested into closest block size
	 * stored in hash buckets which satisfies request.
	 * Account for space used per block for accounting.
	 */
	bucket = 0;
	n = nbytes + sizeof(*op) +  RSLOP;
	while (n > sizes[bucket]) bucket++;
	/*
	 * If nothing in hash bucket right now,
	 * request more memory from the system.
	 */
#ifdef SHARED
	S_LOCK(&lock);
#endif
  	if ((op = nextf[bucket]) == NULL) {
  		morecore(bucket);
  		if ((op = nextf[bucket]) == NULL) {
#ifdef SHARED
			S_UNLOCK(&lock);
#endif
  			return (NULL);
		}
	}
	/* remove from linked list */
  	nextf[bucket] = op->ov_next;
	op->ov_magic = MAGIC;
	op->ov_index = bucket;
#ifdef MSTATS
  	nmalloc[bucket]++;
#endif
#ifdef RCHECK
	/*
	 * Record allocated size of block and
	 * bound space with magic numbers.
	 */
	op->ov_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1);
	op->ov_rmagic = RMAGIC;
  	*(u_short *)((caddr_t)(op + 1) + op->ov_size) = RMAGIC;
#endif
#ifdef SHARED
	S_UNLOCK(&lock);
#endif
  	return ((char *)(op + 1));
}

/*
 * Allocate more memory to the indicated bucket.
 * In the shared environment, this is called with nextf[]
 * locked.
 */
static
morecore(bucket)
	int bucket;
{
  	register union overhead *op;
	register int sz;		/* size of desired block */
  	register int amt;		/* amount to allocate */
  	register int nblks;		/* how many blocks we get */

	sz = sizes[bucket];
	amt = sz < (pagesz * CHUNK) ? (pagesz * CHUNK) : sz;
	nblks = amt / sz;
	while (amt - pagesz >= nblks * sz) amt -= pagesz;
#ifdef DEBUG
	printf("morecore of %d bytes for %d buffers\n", amt, sz);
#endif
	/*
	 * Force all data of sz >= pagesz to be page aligned.
	 */
	op = (union overhead *)sbrk(0);
	if ((int)(op + 1) & (pagesz-1)) {
		/* someone played with brk() or sbrk() so fix */
		if ((int)sbrk(pagesz - sizeof(*op) - ((int)op & (pagesz-1))) == -1)
			return;
	}

	op = (union overhead *)sbrk(amt);
	/* no more room! */
  	if ((int)op == -1)
  		return;
	/*
	 * Add new memory allocated to that on
	 * free list for this hash bucket.
	 */
  	nextf[bucket] = op;
  	while (--nblks > 0) {
		op->ov_next = (union overhead *)((caddr_t)op + sz);
		op = (union overhead *)((caddr_t)op + sz);
  	}
	op->ov_next = 0;	/* don't depend on memory being zero */
}

free(cp)
	char *cp;
{   
  	register int size;
	register union overhead *op;

  	if (cp == NULL)
  		return;
	op = (union overhead *)((caddr_t)cp - sizeof (union overhead));
#ifdef SANITY
  	ASSERT(op->ov_magic == MAGIC, "free");		/* make sure it was in use */
#else
	if (op->ov_magic != MAGIC)
		return;				/* sanity */
#endif
#ifdef RCHECK
  	ASSERT(op->ov_rmagic == RMAGIC, "free");
	ASSERT(*(u_short *)((caddr_t)(op + 1) + op->ov_size) == RMAGIC, "free");
#endif
#ifdef SHARED
	S_LOCK(&lock);
#endif
  	size = op->ov_index;
  	ASSERT(size < NBUCKETS, "free");
	op->ov_next = nextf[size];
  	nextf[size] = op;
#ifdef MSTATS
  	nmalloc[size]--;
#endif
#ifdef SHARED
	S_UNLOCK(&lock);
#endif
}

#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 union overhead *p;
  	int totfree = 0,
  	totused = 0;
#ifdef SHARED
	S_LOCK(&lock);
#endif

  	fprintf(stderr, "Memory allocation statistics %s\nfree:\t", s);
  	for (i = 0; i < NBUCKETS; i++) {
  		for (j = 0, p = nextf[i]; p; p = p->ov_next, j++)
  			;
  		fprintf(stderr, " %d", j);
  		totfree += j * (1 << (i + 3));
  	}
  	fprintf(stderr, "\nused:\t");
  	for (i = 0; i < NBUCKETS; i++) {
  		fprintf(stderr, " %d", nmalloc[i]);
  		totused += nmalloc[i] * (1 << (i + 3));
  	}
  	fprintf(stderr, "\n\tTotal in use: %d, total free: %d\n",
	    totused, totfree);
#ifdef SHARED
	S_UNLOCK(&lock);
#endif
}
#endif

/*
 * Don't support the "compaction" realloc function.  If the input cp block
 * points to a free block, return NULL.
 *
 */

char *
realloc(cp, nbytes)
	char *cp; 
	unsigned nbytes;
{   
  	register unsigned int onb, i;
	union overhead *op;
  	char *res;

  	if (cp == NULL)
  		return (malloc(nbytes));

	op = (union overhead *)((caddr_t)cp - sizeof (union overhead));

	/*
	 * If it's a "free" buffer, return NULL.
	 */
	if (op->ov_magic != MAGIC) 
		return(NULL);

	i = op->ov_index;
	onb = sizes[i] - (sizeof(union overhead) + RSLOP);
	if (i)
		i = sizes[i - 1] - (sizeof(union overhead) + RSLOP);

	/* avoid the copy if same size block */
	if (nbytes <= onb && nbytes > i) {
#ifdef RCHECK
		op->ov_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1);
		*(u_short *)((caddr_t)(op + 1) + op->ov_size) = RMAGIC;
#endif
		return(cp);
	}

  	if ((res = malloc(nbytes)) == NULL) {
		free(cp);
  		return (NULL);
	}
  	if (cp != res){			/* common optimization */
		memcpy((char *)res, (char *)cp, (nbytes < onb) ? nbytes : onb);
		free(cp);
	}
  	return (res);
}
