/*
 * File: rstruct.c
 *  Contents: addmem, cplist, cpset, hmake, hchain, hgener, hgrow, hshrink, memb
 */

#include "..\h\config.h"
#include "..\h\rt.h"
#include "rproto.h"

/*
 * addmem - add a new set element block in the correct spot in
 *  the bucket chain.
 */

novalue addmem(ps,pe,pl)
union block **pl;
struct b_set *ps;
struct b_selem *pe;
   {
   ps->size++;
   if (*pl != NULL )
      pe->clink = *pl;
   *pl = (union block *) pe;
   }

/*
 * cplist(dp1,dp2,i,j) - copy sublist dp1[i:j] into dp2.
 */

int cplist(dp1, dp2, i, j)
dptr dp1, dp2;
word i, j;
   {
   register dptr dp;
   word size, nslots;
   struct b_list *lp1, *lp2;
   struct b_lelem *bp1, *bp2;

   /*
    * Calculate the size of the sublist and fail if it's less than 0.
    *  Also round nslots up to the minimum list block size.
    */
   size = nslots = j - i;

   /*
    * Get pointers to the list and list elements for the source list
    *  (bp1, lp1) and the sublist (bp2, lp2).
    */
   if (blkreq(sizeof(struct b_list) + sizeof(struct b_lelem) +
         nslots * sizeof(struct descrip)) == Error)
      return Error;
   lp1 = (struct b_list *) BlkLoc(*dp1);
   bp1 = (struct b_lelem *) lp1->listhead;
   lp2 = (struct b_list *) alclist(size);
   bp2 = (struct b_lelem *) alclstb(nslots, (word)0, size);
   lp2->listhead = lp2->listtail = (union block *) bp2;
   dp = bp2->lslots;

   /*
    * Locate the block containing element i in the source list.
    */
   if (size > 0) {
      while (i > bp1->nused) {
         i -= bp1->nused;
         bp1 = (struct b_lelem *) bp1->listnext;
         }
      }

   /*
    * Copy elements from the source list into the sublist, moving to
    *  the next list block in the source list when all elements in a
    *  block have been copied.
    */
   while (size > 0) {
      j = bp1->first + i - 1;
      if (j >= bp1->nslots)
         j -= bp1->nslots;
      *dp++ = bp1->lslots[j];
      if (++i > bp1->nused) {
         i = 1;
         bp1 = (struct b_lelem *) bp1->listnext;
         }
      size--;
      }

   /*
    * Fix type and location fields for the new list.
    */
   dp2->dword = D_List;
   BlkLoc(*dp2) = (union block *) lp2;
   return Success;
   }

/*
 * cpset(dp1,dp2,n) - copy set dp1 to dp2, reserving memory for n entries.
 */
int cpset(dp1, dp2, n)
dptr dp1, dp2;
word n;
   {
   register union block **tp, *ep, *old, *new;
   register struct b_slots *seg;
   register word i, slotnum;

   /*
    * Make a new set organized like dp1, with room for n elements.
    */
   new = hmake(T_Set, BlkLoc(*dp1)->set.mask + 1, n);
   if (new == NULL)
      return Error;
   /*
    * Copy the header and slot blocks.
    */
   old = BlkLoc(*dp1);
   new->set.size = old->set.size;	/* actual set size */
   new->set.mask = old->set.mask;	/* hash mask */
   for (i = 0; i < HSegs && old->set.hdir[i] != NULL; i++)
      memcopy((char *)new->set.hdir[i], (char *)old->set.hdir[i],
         old->set.hdir[i]->blksize);
   /*
    * Work down the chain of element blocks in each bucket
    *	and create identical chains in new set.
    */
   for (i = 0; i < HSegs && (seg = new->set.hdir[i]) != NULL; i++)
      for (slotnum = segsize[i] - 1; slotnum >= 0; slotnum--)  {
         tp = &seg->hslots[slotnum];
         for (ep = *tp; ep != NULL; ep = *tp) {
            *tp = (union block *)alcselem(&ep->selem.setmem, ep->selem.hashnum);
            (*tp)->selem.clink = ep->selem.clink;
            tp = &(*tp)->selem.clink;
            }
         }
   dp2->dword = D_Set;
   BlkLoc(*dp2) = new;
   if (TooSparse(new))
      hshrink(dp2);
   return Success;
   }

