]> git.cworth.org Git - vogl/blob - src/voglcore/vogl_mem.cpp
Initial vogl checkin
[vogl] / src / voglcore / vogl_mem.cpp
1 /**************************************************************************
2  *
3  * Copyright 2013-2014 RAD Game Tools and Valve Software
4  * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
5  * All Rights Reserved.
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23  * THE SOFTWARE.
24  *
25  **************************************************************************/
26
27 // File: vogl_mem.cpp
28 #include "vogl_core.h"
29 #include "vogl_console.h"
30 #include "vogl.h"
31 #include "vogl_threading.h"
32 #include "vogl_strutils.h"
33 #include <malloc.h>
34
35 // Set to 1 to enable stb_malloc, otherwise voglcore uses plain malloc/free/realloc
36 #ifndef VOGL_USE_STB_MALLOC
37 #define VOGL_USE_STB_MALLOC 1
38 #endif
39
40 #ifndef VOGL_RAND_FILL_ALLLOCATED_MEMORY
41 #define VOGL_RAND_FILL_ALLLOCATED_MEMORY 0
42 #endif
43
44 #ifndef VOGL_SCRUB_FREED_MEMORY
45 #define VOGL_SCRUB_FREED_MEMORY 0
46 #endif
47
48 #if VOGL_RAND_FILL_ALLLOCATED_MEMORY || VOGL_SCRUB_FREED_MEMORY
49 #warning VOGL_RAND_FILL_ALLLOCATED_MEMORY and/or VOGL_SCRUB_FREED_MEMORY is enabled
50 #endif
51
52 #ifdef VOGL_USE_WIN32_API
53 #include "vogl_winhdr.h"
54 #endif
55
56 #if defined(__GNUC__)
57 #include "mcheck.h"
58 #endif
59
60 #if VOGL_USE_STB_MALLOC
61 #include "vogl_stb_heap.h"
62 #endif
63
64 #if VOGL_MALLOC_DEBUGGING
65 #define MALLOC_DEBUG
66 #include "rmalloc.h"
67 #endif
68
69 #undef vogl_malloc
70 #undef vogl_realloc
71 #undef vogl_calloc
72 #undef vogl_free
73 #undef vogl_check_heap
74 #undef vogl_print_heap_stats
75 #undef vogl_new
76 #undef vogl_new_array
77 #undef vogl_delete
78 #undef vogl_delete_array
79
80 #if VOGL_MALLOC_DEBUGGING
81 #warning vogl_mem.cpp: Malloc debugging enabled
82 #endif
83
84 #if VOGL_USE_STB_MALLOC
85
86 #define STB_ALLOC_INITIAL_HEAP_SIZE 32U * 1024U * 1024U
87 // Purposely written C-style so we can safely do heap allocs BEFORE C++ global construction time.
88
89 static uint64_t g_initial_heap_storage[(STBM_HEAP_SIZEOF + STB_ALLOC_INITIAL_HEAP_SIZE + sizeof(uint64_t) - 1) / sizeof(uint64_t)];
90 static stbm_heap *g_pHeap;
91 static pthread_mutex_t g_mutex;
92 static size_t g_page_size;
93
94 // This flag helps us detect calls to malloc, etc. from within asynchronous signal handlers.
95 static bool g_allocating_flag;
96
97 static void *sys_alloc(void *user_context, size_t size_requested, size_t *size_provided)
98 {
99     VOGL_NOTE_UNUSED(user_context);
100
101     if (!g_page_size)
102     {
103         g_page_size = sysconf(_SC_PAGE_SIZE);
104         if (!g_page_size)
105             g_page_size = 65536;
106     }
107
108     size_requested = (size_requested + g_page_size - 1) & (~(g_page_size - 1));
109
110     void *p = mmap(NULL, size_requested, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
111
112     if ((!p) || (p == MAP_FAILED))
113     {
114         uint e = errno;
115
116         // Not using strerror_r because it fails when we're completely out of memory.
117         char *pError_desc = strerror(e);
118
119         char buf[256];
120         sprintf(buf, "%s: mmap() of %zu bytes failed! Reason: %s (errno 0x%x)\n", VOGL_FUNCTION_NAME, size_requested, pError_desc, e);
121
122         write(STDERR_FILENO, buf, strlen(buf));
123         abort();
124     }
125
126     *size_provided = size_requested;
127
128     return p;
129 }
130
131 static void sys_free(void *user_context, void *ptr, size_t size)
132 {
133     VOGL_NOTE_UNUSED(user_context);
134
135     int res = munmap(ptr, size);
136     if (res != 0)
137     {
138         uint e = errno;
139
140         // Not using strerror_r because it fails when we're completely out of memory.
141         char *pError_desc = strerror(e);
142
143         char buf[256];
144         sprintf(buf, "%s: munmap() ptr 0x%" PRIxPTR " size 0x%" PRIxPTR " failed! Reason: %s (errno 0x%x)\n", VOGL_FUNCTION_NAME, (uintptr_t)ptr, size, pError_desc, e);
145
146         write(STDERR_FILENO, buf, strlen(buf));
147         abort();
148     }
149 }
150
151 #if VOGL_MALLOC_DEBUGGING
152 static void *rmalloc_malloc_callback(size_t size, void *pUser)
153 {
154     VOGL_NOTE_UNUSED(pUser);
155     return stbm_alloc(NULL, g_pHeap, size, 0);
156 }
157
158 static void rmalloc_free_callback(void *ptr, void *pUser)
159 {
160     VOGL_NOTE_UNUSED(pUser);
161     stbm_free(NULL, g_pHeap, ptr);
162 }
163
164 static void *rmalloc_realloc_callback(void *ptr, size_t size, void *pUser)
165 {
166     VOGL_NOTE_UNUSED(pUser);
167     return stbm_realloc(NULL, g_pHeap, ptr, size, 0);
168 }
169 #endif
170
171 static void init_heap()
172 {
173     if (g_pHeap)
174         return;
175
176     pthread_mutex_init(&g_mutex, NULL);
177
178     stbm_heap_config config;
179     memset(&config, 0, sizeof(config));
180     config.system_alloc = sys_alloc;
181     config.system_free = sys_free;
182
183     g_pHeap = stbm_heap_init(g_initial_heap_storage, sizeof(g_initial_heap_storage), &config);
184
185 #if VOGL_MALLOC_DEBUGGING
186     Rmalloc_set_callbacks(rmalloc_malloc_callback, rmalloc_free_callback, rmalloc_realloc_callback, NULL);
187 #endif
188 }
189
190 static void lock_heap()
191 {
192     VOGL_ASSERT(g_pHeap);
193
194     pthread_mutex_lock(&g_mutex);
195
196     VOGL_ASSERT(!g_allocating_flag);
197     g_allocating_flag = true;
198 }
199
200 static void unlock_heap()
201 {
202     g_allocating_flag = false;
203
204     VOGL_ASSERT(g_pHeap);
205     pthread_mutex_unlock(&g_mutex);
206 }
207
208 static void *malloc_block(size_t size, const char *pFile_line)
209 {
210     VOGL_ASSERT(g_pHeap);
211
212     lock_heap();
213
214 #if VOGL_MALLOC_DEBUGGING
215     void *p = Rmalloc(size, pFile_line);
216 #else
217     VOGL_NOTE_UNUSED(pFile_line);
218     void *p = stbm_alloc(NULL, g_pHeap, size, 0);
219 #endif
220
221     unlock_heap();
222
223     return p;
224 }
225
226 static void *realloc_block(void *p, size_t size, const char *pFile_line)
227 {
228     VOGL_ASSERT(g_pHeap);
229
230     lock_heap();
231
232 #if VOGL_MALLOC_DEBUGGING
233     void *q = Rrealloc(p, size, pFile_line);
234 #else
235     VOGL_NOTE_UNUSED(pFile_line);
236     void *q = stbm_realloc(NULL, g_pHeap, p, size, 0);
237 #endif
238
239     unlock_heap();
240
241     return q;
242 }
243
244 static void free_block(void *p, const char *pFile_line)
245 {
246     VOGL_ASSERT(g_pHeap);
247
248     lock_heap();
249
250 #if VOGL_MALLOC_DEBUGGING
251     Rfree(p, pFile_line);
252 #else
253     VOGL_NOTE_UNUSED(pFile_line);
254     stbm_free(NULL, g_pHeap, p);
255 #endif
256
257     unlock_heap();
258 }
259
260 static size_t msize_block(void *p, const char *pFile_line)
261 {
262     VOGL_ASSERT(g_pHeap);
263
264     lock_heap();
265
266 #if VOGL_MALLOC_DEBUGGING
267     size_t n = Rmalloc_usable_size(p, pFile_line);
268 #else
269     VOGL_NOTE_UNUSED(pFile_line);
270     size_t n = stbm_get_allocation_size(p);
271 #endif
272
273     unlock_heap();
274
275     return n;
276 }
277
278 static void print_stats(const char *pFile_line)
279 {
280     VOGL_ASSERT(g_pHeap);
281
282 #if VOGL_MALLOC_DEBUGGING
283     lock_heap();
284
285     Rmalloc_stat(pFile_line);
286
287     unlock_heap();
288 #else
289     VOGL_NOTE_UNUSED(pFile_line);
290 #endif
291 }
292
293 static void check(const char *pFile_line)
294 {
295     VOGL_ASSERT(g_pHeap);
296
297 #if VOGL_MALLOC_DEBUGGING
298     lock_heap();
299
300     Rmalloc_test(pFile_line);
301
302     unlock_heap();
303 #else
304     VOGL_NOTE_UNUSED(pFile_line);
305 #endif
306 }
307
308 #else // !VOGL_USE_STB_MALLOC
309
310 static bool g_heap_initialized;
311 static pthread_mutex_t g_mutex;
312
313 static void init_heap()
314 {
315     if (g_heap_initialized)
316         return;
317
318     pthread_mutex_init(&g_mutex, NULL);
319     g_heap_initialized = true;
320 }
321
322 #if VOGL_USE_STB_MALLOC
323 static void lock_heap()
324 {
325     VOGL_ASSERT(g_heap_initialized);
326     pthread_mutex_lock(&g_mutex);
327 }
328
329 static void unlock_heap()
330 {
331     VOGL_ASSERT(g_heap_initialized);
332     pthread_mutex_unlock(&g_mutex);
333 }
334 #endif
335
336 static void *malloc_block(size_t size, const char *pFile_line)
337 {
338     VOGL_ASSERT(g_heap_initialized);
339     VOGL_NOTE_UNUSED(pFile_line);
340
341 #if VOGL_MALLOC_DEBUGGING
342     lock_heap();
343     void *p = Rmalloc(size, pFile_line);
344     unlock_heap();
345 #else
346     void *p = malloc(size);
347 #endif
348
349     return p;
350 }
351
352 static void *realloc_block(void *p, size_t size, const char *pFile_line)
353 {
354     VOGL_ASSERT(g_heap_initialized);
355     VOGL_NOTE_UNUSED(pFile_line);
356
357 #if VOGL_MALLOC_DEBUGGING
358     lock_heap();
359     void *q = Rrealloc(p, size, pFile_line);
360     unlock_heap();
361 #else
362     void *q = realloc(p, size);
363 #endif
364
365     return q;
366 }
367
368 static void free_block(void *p, const char *pFile_line)
369 {
370     VOGL_ASSERT(g_heap_initialized);
371     VOGL_NOTE_UNUSED(pFile_line);
372
373 #if VOGL_MALLOC_DEBUGGING
374     lock_heap();
375     Rfree(p, pFile_line);
376     unlock_heap();
377 #else
378     free(p);
379 #endif
380 }
381
382 static size_t msize_block(void *p, const char *pFile_line)
383 {
384     VOGL_ASSERT(g_heap_initialized);
385     VOGL_NOTE_UNUSED(pFile_line);
386
387 #if VOGL_MALLOC_DEBUGGING
388     lock_heap();
389     size_t n = Rmalloc_usable_size(p, pFile_line);
390     unlock_heap();
391 #else
392     size_t n = malloc_usable_size(p);
393 #endif
394
395     return n;
396 }
397
398 static void print_stats(const char *pFile_line)
399 {
400     VOGL_ASSERT(g_heap_initialized);
401     VOGL_NOTE_UNUSED(pFile_line);
402
403 #if VOGL_MALLOC_DEBUGGING
404     lock_heap();
405     Rmalloc_stat(pFile_line);
406     unlock_heap();
407 #endif
408 }
409
410 static void check(const char *pFile_line)
411 {
412     VOGL_ASSERT(g_heap_initialized);
413     VOGL_NOTE_UNUSED(pFile_line);
414
415 #if VOGL_MALLOC_DEBUGGING
416     lock_heap();
417     Rmalloc_test(pFile_line);
418     unlock_heap();
419 #endif
420 }
421
422 #endif // #ifdef VOGL_USE_STB_MALLOC
423
424 class global_heap_constructor
425 {
426 public:
427     global_heap_constructor()
428     {
429         //printf("global_heap_constructor\n");
430
431         init_heap();
432     }
433 };
434
435 global_heap_constructor g_global_heap_constructor __attribute__((init_priority(102)));
436
437 VOGL_NAMESPACE_BEGIN(vogl)
438
439 void vogl_init_heap()
440 {
441     init_heap();
442 }
443
444 // TODO: vararg this
445 void vogl_mem_error(const char *pMsg, const char *pFile_line)
446 {
447     char buf[512];
448     vogl::vogl_sprintf_s(buf, sizeof(buf), "%s: Fatal error: %s. Originally called from %s.\n", VOGL_FUNCTION_NAME, pMsg, pFile_line ? pFile_line : "?");
449     vogl_fail(buf, __FILE__, __LINE__);
450     abort();
451 }
452
453 #if VOGL_RAND_FILL_ALLLOCATED_MEMORY || VOGL_SCRUB_FREED_MEMORY
454 static uint32 g_cur_rand = 0xDEADBEEF;
455
456 static void random_fill(void *p, size_t size)
457 {
458 #define JSR (jsr ^= (jsr << 17), jsr ^= (jsr >> 13), jsr ^= (jsr << 5));
459     uint32 jsr = g_cur_rand;
460
461     while (size >= sizeof(uint32) * 4)
462     {
463         static_cast<uint32 *>(p)[0] = jsr;
464         JSR;
465         static_cast<uint32 *>(p)[1] = jsr;
466         JSR;
467         static_cast<uint32 *>(p)[2] = jsr;
468         JSR;
469         static_cast<uint32 *>(p)[3] = jsr;
470         JSR;
471
472         size -= sizeof(uint32) * 4;
473         p = static_cast<uint32 *>(p) + 4;
474     }
475
476     while (size >= sizeof(uint32))
477     {
478         static_cast<uint32 *>(p)[0] = jsr;
479         JSR;
480
481         size -= sizeof(uint32);
482         p = static_cast<uint32 *>(p);
483     }
484
485     while (size)
486     {
487         static_cast<uint8 *>(p)[0] = static_cast<uint8>(jsr);
488         JSR;
489
490         size--;
491         p = static_cast<uint8 *>(p) + 1;
492     }
493
494     g_cur_rand = jsr;
495 #undef JSR
496 }
497 #endif // VOGL_RAND_FILL_ALLLOCATED_MEMORY || VOGL_SCRUB_FREED_MEMORY
498
499 void *vogl_tracked_malloc(const char *pFile_line, size_t size, size_t *pActual_size)
500 {
501     size = (size + sizeof(uint32) - 1U) & ~(sizeof(uint32) - 1U);
502     if (!size)
503         size = sizeof(uint32);
504
505     if (size > VOGL_MAX_POSSIBLE_HEAP_BLOCK_SIZE)
506     {
507         vogl_mem_error("vogl_malloc: size too big", pFile_line);
508         return NULL;
509     }
510
511     uint8 *p_new = (uint8 *)malloc_block(size, pFile_line);
512
513     VOGL_ASSERT((reinterpret_cast<ptr_bits_t>(p_new) & (VOGL_MIN_ALLOC_ALIGNMENT - 1)) == 0);
514
515     if (!p_new)
516     {
517         vogl_mem_error("vogl_malloc: out of memory", pFile_line);
518         return NULL;
519     }
520
521     if (pActual_size)
522     {
523         *pActual_size = msize_block(p_new, pFile_line);
524
525         if ((size) && (*pActual_size >= static_cast<uint64_t>(size) * 16U))
526         {
527             // I've seen this happen with glibc's absolutely terrible debug heap crap, best to let the caller know that shit is going to die
528             fprintf(stderr, "%s: malloc_usable_size may be misbehaving! Requested %zu bytes, but the usable size is reported as %zu bytes.\n", __FUNCTION__, size, *pActual_size);
529         }
530     }
531
532 #if VOGL_RAND_FILL_ALLLOCATED_MEMORY
533     random_fill(p_new, size);
534 #endif
535
536     return p_new;
537 }
538
539 void *vogl_tracked_realloc(const char *pFile_line, void *p, size_t size, size_t *pActual_size)
540 {
541     if ((ptr_bits_t)p & (VOGL_MIN_ALLOC_ALIGNMENT - 1))
542     {
543         vogl_mem_error("vogl_realloc: bad ptr", pFile_line);
544         return NULL;
545     }
546
547     if (size > VOGL_MAX_POSSIBLE_HEAP_BLOCK_SIZE)
548     {
549         vogl_mem_error("vogl_realloc: size too big!", pFile_line);
550         return NULL;
551     }
552
553     if ((size) && (size < sizeof(uint32)))
554         size = sizeof(uint32);
555
556 #if VOGL_RAND_FILL_ALLLOCATED_MEMORY || VOGL_SCRUB_FREED_MEMORY
557     size_t orig_size = p ? msize_block(p, pFile_line) : 0;
558
559 #if VOGL_SCRUB_FREED_MEMORY
560     if ((orig_size) && (!size))
561         random_fill(p, orig_size);
562 #endif
563 #endif
564
565     void *p_new = realloc_block(p, size, pFile_line);
566
567     VOGL_ASSERT((reinterpret_cast<ptr_bits_t>(p_new) & (VOGL_MIN_ALLOC_ALIGNMENT - 1)) == 0);
568
569     if (pActual_size)
570     {
571         *pActual_size = 0;
572
573         if (size)
574         {
575             if (p_new)
576                 *pActual_size = msize_block(p_new, pFile_line);
577             else if (p)
578                 *pActual_size = msize_block(p, pFile_line);
579
580             if (*pActual_size >= static_cast<uint64_t>(size) * 16U)
581             {
582                 // I've seen this happen with glibc's absolutely terrible debug heap crap, best to let the caller know that shit is going to die
583                 fprintf(stderr, "%s: malloc_usable_size may be misbehaving! Requested %zu bytes, but the usable size is reported as %zu bytes.\n", __FUNCTION__, size, *pActual_size);
584             }
585         }
586     }
587
588 #if VOGL_RAND_FILL_ALLLOCATED_MEMORY
589     if ((size) && (p_new))
590     {
591         size_t new_size = msize_block(p_new, pFile_line);
592
593         if (new_size > orig_size)
594             random_fill(static_cast<uint8 *>(p_new) + orig_size, new_size - orig_size);
595     }
596 #endif
597
598     return p_new;
599 }
600
601 void *vogl_tracked_calloc(const char *pFile_line, size_t count, size_t size, size_t *pActual_size)
602 {
603     size_t total = count * size;
604     void *p = vogl_tracked_malloc(pFile_line, total, pActual_size);
605     if (p)
606         memset(p, 0, total);
607     return p;
608 }
609
610 void vogl_tracked_free(const char *pFile_line, void *p)
611 {
612     if (!p)
613         return;
614
615     if (reinterpret_cast<ptr_bits_t>(p) & (VOGL_MIN_ALLOC_ALIGNMENT - 1))
616     {
617         vogl_mem_error("vogl_free: bad ptr", pFile_line);
618         return;
619     }
620
621 #if VOGL_SCRUB_FREED_MEMORY
622     random_fill(p, msize_block(p, pFile_line));
623 #endif
624
625     free_block(p, pFile_line);
626 }
627
628 size_t vogl_msize(void *p)
629 {
630     if (!p)
631         return 0;
632
633     if (reinterpret_cast<ptr_bits_t>(p) & (VOGL_MIN_ALLOC_ALIGNMENT - 1))
634     {
635         vogl_mem_error("vogl_msize: bad ptr", VOGL_FILE_POS_STRING);
636         return 0;
637     }
638
639     return msize_block(p, VOGL_FILE_POS_STRING);
640 }
641
642 void vogl_tracked_print_stats(const char *pFile_line)
643 {
644     print_stats(pFile_line);
645 }
646
647 void vogl_tracked_check_heap(const char *pFile_line)
648 {
649     check(pFile_line);
650
651 #ifdef WIN32
652     // Also see_CrtSetDbgFlag ()
653     int status = _CrtCheckMemory();
654     VOGL_VERIFY(status == TRUE);
655 #elif defined(__GNUC__)
656     // App MUST have been linked with -mlcheck! Unfortunately -mlcheck() causes the CRT's heap API's to be unusable on some dev boxes, no idea why.
657     mcheck_check_all();
658 #else
659     fprintf(stderr, "%s: Need implementation\n", __FUNCTION__);
660 #endif
661 }
662
663 VOGL_NAMESPACE_END(vogl)
664
665 extern "C" void *vogl_realloc(const char *pFile_line, void *p, size_t new_size)
666 {
667     return vogl::vogl_tracked_realloc(pFile_line, p, new_size, NULL);
668 }