]> git.xonotic.org Git - xonotic/gmqcc.git/blob - stat.c
Fix the peak
[xonotic/gmqcc.git] / stat.c
1 #include "gmqcc.h"
2
3 /*
4  * GMQCC performs tons of allocations, constructions, and crazyness
5  * all around. When trying to optimizes systems, or just get fancy
6  * statistics out of the compiler, it's often printf mess. This file
7  * implements the statistics system of the compiler. I.E the allocator
8  * we use to track allocations, and other systems of interest.
9  */
10 #define ST_SIZE 1024
11
12 typedef struct stat_mem_block_s {
13     const char              *file;
14     size_t                   line;
15     size_t                   size;
16     struct stat_mem_block_s *next;
17     struct stat_mem_block_s *prev;
18 } stat_mem_block_t;
19
20 static uint64_t          stat_mem_allocated         = 0;
21 static uint64_t          stat_mem_deallocated       = 0;
22 static uint64_t          stat_mem_allocated_total   = 0;
23 static uint64_t          stat_mem_deallocated_total = 0;
24 static uint64_t          stat_mem_high              = 0;
25 static uint64_t          stat_mem_peak              = 0;
26 static uint64_t          stat_used_strdups          = 0;
27 static uint64_t          stat_used_vectors          = 0;
28 static uint64_t          stat_used_hashtables       = 0;
29 static uint64_t          stat_type_vectors          = 0;
30 static uint64_t          stat_type_hashtables       = 0;
31 static stat_size_table_t stat_size_vectors          = NULL;
32 static stat_size_table_t stat_size_hashtables       = NULL;
33 static stat_mem_block_t *stat_mem_block_root        = NULL;
34
35 /*
36  * A basic header of information wrapper allocator. Simply stores
37  * information as a header, returns the memory + 1 past it, can be
38  * retrieved again with - 1. Where type is stat_mem_block_t*.
39  */
40 void *stat_mem_allocate(size_t size, size_t line, const char *file) {
41     stat_mem_block_t *info = (stat_mem_block_t*)malloc(sizeof(stat_mem_block_t) + size);
42     void             *data = (void*)(info + 1);
43     
44     if(!info)
45         return NULL;
46         
47     info->line = line;
48     info->size = size;
49     info->file = file;
50     info->prev = NULL;
51     info->next = stat_mem_block_root;
52     
53     if (stat_mem_block_root)
54         stat_mem_block_root->prev = info;
55         
56     stat_mem_block_root       = info;
57     stat_mem_allocated       += size;
58     stat_mem_high            += size;
59     stat_mem_allocated_total ++;
60     
61     if (stat_mem_high > stat_mem_peak)
62         stat_mem_peak = stat_mem_high;
63
64     return data;
65 }
66
67 void stat_mem_deallocate(void *ptr) {
68     stat_mem_block_t *info = NULL;
69     
70     if (!ptr)
71         return;
72         
73     info = ((stat_mem_block_t*)ptr - 1);
74     
75     stat_mem_deallocated       += info->size;
76     stat_mem_high              -= info->size;
77     stat_mem_deallocated_total ++;
78     
79     if (info->prev) info->prev->next = info->next;
80     if (info->next) info->next->prev = info->prev;
81     
82     /* move ahead */
83     if (info == stat_mem_block_root)
84         stat_mem_block_root = info->next;
85         
86     free(info);
87 }
88
89 void *stat_mem_reallocate(void *ptr, size_t size, size_t line, const char *file) {
90     stat_mem_block_t *oldinfo = NULL;
91     stat_mem_block_t *newinfo;
92     
93     if (!ptr)
94         return stat_mem_allocate(size, line, file);
95     
96     /* stay consistent with glic */
97     if (!size) {
98         stat_mem_deallocate(ptr);
99         return NULL;
100     }
101     
102     oldinfo = ((stat_mem_block_t*)ptr - 1);
103     newinfo = ((stat_mem_block_t*)malloc(sizeof(stat_mem_block_t) + size));
104     
105     if (!newinfo) {
106         stat_mem_deallocate(ptr);
107         return NULL;
108     }
109     
110     memcpy(newinfo+1, oldinfo+1, oldinfo->size);
111     
112     if (oldinfo->prev) oldinfo->prev->next = oldinfo->next;
113     if (oldinfo->next) oldinfo->next->prev = oldinfo->prev;
114     
115     /* move ahead */
116     if (oldinfo == stat_mem_block_root)
117         stat_mem_block_root = oldinfo->next;
118         
119     newinfo->line = line;
120     newinfo->size = size;
121     newinfo->file = file;
122     newinfo->prev = NULL;
123     newinfo->next = stat_mem_block_root;
124     
125     if (stat_mem_block_root)
126         stat_mem_block_root->prev = newinfo;
127     
128     stat_mem_block_root = newinfo;
129     stat_mem_allocated -= oldinfo->size;
130     stat_mem_high      -= oldinfo->size;
131     stat_mem_allocated += newinfo->size;
132     stat_mem_high      += newinfo->size;
133     
134     if (stat_mem_high > stat_mem_peak)
135         stat_mem_peak = stat_mem_high;
136         
137     free(oldinfo);
138     
139     return newinfo + 1;
140 }
141
142 /*
143  * strdup does it's own malloc, we need to track malloc. We don't want
144  * to overwrite malloc though, infact, we can't really hook it at all
145  * without library specific assumptions. So we re implement strdup.
146  */
147 char *stat_mem_strdup(const char *src, size_t line, const char *file, bool empty) {
148     size_t len = 0;
149     char  *ptr = NULL;
150     
151     if (!src)
152         return NULL;
153     
154     len = strlen(src);
155     if (((!empty) ? len : true) && (ptr = (char*)stat_mem_allocate(len + 1, line, file))) {
156         memcpy(ptr, src, len);
157         ptr[len] = '\0';
158     }
159     
160     stat_used_strdups ++;
161     return ptr;
162 }
163
164 /*
165  * The reallocate function for resizing vectors.
166  */
167 void _util_vec_grow(void **a, size_t i, size_t s) {
168     vector_t          *d = vec_meta(*a);
169     size_t             m = 0;
170     stat_size_entry_t *e = NULL;
171     void              *p = NULL;
172     
173     if (*a) {
174         m = 2 * d->allocated + i;
175         p = mem_r(d, s * m + sizeof(vector_t));
176     } else {
177         m = i + 1;
178         p = mem_a(s * m + sizeof(vector_t));
179         ((vector_t*)p)->used = 0;
180         stat_used_vectors++;
181     }
182     
183     if (!stat_size_vectors)
184         stat_size_vectors = stat_size_new();
185
186     if ((e = stat_size_get(stat_size_vectors, s))) {
187         e->value ++;
188     } else {
189         stat_size_put(stat_size_vectors, s, 1); /* start off with 1 */
190         stat_type_vectors++;
191     }
192
193     *a = (vector_t*)p + 1;
194     vec_meta(*a)->allocated = m;
195 }
196
197 /*
198  * Hash table for generic data, based on dynamic memory allocations
199  * all around.  This is the internal interface, please look for
200  * EXPOSED INTERFACE comment below
201  */
202 typedef struct hash_node_t {
203     char               *key;   /* the key for this node in table */
204     void               *value; /* pointer to the data as void*   */
205     struct hash_node_t *next;  /* next node (linked list)        */
206 } hash_node_t;
207
208 GMQCC_INLINE size_t util_hthash(hash_table_t *ht, const char *key) {
209     const uint32_t       mix   = 0x5BD1E995;
210     const uint32_t       rot   = 24;
211     size_t               size  = strlen(key);
212     uint32_t             hash  = 0x1EF0 /* LICRC TAB */  ^ size;
213     uint32_t             alias = 0;
214     const unsigned char *data  = (const unsigned char*)key;
215
216     while (size >= 4) {
217         alias  = (data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24));
218         alias *= mix;
219         alias ^= alias >> rot;
220         alias *= mix;
221
222         hash  *= mix;
223         hash  ^= alias;
224
225         data += 4;
226         size -= 4;
227     }
228
229     switch (size) {
230         case 3: hash ^= data[2] << 16;
231         case 2: hash ^= data[1] << 8;
232         case 1: hash ^= data[0];
233                 hash *= mix;
234     }
235
236     hash ^= hash >> 13;
237     hash *= mix;
238     hash ^= hash >> 15;
239
240     return (size_t) (hash % ht->size);
241 }
242
243 static hash_node_t *_util_htnewpair(const char *key, void *value) {
244     hash_node_t *node;
245     if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t))))
246         return NULL;
247
248     if (!(node->key = util_strdupe(key))) {
249         mem_d(node);
250         return NULL;
251     }
252
253     node->value = value;
254     node->next  = NULL;
255
256     return node;
257 }
258
259 /*
260  * EXPOSED INTERFACE for the hashtable implementation
261  * util_htnew(size)                             -- to make a new hashtable
262  * util_htset(table, key, value, sizeof(value)) -- to set something in the table
263  * util_htget(table, key)                       -- to get something from the table
264  * util_htdel(table)                            -- to delete the table
265  */
266 hash_table_t *util_htnew(size_t size) {
267     hash_table_t      *hashtable = NULL;
268     stat_size_entry_t *find      = NULL;
269     
270     if (size < 1)
271         return NULL;
272         
273     if (!stat_size_hashtables)
274         stat_size_hashtables = stat_size_new();
275
276     if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t))))
277         return NULL;
278
279     if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) {
280         mem_d(hashtable);
281         return NULL;
282     }
283     
284     if ((find = stat_size_get(stat_size_hashtables, size)))
285         find->value++;
286     else {
287         stat_used_hashtables++;
288         stat_size_put(stat_size_hashtables, size, 1);
289     }
290
291     hashtable->size = size;
292     memset(hashtable->table, 0, sizeof(hash_node_t*) * size);
293
294     stat_type_hashtables++;
295     return hashtable;
296 }
297
298 void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) {
299     hash_node_t *newnode = NULL;
300     hash_node_t *next    = NULL;
301     hash_node_t *last    = NULL;
302
303     next = ht->table[bin];
304
305     while (next && next->key && strcmp(key, next->key) > 0)
306         last = next, next = next->next;
307
308     /* already in table, do a replace */
309     if (next && next->key && strcmp(key, next->key) == 0) {
310         next->value = value;
311     } else {
312         /* not found, grow a pair man :P */
313         newnode = _util_htnewpair(key, value);
314         if (next == ht->table[bin]) {
315             newnode->next  = next;
316             ht->table[bin] = newnode;
317         } else if (!next) {
318             last->next = newnode;
319         } else {
320             newnode->next = next;
321             last->next = newnode;
322         }
323     }
324 }
325
326 void util_htset(hash_table_t *ht, const char *key, void *value) {
327     util_htseth(ht, key, util_hthash(ht, key), value);
328 }
329
330 void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) {
331     hash_node_t *pair = ht->table[bin];
332
333     while (pair && pair->key && strcmp(key, pair->key) > 0)
334         pair = pair->next;
335
336     if (!pair || !pair->key || strcmp(key, pair->key) != 0)
337         return NULL;
338
339     return pair->value;
340 }
341
342 void *util_htget(hash_table_t *ht, const char *key) {
343     return util_htgeth(ht, key, util_hthash(ht, key));
344 }
345
346 void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) {
347     hash_node_t *pair;
348     size_t len, keylen;
349     int cmp;
350
351     keylen = strlen(key);
352
353     pair = ht->table[bin];
354     while (pair && pair->key) {
355         len = strlen(pair->key);
356         if (len < keylen) {
357             pair = pair->next;
358             continue;
359         }
360         if (keylen == len) {
361             cmp = strcmp(key, pair->key);
362             if (cmp == 0)
363                 return pair->value;
364             if (cmp < 0)
365                 return NULL;
366             pair = pair->next;
367             continue;
368         }
369         cmp = strcmp(key, pair->key + len - keylen);
370         if (cmp == 0) {
371             uintptr_t up = (uintptr_t)pair->value;
372             up += len - keylen;
373             return (void*)up;
374         }
375         pair = pair->next;
376     }
377     return NULL;
378 }
379
380 /*
381  * Free all allocated data in a hashtable, this is quite the amount
382  * of work.
383  */
384 void util_htrem(hash_table_t *ht, void (*callback)(void *data)) {
385     size_t i = 0;
386     for (; i < ht->size; i++) {
387         hash_node_t *n = ht->table[i];
388         hash_node_t *p;
389
390         /* free in list */
391         while (n) {
392             if (n->key)
393                 mem_d(n->key);
394             if (callback)
395                 callback(n->value);
396             p = n;
397             n = n->next;
398             mem_d(p);
399         }
400
401     }
402     /* free table */
403     mem_d(ht->table);
404     mem_d(ht);
405 }
406
407 void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) {
408     hash_node_t **pair = &ht->table[bin];
409     hash_node_t *tmp;
410
411     while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0)
412         pair = &(*pair)->next;
413
414     tmp = *pair;
415     if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0)
416         return;
417
418     if (cb)
419         (*cb)(tmp->value);
420
421     *pair = tmp->next;
422     mem_d(tmp->key);
423     mem_d(tmp);
424 }
425
426 void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) {
427     util_htrmh(ht, key, util_hthash(ht, key), cb);
428 }
429
430 void util_htdel(hash_table_t *ht) {
431     util_htrem(ht, NULL);
432 }
433
434 /*
435  * A tiny size_t key-value hashtbale for tracking vector and hashtable
436  * sizes. We can use it for other things too, if we need to. This is
437  * very TIGHT, and efficent in terms of space though.
438  */
439 stat_size_table_t stat_size_new() {
440     return (stat_size_table_t)memset(
441         mem_a(sizeof(stat_size_entry_t*) * ST_SIZE),
442         0, ST_SIZE * sizeof(stat_size_entry_t*)
443     );
444 }
445
446 void stat_size_del(stat_size_table_t table) {
447     size_t i = 0;
448     for (; i < ST_SIZE; i++) if(table[i]) mem_d(table[i]);
449     mem_d(table);
450 }
451
452 stat_size_entry_t *stat_size_get(stat_size_table_t table, size_t key) {
453     size_t hash = (key % ST_SIZE);
454     while (table[hash] && table[hash]->key != key)
455         hash = (hash + 1) % ST_SIZE;
456     return table[hash];
457 }
458 void stat_size_put(stat_size_table_t table, size_t key, size_t value) {
459     size_t hash = (key % ST_SIZE);
460     while (table[hash] && table[hash]->key != key)
461         hash = (hash + 1) % ST_SIZE;
462     table[hash] = (stat_size_entry_t*)mem_a(sizeof(stat_size_entry_t));
463     table[hash]->key = key;
464     table[hash]->value = value;
465 }
466
467 /*
468  * The following functions below implement printing / dumping of statistical
469  * information.
470  */
471 static void stat_dump_mem_contents(stat_mem_block_t *memory, uint16_t cols) {
472     uint32_t i, j;
473     for (i = 0; i < memory->size + ((memory->size % cols) ? (cols - memory->size % cols) : 0); i++) {
474         if (i % cols == 0)    con_out(" 0x%06X: ", i);
475         if (i < memory->size) con_out("%02X " , 0xFF & ((unsigned char*)(memory + 1))[i]);
476         else                  con_out(" ");
477
478         if ((uint16_t)(i % cols) == (cols - 1)) {
479             for (j = i - (cols - 1); j <= i; j++) {
480                 con_out("%c",
481                     (j >= memory->size)
482                         ? ' '
483                         : (isprint(((unsigned char*)(memory + 1))[j]))
484                             ? 0xFF & ((unsigned char*)(memory + 1)) [j]
485                             : '.'
486                 );
487             }
488             con_out("\n");
489         }
490     }
491 }
492
493 static void stat_dump_mem_leaks() {
494     stat_mem_block_t *info;
495     for (info = stat_mem_block_root; info; info = info->next) {
496         con_out("lost: %u (bytes) at %s:%u\n",
497             info->size,
498             info->file,
499             info->line
500         );
501         
502         stat_dump_mem_contents(info, OPTS_OPTION_U16(OPTION_MEMDUMPCOLS));
503     }
504 }
505
506 static void stat_dump_mem_info() {
507     con_out("Memory information:\n\
508     Total allocations:   %llu\n\
509     Total deallocations: %llu\n\
510     Total allocated:     %f (MB)\n\
511     Total deallocated:   %f (MB)\n\
512     Total peak memory:   %f (MB)\n\
513     Total leaked memory: %f (MB) in %llu allocations\n",
514         stat_mem_allocated_total,
515         stat_mem_deallocated_total,
516         (float)(stat_mem_allocated)                        / 1048576.0f,
517         (float)(stat_mem_deallocated)                      / 1048576.0f,
518         (float)(stat_mem_peak)                             / 1048576.0f,
519         (float)(stat_mem_allocated - stat_mem_deallocated) / 1048576.0f,
520         stat_mem_allocated_total - stat_mem_deallocated_total
521     );
522 }
523
524 static void stat_dump_stats_table(stat_size_table_t table, const char *string, uint64_t *size) {
525     size_t i,j;
526     
527     for (i = 0, j = 0; i < ST_SIZE; i++) {
528         stat_size_entry_t *entry;
529
530         if (!(entry = table[i]))
531             continue;
532
533         con_out(string, (unsigned)j, (unsigned)entry->key, (unsigned)entry->value);
534         j++;
535         
536         if (size)
537             *size += entry->key * entry->value;
538     }
539 }
540
541 void stat_info() {
542     if (OPTS_OPTION_BOOL(OPTION_DEBUG))
543         stat_dump_mem_leaks();
544
545     if (OPTS_OPTION_BOOL(OPTION_DEBUG) ||
546         OPTS_OPTION_BOOL(OPTION_MEMCHK))
547         stat_dump_mem_info();
548
549     if (OPTS_OPTION_BOOL(OPTION_MEMCHK) ||
550         OPTS_OPTION_BOOL(OPTION_STATISTICS)) {
551         uint64_t mem;
552         
553         con_out("\nAdditional Statistics:\n\
554     Total vectors allocated:    %llu\n\
555     Total string duplicates:    %llu\n\
556     Total hashtables allocated: %llu\n\
557     Total unique vector sizes:  %llu\n",
558             stat_used_vectors,
559             stat_used_strdups,
560             stat_used_hashtables,
561             stat_type_vectors
562         );
563         
564         stat_dump_stats_table (
565             stat_size_vectors,
566             "        %2u| # of %4u byte vectors: %u\n",
567             &mem
568         );
569         
570         con_out (
571             "    Total unique hashtable sizes: %llu\n",
572             stat_type_hashtables
573         );
574         
575         stat_dump_stats_table (
576             stat_size_hashtables,
577             "        %2u| # of %4u element hashtables: %u\n",
578             NULL
579         );
580         
581         con_out (
582             "    Total vector memory:          %f (MB)\n",
583             (float)(mem) / 1048576.0f
584         );
585     }
586
587     if (stat_size_vectors)
588         stat_size_del(stat_size_vectors);
589     if (stat_size_hashtables)
590         stat_size_del(stat_size_hashtables);
591 }
592 #undef ST_SIZE