/*
 * hmake - make a hash structure (Set or Table) with a given number of slots.
 *  hmake also ensures adequate storage for *nelem* elements, but does not
 *  allocate then.  If *nslots* is zero, a value appropriate for *nelem*
 *  elements is chosen.
 */
union block *hmake(tcode, nslots, nelem)
int tcode;
word nslots, nelem;
   {
   word seg, t, nbytes, blksize, elemsize;
   union block *blk;

   if (nslots == 0)
      nslots = (nelem + MaxHLoad - 1) / MaxHLoad;
   for (seg = t = 0; seg < (HSegs - 1) && (t += segsize[seg]) < nslots; seg++)
      ;
   nslots = ((word)HSlots) << seg;	/* ensure legal power of 2 */
   if (tcode == T_Table) {
      blksize = sizeof(struct b_table);
      elemsize = sizeof(struct b_telem);
      }
   else {	/* T_Set */
      blksize = sizeof(struct b_set);
      elemsize = sizeof(struct b_selem);
      }
   nbytes = blksize + (seg + 1) * (sizeof(struct b_slots) - (HSlots*WordSize)) +
      nslots * WordSize + nelem * elemsize;
   if (blkreq(nbytes) == Error)
      return NULL;				/* sorry, no memory */
   blk = alchash(tcode);
   for (; seg >= 0; seg--)
      blk->set.hdir[seg] = alcsegment(segsize[seg]);
   blk->set.mask = nslots - 1;
   return blk;
   }

/*
 * hchain - return a pointer to the word that points to the head of the hash
 *  chain for hash number hn in hashed structure s.
 */

/*
 * lookup table for log to base 2; must have powers of 2 through (HSegs-1)/2.
 */
static unsigned char log2[] = {
   0,1,2,2, 3,3,3,3, 4,4,4,4, 4,4,4,4, 5,5,5,5, 5,5,5,5, 5,5,5,5, 5,5,5,5,
   };

union block **hchain(pb, hn)
union block *pb;
register uword hn;
   {
   register struct b_set *ps;
   register word slotnum, segnum, segslot;

   ps = (struct b_set *)pb;
   slotnum = hn & ps->mask;
   if (slotnum >= HSlots * sizeof(log2))
      segnum = log2[slotnum >> (LogHSlots + HSegs/2)] + HSegs/2;
   else
      segnum = log2[slotnum >> LogHSlots];
   segslot = hn & (segsize[segnum] - 1);
   return &ps->hdir[segnum]->hslots[segslot];
   }

/*
 * hgener - agent function to generate the elements of a hashed structure.
 *
 *  Arg1 = set or table to enumerate
 *  Arg2 = integer value indicating desired action:
 *     0   generate set elements
 *     1   generate table keys
 *     2   generate table values
 *
 *  Carefully generate each element exactly once, even if the hash chains
 *  split while suspended.  Do this by recording the state of things at the
 *  time of the split and checking past history when starting to process a
 *  new chain.
 *
 *  Elements inserted or deleted while the generator is suspended may or
 *  may not be generated. 
 *
 *  We assume that no structure *shrinks* after its initial creation; they
 *  only *grow*.
 */

