170b9dd813ade51a5fce4dea3f2e0053604b70a9
[xonotic/gmqcc.git] / stat.c
1 /*
2  * Copyright (C) 2012, 2013
3  *     Dale Weiler
4  *     Wolfgang Bumiller
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy of
7  * this software and associated documentation files (the "Software"), to deal in
8  * the Software without restriction, including without limitation the rights to
9  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10  * of the Software, and to permit persons to whom the Software is furnished to do
11  * so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24
25 #include <string.h>
26 #include <stdlib.h>
27
28 #include "gmqcc.h"
29
30 /*
31  * GMQCC performs tons of allocations, constructions, and crazyness
32  * all around. When trying to optimizes systems, or just get fancy
33  * statistics out of the compiler, it's often printf mess. This file
34  * implements the statistics system of the compiler. I.E the allocator
35  * we use to track allocations, and other systems of interest.
36  */
37 #define ST_SIZE 1024
38
39 typedef struct stat_mem_block_s {
40     const char              *file;
41     size_t                   line;
42     size_t                   size;
43     struct stat_mem_block_s *next;
44     struct stat_mem_block_s *prev;
45 } stat_mem_block_t;
46
47 typedef struct {
48     size_t key;
49     size_t value;
50 } stat_size_entry_t, **stat_size_table_t;
51
52 static uint64_t          stat_mem_allocated         = 0;
53 static uint64_t          stat_mem_deallocated       = 0;
54 static uint64_t          stat_mem_allocated_total   = 0;
55 static uint64_t          stat_mem_deallocated_total = 0;
56 static uint64_t          stat_mem_high              = 0;
57 static uint64_t          stat_mem_peak              = 0;
58 static uint64_t          stat_mem_strdups           = 0;
59 static uint64_t          stat_used_strdups          = 0;
60 static uint64_t          stat_used_vectors          = 0;
61 static uint64_t          stat_used_hashtables       = 0;
62 static uint64_t          stat_type_vectors          = 0;
63 static uint64_t          stat_type_hashtables       = 0;
64 static stat_size_table_t stat_size_vectors          = NULL;
65 static stat_size_table_t stat_size_hashtables       = NULL;
66 static stat_mem_block_t *stat_mem_block_root        = NULL;
67
68 /*
69  * A tiny size_t key-value hashtbale for tracking vector and hashtable
70  * sizes. We can use it for other things too, if we need to. This is
71  * very TIGHT, and efficent in terms of space though.
72  */
73 static stat_size_table_t stat_size_new(void) {
74     return (stat_size_table_t)memset(
75         mem_a(sizeof(stat_size_entry_t*) * ST_SIZE),
76         0, ST_SIZE * sizeof(stat_size_entry_t*)
77     );
78 }
79
80 static void stat_size_del(stat_size_table_t table) {
81     size_t i = 0;
82     for (; i < ST_SIZE; i++) if(table[i]) mem_d(table[i]);
83     mem_d(table);
84 }
85
86 static stat_size_entry_t *stat_size_get(stat_size_table_t table, size_t key) {
87     size_t hash = (key % ST_SIZE);
88     while (table[hash] && table[hash]->key != key)
89         hash = (hash + 1) % ST_SIZE;
90     return table[hash];
91 }
92 static void stat_size_put(stat_size_table_t table, size_t key, size_t value) {
93     size_t hash = (key % ST_SIZE);
94     while (table[hash] && table[hash]->key != key)
95         hash = (hash + 1) % ST_SIZE;
96     table[hash]        = (stat_size_entry_t*)mem_a(sizeof(stat_size_entry_t));
97     table[hash]->key   = key;
98     table[hash]->value = value;
99 }
100
101 /*
102  * A basic header of information wrapper allocator. Simply stores
103  * information as a header, returns the memory + 1 past it, can be
104  * retrieved again with - 1. Where type is stat_mem_block_t*.
105  */
106 void *stat_mem_allocate(size_t size, size_t line, const char *file) {
107     stat_mem_block_t *info = (stat_mem_block_t*)malloc(sizeof(stat_mem_block_t) + size);
108     void             *data = (void*)(info + 1);
109
110     if(GMQCC_UNLIKELY(!info))
111         return NULL;
112
113     info->line = line;
114     info->size = size;
115     info->file = file;
116     info->prev = NULL;
117     info->next = stat_mem_block_root;
118
119     /* unlikely since it only happens once */
120     if (GMQCC_UNLIKELY(stat_mem_block_root != NULL))
121         stat_mem_block_root->prev = info;
122
123     stat_mem_block_root       = info;
124     stat_mem_allocated       += size;
125     stat_mem_high            += size;
126     stat_mem_allocated_total ++;
127
128     if (stat_mem_high > stat_mem_peak)
129         stat_mem_peak = stat_mem_high;
130
131     return data;
132 }
133
134 void stat_mem_deallocate(void *ptr) {
135     stat_mem_block_t *info = NULL;
136
137     if (GMQCC_UNLIKELY(!ptr))
138         return;
139
140     info = ((stat_mem_block_t*)ptr - 1);
141
142     stat_mem_deallocated       += info->size;
143     stat_mem_high              -= info->size;
144     stat_mem_deallocated_total ++;
145
146     if (info->prev) info->prev->next = info->next;
147     if (info->next) info->next->prev = info->prev;
148
149     /* move ahead */
150     if (info == stat_mem_block_root)
151         stat_mem_block_root = info->next;
152
153     free(info);
154 }
155
156 void *stat_mem_reallocate(void *ptr, size_t size, size_t line, const char *file) {
157     stat_mem_block_t *oldinfo = NULL;
158     stat_mem_block_t *newinfo;
159
160     if (GMQCC_UNLIKELY(!ptr))
161         return stat_mem_allocate(size, line, file);
162
163     /* stay consistent with glibc */
164     if (GMQCC_UNLIKELY(!size)) {
165         stat_mem_deallocate(ptr);
166         return NULL;
167     }
168
169     oldinfo = ((stat_mem_block_t*)ptr - 1);
170     newinfo = ((stat_mem_block_t*)malloc(sizeof(stat_mem_block_t) + size));
171
172     if (GMQCC_UNLIKELY(!newinfo)) {
173         stat_mem_deallocate(ptr);
174         return NULL;
175     }
176
177     memcpy(newinfo+1, oldinfo+1, oldinfo->size);
178
179     if (oldinfo->prev) oldinfo->prev->next = oldinfo->next;
180     if (oldinfo->next) oldinfo->next->prev = oldinfo->prev;
181
182     /* move ahead */
183     if (oldinfo == stat_mem_block_root)
184         stat_mem_block_root = oldinfo->next;
185
186     newinfo->line = line;
187     newinfo->size = size;
188     newinfo->file = file;
189     newinfo->prev = NULL;
190     newinfo->next = stat_mem_block_root;
191
192     /* 
193      * likely since the only time there is no root is when it's
194      * being initialized first.
195      */
196     if (GMQCC_LIKELY(stat_mem_block_root != NULL))
197         stat_mem_block_root->prev = newinfo;
198
199     stat_mem_block_root = newinfo;
200     stat_mem_allocated -= oldinfo->size;
201     stat_mem_high      -= oldinfo->size;
202     stat_mem_allocated += newinfo->size;
203     stat_mem_high      += newinfo->size;
204
205     if (stat_mem_high > stat_mem_peak)
206         stat_mem_peak = stat_mem_high;
207
208     free(oldinfo);
209
210     return newinfo + 1;
211 }
212
213 /*
214  * strdup does it's own malloc, we need to track malloc. We don't want
215  * to overwrite malloc though, infact, we can't really hook it at all
216  * without library specific assumptions. So we re implement strdup.
217  */
218 char *stat_mem_strdup(const char *src, size_t line, const char *file, bool empty) {
219     size_t len = 0;
220     char  *ptr = NULL;
221
222     if (!src)
223         return NULL;
224
225     len = strlen(src);
226     if (((!empty) ? len : true) && (ptr = (char*)stat_mem_allocate(len + 1, line, file))) {
227         memcpy(ptr, src, len);
228         ptr[len] = '\0';
229     }
230
231     stat_used_strdups ++;
232     stat_mem_strdups  += len;
233     return ptr;
234 }
235
236 /*
237  * The reallocate function for resizing vectors.
238  */
239 void _util_vec_grow(void **a, size_t i, size_t s) {
240     vector_t          *d = vec_meta(*a);
241     size_t             m = 0;
242     stat_size_entry_t *e = NULL;
243     void              *p = NULL;
244
245     if (*a) {
246         m = 2 * d->allocated + i;
247         p = mem_r(d, s * m + sizeof(vector_t));
248     } else {
249         m = i + 1;
250         p = mem_a(s * m + sizeof(vector_t));
251         ((vector_t*)p)->used = 0;
252         stat_used_vectors++;
253     }
254
255     if (!stat_size_vectors)
256         stat_size_vectors = stat_size_new();
257
258     if ((e = stat_size_get(stat_size_vectors, s))) {
259         e->value ++;
260     } else {
261         stat_size_put(stat_size_vectors, s, 1); /* start off with 1 */
262         stat_type_vectors++;
263     }
264
265     *a = (vector_t*)p + 1;
266     vec_meta(*a)->allocated = m;
267 }
268
269 /*
270  * Hash table for generic data, based on dynamic memory allocations
271  * all around.  This is the internal interface, please look for
272  * EXPOSED INTERFACE comment below
273  */
274 typedef struct hash_node_t {
275     char               *key;   /* the key for this node in table */
276     void               *value; /* pointer to the data as void*   */
277     struct hash_node_t *next;  /* next node (linked list)        */
278 } hash_node_t;
279
280 /*
281  * This is a patched version of the Murmur2 hashing function to use
282  * a proper pre-mix and post-mix setup. Infact this is Murmur3 for
283  * the most part just reinvented.
284  *
285  * Murmur 2 contains an inner loop such as:
286  * while (l >= 4) {
287  *      u32 k = *(u32*)d;
288  *      k *= m;
289  *      k ^= k >> r;
290  *      k *= m;
291  *
292  *      h *= m;
293  *      h ^= k;
294  *      d += 4;
295  *      l -= 4;
296  * }
297  *
298  * The two u32s that form the key are the same value x (pulled from data)
299  * this premix stage will perform the same results for both values. Unrolled
300  * this produces just:
301  *  x *= m;
302  *  x ^= x >> r;
303  *  x *= m;
304  *
305  *  h *= m;
306  *  h ^= x;
307  *  h *= m;
308  *  h ^= x;
309  *
310  * This appears to be fine, except what happens when m == 1? well x
311  * cancels out entierly, leaving just:
312  *  x ^= x >> r;
313  *  h ^= x;
314  *  h ^= x;
315  *
316  * So all keys hash to the same value, but how often does m == 1?
317  * well, it turns out testing x for all possible values yeilds only
318  * 172,013,942 unique results instead of 2^32. So nearly ~4.6 bits
319  * are cancelled out on average!
320  *
321  * This means we have a 14.5% (rounded) chance of colliding more, which
322  * results in another bucket/chain for the hashtable.
323  *
324  * We fix it buy upgrading the pre and post mix ssystems to align with murmur
325  * hash 3.
326  */
327 #if 1
328 #define GMQCC_ROTL32(X, R) (((X) << (R)) | ((X) >> (32 - (R))))
329 GMQCC_INLINE size_t util_hthash(hash_table_t *ht, const char *key) {
330     const unsigned char *data   = (const unsigned char *)key;
331     const size_t         len    = strlen(key);
332     const size_t         block  = len / 4;
333     const uint32_t       mask1  = 0xCC9E2D51;
334     const uint32_t       mask2  = 0x1B873593;
335     const uint32_t      *blocks = (const uint32_t*)(data + block * 4);
336     const unsigned char *tail   = (const unsigned char *)(data + block * 4);
337
338     size_t   i;
339     uint32_t k;
340     uint32_t h = 0x1EF0 ^ len;
341
342     for (i = -block; i; i++) {
343         k  = blocks[i];
344         k *= mask1;
345         k  = GMQCC_ROTL32(k, 15);
346         k *= mask2;
347         h ^= k;
348         h  = GMQCC_ROTL32(h, 13);
349         h  = h * 5 + 0xE6546B64;
350     }
351
352     k = 0;
353     switch (len & 3) {
354         case 3:
355             k ^= tail[2] << 16;
356         case 2:
357             k ^= tail[1] << 8;
358         case 1:
359             k ^= tail[0];
360             k *= mask1;
361             k  = GMQCC_ROTL32(k, 15);
362             k *= mask2;
363             h ^= k;
364     }
365
366     h ^= len;
367     h ^= h >> 16;
368     h *= 0x85EBCA6B;
369     h ^= h >> 13;
370     h *= 0xC2B2AE35;
371     h ^= h >> 16;
372
373     return (size_t) (h % ht->size);
374 }
375 #undef GMQCC_ROTL32
376 #else
377 /* We keep the old for reference */
378 GMQCC_INLINE size_t util_hthash(hash_table_t *ht, const char *key) {
379     const uint32_t       mix   = 0x5BD1E995;
380     const uint32_t       rot   = 24;
381     size_t               size  = strlen(key);
382     uint32_t             hash  = 0x1EF0 /* LICRC TAB */  ^ size;
383     uint32_t             alias = 0;
384     const unsigned char *data  = (const unsigned char*)key;
385
386     while (size >= 4) {
387         alias  = (data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24));
388         alias *= mix;
389         alias ^= alias >> rot;
390         alias *= mix;
391
392         hash  *= mix;
393         hash  ^= alias;
394
395         data += 4;
396         size -= 4;
397     }
398
399     switch (size) {
400         case 3: hash ^= data[2] << 16;
401         case 2: hash ^= data[1] << 8;
402         case 1: hash ^= data[0];
403                 hash *= mix;
404     }
405
406     hash ^= hash >> 13;
407     hash *= mix;
408     hash ^= hash >> 15;
409
410     return (size_t) (hash % ht->size);
411 }
412 #endif
413
414 static hash_node_t *_util_htnewpair(const char *key, void *value) {
415     hash_node_t *node;
416     if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t))))
417         return NULL;
418
419     if (!(node->key = util_strdupe(key))) {
420         mem_d(node);
421         return NULL;
422     }
423
424     node->value = value;
425     node->next  = NULL;
426
427     return node;
428 }
429
430 /*
431  * EXPOSED INTERFACE for the hashtable implementation
432  * util_htnew(size)                             -- to make a new hashtable
433  * util_htset(table, key, value, sizeof(value)) -- to set something in the table
434  * util_htget(table, key)                       -- to get something from the table
435  * util_htdel(table)                            -- to delete the table
436  */
437 hash_table_t *util_htnew(size_t size) {
438     hash_table_t      *hashtable = NULL;
439     stat_size_entry_t *find      = NULL;
440
441     if (size < 1)
442         return NULL;
443
444     if (!stat_size_hashtables)
445         stat_size_hashtables = stat_size_new();
446
447     if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t))))
448         return NULL;
449
450     if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) {
451         mem_d(hashtable);
452         return NULL;
453     }
454
455     if ((find = stat_size_get(stat_size_hashtables, size)))
456         find->value++;
457     else {
458         stat_type_hashtables++;
459         stat_size_put(stat_size_hashtables, size, 1);
460     }
461
462     hashtable->size = size;
463     memset(hashtable->table, 0, sizeof(hash_node_t*) * size);
464
465     stat_used_hashtables++;
466     return hashtable;
467 }
468
469 void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) {
470     hash_node_t *newnode = NULL;
471     hash_node_t *next    = NULL;
472     hash_node_t *last    = NULL;
473
474     next = ht->table[bin];
475
476     while (next && next->key && strcmp(key, next->key) > 0)
477         last = next, next = next->next;
478
479     /* already in table, do a replace */
480     if (next && next->key && strcmp(key, next->key) == 0) {
481         next->value = value;
482     } else {
483         /* not found, grow a pair man :P */
484         newnode = _util_htnewpair(key, value);
485         if (next == ht->table[bin]) {
486             newnode->next  = next;
487             ht->table[bin] = newnode;
488         } else if (!next) {
489             last->next = newnode;
490         } else {
491             newnode->next = next;
492             last->next = newnode;
493         }
494     }
495 }
496
497 void util_htset(hash_table_t *ht, const char *key, void *value) {
498     util_htseth(ht, key, util_hthash(ht, key), value);
499 }
500
501 void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) {
502     hash_node_t *pair = ht->table[bin];
503
504     while (pair && pair->key && strcmp(key, pair->key) > 0)
505         pair = pair->next;
506
507     if (!pair || !pair->key || strcmp(key, pair->key) != 0)
508         return NULL;
509
510     return pair->value;
511 }
512
513 void *util_htget(hash_table_t *ht, const char *key) {
514     return util_htgeth(ht, key, util_hthash(ht, key));
515 }
516
517 void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) {
518     hash_node_t *pair;
519     size_t len, keylen;
520     int cmp;
521
522     keylen = strlen(key);
523
524     pair = ht->table[bin];
525     while (pair && pair->key) {
526         len = strlen(pair->key);
527         if (len < keylen) {
528             pair = pair->next;
529             continue;
530         }
531         if (keylen == len) {
532             cmp = strcmp(key, pair->key);
533             if (cmp == 0)
534                 return pair->value;
535             if (cmp < 0)
536                 return NULL;
537             pair = pair->next;
538             continue;
539         }
540         cmp = strcmp(key, pair->key + len - keylen);
541         if (cmp == 0) {
542             uintptr_t up = (uintptr_t)pair->value;
543             up += len - keylen;
544             return (void*)up;
545         }
546         pair = pair->next;
547     }
548     return NULL;
549 }
550
551 /*
552  * Free all allocated data in a hashtable, this is quite the amount
553  * of work.
554  */
555 void util_htrem(hash_table_t *ht, void (*callback)(void *data)) {
556     size_t i = 0;
557
558     for (; i < ht->size; ++i) {
559         hash_node_t *n = ht->table[i];
560         hash_node_t *p;
561
562         /* free in list */
563         while (n) {
564             if (n->key)
565                 mem_d(n->key);
566             if (callback)
567                 callback(n->value);
568             p = n;
569             n = p->next;
570             mem_d(p);
571         }
572
573     }
574     /* free table */
575     mem_d(ht->table);
576     mem_d(ht);
577 }
578
579 void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) {
580     hash_node_t **pair = &ht->table[bin];
581     hash_node_t *tmp;
582
583     while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0)
584         pair = &(*pair)->next;
585
586     tmp = *pair;
587     if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0)
588         return;
589
590     if (cb)
591         (*cb)(tmp->value);
592
593     *pair = tmp->next;
594     mem_d(tmp->key);
595     mem_d(tmp);
596 }
597
598 void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) {
599     util_htrmh(ht, key, util_hthash(ht, key), cb);
600 }
601
602 void util_htdel(hash_table_t *ht) {
603     util_htrem(ht, NULL);
604 }
605
606 /*
607  * The following functions below implement printing / dumping of statistical
608  * information.
609  */
610 static void stat_dump_mem_contents(stat_mem_block_t *memory, uint16_t cols) {
611     uint32_t i, j;
612     for (i = 0; i < memory->size + ((memory->size % cols) ? (cols - memory->size % cols) : 0); i++) {
613         if (i % cols == 0)    con_out(" 0x%06X: ", i);
614         if (i < memory->size) con_out("%02X " , 0xFF & ((unsigned char*)(memory + 1))[i]);
615         else                  con_out(" ");
616
617         if ((uint16_t)(i % cols) == (cols - 1)) {
618             for (j = i - (cols - 1); j <= i; j++) {
619                 con_out("%c",
620                     (j >= memory->size)
621                         ? ' '
622                         : (util_isprint(((unsigned char*)(memory + 1))[j]))
623                             ? 0xFF & ((unsigned char*)(memory + 1)) [j]
624                             : '.'
625                 );
626             }
627             con_out("\n");
628         }
629     }
630 }
631
632 static void stat_dump_mem_leaks(void) {
633     stat_mem_block_t *info;
634     for (info = stat_mem_block_root; info; info = info->next) {
635         con_out("lost: %u (bytes) at %s:%u\n",
636             info->size,
637             info->file,
638             info->line
639         );
640
641         stat_dump_mem_contents(info, OPTS_OPTION_U16(OPTION_MEMDUMPCOLS));
642     }
643 }
644
645 static void stat_dump_mem_info(void) {
646     con_out("Memory Information:\n\
647     Total allocations:   %llu\n\
648     Total deallocations: %llu\n\
649     Total allocated:     %f (MB)\n\
650     Total deallocated:   %f (MB)\n\
651     Total peak memory:   %f (MB)\n\
652     Total leaked memory: %f (MB) in %llu allocations\n",
653         stat_mem_allocated_total,
654         stat_mem_deallocated_total,
655         (float)(stat_mem_allocated)                        / 1048576.0f,
656         (float)(stat_mem_deallocated)                      / 1048576.0f,
657         (float)(stat_mem_peak)                             / 1048576.0f,
658         (float)(stat_mem_allocated - stat_mem_deallocated) / 1048576.0f,
659         stat_mem_allocated_total - stat_mem_deallocated_total
660     );
661 }
662
663 static void stat_dump_stats_table(stat_size_table_t table, const char *string, uint64_t *size) {
664     size_t i,j;
665
666     if (!table)
667         return;
668
669     for (i = 0, j = 1; i < ST_SIZE; i++) {
670         stat_size_entry_t *entry;
671
672         if (!(entry = table[i]))
673             continue;
674
675         con_out(string, (unsigned)j, (unsigned)entry->key, (unsigned)entry->value);
676         j++;
677
678         if (size)
679             *size += entry->key * entry->value;
680     }
681 }
682
683 void stat_info() {
684     if (OPTS_OPTION_BOOL(OPTION_MEMCHK) ||
685         OPTS_OPTION_BOOL(OPTION_STATISTICS)) {
686         uint64_t mem = 0;
687
688         con_out("Memory Statistics:\n\
689     Total vectors allocated:       %llu\n\
690     Total string duplicates:       %llu\n\
691     Total string duplicate memory: %f (MB)\n\
692     Total hashtables allocated:    %llu\n\
693     Total unique vector sizes:     %llu\n",
694             stat_used_vectors,
695             stat_used_strdups,
696             (float)(stat_mem_strdups) / 1048576.0f,
697             stat_used_hashtables,
698             stat_type_vectors
699         );
700
701         stat_dump_stats_table (
702             stat_size_vectors,
703             "        %2u| # of %5u byte vectors: %u\n",
704             &mem
705         );
706
707         con_out (
708             "    Total unique hashtable sizes: %llu\n",
709             stat_type_hashtables
710         );
711
712         stat_dump_stats_table (
713             stat_size_hashtables,
714             "        %2u| # of %5u element hashtables: %u\n",
715             NULL
716         );
717
718         con_out (
719             "    Total vector memory:          %f (MB)\n\n",
720             (float)(mem) / 1048576.0f
721         );
722     }
723
724     if (stat_size_vectors)
725         stat_size_del(stat_size_vectors);
726     if (stat_size_hashtables)
727         stat_size_del(stat_size_hashtables);
728
729     if (OPTS_OPTION_BOOL(OPTION_DEBUG) ||
730         OPTS_OPTION_BOOL(OPTION_MEMCHK))
731         stat_dump_mem_info();
732
733     if (OPTS_OPTION_BOOL(OPTION_DEBUG))
734         stat_dump_mem_leaks();
735 }
736 #undef ST_SIZE