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