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