Blender V4.3
mallocn_lockfree_impl.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2013-2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
11#include <stdarg.h>
12#include <stdio.h> /* printf */
13#include <stdlib.h>
14#include <string.h> /* memcpy */
15#include <sys/types.h>
16
17#include "MEM_guardedalloc.h"
18
19/* Quiet warnings when dealing with allocated data written into the blend file.
20 * This also rounds up and causes warnings which we don't consider bugs in practice. */
21#ifdef WITH_MEM_VALGRIND
22# include "valgrind/memcheck.h"
23#endif
24
25/* to ensure strict conversions */
26#include "../../source/blender/blenlib/BLI_strict_flags.h"
27
28#include "atomic_ops.h"
29#include "mallocn_intern.hh"
31
32using namespace mem_guarded::internal;
33
34typedef struct MemHead {
35 /* Length of allocated memory block. */
36 size_t len;
38static_assert(MEM_MIN_CPP_ALIGNMENT <= alignof(MemHead), "Bad alignment of MemHead");
39static_assert(MEM_MIN_CPP_ALIGNMENT <= sizeof(MemHead), "Bad size of MemHead");
40
41typedef struct MemHeadAligned {
42 short alignment;
43 size_t len;
45static_assert(MEM_MIN_CPP_ALIGNMENT <= alignof(MemHeadAligned), "Bad alignment of MemHeadAligned");
46static_assert(MEM_MIN_CPP_ALIGNMENT <= sizeof(MemHeadAligned), "Bad size of MemHeadAligned");
47
48static bool malloc_debug_memset = false;
49
50static void (*error_callback)(const char *) = nullptr;
51
56enum {
66
67 MEMHEAD_FLAG_MASK = (1 << 2) - 1
68};
69
70#define MEMHEAD_FROM_PTR(ptr) (((MemHead *)ptr) - 1)
71#define PTR_FROM_MEMHEAD(memhead) (memhead + 1)
72#define MEMHEAD_ALIGNED_FROM_PTR(ptr) (((MemHeadAligned *)ptr) - 1)
73#define MEMHEAD_IS_ALIGNED(memhead) ((memhead)->len & size_t(MEMHEAD_FLAG_ALIGN))
74#define MEMHEAD_IS_FROM_CPP_NEW(memhead) ((memhead)->len & size_t(MEMHEAD_FLAG_FROM_CPP_NEW))
75#define MEMHEAD_LEN(memhead) ((memhead)->len & ~size_t(MEMHEAD_FLAG_MASK))
76
77#ifdef __GNUC__
79#endif
80static void
81print_error(const char *message, va_list str_format_args)
82{
83 char buf[512];
84 vsnprintf(buf, sizeof(buf), message, str_format_args);
85 buf[sizeof(buf) - 1] = '\0';
86
87 if (error_callback) {
88 error_callback(buf);
89 }
90}
91
92#ifdef __GNUC__
94#endif
95static void
96print_error(const char *message, ...)
97{
98 va_list str_format_args;
99 va_start(str_format_args, message);
100 print_error(message, str_format_args);
101 va_end(str_format_args);
102}
103
104#ifdef __GNUC__
106#endif
107static void
108report_error_on_address(const void *vmemh, const char *message, ...)
109{
110 va_list str_format_args;
111
112 va_start(str_format_args, message);
113 print_error(message, str_format_args);
114 va_end(str_format_args);
115
116 if (vmemh == nullptr) {
118 return;
119 }
120
121 const MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
122 const size_t len = MEMHEAD_LEN(memh);
123
124 const void *address = memh;
125 size_t size = len + sizeof(*memh);
126 if (UNLIKELY(MEMHEAD_IS_ALIGNED(memh))) {
127 const MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
128 address = MEMHEAD_REAL_PTR(memh_aligned);
129 size = len + sizeof(*memh_aligned) + MEMHEAD_ALIGN_PADDING(memh_aligned->alignment);
130 }
132}
133
134size_t MEM_lockfree_allocN_len(const void *vmemh)
135{
136 if (LIKELY(vmemh)) {
137 return MEMHEAD_LEN(MEMHEAD_FROM_PTR(vmemh));
138 }
139
140 return 0;
141}
142
143void MEM_lockfree_freeN(void *vmemh, AllocationType allocation_type)
144{
147 }
148
149 if (UNLIKELY(vmemh == nullptr)) {
150 report_error_on_address(vmemh, "Attempt to free nullptr pointer\n");
151 return;
152 }
153
154 MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
155 size_t len = MEMHEAD_LEN(memh);
156
157 if (allocation_type != AllocationType::NEW_DELETE && MEMHEAD_IS_FROM_CPP_NEW(memh)) {
159 vmemh,
160 "Attempt to use C-style MEM_freeN on a pointer created with CPP-style MEM_new or new\n");
161 }
162
164
166 memset(memh + 1, 255, len);
167 }
168 if (UNLIKELY(MEMHEAD_IS_ALIGNED(memh))) {
169 MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
170 aligned_free(MEMHEAD_REAL_PTR(memh_aligned));
171 }
172 else {
173 free(memh);
174 }
175}
176
177void *MEM_lockfree_dupallocN(const void *vmemh)
178{
179 void *newp = nullptr;
180 if (vmemh) {
181 const MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
182 const size_t prev_size = MEM_lockfree_allocN_len(vmemh);
183
184 if (MEMHEAD_IS_FROM_CPP_NEW(memh)) {
186 "Attempt to use C-style MEM_dupallocN on a pointer created with "
187 "CPP-style MEM_new or new\n");
188 }
189
190 if (UNLIKELY(MEMHEAD_IS_ALIGNED(memh))) {
191 const MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
193 prev_size, size_t(memh_aligned->alignment), "dupli_malloc", AllocationType::ALLOC_FREE);
194 }
195 else {
196 newp = MEM_lockfree_mallocN(prev_size, "dupli_malloc");
197 }
198 memcpy(newp, vmemh, prev_size);
199 }
200 return newp;
201}
202
203void *MEM_lockfree_reallocN_id(void *vmemh, size_t len, const char *str)
204{
205 void *newp = nullptr;
206
207 if (vmemh) {
208 const MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
209 const size_t old_len = MEM_lockfree_allocN_len(vmemh);
210
211 if (MEMHEAD_IS_FROM_CPP_NEW(memh)) {
213 "Attempt to use C-style MEM_reallocN on a pointer created with "
214 "CPP-style MEM_new or new\n");
215 }
216
217 if (LIKELY(!MEMHEAD_IS_ALIGNED(memh))) {
218 newp = MEM_lockfree_mallocN(len, "realloc");
219 }
220 else {
221 const MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
223 len, size_t(memh_aligned->alignment), "realloc", AllocationType::ALLOC_FREE);
224 }
225
226 if (newp) {
227 if (len < old_len) {
228 /* shrink */
229 memcpy(newp, vmemh, len);
230 }
231 else {
232 /* grow (or remain same size) */
233 memcpy(newp, vmemh, old_len);
234 }
235 }
236
237 MEM_lockfree_freeN(vmemh, AllocationType::ALLOC_FREE);
238 }
239 else {
241 }
242
243 return newp;
244}
245
246void *MEM_lockfree_recallocN_id(void *vmemh, size_t len, const char *str)
247{
248 void *newp = nullptr;
249
250 if (vmemh) {
251 const MemHead *memh = MEMHEAD_FROM_PTR(vmemh);
252 const size_t old_len = MEM_lockfree_allocN_len(vmemh);
253
254 if (MEMHEAD_IS_FROM_CPP_NEW(memh)) {
256 "Attempt to use C-style MEM_recallocN on a pointer created with "
257 "CPP-style MEM_new or new\n");
258 }
259
260 if (LIKELY(!MEMHEAD_IS_ALIGNED(memh))) {
261 newp = MEM_lockfree_mallocN(len, "recalloc");
262 }
263 else {
264 const MemHeadAligned *memh_aligned = MEMHEAD_ALIGNED_FROM_PTR(vmemh);
266 len, size_t(memh_aligned->alignment), "recalloc", AllocationType::ALLOC_FREE);
267 }
268
269 if (newp) {
270 if (len < old_len) {
271 /* shrink */
272 memcpy(newp, vmemh, len);
273 }
274 else {
275 memcpy(newp, vmemh, old_len);
276
277 if (len > old_len) {
278 /* grow */
279 /* zero new bytes */
280 memset(((char *)newp) + old_len, 0, len - old_len);
281 }
282 }
283 }
284
285 MEM_lockfree_freeN(vmemh, AllocationType::ALLOC_FREE);
286 }
287 else {
289 }
290
291 return newp;
292}
293
294void *MEM_lockfree_callocN(size_t len, const char *str)
295{
296 MemHead *memh;
297
299
300 memh = (MemHead *)calloc(1, len + sizeof(MemHead));
301
302 if (LIKELY(memh)) {
303 memh->len = len;
305
306 return PTR_FROM_MEMHEAD(memh);
307 }
308 print_error("Calloc returns null: len=" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
309 SIZET_ARG(len),
310 str,
312 return nullptr;
313}
314
315void *MEM_lockfree_calloc_arrayN(size_t len, size_t size, const char *str)
316{
317 size_t total_size;
318 if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
320 "Calloc array aborted due to integer overflow: "
321 "len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
322 SIZET_ARG(len),
323 SIZET_ARG(size),
324 str,
326 abort();
327 return nullptr;
328 }
329
330 return MEM_lockfree_callocN(total_size, str);
331}
332
333void *MEM_lockfree_mallocN(size_t len, const char *str)
334{
335 MemHead *memh;
336
337#ifdef WITH_MEM_VALGRIND
338 const size_t len_unaligned = len;
339#endif
341
342 memh = (MemHead *)malloc(len + sizeof(MemHead));
343
344 if (LIKELY(memh)) {
345
346 if (LIKELY(len)) {
348 memset(memh + 1, 255, len);
349 }
350#ifdef WITH_MEM_VALGRIND
352 VALGRIND_MAKE_MEM_UNDEFINED(memh + 1, len_unaligned);
353 }
354 else {
355 VALGRIND_MAKE_MEM_DEFINED((const char *)(memh + 1) + len_unaligned, len - len_unaligned);
356 }
357#endif /* WITH_MEM_VALGRIND */
358 }
359
360 memh->len = len;
362
363 return PTR_FROM_MEMHEAD(memh);
364 }
365 print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
366 SIZET_ARG(len),
367 str,
369 return nullptr;
370}
371
372void *MEM_lockfree_malloc_arrayN(size_t len, size_t size, const char *str)
373{
374 size_t total_size;
375 if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
377 "Malloc array aborted due to integer overflow: "
378 "len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
379 SIZET_ARG(len),
380 SIZET_ARG(size),
381 str,
383 abort();
384 return nullptr;
385 }
386
387 return MEM_lockfree_mallocN(total_size, str);
388}
389
391 size_t alignment,
392 const char *str,
393 const AllocationType allocation_type)
394{
395 /* Huge alignment values doesn't make sense and they wouldn't fit into 'short' used in the
396 * MemHead. */
397 assert(alignment < 1024);
398
399 /* We only support alignments that are a power of two. */
400 assert(IS_POW2(alignment));
401
402 /* Some OS specific aligned allocators require a certain minimal alignment. */
403 if (alignment < ALIGNED_MALLOC_MINIMUM_ALIGNMENT) {
405 }
406
407 /* It's possible that MemHead's size is not properly aligned,
408 * do extra padding to deal with this.
409 *
410 * We only support small alignments which fits into short in
411 * order to save some bits in MemHead structure.
412 */
413 size_t extra_padding = MEMHEAD_ALIGN_PADDING(alignment);
414
415#ifdef WITH_MEM_VALGRIND
416 const size_t len_unaligned = len;
417#endif
419
421 len + extra_padding + sizeof(MemHeadAligned), alignment);
422
423 if (LIKELY(memh)) {
424 /* We keep padding in the beginning of MemHead,
425 * this way it's always possible to get MemHead
426 * from the data pointer.
427 */
428 memh = (MemHeadAligned *)((char *)memh + extra_padding);
429
430 if (LIKELY(len)) {
432 memset(memh + 1, 255, len);
433 }
434#ifdef WITH_MEM_VALGRIND
436 VALGRIND_MAKE_MEM_UNDEFINED(memh + 1, len_unaligned);
437 }
438 else {
439 VALGRIND_MAKE_MEM_DEFINED((const char *)(memh + 1) + len_unaligned, len - len_unaligned);
440 }
441#endif /* WITH_MEM_VALGRIND */
442 }
443
444 memh->len = len | size_t(MEMHEAD_FLAG_ALIGN) |
445 size_t(allocation_type == AllocationType::NEW_DELETE ? MEMHEAD_FLAG_FROM_CPP_NEW :
446 0);
447 memh->alignment = short(alignment);
449
450 return PTR_FROM_MEMHEAD(memh);
451 }
452 print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
453 SIZET_ARG(len),
454 str,
456 return nullptr;
457}
458
460 const size_t size,
461 const size_t alignment,
462 const char *str)
463{
464 size_t bytes_num;
465 if (UNLIKELY(!MEM_size_safe_multiply(len, size, &bytes_num))) {
467 "Calloc array aborted due to integer overflow: "
468 "len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total " SIZET_FORMAT "\n",
469 SIZET_ARG(len),
470 SIZET_ARG(size),
471 str,
473 abort();
474 return nullptr;
475 }
476 if (alignment <= MEM_MIN_CPP_ALIGNMENT) {
477 return MEM_callocN(bytes_num, str);
478 }
479 /* There is no lower level #calloc with an alignment parameter, so we have to fallback to using
480 * #memset unfortunately. */
481 void *ptr = MEM_mallocN_aligned(bytes_num, alignment, str);
482 if (!ptr) {
483 return nullptr;
484 }
485 memset(ptr, 0, bytes_num);
486 return ptr;
487}
488
490
492
494
495/* unused */
496void MEM_lockfree_callbackmemlist(void (*func)(void *))
497{
498 (void)func; /* Ignored. */
499}
500
502{
503 printf("\ntotal memory len: %.3f MB\n", double(memory_usage_current()) / double(1024 * 1024));
504 printf("peak memory len: %.3f MB\n", double(memory_usage_peak()) / double(1024 * 1024));
505 printf(
506 "\nFor more detailed per-block statistics run Blender with memory debugging command line "
507 "argument.\n");
508
509#ifdef HAVE_MALLOC_STATS
510 printf("System Statistics:\n");
511 malloc_stats();
512#endif
513}
514
515void MEM_lockfree_set_error_callback(void (*func)(const char *))
516{
517 error_callback = func;
518}
519
521{
522 return true;
523}
524
529
534
539
540/* dummy */
545
547{
548 return memory_usage_peak();
549}
550
551#ifndef NDEBUG
552const char *MEM_lockfree_name_ptr(void *vmemh)
553{
554 if (vmemh) {
555 return "unknown block name ptr";
556 }
557
558 return "MEM_lockfree_name_ptr(nullptr)";
559}
560
561void MEM_lockfree_name_ptr_set(void *UNUSED(vmemh), const char *UNUSED(str)) {}
562#endif /* !NDEBUG */
void BLI_kdtree_nd_ free(KDTree *tree)
unsigned int uint
#define UNUSED(x)
#define UNLIKELY(x)
#define LIKELY(x)
Read Guarded memory(de)allocation.
Provides wrapper around system-specific atomic primitives, and some extensions (faked-atomic operatio...
static __attribute__((constructor)) void cpu_check()
Definition cpu_check.cc:94
#define printf
int len
#define str(s)
bool leak_detector_has_run
char free_after_leak_detection_message[]
format
void aligned_free(void *ptr)
Definition mallocn.cc:96
void * MEM_mallocN_aligned(size_t len, size_t alignment, const char *str)
Definition mallocn.cc:110
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
void * aligned_malloc(size_t size, size_t alignment)
Definition mallocn.cc:74
MEM_INLINE bool MEM_size_safe_multiply(size_t a, size_t b, size_t *result)
MEM_INLINE void MEM_trigger_error_on_memory_block(const void *, const size_t)
size_t memory_usage_current(void)
#define MEMHEAD_REAL_PTR(memh)
#define IS_POW2(a)
#define MEMHEAD_ALIGN_PADDING(alignment)
void memory_usage_block_alloc(size_t size)
#define SIZET_ARG(a)
#define SIZET_ALIGN_4(len)
size_t memory_usage_block_num(void)
#define SIZET_FORMAT
void memory_usage_block_free(size_t size)
size_t memory_usage_peak(void)
#define ALIGNED_MALLOC_MINIMUM_ALIGNMENT
void memory_usage_peak_reset(void)
uint MEM_lockfree_get_memory_blocks_in_use()
void * MEM_lockfree_mallocN(size_t len, const char *str)
void MEM_lockfree_printmemlist_pydict()
void * MEM_lockfree_dupallocN(const void *vmemh)
void * MEM_lockfree_malloc_arrayN(size_t len, size_t size, const char *str)
void MEM_lockfree_reset_peak_memory()
void MEM_lockfree_set_error_callback(void(*func)(const char *))
void MEM_lockfree_name_ptr_set(void *UNUSED(vmemh), const char *UNUSED(str))
void * MEM_lockfree_mallocN_aligned(size_t len, size_t alignment, const char *str, const AllocationType allocation_type)
#define PTR_FROM_MEMHEAD(memhead)
void * MEM_lockfree_calloc_arrayN_aligned(const size_t len, const size_t size, const size_t alignment, const char *str)
static bool malloc_debug_memset
size_t MEM_lockfree_get_peak_memory()
@ MEMHEAD_FLAG_MASK
@ MEMHEAD_FLAG_FROM_CPP_NEW
@ MEMHEAD_FLAG_ALIGN
static void(* error_callback)(const char *)
void * MEM_lockfree_reallocN_id(void *vmemh, size_t len, const char *str)
static void print_error(const char *message, va_list str_format_args)
void * MEM_lockfree_callocN(size_t len, const char *str)
void * MEM_lockfree_calloc_arrayN(size_t len, size_t size, const char *str)
struct MemHead MemHead
static void report_error_on_address(const void *vmemh, const char *message,...)
void MEM_lockfree_set_memory_debug()
bool MEM_lockfree_consistency_check()
void MEM_lockfree_printmemlist()
#define MEMHEAD_LEN(memhead)
#define MEMHEAD_IS_ALIGNED(memhead)
void * MEM_lockfree_recallocN_id(void *vmemh, size_t len, const char *str)
const char * MEM_lockfree_name_ptr(void *vmemh)
struct MemHeadAligned MemHeadAligned
void MEM_lockfree_callbackmemlist(void(*func)(void *))
#define MEMHEAD_FROM_PTR(ptr)
size_t MEM_lockfree_get_memory_in_use()
void MEM_lockfree_freeN(void *vmemh, AllocationType allocation_type)
#define MEMHEAD_IS_FROM_CPP_NEW(memhead)
#define MEMHEAD_ALIGNED_FROM_PTR(ptr)
void mem_lockfree_clearmemlist()
size_t MEM_lockfree_allocN_len(const void *vmemh)
void MEM_lockfree_printmemlist_stats()
PointerRNA * ptr
Definition wm_files.cc:4126