AgtDcl(hgener)
   {
   int i, segnum;
   word d, m, func, slotnum;
   uword hn;
   union block *ep;

   word tmask;		/* structure mask before suspension */
   word sgmask[HSegs];	/* mask being used when the segment was created */
   uword sghash[HSegs];	/* hashnum in process when the segment was created */

   for (i = 0; i < HSegs; i++)
      sghash[i] = sgmask[i] = 0;		/* set initial state */
   tmask = BlkLoc(Arg1)->table.mask;

   func = IntVal(Arg2);				/* save function code */
   Arg2.dword = D_Telem;			/* use Arg2 to tend address */

   for (segnum = 0; segnum < HSegs; segnum++) {
      if (BlkLoc(Arg1)->table.hdir[segnum] == NULL)
         break;
      for (slotnum = 0; slotnum < segsize[segnum]; slotnum++) {
         ep = BlkLoc(Arg1)->table.hdir[segnum]->hslots[slotnum];
         /*
          * Check to see if parts of this hash chain were already processed.
          *  This could happen if the elements were in a different chain,
          *  but a split occurred while we were suspended.
          */
         for (i = segnum; (m = sgmask[i]) != 0; i--) {
            d = (word)(m & slotnum) - (word)(m & sghash[i]);
            if (d < 0)			/* if all elements processed earlier */
               ep = NULL;		/* skip this slot */
            else if (d == 0) {
               /*
                * This chain was split from its parent while the parent was
                *  being processed.  Skip past elements already processed.
                */
               while (ep != NULL && ep->telem.hashnum <= sghash[i])
                  ep = ep->telem.clink;
               }
            }
         /*
          * Process the elements of the hash chain, in turn.
          */
         while (ep != NULL) {
            switch ((int)func) {
               case 0:  Arg0 = ep->selem.setmem;  break;
               case 1:  Arg0 = ep->telem.tref;    break;
               case 2:  Arg0 = ep->telem.tval;    break;
               }
            BlkLoc(Arg2) = ep;		/* save pointer, so it gets tended */
            Suspend;			/* suspend, returning current element */
            ep = BlkLoc(Arg2);		/* restore pointer */

            if (BlkLoc(Arg1)->table.mask != tmask &&
                  (ep->telem.clink == NULL ||
                  ep->telem.clink->telem.hashnum != ep->telem.hashnum)) {
               /*
                * The set or table's hash buckets split, once or more, while
                *  we were suspended.  (We notice this unless the next entry
                *  has same hash value as the current one.  In that case we
                *  ignore it for now and will pick it up on the next pass.)
                *
                * Make a note of the current state.
                */
               hn = ep->telem.hashnum;
               for (i = 1; i < HSegs; i++)
                  if ((((word)HSlots) << (i - 1)) > tmask) {
                     /*
                      * For the newly created segments only, save the mask and
                      *  hash number being processed at time of creation.
                      */
                     sgmask[i] = tmask;
                     sghash[i] = hn;
                  }
               tmask = BlkLoc(Arg1)->table.mask;
               /*
                * Find the next element in our original segment by starting
                *  from the beginning and skipping through the current hash
                *  number.  We can't just follow the link from the current
                *  element, because it may have moved to a new segment.
                */
               ep = BlkLoc(Arg1)->table.hdir[segnum]->hslots[slotnum];
               while (ep != NULL && ep->telem.hashnum <= hn)
                  ep = ep->telem.clink;
               }

            else {
               /*
                * Nothing happened during the suspend, or else if it did we're
                *  between items with identical hash numbers.  Just move on.
                */
               ep = ep->telem.clink;
               }
            }
         }
      }
   Fail;
   }

/*
 * hgrow - split a hashed structure (doubling the buckets) for faster access.
 */

