]> git.xonotic.org Git - xonotic/gmqcc.git/blob - stat.c
Rework some build stuff for better output and to enable strict prototypes
[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     return newinfo + 1;
210 }
211
212 /*
213  * strdup does it's own malloc, we need to track malloc. We don't want
214  * to overwrite malloc though, infact, we can't really hook it at all
215  * without library specific assumptions. So we re implement strdup.
216  */
217 char *stat_mem_strdup(const char *src, size_t line, const char *file, bool empty) {
218     size_t len = 0;
219     char  *ptr = NULL;
220
221     if (!src)
222         return NULL;
223
224     len = strlen(src);
225     if (((!empty) ? len : true) && (ptr = (char*)stat_mem_allocate(len + 1, line, file))) {
226         memcpy(ptr, src, len);
227         ptr[len] = '\0';
228     }
229
230     stat_used_strdups ++;
231     stat_mem_strdups  += len;
232     return ptr;
233 }
234
235 /*
236  * The reallocate function for resizing vectors.
237  */
238 void _util_vec_grow(void **a, size_t i, size_t s) {
239     vector_t          *d = vec_meta(*a);
240     size_t             m = 0;
241     stat_size_entry_t *e = NULL;
242     void              *p = NULL;
243
244     if (*a) {
245         m = 2 * d->allocated + i;
246         p = mem_r(d, s * m + sizeof(vector_t));
247     } else {
248         m = i + 1;
249         p = mem_a(s * m + sizeof(vector_t));
250         ((vector_t*)p)->used = 0;
251         stat_used_vectors++;
252     }
253
254     if (!stat_size_vectors)
255         stat_size_vectors = stat_size_new();
256
257     if ((e = stat_size_get(stat_size_vectors, s))) {
258         e->value ++;
259     } else {
260         stat_size_put(stat_size_vectors, s, 1); /* start off with 1 */
261         stat_type_vectors++;
262     }
263
264     *a = (vector_t*)p + 1;
265     vec_meta(*a)->allocated = m;
266 }
267
268 /*
269  * Hash table for generic data, based on dynamic memory allocations
270  * all around.  This is the internal interface, please look for
271  * EXPOSED INTERFACE comment below
272  */
273 typedef struct hash_node_t {
274     char               *key;   /* the key for this node in table */
275     void               *value; /* pointer to the data as void*   */
276     struct hash_node_t *next;  /* next node (linked list)        */
277 } hash_node_t;
278
279 /*
280  * This is a patched version of the Murmur2 hashing function to use
281  * a proper pre-mix and post-mix setup. Infact this is Murmur3 for
282  * the most part just reinvented.
283  *
284  * Murmur 2 contains an inner loop such as:
285  * while (l >= 4) {
286  *      u32 k = *(u32*)d;
287  *      k *= m;
288  *      k ^= k >> r;
289  *      k *= m;
290  *
291  *      h *= m;
292  *      h ^= k;
293  *      d += 4;
294  *      l -= 4;
295  * }
296  *
297  * The two u32s that form the key are the same value x (pulled from data)
298  * this premix stage will perform the same results for both values. Unrolled
299  * this produces just:
300  *  x *= m;
301  *  x ^= x >> r;
302  *  x *= m;
303  *
304  *  h *= m;
305  *  h ^= x;
306  *  h *= m;
307  *  h ^= x;
308  *
309  * This appears to be fine, except what happens when m == 1? well x
310  * cancels out entierly, leaving just:
311  *  x ^= x >> r;
312  *  h ^= x;
313  *  h ^= x;
314  *
315  * So all keys hash to the same value, but how often does m == 1?
316  * well, it turns out testing x for all possible values yeilds only
317  * 172,013,942 unique results instead of 2^32. So nearly ~4.6 bits
318  * are cancelled out on average!
319  *
320  * This means we have a 14.5% (rounded) chance of colliding more, which
321  * results in another bucket/chain for the hashtable.
322  *
323  * We fix it buy upgrading the pre and post mix ssystems to align with murmur
324  * hash 3.
325  */
326 #if 1
327 #define GMQCC_ROTL32(X, R) (((X) << (R)) | ((X) >> (32 - (R))))
328 GMQCC_INLINE size_t util_hthash(hash_table_t *ht, const char *key) {
329     const unsigned char *data   = (const unsigned char *)key;
330     const size_t         len    = strlen(key);
331     const size_t         block  = len / 4;
332     const uint32_t       mask1  = 0xCC9E2D51;
333     const uint32_t       mask2  = 0x1B873593;
334     const uint32_t      *blocks = (const uint32_t*)(data + block * 4);
335     const unsigned char *tail   = (const unsigned char *)(data + block * 4);
336
337     size_t   i;
338     uint32_t k;
339     uint32_t h = 0x1EF0 ^ len;
340
341     for (i = -block; i; i++) {
342         k  = blocks[i];
343         k *= mask1;
344         k  = GMQCC_ROTL32(k, 15);
345         k *= mask2;
346         h ^= k;
347         h  = GMQCC_ROTL32(h, 13);
348         h  = h * 5 + 0xE6546B64;
349     }
350
351     k = 0;
352     switch (len & 3) {
353         case 3:
354             k ^= tail[2] << 16;
355         case 2:
356             k ^= tail[1] << 8;
357         case 1:
358             k ^= tail[0];
359             k *= mask1;
360             k  = GMQCC_ROTL32(k, 15);
361             k *= mask2;
362             h ^= k;
363     }
364
365     h ^= len;
366     h ^= h >> 16;
367     h *= 0x85EBCA6B;
368     h ^= h >> 13;
369     h *= 0xC2B2AE35;
370     h ^= h >> 16;
371
372     return (size_t) (h % ht->size);
373 }
374 #undef GMQCC_ROTL32
375 #else
376 /* We keep the old for reference */
377 GMQCC_INLINE size_t util_hthash(hash_table_t *ht, const char *key) {
378     const uint32_t       mix   = 0x5BD1E995;
379     const uint32_t       rot   = 24;
380     size_t               size  = strlen(key);
381     uint32_t             hash  = 0x1EF0 /* LICRC TAB */  ^ size;
382     uint32_t             alias = 0;
383     const unsigned char *data  = (const unsigned char*)key;
384
385     while (size >= 4) {
386         alias  = (data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24));
387         alias *= mix;
388         alias ^= alias >> rot;
389         alias *= mix;
390
391         hash  *= mix;
392         hash  ^= alias;
393
394         data += 4;
395         size -= 4;
396     }
397
398     switch (size) {
399         case 3: hash ^= data[2] << 16;
400         case 2: hash ^= data[1] << 8;
401         case 1: hash ^= data[0];
402                 hash *= mix;
403     }
404
405     hash ^= hash >> 13;
406     hash *= mix;
407     hash ^= hash >> 15;
408
409     return (size_t) (hash % ht->size);
410 }
411 #endif
412
413 static hash_node_t *_util_htnewpair(const char *key, void *value) {
414     hash_node_t *node;
415     if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t))))
416         return NULL;
417
418     if (!(node->key = util_strdupe(key))) {
419         mem_d(node);
420         return NULL;
421     }
422
423     node->value = value;
424     node->next  = NULL;
425
426     return node;
427 }
428
429 /*
430  * EXPOSED INTERFACE for the hashtable implementation
431  * util_htnew(size)                             -- to make a new hashtable
432  * util_htset(table, key, value, sizeof(value)) -- to set something in the table
433  * util_htget(table, key)                       -- to get something from the table
434  * util_htdel(table)                            -- to delete the table
435  */
436 hash_table_t *util_htnew(size_t size) {
437     hash_table_t      *hashtable = NULL;
438     stat_size_entry_t *find      = NULL;
439
440     if (size < 1)
441         return NULL;
442
443     if (!stat_size_hashtables)
444         stat_size_hashtables = stat_size_new();
445
446     if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t))))
447         return NULL;
448
449     if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) {
450         mem_d(hashtable);
451         return NULL;
452     }
453
454     if ((find = stat_size_get(stat_size_hashtables, size)))
455         find->value++;
456     else {
457         stat_type_hashtables++;
458         stat_size_put(stat_size_hashtables, size, 1);
459     }
460
461     hashtable->size = size;
462     memset(hashtable->table, 0, sizeof(hash_node_t*) * size);
463
464     stat_used_hashtables++;
465     return hashtable;
466 }
467
468 void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) {
469     hash_node_t *newnode = NULL;
470     hash_node_t *next    = NULL;
471     hash_node_t *last    = NULL;
472
473     next = ht->table[bin];
474
475     while (next && next->key && strcmp(key, next->key) > 0)
476         last = next, next = next->next;
477
478     /* already in table, do a replace */
479     if (next && next->key && strcmp(key, next->key) == 0) {
480         next->value = value;
481     } else {
482         /* not found, grow a pair man :P */
483         newnode = _util_htnewpair(key, value);
484         if (next == ht->table[bin]) {
485             newnode->next  = next;
486             ht->table[bin] = newnode;
487         } else if (!next) {
488             last->next = newnode;
489         } else {
490             newnode->next = next;
491             last->next = newnode;
492         }
493     }
494 }
495
496 void util_htset(hash_table_t *ht, const char *key, void *value) {
497     util_htseth(ht, key, util_hthash(ht, key), value);
498 }
499
500 void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) {
501     hash_node_t *pair = ht->table[bin];
502
503     while (pair && pair->key && strcmp(key, pair->key) > 0)
504         pair = pair->next;
505
506     if (!pair || !pair->key || strcmp(key, pair->key) != 0)
507         return NULL;
508
509     return pair->value;
510 }
511
512 void *util_htget(hash_table_t *ht, const char *key) {
513     return util_htgeth(ht, key, util_hthash(ht, key));
514 }
515
516 void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin);
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