ClangFormat: apply to source, most of intern
[blender.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 <stdlib.h>
24 #include <string.h> /* memcpy */
25 #include <stdarg.h>
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   MemHeadAligned *memh;
350
351   /* It's possible that MemHead's size is not properly aligned,
352    * do extra padding to deal with this.
353    *
354    * We only support small alignments which fits into short in
355    * order to save some bits in MemHead structure.
356    */
357   size_t extra_padding = MEMHEAD_ALIGN_PADDING(alignment);
358
359   /* Huge alignment values doesn't make sense and they
360    * wouldn't fit into 'short' used in the MemHead.
361    */
362   assert(alignment < 1024);
363
364   /* We only support alignment to a power of two. */
365   assert(IS_POW2(alignment));
366
367   len = SIZET_ALIGN_4(len);
368
369   memh = (MemHeadAligned *)aligned_malloc(len + extra_padding + sizeof(MemHeadAligned), alignment);
370
371   if (LIKELY(memh)) {
372     /* We keep padding in the beginning of MemHead,
373      * this way it's always possible to get MemHead
374      * from the data pointer.
375      */
376     memh = (MemHeadAligned *)((char *)memh + extra_padding);
377
378     if (UNLIKELY(malloc_debug_memset && len)) {
379       memset(memh + 1, 255, len);
380     }
381
382     memh->len = len | (size_t)MEMHEAD_ALIGN_FLAG;
383     memh->alignment = (short)alignment;
384     atomic_add_and_fetch_u(&totblock, 1);
385     atomic_add_and_fetch_z(&mem_in_use, len);
386     update_maximum(&peak_mem, mem_in_use);
387
388     return PTR_FROM_MEMHEAD(memh);
389   }
390   print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total %u\n",
391               SIZET_ARG(len),
392               str,
393               (unsigned int)mem_in_use);
394   return NULL;
395 }
396
397 void *MEM_lockfree_mapallocN(size_t len, const char *str)
398 {
399   MemHead *memh;
400
401   /* on 64 bit, simply use calloc instead, as mmap does not support
402    * allocating > 4 GB on Windows. the only reason mapalloc exists
403    * is to get around address space limitations in 32 bit OSes. */
404   if (sizeof(void *) >= 8)
405     return MEM_lockfree_callocN(len, str);
406
407   len = SIZET_ALIGN_4(len);
408
409 #if defined(WIN32)
410   /* our windows mmap implementation is not thread safe */
411   mem_lock_thread();
412 #endif
413   memh = mmap(NULL, len + sizeof(MemHead), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
414 #if defined(WIN32)
415   mem_unlock_thread();
416 #endif
417
418   if (memh != (MemHead *)-1) {
419     memh->len = len | (size_t)MEMHEAD_MMAP_FLAG;
420     atomic_add_and_fetch_u(&totblock, 1);
421     atomic_add_and_fetch_z(&mem_in_use, len);
422     atomic_add_and_fetch_z(&mmap_in_use, len);
423
424     update_maximum(&peak_mem, mem_in_use);
425     update_maximum(&peak_mem, mmap_in_use);
426
427     return PTR_FROM_MEMHEAD(memh);
428   }
429   print_error(
430       "Mapalloc returns null, fallback to regular malloc: "
431       "len=" SIZET_FORMAT " in %s, total %u\n",
432       SIZET_ARG(len),
433       str,
434       (unsigned int)mmap_in_use);
435   return MEM_lockfree_callocN(len, str);
436 }
437
438 void MEM_lockfree_printmemlist_pydict(void)
439 {
440 }
441
442 void MEM_lockfree_printmemlist(void)
443 {
444 }
445
446 /* unused */
447 void MEM_lockfree_callbackmemlist(void (*func)(void *))
448 {
449   (void)func; /* Ignored. */
450 }
451
452 void MEM_lockfree_printmemlist_stats(void)
453 {
454   printf("\ntotal memory len: %.3f MB\n", (double)mem_in_use / (double)(1024 * 1024));
455   printf("peak memory len: %.3f MB\n", (double)peak_mem / (double)(1024 * 1024));
456   printf(
457       "\nFor more detailed per-block statistics run Blender with memory debugging command line "
458       "argument.\n");
459
460 #ifdef HAVE_MALLOC_STATS
461   printf("System Statistics:\n");
462   malloc_stats();
463 #endif
464 }
465
466 void MEM_lockfree_set_error_callback(void (*func)(const char *))
467 {
468   error_callback = func;
469 }
470
471 bool MEM_lockfree_consistency_check(void)
472 {
473   return true;
474 }
475
476 void MEM_lockfree_set_lock_callback(void (*lock)(void), void (*unlock)(void))
477 {
478   thread_lock_callback = lock;
479   thread_unlock_callback = unlock;
480 }
481
482 void MEM_lockfree_set_memory_debug(void)
483 {
484   malloc_debug_memset = true;
485 }
486
487 size_t MEM_lockfree_get_memory_in_use(void)
488 {
489   return mem_in_use;
490 }
491
492 size_t MEM_lockfree_get_mapped_memory_in_use(void)
493 {
494   return mmap_in_use;
495 }
496
497 unsigned int MEM_lockfree_get_memory_blocks_in_use(void)
498 {
499   return totblock;
500 }
501
502 /* dummy */
503 void MEM_lockfree_reset_peak_memory(void)
504 {
505   peak_mem = mem_in_use;
506 }
507
508 size_t MEM_lockfree_get_peak_memory(void)
509 {
510   return peak_mem;
511 }
512
513 #ifndef NDEBUG
514 const char *MEM_lockfree_name_ptr(void *vmemh)
515 {
516   if (vmemh) {
517     return "unknown block name ptr";
518   }
519   else {
520     return "MEM_lockfree_name_ptr(NULL)";
521   }
522 }
523 #endif /* NDEBUG */