Lock-free memory allocator
[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  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): Brecht Van Lommel
24  *                 Campbell Barton
25  *                 Sergey Sharybin
26  *
27  * ***** END GPL LICENSE BLOCK *****
28  */
29
30 /** \file guardedalloc/intern/mallocn.c
31  *  \ingroup MEM
32  *
33  * Memory allocation which keeps track on allocated memory counters
34  */
35
36 #include <stdlib.h>
37 #include <string.h> /* memcpy */
38 #include <stdarg.h>
39 #include <sys/types.h>
40
41 #include "MEM_guardedalloc.h"
42
43 /* to ensure strict conversions */
44 #include "../../source/blender/blenlib/BLI_strict_flags.h"
45
46 #include "atomic_ops.h"
47 #include "mallocn_intern.h"
48
49 typedef struct MemHead {
50         /* Length of allocated memory block. */
51         size_t len;
52 } MemHead;
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 #define MEMHEAD_FROM_PTR(ptr) (((MemHead*) vmemh) - 1)
63 #define PTR_FROM_MEMHEAD(memhead) (memhead + 1)
64
65 #ifdef __GNUC__
66 __attribute__ ((format(printf, 1, 2)))
67 #endif
68 static void print_error(const char *str, ...)
69 {
70         char buf[512];
71         va_list ap;
72
73         va_start(ap, str);
74         vsnprintf(buf, sizeof(buf), str, ap);
75         va_end(ap);
76         buf[sizeof(buf) - 1] = '\0';
77
78         if (error_callback) {
79                 error_callback(buf);
80         }
81 }
82
83 #if defined(WIN32)
84 static void mem_lock_thread(void)
85 {
86         if (thread_lock_callback)
87                 thread_lock_callback();
88 }
89
90 static void mem_unlock_thread(void)
91 {
92         if (thread_unlock_callback)
93                 thread_unlock_callback();
94 }
95 #endif
96
97 size_t MEM_lockfree_allocN_len(const void *vmemh)
98 {
99         if (vmemh) {
100                 return MEMHEAD_FROM_PTR(vmemh)->len & ~((size_t) 1);
101         }
102         else {
103                 return 0;
104         }
105 }
106
107 void MEM_lockfree_freeN(void *vmemh)
108 {
109         MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
110         size_t len = MEM_lockfree_allocN_len(vmemh);
111
112         atomic_sub_u(&totblock, 1);
113         atomic_sub_z(&mem_in_use, len);
114
115         if (memh->len & (size_t) 1) {
116                 atomic_sub_z(&mmap_in_use, len);
117 #if defined(WIN32)
118                 /* our windows mmap implementation is not thread safe */
119                 mem_lock_thread();
120 #endif
121                 if (munmap(memh, memh->len + sizeof(MemHead)))
122                         printf("Couldn't unmap memory\n");
123 #if defined(WIN32)
124                 mem_unlock_thread();
125 #endif
126         }
127         else {
128                 if (malloc_debug_memset && len) {
129                         memset(memh + 1, 255, len);
130                 }
131                 free(memh);
132         }
133 }
134
135 void *MEM_lockfree_dupallocN(const void *vmemh)
136 {
137         void *newp = NULL;
138         if (vmemh) {
139                 const size_t prev_size = MEM_allocN_len(vmemh);
140                 newp = MEM_lockfree_mallocN(prev_size, "dupli_malloc");
141                 memcpy(newp, vmemh, prev_size);
142         }
143         return newp;
144 }
145
146 void *MEM_lockfree_reallocN_id(void *vmemh, size_t len, const char *str)
147 {
148         void *newp = NULL;
149
150         if (vmemh) {
151                 size_t old_len = MEM_allocN_len(vmemh);
152
153                 newp = MEM_lockfree_mallocN(len, "realloc");
154                 if (newp) {
155                         if (len < old_len) {
156                                 /* shrink */
157                                 memcpy(newp, vmemh, len);
158                         }
159                         else {
160                                 /* grow (or remain same size) */
161                                 memcpy(newp, vmemh, old_len);
162                         }
163                 }
164
165                 MEM_lockfree_freeN(vmemh);
166         }
167         else {
168                 newp = MEM_lockfree_mallocN(len, str);
169         }
170
171         return newp;
172 }
173
174 void *MEM_lockfree_recallocN_id(void *vmemh, size_t len, const char *str)
175 {
176         void *newp = NULL;
177
178         if (vmemh) {
179                 size_t old_len = MEM_allocN_len(vmemh);
180
181                 newp = MEM_lockfree_mallocN(len, "recalloc");
182                 if (newp) {
183                         if (len < old_len) {
184                                 /* shrink */
185                                 memcpy(newp, vmemh, len);
186                         }
187                         else {
188                                 memcpy(newp, vmemh, old_len);
189
190                                 if (len > old_len) {
191                                         /* grow */
192                                         /* zero new bytes */
193                                         memset(((char *)newp) + old_len, 0, len - old_len);
194                                 }
195                         }
196                 }
197
198                 MEM_lockfree_freeN(vmemh);
199         }
200         else {
201                 newp = MEM_lockfree_callocN(len, str);
202         }
203
204         return newp;
205 }
206
207 void *MEM_lockfree_callocN(size_t len, const char *str)
208 {
209         MemHead *memh;
210
211         len = SIZET_ALIGN_4(len);
212
213         memh = (MemHead *)calloc(1, len + sizeof(MemHead));
214
215         if (memh) {
216                 memh->len = len;
217                 atomic_add_u(&totblock, 1);
218                 atomic_add_z(&mem_in_use, len);
219
220                 /* TODO(sergey): Not strictly speaking thread-safe. */
221                 peak_mem = mem_in_use > peak_mem ? mem_in_use : peak_mem;
222
223                 return PTR_FROM_MEMHEAD(memh);
224         }
225         print_error("Calloc returns null: len=" SIZET_FORMAT " in %s, total %u\n",
226                     SIZET_ARG(len), str, (unsigned int) mem_in_use);
227         return NULL;
228 }
229
230 void *MEM_lockfree_mallocN(size_t len, const char *str)
231 {
232         MemHead *memh;
233
234         len = SIZET_ALIGN_4(len);
235
236         memh = (MemHead *)malloc(len + sizeof(MemHead));
237
238         if (memh) {
239                 if (malloc_debug_memset && len) {
240                         memset(memh + 1, 255, len);
241                 }
242
243                 memh->len = len;
244                 atomic_add_u(&totblock, 1);
245                 atomic_add_z(&mem_in_use, len);
246
247                 /* TODO(sergey): Not strictly speaking thread-safe. */
248                 peak_mem = mem_in_use > peak_mem ? mem_in_use : peak_mem;
249
250                 return PTR_FROM_MEMHEAD(memh);
251         }
252         print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total %u\n",
253                     SIZET_ARG(len), str, (unsigned int) mem_in_use);
254         return NULL;
255 }
256
257 void *MEM_lockfree_mapallocN(size_t len, const char *str)
258 {
259         MemHead *memh;
260
261         len = SIZET_ALIGN_4(len);
262
263 #if defined(WIN32)
264         /* our windows mmap implementation is not thread safe */
265         mem_lock_thread();
266 #endif
267         memh = mmap(NULL, len + sizeof(MemHead),
268                     PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
269 #if defined(WIN32)
270         mem_unlock_thread();
271 #endif
272
273         if (memh != (MemHead *)-1) {
274                 memh->len = len | (size_t) 1;
275                 atomic_add_u(&totblock, 1);
276                 atomic_add_z(&mem_in_use, len);
277                 atomic_add_z(&mmap_in_use, len);
278
279                 /* TODO(sergey): Not strictly speaking thread-safe. */
280                 peak_mem = mem_in_use > peak_mem ? mem_in_use : peak_mem;
281                 peak_mem = mmap_in_use > peak_mem ? mmap_in_use : peak_mem;
282
283                 return PTR_FROM_MEMHEAD(memh);
284         }
285         print_error("Mapalloc returns null, fallback to regular malloc: "
286                     "len=" SIZET_FORMAT " in %s, total %u\n",
287                     SIZET_ARG(len), str, (unsigned int) mmap_in_use);
288         return MEM_lockfree_callocN(len, str);
289 }
290
291 void MEM_lockfree_printmemlist_pydict(void)
292 {
293 }
294
295 void MEM_lockfree_printmemlist(void)
296 {
297 }
298
299 /* unused */
300 void MEM_lockfree_callbackmemlist(void (*func)(void *))
301 {
302         (void) func;  /* Ignored. */
303 }
304
305 void MEM_lockfree_printmemlist_stats(void)
306 {
307         printf("\ntotal memory len: %.3f MB\n",
308                (double)mem_in_use / (double)(1024 * 1024));
309         printf("peak memory len: %.3f MB\n",
310                (double)peak_mem / (double)(1024 * 1024));
311         printf("\nFor more detailed per-block statistics run Blender with memory debugging command line argument.\n");
312
313 #ifdef HAVE_MALLOC_STATS
314         printf("System Statistics:\n");
315         malloc_stats();
316 #endif
317 }
318
319 void MEM_lockfree_set_error_callback(void (*func)(const char *))
320 {
321         error_callback = func;
322 }
323
324 bool MEM_lockfree_check_memory_integrity(void)
325 {
326         return true;
327 }
328
329 void MEM_lockfree_set_lock_callback(void (*lock)(void), void (*unlock)(void))
330 {
331         thread_lock_callback = lock;
332         thread_unlock_callback = unlock;
333 }
334
335 void MEM_lockfree_set_memory_debug(void)
336 {
337         malloc_debug_memset = true;
338 }
339
340 uintptr_t MEM_lockfree_get_memory_in_use(void)
341 {
342         return mem_in_use;
343 }
344
345 uintptr_t MEM_lockfree_get_mapped_memory_in_use(void)
346 {
347         return mmap_in_use;
348 }
349
350 unsigned int MEM_lockfree_get_memory_blocks_in_use(void)
351 {
352         return totblock;
353 }
354
355 /* dummy */
356 void MEM_lockfree_reset_peak_memory(void)
357 {
358         peak_mem = 0;
359 }
360
361 uintptr_t MEM_lockfree_get_peak_memory(void)
362 {
363         return peak_mem;
364 }
365
366 #ifndef NDEBUG
367 const char *MEM_lockfree_name_ptr(void *vmemh)
368 {
369         if (vmemh) {
370                 return "unknown block name ptr";
371         }
372         else {
373                 return "MEM_lockfree_name_ptr(NULL)";
374         }
375 }
376 #endif  /* NDEBUG */