novalue hgrow(dp)
dptr dp;
   {
   register union block **tp0, **tp1, *ep;
   register word newslots, slotnum, segnum;
   struct b_set *ps;
   struct b_slots *seg, *newseg;
   union block **curslot;

   ps = (struct b_set *)BlkLoc(*dp);
   if (ps->hdir[HSegs-1] != NULL)
      return;				/* can't split further */
   newslots = ps->mask + 1;
   if (blkreq(sizeof(struct b_slots) + (newslots - HSlots) * WordSize) == Error)
      return;				/* sorry, no memory */
   ps = (struct b_set *)BlkLoc(*dp);	/* refresh address -- may have moved */
   newseg = alcsegment(newslots);

   curslot = newseg->hslots;
   for (segnum = 0; (seg = ps->hdir[segnum]) != NULL; segnum++)
      for (slotnum = 0; slotnum < segsize[segnum]; slotnum++)  {
         tp0 = &seg->hslots[slotnum];	/* ptr to tail of old slot */
         tp1 = curslot++;		/* ptr to tail of new slot */
         for (ep = *tp0; ep != NULL; ep = ep->selem.clink) {
            if ((ep->selem.hashnum & newslots) == 0) {
               *tp0 = ep;		/* element does not move */
               tp0 = &ep->selem.clink;
               }
            else {
               *tp1 = ep;		/* element moves to new slot */
               tp1 = &ep->selem.clink;
               }
            }
         *tp0 = *tp1 = NULL;
         }
   ps->hdir[segnum] = newseg;
   ps->mask = (ps->mask << 1) | 1;
   }

/*
 * hshrink - combine buckets in a set or table that is too sparse.
 *
 *  Call this only for newly created structures.  Shrinking an active structure
 *  can wreak havoc on suspended generators.
 */
novalue hshrink(dp)
dptr dp;
   {
   register union block **tp, *ep0, *ep1;
   int topseg, curseg;
   word slotnum;
   struct b_set *ps;
   struct b_slots *seg;
   union block **uppslot;

   ps = (struct b_set *)BlkLoc(*dp);
   topseg = 0;
   for (topseg = 1; topseg < HSegs && ps->hdir[topseg] != NULL; topseg++)
      ;
   topseg--;
   while (TooSparse(ps)) {
      uppslot = ps->hdir[topseg]->hslots;
      ps->hdir[topseg--] = NULL;
      for (curseg = 0; (seg = ps->hdir[curseg]) != NULL; curseg++)
         for (slotnum = 0; slotnum < segsize[curseg]; slotnum++)  {
            tp = &seg->hslots[slotnum];		/* tail pointer */
            ep0 = seg->hslots[slotnum];		/* lower slot entry pointer */
            ep1 = *uppslot++;			/* upper slot entry pointer */
            while (ep0 != NULL && ep1 != NULL)
               if (ep0->selem.hashnum < ep1->selem.hashnum) {
                  *tp = ep0;
                  tp = &ep0->selem.clink;
                  ep0 = ep0->selem.clink;
                  }
               else {
                  *tp = ep1;
                  tp = &ep1->selem.clink;
                  ep1 = ep1->selem.clink;
                  }
            while (ep0 != NULL) {
               *tp = ep0;
               tp = &ep0->selem.clink;
               ep0 = ep0->selem.clink;
               }
            while (ep1 != NULL) {
               *tp = ep1;
               tp = &ep1->selem.clink;
               ep1 = ep1->selem.clink;
               }
            }
      ps->mask >>= 1;
      }
   }

/*
 * memb - sets res flag to 1 if x is a member of a set or table, or to 0 if not.
 *  Returns a pointer to the word which points to the element, or which
 *  would point to it if it were there.
 */

union block **memb(pb, x, hn, res)
union block *pb;
dptr x;
register uword hn;
int *res;				/* pointer to integer result flag */
   {
   struct b_set *ps;
   register union block **lp;
   register struct b_selem *pe;
   register uword eh;

   ps = (struct b_set *)pb;
   lp = hchain(pb, hn);
   /*
    * Look for x in the hash chain.
    */
   *res = 0;
   while ((pe = (struct b_selem *)*lp) != NULL) {
      eh = pe->hashnum;
      if (eh > hn)			/* too far - it isn't there */
         return lp;
      else if ((eh == hn) && (equiv(&pe->setmem, x)))  {
         *res = 1;
         return lp;
         }
      /*
       * We haven't reached the right hashnumber yet or
       *  the element isn't the right one so keep looking.
       */
      lp = &(pe->clink);
      }
   /*
    *  At end of chain - not there.
    */
   return lp;
   }
