Coverage Report

Created: 2026-08-31 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}