Memory allocator: Clarify consistency check function
[blender-staging.git] / intern / guardedalloc / intern / mallocn_lockfree_impl.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor(s): Brecht Van Lommel
19  *                 Campbell Barton
20  *                 Sergey Sharybin
21  *
22  * ***** END GPL LICENSE BLOCK *****
23  */
24
25 /** \file guardedalloc/intern/mallocn.c
26  *  \ingroup MEM
27  *
28  * Memory allocation which keeps track on allocated memory counters
29  */
30
31 #include <stdlib.h>
32 #include <string.h> /* memcpy */
33 #include <stdarg.h>
34 #include <sys/types.h>
35
36 #include "MEM_guardedalloc.h"
37
38 /* to ensure strict conversions */
39 #include "../../source/blender/blenlib/BLI_strict_flags.h"
40
41 #include "atomic_ops.h"
42 #include "mallocn_intern.h"
43
44 typedef struct MemHead {
45         /* Length of allocated memory block. */
46         size_t len;
47 } MemHead;
48
49 typedef struct MemHeadAligned {
50         short alignment;
51         size_t len;
52 } MemHeadAligned;
53
54 static unsigned int totblock = 0;
55 static size_t mem_in_use = 0, mmap_in_use = 0, peak_mem = 0;
56 static bool malloc_debug_memset = false;
57
58 static void (*error_callback)(const char *) = NULL;
59 static void (*thread_lock_callback)(void) = NULL;
60 static void (*thread_unlock_callback)(void) = NULL;
61
62 enum {
63         MEMHEAD_MMAP_FLAG = 1,
64         MEMHEAD_ALIGN_FLAG = 2,
65 };
66
67 #define MEMHEAD_FROM_PTR(ptr) (((MemHead*) ptr) - 1)
68 #define PTR_FROM_MEMHEAD(memhead) (memhead + 1)
69 #define MEMHEAD_ALIGNED_FROM_PTR(ptr) (((MemHeadAligned*) ptr) - 1)
70 #define MEMHEAD_IS_MMAP(memhead) ((memhead)->len & (size_t) MEMHEAD_MMAP_FLAG)
71 #define MEMHEAD_IS_ALIGNED(memhead) ((memhead)->len & (size_t) MEMHEAD_ALIGN_FLAG)
72
73 /* Uncomment this to have proper peak counter. */
74 #define USE_ATOMIC_MAX
75
76 MEM_INLINE void update_maximum(size_t *maximum_value, size_t value)
77 {
78 #ifdef USE_ATOMIC_MAX
79         atomic_fetch_and_update_max_z(maximum_value, value);
80 #else
81         *maximum_value = value > *maximum_value ? value : *maximum_value;
82 #endif
83 }
84
85 #ifdef __GNUC__
86 __attribute__ ((format(printf, 1, 2)))
87 #endif
88 static void print_error(const char *str, ...)
89 {
90         char buf[512];
91         va_list ap;
92
93         va_start(ap, str);
94         vsnprintf(buf, sizeof(buf), str, ap);
95         va_end(ap);
96         buf[sizeof(buf) - 1] = '\0';
97
98         if (error_callback) {
99                 error_callback(buf);
100         }
101 }
102
103 #if defined(WIN32)
104 static void mem_lock_thread(void)
105 {
106         if (thread_lock_callback)
107                 thread_lock_callback();
108 }
109
110 static void mem_unlock_thread(void)
111 {
112         if (thread_unlock_callback)
113                 thread_unlock_callback();
114 }
115 #endif
116
117 size_t MEM_lockfree_allocN_len(const void *vmemh)
118 {
119         if (vmemh) {
120                 return MEMHEAD_FROM_PTR(vmemh)->len & ~((size_t) (MEMHEAD_MMAP_FLAG | MEMHEAD_ALIGN_FLAG));
121         }
122         else {
123                 return 0;
124         }
125 }
126
127 void MEM_lockfree_freeN(void *vmemh)
128 {
129         MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
130         size_t len = MEM_lockfree_allocN_len(vmemh);
131
132         if (vmemh == NULL) {
133                 print_error("Attempt to free NULL pointer\n");
134 #ifdef WITH_ASSERT_ABORT
135                 abort();
136 #endif
137                 return;
138         }
139
140         atomic_sub_and_fetch_u(&totblock, 1);
141         atomic_sub_and_fetch_z(&mem_in_use, len);
142
143         if (MEMHEAD_IS_MMAP(memh)) {
144                 atomic_sub_and_fetch_z(&mmap_in_use, len);
145 #if defined(WIN32)
146                 /* our windows mmap implementation is not thread safe */
147                 mem_lock_thread();
148 #endif
149                 if (munmap(memh, len + sizeof(MemHead)))
150                         printf("Couldn't unmap memory\n");
151 #if defined(WIN32)
152                 mem_unlock_thread();
153 #endif
154         }
155         else {
156                 if (UNLIKELY(malloc_debug_memset && len)) {
157                         memset(memh + 1, 255, len);
158                 }
159                 if (UNLIKELY(MEMHEAD_IS_ALIGNED(memh))) {
160                         MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
161                         aligned_free(MEMHEAD_REAL_PTR(memh_aligned));
162                 }
163                 else {
164                         free(memh);
165                 }
166         }
167 }
168
169 void *MEM_lockfree_dupallocN(const void *vmemh)
170 {
171         void *newp = NULL;
172         if (vmemh) {
173                 MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
174                 const size_t prev_size = MEM_allocN_len(vmemh);
175                 if (UNLIKELY(MEMHEAD_IS_MMAP(memh))) {
176                         newp = MEM_lockfree_mapallocN(prev_size, "dupli_mapalloc");
177                 }
178                 else if (UNLIKELY(MEMHEAD_IS_ALIGNED(memh))) {
179                         MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
180                         newp = MEM_lockfree_mallocN_aligned(
181                                 prev_size,
182                                 (size_t)memh_aligned->alignment,
183                                 "dupli_malloc");
184                 }
185                 else {
186                         newp = MEM_lockfree_mallocN(prev_size, "dupli_malloc");
187                 }
188                 memcpy(newp, vmemh, prev_size);
189         }
190         return newp;
191 }
192
193 void *MEM_lockfree_reallocN_id(void *vmemh, size_t len, const char *str)
194 {
195         void *newp = NULL;
196
197         if (vmemh) {
198                 MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
199                 size_t old_len = MEM_allocN_len(vmemh);
200
201                 if (LIKELY(!MEMHEAD_IS_ALIGNED(memh))) {
202                         newp = MEM_lockfree_mallocN(len, "realloc");
203                 }
204                 else {
205                         MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
206                         newp = MEM_lockfree_mallocN_aligned(
207                                 old_len,
208                                 (size_t)memh_aligned->alignment,
209                                 "realloc");
210                 }
211
212                 if (newp) {
213                         if (len < old_len) {
214                                 /* shrink */
215                                 memcpy(newp, vmemh, len);
216                         }
217                         else {
218                                 /* grow (or remain same size) */
219                                 memcpy(newp, vmemh, old_len);
220                         }
221                 }
222
223                 MEM_lockfree_freeN(vmemh);
224         }
225         else {
226                 newp = MEM_lockfree_mallocN(len, str);
227         }
228
229         return newp;
230 }
231
232 void *MEM_lockfree_recallocN_id(void *vmemh, size_t len, const char *str)
233 {
234         void *newp = NULL;
235
236         if (vmemh) {
237                 MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
238                 size_t old_len = MEM_allocN_len(vmemh);
239
240                 if (LIKELY(!MEMHEAD_IS_ALIGNED(memh))) {
241                         newp = MEM_lockfree_mallocN(len, "recalloc");
242                 }
243                 else {
244                         MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
245                         newp = MEM_lockfree_mallocN_aligned(old_len,
246                                                             (size_t)memh_aligned->alignment,
247                                                             "recalloc");
248                 }
249
250                 if (newp) {
251                         if (len < old_len) {
252                                 /* shrink */
253                                 memcpy(newp, vmemh, len);
254                         }
255                         else {
256                                 memcpy(newp, vmemh, old_len);
257
258                                 if (len > old_len) {
259                                         /* grow */
260                                         /* zero new bytes */
261                                         memset(((char *)newp) + old_len, 0, len - old_len);
262                                 }
263                         }
264                 }
265
266                 MEM_lockfree_freeN(vmemh);
267         }
268         else {
269                 newp = MEM_lockfree_callocN(len, str);
270         }
271
272         return newp;
273 }
274
275 void *MEM_lockfree_callocN(size_t len, const char *str)
276 {
277         MemHead *memh;
278
279         len = SIZET_ALIGN_4(len);
280
281         memh = (MemHead *)calloc(1, len + sizeof(MemHead));
282
283         if (LIKELY(memh)) {
284                 memh->len = len;
285                 atomic_add_and_fetch_u(&totblock, 1);
286                 atomic_add_and_fetch_z(&mem_in_use, len);
287                 update_maximum(&peak_mem, mem_in_use);
288
289                 return PTR_FROM_MEMHEAD(memh);
290         }
291         print_error("Calloc returns null: len=" SIZET_FORMAT " in %s, total %u\n",
292                     SIZET_ARG(len), str, (unsigned int) mem_in_use);
293         return NULL;
294 }
295
296 void *MEM_lockfree_calloc_arrayN(size_t len, size_t size, const char *str)
297 {
298         size_t total_size;
299         if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
300                 print_error("Calloc array aborted due to integer overflow: "
301                             "len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total %u\n",
302                             SIZET_ARG(len), SIZET_ARG(size), str,
303                             (unsigned int) mem_in_use);
304                 abort();
305                 return NULL;
306         }
307
308         return MEM_lockfree_callocN(total_size, str);
309 }
310
311 void *MEM_lockfree_mallocN(size_t len, const char *str)
312 {
313         MemHead *memh;
314
315         len = SIZET_ALIGN_4(len);
316
317         memh = (MemHead *)malloc(len + sizeof(MemHead));
318
319         if (LIKELY(memh)) {
320                 if (UNLIKELY(malloc_debug_memset && len)) {
321                         memset(memh + 1, 255, len);
322                 }
323
324                 memh->len = len;
325                 atomic_add_and_fetch_u(&totblock, 1);
326                 atomic_add_and_fetch_z(&mem_in_use, len);
327                 update_maximum(&peak_mem, mem_in_use);
328
329                 return PTR_FROM_MEMHEAD(memh);
330         }
331         print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total %u\n",
332                     SIZET_ARG(len), str, (unsigned int) mem_in_use);
333         return NULL;
334 }
335
336 void *MEM_lockfree_malloc_arrayN(size_t len, size_t size, const char *str)
337 {
338         size_t total_size;
339         if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
340                 print_error("Malloc array aborted due to integer overflow: "
341                             "len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total %u\n",
342                             SIZET_ARG(len), SIZET_ARG(size), str,
343                             (unsigned int) mem_in_use);
344                 abort();
345                 return NULL;
346         }
347
348         return MEM_lockfree_mallocN(total_size, str);
349 }
350
351 void *MEM_lockfree_mallocN_aligned(size_t len, size_t alignment, const char *str)
352 {
353         MemHeadAligned *memh;
354
355         /* It's possible that MemHead's size is not properly aligned,
356          * do extra padding to deal with this.
357          *
358          * We only support small alignments which fits into short in
359          * order to save some bits in MemHead structure.
360          */
361         size_t extra_padding = MEMHEAD_ALIGN_PADDING(alignment);
362
363         /* Huge alignment values doesn't make sense and they
364          * wouldn't fit into 'short' used in the MemHead.
365          */
366         assert(alignment < 1024);
367
368         /* We only support alignment to a power of two. */
369         assert(IS_POW2(alignment));
370
371         len = SIZET_ALIGN_4(len);
372
373         memh = (MemHeadAligned *)aligned_malloc(
374                 len + extra_padding + sizeof(MemHeadAligned), alignment);
375
376         if (LIKELY(memh)) {
377                 /* We keep padding in the beginning of MemHead,
378                  * this way it's always possible to get MemHead
379                  * from the data pointer.
380                  */
381                 memh = (MemHeadAligned *)((char *)memh + extra_padding);
382
383                 if (UNLIKELY(malloc_debug_memset && len)) {
384                         memset(memh + 1, 255, len);
385                 }
386
387                 memh->len = len | (size_t) MEMHEAD_ALIGN_FLAG;
388                 memh->alignment = (short) alignment;
389                 atomic_add_and_fetch_u(&totblock, 1);
390                 atomic_add_and_fetch_z(&mem_in_use, len);
391                 update_maximum(&peak_mem, mem_in_use);
392
393                 return PTR_FROM_MEMHEAD(memh);
394         }
395         print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total %u\n",
396                     SIZET_ARG(len), str, (unsigned int) mem_in_use);
397         return NULL;
398 }
399
400 void *MEM_lockfree_mapallocN(size_t len, const char *str)
401 {
402         MemHead *memh;
403
404         /* on 64 bit, simply use calloc instead, as mmap does not support
405          * allocating > 4 GB on Windows. the only reason mapalloc exists
406          * is to get around address space limitations in 32 bit OSes. */
407         if (sizeof(void *) >= 8)
408                 return MEM_lockfree_callocN(len, str);
409
410         len = SIZET_ALIGN_4(len);
411
412 #if defined(WIN32)
413         /* our windows mmap implementation is not thread safe */
414         mem_lock_thread();
415 #endif
416         memh = mmap(NULL, len + sizeof(MemHead),
417                     PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
418 #if defined(WIN32)
419         mem_unlock_thread();
420 #endif
421
422         if (memh != (MemHead *)-1) {
423                 memh->len = len | (size_t) MEMHEAD_MMAP_FLAG;
424                 atomic_add_and_fetch_u(&totblock, 1);
425                 atomic_add_and_fetch_z(&mem_in_use, len);
426                 atomic_add_and_fetch_z(&mmap_in_use, len);
427
428                 update_maximum(&peak_mem, mem_in_use);
429                 update_maximum(&peak_mem, mmap_in_use);
430
431                 return PTR_FROM_MEMHEAD(memh);
432         }
433         print_error("Mapalloc returns null, fallback to regular malloc: "
434                     "len=" SIZET_FORMAT " in %s, total %u\n",
435                     SIZET_ARG(len), str, (unsigned int) mmap_in_use);
436         return MEM_lockfree_callocN(len, str);
437 }
438
439 void MEM_lockfree_printmemlist_pydict(void)
440 {
441 }
442
443 void MEM_lockfree_printmemlist(void)
444 {
445 }
446
447 /* unused */
448 void MEM_lockfree_callbackmemlist(void (*func)(void *))
449 {
450         (void) func;  /* Ignored. */
451 }
452
453 void MEM_lockfree_printmemlist_stats(void)
454 {
455         printf("\ntotal memory len: %.3f MB\n",
456                (double)mem_in_use / (double)(1024 * 1024));
457         printf("peak memory len: %.3f MB\n",
458                (double)peak_mem / (double)(1024 * 1024));
459         printf("\nFor more detailed per-block statistics run Blender with memory debugging command line argument.\n");
460
461 #ifdef HAVE_MALLOC_STATS
462         printf("System Statistics:\n");
463         malloc_stats();
464 #endif
465 }
466
467 void MEM_lockfree_set_error_callback(void (*func)(const char *))
468 {
469         error_callback = func;
470 }
471
472 bool MEM_lockfree_consistency_check(void)
473 {
474         return true;
475 }
476
477 void MEM_lockfree_set_lock_callback(void (*lock)(void), void (*unlock)(void))
478 {
479         thread_lock_callback = lock;
480         thread_unlock_callback = unlock;
481 }
482
483 void MEM_lockfree_set_memory_debug(void)
484 {
485         malloc_debug_memset = true;
486 }
487
488 size_t MEM_lockfree_get_memory_in_use(void)
489 {
490         return mem_in_use;
491 }
492
493 size_t MEM_lockfree_get_mapped_memory_in_use(void)
494 {
495         return mmap_in_use;
496 }
497
498 unsigned int MEM_lockfree_get_memory_blocks_in_use(void)
499 {
500         return totblock;
501 }
502
503 /* dummy */
504 void MEM_lockfree_reset_peak_memory(void)
505 {
506         peak_mem = mem_in_use;
507 }
508
509 size_t MEM_lockfree_get_peak_memory(void)
510 {
511         return peak_mem;
512 }
513
514 #ifndef NDEBUG
515 const char *MEM_lockfree_name_ptr(void *vmemh)
516 {
517         if (vmemh) {
518                 return "unknown block name ptr";
519         }
520         else {
521                 return "MEM_lockfree_name_ptr(NULL)";
522         }
523 }
524 #endif  /* NDEBUG */