* This software is part of the SBCL system. See the README file for
* more information.
*
* This software is derived from the CMU CL system, which was
* written at Carnegie Mellon University and released into the
* public domain. The software is in the public domain and is
* provided with absolutely no warranty. See the COPYING and CREDITS
* files for more information.
*/
* Our implementation of the hopscotch algorithm described in
* http://people.csail.mit.edu/shanir/publications/disc2008_submission_98.pdf
* which is extremely simple variation on linear probing
* that provides a guaranteed bound on number of probes.
*/
#include "os.h"
#include "gc-internal.h"
#include "hopscotch.h"
#include <stdint.h>
#include <stdio.h>
#ifdef LISP_FEATURE_WIN32
extern int ffs(int);
#else
says strings.h */
#include <strings.h>
#endif
#include "genesis/vector.h"
#include "murmur_hash.h"
#define hopscotch_allocate(nbytes) os_allocate(nbytes)
#define hopscotch_deallocate(addr,length) os_deallocate(addr, length)
typedef struct hopscotch_table* tableptr;
void hopscotch_integrity_check(tableptr,char*,int);
#define table_size(table) (1+(table).mask)
#define INTEGRITY_CHECK(when) {}
static inline uint32_t hash(tableptr ht, lispobj x) {
return ht->hash ? ht->hash(x) :
#ifdef LISP_FEATURE_GENCGC
(x >> GENCGC_CARD_SHIFT) ^ (x >> (1+WORD_SHIFT));
#else
(x >> (1+WORD_SHIFT));
#endif
}
uint32_t hopscotch_hmix(uword_t key)
{
uint32_t h = key >> N_LOWTAG_BITS;
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}
static inline void set_hop_bit(tableptr ht, unsigned index, int bit)
{
unsigned mask = 1U<<bit;
ht->hops[index] |= mask;
}
static inline void set_hop_mask(tableptr ht, unsigned index, unsigned bits)
{
ht->hops[index] = bits;
}
static inline unsigned get_hop_mask(tableptr ht, unsigned index)
{
return ht->hops[index];
}
static void set_val(tableptr ht, int index, sword_t val)
{
switch(ht->value_size) {
#ifdef LISP_FEATURE_64_BIT
case 8: ht->values[index] = val; break;
#endif
case 4: ((int32_t*)ht->values)[index] = val; break;
case 2: ((int16_t*)ht->values)[index] = val; break;
case 1: ((int8_t* )ht->values)[index] = val; break;
}
}
static sword_t get_val(tableptr ht, int index)
{
switch(ht->value_size) {
#ifdef LISP_FEATURE_64_BIT
case 8: return ht->values[index];
#endif
case 4: return ((int32_t*)ht->values)[index];
case 2: return ((int16_t*)ht->values)[index];
case 1: return ((int8_t *)ht->values)[index];
}
return index + 1;
}
#ifdef LISP_FEATURE_64_BIT
static sword_t get_val8(tableptr ht, int index) { return ht->values[index]; }
#endif
static sword_t get_val4(tableptr ht, int index) { return ((int32_t*)ht->values)[index]; }
static sword_t get_val2(tableptr ht, int index) { return ((int16_t*)ht->values)[index]; }
static sword_t get_val1(tableptr ht, int index) { return ((int8_t *)ht->values)[index]; }
#ifdef LISP_FEATURE_SB_SAFEPOINT
#include <stdlib.h>
#define cached_allocate(n) calloc(1,n)
#define cached_deallocate(ptr,size) free(ptr)
void hopscotch_init() { }
#else
static int hh_allocation_granularity = 4096;
#define ALLOCATION_OVERHEAD (2*sizeof(unsigned int))
#define usable_size(x) ((unsigned int*)x)[-1]
#define N_CACHED_ALLOCS 2
char* cached_alloc[N_CACHED_ALLOCS];
void hopscotch_init()
{
int n_bytes_per_slice = os_reported_page_size;
int n_bytes_total = N_CACHED_ALLOCS * n_bytes_per_slice;
char* mem = hopscotch_allocate(n_bytes_total);
gc_assert(mem);
cached_alloc[0] = mem + ALLOCATION_OVERHEAD;
cached_alloc[1] = cached_alloc[0] + n_bytes_per_slice;
usable_size(cached_alloc[0]) = n_bytes_per_slice - ALLOCATION_OVERHEAD;
usable_size(cached_alloc[1]) = n_bytes_per_slice - ALLOCATION_OVERHEAD;
}
* This is not a general-purpose thing - it's only intended to keep
* one or perhaps two hopscotch hash tables around during GC,
* for pinned objects, and maybe something else.
* As such, no attempt is made to minimize storage use,
* and if used more generally, would badly suffer from fragmentation.
*/
static char* cached_allocate(os_vm_size_t nbytes)
{
if (cached_alloc[0] && usable_size(cached_alloc[0]) >= nbytes) {
char* result = cached_alloc[0];
cached_alloc[0] = 0;
return result;
}
if (cached_alloc[1] && usable_size(cached_alloc[1]) >= nbytes) {
char* result = cached_alloc[1];
cached_alloc[1] = 0;
return result;
}
nbytes = ALIGN_UP(nbytes, hh_allocation_granularity);
char* result = hopscotch_allocate(nbytes);
gc_assert(result);
result += ALLOCATION_OVERHEAD;
usable_size(result) = nbytes - ALLOCATION_OVERHEAD;
return result;
}
* Though the memory size is recorded in the header of the memory block,
* the allocator doesn't know how many bytes were touched by the requestor,
* which is why the length is specified again.
* If returning it to the OS and not the cache, then don't bother 0-filling.
*/
static void cached_deallocate(char* mem, uword_t zero_fill_length)
{
int line = 0;
if (!cached_alloc[0]) {
} else if (!cached_alloc[1])
line = 1;
else {
int this_size = usable_size(mem);
int cached_size0 = usable_size(cached_alloc[0]);
int cached_size1 = usable_size(cached_alloc[1]);
if (!(this_size > cached_size0 || this_size > cached_size1)) {
hopscotch_deallocate(mem - ALLOCATION_OVERHEAD,
usable_size(mem) + ALLOCATION_OVERHEAD);
return;
}
if (cached_size1 < cached_size0)
line = 1;
hopscotch_deallocate(cached_alloc[line] - ALLOCATION_OVERHEAD,
usable_size(cached_alloc[line]) + ALLOCATION_OVERHEAD);
}
memset(mem, 0, zero_fill_length);
cached_alloc[line] = mem;
}
#endif
* 'valuesp' makes a hash-map if true; a hash-set if false.
* Hop range will be selected automatically if specified as 0.
*/
static void hopscotch_realloc(tableptr ht, int size, char hop_range)
{
if (hop_range == 0) {
if (size <= 1023) hop_range = 8;
else if (size <= 16384) hop_range = 16;
else hop_range = 32;
}
int n_keys = size + (hop_range - 1);
uword_t storage_size = (sizeof (uword_t) + ht->value_size) * n_keys
+ sizeof (int) * size;
if (ht->keys) {
#ifndef LISP_FEATURE_SB_SAFEPOINT
gc_assert(usable_size(ht->keys) >= storage_size);
#endif
} else
ht->keys = (uword_t*)cached_allocate(storage_size);
ht->mem_size = storage_size;
ht->mask = size - 1;
ht->hop_range = hop_range;
ht->threshold = n_keys * 13 / 16;
ht->hops = (unsigned*)(ht->keys + n_keys);
ht->values = ht->value_size ? (sword_t*)(ht->hops + size) : 0;
}
uword_t sxhash_simple_string(struct vector* string)
{
#ifdef SIMPLE_CHARACTER_STRING_WIDETAG
unsigned int* char_string = (unsigned int*)(string->data);
#endif
unsigned char* base_string = (unsigned char*)(string->data);
sword_t len = vector_len(string);
uword_t result = 0;
sword_t i;
switch (widetag_of(&string->header)) {
#define MIX(ch) {result += ch; result += result<<10; result ^= (result>>6);}
#ifdef SIMPLE_CHARACTER_STRING_WIDETAG
case SIMPLE_CHARACTER_STRING_WIDETAG:
for(i=0;i<len;++i) MIX(char_string[i])
break;
#endif
case SIMPLE_BASE_STRING_WIDETAG:
for(i=0;i<len;++i) MIX(base_string[i])
break;
}
result += result << 3;
result ^= result >> 11;
result ^= result << 15;
result &= (~(uword_t)0) >> (1+N_FIXNUM_TAG_BITS);
return result;
}
static uword_t vector_sxhash(lispobj* object)
{
lispobj header = *object;
int widetag = header_widetag(header);
sword_t nwords = sizetab[widetag](object);
return gpr_murmur_hash3(object+1,
(nwords-1) * N_WORD_BYTES,
widetag >= SIMPLE_ARRAY_WIDETAG
? hopscotch_hmix(widetag)
: hopscotch_hmix(header & 0xFFFFFFFF));
}
* This is unlike EQUAL because it works on any specialized vector,
* and unlike EQUALP because it compares strings case-sensitively.
* Note: this works on most numbers too, not just vectors.
*/
static boolean vector_eql(uword_t arg1, uword_t arg2)
{
if (arg1 == arg2) return 1;
lispobj* obj1 = (lispobj*)arg1;
lispobj* obj2 = (lispobj*)arg2;
lispobj header1 = *obj1;
if (((header1 ^ *obj2) & WIDETAG_MASK) != 0)
return 0;
int widetag1 = header_widetag(header1);
sword_t nwords = sizetab[widetag1](obj1);
if (widetag1 <= COMPLEX_DOUBLE_FLOAT_WIDETAG)
return header1 == *obj2
&& !memcmp(obj1 + 1, obj2 + 1, (nwords-1) << WORD_SHIFT);
struct vector *v1 = (void*)obj1;
struct vector *v2 = (void*)obj2;
return (vector_len(v1) == vector_len(v2))
&& !memcmp(v1->data, v2->data,
(nwords-2) << WORD_SHIFT);
}
* and allocating storage.
*/
void hopscotch_create(tableptr ht, int hashfun,
int bytes_per_value, int size, char hop_range)
{
gc_assert((size & (size-1)) == 0);
ht->hashfun = hashfun;
switch (hashfun) {
case HOPSCOTCH_HASH_FUN_DEFAULT:
ht->compare = 0; ht->hash = 0; break;
case HOPSCOTCH_HASH_FUN_MIX:
ht->compare = 0; ht->hash = hopscotch_hmix; break;
case HOPSCOTCH_STRING_HASH:
ht->compare = vector_eql;
ht->hash = (uint32_t(*)(uword_t))sxhash_simple_string;
break;
case HOPSCOTCH_VECTOR_HASH:
ht->compare = vector_eql;
ht->hash = (uint32_t(*)(uword_t))vector_sxhash;
break;
default: lose("Bad hash function");
}
switch (bytes_per_value) {
case 0: ht->get_value = 0; break;
case 1: ht->get_value = get_val1; break;
case 2: ht->get_value = get_val2; break;
case 4: ht->get_value = get_val4; break;
#ifdef LISP_FEATURE_64_BIT
case 8: ht->get_value = get_val8; break;
#endif
default: lose("Bad value size");
}
ht->count = 0;
ht->rehashing = 0;
ht->resized = 0;
ht->prev_size = size;
ht->hit .n_seeks = ht->hit .n_probes = 0;
ht->miss.n_seeks = ht->miss.n_probes = 0;
ht->value_size = bytes_per_value;
ht->keys = 0;
hopscotch_realloc(ht, size, hop_range);
}
#define need_to_zero(h) (h->count!=0)
void hopscotch_destroy(tableptr ht)
{
if (ht->mem_size) {
cached_deallocate((char*)ht->keys, need_to_zero(ht) ? ht->mem_size : 0);
ht->keys = 0;
ht->hops = 0;
ht->values = 0;
}
}
void hopscotch_reset(tableptr ht)
{
if (need_to_zero(ht)) {
memset(ht->keys, 0, ht->mem_size);
ht->count = 0;
}
int size = table_size(*ht);
#if 0
fprintf(stderr, "hh reset: size=%d prev=%d upsized=%d\n",
size, ht->prev_size, ht->resized);
#endif
if (size > (ht->prev_size << 1)
|| (size == ht->prev_size && !ht->resized && size > 8))
hopscotch_realloc(ht, size >> 1, 0);
ht->prev_size = size;
ht->resized = 0;
if (ht->hashfun == HOPSCOTCH_HASH_FUN_DEFAULT)
ht->hash = 0;
INTEGRITY_CHECK("after reset");
}
tableptr hopscotch_resize_up(tableptr ht)
{
int size = ht->mask + 1;
int old_max_index = hopscotch_max_key_index(*ht);
struct hopscotch_table copy;
#if 0
fprintf(stderr, "resize up: ct=%d cap=%d hop=%d LF=%f\n",
ht->count, 1+old_max_index, ht->hop_range,
(float)ht->count/(1+old_max_index));
#endif
INTEGRITY_CHECK("before resize");
int i;
do {
size *= 2;
hopscotch_create(©, ht->hashfun, ht->value_size, size, 0);
if (copy.hop_range > 16 && copy.hash == 0)
copy.hash = hopscotch_hmix;
copy.rehashing = 1;
if (ht->values) {
for(i=old_max_index ; i >= 0 ; --i)
if (ht->keys[i])
if (!hopscotch_insert(©, ht->keys[i], get_val(ht,i)))
break;
} else {
for(i=old_max_index ; i >= 0 ; --i)
if (ht->keys[i])
if (!hopscotch_insert(©, ht->keys[i], 1)) {
#if 0
fprintf(stderr, "resize failed with new size %d, hop_range %d\n",
size, copy.hop_range);
#endif
break;
}
}
} while (i >= 0 && (hopscotch_destroy(©), 1));
cached_deallocate((char*)ht->keys, ht->mem_size);
ht->hash = copy.hash;
ht->mem_size = copy.mem_size;
ht->mask = copy.mask;
ht->hop_range = copy.hop_range;
ht->threshold = copy.threshold;
ht->keys = copy.keys;
ht->hops = copy.hops;
ht->values = copy.values;
ht->resized = 1;
INTEGRITY_CHECK("after resize");
return ht;
}
void hopscotch_log_stats(tableptr __attribute__((unused)) ht,
char __attribute__((unused)) *tablename)
{
#ifdef HOPSCOTCH_INSTRUMENT
static FILE *hh_logfile;
if (!hh_logfile)
hh_logfile = fopen("hash-stats.txt","a");
fprintf(hh_logfile,
"[hopscotch]: %s ct=%5d cap=%5d LF=%f seek=%5d+%5d probe/seek=%f+%f (hit+miss)\n",
tablename, ht->count,
(ht->mask + ht->hop_range),
(float)ht->count / (ht->mask + ht->hop_range),
ht->hit.n_seeks, ht->miss.n_seeks,
ht->hit.n_seeks>0 ? (float)ht->hit.n_probes / ht->hit.n_seeks : 0.0,
ht->miss.n_seeks>0 ? (float)ht->miss.n_probes / ht->miss.n_seeks : 0.0);
fflush(hh_logfile);
ht->hit.n_seeks = ht->hit.n_probes = 0;
ht->miss.n_seeks = ht->miss.n_probes = 0;
#endif
}
* This does not do the right thing for n = 0, but that's fine!
* (Shifting an unsigned 32-bit integer rightward by 32 is not defined.
* 32-bit x86 masks the shift amount to 5 bits, so you get 0 shift)
*/
static inline unsigned int bitmask_of_width(int n) {
return (0xFFFFFFFFU >> (32 - n));
}
#define put_pair(i,k,v) ht->keys[i] = k; if(ht->values) set_val(ht, i, v)
* Key MUST NOT be present in the table */
int hopscotch_insert(tableptr ht, uword_t key, sword_t val)
{
gc_dcheck(key);
int desired_index = hash(ht, key) & ht->mask;
if (ht->keys[desired_index] == 0) {
put_pair(desired_index, key, val);
set_hop_bit(ht, desired_index, 0);
return ++ht->count;
}
if (!ht->rehashing && ht->count >= ht->threshold)
return hopscotch_insert(hopscotch_resize_up(ht), key, val);
int limit = hopscotch_max_key_index(*ht);
int free_index = desired_index;
int displacement;
while (ht->keys[++free_index] != 0)
if (free_index == limit)
return ht->rehashing ? 0 :
hopscotch_insert(hopscotch_resize_up(ht), key, val);
retry:
if ((displacement = free_index - desired_index) < ht->hop_range) {
put_pair(free_index, key, val);
set_hop_bit(ht, desired_index, displacement);
return ++ht->count;
}
int logical_bin = free_index - (ht->hop_range - 1);
limit = free_index - 1;
if (limit >= (int)ht->mask)
limit = ht->mask;
for ( ; logical_bin <= limit ; ++logical_bin ) {
displacement = free_index - logical_bin;
unsigned bits = get_hop_mask(ht, logical_bin);
unsigned masked_bits = bits & bitmask_of_width(displacement);
if (masked_bits) {
int victim = ffs(masked_bits) - 1;
int physical_elt = logical_bin + victim;
put_pair(free_index, ht->keys[physical_elt], get_val(ht, physical_elt));
put_pair(physical_elt, 0, 0);
set_hop_mask(ht, logical_bin, bits ^ (1U<<displacement | 1U<<victim));
free_index = physical_elt;
goto retry;
}
}
return ht->rehashing ? 0 : hopscotch_insert(hopscotch_resize_up(ht), key, val);
#undef put_pair
}
#ifdef HOPSCOTCH_INSTRUMENT
#define probe(mask,i,action) ++probes; if (ht->keys[i] == key) { \
++ht->hit.n_seeks; ht->hit.n_probes += probes; action; }
#define tally_miss(table,n) ++table->miss.n_seeks; table->miss.n_probes += n
#else
#define probe(mask,i,action) if (ht->keys[i] == key) action;
#define tally_miss(table,n)
#endif
int hopscotch_containsp(tableptr ht, uword_t key)
{
unsigned long index = hash(ht, key) & ht->mask;
unsigned bits = get_hop_mask(ht, index);
int __attribute__((unused)) probes = 0;
if (ht->compare) {
for ( ; bits ; bits >>= 1, ++index )
if ((bits & 1) && ht->compare(ht->keys[index], key))
return 1;
return 0;
}
if (bits & 0xff) {
probe((1<<0), index+0, return 1);
probe((1<<1), index+1, return 1);
probe((1<<2), index+2, return 1);
probe((1<<3), index+3, return 1);
probe((1<<4), index+4, return 1);
probe((1<<5), index+5, return 1);
probe((1<<6), index+6, return 1);
probe((1<<7), index+7, return 1);
}
while ((bits >>= 8) != 0) {
index += 8;
if (bits & 0xff) {
probe((1<<0), index+0, return 1);
probe((1<<1), index+1, return 1);
probe((1<<2), index+2, return 1);
probe((1<<3), index+3, return 1);
probe((1<<4), index+4, return 1);
probe((1<<5), index+5, return 1);
probe((1<<6), index+6, return 1);
probe((1<<7), index+7, return 1);
}
}
tally_miss(ht, probes);
return 0;
}
sword_t hopscotch_get(tableptr ht, uword_t key, sword_t notfound)
{
gc_dcheck(key);
int index = hash(ht, key) & ht->mask;
unsigned bits = get_hop_mask(ht, index);
int __attribute__((unused)) probes = 0;
if (ht->compare)
for ( ; bits ; bits >>= 1, ++index ) {
if ((bits & 1) && ht->compare(ht->keys[index], key))
goto found0;
}
else for ( ; bits ; bits >>= 4, index += 4)
if (bits & 0xf) {
probe(1, index+0, goto found0);
probe(2, index+1, goto found1);
probe(4, index+2, goto found2);
probe(8, index+3, goto found3);
}
tally_miss(ht, probes);
return notfound;
found3: ++index;
found2: ++index;
found1: ++index;
found0:
return get_val(ht, index);
}
insert 'key' with value 0 if it was not found. */
void* hopscotch_get_ref(tableptr ht, uword_t key)
{
gc_dcheck(key);
int index = hash(ht, key) & ht->mask;
unsigned bits = get_hop_mask(ht, index);
int __attribute__((unused)) probes = 0;
if (ht->compare)
for ( ; bits ; bits >>= 1, ++index ) {
if ((bits & 1) && ht->compare(ht->keys[index], key))
goto found0;
}
else for ( ; bits ; bits >>= 4, index += 4)
if (bits & 0xf) {
probe(1, index+0, goto found0);
probe(2, index+1, goto found1);
probe(4, index+2, goto found2);
probe(8, index+3, goto found3);
}
tally_miss(ht, probes);
hopscotch_insert(ht, key, 0);
return hopscotch_get_ref(ht, key);
found3: ++index;
found2: ++index;
found1: ++index;
found0:
switch(ht->value_size) {
#ifdef LISP_FEATURE_64_BIT
case 8: return (int64_t*)ht->values + index;
#endif
case 4: return (int32_t*)ht->values + index;
case 2: return (int16_t*)ht->values + index;
case 1: return (int8_t *)ht->values + index;
}
return 0;
}
* the key was inserted, or zero if the key existed. */
int hopscotch_put(tableptr ht, uword_t key, sword_t val)
{
gc_dcheck(key);
int index = hash(ht, key) & ht->mask;
unsigned bits = get_hop_mask(ht, index);
int __attribute__((unused)) probes = 0;
if (ht->compare)
for ( ; bits ; bits >>= 1, ++index ) {
if ((bits & 1) && ht->compare(ht->keys[index], key))
goto found0;
}
else for ( ; bits ; bits >>= 4, index += 4 )
if (bits & 0xf) {
probe(1, index+0, goto found0);
probe(2, index+1, goto found1);
probe(4, index+2, goto found2);
probe(8, index+3, goto found3);
}
tally_miss(ht, probes);
return hopscotch_insert(ht, key, val);
found3: ++index;
found2: ++index;
found1: ++index;
found0:
set_val(ht, index, val);
return 0;
}
#undef probe
boolean hopscotch_delete(tableptr ht, uword_t key)
{
gc_dcheck(key);
int logical_index = hash(ht, key) & ht->mask;
int physical_index = logical_index;
unsigned bits = get_hop_mask(ht, logical_index);
if (ht->compare) {
for ( ; bits ; bits >>= 1, ++physical_index )
if ((bits & 1) && ht->compare(ht->keys[physical_index], key))
break;
} else {
for ( ; bits ; bits >>= 1, ++physical_index )
if ((bits & 1) && ht->keys[physical_index] == key)
break;
}
if (!bits)
return 0;
ht->keys[physical_index] = 0;
set_val(ht, physical_index, 0);
ht->hops[logical_index] ^= (1<<(physical_index - logical_index));
--ht->count;
return 1;
}
#if 0
#include <stdio.h>
int popcount(unsigned x)
{
int count = 0;
for ( ; x != 0 ; x >>= 1 )
if (x&1) ++count;
return count;
}
void hopscotch_integrity_check(tableptr ht, char*when, int verbose)
{
int n_items = 0, tot_bits_set = 0, i;
int size = table_size(*ht);
int n_kv_pairs = size + ht->hop_range-1;
int fail = 0;
FILE * s = stderr;
for(i=n_kv_pairs-1 ; i >= 0 ; --i) if (ht->keys[i]) ++n_items;
for(i=ht->mask; i>= 0; --i) tot_bits_set += popcount(get_hop_mask(ht,i));
if (verbose)
fprintf(s, "(%s) Verifying table @ %p. count=%d actual=%d bits=%d\n",
when, ht, ht->count, n_items, tot_bits_set);
for (i=0;i<n_kv_pairs;++i) {
uword_t key = ht->keys[i];
int claimed;
if (key != 0 || (i<=ht->mask && get_hop_mask(ht,i) != 0)) {
int start_index = i - (ht->hop_range-1);
if (start_index < 0) start_index = 0;
int end_index = i;
if (end_index > ht->mask) end_index = ht->mask;
claimed = -1;
int logical_cell;
for (logical_cell = start_index ; logical_cell <= end_index ; ++logical_cell) {
unsigned hop_bits = get_hop_mask(ht, logical_cell);
if (hop_bits & (1<<(i - logical_cell))) {
if (claimed == -1)
claimed = logical_cell;
else {
fprintf(stderr,
"physical cell %d duplicately claimed: %d and %d",
i, claimed, logical_cell);
fail = 1;
}
}
}
if (verbose) {
if (claimed==i || (claimed==-1 && !key))
fprintf(s, " ");
else if (claimed!=-1) {
fprintf(s, "[%6d]", claimed);
if ((int)(ht->mask & hash(ht, key)) != claimed)
lose("key hashes to wrong logical cell?");
} else {
fprintf(s, " **** ");
fail = 1;
}
fprintf(s, " %6d: %04x", i, i <= ht->mask ? get_hop_mask(ht,i) : 0);
if (key) {
fprintf(s, " %lx -> %d", key, (int)(ht->mask & hash(ht, key)));
if (ht->values)
fprintf(s, " {val=%lx}", hopscotch_get(ht, key, -1));
}
putc('\n', s);
}
}
}
if (ht->count != n_items || tot_bits_set != n_items || fail)
lose("integrity check on hashtable %p failed", ht);
fflush(s);
}
#endif