lpc4/lib/
lpc4/lib/doc/efun/
lpc4/lib/doc/lfun/
lpc4/lib/doc/operators/
lpc4/lib/doc/simul_efuns/
lpc4/lib/doc/types/
lpc4/lib/etc/
lpc4/lib/include/
lpc4/lib/include/arpa/
lpc4/lib/obj/d/
lpc4/lib/save/
lpc4/lib/secure/
lpc4/lib/std/
lpc4/lib/std/living/
/* Satoria's malloc intended to be optimized for lpmud.
** this memory manager distinguishes between two sizes
** of blocks: small and large.  It manages them separately
** in the hopes of avoiding fragmentation between them.
** It expects small blocks to mostly be temporaries.
** It expects an equal number of future requests as small
** block deallocations.
*/
#include "global.h"
#include "dynamic_buffer.h"
#include "simulate.h"

#ifdef MSDOS
#define char void
#endif

#if 0
static void fake(char *s)
{
  fprintf(stderr,"%s\n",s);
}
#else
#define fake(s)
#endif

#define smalloc malloc
#define sfree free
#define srealloc realloc

#define SMALL_BLOCK_MAX_BYTES	40
#define SMALL_CHUNK_SIZE	0x4000
#define CHUNK_SIZE		0x40000

#define SINT sizeof(int)
#define SMALL_BLOCK_MAX (SMALL_BLOCK_MAX_BYTES/SINT)

#define PREV_BLOCK	0x80000000
#define THIS_BLOCK	0x40000000
#define MASK		0x0FFFFFFF

#define MAGIC		0x17952932

/* SMALL BLOCK info */

typedef size_t u;

static u *sfltable[SMALL_BLOCK_MAX];	/* freed list */
static u *next_unused=0;
static u unused_size;			/* until we need a new chunk */

/* LARGE BLOCK info */

static u *free_list=0;
static u *start_next_block=0;

/* STATISTICS */

static int small_count[SMALL_BLOCK_MAX];
static int small_total[SMALL_BLOCK_MAX];
static int small_max[SMALL_BLOCK_MAX];
static int small_free[SMALL_BLOCK_MAX];

typedef struct { unsigned counter, size; } stat;
#define count(a,b) { a.size+=(b); if ((b)<0) --a.counter; else ++a.counter; }
#define count_up(a,b) { a.size+=(b); ++a.counter; }
#define count_down(a,b) { a.size+=(b); --a.counter; }


int debugmalloc;	/* Only used when debuging malloc() */

/********************************************************/
/*  SMALL BLOCK HANDLER					*/
/********************************************************/

static char *large_malloc();
static void large_free();

#define s_size_ptr(p)	(p)
#define s_next_ptr(p)	((u **) (p+1))

static stat small_alloc_stat;
static stat small_free_stat;
static stat small_chunk_stat;

#ifdef MALLOC_DEBUG

static char my_watch;
static char *my_watchpoint=0;
void do_my_watch(char *p)
{
  my_watchpoint=p;
  my_watch=*p;
}

static apa()
{
  char *p;
  p="ABORTING\n";
  write(1,p,strlen(p));
  abort();
}

void verify_sfltable();
void check_sfltable()
{
  u *foo;
  u *last=0;
  int e;
  if(my_watchpoint)
  {
    if(my_watch!=*my_watchpoint)
    {
      fprintf(stderr,"Watchpoint alert! %d!=%d\n",my_watch , *my_watchpoint);
    }
  }
  
  if(!debugmalloc) return;

  for(e=0;e<SMALL_BLOCK_MAX;e++)
  {
    for(foo=sfltable[e];foo;foo=*s_next_ptr(foo))
    {
      if(last==foo)
      {
	fprintf(stderr,"sfltable[%d] out of order, or?\n",e);
	break;
      }
      if(*s_size_ptr(foo)!=e+2)
      {
	fprintf(stderr,"sfltable[%d] out of order\n",e);
	apa();
      }
      last=foo;
    }
  }
  verify_sfltable();
}

POINTER smalloc2(u size);
static u smsizeof(POINTER p);
POINTER smalloc(u size)
{
  char *foo;
  int msize;
  check_sfltable();
  foo=smalloc2(size);
  msize=smsizeof(foo);
  if((msize>SMALL_BLOCK_MAX_BYTES) ^ (size>SMALL_BLOCK_MAX_BYTES))
  {
    fprintf(stderr,"Urgh... malloc out of order.\n");
    apa();
  }
  check_sfltable();
  return foo;
}


