/*
 * File: rmemexp.c - memory management functions for expandable regions
 *  Contents: initalloc, reclaim, malloc, calloc, free
 */

/*
 * Prototypes.
 */

hidden	novalue moremem	Params((uword units));
hidden	novalue	reclaim	Params((int region));

word xcodesize;

/*
 * initalloc - initialization routine to allocate memory regions
 */

novalue initalloc(codesize)
word codesize;
   {

   xcodesize = codesize;

   /*
    * Establish icode region
    */
   code = (char *)sbrk((word)0);

   /*
    * Set up allocated memory.	The regions are:
    *
    *	Static memory region
    *	Allocated string region
    *	Allocate block region
    *	Qualifier list
    */

   statfree = statbase = (char *)((uword)(code + codesize + 3)  & ~03);

/*
 * The following code is operating-system dependent [@rmemexp.01].  Set end of
 *  static region, rounding up if necessary.
 */

#if PORT
   statend = (char *)(((uword)statbase) + mstksize + statsize);
Deliberate Syntax Error
#endif					/* PORT */

#if AMIGA || HIGHC_386 || OS2 || ((MVS || VM) && !SASC)
   /* use fixed regions */
#endif					/* AMIGA  || HIGHC_386 || ... */

#if MSDOS
   statend =
      (char *)(((uword)statbase) + (((mstksize + statsize + 511)/512) * 512));
#endif					/* MSDOS */

#if MACINTOSH
#if MPW
   statend = (char *)(((uword)statbase) + mstksize + statsize);
#endif					/* MPW */
#endif					/* MACINTOSH */

#if ATARI_ST || SASC || UNIX || VMS
   statend = (char *)(((uword)statbase) + mstksize + statsize);
#endif					/* ATARI_ST || SASC || UNIX || VMS */

/*
 * End of operating-system specific code.
 */

   strfree = strbase = (char *)((uword)(statend + 63) & ~077);
   blkfree = blkbase = strend = (char *)((((uword)strbase) + ssize +
      63) & ~077);
   equallist = (dptr *)(blkend =
      (char *)((((uword)(blkbase) + abrsize + 63)) & ~077));

   /*
    * Try to move the break back to the end of memory to allocate (the
    *  end of the string qualifier list) and die if the space isn't
    *  available.
    */
   if ((int)brk((char *)equallist) == -1)
      error("insufficient memory");
   currend = (char *)sbrk((word)0);	/* keep track of end of memory */
   }

/*
 * reclaim - reclaim space in the allocated memory regions. The marking
 *  phase has already been completed.
 */

