/*
 *
 *	$Header: /usr/u/wjr/src/ADT/RCS/chunk.c,v 1.4 1993/07/26 22:15:42 wjr Exp $
 *
 * Copyright (c) 1990, 1991, 1992, 1993 Cornell University.  All Rights
 * Reserved.
 *
 * Copyright (c) 1991, 1992 Xerox Corporation.  All Rights Reserved.
 *
 * Use, reproduction and preparation of derivative works of this software is
 * permitted.  Any copy of this software or of any derivative work must
 * include both the above copyright notices of Cornell University and Xerox
 * Corporation and this paragraph.  This software is made available AS IS, and
 * XEROX CORPORATION DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
 * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE, AND NOTWITHSTANDING ANY OTHER PROVISION CONTAINED
 * HEREIN, ANY LIABILITY FOR DAMAGES RESULTING FROM THE SOFTWARE OR ITS USE IS
 * EXPRESSLY DISCLAIMED, WHETHER ARISING IN CONTRACT, TORT (INCLUDING
 * NEGLIGENCE) OR STRICT LIABILITY, EVEN IF XEROX CORPORATION IS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 */
/*
 * Xerox may use this code without restriction.
 */

static char *rcsid = "@(#)$Header: /usr/u/wjr/src/ADT/RCS/chunk.c,v 1.4 1993/07/26 22:15:42 wjr Exp $";

/*
 *	chunk.c - Chunk manager
 *
 * A lot of programs allocate and free a lot of objects of the same type.
 * Using the general-purpose malloc/free routines for this is inefficient,
 * since they must deal with arbitrary allocations. This module is intended
 * to provide a simple, efficient method for allocating and freeing a lot
 * of objects of the same type.
 *
 * Exports:
 *	type Chunk
 *
 *	constant Chunk NULLCHUNK
 *
 *	Chunk chNew(unsigned size) - create a new area for allocating elements
 *		of size bytes
 *
 *	void *chAlloc(Chunk c) - get a new object from that chunk
 *
 *	void chFree(Chunk c, void *p) - free an object
 *
 * An object of type Chunk can be used to allocate objects of the size given
 *	when the chunk was created. Alignment is guaranteed, assuming that
 *	the size given is actually sizeof(type), where type is the type of
 *	the object being allocated.
 *
 * chNew creates a new chunk containing objects of the given size. If this
 *	is not possible, it returns NULLCHUNK.
 *
 * chAlloc allocates an object from that chunk. If this is not possible, it
 *	returns (void *)NULL.
 *
 * chFree frees the object, which must have been allocated from the given
 *	chunk.
 *
 */

#include "misc.h"
#include "chunk.h"

/* How many objects to allocate at once */
#define	DEFAULT_QUANTUM	1000

Chunk chNew(unsigned size)
{
  return chNewQ(size, DEFAULT_QUANTUM);
}

Chunk chNewQ(unsigned size, unsigned quant)
{
  Chunk c;


  c = (Chunk)malloc(sizeof(*c));
  if (c == (Chunk)NULL) {
    return(NULLCHUNK);
  }

  /* We don't allocate any storage at all initially */
  c->freelist = (struct chunkBit *)NULL;
  c->blobs    = (chunkBlob *) NULL;
  c->quantum  = (quant <= 1) ? DEFAULT_QUANTUM : quant;
  
  /*
   * We put freelist pointers in the empty bits. We need to ensure that
   * both pointers and the things we're allocating are aligned properly,
   * and that there's enough space for either.
   *
   * If alignment rules are *really* strange, this could break: should be
   * LCM of the sizes; this will typically work though.
   */

  c->size = MAX(sizeof(struct chunkBit), size);
  if (c->size % sizeof(struct chunkBit) != 0) { /* Round it up */
    c->size += sizeof(struct chunkBit) - (c->size % sizeof(struct chunkBit));
  }

  return(c);
}

void *chAlloc(Chunk c)
{
  void *p;
  char *q;
  int i;

  assert(c != NULLCHUNK  && c->quantum > 1);

  if (c->freelist) {
    /*
     * If there is a free element, grab it and return.
     * This should be the common case, and so be simple and fast.
     */
    p = (void *)c->freelist;
    c->freelist = c->freelist->next;
    return(p);
  }
  else {                                     /* Allocate a blob */
    chunkBlob *blob;
    
    p = malloc(c->size * c->quantum + sizeof(chunkBlob));
    if (p == (void *)NULL) {
      return((void *)NULL);
    }

    /* add to bloblist */
    blob = (chunkBlob *) (((char *)p) + c->size * c->quantum);
    blob->next = c->blobs;
    blob->blob = (void *) p;
    c->blobs = blob;
    
    /* Thread the list through it */
    for (i = 0, q = (char *)p; i < c->quantum - 1; i++, q += c->size) {
      ((struct chunkBit *)q)->next = (struct chunkBit *)(q + c->size);
    }
    /* Terminate the list */
    ((struct chunkBit *)q)->next = (struct chunkBit *)NULL;

    /* Point the freelist at the second element */
    c->freelist = (struct chunkBit *)(((char *)p) + c->size);
    /* and return the first */
    return(p);
  }
}

