diff options
Diffstat (limited to 'src/coucal.c')
-rw-r--r-- | src/coucal.c | 1476 |
1 files changed, 1476 insertions, 0 deletions
diff --git a/src/coucal.c b/src/coucal.c new file mode 100644 index 0000000..593bf94 --- /dev/null +++ b/src/coucal.c @@ -0,0 +1,1476 @@ +/* ------------------------------------------------------------ */ +/* +HTTrack Website Copier, Offline Browser for Windows and Unix +Copyright (C) 1998-2014 Xavier Roche and other contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +Important notes: + +- We hereby ask people using this source NOT to use it in purpose of grabbing +emails addresses, or collecting any other private information on persons. +This would disgrace our work, and spoil the many hours we spent on it. + +Please visit our Website: http://www.httrack.com +*/ + +/* ------------------------------------------------------------ */ +/* File: hash tables */ +/* Author: Xavier Roche */ +/* ------------------------------------------------------------ */ + +/* Internal engine bytecode */ +#define HTS_INTERNAL_BYTECODE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdarg.h> + +#include "htsinthash.h" + +/* We use md5 as hashing function for its quality regarding diffusion and + collisions. MD5 is slower than other hashing functions, but is known to be + an excellent hashing function. FNV-1 is generally good enough for this + purpose, too, but the performance gain is not sufficient to use it by + default. + + On several benchmarks, both MD5 and FNV were quite good (0.45 cuckoo moved + on average for each new item inserted in the hashtable), but FNV-1 was more + prone to mutual collisions (creating cycles requiring stash handling), and + was causing the stash area to be more filled than the MD5 variant. + + Simpler hashing functions, such as rolling hashes (LCG) were also tested, + but with collision rate and diffusion were terrible. + + [ On a 10M key tests, both variants acheived 0.45 cuckoo/add ration, + but the FNV-1 variant collided 11 times with a maximum stash area + filled with 4 entries ; whereas the MD5 variant did only collide + once ] +*/ +#if (!defined(HTS_INTHASH_USES_MD5) && !defined(HTS_INTHASH_USES_MURMUR)) +#define HTS_INTHASH_USES_MD5 1 +#endif + +#if HTS_INTHASH_USES_MD5 == 1 +#include "md5.h" +#elif (defined(HTS_INTHASH_USES_MURMUR)) +#include "murmurhash3.h" +#else +/* use the Openssl implementation */ +#if 0 +#include <openssl/md5.h> +#define MD5Init MD5_Init +#define MD5Update MD5_Update +#define MD5Final MD5_Final +#endif +#endif + +/** Size of auxiliary stash. **/ +#define STASH_SIZE 16 + +/** Minimum value for lg_size. **/ +#define MIN_LG_SIZE 4 + +/** Minimum value for pool.capacity. **/ +#define MIN_POOL_CAPACITY 256 + +/* 64-bit constant */ +#if defined(WIN32) +#define UINT_64_CONST(X) ((uint64_t) (X)) +#define UINT_64_FORMAT "I64d" +#elif (defined(_LP64) || defined(__x86_64__) \ + || defined(__powerpc64__) || defined(__64BIT__)) +#define UINT_64_CONST(X) ((uint64_t) X##UL) +#define UINT_64_FORMAT "ld" +#else +#define UINT_64_CONST(X) ((uint64_t) X##ULL) +#define UINT_64_FORMAT "lld" +#endif + +/** Hashtable. **/ +struct struct_inthash { + /** Hashtable items. **/ + inthash_item *items; + + /** Log-2 of the hashtable size. **/ + size_t lg_size; + + /** Number of used items (<= POW2(lg_size)). **/ + size_t used; + + /** Stash area for collisions. **/ + struct { + /** Stash items. **/ + inthash_item items[STASH_SIZE]; + + /** Stash size (<= STASH_SIZE). **/ + size_t size; + } stash; + + /** String pool. **/ + struct { + /** String buffer. **/ + char *buffer; + /** Buffer used size (high watermark). **/ + size_t size; + /** Buffer capacity. **/ + size_t capacity; + /** Used chars (== size if compacted). **/ + size_t used; + } pool; + + /** Statistics **/ + struct { + /** Highest stash.size seen. **/ + size_t max_stash_size; + /** Number of writes. **/ + size_t write_count; + /** Number of writes causing an add. **/ + size_t add_count; + /** Number of cuckoo moved during adds. **/ + size_t cuckoo_moved; + /** Number of items added to stash. **/ + size_t stash_added; + /** Number of hashtable rehash/expand operations. **/ + size_t rehash_count; + /** Number of pool compact operations. **/ + size_t pool_compact_count; + /** Number of pool realloc operations. **/ + size_t pool_realloc_count; + } stats; + + /** Settings. **/ + struct { + /** How to handle values (might be NULL). **/ + struct { + /** free() **/ + t_inthash_value_freehandler free; + /** opaque argument **/ + inthash_opaque arg; + } value; + + /** How to handle names (might be NULL). **/ + struct { + /** strdup() **/ + t_inthash_duphandler dup; + /** free() **/ + t_inthash_key_freehandler free; + /** hash **/ + t_inthash_hasheshandler hash; + /** comparison **/ + t_inthash_cmphandler equals; + /** opaque argument **/ + inthash_opaque arg; + } key; + + /** How to handle fatal assertions (might be NULL). **/ + struct { + /** logging **/ + t_inthash_loghandler log; + /** abort() **/ + t_inthash_asserthandler fatal; + /** opaque argument **/ + inthash_opaque arg; + /** hashtable name for logging **/ + inthash_key_const name; + } error; + + /** How to handle pretty-print (debug) (might be NULL). **/ + struct { + /** key print() **/ + t_inthash_printkeyhandler key; + /** value print() **/ + t_inthash_printvaluehandler value; + /** opaque argument **/ + inthash_opaque arg; + } print; + } custom; +}; + +/* Assertion check. */ +#define inthash_assert(HASHTABLE, EXP) \ + (void)( (EXP) || (inthash_assert_failed(HASHTABLE, #EXP, __FILE__, __LINE__), 0) ) + +/* Compiler-specific. */ +#ifdef __GNUC__ +#define INTHASH_PRINTF_FUN(fmt, arg) __attribute__ ((format (printf, fmt, arg))) +#define INTHASH_INLINE __inline__ +#elif (defined(_MSC_VER)) +#define INTHASH_PRINTF_FUN(FMT, ARGS) +#define INTHASH_INLINE __inline +#else +#define INTHASH_PRINTF_FUN(FMT, ARGS) +#define INTHASH_INLINE +#endif + +/* Logging level. */ +static void inthash_log(const inthash hashtable, inthash_loglevel level, + const char *format, va_list args); +#define DECLARE_LOG_FUNCTION(NAME, LEVEL) \ +static void NAME(const inthash hashtable, const char *format, ...) \ + INTHASH_PRINTF_FUN(2, 3); \ +static void NAME(const inthash hashtable, const char *format, ...) { \ + va_list args; \ + va_start(args, format); \ + inthash_log(hashtable, LEVEL, format, args); \ + va_end(args); \ +} +#if 0 +/* Verbose. */ +DECLARE_LOG_FUNCTION(inthash_crit, inthash_log_critical) +DECLARE_LOG_FUNCTION(inthash_warning, inthash_log_warning) +DECLARE_LOG_FUNCTION(inthash_info, inthash_log_info) +DECLARE_LOG_FUNCTION(inthash_debug, inthash_log_debug) +DECLARE_LOG_FUNCTION(inthash_trace, inthash_log_trace) +#elif 0 +/* Info. */ +DECLARE_LOG_FUNCTION(inthash_crit, inthash_log_critical) +DECLARE_LOG_FUNCTION(inthash_warning, inthash_log_warning) +DECLARE_LOG_FUNCTION(inthash_info, inthash_log_info) +#define inthash_debug inthash_log +#define inthash_trace inthash_nolog +#else +/* No logging except stats and critical. */ +DECLARE_LOG_FUNCTION(inthash_crit, inthash_log_critical) +DECLARE_LOG_FUNCTION(inthash_warning, inthash_log_warning) +DECLARE_LOG_FUNCTION(inthash_info, inthash_log_info) +#define inthash_debug inthash_nolog +#define inthash_trace inthash_nolog +#endif + +/* 2**X */ +#define POW2(X) ( (size_t) 1 << (X) ) + +/* the empty string for the string pool */ +static char the_empty_string[1] = { 0 }; + +/* global assertion handler */ +static t_inthash_asserthandler global_assert_handler = NULL; + +/* global assertion handler */ +static t_inthash_loghandler global_log_handler = NULL; + +/* default assertion handler, if neither hashtable one nor global one + were defined */ +static void inthash_fail(const char* exp, const char* file, int line) { + fprintf(stderr, "assertion '%s' failed at %s:%d\n", exp, file, line); + abort(); +} + +/* assert failed handler. */ +static void inthash_assert_failed(const inthash hashtable, const char* exp, const char* file, int line) { + const char *const name = inthash_get_name(hashtable); + inthash_crit(hashtable, "hashtable %s: %s failed at %s:%d", + name != NULL ? name : "<unknown>", exp, file, line); + if (hashtable != NULL && hashtable->custom.error.fatal != NULL) { + hashtable->custom.error.fatal(hashtable->custom.error.arg, exp, file, line); + } else if (global_assert_handler != NULL) { + global_assert_handler(hashtable, exp, file, line); + } else { + inthash_fail(exp, file, line); + } + abort(); +} + +/* Logging */ +static void inthash_log(const inthash hashtable, inthash_loglevel level, + const char *format, va_list args) { + inthash_assert(hashtable, format != NULL); + if (hashtable != NULL && hashtable->custom.error.log != NULL) { + hashtable->custom.error.log(hashtable->custom.error.arg, level, format, args); + } else if (global_log_handler != NULL) { + global_log_handler(hashtable, level, format, args); + } else { + fprintf(stderr, "[%p] ", (void*) hashtable); + (void) vfprintf(stderr, format, args); + putc('\n', stderr); + } +} + +/* No logging (should be dropped by the compiler) */ +static void inthash_nolog(const inthash hashtable, const char *format, ...) + INTHASH_PRINTF_FUN(2, 3); +static void inthash_nolog(const inthash hashtable, const char *format, ...) { + (void) hashtable; + (void) format; +} + +const char* inthash_get_name(inthash hashtable) { + return hashtable->custom.error.name; +} + +static void inthash_log_stats(inthash hashtable) { + const char *const name = inthash_get_name(hashtable); + inthash_info(hashtable, "hashtable %s%s%ssummary: " + "size=%"UINT_64_FORMAT" (lg2=%"UINT_64_FORMAT") " + "used=%"UINT_64_FORMAT" " + "stash-size=%"UINT_64_FORMAT" " + "pool-size=%"UINT_64_FORMAT" " + "pool-capacity=%"UINT_64_FORMAT" " + "pool-used=%"UINT_64_FORMAT" " + "writes=%"UINT_64_FORMAT" " + "(new=%"UINT_64_FORMAT") " + "moved=%"UINT_64_FORMAT " " + "stashed=%"UINT_64_FORMAT" " + "max-stash-size=%"UINT_64_FORMAT" " + "avg-moved=%g " + "rehash=%"UINT_64_FORMAT" " + "pool-compact=%"UINT_64_FORMAT" " + "pool-realloc=%"UINT_64_FORMAT" " + "memory=%"UINT_64_FORMAT, + name != NULL ? "\"" : "", + name != NULL ? name : "", + name != NULL ? "\" " : "", + (uint64_t) POW2(hashtable->lg_size), + (uint64_t) hashtable->lg_size, + (uint64_t) hashtable->used, + (uint64_t) hashtable->stash.size, + (uint64_t) hashtable->pool.size, + (uint64_t) hashtable->pool.capacity, + (uint64_t) hashtable->pool.used, + (uint64_t) hashtable->stats.write_count, + (uint64_t) hashtable->stats.add_count, + (uint64_t) hashtable->stats.cuckoo_moved, + (uint64_t) hashtable->stats.stash_added, + (uint64_t) hashtable->stats.max_stash_size, + (double) hashtable->stats.cuckoo_moved / (double) hashtable->stats.add_count, + (uint64_t) hashtable->stats.rehash_count, + (uint64_t) hashtable->stats.pool_compact_count, + (uint64_t) hashtable->stats.pool_realloc_count, + (uint64_t) inthash_memory_size(hashtable) + ); +} + +/* default hash function when key is a regular C-string */ +inthash_hashkeys inthash_hash_string(const char *name) { +#if HTS_INTHASH_USES_MD5 == 1 + /* compute a regular MD5 and extract two 32-bit integers */ + MD5_CTX ctx; + union { + unsigned char md5digest[16]; + inthash_hashkeys mhashes[2]; + inthash_hashkeys hashes; + } u; + + /* compute MD5 */ + MD5Init(&ctx, 0); + MD5Update(&ctx, (const unsigned char *) name, + (unsigned int) strlen(name)); + MD5Final(u.md5digest, &ctx); + + /* mix mix mix */ + u.mhashes[0].hash1 ^= u.mhashes[1].hash1; + u.mhashes[0].hash2 ^= u.mhashes[1].hash2; + + /* do not keep identical hashes */ + if (u.hashes.hash1 == u.hashes.hash2) { + u.hashes.hash2 = ~u.hashes.hash2; + } + + return u.hashes; +#elif (defined(HTS_INTHASH_USES_MURMUR)) + union { + uint32_t result[4]; + inthash_hashkeys hashes; + } u; + MurmurHash3_x86_128(name, (const int) strlen(name), + 42, &u.result) ; + + /* mix mix mix */ + u.result[0] ^= u.result[2]; + u.result[1] ^= u.result[3]; + + /* do not keep identical hashes */ + if (u.hashes.hash1 == u.hashes.hash2) { + u.hashes.hash2 = ~u.hashes.hash2; + } + + return u.hashes; +#else + /* compute two Fowler-Noll-Vo hashes (64-bit FNV-1 variant) ; + each 64-bit hash being XOR-folded into a single 32-bit hash. */ + size_t i; + inthash_hashkeys hashes; + uint64_t h1, h2; + + /* FNV-1, 64-bit. */ +#define FNV1_PRIME UINT_64_CONST(1099511628211) +#define FNV1_OFFSET_BASIS UINT_64_CONST(14695981039346656037) + + /* compute the hashes ; second variant is using xored data */ + h1 = FNV1_OFFSET_BASIS; + h2 = ~FNV1_OFFSET_BASIS; + for(i = 0 ; name[i] != '\0' ; i++) { + const unsigned char c1 = name[i]; + const unsigned char c2 = ~c1; + h1 = ( h1 * FNV1_PRIME ) ^ c1; + h2 = ( h2 * FNV1_PRIME ) ^ c2; + } + + /* XOR-folding to improve diffusion (Wikipedia) */ + hashes.hash1 = ( (uint32_t) h1 ^ (uint32_t) ( h1 >> 32 ) ); + hashes.hash2 = ( (uint32_t) h2 ^ (uint32_t) ( h2 >> 32 ) ); + +#undef FNV1_PRIME +#undef FNV1_OFFSET_BASIS + + /* do not keep identical hashes */ + if (hashes.hash1 == hashes.hash2) { + hashes.hash2 = ~hashes.hash2; + } + + return hashes; +#endif +} + +static INTHASH_INLINE inthash_hashkeys inthash_calc_hashes(inthash hashtable, + inthash_key_const value) { + return hashtable->custom.key.hash == NULL + ? inthash_hash_string(value) + : hashtable->custom.key.hash(hashtable->custom.key.arg, value); +} + +/* position 'pos' is free ? */ +static INTHASH_INLINE int inthash_is_free(const inthash hashtable, size_t pos) { + return hashtable->items[pos].name == NULL; +} + +/* compare two keys ; by default using strcmp() */ +static INTHASH_INLINE int inthash_equals(inthash hashtable, + inthash_key_const a, + inthash_key_const b) { + return hashtable->custom.key.equals == NULL + ? strcmp((const char*) a, (const char*) b) == 0 + : hashtable->custom.key.equals(hashtable->custom.key.arg, a, b); +} + +static INTHASH_INLINE int inthash_matches_(inthash hashtable, + const inthash_item *const item, + inthash_key_const name, + const inthash_hashkeys *hashes) { + return item->name != NULL + && item->hashes.hash1 == hashes->hash1 + && item->hashes.hash2 == hashes->hash2 + && inthash_equals(hashtable, item->name, name); +} + +static INTHASH_INLINE int inthash_matches(inthash hashtable, size_t pos, + inthash_key_const name, + const inthash_hashkeys *hashes) { + const inthash_item *const item = &hashtable->items[pos]; + return inthash_matches_(hashtable, item, name, hashes); +} + +/* compact string pool ; does not change the capacity */ +static void inthash_compact_pool(inthash hashtable, size_t capacity) { + const size_t hash_size = POW2(hashtable->lg_size); + size_t i; + char*const old_pool = hashtable->pool.buffer; + const size_t old_size = hashtable->pool.size; + size_t count = 0; + + /* we manage the string pool */ + inthash_assert(hashtable, hashtable->custom.key.dup == NULL); + + /* statistics */ + hashtable->stats.pool_compact_count++; + + /* change capacity now */ + if (hashtable->pool.capacity != capacity) { + hashtable->pool.capacity = capacity; + } + + /* realloc */ + hashtable->pool.buffer = malloc(hashtable->pool.capacity); + hashtable->pool.size = 0; + hashtable->pool.used = 0; + if (hashtable->pool.buffer == NULL) { + inthash_debug(hashtable, + "** hashtable string pool compaction error: could not allocate " + "%"UINT_64_FORMAT" bytes", + (uint64_t) hashtable->pool.capacity); + inthash_assert(hashtable, ! "hashtable string pool compaction error"); + } + + /* relocate a string on a different pool */ +#define RELOCATE_STRING(S) do { \ + if (S != NULL && S != the_empty_string) { \ + const char *const src = (S); \ + char *const dest = \ + &hashtable->pool.buffer[hashtable->pool.size]; \ + const size_t capacity = hashtable->pool.capacity; \ + char *const max_dest = \ + &hashtable->pool.buffer[capacity]; \ + /* copy string */ \ + inthash_assert(hashtable, dest < max_dest); \ + dest[0] = src[0]; \ + { \ + size_t i; \ + for(i = 1 ; src[i - 1] != '\0' ; i++) { \ + inthash_assert(hashtable, &dest[i] < max_dest); \ + dest[i] = src[i]; \ + } \ + /* update pool size */ \ + hashtable->pool.size += i; \ + inthash_assert(hashtable, \ + hashtable->pool.size <= capacity); \ + } \ + /* update source */ \ + S = dest; \ + count++; \ + } \ +} while(0) + + /* relocate */ + for(i = 0 ; i < hash_size ; i++) { + RELOCATE_STRING(hashtable->items[i].name); + } + for(i = 0 ; i < hashtable->stash.size ; i++) { + RELOCATE_STRING(hashtable->stash.items[i].name); + } + +#undef RELOCATE_STRING + + /* compacted (used chars == current size) */ + hashtable->pool.used = hashtable->pool.size; + + /* wipe previous pool */ + free(old_pool); + + inthash_debug(hashtable, + "compacted string pool for %"UINT_64_FORMAT" strings: " + "%"UINT_64_FORMAT" bytes => %"UINT_64_FORMAT" bytes", + (uint64_t) count, (uint64_t) old_size, + (uint64_t) hashtable->pool.size); +} + +/* realloc (expand) string pool ; does not change the compacity */ +static void inthash_realloc_pool(inthash hashtable, size_t capacity) { + const size_t hash_size = POW2(hashtable->lg_size); + char *const oldbase = hashtable->pool.buffer; + size_t i; + size_t count = 0; + + /* we manage the string pool */ + inthash_assert(hashtable, hashtable->custom.key.dup == NULL); + + /* compact instead ? */ + if (hashtable->pool.used < ( hashtable->pool.size*3 ) / 4) { + inthash_compact_pool(hashtable, capacity); + return ; + } + + /* statistics */ + hashtable->stats.pool_realloc_count++; + + /* change capacity now */ + hashtable->pool.capacity = capacity; + + /* realloc */ + hashtable->pool.buffer = realloc(hashtable->pool.buffer, + hashtable->pool.capacity); + if (hashtable->pool.buffer == NULL) { + inthash_crit(hashtable, + "** hashtable string pool allocation error: could not allocate " + "%"UINT_64_FORMAT" bytes", + (uint64_t) hashtable->pool.capacity); + inthash_assert(hashtable, ! "hashtable string pool allocation error"); + } + + /* recompute string address */ +#define RECOMPUTE_STRING(S) do { \ + if (S != NULL && S != the_empty_string) { \ + const size_t offset = (const char*) (S) - oldbase; \ + inthash_assert(hashtable, offset < hashtable->pool.capacity); \ + S = &hashtable->pool.buffer[offset]; \ + count++; \ + } \ +} while(0) + + /* recompute */ + for(i = 0 ; i < hash_size ; i++) { + RECOMPUTE_STRING(hashtable->items[i].name); + } + for(i = 0 ; i < hashtable->stash.size ; i++) { + RECOMPUTE_STRING(hashtable->stash.items[i].name); + } + +#undef RECOMPUTE_STRING + + inthash_debug(hashtable, "reallocated string pool for " + "%"UINT_64_FORMAT" strings: %"UINT_64_FORMAT" bytes", + (uint64_t) count, (uint64_t) hashtable->pool.capacity); +} + +static inthash_key inthash_dup_name_internal(inthash hashtable, + inthash_key_const name_) { + const char *const name = (const char*) name_; + const size_t len = strlen(name) + 1; + char *s; + + /* the pool does not allow empty strings for safety purpose ; handhe that + (keys are being emptied when free'd to detect duplicate free) */ + if (len == 1) { + inthash_assert(hashtable, the_empty_string[0] == '\0'); + return the_empty_string; + } + + /* expand pool capacity */ + inthash_assert(hashtable, hashtable->pool.size <= hashtable->pool.capacity); + if (hashtable->pool.capacity - hashtable->pool.size < len) { + size_t capacity; + for(capacity = MIN_POOL_CAPACITY ; capacity < hashtable->pool.size + len + ; capacity <<= 1) ; + inthash_assert(hashtable, hashtable->pool.size < capacity); + inthash_realloc_pool(hashtable, capacity); + } + + /* copy */ + inthash_assert(hashtable, len + hashtable->pool.size <= hashtable->pool.capacity); + s = &hashtable->pool.buffer[hashtable->pool.size]; + memcpy(s, name, len); + hashtable->pool.size += len; + hashtable->pool.used += len; + + return s; +} + +/* duplicate a key. default is to use the internal pool. */ +static INTHASH_INLINE inthash_key inthash_dup_name(inthash hashtable, + inthash_key_const name) { + return hashtable->custom.key.dup == NULL + ? inthash_dup_name_internal(hashtable, name) + : hashtable->custom.key.dup(hashtable->custom.key.arg, name); +} + +/* internal pool free handler. + note: pointer must have been kicked from the pool first */ +static void inthash_free_key_internal(inthash hashtable, inthash_key name_) { + char *const name = (char*) name_; + const size_t len = strlen(name) + 1; + + /* see inthash_dup_name_internal() handling */ + if (len == 1 && name == the_empty_string) { + inthash_assert(hashtable, the_empty_string[0] == '\0'); + return ; + } + + inthash_assert(hashtable, *name != '\0' || !"duplicate or bad string pool release"); + hashtable->pool.used -= len; + *name = '\0'; /* the string is now invalidated */ + + /* compact the pool is too many holes */ + if (hashtable->pool.used < hashtable->pool.size / 2) { + size_t capacity = hashtable->pool.capacity; + /* compact and shrink */ + if (hashtable->pool.used < capacity / 4) { + capacity /= 2; + } + inthash_assert(hashtable, hashtable->pool.used < capacity); + inthash_compact_pool(hashtable, capacity); + } +} + +/* free a key. default is to use the internal pool. + note: pointer must have been kicked from the pool first */ +static void inthash_free_key(inthash hashtable, inthash_key name) { + if (hashtable->custom.key.free == NULL) { + inthash_free_key_internal(hashtable, name); + } else { + hashtable->custom.key.free(hashtable->custom.key.arg, name); + } +} + +static INTHASH_INLINE size_t inthash_hash_to_pos_(size_t lg_size, + inthash_hashkey hash) { + const inthash_hashkey mask = POW2(lg_size) - 1; + return hash & mask; +} + +static INTHASH_INLINE size_t inthash_hash_to_pos(const inthash hashtable, + inthash_hashkey hash) { + return inthash_hash_to_pos_(hashtable->lg_size, hash); +} + +int inthash_read_pvoid(inthash hashtable, inthash_key_const name, void **pvalue) { + inthash_value value = INTHASH_VALUE_NULL; + const int ret = + inthash_read_value(hashtable, name, (pvalue != NULL) ? &value : NULL); + if (pvalue != NULL) + *pvalue = value.ptr; + return ret; +} + +int inthash_write_pvoid(inthash hashtable, inthash_key_const name, void *pvalue) { + inthash_value value = INTHASH_VALUE_NULL; + + value.ptr = pvalue; + return inthash_write_value(hashtable, name, value); +} + +void inthash_add_pvoid(inthash hashtable, inthash_key_const name, void *pvalue) { + inthash_value value = INTHASH_VALUE_NULL; + + value.ptr = pvalue; + inthash_write_value(hashtable, name, value); +} + +int inthash_write(inthash hashtable, inthash_key_const name, intptr_t intvalue) { + inthash_value value = INTHASH_VALUE_NULL; + + value.intg = intvalue; + return inthash_write_value(hashtable, name, value); +} + +static void inthash_default_free_handler(inthash_opaque arg, + inthash_value value) { + (void) arg; + if (value.ptr != NULL) + free(value.ptr); +} + +static void inthash_del_value_(inthash hashtable, inthash_value *pvalue) { + if (hashtable->custom.value.free != NULL) + hashtable->custom.value.free(hashtable->custom.value.arg, *pvalue); + pvalue->ptr = NULL; +} + +static void inthash_del_value(inthash hashtable, size_t pos) { + inthash_del_value_(hashtable, &hashtable->items[pos].value); +} + +static void inthash_del_name(inthash hashtable, inthash_item *item) { + const inthash_hashkeys nullHash = INTHASH_KEYS_NULL; + char *const name = (char*) item->name; + item->name = NULL; /* there must be no reference remaining */ + item->hashes = nullHash; + /* free after detach (we may compact the pool) */ + inthash_free_key(hashtable, name); +} + +static void inthash_del_item(inthash hashtable, inthash_item *pitem) { + inthash_del_value_(hashtable, &pitem->value); + inthash_del_name(hashtable, pitem); +} + +static int inthash_add_item_(inthash hashtable, inthash_item item); + +/* Write (add or replace) a value in the hashtable. */ +static int inthash_write_value_(inthash hashtable, inthash_key_const name, + inthash_value value) { + inthash_item item; + size_t pos; + const inthash_hashkeys hashes = inthash_calc_hashes(hashtable, name); + + /* Statistics */ + hashtable->stats.write_count++; + + /* replace at position 1 ? */ + pos = inthash_hash_to_pos(hashtable, hashes.hash1); + if (inthash_matches(hashtable, pos, name, &hashes)) { + inthash_del_value(hashtable, pos); + hashtable->items[pos].value = value; + return 0; /* replaced */ + } + + /* replace at position 2 ? */ + pos = inthash_hash_to_pos(hashtable, hashes.hash2); + if (inthash_matches(hashtable, pos, name, &hashes)) { + inthash_del_value(hashtable, pos); + hashtable->items[pos].value = value; + return 0; /* replaced */ + } + + /* replace in the stash ? */ + if (hashtable->stash.size != 0) { + size_t i; + for(i = 0 ; i < hashtable->stash.size ; i++) { + if (inthash_matches_(hashtable, &hashtable->stash.items[i], name, + &hashes)) { + inthash_del_value_(hashtable, &hashtable->stash.items[i].value); + hashtable->stash.items[i].value = value; + return 0; /* replaced */ + } + } + } + + /* Statistics */ + hashtable->stats.add_count++; + + /* otherwise we need to create a new item */ + item.name = inthash_dup_name(hashtable, name); + item.value = value; + item.hashes = hashes; + + return inthash_add_item_(hashtable, item); +} + +/* Return the string representation of a key */ +static const char* inthash_print_key(inthash hashtable, + inthash_key_const name) { + return hashtable->custom.print.key != NULL + ? hashtable->custom.print.key(hashtable->custom.print.arg, name) + : (const char*) name; +} + +/* Add a new item in the hashtable. The item SHALL NOT be alreasy present. */ +static int inthash_add_item_(inthash hashtable, inthash_item item) { + inthash_hashkey cuckoo_hash, initial_cuckoo_hash; + size_t loops; + size_t pos; + + /* place at free position 1 ? */ + pos = inthash_hash_to_pos(hashtable, item.hashes.hash1); + if (inthash_is_free(hashtable, pos)) { + hashtable->items[pos] = item; + return 1; /* added */ + } else { + /* place at free position 2 ? */ + pos = inthash_hash_to_pos(hashtable, item.hashes.hash2); + if (inthash_is_free(hashtable, pos)) { + hashtable->items[pos] = item; + return 1; /* added */ + } + /* prepare cuckoo ; let's take position 1 */ + else { + cuckoo_hash = initial_cuckoo_hash = item.hashes.hash1; + inthash_trace(hashtable, + "debug:collision with '%s' at %"UINT_64_FORMAT" (%x)", + inthash_print_key(hashtable, item.name), + (uint64_t) pos, cuckoo_hash); + } + } + + /* put 'item' in place with hash 'cuckoo_hash' */ + for(loops = POW2(hashtable->lg_size) ; loops != 0 ; --loops) { + const size_t pos = inthash_hash_to_pos(hashtable, cuckoo_hash); + + inthash_trace(hashtable, + "\tdebug:placing cuckoo '%s' at %"UINT_64_FORMAT" (%x)", + inthash_print_key(hashtable, item.name), + (uint64_t) pos, cuckoo_hash); + + /* place at alternate free position ? */ + if (inthash_is_free(hashtable, pos)) { + inthash_trace(hashtable, "debug:free position"); + hashtable->items[pos] = item; + return 1; /* added */ + } + /* then cuckoo's place it is */ + else { + /* replace */ + const inthash_item backup_item = hashtable->items[pos]; + hashtable->items[pos] = item; + + /* statistics */ + hashtable->stats.cuckoo_moved++; + + /* take care of new lost item */ + item = backup_item; + + /* we just kicked this item from its position 1 */ + if (pos == inthash_hash_to_pos(hashtable, item.hashes.hash1)) { + /* then place it on position 2 on next run */ + inthash_trace(hashtable, "\tdebug:position 1"); + cuckoo_hash = item.hashes.hash2; + } + /* we just kicked this item from its position 2 */ + else if (pos == inthash_hash_to_pos(hashtable, item.hashes.hash2)) { + /* then place it on position 1 on next run */ + inthash_trace(hashtable, "\tdebug:position 2"); + cuckoo_hash = item.hashes.hash1; + } + else { + inthash_assert(hashtable, ! "hashtable internal error: unexpected position"); + } + + /* we are looping (back to same hash) */ + /* TODO FIXME: we should actually check the positions */ + if (cuckoo_hash == initial_cuckoo_hash) { + /* emergency stash */ + break; + } + } + } + + /* emergency stashing for the rare cases of collisions */ + if (hashtable->stash.size < STASH_SIZE) { + hashtable->stash.items[hashtable->stash.size] = item; + hashtable->stash.size++; + /* for statistics */ + hashtable->stats.stash_added++; + if (hashtable->stash.size > hashtable->stats.max_stash_size) { + hashtable->stats.max_stash_size = hashtable->stash.size; + } + inthash_debug(hashtable, "used stash because of collision (%d entries)", + (int) hashtable->stash.size); + return 1; /* added */ + } else { + /* debugging */ + if (hashtable->custom.print.key != NULL + && hashtable->custom.print.value != NULL) { + size_t i; + for(i = 0 ; i < hashtable->stash.size ; i++) { + inthash_item *const item = &hashtable->stash.items[i]; + const size_t pos1 = inthash_hash_to_pos(hashtable, item->hashes.hash1); + const size_t pos2 = inthash_hash_to_pos(hashtable, item->hashes.hash2); + inthash_crit(hashtable, + "stash[%u]: key='%s' value='%s' pos1=%d pos2=%d hash1=%04x hash2=%04x", + (int) i, + hashtable->custom.print.key(hashtable->custom.print.arg, item->name), + hashtable->custom.print.value(hashtable->custom.print.arg, item->value), + (int) pos1, (int) pos2, + item->hashes.hash1, item->hashes.hash2); + if (!inthash_is_free(hashtable, pos1)) { + inthash_item *const item = &hashtable->items[pos1]; + const size_t pos1 = inthash_hash_to_pos(hashtable, item->hashes.hash1); + const size_t pos2 = inthash_hash_to_pos(hashtable, item->hashes.hash2); + inthash_crit(hashtable, + "\t.. collisionning with key='%s' value='%s' pos1=%d pos2=%d hash1=%04x hash2=%04x", + hashtable->custom.print.key(hashtable->custom.print.arg, item->name), + hashtable->custom.print.value(hashtable->custom.print.arg, item->value), + (int) pos1, (int) pos2, + item->hashes.hash1, item->hashes.hash2); + } else { + inthash_crit(hashtable, "\t.. collisionning with a free slot (%d)!", (int) pos1); + } + if (!inthash_is_free(hashtable, pos2)) { + inthash_item *const item = &hashtable->items[pos2]; + const size_t pos1 = inthash_hash_to_pos(hashtable, item->hashes.hash1); + const size_t pos2 = inthash_hash_to_pos(hashtable, item->hashes.hash2); + inthash_crit(hashtable, + "\t.. collisionning with key='%s' value='%s' pos1=%d pos2=%d hash1=%04x hash2=%04x", + hashtable->custom.print.key(hashtable->custom.print.arg, item->name), + hashtable->custom.print.value(hashtable->custom.print.arg, item->value), + (int) pos1, (int) pos2, + item->hashes.hash1, item->hashes.hash2); + } else { + inthash_crit(hashtable, "\t.. collisionning with a free slot (%d)!", (int) pos2); + } + } + //struct_inthash_enum e = inthash_enum_new(hashtable); + //while((item = inthash_enum_next(&e)) != NULL) { + // inthash_crit(hashtable, "element key='%s' value='%s' hash1=%04x hash2=%04x", + // hashtable->custom.print.key(hashtable->custom.print.arg, item->name), + // hashtable->custom.print.value(hashtable->custom.print.arg, item->value.ptr), + // item->hashes.hash1, item->hashes.hash2); + //} + } + + /* we are doomed. hopefully the probability is lower than being killed + by a wandering radioactive monkey */ + inthash_log_stats(hashtable); + inthash_assert(hashtable, ! "hashtable internal error: cuckoo/stash collision"); + + /* not reachable code */ + return -1; + } +} + +int inthash_write_value(inthash hashtable, inthash_key_const name, + inthash_value_const value) { + /* replace of add item */ + const int ret = inthash_write_value_(hashtable, name, value); + + /* added ? */ + if (ret) { + /* size of half of the table */ + const size_t half_size = POW2(hashtable->lg_size - 1); + + /* size of half of the stash */ + const size_t half_stash_size = STASH_SIZE / 2; + + /* item was added: increase count */ + hashtable->used++; + + /* table is more than half-full, or stash is more than half-full */ + if (hashtable->used >= half_size + || hashtable->stash.size >= half_stash_size) { + size_t i; + + /* size before */ + const size_t prev_power = hashtable->lg_size; + const size_t prev_size = half_size * 2; + const size_t prev_alloc_size = prev_size*sizeof(inthash_item); + + /* size after doubling it */ + const size_t alloc_size = prev_alloc_size * 2; + + /* log stash issues */ + if (hashtable->stash.size >= half_stash_size + && half_size > POW2(16) + && hashtable->used < half_size / 4) { + inthash_warning(hashtable, + "stash size still full despite %"UINT_64_FORMAT + " elements used out of %"UINT_64_FORMAT, + (uint64_t) hashtable->used, (uint64_t) half_size*2); + } + + /* statistics */ + hashtable->stats.rehash_count++; + + /* realloc */ + hashtable->lg_size++; + hashtable->items = + (inthash_item *) realloc(hashtable->items, alloc_size); + if (hashtable->items == NULL) { + inthash_crit(hashtable, + "** hashtable allocation error: " + "could not allocate %"UINT_64_FORMAT" bytes", + (uint64_t) alloc_size); + inthash_assert(hashtable, ! "hashtable allocation error"); + } + + /* clear upper half */ + memset(&hashtable->items[prev_size], 0, prev_alloc_size); + + /* relocate lower half items when needed */ + for(i = 0 ; i < prev_size ; i++) { + if (!inthash_is_free(hashtable, i)) { + const inthash_hashkeys *const hashes = &hashtable->items[i].hashes; + + /* currently at old position 1 */ + if (inthash_hash_to_pos_(prev_power, hashes->hash1) == i) { + const size_t pos = inthash_hash_to_pos(hashtable, hashes->hash1); + /* no more the expected position */ + if (pos != i) { + inthash_assert(hashtable, pos >= prev_size); + hashtable->items[pos] = hashtable->items[i]; + memset(&hashtable->items[i], 0, sizeof(hashtable->items[i])); + } + } + else if (inthash_hash_to_pos_(prev_power, hashes->hash2) == i) { + const size_t pos = inthash_hash_to_pos(hashtable, hashes->hash2); + /* no more the expected position */ + if (pos != i) { + inthash_assert(hashtable, pos >= prev_size); + hashtable->items[pos] = hashtable->items[i]; + memset(&hashtable->items[i], 0, sizeof(hashtable->items[i])); + } + } + else { + inthash_assert(hashtable, ! "hashtable unexpected internal error (bad position)"); + } + } + } + + inthash_debug(hashtable, + "expanded hashtable to %"UINT_64_FORMAT" elements", + (uint64_t) POW2(hashtable->lg_size)); + + /* attempt to merge the stash if present */ + if (hashtable->stash.size != 0) { + const size_t old_size = hashtable->stash.size; + size_t i; + + /* backup stash and reset it */ + inthash_item stash[STASH_SIZE]; + memcpy(&stash, hashtable->stash.items, sizeof(hashtable->stash.items)); + hashtable->stash.size = 0; + + /* insert all items */ + for(i = 0 ; i < old_size ; i++) { + const int ret = inthash_add_item_(hashtable, stash[i]); + if (ret == 0) { + inthash_assert(hashtable, ! "hashtable duplicate key when merging the stash"); + } + } + + /* logging */ + inthash_assert(hashtable, hashtable->stash.size <= old_size); + if (hashtable->stash.size < old_size) { + inthash_debug(hashtable, "reduced stash size from %"UINT_64_FORMAT" " + "to %"UINT_64_FORMAT, + (uint64_t) old_size, (uint64_t) hashtable->stash.size); + } else { + inthash_trace(hashtable, "stash has still %"UINT_64_FORMAT" elements", + (uint64_t) hashtable->stash.size); + } + } + + } + } + + return ret; +} + +void inthash_add(inthash hashtable, inthash_key_const name, intptr_t intvalue) { + inthash_value value = INTHASH_VALUE_NULL; + + memset(&value, 0, sizeof(value)); + value.intg = intvalue; + inthash_write_value(hashtable, name, value); +} + +int inthash_read(inthash hashtable, inthash_key_const name, intptr_t * intvalue) { + inthash_value value = INTHASH_VALUE_NULL; + int ret = + inthash_read_value(hashtable, name, (intvalue != NULL) ? &value : NULL); + if (intvalue != NULL) + *intvalue = value.intg; + return ret; +} + +static inthash_value* inthash_read_value_(inthash hashtable, + inthash_key_const name) { + const inthash_hashkeys hashes = inthash_calc_hashes(hashtable, name); + size_t pos; + + /* found at position 1 ? */ + pos = inthash_hash_to_pos(hashtable, hashes.hash1); + if (inthash_matches(hashtable, pos, name, &hashes)) { + return &hashtable->items[pos].value; + } + + /* found at position 2 ? */ + pos = inthash_hash_to_pos(hashtable, hashes.hash2); + if (inthash_matches(hashtable, pos, name, &hashes)) { + return &hashtable->items[pos].value; + } + + /* find in stash ? */ + if (hashtable->stash.size != 0) { + size_t i; + for(i = 0 ; i < hashtable->stash.size ; i++) { + if (inthash_matches_(hashtable, &hashtable->stash.items[i], name, + &hashes)) { + return &hashtable->stash.items[i].value; + } + } + } + + /* not found */ + return NULL; +} + +int inthash_read_value(inthash hashtable, inthash_key_const name, + inthash_value * pvalue) { + inthash_value* const value = inthash_read_value_(hashtable, name); + if (value != NULL) { + if (pvalue != NULL) { + *pvalue = *value; + } + return 1; + } + return 0; +} + +static size_t inthash_inc_(inthash hashtable, inthash_key_const name, + size_t inc) { + inthash_value* const value = inthash_read_value_(hashtable, name); + if (value != NULL) { + value->uintg += inc; + return value->uintg; + } else { + /* create a new value */ + const int ret = inthash_write(hashtable, name, inc); + inthash_assert(hashtable, ret); + return inc; + } +} + +int inthash_inc(inthash hashtable, inthash_key_const name) { + return (int) inthash_inc_(hashtable, name, 1); +} + +int inthash_dec(inthash hashtable, inthash_key_const name) { + return (int) inthash_inc_(hashtable, name, (size_t) -1); +} + +int inthash_exists(inthash hashtable, inthash_key_const name) { + return inthash_read_value(hashtable, name, NULL); +} + +static int inthash_remove_(inthash hashtable, inthash_key_const name, + const inthash_hashkeys *hashes, size_t *removed) { + size_t pos; + + /* found at position 1 ? */ + pos = inthash_hash_to_pos(hashtable, hashes->hash1); + if (inthash_matches(hashtable, pos, name, hashes)) { + inthash_del_item(hashtable, &hashtable->items[pos]); + *removed = pos; + return 1; + } + + /* found at position 2 ? */ + pos = inthash_hash_to_pos(hashtable, hashes->hash2); + if (inthash_matches(hashtable, pos, name, hashes)) { + inthash_del_item(hashtable, &hashtable->items[pos]); + *removed = pos; + return 1; + } + + /* find in stash ? */ + if (hashtable->stash.size != 0) { + size_t i; + for(i = 0 ; i < hashtable->stash.size ; i++) { + if (inthash_matches_(hashtable, &hashtable->stash.items[i], name, + hashes)) { + inthash_del_item(hashtable, &hashtable->stash.items[i]); + for( ; i + 1 < hashtable->stash.size ; i++) { + hashtable->stash.items[i] = hashtable->stash.items[i + 1]; + } + hashtable->stash.size--; + *removed = (size_t) -1; + inthash_debug(hashtable, "debug:deleted item in stash (%d entries)", + (int) hashtable->stash.size); + return 1; + } + } + } + + /* not found */ + return 0; +} + +int inthash_remove(inthash hashtable, inthash_key_const name) { + const inthash_hashkeys hashes = inthash_calc_hashes(hashtable, name); + size_t removed; + const int ret = inthash_remove_(hashtable, name, &hashes, &removed); + + if (ret) { + /* item was removed: decrease count */ + inthash_assert(hashtable, hashtable->used != 0); + hashtable->used--; + + /* can we place stash entry back to the table ? */ + if (hashtable->stash.size != 0 && removed != (size_t) -1) { + size_t i; + for(i = 0 ; i < hashtable->stash.size ; i++) { + const size_t pos1 = + inthash_hash_to_pos(hashtable, hashtable->stash.items[i].hashes.hash1); + const size_t pos2 = + inthash_hash_to_pos(hashtable, hashtable->stash.items[i].hashes.hash2); + if (pos1 == removed || pos2 == removed) { + if (pos1 == removed) { + hashtable->items[pos1] = hashtable->stash.items[i]; + } else if (pos2 == removed) { + hashtable->items[pos2] = hashtable->stash.items[i]; + } + for( ; i + 1 < hashtable->stash.size ; i++) { + hashtable->stash.items[i] = hashtable->stash.items[i + 1]; + } + hashtable->stash.size--; + inthash_debug(hashtable, "debug:moved item from stash (%d entries)", + (int) hashtable->stash.size); + break; + } + } + } + } + + return ret; +} + +int inthash_readptr(inthash hashtable, inthash_key_const name, intptr_t * value) { + int ret; + + *value = 0; + ret = inthash_read(hashtable, name, value); + if (*value == 0) + ret = 0; + return ret; +} + +inthash inthash_new(size_t initial_size) { + inthash hashtable = (inthash) calloc(1, sizeof(struct_inthash)); + + if (hashtable) { + size_t size; + for(size = MIN_LG_SIZE ; POW2(size) < initial_size ; size++) ; + if ((hashtable->items = + (inthash_item *) calloc(POW2(size), sizeof(inthash_item)))) { + hashtable->lg_size = size; + } + hashtable->used = 0; + hashtable->stash.size = 0; + hashtable->pool.buffer = NULL; + hashtable->pool.size = 0; + hashtable->pool.capacity = 0; + hashtable->pool.used = 0; + hashtable->stats.max_stash_size = 0; + hashtable->stats.write_count = 0; + hashtable->stats.add_count = 0; + hashtable->stats.cuckoo_moved = 0; + hashtable->stats.stash_added= 0; + hashtable->stats.pool_compact_count = 0; + hashtable->stats.pool_realloc_count = 0; + hashtable->stats.rehash_count = 0; + hashtable->custom.value.free = NULL; + hashtable->custom.value.arg = NULL; + hashtable->custom.key.dup = NULL; + hashtable->custom.key.free = NULL; + hashtable->custom.key.hash = NULL; + hashtable->custom.key.equals = NULL; + hashtable->custom.key.arg = NULL; + hashtable->custom.error.log = NULL; + hashtable->custom.error.fatal = NULL; + hashtable->custom.error.name = NULL; + hashtable->custom.error.arg = NULL; + hashtable->custom.print.key = NULL; + hashtable->custom.print.value = NULL; + hashtable->custom.print.arg = NULL; + } + return hashtable; +} + +int inthash_created(inthash hashtable) { + return hashtable != NULL && hashtable->items != NULL; +} + +void inthash_value_is_malloc(inthash hashtable, int flag) { + if (flag) { + if (hashtable->custom.value.free == NULL) { + hashtable->custom.value.free = inthash_default_free_handler; + hashtable->custom.value.arg = NULL; + } + } else { + hashtable->custom.value.free = NULL; + hashtable->custom.value.arg = NULL; + } +} + +void inthash_set_name(inthash hashtable, inthash_key_const name) { + hashtable->custom.error.name = name; +} + +void inthash_value_set_value_handler(inthash hashtable, + t_inthash_value_freehandler free, + inthash_opaque arg) { + hashtable->custom.value.free = free; + hashtable->custom.value.arg = arg; +} + +void inthash_value_set_key_handler(inthash hashtable, + t_inthash_duphandler dup, + t_inthash_key_freehandler free, + t_inthash_hasheshandler hash, + t_inthash_cmphandler equals, + inthash_opaque arg) { + /* dup and free must be consistent */ + inthash_assert(hashtable, ( dup == NULL ) == ( free == NULL ) ); + hashtable->custom.key.dup = dup; + hashtable->custom.key.free = free; + hashtable->custom.key.hash = hash; + hashtable->custom.key.equals = equals; + hashtable->custom.key.arg = arg; +} + +void inthash_set_assert_handler(inthash hashtable, + t_inthash_loghandler log, + t_inthash_asserthandler fatal, + inthash_opaque arg) { + hashtable->custom.error.log = log; + hashtable->custom.error.fatal = fatal; + hashtable->custom.error.arg = arg; +} + +void inthash_set_print_handler(inthash hashtable, + t_inthash_printkeyhandler key, + t_inthash_printvaluehandler value, + inthash_opaque arg) { + hashtable->custom.print.key = key; + hashtable->custom.print.value = value; + hashtable->custom.print.arg = arg; +} + +size_t inthash_nitems(inthash hashtable) { + if (hashtable != NULL) + return hashtable->used; + return 0; +} + +size_t inthash_memory_size(inthash hashtable) { + const size_t size_struct = sizeof(struct_inthash); + const size_t hash_size = POW2(hashtable->lg_size)*sizeof(inthash_item); + const size_t pool_size = hashtable->pool.capacity*sizeof(char); + return size_struct + hash_size + pool_size; +} + +void inthash_delete(inthash *phashtable) { + if (phashtable != NULL) { + inthash hashtable = *phashtable; + if (hashtable != NULL) { + inthash_log_stats(hashtable); + if (hashtable->items != NULL) { + /* we need to delete values */ + const size_t hash_size = POW2(hashtable->lg_size); + size_t i; + + /* wipe hashtable values (not names) */ + for(i = 0 ; i < hash_size ; i++) { + if (!inthash_is_free(hashtable, i)) { + inthash_del_value(hashtable, i); + } + } + + /* wipe auxiliary stash values (not names) if any */ + for(i = 0 ; i < hashtable->stash.size ; i++) { + inthash_del_value_(hashtable, &hashtable->stash.items[i].value); + } + } + /* wipe top-level */ + hashtable->lg_size = 0; + hashtable->used = 0; + free(hashtable->pool.buffer); + hashtable->pool.buffer = NULL; + free(hashtable->items); + hashtable->items = NULL; + free(hashtable); + *phashtable = NULL; + } + } +} + +/* Enumerator */ + +struct_inthash_enum inthash_enum_new(inthash hashtable) { + struct_inthash_enum e; + + e.index = 0; + e.table = hashtable; + return e; +} + +inthash_item *inthash_enum_next(struct_inthash_enum * e) { + const size_t hash_size = POW2(e->table->lg_size); + for( ; e->index < hash_size + && inthash_is_free(e->table, e->index) ; e->index++) ; + /* enumerate all table */ + if (e->index < hash_size) { + inthash_item *const next = &e->table->items[e->index]; + e->index++; + return next; + } + /* enumerate stash if present */ + else if (e->index < hash_size + e->table->stash.size) { + const size_t index = e->index - hash_size; + inthash_item *const next = &e->table->stash.items[index]; + e->index++; + return next; + } + /* eof */ + else { + return NULL; + } +} + +void inthash_set_global_assert_handler(t_inthash_loghandler log, + t_inthash_asserthandler fatal) { + global_log_handler = log; + global_assert_handler = fatal; +} |