POINTER smalloc2(u size)
#else
POINTER smalloc(u size)
#endif
{
  int i;
  u *temp;

  if (size == 0)
      fatal("Malloc size 0.\n");
  if (size>SMALL_BLOCK_MAX_BYTES)
    return large_malloc(size,0);

  i = (size - 1) >> 2;
  size = i+2;				/* block size in ints */
  count_up(small_alloc_stat,size << 2);

  small_count[i] += 1;			/* update statistics */
  small_total[i] += 1;
  if (small_count[i] >= small_max[i])
    small_max[i] = small_count[i];

  if (sfltable[i]) 
  {				/* allocate from the free list */
    count_down(small_free_stat, -(int) (size << 2));
    temp = sfltable[i];
    sfltable[i] = * (u **) (temp+1);
    fake("From free list.");
    return (char *) (temp+1);
  } /* else allocate from the chunk */

  if (unused_size<size)			/* no room in chunk, get another */
  {
    fake("Allocating new small chunk.");
    next_unused = (u *) large_malloc(SMALL_CHUNK_SIZE,1);
    if (next_unused == 0)
      return 0;
    count_up(small_chunk_stat, SMALL_CHUNK_SIZE+SINT);
    unused_size = SMALL_CHUNK_SIZE / SINT;
  }else{
    fake("Allocated from chunk.");
  }


  temp = (u *) s_next_ptr(next_unused); 

  *s_size_ptr(next_unused) = size;
  next_unused += size;
  unused_size -= size;

  return (char *) temp;
}

static char *debug_free_ptr;

#ifdef FREE_RETURNS_VOID
void sfree(POINTER ptr)
#else
int sfree(POINTER ptr)
#endif
{
  u *block;
  u i;
#ifdef MALLOC_DEBUG
  check_sfltable();
#endif
  debug_free_ptr = ptr;
  block = (u *) ptr;
  block -= 1;
  if ((*s_size_ptr(block) & MASK) > SMALL_BLOCK_MAX + 1)
  {
    large_free(ptr);
  }else{

    i = *block - 2;
    count_down(small_alloc_stat, - (int) ((i+2) << 2));
    count_up(small_free_stat, (i+2) << 2);
#ifdef DEBUG
  {
    u *e;
    for(e=sfltable[i];e;e=*s_next_ptr(e))
    {
      if(e==block)
      {
	fprintf(stderr,"Freeing small block again.\n");
	apa();
      }
    }
  }
#endif
    *s_next_ptr(block) = sfltable[i];
    sfltable[i] = block;
    small_free[i] += 1;
    fake("Freed");
  }
#ifndef FREE_RETURNS_VOID
  return 1;
#endif
}

/************************************************/
/*	LARGE BLOCK HANDLER			*/
/************************************************/

#define BEST_FIT	0
#define FIRST_FIT	1
#define HYBRID		2
int fit_style =BEST_FIT;

#define l_size_ptr(p)		(p)
#define l_next_ptr(p)		(*((u **) (p+1)))
#define l_prev_ptr(p)		(*((u **) (p+2)))
#define l_next_block(p)		(p + (*(p)))
#define l_prev_block(p) 	(p - (*(p-1)))
#define l_prev_free(p)		(!(*p & PREV_BLOCK))
#define l_next_free(p)		(!(*l_next_block(p) & THIS_BLOCK))

void show_block(u *ptr)
{
  fprintf(stderr,"[%c%d: %d]  ",(*ptr & THIS_BLOCK ? '+' : '-'),
		(int) ptr, *ptr & MASK);
}

void show_free_list()
{
  u *p;
  p = free_list;
  while (p) {
    show_block(p);
    p = l_next_ptr(p);
  }
  fprintf(stderr,"\n");
}

static stat large_free_stat;     
static void remove_from_free_list(u *ptr)
{
  count_down(large_free_stat, - (int) (*ptr & MASK) << 2);

  if (l_prev_ptr(ptr))
    l_next_ptr(l_prev_ptr(ptr)) = l_next_ptr(ptr);
  else
    free_list = l_next_ptr(ptr);

  if (l_next_ptr(ptr))
    l_prev_ptr(l_next_ptr(ptr)) = l_prev_ptr(ptr);
}

static void add_to_free_list(u *ptr)
{
  extern int puts();
  count_up(large_free_stat, (*ptr & MASK) << 2);

  if (free_list && l_prev_ptr(free_list)) 
    puts("Free list consistency error.");

  l_next_ptr(ptr) = free_list;
  if (free_list) 
    l_prev_ptr(free_list) = ptr;
  l_prev_ptr(ptr) = 0;
  free_list = ptr;
}