static novalue reclaim(region)
int region;
{
   register word stat_extra, str_extra, blk_extra;
   register char *newend;

   stat_extra = 0;
   str_extra = 0;
   blk_extra = 0;

   /*
    * Collect available co-expression blocks.
    */
   cofree();

   /*
    * If there was no room to construct the qualifier list, the string
    *  region cannot be collected and the static region cannot be expanded.
    */
   if (!qualfail) {
      /*
       * Check whether the static region needs to be expanded. Regions cannot
       *  be expanded if someone else has moved the end of allocated storage.
       */
      if (statneed && currend == sbrk((word)0)) {
         /*
          * Make sure there is space for the requested static region expansion.
          *  The check involving equallist and newend appears to only be
          *  required on machines where the above addition of statneed might
          *  overflow.
          */
         newend = (char *)equallist + statneed;
         if ((uword)newend >= (uword)(char *)equallist &&
             (int)brk((char *)newend) != -1) {
               stat_extra = statneed;
               statneed = 0;
               statend += stat_extra;
               equallist = (dptr *)newend;
               currend = sbrk((word)0);
               }
         }
   
      /*
       * Collect the string space, indicating that it must be moved back
       *  extra bytes.
       */
      scollect(stat_extra);
   
      if (region == Strings && currend == sbrk((word)0)) {
         /*
          * Calculate a value for extra space.  The value is (the larger of
          *  (twice the string space needed) or (a quarter of the string space))
          *  minus the unallocated string space.
          */
         str_extra = (Max(2*strneed, ((uword)strend - (uword)strbase)/4) -
               ((uword)strend - (uword)strfree) + (GranSize-1)) & ~(GranSize-1);
         while (str_extra > 0) {
            /*
             * Try to get str_extra more bytes of storage.  If it can't be
             *  gotten, decrease the value by GranSize and try again.  If
             *  it's gotten, move back equallist.
             */
            newend = (char *)equallist + str_extra;
            if ((uword)newend >= (uword)(char *)equallist &&
                (int)brk((char *)newend) != -1) {
                   equallist = (dptr *) newend;
                   currend = sbrk((word)0);
                   break;
                   }
            str_extra -= GranSize;
            }
         if (str_extra < 0)
            str_extra = 0;
         }
      }

   /*
    * Adjust the pointers in the block region.
    */
   adjust(blkbase, blkbase + stat_extra + str_extra);

   /*
    * Compact the block region.
    */
   compact(blkbase);

   if (region == Blocks && currend == sbrk((word)0)) {
      /*
       * Calculate a value for extra space.  The value is (the larger of
       *  (twice the block region space needed) or (one quarter of the
       *  block region)) plus the unallocated block space.
       */
      blk_extra = (Max(2*blkneed, ((uword)blkend - (uword)blkbase)/4) -
               ((uword)blkend - (uword)blkfree) + (GranSize-1)) & ~(GranSize-1);
      while (blk_extra > 0) {
         /*
          * Try to get blk_extra more bytes of storage.  If it can't be gotten,
          *  decrease the value by GranSize and try again.  If it's gotten,
          *  move back equallist.
          */
         newend = (char *)equallist + blk_extra;
         if ((uword)newend >= (uword)(char *)equallist &&
             (int)brk((char *)newend) != -1) {
                equallist = (dptr *) newend;
                currend = sbrk((word)0);
                break;
                }
         blk_extra -= GranSize;
         }
      if (blk_extra < 0)
         blk_extra = 0;
   }
                
   if (stat_extra + str_extra > 0) {
      /*
       * The block region must be moved.  There is an assumption here that the
       *  block region always moves up in memory, i.e., the static and
       *  string regions never shrink.	With this assumption in hand,
       *  the block region must be moved before the string space lest the
       *  string space overwrite block data.  The assumption is valid,
       *  but beware if shrinking regions are ever implemented.
       */
      mvc((uword)blkfree - (uword)blkbase, blkbase, blkbase + stat_extra +
         str_extra);
      blkbase += stat_extra + str_extra;
      blkfree += stat_extra + str_extra;
      }
   blkend += stat_extra + str_extra + blk_extra;

   if (stat_extra > 0) {
      /*
       * The string space must be moved up in memory.
       */
      mvc((uword)strfree - (uword)strbase, strbase, strbase + stat_extra);
      strbase += stat_extra;
      strfree += stat_extra;
      }
   strend += stat_extra + str_extra;
   }

/*
 * These are Icon's own versions of the allocation routines.  They are
 *  not used for the fixed-regions versions of memory management.  They
 *  normally overload the corresponding library routines. If this is not
 *  possible, they are re-named and calls to them are renamed.
 */

static HEADER base;		/* start with empty list */
static HEADER *allocp = NULL;	/* last allocated block */

#if LATTICE || LSC
#define nothing 0
int free(ap)
#else					/* LATTICE || LSC */
#define nothing
novalue free(ap)		/* return block pointed to by ap to free list */
#endif					/* LATTICE || LSC */
pointer ap;
   {
   register HEADER *p, *q;

/* free may be called to free a block before the static region is
 *  initialized.
 */
   if (statbase == (char *)NULL || (char *)ap < statbase)
      return nothing;

   p = (HEADER *)ap - 1;	/* point to header */

#ifdef MemMon
   if (p->s.bsize > 1)	{
      if (*(int *)(p + 1) != T_Coexpr)
         MMStat((char *)ap, (word)((p->s.bsize - 1) * sizeof(HEADER)), 'F');
      *(int *)(p + 1) = FREEMAGIC;
      }
#endif					/* MemMon */

   if (p->s.bsize * sizeof(HEADER) >= statneed)
     statneed = 0;
   for (q = allocp; !((uword)p > (uword)q && (uword)p < (uword)q->s.ptr);
      q = q->s.ptr)
         if ((uword)q >= (uword)q->s.ptr && ((uword)p > (uword)q ||
            (uword)p < (uword)q->s.ptr))
               break; 		/* at one end or the other */
   if ((uword)p + sizeof(HEADER) * p->s.bsize
      == (uword)q->s.ptr) {	/* join to upper */
      p->s.bsize += q->s.ptr->s.bsize;
      if (p->s.bsize * sizeof(HEADER) >= statneed)
         statneed = 0;
      p->s.ptr = q->s.ptr->s.ptr;
      }
   else
      p->s.ptr = q->s.ptr;
   if ((uword)q + sizeof(HEADER) * q->s.bsize ==
      (uword)p) {		/* join to lower */
         q->s.bsize += p->s.bsize;
         if (q->s.bsize * sizeof(HEADER) >= statneed)
            statneed = 0;
         q->s.ptr = p->s.ptr;
         }
   else
      q->s.ptr = p;
   allocp = q;
   }

