+/* Internal helper, to move entries from SRC to DST. Both tables must
+ share the same free entry list. If SAFE, only move overflow
+ entries, saving bucket heads for later, so that no allocations will
+ occur. Return false if the free entry list is exhausted and an
+ allocation fails. */
+
+static bool
+transfer_entries (Hash_table *dst, Hash_table *src, bool safe)
+{
+ struct hash_entry *bucket;
+ struct hash_entry *cursor;
+ struct hash_entry *next;
+ for (bucket = src->bucket; bucket < src->bucket_limit; bucket++)
+ if (bucket->data)
+ {
+ void *data;
+ struct hash_entry *new_bucket;
+
+ /* Within each bucket, transfer overflow entries first and
+ then the bucket head, to minimize memory pressure. After
+ all, the only time we might allocate is when moving the
+ bucket head, but moving overflow entries first may create
+ free entries that can be recycled by the time we finally
+ get to the bucket head. */
+ for (cursor = bucket->next; cursor; cursor = next)
+ {
+ data = cursor->data;
+ new_bucket = (dst->bucket + dst->hasher (data, dst->n_buckets));
+
+ if (! (new_bucket < dst->bucket_limit))
+ abort ();
+
+ next = cursor->next;
+
+ if (new_bucket->data)
+ {
+ /* Merely relink an existing entry, when moving from a
+ bucket overflow into a bucket overflow. */
+ cursor->next = new_bucket->next;
+ new_bucket->next = cursor;
+ }
+ else
+ {
+ /* Free an existing entry, when moving from a bucket
+ overflow into a bucket header. */
+ new_bucket->data = data;
+ dst->n_buckets_used++;
+ free_entry (dst, cursor);
+ }
+ }
+ /* Now move the bucket head. Be sure that if we fail due to
+ allocation failure that the src table is in a consistent
+ state. */
+ data = bucket->data;
+ bucket->next = NULL;
+ if (safe)
+ continue;
+ new_bucket = (dst->bucket + dst->hasher (data, dst->n_buckets));
+
+ if (! (new_bucket < dst->bucket_limit))
+ abort ();
+
+ if (new_bucket->data)
+ {
+ /* Allocate or recycle an entry, when moving from a bucket
+ header into a bucket overflow. */
+ struct hash_entry *new_entry = allocate_entry (dst);
+
+ if (new_entry == NULL)
+ return false;
+
+ new_entry->data = data;
+ new_entry->next = new_bucket->next;
+ new_bucket->next = new_entry;
+ }
+ else
+ {
+ /* Move from one bucket header to another. */
+ new_bucket->data = data;
+ dst->n_buckets_used++;
+ }
+ bucket->data = NULL;
+ src->n_buckets_used--;
+ }
+ return true;
+}
+