/* build a properly annotated unalloc block */
static void build_block(u *ptr,u size)
{
  *(ptr) = (*ptr & PREV_BLOCK) | size;		/* mark this block as free */
  *(ptr+size-1) = size;
  *(ptr+size) &= (MASK | THIS_BLOCK); /* unmark previous block */
}

static void mark_block(u *ptr)		/* mark this block as allocated */
{
  *l_next_block(ptr) |= PREV_BLOCK;
  *ptr |= THIS_BLOCK;
}

/*
 * It is system dependent how sbrk() aligns data, so we simply use brk()
 * to insure that we have enough.
 */
static stat sbrk_stat;
static char *esbrk(u size)
{
#ifndef HAVE_UNISTD_H
  extern char *sbrk();
  extern int brk();
#endif
  static char *current_break;

  if (current_break == 0)
    current_break = sbrk(0);
  if (brk(current_break + size) == -1)
    return 0;
  count_up(sbrk_stat,size);
  current_break += size;
  return current_break - size;
}

stat large_alloc_stat;
static char *large_malloc(u size,int force_more)
{
  u best_size, real_size;
  u *first, *best, *ptr;

  size = (size + 7) >> 2;	/* plus overhead */
  count_up(large_alloc_stat, size << 2);
  first = best = 0;
  best_size = MASK;

  if (force_more)
    ptr = 0;
  else
    ptr = free_list;

  while (ptr) {
    u tempsize;
    /* Perfect fit? */
    tempsize = *ptr & MASK;
    if (tempsize == size) {
      best = first = ptr;
      break;			/* always accept perfect fit */
    }

    /* does it really even fit at all */
    if (tempsize >= size + SMALL_BLOCK_MAX + 1)
    {
      /* try first fit */
      if (!first) 
      {
	first = ptr;
	if (fit_style == FIRST_FIT)
	  break;		/* just use this one! */
      }
      /* try best fit */
      tempsize -= size;
      if (tempsize>0 && tempsize<=best_size) 
      {
	best = ptr;
	best_size = tempsize;
      }
    }
    ptr = l_next_ptr(ptr);
  } /* end while */

  if (fit_style==BEST_FIT) ptr = best;
  else ptr = first;		/* FIRST_FIT and HYBRID both leave it in first */

  if (!ptr)			/* no match, allocate more memory */
  {
    u chunk_size, block_size;
    block_size = size*SINT;
    if (force_more || (block_size>CHUNK_SIZE))
      chunk_size = block_size;
    else
      chunk_size = CHUNK_SIZE;

    if (!start_next_block) {
      start_next_block = (u *) esbrk(SINT);
      if (!start_next_block)
	return 0;
      *(start_next_block) = PREV_BLOCK;
      fake("Allocated little fake block");
    }

    ptr = (u *) esbrk(chunk_size);
    if (ptr == 0)
      return 0;
    ptr -= 1;			/* overlap old memory block */
    block_size = chunk_size / SINT;

    /* configure header info on chunk */
      
    build_block(ptr,block_size);
    if (force_more)
    {
      fake("Build little block");
    }else{
      fake("Built memory block description.");
    }
    *l_next_block(ptr)=THIS_BLOCK;
    add_to_free_list(ptr);
  } /* end of creating a new chunk */

  remove_from_free_list(ptr);
  real_size = *ptr & MASK;

  if (real_size != size) {
    /* split block pointed to by ptr into two blocks */
    build_block(ptr+size, real_size-size);
    fake("Built empty block");
    add_to_free_list(ptr+size);
    build_block(ptr, size);
  }

  mark_block(ptr);
  fake("built allocated block");
  return (char *) (ptr + 1);
}

static void large_free(char *ptr)
{
  u size, *p;
  p = (u *) ptr;
  p-=1;
  size = *p & MASK;
  count(large_alloc_stat, - (int) (size << 2));

#ifdef DEBUG
  {
    u *b;
    for(b=free_list;b;b=l_next_ptr(b))
    {
      if(b==p)
      {
	fprintf(stderr,"Freeing large block twice.\n");
	apa();
      }
    }
  }
#endif

  if (l_next_free(p)) {
    remove_from_free_list(l_next_block(p));
    size += (*l_next_block(p) & MASK);
    *p = (*p & PREV_BLOCK) | size;
  }

  if (l_prev_free(p)) {
    remove_from_free_list(l_prev_block(p));
    size += (*l_prev_block(p) & MASK);
    p = l_prev_block(p);
  }

  build_block(p, size);

  add_to_free_list(p);
}

