/src/httrack/src/coucal/coucal.c
Line | Count | Source |
1 | | /* ------------------------------------------------------------ */ |
2 | | /* |
3 | | Coucal, Cuckoo hashing-based hashtable with stash area. |
4 | | Copyright (C) 2013-2014 Xavier Roche (https://www.httrack.com/) |
5 | | All rights reserved. |
6 | | |
7 | | Redistribution and use in source and binary forms, with or without |
8 | | modification, are permitted provided that the following conditions are met: |
9 | | |
10 | | 1. Redistributions of source code must retain the above copyright notice, this |
11 | | list of conditions and the following disclaimer. |
12 | | |
13 | | 2. Redistributions in binary form must reproduce the above copyright notice, |
14 | | this list of conditions and the following disclaimer in the documentation |
15 | | and/or other materials provided with the distribution. |
16 | | |
17 | | 3. Neither the name of the copyright holder nor the names of its contributors |
18 | | may be used to endorse or promote products derived from this software without |
19 | | specific prior written permission. |
20 | | |
21 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
22 | | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
23 | | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
24 | | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
25 | | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
26 | | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
27 | | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
28 | | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
29 | | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
30 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | | */ |
32 | | |
33 | | #include <stdio.h> |
34 | | #include <stdlib.h> |
35 | | #include <string.h> |
36 | | #include <assert.h> |
37 | | #include <stdarg.h> |
38 | | #include <inttypes.h> |
39 | | |
40 | | #include "coucal.h" |
41 | | |
42 | | /* We use murmur hashing by default, even if md5 can be a good candidate, |
43 | | for its quality regarding diffusion and collisions. |
44 | | MD5 is slower than other hashing functions, but is known to be an excellent |
45 | | hashing function. FNV-1 is generally good enough for this purpose, too, but |
46 | | the performance gain is not sufficient to use it by default. |
47 | | |
48 | | On several benchmarks, both MD5 and FNV were quite good (0.45 cuckoo moved |
49 | | on average for each new item inserted in the hashtable), but FNV-1 was more |
50 | | prone to mutual collisions (creating cycles requiring stash handling), and |
51 | | was causing the stash area to be more filled than the MD5 variant. |
52 | | |
53 | | Simpler hashing functions, such as rolling hashes (LCG) were also tested, |
54 | | but with collision rate and diffusion were terrible. |
55 | | |
56 | | [ On a 10M key tests, both variants acheived 0.45 cuckoo/add ration, |
57 | | but the FNV-1 variant collided 11 times with a maximum stash area |
58 | | filled with 4 entries ; whereas the MD5 variant did only collide |
59 | | once ] |
60 | | */ |
61 | | #if (!defined(HTS_INTHASH_USES_MD5) \ |
62 | | && !defined(HTS_INTHASH_USES_OPENSSL_MD5) \ |
63 | | && !defined(HTS_INTHASH_USES_MURMUR) \ |
64 | | && !defined(HTS_INTHASH_USES_FNV1) \ |
65 | | ) |
66 | | /* Temporry: fixing Invalid address alignment issues */ |
67 | | #if (defined(HAVE_ALIGNED_ACCESS_REQUIRED) \ |
68 | | || defined(__sparc__) \ |
69 | | || defined(mips) || defined(__mips__) || defined(MIPS) || defined(_MIPS_) \ |
70 | | || defined(arm) || defined(__arm__) || defined(ARM) || defined(_ARM_) \ |
71 | | ) |
72 | | #ifndef LIBHTTRACK_EXPORTS |
73 | | #define HTS_INTHASH_USES_OPENSSL_MD5 1 |
74 | | #else |
75 | | #define HTS_INTHASH_USES_MD5 1 |
76 | | #endif |
77 | | #else |
78 | | #define HTS_INTHASH_USES_MURMUR 1 |
79 | | #endif |
80 | | #endif |
81 | | |
82 | | /* Dispatch includes */ |
83 | | #if (defined(HTS_INTHASH_USES_MURMUR)) |
84 | | #include "murmurhash3.h" |
85 | | #elif (defined(HTS_INTHASH_USES_MD5)) |
86 | | #include "md5.h" |
87 | | #define HashMD5Init(CTX, FLAG) MD5Init(CTX, FLAG) |
88 | | #define HashMD5Update(CTX, DATA, SIZE) MD5Update(CTX, DATA, SIZE) |
89 | | #define HashMD5Final(DIGEST, CTX) MD5Final(DIGEST, CTX) |
90 | | #define HashMD5Context MD5CTX |
91 | | #elif (defined(HTS_INTHASH_USES_OPENSSL_MD5)) |
92 | | /* OpenSSL's low-level MD5_Init/Update/Final were deprecated in OpenSSL 3.0; |
93 | | drive MD5 through the EVP interface instead. HashMD5Context is the context |
94 | | pointer, so the &ctx passed at the call sites is EVP_MD_CTX** -- Init |
95 | | allocates it, Final digests and frees it. */ |
96 | | #include <openssl/evp.h> |
97 | | #define HashMD5Context EVP_MD_CTX * |
98 | | /* EVP_MD_CTX_new() allocates and can return NULL under OOM (the low-level |
99 | | MD5_Init it replaces could not fail). Guard it the same way coucal guards |
100 | | every other allocation -- coucal_assert() routes NULL through the fatal |
101 | | handler / abort() -- so we never dereference a NULL ctx in EVP_DigestInit_ex. */ |
102 | | #define HashMD5Init(CTX, FLAG) \ |
103 | | (coucal_assert(NULL, (*(CTX) = EVP_MD_CTX_new()) != NULL), \ |
104 | | EVP_DigestInit_ex(*(CTX), EVP_md5(), NULL)) |
105 | | #define HashMD5Update(CTX, DATA, SIZE) EVP_DigestUpdate(*(CTX), DATA, SIZE) |
106 | | #define HashMD5Final(DIGEST, CTX) \ |
107 | | do { \ |
108 | | unsigned int md5len_ = 0; \ |
109 | | EVP_DigestFinal_ex(*(CTX), DIGEST, &md5len_); \ |
110 | | EVP_MD_CTX_free(*(CTX)); \ |
111 | | } while (0) |
112 | | #elif (defined(HTS_INTHASH_USES_FNV1)) |
113 | | /* FNV-1 is computed inline in coucal_hash_data(); no external header needed. */ |
114 | | #else |
115 | | #error "No hash method defined" |
116 | | #endif |
117 | | |
118 | | /** Size of auxiliary stash. **/ |
119 | 0 | #define STASH_SIZE 16 |
120 | | |
121 | | /** Minimum value for lg_size. **/ |
122 | 0 | #define MIN_LG_SIZE 4 |
123 | | |
124 | | /** Minimum value for pool.capacity. **/ |
125 | 0 | #define MIN_POOL_CAPACITY 256 |
126 | | |
127 | | /* 64-bit constant */ |
128 | | #if (defined(WIN32)) |
129 | | #define UINT_64_CONST(X) ((uint64_t) (X)) |
130 | | #elif (defined(_LP64) || defined(__x86_64__) \ |
131 | | || defined(__powerpc64__) || defined(__64BIT__)) |
132 | | #define UINT_64_CONST(X) ((uint64_t) X##UL) |
133 | | #else |
134 | | #define UINT_64_CONST(X) ((uint64_t) X##ULL) |
135 | | #endif |
136 | | |
137 | | /* printf length modifier for uint64_t. The hand-rolled "ld"/"lld" guess broke |
138 | | on LP64 platforms where uint64_t is unsigned long long rather than unsigned |
139 | | long (e.g. Apple arm64), tripping clang -Wformat. PRIu64 matches uint64_t |
140 | | exactly everywhere; all UINT_64_FORMAT arguments are (uint64_t)-cast counts. */ |
141 | 0 | #define UINT_64_FORMAT PRIu64 |
142 | | |
143 | | /* printf length modifier for a coucal_hashkey printed in hex (debug traces). |
144 | | A bare "%x" only matches a 32-bit hashkey; on COUCAL_HASH_SIZE==64 the |
145 | | argument is 64-bit, so those trace sites cast to uint64_t and use this. */ |
146 | 0 | #define UINT_64_HEX_FORMAT PRIx64 |
147 | | |
148 | | /** Hashtable. **/ |
149 | | struct struct_coucal { |
150 | | /** Hashtable items. **/ |
151 | | coucal_item *items; |
152 | | |
153 | | /** Log-2 of the hashtable size. **/ |
154 | | size_t lg_size; |
155 | | |
156 | | /** Number of used items (<= POW2(lg_size)). **/ |
157 | | size_t used; |
158 | | |
159 | | /** Stash area for collisions. **/ |
160 | | struct { |
161 | | /** Stash items. **/ |
162 | | coucal_item items[STASH_SIZE]; |
163 | | |
164 | | /** Stash size (<= STASH_SIZE), holes excluded. **/ |
165 | | size_t size; |
166 | | |
167 | | /** Slots in use ; a deletion leaves a hole rather than shifting. **/ |
168 | | size_t extent; |
169 | | } stash; |
170 | | |
171 | | /** String pool. **/ |
172 | | struct { |
173 | | /** String buffer. **/ |
174 | | char *buffer; |
175 | | /** Buffer used size (high watermark). **/ |
176 | | size_t size; |
177 | | /** Buffer capacity. **/ |
178 | | size_t capacity; |
179 | | /** Used chars (== size if compacted). **/ |
180 | | size_t used; |
181 | | } pool; |
182 | | |
183 | | /** Statistics **/ |
184 | | struct { |
185 | | /** Highest stash.size seen. **/ |
186 | | size_t max_stash_size; |
187 | | /** Number of writes. **/ |
188 | | size_t write_count; |
189 | | /** Number of writes causing an add. **/ |
190 | | size_t add_count; |
191 | | /** Number of cuckoo moved during adds. **/ |
192 | | size_t cuckoo_moved; |
193 | | /** Number of items added to stash. **/ |
194 | | size_t stash_added; |
195 | | /** Number of hashtable rehash/expand operations. **/ |
196 | | size_t rehash_count; |
197 | | /** Number of pool compact operations. **/ |
198 | | size_t pool_compact_count; |
199 | | /** Number of pool realloc operations. **/ |
200 | | size_t pool_realloc_count; |
201 | | } stats; |
202 | | |
203 | | /** Settings. **/ |
204 | | struct { |
205 | | /** How to handle values (might be NULL). **/ |
206 | | struct { |
207 | | /** free() **/ |
208 | | t_coucal_value_freehandler free; |
209 | | /** opaque argument **/ |
210 | | coucal_opaque arg; |
211 | | } value; |
212 | | |
213 | | /** How to handle names (might be NULL). **/ |
214 | | struct { |
215 | | /** strdup() **/ |
216 | | t_coucal_duphandler dup; |
217 | | /** free() **/ |
218 | | t_coucal_key_freehandler free; |
219 | | /** hash **/ |
220 | | t_coucal_hasheshandler hash; |
221 | | /** comparison **/ |
222 | | t_coucal_cmphandler equals; |
223 | | /** opaque argument **/ |
224 | | coucal_opaque arg; |
225 | | } key; |
226 | | |
227 | | /** How to handle fatal assertions (might be NULL). **/ |
228 | | struct { |
229 | | /** logging **/ |
230 | | t_coucal_loghandler log; |
231 | | /** abort() **/ |
232 | | t_coucal_asserthandler fatal; |
233 | | /** opaque argument **/ |
234 | | coucal_opaque arg; |
235 | | /** hashtable name for logging **/ |
236 | | coucal_key_const name; |
237 | | } error; |
238 | | |
239 | | /** How to handle pretty-print (debug) (might be NULL). **/ |
240 | | struct { |
241 | | /** key print() **/ |
242 | | t_coucal_printkeyhandler key; |
243 | | /** value print() **/ |
244 | | t_coucal_printvaluehandler value; |
245 | | /** opaque argument **/ |
246 | | coucal_opaque arg; |
247 | | } print; |
248 | | } custom; |
249 | | }; |
250 | | |
251 | | /* Assertion check. */ |
252 | | #define coucal_assert(HASHTABLE, EXP) \ |
253 | 0 | (void)( (EXP) || (coucal_assert_failed(HASHTABLE, #EXP, __FILE__, __LINE__), 0) ) |
254 | | |
255 | | /* Compiler-specific. */ |
256 | | #ifdef __GNUC__ |
257 | | #define INTHASH_PRINTF_FUN(fmt, arg) __attribute__ ((format (printf, fmt, arg))) |
258 | | #define INTHASH_UNUSED_FUN __attribute__((unused)) |
259 | | #define INTHASH_INLINE __inline__ |
260 | | #elif (defined(_MSC_VER)) |
261 | | #define INTHASH_PRINTF_FUN(FMT, ARGS) |
262 | | #define INTHASH_UNUSED_FUN |
263 | | #define INTHASH_INLINE __inline |
264 | | #else |
265 | | #define INTHASH_PRINTF_FUN(FMT, ARGS) |
266 | | #define INTHASH_UNUSED_FUN |
267 | | #define INTHASH_INLINE |
268 | | #endif |
269 | | |
270 | | /* Logging level. */ |
271 | | static void coucal_log(const coucal hashtable, coucal_loglevel level, |
272 | | const char *format, va_list args); |
273 | | #define DECLARE_LOG_FUNCTION(NAME, LEVEL) \ |
274 | | static void NAME(const coucal hashtable, const char *format, ...) \ |
275 | | INTHASH_PRINTF_FUN(2, 3) INTHASH_UNUSED_FUN; \ |
276 | 0 | static void NAME(const coucal hashtable, const char *format, ...) { \ |
277 | 0 | va_list args; \ |
278 | 0 | va_start(args, format); \ |
279 | 0 | coucal_log(hashtable, LEVEL, format, args); \ |
280 | 0 | va_end(args); \ |
281 | 0 | } Unexecuted instantiation: coucal.c:coucal_do_debug Unexecuted instantiation: coucal.c:coucal_do_trace |
282 | | /* a compiled-out level must not evaluate its args ; -Wformat still applies */ |
283 | 0 | #define COUCAL_NEVER while (0) |
284 | 0 | #define COUCAL_NO_LOG COUCAL_NEVER coucal_nolog |
285 | | |
286 | | /* all levels always compile, tagged unused so consumers need no extra flag */ |
287 | 0 | DECLARE_LOG_FUNCTION(coucal_do_crit, coucal_log_critical) |
288 | 0 | DECLARE_LOG_FUNCTION(coucal_do_warning, coucal_log_warning) |
289 | 0 | DECLARE_LOG_FUNCTION(coucal_do_info, coucal_log_info) |
290 | | DECLARE_LOG_FUNCTION(coucal_do_debug, coucal_log_debug) |
291 | | DECLARE_LOG_FUNCTION(coucal_do_trace, coucal_log_trace) |
292 | | |
293 | | /* critical carries assertion failures and is never compiled out */ |
294 | 0 | #define coucal_crit coucal_do_crit |
295 | | |
296 | | #if COUCAL_LOG_LEVEL >= COUCAL_LOG_WARNING |
297 | 0 | #define coucal_warning coucal_do_warning |
298 | | #else |
299 | | #define coucal_warning COUCAL_NO_LOG |
300 | | #endif |
301 | | |
302 | | #if COUCAL_LOG_LEVEL >= COUCAL_LOG_INFO |
303 | 0 | #define coucal_info coucal_do_info |
304 | | #else |
305 | | #define coucal_info COUCAL_NO_LOG |
306 | | #endif |
307 | | |
308 | | #if COUCAL_LOG_LEVEL >= COUCAL_LOG_DEBUG |
309 | | #define coucal_debug coucal_do_debug |
310 | | #else |
311 | 0 | #define coucal_debug COUCAL_NO_LOG |
312 | | #endif |
313 | | |
314 | | #if COUCAL_LOG_LEVEL >= COUCAL_LOG_TRACE |
315 | | #define coucal_trace coucal_do_trace |
316 | | #else |
317 | 0 | #define coucal_trace COUCAL_NO_LOG |
318 | | #endif |
319 | | |
320 | | /* 2**X */ |
321 | 0 | #define POW2(X) ( (size_t) 1 << (X) ) |
322 | | |
323 | | /* the empty string for the string pool ; shared process-wide, hence const */ |
324 | | static const char the_empty_string[1] = {0}; |
325 | | |
326 | | /* global assertion handler */ |
327 | | static t_coucal_asserthandler global_assert_handler = NULL; |
328 | | |
329 | | /* global assertion handler */ |
330 | | static t_coucal_loghandler global_log_handler = NULL; |
331 | | |
332 | | /* default assertion handler, if neither hashtable one nor global one |
333 | | were defined */ |
334 | 0 | static void coucal_fail(const char* exp, const char* file, int line) { |
335 | 0 | fprintf(stderr, "assertion '%s' failed at %s:%d\n", exp, file, line); |
336 | 0 | abort(); |
337 | 0 | } |
338 | | |
339 | | /* assert failed handler. */ |
340 | 0 | static void coucal_assert_failed(const coucal hashtable, const char* exp, const char* file, int line) { |
341 | 0 | const char *const name = coucal_get_name(hashtable); |
342 | 0 | coucal_crit(hashtable, "hashtable %s: %s failed at %s:%d", |
343 | 0 | name != NULL ? name : "<unknown>", exp, file, line); |
344 | 0 | if (hashtable != NULL && hashtable->custom.error.fatal != NULL) { |
345 | 0 | hashtable->custom.error.fatal(hashtable->custom.error.arg, exp, file, line); |
346 | 0 | } else if (global_assert_handler != NULL) { |
347 | 0 | global_assert_handler(hashtable, exp, file, line); |
348 | 0 | } else { |
349 | 0 | coucal_fail(exp, file, line); |
350 | 0 | } |
351 | 0 | abort(); |
352 | 0 | } |
353 | | |
354 | | /* Logging */ |
355 | | static void coucal_log(const coucal hashtable, coucal_loglevel level, |
356 | 0 | const char *format, va_list args) { |
357 | 0 | coucal_assert(hashtable, format != NULL); |
358 | 0 | if (hashtable != NULL && hashtable->custom.error.log != NULL) { |
359 | 0 | hashtable->custom.error.log(hashtable->custom.error.arg, level, format, args); |
360 | 0 | } else if (global_log_handler != NULL) { |
361 | 0 | global_log_handler(hashtable, level, format, args); |
362 | 0 | } else { |
363 | 0 | fprintf(stderr, "[%p] ", (void*) hashtable); |
364 | 0 | (void) vfprintf(stderr, format, args); |
365 | 0 | putc('\n', stderr); |
366 | 0 | } |
367 | 0 | } |
368 | | |
369 | | /* No logging (should be dropped by the compiler) */ |
370 | | static INTHASH_INLINE void coucal_nolog(const coucal hashtable, |
371 | | const char *format, ...) |
372 | | INTHASH_PRINTF_FUN(2, 3) INTHASH_UNUSED_FUN; |
373 | | static INTHASH_INLINE void coucal_nolog(const coucal hashtable, |
374 | 0 | const char *format, ...) { |
375 | 0 | (void) hashtable; |
376 | 0 | (void) format; |
377 | 0 | } |
378 | | |
379 | 0 | const char* coucal_get_name(coucal hashtable) { |
380 | | /* the assertion path calls this with a NULL table (see HashMD5Init) */ |
381 | 0 | return hashtable != NULL ? hashtable->custom.error.name : NULL; |
382 | 0 | } |
383 | | |
384 | 0 | static void coucal_log_stats(coucal hashtable) { |
385 | 0 | const char *const name = coucal_get_name(hashtable); |
386 | 0 | const double avg_moved = |
387 | 0 | hashtable->stats.add_count != 0 |
388 | 0 | ? (double) hashtable->stats.cuckoo_moved / hashtable->stats.add_count |
389 | 0 | : 0.0; |
390 | | /* clang-format off */ |
391 | 0 | coucal_info(hashtable, "hashtable %s%s%ssummary: " |
392 | 0 | "size=%"UINT_64_FORMAT" (lg2=%"UINT_64_FORMAT") " |
393 | 0 | "used=%"UINT_64_FORMAT" " |
394 | 0 | "stash-size=%"UINT_64_FORMAT" " |
395 | 0 | "pool-size=%"UINT_64_FORMAT" " |
396 | 0 | "pool-capacity=%"UINT_64_FORMAT" " |
397 | 0 | "pool-used=%"UINT_64_FORMAT" " |
398 | 0 | "writes=%"UINT_64_FORMAT" " |
399 | 0 | "(new=%"UINT_64_FORMAT") " |
400 | 0 | "moved=%"UINT_64_FORMAT " " |
401 | 0 | "stashed=%"UINT_64_FORMAT" " |
402 | 0 | "max-stash-size=%"UINT_64_FORMAT" " |
403 | 0 | "avg-moved=%g " |
404 | 0 | "rehash=%"UINT_64_FORMAT" " |
405 | 0 | "pool-compact=%"UINT_64_FORMAT" " |
406 | 0 | "pool-realloc=%"UINT_64_FORMAT" " |
407 | 0 | "memory=%"UINT_64_FORMAT, |
408 | 0 | name != NULL ? "\"" : "", |
409 | 0 | name != NULL ? name : "", |
410 | 0 | name != NULL ? "\" " : "", |
411 | 0 | (uint64_t) POW2(hashtable->lg_size), |
412 | 0 | (uint64_t) hashtable->lg_size, |
413 | 0 | (uint64_t) hashtable->used, |
414 | 0 | (uint64_t) hashtable->stash.size, |
415 | 0 | (uint64_t) hashtable->pool.size, |
416 | 0 | (uint64_t) hashtable->pool.capacity, |
417 | 0 | (uint64_t) hashtable->pool.used, |
418 | 0 | (uint64_t) hashtable->stats.write_count, |
419 | 0 | (uint64_t) hashtable->stats.add_count, |
420 | 0 | (uint64_t) hashtable->stats.cuckoo_moved, |
421 | 0 | (uint64_t) hashtable->stats.stash_added, |
422 | 0 | (uint64_t) hashtable->stats.max_stash_size, |
423 | 0 | avg_moved, |
424 | 0 | (uint64_t) hashtable->stats.rehash_count, |
425 | 0 | (uint64_t) hashtable->stats.pool_compact_count, |
426 | 0 | (uint64_t) hashtable->stats.pool_realloc_count, |
427 | 0 | (uint64_t) coucal_memory_size(hashtable) |
428 | 0 | ); |
429 | | /* clang-format on */ |
430 | 0 | } |
431 | | |
432 | | /* default hash function when key is a regular C-string */ |
433 | 0 | coucal_hashkeys coucal_hash_data(const void *data_, size_t size) { |
434 | 0 | const unsigned char *const data = (const unsigned char *) data_; |
435 | | #if (defined(HTS_INTHASH_USES_MD5) || defined(HTS_INTHASH_USES_OPENSSL_MD5)) |
436 | | /* compute a regular MD5 and extract two 32-bit integers */ |
437 | | HashMD5Context ctx; |
438 | | union { |
439 | | unsigned char md5digest[16]; |
440 | | #if (COUCAL_HASH_SIZE == 32) |
441 | | coucal_hashkeys mhashes[2]; |
442 | | #endif |
443 | | coucal_hashkeys hashes; |
444 | | } u; |
445 | | size_t offset; |
446 | | |
447 | | /* compute MD5 ; fed by chunks, as the update size is an unsigned int */ |
448 | | HashMD5Init(&ctx, 0); |
449 | | for (offset = 0; offset < size;) { |
450 | | const size_t remaining = size - offset; |
451 | | const unsigned int chunk = |
452 | | remaining <= 0x10000000 ? (unsigned int) remaining : 0x10000000; |
453 | | HashMD5Update(&ctx, data + offset, chunk); |
454 | | offset += chunk; |
455 | | } |
456 | | HashMD5Final(u.md5digest, &ctx); |
457 | | |
458 | | #if (COUCAL_HASH_SIZE == 32) |
459 | | /* mix mix mix */ |
460 | | u.mhashes[0].hash1 ^= u.mhashes[1].hash1; |
461 | | u.mhashes[0].hash2 ^= u.mhashes[1].hash2; |
462 | | #endif |
463 | | |
464 | | /* do not keep identical hashes */ |
465 | | if (u.hashes.hash1 == u.hashes.hash2) { |
466 | | u.hashes.hash2 = ~u.hashes.hash2; |
467 | | } |
468 | | |
469 | | return u.hashes; |
470 | | #elif (defined(HTS_INTHASH_USES_MURMUR)) |
471 | | union { |
472 | 0 | uint32_t result[4]; |
473 | 0 | coucal_hashkeys hashes; |
474 | 0 | } u; |
475 | 0 | MurmurHash3_x86_128(data, size, 42, &u.result); |
476 | |
|
477 | 0 | #if (COUCAL_HASH_SIZE == 32) |
478 | | /* mix mix mix */ |
479 | 0 | u.result[0] ^= u.result[2]; |
480 | 0 | u.result[1] ^= u.result[3]; |
481 | 0 | #endif |
482 | | |
483 | | /* do not keep identical hashes */ |
484 | 0 | if (u.hashes.hash1 == u.hashes.hash2) { |
485 | 0 | u.hashes.hash2 = ~u.hashes.hash2; |
486 | 0 | } |
487 | |
|
488 | 0 | return u.hashes; |
489 | | #elif (defined(HTS_INTHASH_USES_FNV1)) |
490 | | /* compute two Fowler-Noll-Vo hashes (64-bit FNV-1 variant) ; |
491 | | each 64-bit hash being XOR-folded into a single 32-bit hash. */ |
492 | | size_t i; |
493 | | coucal_hashkeys hashes; |
494 | | uint64_t h1, h2; |
495 | | |
496 | | /* FNV-1, 64-bit. */ |
497 | | #define FNV1_PRIME UINT_64_CONST(1099511628211) |
498 | | #define FNV1_OFFSET_BASIS UINT_64_CONST(14695981039346656037) |
499 | | |
500 | | /* compute the hashes ; second variant is using xored data */ |
501 | | h1 = FNV1_OFFSET_BASIS; |
502 | | h2 = ~FNV1_OFFSET_BASIS; |
503 | | for(i = 0 ; i < size ; i++) { |
504 | | const unsigned char c1 = data[i]; |
505 | | const unsigned char c2 = ~c1; |
506 | | h1 = ( h1 * FNV1_PRIME ) ^ c1; |
507 | | h2 = ( h2 * FNV1_PRIME ) ^ c2; |
508 | | } |
509 | | |
510 | | #if (COUCAL_HASH_SIZE == 32) |
511 | | /* XOR-folding to improve diffusion (Wikipedia) */ |
512 | | hashes.hash1 = ( (uint32_t) h1 ^ (uint32_t) ( h1 >> 32 ) ); |
513 | | hashes.hash2 = ( (uint32_t) h2 ^ (uint32_t) ( h2 >> 32 ) ); |
514 | | #elif (COUCAL_HASH_SIZE == 64) |
515 | | /* Direct hashes */ |
516 | | hashes.hash1 = h1; |
517 | | hashes.hash2 = h2; |
518 | | #else |
519 | | #error "Unsupported COUCAL_HASH_SIZE" |
520 | | #endif |
521 | | |
522 | | #undef FNV1_PRIME |
523 | | #undef FNV1_OFFSET_BASIS |
524 | | |
525 | | /* do not keep identical hashes */ |
526 | | if (hashes.hash1 == hashes.hash2) { |
527 | | hashes.hash2 = ~hashes.hash2; |
528 | | } |
529 | | |
530 | | return hashes; |
531 | | |
532 | | #else |
533 | | #error "Undefined hashing method" |
534 | | #endif |
535 | 0 | } |
536 | | |
537 | 0 | INTHASH_INLINE coucal_hashkeys coucal_hash_string(const char *name) { |
538 | 0 | return coucal_hash_data(name, strlen(name)); |
539 | 0 | } |
540 | | |
541 | | INTHASH_INLINE coucal_hashkeys coucal_calc_hashes(coucal hashtable, |
542 | 0 | coucal_key_const value) { |
543 | 0 | return hashtable->custom.key.hash == NULL |
544 | 0 | ? coucal_hash_string(value) |
545 | 0 | : hashtable->custom.key.hash(hashtable->custom.key.arg, value); |
546 | 0 | } |
547 | | |
548 | | /* position 'pos' is free ? */ |
549 | 0 | static INTHASH_INLINE int coucal_is_free(const coucal hashtable, size_t pos) { |
550 | 0 | return hashtable->items[pos].name == NULL; |
551 | 0 | } |
552 | | |
553 | | /* compare two keys ; by default using strcmp() */ |
554 | | static INTHASH_INLINE int coucal_equals(coucal hashtable, |
555 | | coucal_key_const a, |
556 | 0 | coucal_key_const b) { |
557 | 0 | return hashtable->custom.key.equals == NULL |
558 | 0 | ? strcmp((const char*) a, (const char*) b) == 0 |
559 | 0 | : hashtable->custom.key.equals(hashtable->custom.key.arg, a, b); |
560 | 0 | } |
561 | | |
562 | | static INTHASH_INLINE int coucal_matches_(coucal hashtable, |
563 | | const coucal_item *const item, |
564 | | coucal_key_const name, |
565 | 0 | const coucal_hashkeys *hashes) { |
566 | 0 | return item->name != NULL |
567 | 0 | && item->hashes.hash1 == hashes->hash1 |
568 | 0 | && item->hashes.hash2 == hashes->hash2 |
569 | 0 | && coucal_equals(hashtable, item->name, name); |
570 | 0 | } |
571 | | |
572 | | static INTHASH_INLINE int coucal_matches(coucal hashtable, size_t pos, |
573 | | coucal_key_const name, |
574 | 0 | const coucal_hashkeys *hashes) { |
575 | 0 | const coucal_item *const item = &hashtable->items[pos]; |
576 | 0 | return coucal_matches_(hashtable, item, name, hashes); |
577 | 0 | } |
578 | | |
579 | | /* compact string pool ; does not necessarily change the capacity */ |
580 | 0 | static void coucal_compact_pool(coucal hashtable, size_t capacity) { |
581 | 0 | const size_t hash_size = POW2(hashtable->lg_size); |
582 | 0 | size_t i; |
583 | 0 | char*const old_pool = hashtable->pool.buffer; |
584 | 0 | const size_t old_size = hashtable->pool.size; |
585 | 0 | size_t count = 0; |
586 | | |
587 | | /* we manage the string pool */ |
588 | 0 | coucal_assert(hashtable, hashtable->custom.key.dup == NULL); |
589 | | |
590 | | /* statistics */ |
591 | 0 | hashtable->stats.pool_compact_count++; |
592 | | |
593 | | /* change capacity now */ |
594 | 0 | if (hashtable->pool.capacity != capacity) { |
595 | 0 | hashtable->pool.capacity = capacity; |
596 | 0 | } |
597 | | |
598 | | /* realloc */ |
599 | 0 | hashtable->pool.buffer = malloc(hashtable->pool.capacity); |
600 | 0 | hashtable->pool.size = 0; |
601 | 0 | hashtable->pool.used = 0; |
602 | 0 | if (hashtable->pool.buffer == NULL) { |
603 | 0 | coucal_debug(hashtable, |
604 | 0 | "** hashtable string pool compaction error: could not allocate " |
605 | 0 | "%"UINT_64_FORMAT" bytes", |
606 | 0 | (uint64_t) hashtable->pool.capacity); |
607 | 0 | coucal_assert(hashtable, ! "hashtable string pool compaction error"); |
608 | 0 | } |
609 | | |
610 | | /* relocate a string on a different pool */ |
611 | 0 | #define RELOCATE_STRING(S) do { \ |
612 | 0 | if (S != NULL && S != the_empty_string) { \ |
613 | 0 | const char *const src = (S); \ |
614 | 0 | char *const dest = \ |
615 | 0 | &hashtable->pool.buffer[hashtable->pool.size]; \ |
616 | 0 | const size_t capacity = hashtable->pool.capacity; \ |
617 | 0 | char *const max_dest = \ |
618 | 0 | &hashtable->pool.buffer[capacity]; \ |
619 | | /* copy string */ \ |
620 | 0 | coucal_assert(hashtable, dest < max_dest); \ |
621 | 0 | dest[0] = src[0]; \ |
622 | 0 | { \ |
623 | 0 | size_t i; \ |
624 | 0 | for(i = 1 ; src[i - 1] != '\0' ; i++) { \ |
625 | 0 | coucal_assert(hashtable, &dest[i] < max_dest); \ |
626 | 0 | dest[i] = src[i]; \ |
627 | 0 | } \ |
628 | | /* update pool size */ \ |
629 | 0 | hashtable->pool.size += i; \ |
630 | 0 | coucal_assert(hashtable, \ |
631 | 0 | hashtable->pool.size <= capacity); \ |
632 | 0 | } \ |
633 | | /* update source */ \ |
634 | 0 | S = dest; \ |
635 | 0 | count++; \ |
636 | 0 | } \ |
637 | 0 | } while(0) |
638 | | |
639 | | /* relocate */ |
640 | 0 | for(i = 0 ; i < hash_size ; i++) { |
641 | 0 | RELOCATE_STRING(hashtable->items[i].name); |
642 | 0 | } |
643 | 0 | for (i = 0; i < hashtable->stash.extent; i++) { |
644 | 0 | RELOCATE_STRING(hashtable->stash.items[i].name); |
645 | 0 | } |
646 | |
|
647 | 0 | #undef RELOCATE_STRING |
648 | | |
649 | | /* compacted (used chars == current size) */ |
650 | 0 | hashtable->pool.used = hashtable->pool.size; |
651 | | |
652 | | /* wipe previous pool */ |
653 | 0 | free(old_pool); |
654 | |
|
655 | 0 | coucal_debug(hashtable, |
656 | 0 | "compacted string pool for %"UINT_64_FORMAT" strings: " |
657 | 0 | "%"UINT_64_FORMAT" bytes => %"UINT_64_FORMAT" bytes", |
658 | 0 | (uint64_t) count, (uint64_t) old_size, |
659 | 0 | (uint64_t) hashtable->pool.size); |
660 | 0 | } |
661 | | |
662 | | /* realloc (expand) string pool ; does not change the compacity */ |
663 | 0 | static void coucal_realloc_pool(coucal hashtable, size_t capacity) { |
664 | 0 | const size_t hash_size = POW2(hashtable->lg_size); |
665 | | /* keep the old base as an integer: after realloc() the old pointer value is |
666 | | indeterminate and must not be used in pointer arithmetic (-Wuse-after-free) */ |
667 | 0 | const uintptr_t oldbase = (uintptr_t) hashtable->pool.buffer; |
668 | 0 | size_t count = 0; |
669 | | |
670 | | /* we manage the string pool */ |
671 | 0 | coucal_assert(hashtable, hashtable->custom.key.dup == NULL); |
672 | | |
673 | | /* compact instead ? */ |
674 | 0 | if (hashtable->pool.used < ( hashtable->pool.size*3 ) / 4) { |
675 | 0 | coucal_compact_pool(hashtable, capacity); |
676 | 0 | return ; |
677 | 0 | } |
678 | | |
679 | | /* statistics */ |
680 | 0 | hashtable->stats.pool_realloc_count++; |
681 | | |
682 | | /* change capacity now */ |
683 | 0 | hashtable->pool.capacity = capacity; |
684 | | |
685 | | /* realloc */ |
686 | 0 | hashtable->pool.buffer = realloc(hashtable->pool.buffer, |
687 | 0 | hashtable->pool.capacity); |
688 | 0 | if (hashtable->pool.buffer == NULL) { |
689 | 0 | coucal_crit(hashtable, |
690 | 0 | "** hashtable string pool allocation error: could not allocate " |
691 | 0 | "%"UINT_64_FORMAT" bytes", |
692 | 0 | (uint64_t) hashtable->pool.capacity); |
693 | 0 | coucal_assert(hashtable, ! "hashtable string pool allocation error"); |
694 | 0 | } |
695 | | |
696 | | /* recompute string address */ |
697 | 0 | #define RECOMPUTE_STRING(S) do { \ |
698 | 0 | if (S != NULL && S != the_empty_string) { \ |
699 | 0 | const size_t offset = (uintptr_t) (const char*) (S) - oldbase; \ |
700 | 0 | coucal_assert(hashtable, offset < hashtable->pool.capacity); \ |
701 | 0 | S = &hashtable->pool.buffer[offset]; \ |
702 | 0 | count++; \ |
703 | 0 | } \ |
704 | 0 | } while(0) |
705 | | |
706 | | /* recompute string addresses */ |
707 | 0 | if ((uintptr_t) hashtable->pool.buffer != oldbase) { |
708 | 0 | size_t i; |
709 | 0 | for(i = 0 ; i < hash_size ; i++) { |
710 | 0 | RECOMPUTE_STRING(hashtable->items[i].name); |
711 | 0 | } |
712 | 0 | for (i = 0; i < hashtable->stash.extent; i++) { |
713 | 0 | RECOMPUTE_STRING(hashtable->stash.items[i].name); |
714 | 0 | } |
715 | 0 | } |
716 | |
|
717 | 0 | #undef RECOMPUTE_STRING |
718 | |
|
719 | 0 | coucal_debug(hashtable, "reallocated string pool for " |
720 | 0 | "%"UINT_64_FORMAT" strings: %"UINT_64_FORMAT" bytes", |
721 | 0 | (uint64_t) count, (uint64_t) hashtable->pool.capacity); |
722 | 0 | } |
723 | | |
724 | | /* is this key stored inside the string pool ? */ |
725 | | static INTHASH_INLINE int coucal_is_pooled(const coucal hashtable, |
726 | 0 | const char *name) { |
727 | 0 | const uintptr_t base = (uintptr_t) hashtable->pool.buffer; |
728 | 0 | const uintptr_t addr = (uintptr_t) name; |
729 | 0 | return hashtable->pool.buffer != NULL && addr >= base && |
730 | 0 | addr - base < hashtable->pool.capacity; |
731 | 0 | } |
732 | | |
733 | | static coucal_key coucal_dup_name_internal(coucal hashtable, |
734 | 0 | coucal_key_const name_) { |
735 | 0 | const char *name = (const char *) name_; |
736 | 0 | const size_t len = strlen(name) + 1; |
737 | 0 | char *staged = NULL; |
738 | 0 | char *s; |
739 | | |
740 | | /* the pool does not allow empty strings for safety purpose ; handhe that |
741 | | (keys are being emptied when free'd to detect duplicate free) */ |
742 | 0 | if (len == 1) { |
743 | | /* uintptr_t round-trip: this key alone is const, a cast would discard it */ |
744 | 0 | return (coucal_key) (uintptr_t) the_empty_string; |
745 | 0 | } |
746 | | |
747 | | /* expand pool capacity */ |
748 | 0 | coucal_assert(hashtable, hashtable->pool.size <= hashtable->pool.capacity); |
749 | 0 | if (hashtable->pool.capacity - hashtable->pool.size < len) { |
750 | 0 | size_t capacity; |
751 | | |
752 | | /* growing the pool may relocate or free the block `name` points into */ |
753 | 0 | if (coucal_is_pooled(hashtable, name)) { |
754 | 0 | staged = (char *) malloc(len); |
755 | 0 | if (staged == NULL) { |
756 | 0 | coucal_crit(hashtable, |
757 | 0 | "** hashtable key staging error: could not allocate " |
758 | 0 | "%" UINT_64_FORMAT " bytes", |
759 | 0 | (uint64_t) len); |
760 | 0 | coucal_assert(hashtable, !"hashtable key staging error"); |
761 | 0 | } |
762 | 0 | memcpy(staged, name, len); |
763 | 0 | name = staged; |
764 | 0 | } |
765 | |
|
766 | 0 | for(capacity = MIN_POOL_CAPACITY ; capacity < hashtable->pool.size + len |
767 | 0 | ; capacity <<= 1) ; |
768 | 0 | coucal_assert(hashtable, hashtable->pool.size < capacity); |
769 | 0 | coucal_realloc_pool(hashtable, capacity); |
770 | 0 | } |
771 | | |
772 | | /* copy */ |
773 | 0 | coucal_assert(hashtable, len + hashtable->pool.size <= hashtable->pool.capacity); |
774 | 0 | s = &hashtable->pool.buffer[hashtable->pool.size]; |
775 | 0 | memcpy(s, name, len); |
776 | 0 | hashtable->pool.size += len; |
777 | 0 | hashtable->pool.used += len; |
778 | |
|
779 | 0 | if (staged != NULL) { |
780 | 0 | free(staged); |
781 | 0 | } |
782 | |
|
783 | 0 | return s; |
784 | 0 | } |
785 | | |
786 | | /* duplicate a key. default is to use the internal pool. */ |
787 | | static INTHASH_INLINE coucal_key coucal_dup_name(coucal hashtable, |
788 | 0 | coucal_key_const name) { |
789 | 0 | return hashtable->custom.key.dup == NULL |
790 | 0 | ? coucal_dup_name_internal(hashtable, name) |
791 | 0 | : hashtable->custom.key.dup(hashtable->custom.key.arg, name); |
792 | 0 | } |
793 | | |
794 | | /* internal pool free handler. |
795 | | note: pointer must have been kicked from the pool first */ |
796 | 0 | static void coucal_free_key_internal(coucal hashtable, coucal_key name_) { |
797 | 0 | char *const name = (char*) name_; |
798 | 0 | const size_t len = strlen(name) + 1; |
799 | | |
800 | | /* see coucal_dup_name_internal() handling */ |
801 | 0 | if (len == 1 && name == the_empty_string) { |
802 | 0 | return ; |
803 | 0 | } |
804 | | |
805 | 0 | coucal_assert(hashtable, *name != '\0' || !"duplicate or bad string pool release"); |
806 | 0 | hashtable->pool.used -= len; |
807 | 0 | *name = '\0'; /* the string is now invalidated */ |
808 | | |
809 | | /* compact the pool is too many holes */ |
810 | 0 | if (hashtable->pool.used != 0 |
811 | 0 | && hashtable->pool.used < hashtable->pool.size / 2) { |
812 | 0 | size_t capacity = hashtable->pool.capacity; |
813 | | /* compact and shrink */ |
814 | 0 | if (hashtable->pool.used < capacity / 4) { |
815 | 0 | capacity /= 2; |
816 | 0 | } |
817 | 0 | coucal_assert(hashtable, hashtable->pool.used < capacity); |
818 | 0 | coucal_compact_pool(hashtable, capacity); |
819 | 0 | } |
820 | 0 | } |
821 | | |
822 | | /* free a key. default is to use the internal pool. |
823 | | note: pointer must have been kicked from the pool first */ |
824 | 0 | static void coucal_free_key(coucal hashtable, coucal_key name) { |
825 | 0 | if (hashtable->custom.key.free == NULL) { |
826 | 0 | coucal_free_key_internal(hashtable, name); |
827 | 0 | } else { |
828 | 0 | hashtable->custom.key.free(hashtable->custom.key.arg, name); |
829 | 0 | } |
830 | 0 | } |
831 | | |
832 | | static INTHASH_INLINE size_t coucal_hash_to_pos_(size_t lg_size, |
833 | 0 | coucal_hashkey hash) { |
834 | 0 | const coucal_hashkey mask = POW2(lg_size) - 1; |
835 | 0 | return hash & mask; |
836 | 0 | } |
837 | | |
838 | | static INTHASH_INLINE size_t coucal_hash_to_pos(const coucal hashtable, |
839 | 0 | coucal_hashkey hash) { |
840 | 0 | return coucal_hash_to_pos_(hashtable->lg_size, hash); |
841 | 0 | } |
842 | | |
843 | 0 | int coucal_read_pvoid(coucal hashtable, coucal_key_const name, void **pvalue) { |
844 | 0 | coucal_value value = INTHASH_VALUE_NULL; |
845 | 0 | const int ret = |
846 | 0 | coucal_read_value(hashtable, name, (pvalue != NULL) ? &value : NULL); |
847 | 0 | if (pvalue != NULL) |
848 | 0 | *pvalue = value.ptr; |
849 | 0 | return ret; |
850 | 0 | } |
851 | | |
852 | 0 | void* coucal_get_pvoid(coucal hashtable, coucal_key_const name) { |
853 | 0 | void *value; |
854 | 0 | if (!coucal_read_pvoid(hashtable, name, &value)) { |
855 | 0 | return NULL; |
856 | 0 | } |
857 | 0 | return value; |
858 | 0 | } |
859 | | |
860 | 0 | int coucal_write_pvoid(coucal hashtable, coucal_key_const name, void *pvalue) { |
861 | 0 | coucal_value value = INTHASH_VALUE_NULL; |
862 | |
|
863 | 0 | value.ptr = pvalue; |
864 | 0 | return coucal_write_value(hashtable, name, value); |
865 | 0 | } |
866 | | |
867 | 0 | void coucal_add_pvoid(coucal hashtable, coucal_key_const name, void *pvalue) { |
868 | 0 | coucal_value value = INTHASH_VALUE_NULL; |
869 | |
|
870 | 0 | value.ptr = pvalue; |
871 | 0 | coucal_write_value(hashtable, name, value); |
872 | 0 | } |
873 | | |
874 | 0 | int coucal_write(coucal hashtable, coucal_key_const name, intptr_t intvalue) { |
875 | 0 | coucal_value value = INTHASH_VALUE_NULL; |
876 | |
|
877 | 0 | value.intg = intvalue; |
878 | 0 | return coucal_write_value(hashtable, name, value); |
879 | 0 | } |
880 | | |
881 | | static void coucal_default_free_handler(coucal_opaque arg, |
882 | 0 | coucal_value value) { |
883 | 0 | (void) arg; |
884 | 0 | if (value.ptr != NULL) |
885 | 0 | free(value.ptr); |
886 | 0 | } |
887 | | |
888 | 0 | static INTHASH_INLINE void coucal_del_value_(coucal hashtable, coucal_value *pvalue) { |
889 | 0 | if (pvalue->ptr != NULL) { |
890 | 0 | if (hashtable->custom.value.free != NULL) |
891 | 0 | hashtable->custom.value.free(hashtable->custom.value.arg, *pvalue); |
892 | 0 | pvalue->ptr = NULL; |
893 | 0 | } |
894 | 0 | } |
895 | | |
896 | 0 | static INTHASH_INLINE void coucal_del_value(coucal hashtable, size_t pos) { |
897 | 0 | coucal_del_value_(hashtable, &hashtable->items[pos].value); |
898 | 0 | } |
899 | | |
900 | 0 | static void coucal_del_name(coucal hashtable, coucal_item *item) { |
901 | 0 | const coucal_hashkeys nullHash = INTHASH_KEYS_NULL; |
902 | 0 | char *const name = (char*) item->name; |
903 | 0 | item->name = NULL; /* there must be no reference remaining */ |
904 | 0 | item->hashes = nullHash; |
905 | | /* free after detach (we may compact the pool) */ |
906 | 0 | coucal_free_key(hashtable, name); |
907 | 0 | } |
908 | | |
909 | 0 | static void coucal_del_item(coucal hashtable, coucal_item *pitem) { |
910 | 0 | coucal_del_value_(hashtable, &pitem->value); |
911 | 0 | coucal_del_name(hashtable, pitem); |
912 | 0 | } |
913 | | |
914 | | static int coucal_add_item_(coucal hashtable, coucal_item item); |
915 | | static void coucal_drain_stash(coucal hashtable); |
916 | | |
917 | | /* Write (add or replace) a value in the hashtable. */ |
918 | | static int coucal_write_value_(coucal hashtable, coucal_key_const name, |
919 | 0 | coucal_value value) { |
920 | 0 | coucal_item item; |
921 | 0 | size_t pos; |
922 | 0 | const coucal_hashkeys hashes = coucal_calc_hashes(hashtable, name); |
923 | | |
924 | | /* Statistics */ |
925 | 0 | hashtable->stats.write_count++; |
926 | | |
927 | | /* replace at position 1 ? */ |
928 | 0 | pos = coucal_hash_to_pos(hashtable, hashes.hash1); |
929 | 0 | if (coucal_matches(hashtable, pos, name, &hashes)) { |
930 | 0 | coucal_del_value(hashtable, pos); |
931 | 0 | hashtable->items[pos].value = value; |
932 | 0 | return 0; /* replaced */ |
933 | 0 | } |
934 | | |
935 | | /* replace at position 2 ? */ |
936 | 0 | pos = coucal_hash_to_pos(hashtable, hashes.hash2); |
937 | 0 | if (coucal_matches(hashtable, pos, name, &hashes)) { |
938 | 0 | coucal_del_value(hashtable, pos); |
939 | 0 | hashtable->items[pos].value = value; |
940 | 0 | return 0; /* replaced */ |
941 | 0 | } |
942 | | |
943 | | /* replace in the stash ? */ |
944 | 0 | if (hashtable->stash.size != 0) { |
945 | 0 | size_t i; |
946 | 0 | for (i = 0; i < hashtable->stash.extent; i++) { |
947 | 0 | if (coucal_matches_(hashtable, &hashtable->stash.items[i], name, |
948 | 0 | &hashes)) { |
949 | 0 | coucal_del_value_(hashtable, &hashtable->stash.items[i].value); |
950 | 0 | hashtable->stash.items[i].value = value; |
951 | 0 | return 0; /* replaced */ |
952 | 0 | } |
953 | 0 | } |
954 | 0 | } |
955 | | |
956 | | /* Statistics */ |
957 | 0 | hashtable->stats.add_count++; |
958 | | |
959 | | /* the write turned out to be an add: reshuffling is allowed from here on */ |
960 | 0 | if (hashtable->stash.size != 0) { |
961 | 0 | coucal_drain_stash(hashtable); |
962 | 0 | } |
963 | | |
964 | | /* otherwise we need to create a new item */ |
965 | 0 | item.name = coucal_dup_name(hashtable, name); |
966 | 0 | item.value = value; |
967 | 0 | item.hashes = hashes; |
968 | |
|
969 | 0 | return coucal_add_item_(hashtable, item); |
970 | 0 | } |
971 | | |
972 | | /* Return the string representation of a key */ |
973 | | static const char* coucal_print_key(coucal hashtable, |
974 | 0 | coucal_key_const name) { |
975 | 0 | return hashtable->custom.print.key != NULL |
976 | 0 | ? hashtable->custom.print.key(hashtable->custom.print.arg, name) |
977 | 0 | : (const char*) name; |
978 | 0 | } |
979 | | |
980 | | /* Add a new item in the hashtable. The item SHALL NOT be already present. */ |
981 | 0 | static int coucal_add_item_(coucal hashtable, coucal_item item) { |
982 | 0 | coucal_hashkey cuckoo_hash, initial_cuckoo_hash; |
983 | 0 | size_t loops; |
984 | 0 | size_t pos; |
985 | | |
986 | | /* place at free position 1 ? */ |
987 | 0 | pos = coucal_hash_to_pos(hashtable, item.hashes.hash1); |
988 | 0 | if (coucal_is_free(hashtable, pos)) { |
989 | 0 | hashtable->items[pos] = item; |
990 | 0 | return 1; /* added */ |
991 | 0 | } else { |
992 | | /* place at free position 2 ? */ |
993 | 0 | pos = coucal_hash_to_pos(hashtable, item.hashes.hash2); |
994 | 0 | if (coucal_is_free(hashtable, pos)) { |
995 | 0 | hashtable->items[pos] = item; |
996 | 0 | return 1; /* added */ |
997 | 0 | } |
998 | | /* prepare cuckoo ; let's take position 1 */ |
999 | 0 | else { |
1000 | 0 | cuckoo_hash = initial_cuckoo_hash = item.hashes.hash1; |
1001 | 0 | coucal_trace(hashtable, |
1002 | 0 | "debug:collision with '%s' at %"UINT_64_FORMAT |
1003 | 0 | " (%"UINT_64_HEX_FORMAT")", |
1004 | 0 | coucal_print_key(hashtable, item.name), |
1005 | 0 | (uint64_t) pos, (uint64_t) cuckoo_hash); |
1006 | 0 | } |
1007 | 0 | } |
1008 | | |
1009 | | /* put 'item' in place with hash 'cuckoo_hash' */ |
1010 | 0 | for(loops = POW2(hashtable->lg_size) ; loops != 0 ; --loops) { |
1011 | 0 | const size_t pos = coucal_hash_to_pos(hashtable, cuckoo_hash); |
1012 | |
|
1013 | 0 | coucal_trace(hashtable, |
1014 | 0 | "\tdebug:placing cuckoo '%s' at %"UINT_64_FORMAT |
1015 | 0 | " (%"UINT_64_HEX_FORMAT")", |
1016 | 0 | coucal_print_key(hashtable, item.name), |
1017 | 0 | (uint64_t) pos, (uint64_t) cuckoo_hash); |
1018 | | |
1019 | | /* place at alternate free position ? */ |
1020 | 0 | if (coucal_is_free(hashtable, pos)) { |
1021 | 0 | coucal_trace(hashtable, "debug:free position"); |
1022 | 0 | hashtable->items[pos] = item; |
1023 | 0 | return 1; /* added */ |
1024 | 0 | } |
1025 | | /* then cuckoo's place it is */ |
1026 | 0 | else { |
1027 | | /* replace */ |
1028 | 0 | const coucal_item backup_item = hashtable->items[pos]; |
1029 | 0 | hashtable->items[pos] = item; |
1030 | | |
1031 | | /* statistics */ |
1032 | 0 | hashtable->stats.cuckoo_moved++; |
1033 | | |
1034 | | /* take care of new lost item */ |
1035 | 0 | item = backup_item; |
1036 | | |
1037 | | /* we just kicked this item from its position 1 */ |
1038 | 0 | if (pos == coucal_hash_to_pos(hashtable, item.hashes.hash1)) { |
1039 | | /* then place it on position 2 on next run */ |
1040 | 0 | coucal_trace(hashtable, "\tdebug:position 1"); |
1041 | 0 | cuckoo_hash = item.hashes.hash2; |
1042 | 0 | } |
1043 | | /* we just kicked this item from its position 2 */ |
1044 | 0 | else if (pos == coucal_hash_to_pos(hashtable, item.hashes.hash2)) { |
1045 | | /* then place it on position 1 on next run */ |
1046 | 0 | coucal_trace(hashtable, "\tdebug:position 2"); |
1047 | 0 | cuckoo_hash = item.hashes.hash1; |
1048 | 0 | } |
1049 | 0 | else { |
1050 | 0 | coucal_assert(hashtable, ! "hashtable internal error: unexpected position"); |
1051 | 0 | } |
1052 | | |
1053 | | /* we are looping (back to same hash) */ |
1054 | | /* TODO FIXME: we should actually check the positions */ |
1055 | 0 | if (cuckoo_hash == initial_cuckoo_hash) { |
1056 | | /* emergency stash */ |
1057 | 0 | break; |
1058 | 0 | } |
1059 | 0 | } |
1060 | 0 | } |
1061 | | |
1062 | | /* emergency stashing for the rare cases of collisions */ |
1063 | 0 | if (hashtable->stash.size < STASH_SIZE) { |
1064 | 0 | size_t i; |
1065 | 0 | for (i = 0; |
1066 | 0 | i < hashtable->stash.extent && hashtable->stash.items[i].name != NULL; |
1067 | 0 | i++) |
1068 | 0 | ; |
1069 | 0 | if (i == hashtable->stash.extent) { |
1070 | 0 | hashtable->stash.extent++; |
1071 | 0 | } |
1072 | 0 | hashtable->stash.items[i] = item; |
1073 | 0 | hashtable->stash.size++; |
1074 | | /* for statistics */ |
1075 | 0 | hashtable->stats.stash_added++; |
1076 | 0 | if (hashtable->stash.size > hashtable->stats.max_stash_size) { |
1077 | 0 | hashtable->stats.max_stash_size = hashtable->stash.size; |
1078 | 0 | } |
1079 | 0 | coucal_debug(hashtable, "used stash because of collision (%d entries)", |
1080 | 0 | (int) hashtable->stash.size); |
1081 | 0 | return 1; /* added */ |
1082 | 0 | } else { |
1083 | | /* debugging */ |
1084 | 0 | if (hashtable->custom.print.key != NULL |
1085 | 0 | && hashtable->custom.print.value != NULL) { |
1086 | 0 | size_t i; |
1087 | 0 | for (i = 0; i < hashtable->stash.extent; i++) { |
1088 | 0 | coucal_item *const item = &hashtable->stash.items[i]; |
1089 | 0 | const size_t pos1 = coucal_hash_to_pos(hashtable, item->hashes.hash1); |
1090 | 0 | const size_t pos2 = coucal_hash_to_pos(hashtable, item->hashes.hash2); |
1091 | 0 | if (item->name == NULL) { |
1092 | 0 | continue; |
1093 | 0 | } |
1094 | 0 | coucal_crit(hashtable, |
1095 | 0 | "stash[%u]: key='%s' value='%s' pos1=%d pos2=%d" |
1096 | 0 | " hash1=%04"UINT_64_HEX_FORMAT" hash2=%04"UINT_64_HEX_FORMAT, |
1097 | 0 | (int) i, |
1098 | 0 | hashtable->custom.print.key(hashtable->custom.print.arg, item->name), |
1099 | 0 | hashtable->custom.print.value(hashtable->custom.print.arg, item->value), |
1100 | 0 | (int) pos1, (int) pos2, |
1101 | 0 | (uint64_t) item->hashes.hash1, (uint64_t) item->hashes.hash2); |
1102 | 0 | if (!coucal_is_free(hashtable, pos1)) { |
1103 | 0 | coucal_item *const item = &hashtable->items[pos1]; |
1104 | 0 | const size_t pos1 = coucal_hash_to_pos(hashtable, item->hashes.hash1); |
1105 | 0 | const size_t pos2 = coucal_hash_to_pos(hashtable, item->hashes.hash2); |
1106 | 0 | coucal_crit(hashtable, |
1107 | 0 | "\t.. collisionning with key='%s' value='%s' pos1=%d pos2=%d" |
1108 | 0 | " hash1=%04"UINT_64_HEX_FORMAT" hash2=%04"UINT_64_HEX_FORMAT, |
1109 | 0 | hashtable->custom.print.key(hashtable->custom.print.arg, item->name), |
1110 | 0 | hashtable->custom.print.value(hashtable->custom.print.arg, item->value), |
1111 | 0 | (int) pos1, (int) pos2, |
1112 | 0 | (uint64_t) item->hashes.hash1, (uint64_t) item->hashes.hash2); |
1113 | 0 | } else { |
1114 | 0 | coucal_crit(hashtable, "\t.. collisionning with a free slot (%d)!", (int) pos1); |
1115 | 0 | } |
1116 | 0 | if (!coucal_is_free(hashtable, pos2)) { |
1117 | 0 | coucal_item *const item = &hashtable->items[pos2]; |
1118 | 0 | const size_t pos1 = coucal_hash_to_pos(hashtable, item->hashes.hash1); |
1119 | 0 | const size_t pos2 = coucal_hash_to_pos(hashtable, item->hashes.hash2); |
1120 | 0 | coucal_crit(hashtable, |
1121 | 0 | "\t.. collisionning with key='%s' value='%s' pos1=%d pos2=%d" |
1122 | 0 | " hash1=%04"UINT_64_HEX_FORMAT" hash2=%04"UINT_64_HEX_FORMAT, |
1123 | 0 | hashtable->custom.print.key(hashtable->custom.print.arg, item->name), |
1124 | 0 | hashtable->custom.print.value(hashtable->custom.print.arg, item->value), |
1125 | 0 | (int) pos1, (int) pos2, |
1126 | 0 | (uint64_t) item->hashes.hash1, (uint64_t) item->hashes.hash2); |
1127 | 0 | } else { |
1128 | 0 | coucal_crit(hashtable, "\t.. collisionning with a free slot (%d)!", (int) pos2); |
1129 | 0 | } |
1130 | 0 | } |
1131 | 0 | } |
1132 | | |
1133 | | /* we are doomed. hopefully the probability is lower than being killed |
1134 | | by a wandering radioactive monkey */ |
1135 | 0 | coucal_log_stats(hashtable); |
1136 | 0 | coucal_assert(hashtable, ! "hashtable internal error: cuckoo/stash collision"); |
1137 | | |
1138 | | /* not reachable code */ |
1139 | 0 | return -1; |
1140 | 0 | } |
1141 | 0 | } |
1142 | | |
1143 | 0 | static INTHASH_INLINE int coucal_is_acceptable_pow2(size_t lg_size) { |
1144 | 0 | return lg_size <= COUCAL_HASH_SIZE && lg_size < sizeof(size_t)*8; |
1145 | 0 | } |
1146 | | |
1147 | | /* Never called on delete: that would move a stashed item backwards past a |
1148 | | running enumeration. */ |
1149 | 0 | static void coucal_drain_stash(coucal hashtable) { |
1150 | 0 | size_t i; |
1151 | |
|
1152 | 0 | for (i = 0; i < hashtable->stash.extent; i++) { |
1153 | 0 | coucal_item *const item = &hashtable->stash.items[i]; |
1154 | 0 | if (item->name != NULL) { |
1155 | 0 | const size_t pos1 = coucal_hash_to_pos(hashtable, item->hashes.hash1); |
1156 | 0 | const size_t pos2 = coucal_hash_to_pos(hashtable, item->hashes.hash2); |
1157 | 0 | const size_t pos = |
1158 | 0 | coucal_is_free(hashtable, pos1) |
1159 | 0 | ? pos1 |
1160 | 0 | : (coucal_is_free(hashtable, pos2) ? pos2 : (size_t) -1); |
1161 | 0 | if (pos != (size_t) -1) { |
1162 | 0 | hashtable->items[pos] = *item; |
1163 | 0 | memset(item, 0, sizeof(*item)); |
1164 | 0 | hashtable->stash.size--; |
1165 | 0 | coucal_debug(hashtable, "debug:moved item from stash (%d entries)", |
1166 | 0 | (int) hashtable->stash.size); |
1167 | 0 | } |
1168 | 0 | } |
1169 | 0 | } |
1170 | 0 | while (hashtable->stash.extent != 0 && |
1171 | 0 | hashtable->stash.items[hashtable->stash.extent - 1].name == NULL) { |
1172 | 0 | hashtable->stash.extent--; |
1173 | 0 | } |
1174 | 0 | } |
1175 | | |
1176 | | int coucal_write_value(coucal hashtable, coucal_key_const name, |
1177 | 0 | coucal_value_const value) { |
1178 | | /* replace of add item */ |
1179 | 0 | const int ret = coucal_write_value_(hashtable, name, value); |
1180 | | |
1181 | | /* added ? */ |
1182 | 0 | if (ret) { |
1183 | | /* size of half of the table */ |
1184 | 0 | const size_t half_size = POW2(hashtable->lg_size - 1); |
1185 | | |
1186 | | /* size of half of the stash */ |
1187 | 0 | const size_t half_stash_size = STASH_SIZE / 2; |
1188 | | |
1189 | | /* item was added: increase count */ |
1190 | 0 | hashtable->used++; |
1191 | | |
1192 | | /* table is more than half-full, or stash is more than half-full */ |
1193 | 0 | if (hashtable->used >= half_size |
1194 | 0 | || hashtable->stash.size >= half_stash_size) { |
1195 | 0 | size_t i; |
1196 | | |
1197 | | /* size before */ |
1198 | 0 | const size_t prev_power = hashtable->lg_size; |
1199 | 0 | const size_t prev_size = half_size * 2; |
1200 | 0 | const size_t prev_alloc_size = prev_size*sizeof(coucal_item); |
1201 | | |
1202 | | /* size after doubling it ; a wrap here would silently under-allocate */ |
1203 | 0 | const int size_overflow = |
1204 | 0 | prev_size > ((size_t) -1) / (2 * sizeof(coucal_item)); |
1205 | 0 | const size_t alloc_size = size_overflow ? 0 : prev_alloc_size * 2; |
1206 | | |
1207 | | /* log stash issues */ |
1208 | 0 | if (hashtable->stash.size >= half_stash_size |
1209 | 0 | && half_size > POW2(16) |
1210 | 0 | && hashtable->used < half_size / 4) { |
1211 | 0 | coucal_warning(hashtable, |
1212 | 0 | "stash size still full despite %"UINT_64_FORMAT |
1213 | 0 | " elements used out of %"UINT_64_FORMAT, |
1214 | 0 | (uint64_t) hashtable->used, (uint64_t) half_size*2); |
1215 | 0 | } |
1216 | | |
1217 | | /* statistics */ |
1218 | 0 | hashtable->stats.rehash_count++; |
1219 | | |
1220 | | /* realloc */ |
1221 | 0 | hashtable->lg_size++; |
1222 | 0 | coucal_assert(hashtable, coucal_is_acceptable_pow2(hashtable->lg_size)); |
1223 | 0 | coucal_assert(hashtable, !size_overflow); |
1224 | 0 | hashtable->items = |
1225 | 0 | (coucal_item *) realloc(hashtable->items, alloc_size); |
1226 | 0 | if (hashtable->items == NULL) { |
1227 | 0 | coucal_crit(hashtable, |
1228 | 0 | "** hashtable allocation error: " |
1229 | 0 | "could not allocate %"UINT_64_FORMAT" bytes", |
1230 | 0 | (uint64_t) alloc_size); |
1231 | 0 | coucal_assert(hashtable, ! "hashtable allocation error"); |
1232 | 0 | } |
1233 | | |
1234 | | /* clear upper half */ |
1235 | 0 | memset(&hashtable->items[prev_size], 0, prev_alloc_size); |
1236 | | |
1237 | | /* relocate lower half items when needed */ |
1238 | 0 | for(i = 0 ; i < prev_size ; i++) { |
1239 | 0 | if (!coucal_is_free(hashtable, i)) { |
1240 | 0 | const coucal_hashkeys *const hashes = &hashtable->items[i].hashes; |
1241 | | |
1242 | | /* currently at old position 1 */ |
1243 | 0 | if (coucal_hash_to_pos_(prev_power, hashes->hash1) == i) { |
1244 | 0 | const size_t pos = coucal_hash_to_pos(hashtable, hashes->hash1); |
1245 | | /* no more the expected position */ |
1246 | 0 | if (pos != i) { |
1247 | 0 | coucal_assert(hashtable, pos >= prev_size); |
1248 | 0 | hashtable->items[pos] = hashtable->items[i]; |
1249 | 0 | memset(&hashtable->items[i], 0, sizeof(hashtable->items[i])); |
1250 | 0 | } |
1251 | 0 | } |
1252 | 0 | else if (coucal_hash_to_pos_(prev_power, hashes->hash2) == i) { |
1253 | 0 | const size_t pos = coucal_hash_to_pos(hashtable, hashes->hash2); |
1254 | | /* no more the expected position */ |
1255 | 0 | if (pos != i) { |
1256 | 0 | coucal_assert(hashtable, pos >= prev_size); |
1257 | 0 | hashtable->items[pos] = hashtable->items[i]; |
1258 | 0 | memset(&hashtable->items[i], 0, sizeof(hashtable->items[i])); |
1259 | 0 | } |
1260 | 0 | } |
1261 | 0 | else { |
1262 | 0 | coucal_assert(hashtable, ! "hashtable unexpected internal error (bad position)"); |
1263 | 0 | } |
1264 | 0 | } |
1265 | 0 | } |
1266 | |
|
1267 | 0 | coucal_debug(hashtable, |
1268 | 0 | "expanded hashtable to %"UINT_64_FORMAT" elements", |
1269 | 0 | (uint64_t) POW2(hashtable->lg_size)); |
1270 | | |
1271 | | /* attempt to merge the stash if present */ |
1272 | 0 | if (hashtable->stash.size != 0) { |
1273 | 0 | const size_t old_extent = hashtable->stash.extent; |
1274 | 0 | const size_t old_size = hashtable->stash.size; |
1275 | 0 | size_t i; |
1276 | | |
1277 | | /* backup stash and reset it */ |
1278 | 0 | coucal_item stash[STASH_SIZE]; |
1279 | 0 | memcpy(&stash, hashtable->stash.items, sizeof(hashtable->stash.items)); |
1280 | | /* stale copies above the new extent would alias live entries */ |
1281 | 0 | memset(hashtable->stash.items, 0, sizeof(hashtable->stash.items)); |
1282 | 0 | hashtable->stash.extent = 0; |
1283 | 0 | hashtable->stash.size = 0; |
1284 | | |
1285 | | /* insert all items */ |
1286 | 0 | for (i = 0; i < old_extent; i++) { |
1287 | 0 | if (stash[i].name != NULL) { |
1288 | 0 | const int ret = coucal_add_item_(hashtable, stash[i]); |
1289 | 0 | if (ret == 0) { |
1290 | 0 | coucal_assert(hashtable, |
1291 | 0 | !"hashtable duplicate key when merging the stash"); |
1292 | 0 | } |
1293 | 0 | } |
1294 | 0 | } |
1295 | | |
1296 | | /* logging */ |
1297 | 0 | coucal_assert(hashtable, hashtable->stash.size <= old_size); |
1298 | 0 | if (hashtable->stash.size < old_size) { |
1299 | 0 | coucal_debug(hashtable, "reduced stash size from %"UINT_64_FORMAT" " |
1300 | 0 | "to %"UINT_64_FORMAT, |
1301 | 0 | (uint64_t) old_size, (uint64_t) hashtable->stash.size); |
1302 | 0 | } else { |
1303 | 0 | coucal_trace(hashtable, "stash has still %"UINT_64_FORMAT" elements", |
1304 | 0 | (uint64_t) hashtable->stash.size); |
1305 | 0 | } |
1306 | 0 | } |
1307 | |
|
1308 | 0 | } |
1309 | 0 | } |
1310 | |
|
1311 | 0 | return ret; |
1312 | 0 | } |
1313 | | |
1314 | 0 | void coucal_add(coucal hashtable, coucal_key_const name, intptr_t intvalue) { |
1315 | 0 | coucal_value value = INTHASH_VALUE_NULL; |
1316 | |
|
1317 | 0 | memset(&value, 0, sizeof(value)); |
1318 | 0 | value.intg = intvalue; |
1319 | 0 | coucal_write_value(hashtable, name, value); |
1320 | 0 | } |
1321 | | |
1322 | 0 | int coucal_read(coucal hashtable, coucal_key_const name, intptr_t * intvalue) { |
1323 | 0 | coucal_value value = INTHASH_VALUE_NULL; |
1324 | 0 | int ret = |
1325 | 0 | coucal_read_value(hashtable, name, (intvalue != NULL) ? &value : NULL); |
1326 | 0 | if (intvalue != NULL) |
1327 | 0 | *intvalue = value.intg; |
1328 | 0 | return ret; |
1329 | 0 | } |
1330 | | |
1331 | | coucal_value* coucal_fetch_value_hashes(coucal hashtable, |
1332 | | coucal_key_const name, |
1333 | 0 | const coucal_hashkeys *hashes) { |
1334 | 0 | size_t pos; |
1335 | | |
1336 | | /* found at position 1 ? */ |
1337 | 0 | pos = coucal_hash_to_pos(hashtable, hashes->hash1); |
1338 | 0 | if (coucal_matches(hashtable, pos, name, hashes)) { |
1339 | 0 | return &hashtable->items[pos].value; |
1340 | 0 | } |
1341 | | |
1342 | | /* found at position 2 ? */ |
1343 | 0 | pos = coucal_hash_to_pos(hashtable, hashes->hash2); |
1344 | 0 | if (coucal_matches(hashtable, pos, name, hashes)) { |
1345 | 0 | return &hashtable->items[pos].value; |
1346 | 0 | } |
1347 | | |
1348 | | /* find in stash ? */ |
1349 | 0 | if (hashtable->stash.size != 0) { |
1350 | 0 | size_t i; |
1351 | 0 | for (i = 0; i < hashtable->stash.extent; i++) { |
1352 | 0 | if (coucal_matches_(hashtable, &hashtable->stash.items[i], name, |
1353 | 0 | hashes)) { |
1354 | 0 | return &hashtable->stash.items[i].value; |
1355 | 0 | } |
1356 | 0 | } |
1357 | 0 | } |
1358 | | |
1359 | | /* not found */ |
1360 | 0 | return NULL; |
1361 | 0 | } |
1362 | | |
1363 | | INTHASH_INLINE coucal_value* coucal_fetch_value(coucal hashtable, |
1364 | 0 | coucal_key_const name) { |
1365 | 0 | const coucal_hashkeys hashes = coucal_calc_hashes(hashtable, name); |
1366 | 0 | return coucal_fetch_value_hashes(hashtable, name, &hashes); |
1367 | 0 | } |
1368 | | |
1369 | | int coucal_read_value(coucal hashtable, coucal_key_const name, |
1370 | 0 | coucal_value * pvalue) { |
1371 | 0 | coucal_value* const value = coucal_fetch_value(hashtable, name); |
1372 | 0 | if (value != NULL) { |
1373 | 0 | if (pvalue != NULL) { |
1374 | 0 | *pvalue = *value; |
1375 | 0 | } |
1376 | 0 | return 1; |
1377 | 0 | } |
1378 | 0 | return 0; |
1379 | 0 | } |
1380 | | |
1381 | | static size_t coucal_inc_(coucal hashtable, coucal_key_const name, |
1382 | 0 | size_t inc) { |
1383 | 0 | coucal_value* const value = coucal_fetch_value(hashtable, name); |
1384 | 0 | if (value != NULL) { |
1385 | 0 | value->uintg += inc; |
1386 | 0 | return value->uintg; |
1387 | 0 | } else { |
1388 | | /* create a new value */ |
1389 | 0 | const int ret = coucal_write(hashtable, name, inc); |
1390 | 0 | coucal_assert(hashtable, ret); |
1391 | 0 | return inc; |
1392 | 0 | } |
1393 | 0 | } |
1394 | | |
1395 | 0 | int coucal_inc(coucal hashtable, coucal_key_const name) { |
1396 | 0 | return (int) coucal_inc_(hashtable, name, 1); |
1397 | 0 | } |
1398 | | |
1399 | 0 | int coucal_dec(coucal hashtable, coucal_key_const name) { |
1400 | 0 | return (int) coucal_inc_(hashtable, name, (size_t) -1); |
1401 | 0 | } |
1402 | | |
1403 | 0 | int coucal_exists(coucal hashtable, coucal_key_const name) { |
1404 | 0 | return coucal_read_value(hashtable, name, NULL); |
1405 | 0 | } |
1406 | | |
1407 | | static int coucal_remove_(coucal hashtable, coucal_key_const name, |
1408 | 0 | const coucal_hashkeys *hashes) { |
1409 | 0 | size_t pos; |
1410 | | |
1411 | | /* found at position 1 ? */ |
1412 | 0 | pos = coucal_hash_to_pos(hashtable, hashes->hash1); |
1413 | 0 | if (coucal_matches(hashtable, pos, name, hashes)) { |
1414 | 0 | coucal_del_item(hashtable, &hashtable->items[pos]); |
1415 | 0 | return 1; |
1416 | 0 | } |
1417 | | |
1418 | | /* found at position 2 ? */ |
1419 | 0 | pos = coucal_hash_to_pos(hashtable, hashes->hash2); |
1420 | 0 | if (coucal_matches(hashtable, pos, name, hashes)) { |
1421 | 0 | coucal_del_item(hashtable, &hashtable->items[pos]); |
1422 | 0 | return 1; |
1423 | 0 | } |
1424 | | |
1425 | | /* find in stash ? */ |
1426 | 0 | if (hashtable->stash.size != 0) { |
1427 | 0 | size_t i; |
1428 | 0 | for (i = 0; i < hashtable->stash.extent; i++) { |
1429 | 0 | if (coucal_matches_(hashtable, &hashtable->stash.items[i], name, |
1430 | 0 | hashes)) { |
1431 | | /* leave a hole: compacting would shift entries backwards past a |
1432 | | running enumeration's cursor */ |
1433 | 0 | coucal_del_item(hashtable, &hashtable->stash.items[i]); |
1434 | 0 | hashtable->stash.size--; |
1435 | 0 | while (hashtable->stash.extent != 0 && |
1436 | 0 | hashtable->stash.items[hashtable->stash.extent - 1].name == |
1437 | 0 | NULL) { |
1438 | 0 | hashtable->stash.extent--; |
1439 | 0 | } |
1440 | 0 | coucal_debug(hashtable, "debug:deleted item in stash (%d entries)", |
1441 | 0 | (int) hashtable->stash.size); |
1442 | 0 | return 1; |
1443 | 0 | } |
1444 | 0 | } |
1445 | 0 | } |
1446 | | |
1447 | | /* not found */ |
1448 | 0 | return 0; |
1449 | 0 | } |
1450 | | |
1451 | 0 | int coucal_remove(coucal hashtable, coucal_key_const name) { |
1452 | 0 | const coucal_hashkeys hashes = coucal_calc_hashes(hashtable, name); |
1453 | 0 | const int ret = coucal_remove_(hashtable, name, &hashes); |
1454 | |
|
1455 | 0 | if (ret) { |
1456 | | /* item was removed: decrease count */ |
1457 | 0 | coucal_assert(hashtable, hashtable->used != 0); |
1458 | 0 | hashtable->used--; |
1459 | 0 | } |
1460 | |
|
1461 | 0 | return ret; |
1462 | 0 | } |
1463 | | |
1464 | 0 | int coucal_readptr(coucal hashtable, coucal_key_const name, intptr_t * value) { |
1465 | 0 | intptr_t discarded; |
1466 | 0 | intptr_t *const dest = (value != NULL) ? value : &discarded; |
1467 | 0 | int ret; |
1468 | |
|
1469 | 0 | *dest = 0; |
1470 | 0 | ret = coucal_read(hashtable, name, dest); |
1471 | 0 | if (*dest == 0) |
1472 | 0 | ret = 0; |
1473 | 0 | return ret; |
1474 | 0 | } |
1475 | | |
1476 | 0 | intptr_t coucal_get_intptr(coucal hashtable, coucal_key_const name) { |
1477 | 0 | intptr_t value; |
1478 | 0 | if (!coucal_read(hashtable, name, &value)) { |
1479 | 0 | return 0; |
1480 | 0 | } |
1481 | 0 | return value; |
1482 | 0 | } |
1483 | | |
1484 | 0 | static INTHASH_INLINE size_t coucal_get_pow2(size_t initial_size) { |
1485 | 0 | size_t size; |
1486 | | /* short-circuit: POW2() must never shift by the width of size_t */ |
1487 | 0 | for (size = MIN_LG_SIZE; |
1488 | 0 | coucal_is_acceptable_pow2(size) && POW2(size) < initial_size; size++) |
1489 | 0 | ; |
1490 | 0 | return size; |
1491 | 0 | } |
1492 | | |
1493 | 0 | coucal coucal_new(size_t initial_size) { |
1494 | 0 | const size_t lg_size = coucal_get_pow2(initial_size); |
1495 | 0 | coucal hashtable; |
1496 | 0 | coucal_item *items; |
1497 | |
|
1498 | 0 | if (!coucal_is_acceptable_pow2(lg_size)) { |
1499 | 0 | return NULL; |
1500 | 0 | } |
1501 | | |
1502 | 0 | hashtable = (coucal) calloc(1, sizeof(struct_coucal)); |
1503 | 0 | items = (coucal_item *) calloc(POW2(lg_size), sizeof(coucal_item)); |
1504 | |
|
1505 | 0 | if (items != NULL && hashtable != NULL) { |
1506 | 0 | hashtable->lg_size = lg_size; |
1507 | 0 | hashtable->items = items; |
1508 | 0 | hashtable->used = 0; |
1509 | 0 | hashtable->stash.extent = 0; |
1510 | 0 | hashtable->stash.size = 0; |
1511 | 0 | hashtable->pool.buffer = NULL; |
1512 | 0 | hashtable->pool.size = 0; |
1513 | 0 | hashtable->pool.capacity = 0; |
1514 | 0 | hashtable->pool.used = 0; |
1515 | 0 | hashtable->stats.max_stash_size = 0; |
1516 | 0 | hashtable->stats.write_count = 0; |
1517 | 0 | hashtable->stats.add_count = 0; |
1518 | 0 | hashtable->stats.cuckoo_moved = 0; |
1519 | 0 | hashtable->stats.stash_added= 0; |
1520 | 0 | hashtable->stats.pool_compact_count = 0; |
1521 | 0 | hashtable->stats.pool_realloc_count = 0; |
1522 | 0 | hashtable->stats.rehash_count = 0; |
1523 | 0 | hashtable->custom.value.free = NULL; |
1524 | 0 | hashtable->custom.value.arg = NULL; |
1525 | 0 | hashtable->custom.key.dup = NULL; |
1526 | 0 | hashtable->custom.key.free = NULL; |
1527 | 0 | hashtable->custom.key.hash = NULL; |
1528 | 0 | hashtable->custom.key.equals = NULL; |
1529 | 0 | hashtable->custom.key.arg = NULL; |
1530 | 0 | hashtable->custom.error.log = NULL; |
1531 | 0 | hashtable->custom.error.fatal = NULL; |
1532 | 0 | hashtable->custom.error.name = NULL; |
1533 | 0 | hashtable->custom.error.arg = NULL; |
1534 | 0 | hashtable->custom.print.key = NULL; |
1535 | 0 | hashtable->custom.print.value = NULL; |
1536 | 0 | hashtable->custom.print.arg = NULL; |
1537 | 0 | return hashtable; |
1538 | 0 | } |
1539 | 0 | if (items != NULL) { |
1540 | 0 | free(items); |
1541 | 0 | } |
1542 | 0 | if (hashtable != NULL) { |
1543 | 0 | free(hashtable); |
1544 | 0 | } |
1545 | 0 | return NULL; |
1546 | 0 | } |
1547 | | |
1548 | 0 | int coucal_created(coucal hashtable) { |
1549 | 0 | return hashtable != NULL && hashtable->items != NULL; |
1550 | 0 | } |
1551 | | |
1552 | 0 | void coucal_value_is_malloc(coucal hashtable, int flag) { |
1553 | 0 | if (flag) { |
1554 | 0 | if (hashtable->custom.value.free == NULL) { |
1555 | 0 | hashtable->custom.value.free = coucal_default_free_handler; |
1556 | 0 | hashtable->custom.value.arg = NULL; |
1557 | 0 | } |
1558 | 0 | } else { |
1559 | 0 | hashtable->custom.value.free = NULL; |
1560 | 0 | hashtable->custom.value.arg = NULL; |
1561 | 0 | } |
1562 | 0 | } |
1563 | | |
1564 | 0 | void coucal_set_name(coucal hashtable, coucal_key_const name) { |
1565 | 0 | hashtable->custom.error.name = name; |
1566 | 0 | } |
1567 | | |
1568 | | void coucal_value_set_value_handler(coucal hashtable, |
1569 | | t_coucal_value_freehandler free, |
1570 | 0 | coucal_opaque arg) { |
1571 | 0 | hashtable->custom.value.free = free; |
1572 | 0 | hashtable->custom.value.arg = arg; |
1573 | 0 | } |
1574 | | |
1575 | | void coucal_value_set_key_handler(coucal hashtable, |
1576 | | t_coucal_duphandler dup, |
1577 | | t_coucal_key_freehandler free, |
1578 | | t_coucal_hasheshandler hash, |
1579 | | t_coucal_cmphandler equals, |
1580 | 0 | coucal_opaque arg) { |
1581 | | /* dup and free must be consistent */ |
1582 | 0 | coucal_assert(hashtable, ( dup == NULL ) == ( free == NULL ) ); |
1583 | 0 | hashtable->custom.key.dup = dup; |
1584 | 0 | hashtable->custom.key.free = free; |
1585 | 0 | hashtable->custom.key.hash = hash; |
1586 | 0 | hashtable->custom.key.equals = equals; |
1587 | 0 | hashtable->custom.key.arg = arg; |
1588 | 0 | } |
1589 | | |
1590 | | void coucal_set_assert_handler(coucal hashtable, |
1591 | | t_coucal_loghandler log, |
1592 | | t_coucal_asserthandler fatal, |
1593 | 0 | coucal_opaque arg) { |
1594 | 0 | hashtable->custom.error.log = log; |
1595 | 0 | hashtable->custom.error.fatal = fatal; |
1596 | 0 | hashtable->custom.error.arg = arg; |
1597 | 0 | } |
1598 | | |
1599 | | void coucal_set_print_handler(coucal hashtable, |
1600 | | t_coucal_printkeyhandler key, |
1601 | | t_coucal_printvaluehandler value, |
1602 | 0 | coucal_opaque arg) { |
1603 | 0 | hashtable->custom.print.key = key; |
1604 | 0 | hashtable->custom.print.value = value; |
1605 | 0 | hashtable->custom.print.arg = arg; |
1606 | 0 | } |
1607 | | |
1608 | 0 | size_t coucal_nitems(coucal hashtable) { |
1609 | 0 | if (hashtable != NULL) |
1610 | 0 | return hashtable->used; |
1611 | 0 | return 0; |
1612 | 0 | } |
1613 | | |
1614 | 0 | size_t coucal_memory_size(coucal hashtable) { |
1615 | 0 | const size_t size_struct = sizeof(struct_coucal); |
1616 | 0 | const size_t hash_size = POW2(hashtable->lg_size)*sizeof(coucal_item); |
1617 | 0 | const size_t pool_size = hashtable->pool.capacity*sizeof(char); |
1618 | 0 | return size_struct + hash_size + pool_size; |
1619 | 0 | } |
1620 | | |
1621 | 0 | size_t coucal_hash_size(void) { |
1622 | 0 | return COUCAL_HASH_SIZE; |
1623 | 0 | } |
1624 | | |
1625 | 0 | void coucal_delete(coucal *phashtable) { |
1626 | 0 | if (phashtable != NULL) { |
1627 | 0 | coucal hashtable = *phashtable; |
1628 | 0 | if (hashtable != NULL) { |
1629 | 0 | coucal_log_stats(hashtable); |
1630 | 0 | if (hashtable->items != NULL) { |
1631 | | /* we need to delete values */ |
1632 | 0 | const size_t hash_size = POW2(hashtable->lg_size); |
1633 | | /* internal-pool names go away with the pool buffer below */ |
1634 | 0 | const int free_names = hashtable->custom.key.free != NULL; |
1635 | 0 | size_t i; |
1636 | | |
1637 | | /* wipe hashtable values (and names, if custom-allocated) */ |
1638 | 0 | for(i = 0 ; i < hash_size ; i++) { |
1639 | 0 | if (!coucal_is_free(hashtable, i)) { |
1640 | 0 | coucal_del_value(hashtable, i); |
1641 | 0 | if (free_names) { |
1642 | 0 | coucal_del_name(hashtable, &hashtable->items[i]); |
1643 | 0 | } |
1644 | 0 | } |
1645 | 0 | } |
1646 | | |
1647 | | /* wipe stash values (and names); holes below extent are not live */ |
1648 | 0 | for (i = 0; i < hashtable->stash.extent; i++) { |
1649 | 0 | coucal_item *const item = &hashtable->stash.items[i]; |
1650 | 0 | if (item->name != NULL) { |
1651 | 0 | coucal_del_value_(hashtable, &item->value); |
1652 | 0 | if (free_names) { |
1653 | 0 | coucal_del_name(hashtable, item); |
1654 | 0 | } |
1655 | 0 | } |
1656 | 0 | } |
1657 | 0 | } |
1658 | | /* wipe top-level */ |
1659 | 0 | hashtable->lg_size = 0; |
1660 | 0 | hashtable->used = 0; |
1661 | 0 | free(hashtable->pool.buffer); |
1662 | 0 | hashtable->pool.buffer = NULL; |
1663 | 0 | free(hashtable->items); |
1664 | 0 | hashtable->items = NULL; |
1665 | 0 | free(hashtable); |
1666 | 0 | *phashtable = NULL; |
1667 | 0 | } |
1668 | 0 | } |
1669 | 0 | } |
1670 | | |
1671 | | /* Enumerator */ |
1672 | | |
1673 | 0 | struct_coucal_enum coucal_enum_new(coucal hashtable) { |
1674 | 0 | struct_coucal_enum e; |
1675 | |
|
1676 | 0 | e.index = 0; |
1677 | 0 | e.table = hashtable; |
1678 | 0 | return e; |
1679 | 0 | } |
1680 | | |
1681 | 0 | coucal_item *coucal_enum_next(struct_coucal_enum * e) { |
1682 | 0 | const size_t hash_size = POW2(e->table->lg_size); |
1683 | 0 | for( ; e->index < hash_size |
1684 | 0 | && coucal_is_free(e->table, e->index) ; e->index++) ; |
1685 | | /* enumerate all table */ |
1686 | 0 | if (e->index < hash_size) { |
1687 | 0 | coucal_item *const next = &e->table->items[e->index]; |
1688 | 0 | e->index++; |
1689 | 0 | return next; |
1690 | 0 | } |
1691 | | /* enumerate stash if present */ |
1692 | 0 | for (; e->index < hash_size + e->table->stash.extent && |
1693 | 0 | e->table->stash.items[e->index - hash_size].name == NULL; |
1694 | 0 | e->index++) |
1695 | 0 | ; |
1696 | 0 | if (e->index < hash_size + e->table->stash.extent) { |
1697 | 0 | coucal_item *const next = &e->table->stash.items[e->index - hash_size]; |
1698 | 0 | e->index++; |
1699 | 0 | return next; |
1700 | 0 | } |
1701 | | /* eof */ |
1702 | 0 | return NULL; |
1703 | 0 | } |
1704 | | |
1705 | | void coucal_set_global_assert_handler(t_coucal_loghandler log, |
1706 | 0 | t_coucal_asserthandler fatal) { |
1707 | 0 | global_log_handler = log; |
1708 | 0 | global_assert_handler = fatal; |
1709 | 0 | } |