pointer malloc(nbytes)
msize nbytes;
   {
   register HEADER *p, *q;
   register uword nunits;
   register pointer xbase;
   int attempts;

   if (statbase == NULL) {
     if ((xbase = sbrk(nbytes)) == (pointer)-1)
        syserr("malloc: failed during startup");
     return xbase;
     }

   nunits = 1 + (nbytes + sizeof(HEADER) - 1) / sizeof(HEADER);

   if ((q = allocp) == NULL) {	/* no free list yet */
      base.s.ptr = allocp = q = &base;
      base.s.bsize = 0;
      }

   for (attempts = 2; attempts--; q = allocp) {
      for (p = q->s.ptr;; q = p, p = p->s.ptr) {
         if (p->s.bsize >= nunits) {	/* block is big enough */
            if (p->s.bsize == nunits)	/* exactly right */
               q->s.ptr = p->s.ptr;
            else {			/* allocate tail end */
               p->s.bsize -= nunits;
               p += p->s.bsize;
               p->s.bsize = nunits;
               }
            allocp = q;

#ifdef MemMon
            if (nunits > 1)   {
               MMStat((char *)(p + 1), (word) nbytes, 'A');
               *(int *)(p + 1) = 0;	/* clear FREEMAGIC flag */
               }
#endif					/* MemMon */

            return (char *)(p + 1);
            }
         if (p == allocp) {	/* wrap around */
            moremem(nunits);	/* garbage collect and expand if needed */
            break;
            }
         }
      }

      return NULL;
   }

#define FREESIZE 2	/* units sizeof(HEADER) that justify free() */

/*
 *  realloc() allocates a block of memory of a requested size (amount) to
 *  contain the contents of the current block (curmem) or as much as will
 *  fit.  Blocks are allocated in units of sizeof(HEADER)
 */

pointer realloc(curmem,newsiz)
register pointer curmem;		/* the current memory pointer */
msize newsiz;				/* bytes needed for new allocation */
   {
   register int cunits;		/* currently allocated units */
   register int nunits;		/* new units required */
   char *newmem;		/* the new memory pointer */
   register HEADER *head;	/* all blocks used or free have a header */

   /*
    * First establish the unit sizes involved.
    */

   nunits = 1 + (newsiz + sizeof(HEADER) - 1) / sizeof(HEADER);
   head = ((HEADER *)curmem) - 1;	/* move back a block header */
   cunits = (int)head->s.bsize;

   /*
    * Now allocate or free space as required.
    */

   if (nunits <= cunits) {	/* we already have the space */
      if (cunits - nunits < FREESIZE)
         return curmem;
      else {			/* free space at end of current block */
         head->s.bsize = nunits;	/* reduce space used */
         head += nunits;		/* move to free space */
         head->s.bsize = cunits - nunits;
         free((pointer)(++head));	/* free this new block */
         return curmem;
         }
      }
   else {				/* more space needed */
      if ((newmem = malloc((msize)newsiz)) != NULL) {
         memcopy(newmem,curmem,(word)((cunits - 1) * sizeof(HEADER)));
         free(curmem);
         return newmem;
         }
      }
   return NULL;
   }

/*
 * calloc() allocates ecnt number of esiz-sized chunks of zero-initialized
 * memory for an array of ecnt elements.
 */

pointer calloc(ecnt,esiz)
   register msize ecnt, esiz;
   {
   register char *mem;			/* the memory pointer */
   register msize amount;		/* the amount of memory needed */

   amount = ecnt * esiz;
   if ((mem = malloc(amount)) != NULL) {
      memfill(mem,0,(word)amount);		/* initialize it to zero */
      return mem;
      }
   return NULL;
   }

static novalue moremem(nunits)
uword nunits;
   {
   register HEADER *up;
   register word rnu;
   word n;

   rnu = NALLOC * ((nunits + NALLOC - 1) / NALLOC);
   n = rnu * sizeof(HEADER);
   if (((uword)statfree) + n > (uword)statend) {
      statneed = ((n / statincr) + 1) * statincr;
      coll_stat++;
      collect(Static);
      }
   /*
    * See if there is any room left.
    */
   if ((uword)statend - (uword)statfree > sizeof(HEADER)) {
      up = (HEADER *) statfree;
      up->s.bsize = ((uword)statend - (uword)statfree) / sizeof(HEADER);
      statfree = (char *) (up + up->s.bsize);
      free((pointer)(up + 1));	/* add block to free memory */
      }
   }