void chFree(Chunk c, void *p)
{
  assert(c != NULLCHUNK  && c->quantum > 1);
  assert(p != (void *)NULL);

  ((struct chunkBit *)p)->next = c->freelist;
  c->freelist = (struct chunkBit *)p;
}

void chDelete(Chunk c, chCheckType check)
{
  unsigned quant;
  chunkBlob *blobs, *blp;
  unsigned size;
  struct chunkBit *freep, *fcp;
  static char msg[1000];
  

  if (c == NULLCHUNK)
    return ;

  assert(c->quantum > 1);

  size  = c->size;
  freep = c->freelist;
  blobs = c->blobs;
  quant = c->quantum;
  c->quantum = 0;                            /* mark deleted */

  if (check == chNoCheck) {
  }
  else if (check == chFastCheck  ||  size < sizeof(struct chunkBit *) + sizeof(int)) {
    int nblobs, nchunks, mismatch;

    for (nblobs = 0, blp = blobs; blp != NULL; nblobs++, blp = blp->next)
      ;

    for (nchunks = 0, fcp = freep; fcp != NULL; nchunks++, fcp = fcp->next)
      ;

    mismatch = nblobs * quant - nchunks;

    if (mismatch != 0) {
      sprintf(msg, "mismatch in chDelete() for chunk %p: %d %s chunks",
              c, ABS(mismatch), mismatch > 0 ? "unfreed" : "too many");
      panic(msg);
    }
  }
  else {
    int unfreed = 0, mulfreed = 0, notfound = 0;
    int i, j, nf, *nfp;
    char *q;
    int nblobs;

    for (nblobs = 0, blp = blobs; blp != NULL; nblobs++, blp = blp->next)
      ;

    for (blp = blobs; blp != NULL; blp = blp->next) {
      for (q = (char *) blp->blob, i = 0; i < quant; i++, q += size) {
        *((int *) (q + sizeof(struct chunkBit *))) = 0;
      }
    }

    if (check == chReport) {
      fprintf(stderr, "Reporting for chunk %p:  %d chunks allocated (%d blobs)\n",
              c, nblobs*quant, nblobs);
    }
      
    for (fcp = freep; fcp != NULL; fcp = fcp->next) {
      for (blp = blobs; blp != NULL; blp = blp->next) {
        if ((char *) fcp >= (char *) blp->blob  &&
            (char *) fcp <  (char *) blp->blob + size*quant) {
          nfp = (int *) (((char *) fcp) + sizeof(struct chunkBit *));
          if (*nfp >= 1)                     /* loop */
            fcp->next = NULL;
          (*nfp)++;
          goto next_chunk;
        }
      }

      notfound++;
      if (check == chReport) {
        fprintf(stderr, "memory at %p was not allocated for chunk %p\n", fcp, c);
      }

     next_chunk:
      ;
    }

    for (j = 0, blp = blobs; blp != NULL; j++, blp = blp->next) {
      for (q = (char *) blp->blob, i = 0; i < quant; i++, q += size) {
        nf = *((int *) (q + sizeof(struct chunkBit *)));
        if (nf == 0) {
          unfreed++;
          if (check == chReport) {
            fprintf(stderr, "chunk number %d of blob %d of chunk %p not freed\n",
                    i, nblobs - j - 1, c);
          }
        }
        else if (nf > 1) {
          mulfreed++;
          if (check == chReport) {
            fprintf(stderr, "chunk number %d of blob %d of chunk %p freed %d times\n",
                    i, nblobs - j - 1, c, nf);
          }
        }
      }
    }

     
    if (check == chReport) {
      fprintf(stderr, "\n\n");
    }

    if (unfreed > 0  ||  mulfreed > 0  ||  notfound > 0) {
      sprintf(msg, "chunk allocation error in chDelete() for chunk %p: "
              "%d unfreed, %d multiply freed, and %d not found",
              c, unfreed, mulfreed, notfound);
      panic(msg);
    }
  }
  
  for ( ; blobs != NULL; blobs = blp) {
    blp = blobs->next;
    free((void *) blobs->blob);
  }
  free((void *) c);
}