POINTER srealloc(POINTER p,u size)
{
   unsigned *q, old_size;
   char *t;
#ifdef MALLOC_DEBUG
  check_sfltable();
#endif
   
   q = (unsigned *) p;
   --q;
   old_size = ((*q & MASK)-1)*sizeof(int);
   if (old_size >= size)
      return p;

   t = malloc(size);
   if (t == 0) return (char *) 0;

   MEMCPY(t, p, old_size);
   free(p);
   return t;
}

static u smsizeof(POINTER p)
{
  unsigned *q;
  q= (unsigned *)p;
#ifdef MALLOC_DEBUG
  check_sfltable();
#endif
  --q;
  return (( *q & MASK) -1) *sizeof(int);
}

int resort_free_list() { return 0; }

#define dump_stat(str,stat) sprintf(buffer,str,stat.counter,stat.size); my_strcat(buffer);

char *dump_malloc_data()
{
  char buffer[300];
  init_buf();

  my_strcat("Type                   Count      Space (bytes)\n");
  dump_stat("sbrk requests:     %8d        %10d (a)\n",sbrk_stat);
  dump_stat("large blocks:      %8d        %10d (b)\n",large_alloc_stat);
  dump_stat("large free blocks: %8d        %10d (c)\n\n",large_free_stat);
  dump_stat("small chunks:      %8d        %10d (d)\n",small_chunk_stat);
  dump_stat("small blocks:      %8d        %10d (e)\n",small_alloc_stat);
  dump_stat("small free blocks: %8d        %10d (f)\n",small_free_stat);

  sprintf(buffer,"unused from current chunk          %10d (g)\n\n",unused_size<<2);
  my_strcat(buffer);
  sprintf(buffer,"    Small blocks are stored in small chunks, which are allocated as\n");
  my_strcat(buffer);
  sprintf(buffer,"large blocks.  Therefore, the total large blocks allocated (b) plus\n");
  my_strcat(buffer);
  sprintf(buffer,"the large free blocks (c) should equal total storage from sbrk (a).\n");
  my_strcat(buffer);
  sprintf(buffer,"Similarly, (e) + (f) + (g) equals (d).  The total amount of storage\n");
  my_strcat(buffer);
  sprintf(buffer,"wasted is (c) + (f) + (g); the amount allocated is (b) - (f) - (g).\n");
  my_strcat(buffer);
  return free_buf();
}

/*
 * calloc() is provided because some stdio packages uses it.
 */
POINTER calloc(u nelem,u sizel)
{
  char *p;
#ifdef MALLOC_DEBUG
  check_sfltable();
#endif

  if (nelem == 0 || sizel == 0)
    return 0;
  p = malloc(nelem * sizel);
  if (p == 0)
    return 0;
  (void)MEMSET(p, '\0', nelem * sizel);
  return p;
}

/*
 * Functions below can be used to debug malloc.
 */

#ifdef MALLOC_DEBUG
/*
 * Verify that the free list is correct. The upper limit compared to
 * is very machine dependant.
 */
void verify_sfltable()
{
  u *p;
  int i, j;
  extern int end;

  if (!debugmalloc)
    return;
  if (unused_size > SMALL_CHUNK_SIZE / SINT)
    apa();
  for (i=0; i < SMALL_BLOCK_MAX; i++) {
    for (j=0, p = sfltable[i]; p; p = * (u **) (p + 1), j++) {
      if (p < (u *)&end || p > (u *) 0xfffff)
	apa();
      if (*p - 2 != i)
	apa();
    }
    if (p >= next_unused && p < next_unused + unused_size)
      apa();
  }
  p = free_list;
  while (p) {
    if (p >= next_unused && p < next_unused + unused_size)
      apa();
    p = l_next_ptr(p);
  }
}

void verify_free(u *ptr)
{
  u *p;
  int i, j;

  if (!debugmalloc)
    return;
  for (i=0; i < SMALL_BLOCK_MAX; i++) {
    for (j=0, p = sfltable[i]; p; p = * (u **) (p + 1), j++) {
      if (*p - 2 != i)
	apa();
      if (ptr >= p && ptr < p + *p)
	apa();
      if (p >= ptr && p < ptr + *ptr)
	apa();
      if (p >= next_unused && p < next_unused + unused_size)
	apa();
    }
  }

  p = free_list;
  while (p) {
    if (ptr >= p && ptr < p + (*p & MASK))
      apa();
    if (p >= ptr && p < ptr + (*ptr & MASK))
      apa();
    if (p >= next_unused && p < next_unused + unused_size)
      apa();
    p = l_next_ptr(p);
  }
  if (ptr >= next_unused && ptr < next_unused + unused_size)
    apa();
}


static char *ref;
void test_malloc(char * p)
{
  if (p == ref)
    printf("Found 0x%p\n", p);
}

#endif /* 0 (never) */