WordSetU* HG_(newWordSetU) ( void* (*alloc_nofail)( HChar*, SizeT ),
                             HChar* cc,
                             void  (*dealloc)(void*),
                             Word  cacheSize )
{
   WordSetU* wsu;
   WordVec*  empty;

   wsu          = alloc_nofail( cc, sizeof(WordSetU) );
   VG_(memset)( wsu, 0, sizeof(WordSetU) );
   wsu->alloc   = alloc_nofail;
   wsu->cc      = cc;
   wsu->dealloc = dealloc;
   wsu->vec2ix  = VG_(newFM)( alloc_nofail, cc,
                              dealloc, cmp_WordVecs_for_FM );
   wsu->ix2vec_used = 0;
   wsu->ix2vec_size = 0;
   wsu->ix2vec      = NULL;
   wsu->ix2vec_free = NULL;
   WCache_INIT(wsu->cache_addTo,     cacheSize);
   WCache_INIT(wsu->cache_delFrom,   cacheSize);
   WCache_INIT(wsu->cache_intersect, cacheSize);
   WCache_INIT(wsu->cache_minus,     cacheSize);
   empty = new_WV_of_size( wsu, 0 );
   wsu->empty = add_or_dealloc_WordVec( wsu, empty );

   return wsu;
}
WordSet HG_(delFromWS) ( WordSetU* wsu, WordSet ws, Word w )
{
   Int      i, j, k;
   WordVec* wv_new;
   WordSet  result = (WordSet)(-1); /* bogus */
   WordVec* wv = do_ix2vec( wsu, ws );

   wsu->n_del++;

   /* special case empty set */
   if (wv->size == 0) {
      tl_assert(ws == wsu->empty);
      return ws;
   }

   WCache_LOOKUP_AND_RETURN(WordSet, wsu->cache_delFrom, ws, w);
   wsu->n_del_uncached++;

   /* If not already present, this is a no-op. */
   for (i = 0; i < wv->size; i++) {
      if (wv->words[i] == w)
         break;
   }
   if (i == wv->size) {
      result = ws;
      goto out;
   }
   /* So w is present in ws, and the new set will be one element
      smaller. */
   tl_assert(i >= 0 && i < wv->size);
   tl_assert(wv->size > 0);

   wv_new = new_WV_of_size( wsu, wv->size - 1 );
   j = k = 0;
   for (; j < wv->size; j++) {
      if (j == i)
         continue;
      wv_new->words[k++] = wv->words[j];
   }
   tl_assert(k == wv_new->size);

   result = add_or_dealloc_WordVec( wsu, wv_new );
   if (wv->size == 1) {
      tl_assert(result == wsu->empty);
   }

  out:
   WCache_UPDATE(wsu->cache_delFrom, ws, w, result);
   return result;
}
WordSet HG_(delFromWS) ( WordSetU* wsu, WordSet ws, UWord w )
{
   UWord    i, j, k;
   WordVec* wv_new;
   WordSet  result = (WordSet)(-1); 
   WordVec* wv = do_ix2vec( wsu, ws );

   wsu->n_del++;

   
   if (wv->size == 0) {
      tl_assert(ws == wsu->empty);
      return ws;
   }

   WCache_LOOKUP_AND_RETURN(WordSet, wsu->cache_delFrom, ws, w);
   wsu->n_del_uncached++;

   
   for (i = 0; i < wv->size; i++) {
      if (wv->words[i] == w)
         break;
   }
   if (i == wv->size) {
      result = ws;
      goto out;
   }
   tl_assert(i >= 0 && i < wv->size);
   tl_assert(wv->size > 0);

   wv_new = new_WV_of_size( wsu, wv->size - 1 );
   j = k = 0;
   for (; j < wv->size; j++) {
      if (j == i)
         continue;
      wv_new->words[k++] = wv->words[j];
   }
   tl_assert(k == wv_new->size);

   result = add_or_dealloc_WordVec( wsu, wv_new );
   if (wv->size == 1) {
      tl_assert(result == wsu->empty);
   }

  out:
   WCache_UPDATE(wsu->cache_delFrom, ws, w, result);
   return result;
}
WordSet HG_(doubletonWS) ( WordSetU* wsu, Word w1, Word w2 )
{
   WordVec* wv;
   wsu->n_doubleton++;
   if (w1 == w2) {
      wv = new_WV_of_size(wsu, 1);
      wv->words[0] = w1;
   }
   else if (w1 < w2) {
      wv = new_WV_of_size(wsu, 2);
      wv->words[0] = w1;
      wv->words[1] = w2;
   }
   else {
      tl_assert(w1 > w2);
      wv = new_WV_of_size(wsu, 2);
      wv->words[0] = w2;
      wv->words[1] = w1;
   }
   return add_or_dealloc_WordVec( wsu, wv );
}
WordSet HG_(addToWS) ( WordSetU* wsu, WordSet ws, Word w )
{
   Int      k, j;
   WordVec* wv_new;
   WordVec* wv;
   WordSet  result = (WordSet)(-1); /* bogus */

   wsu->n_add++;
   WCache_LOOKUP_AND_RETURN(WordSet, wsu->cache_addTo, ws, w);
   wsu->n_add_uncached++;

   /* If already present, this is a no-op. */
   wv = do_ix2vec( wsu, ws );
   for (k = 0; k < wv->size; k++) {
      if (wv->words[k] == w) {
         result = ws;
         goto out;
      }
   }
   /* Ok, not present.  Build a new one ... */
   wv_new = new_WV_of_size( wsu, wv->size + 1 );
   k = j = 0;
   for (; k < wv->size && wv->words[k] < w; k++) {
      wv_new->words[j++] = wv->words[k];
   }
   wv_new->words[j++] = w;
   for (; k < wv->size; k++) {
      tl_assert(wv->words[k] > w);
      wv_new->words[j++] = wv->words[k];
   }
   tl_assert(j == wv_new->size);

   /* Find any existing copy, or add the new one. */
   result = add_or_dealloc_WordVec( wsu, wv_new );
   tl_assert(result != (WordSet)(-1));

  out:
   WCache_UPDATE(wsu->cache_addTo, ws, w, result);
   return result;
}
WordSet HG_(addToWS) ( WordSetU* wsu, WordSet ws, UWord w )
{
   UWord    k, j;
   WordVec* wv_new;
   WordVec* wv;
   WordSet  result = (WordSet)(-1); 

   wsu->n_add++;
   WCache_LOOKUP_AND_RETURN(WordSet, wsu->cache_addTo, ws, w);
   wsu->n_add_uncached++;

   
   wv = do_ix2vec( wsu, ws );
   for (k = 0; k < wv->size; k++) {
      if (wv->words[k] == w) {
         result = ws;
         goto out;
      }
   }
   
   wv_new = new_WV_of_size( wsu, wv->size + 1 );
   k = j = 0;
   for (; k < wv->size && wv->words[k] < w; k++) {
      wv_new->words[j++] = wv->words[k];
   }
   wv_new->words[j++] = w;
   for (; k < wv->size; k++) {
      tl_assert(wv->words[k] > w);
      wv_new->words[j++] = wv->words[k];
   }
   tl_assert(j == wv_new->size);

   
   result = add_or_dealloc_WordVec( wsu, wv_new );
   tl_assert(result != (WordSet)(-1));

  out:
   WCache_UPDATE(wsu->cache_addTo, ws, w, result);
   return result;
}
WordSet HG_(minusWS) ( WordSetU* wsu, WordSet ws1, WordSet ws2 )
{
   Int      i1, i2, k, sz;
   WordSet  ws_new = (WordSet)(-1); /* bogus */
   WordVec* wv_new;
   WordVec* wv1;
   WordVec* wv2;
   
   wsu->n_minus++;
   WCache_LOOKUP_AND_RETURN(WordSet, wsu->cache_minus, ws1, ws2);
   wsu->n_minus_uncached++;

   wv1 = do_ix2vec( wsu, ws1 );
   wv2 = do_ix2vec( wsu, ws2 );
   sz = 0;
   i1 = i2 = 0;
   while (1) {
      if (i1 >= wv1->size || i2 >= wv2->size)
         break;
      if (wv1->words[i1] < wv2->words[i2]) {
         sz++;
         i1++;
      } else 
      if (wv1->words[i1] > wv2->words[i2]) {
         i2++;
      } else {
         i1++;
         i2++;
      }
   }
   tl_assert(i1 <= wv1->size);
   tl_assert(i2 <= wv2->size);
   tl_assert(i1 == wv1->size || i2 == wv2->size);
   if (i2 == wv2->size && i1 < wv1->size) {
      sz += (wv1->size - i1);
   }

   wv_new = new_WV_of_size( wsu, sz );
   k = 0;

   i1 = i2 = 0;
   while (1) {
      if (i1 >= wv1->size || i2 >= wv2->size)
         break;
      if (wv1->words[i1] < wv2->words[i2]) {
         wv_new->words[k++] = wv1->words[i1];
         i1++;
      } else 
      if (wv1->words[i1] > wv2->words[i2]) {
         i2++;
      } else {
         i1++;
         i2++;
      }
   }
   tl_assert(i1 <= wv1->size);
   tl_assert(i2 <= wv2->size);
   tl_assert(i1 == wv1->size || i2 == wv2->size);
   if (i2 == wv2->size && i1 < wv1->size) {
      while (i1 < wv1->size)
         wv_new->words[k++] = wv1->words[i1++];
   }

   tl_assert(k == sz);

   ws_new = add_or_dealloc_WordVec( wsu, wv_new );
   if (sz == 0) {
      tl_assert(ws_new == wsu->empty);
   }

   tl_assert(ws_new != (WordSet)(-1));
   WCache_UPDATE(wsu->cache_minus, ws1, ws2, ws_new);

   return ws_new;
}
WordSet HG_(intersectWS) ( WordSetU* wsu, WordSet ws1, WordSet ws2 )
{
   Int      i1, i2, k, sz;
   WordSet  ws_new = (WordSet)(-1); /* bogus */
   WordVec* wv_new;
   WordVec* wv1; 
   WordVec* wv2; 

   wsu->n_intersect++;

   /* Deal with an obvious case fast. */
   if (ws1 == ws2)
      return ws1;

   /* Since intersect(x,y) == intersect(y,x), convert both variants to
      the same query.  This reduces the number of variants the cache
      has to deal with. */
   if (ws1 > ws2) {
      WordSet wst = ws1; ws1 = ws2; ws2 = wst;
   }

   WCache_LOOKUP_AND_RETURN(WordSet, wsu->cache_intersect, ws1, ws2);
   wsu->n_intersect_uncached++;

   wv1 = do_ix2vec( wsu, ws1 );
   wv2 = do_ix2vec( wsu, ws2 );
   sz = 0;
   i1 = i2 = 0;
   while (1) {
      if (i1 >= wv1->size || i2 >= wv2->size)
         break;
      if (wv1->words[i1] < wv2->words[i2]) {
         i1++;
      } else 
      if (wv1->words[i1] > wv2->words[i2]) {
         i2++;
      } else {
         sz++;
         i1++;
         i2++;
      }
   }
   tl_assert(i1 <= wv1->size);
   tl_assert(i2 <= wv2->size);
   tl_assert(i1 == wv1->size || i2 == wv2->size);

   wv_new = new_WV_of_size( wsu, sz );
   k = 0;

   i1 = i2 = 0;
   while (1) {
      if (i1 >= wv1->size || i2 >= wv2->size)
         break;
      if (wv1->words[i1] < wv2->words[i2]) {
         i1++;
      } else 
      if (wv1->words[i1] > wv2->words[i2]) {
         i2++;
      } else {
         wv_new->words[k++] = wv1->words[i1];
         i1++;
         i2++;
      }
   }
   tl_assert(i1 <= wv1->size);
   tl_assert(i2 <= wv2->size);
   tl_assert(i1 == wv1->size || i2 == wv2->size);

   tl_assert(k == sz);

   ws_new = add_or_dealloc_WordVec( wsu, wv_new );
   if (sz == 0) {
      tl_assert(ws_new == wsu->empty);
   }

   tl_assert(ws_new != (WordSet)(-1));
   WCache_UPDATE(wsu->cache_intersect, ws1, ws2, ws_new);

   return ws_new;
}
WordSet HG_(unionWS) ( WordSetU* wsu, WordSet ws1, WordSet ws2 )
{
   Int      i1, i2, k, sz;
   WordVec* wv_new;
   WordVec* wv1 = do_ix2vec( wsu, ws1 );
   WordVec* wv2 = do_ix2vec( wsu, ws2 );
   wsu->n_union++;
   sz = 0;
   i1 = i2 = 0;
   while (1) {
      if (i1 >= wv1->size || i2 >= wv2->size)
         break;
      sz++;
      if (wv1->words[i1] < wv2->words[i2]) {
         i1++;
      } else 
      if (wv1->words[i1] > wv2->words[i2]) {
         i2++;
      } else {
         i1++;
         i2++;
      }
   }
   tl_assert(i1 <= wv1->size);
   tl_assert(i2 <= wv2->size);
   tl_assert(i1 == wv1->size || i2 == wv2->size);
   if (i1 == wv1->size && i2 < wv2->size) {
      sz += (wv2->size - i2);
   }
   if (i2 == wv2->size && i1 < wv1->size) {
      sz += (wv1->size - i1);
   }

   wv_new = new_WV_of_size( wsu, sz );
   k = 0;

   i1 = i2 = 0;
   while (1) {
      if (i1 >= wv1->size || i2 >= wv2->size)
         break;
      if (wv1->words[i1] < wv2->words[i2]) {
         wv_new->words[k++] = wv1->words[i1];
         i1++;
      } else 
      if (wv1->words[i1] > wv2->words[i2]) {
         wv_new->words[k++] = wv2->words[i2];
         i2++;
      } else {
         wv_new->words[k++] = wv1->words[i1];
         i1++;
         i2++;
      }
   }
   tl_assert(i1 <= wv1->size);
   tl_assert(i2 <= wv2->size);
   tl_assert(i1 == wv1->size || i2 == wv2->size);
   if (i1 == wv1->size && i2 < wv2->size) {
      while (i2 < wv2->size)
         wv_new->words[k++] = wv2->words[i2++];
   }
   if (i2 == wv2->size && i1 < wv1->size) {
      while (i1 < wv1->size)
         wv_new->words[k++] = wv1->words[i1++];
   }

   tl_assert(k == sz);

   return add_or_dealloc_WordVec( wsu, wv_new );
}
WordSet HG_(intersectWS) ( WordSetU* wsu, WordSet ws1, WordSet ws2 )
{
   UWord    i1, i2, k, sz;
   WordSet  ws_new = (WordSet)(-1); 
   WordVec* wv_new;
   WordVec* wv1; 
   WordVec* wv2; 

   wsu->n_intersect++;

   
   if (ws1 == ws2)
      return ws1;

   if (ws1 > ws2) {
      WordSet wst = ws1; ws1 = ws2; ws2 = wst;
   }

   WCache_LOOKUP_AND_RETURN(WordSet, wsu->cache_intersect, ws1, ws2);
   wsu->n_intersect_uncached++;

   wv1 = do_ix2vec( wsu, ws1 );
   wv2 = do_ix2vec( wsu, ws2 );
   sz = 0;
   i1 = i2 = 0;
   while (1) {
      if (i1 >= wv1->size || i2 >= wv2->size)
         break;
      if (wv1->words[i1] < wv2->words[i2]) {
         i1++;
      } else 
      if (wv1->words[i1] > wv2->words[i2]) {
         i2++;
      } else {
         sz++;
         i1++;
         i2++;
      }
   }
   tl_assert(i1 <= wv1->size);
   tl_assert(i2 <= wv2->size);
   tl_assert(i1 == wv1->size || i2 == wv2->size);

   wv_new = new_WV_of_size( wsu, sz );
   k = 0;

   i1 = i2 = 0;
   while (1) {
      if (i1 >= wv1->size || i2 >= wv2->size)
         break;
      if (wv1->words[i1] < wv2->words[i2]) {
         i1++;
      } else 
      if (wv1->words[i1] > wv2->words[i2]) {
         i2++;
      } else {
         wv_new->words[k++] = wv1->words[i1];
         i1++;
         i2++;
      }
   }
   tl_assert(i1 <= wv1->size);
   tl_assert(i2 <= wv2->size);
   tl_assert(i1 == wv1->size || i2 == wv2->size);

   tl_assert(k == sz);

   ws_new = add_or_dealloc_WordVec( wsu, wv_new );
   if (sz == 0) {
      tl_assert(ws_new == wsu->empty);
   }

   tl_assert(ws_new != (WordSet)(-1));
   WCache_UPDATE(wsu->cache_intersect, ws1, ws2, ws_new);

   return ws_new;
}