diff --git a/CMakeLists.txt b/CMakeLists.txt index e88a5e0eea7..6eb8ec097b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -287,9 +287,6 @@ mark_as_advanced(WITH_MEM_VALGRIND) option(WITH_CXX_GUARDEDALLOC "Enable GuardedAlloc for C++ memory allocation tracking (only enable for development)" OFF) mark_as_advanced(WITH_CXX_GUARDEDALLOC) -option(WITH_GUARDEDALLOC "Enable GuardedAlloc (DISABLE AT OWN RISK!)" ON) -mark_as_advanced(WITH_GUARDEDALLOC) - option(WITH_ASSERT_ABORT "Call abort() when raising an assertion through BLI_assert()" OFF) mark_as_advanced(WITH_ASSERT_ABORT) diff --git a/intern/guardedalloc/CMakeLists.txt b/intern/guardedalloc/CMakeLists.txt index 1e140c5b674..b7a59da7813 100644 --- a/intern/guardedalloc/CMakeLists.txt +++ b/intern/guardedalloc/CMakeLists.txt @@ -34,11 +34,11 @@ set(INC_SYS set(SRC ./intern/mallocn.c + ./intern/mallocn_guarded_impl.c + ./intern/mallocn_lockfree_impl.c MEM_guardedalloc.h - - # include here since its a header-only - ../atomic/atomic_ops.h + ./intern/mallocn_intern.h ) if(WIN32 AND NOT UNIX) @@ -49,12 +49,6 @@ if(WIN32 AND NOT UNIX) ) endif() -if (WITH_GUARDEDALLOC) - add_definitions(-DWITH_GUARDEDALLOC) -else() - message(WARNING "Disabling GuardedAlloc is experemental, use at own risk!") -endif() - blender_add_lib(bf_intern_guardedalloc "${SRC}" "${INC}" "${INC_SYS}") # Override C++ alloc, optional. diff --git a/intern/guardedalloc/MEM_guardedalloc.h b/intern/guardedalloc/MEM_guardedalloc.h index 0b32596b36c..a2a446eb0a8 100644 --- a/intern/guardedalloc/MEM_guardedalloc.h +++ b/intern/guardedalloc/MEM_guardedalloc.h @@ -62,11 +62,9 @@ #include /* needed for FILE* */ -/* needed for uintptr_t, exception, dont use BLI anywhere else in MEM_* */ +/* needed for uintptr_t and attributes, exception, dont use BLI anywhere else in MEM_* */ #include "../../source/blender/blenlib/BLI_sys_types.h" - -/* some GNU attributes are only available from GCC 4.3 */ -#define MEM_GNU_ATTRIBUTES (defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 403)) +#include "../../source/blender/blenlib/BLI_compiler_attrs.h" #ifdef __cplusplus extern "C" { @@ -75,57 +73,36 @@ extern "C" { /** Returns the length of the allocated memory segment pointed at * by vmemh. If the pointer was not previously allocated by this * module, the result is undefined.*/ - size_t MEM_allocN_len(const void *vmemh) -#if MEM_GNU_ATTRIBUTES - __attribute__((warn_unused_result)) -#endif - ; + extern size_t (*MEM_allocN_len)(const void *vmemh) ATTR_WARN_UNUSED_RESULT; /** * Release memory previously allocatred by this module. */ - void MEM_freeN(void *vmemh); + extern void (*MEM_freeN)(void *vmemh); #if 0 /* UNUSED */ /** * Return zero if memory is not in allocated list */ - short MEM_testN(void *vmemh); + extern short (*MEM_testN)(void *vmemh); #endif /** * Duplicates a block of memory, and returns a pointer to the * newly allocated block. */ - void *MEM_dupallocN(const void *vmemh) -#if MEM_GNU_ATTRIBUTES - __attribute__((malloc)) - __attribute__((warn_unused_result)) -#endif - ; + extern void *(*MEM_dupallocN)(const void *vmemh) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT; /** * Reallocates a block of memory, and returns pointer to the newly * allocated block, the old one is freed. this is not as optimized * as a system realloc but just makes a new allocation and copies * over from existing memory. */ - void *MEM_reallocN_id(void *vmemh, size_t len, const char *str) -#if MEM_GNU_ATTRIBUTES - __attribute__((malloc)) - __attribute__((warn_unused_result)) - __attribute__((alloc_size(2))) -#endif - ; + extern void *(*MEM_reallocN_id)(void *vmemh, size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(2); /** * A variant of realloc which zeros new bytes */ - void *MEM_recallocN_id(void *vmemh, size_t len, const char *str) -#if MEM_GNU_ATTRIBUTES - __attribute__((malloc)) - __attribute__((warn_unused_result)) - __attribute__((alloc_size(2))) -#endif - ; + extern void *(*MEM_recallocN_id)(void *vmemh, size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(2); #define MEM_reallocN(vmemh, len) MEM_reallocN_id(vmemh, len, __func__) #define MEM_recallocN(vmemh, len) MEM_recallocN_id(vmemh, len, __func__) @@ -134,97 +111,75 @@ extern "C" { * Allocate a block of memory of size len, with tag name str. The * memory is cleared. The name must be static, because only a * pointer to it is stored ! */ - void *MEM_callocN(size_t len, const char *str) -#if MEM_GNU_ATTRIBUTES - __attribute__((malloc)) - __attribute__((warn_unused_result)) - __attribute__((nonnull(2))) - __attribute__((alloc_size(1))) -#endif - ; + extern void *(*MEM_callocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); /** * Allocate a block of memory of size len, with tag name str. The * name must be a static, because only a pointer to it is stored ! * */ - void *MEM_mallocN(size_t len, const char *str) -#if MEM_GNU_ATTRIBUTES - __attribute__((malloc)) - __attribute__((warn_unused_result)) - __attribute__((nonnull(2))) - __attribute__((alloc_size(1))) -#endif - ; + extern void *(*MEM_mallocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); /** * Same as callocN, clears memory and uses mmap (disk cached) if supported. * Can be free'd with MEM_freeN as usual. * */ - void *MEM_mapallocN(size_t len, const char *str) -#if MEM_GNU_ATTRIBUTES - __attribute__((malloc)) - __attribute__((warn_unused_result)) - __attribute__((nonnull(2))) - __attribute__((alloc_size(1))) -#endif - ; + extern void *(*MEM_mapallocN)(size_t len, const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); /** Print a list of the names and sizes of all allocated memory * blocks. as a python dict for easy investigation */ - void MEM_printmemlist_pydict(void); + extern void (*MEM_printmemlist_pydict)(void); /** Print a list of the names and sizes of all allocated memory * blocks. */ - void MEM_printmemlist(void); + extern void (*MEM_printmemlist)(void); /** calls the function on all allocated memory blocks. */ - void MEM_callbackmemlist(void (*func)(void *)); + extern void (*MEM_callbackmemlist)(void (*func)(void *)); /** Print statistics about memory usage */ - void MEM_printmemlist_stats(void); + extern void (*MEM_printmemlist_stats)(void); /** Set the callback function for error output. */ - void MEM_set_error_callback(void (*func)(const char *)); + extern void (*MEM_set_error_callback)(void (*func)(const char *)); /** * Are the start/end block markers still correct ? * * @retval 0 for correct memory, 1 for corrupted memory. */ - bool MEM_check_memory_integrity(void); + extern bool (*MEM_check_memory_integrity)(void); /** Set thread locking functions for safe memory allocation from multiple * threads, pass NULL pointers to disable thread locking again. */ - void MEM_set_lock_callback(void (*lock)(void), void (*unlock)(void)); + extern void (*MEM_set_lock_callback)(void (*lock)(void), void (*unlock)(void)); /** Attempt to enforce OSX (or other OS's) to have malloc and stack nonzero */ - void MEM_set_memory_debug(void); + extern void (*MEM_set_memory_debug)(void); /** * Memory usage stats * - MEM_get_memory_in_use is all memory * - MEM_get_mapped_memory_in_use is a subset of all memory */ - uintptr_t MEM_get_memory_in_use(void); + extern uintptr_t (*MEM_get_memory_in_use)(void); /** Get mapped memory usage. */ - uintptr_t MEM_get_mapped_memory_in_use(void); + extern uintptr_t (*MEM_get_mapped_memory_in_use)(void); /** Get amount of memory blocks in use. */ - unsigned int MEM_get_memory_blocks_in_use(void); + extern unsigned int (*MEM_get_memory_blocks_in_use)(void); /** Reset the peak memory statistic to zero. */ - void MEM_reset_peak_memory(void); + extern void (*MEM_reset_peak_memory)(void); /** Get the peak memory usage in bytes, including mmap allocations. */ - size_t MEM_get_peak_memory(void) -#if MEM_GNU_ATTRIBUTES - __attribute__((warn_unused_result)) -#endif - ; + extern size_t (*MEM_get_peak_memory)(void) ATTR_WARN_UNUSED_RESULT; #define MEM_SAFE_FREE(v) if (v) { MEM_freeN(v); v = NULL; } (void)0 #ifndef NDEBUG -const char *MEM_name_ptr(void *vmemh); +extern const char *(*MEM_name_ptr)(void *vmemh); #endif +/* Switch allocator to slower but fully guarded mode. */ +void MEM_use_guarded_allocator(void); + #ifdef __cplusplus /* alloc funcs for C++ only */ #define MEM_CXX_CLASS_ALLOC_FUNCS(_id) \ diff --git a/intern/guardedalloc/SConscript b/intern/guardedalloc/SConscript index e3f787fe6c2..3bae808cc94 100644 --- a/intern/guardedalloc/SConscript +++ b/intern/guardedalloc/SConscript @@ -29,10 +29,12 @@ Import('env') defs = [] -sources = ['intern/mallocn.c', 'intern/mmap_win.c'] - -# could make this optional -defs.append('WITH_GUARDEDALLOC') +sources = [ + 'intern/mallocn.c', + 'intern/mallocn_guarded_impl.c', + 'intern/mallocn_lockfree_impl.c', + 'intern/mmap_win.c' +] if env['WITH_BF_CXX_GUARDEDALLOC']: sources.append('cpp/mallocn.cpp') diff --git a/intern/guardedalloc/intern/mallocn.c b/intern/guardedalloc/intern/mallocn.c index e8102d98345..2ac01a6c7e4 100644 --- a/intern/guardedalloc/intern/mallocn.c +++ b/intern/guardedalloc/intern/mallocn.c @@ -32,1262 +32,64 @@ * Guarded memory allocation, and boundary-write detection. */ -#include -#include /* memcpy */ -#include -#include - -/* mmap exception */ -#if defined(WIN32) -# include "mmap_win.h" -#else -# include -#endif - -#if defined(_MSC_VER) -# define __func__ __FUNCTION__ -#endif - -/* only for utility functions */ -#if defined(__GNUC__) && defined(__linux__) -#include -# define HAVE_MALLOC_H -#endif - #include "MEM_guardedalloc.h" /* to ensure strict conversions */ #include "../../source/blender/blenlib/BLI_strict_flags.h" - -/* should always be defined except for experimental cases */ -#ifdef WITH_GUARDEDALLOC - -#include "atomic_ops.h" - -/* Only for debugging: - * store original buffer's name when doing MEM_dupallocN - * helpful to profile issues with non-freed "dup_alloc" buffers, - * but this introduces some overhead to memory header and makes - * things slower a bit, so better to keep disabled by default - */ -//#define DEBUG_MEMDUPLINAME - -/* Only for debugging: - * lets you count the allocations so as to find the allocator of unfreed memory - * in situations where the leak is predictable */ - -//#define DEBUG_MEMCOUNTER - -/* Only for debugging: - * defining DEBUG_THREADS will enable check whether memory manager - * is locked with a mutex when allocation is called from non-main - * thread. - * - * This helps troubleshooting memory issues caused by the fact - * guarded allocator is not thread-safe, however this check will - * fail to check allocations from openmp threads. - */ -//#define DEBUG_THREADS - -/* Only for debugging: - * Defining DEBUG_BACKTRACE will store a backtrace from where - * memory block was allocated and print this trace for all - * unfreed blocks. - */ -//#define DEBUG_BACKTRACE - -#ifdef DEBUG_BACKTRACE -# define BACKTRACE_SIZE 100 -#endif - -#ifdef DEBUG_MEMCOUNTER - /* set this to the value that isn't being freed */ -# define DEBUG_MEMCOUNTER_ERROR_VAL 0 -static int _mallocn_count = 0; - -/* breakpoint here */ -static void memcount_raise(const char *name) -{ - fprintf(stderr, "%s: memcount-leak, %d\n", name, _mallocn_count); -} -#endif - -/* Blame Microsoft for LLP64 and no inttypes.h, quick workaround needed: */ -#if defined(WIN64) -# define SIZET_FORMAT "%I64u" -# define SIZET_ARG(a) ((unsigned long long)(a)) -#else -# define SIZET_FORMAT "%lu" -# define SIZET_ARG(a) ((unsigned long)(a)) -#endif - -#define SIZET_ALIGN_4(len) ((len + 3) & ~(size_t)3) - - -/* --------------------------------------------------------------------- */ -/* Data definition */ -/* --------------------------------------------------------------------- */ -/* all memory chunks are put in linked lists */ -typedef struct localLink { - struct localLink *next, *prev; -} localLink; - -typedef struct localListBase { - void *first, *last; -} localListBase; - -/* note: keep this struct aligned (e.g., irix/gcc) - Hos */ -typedef struct MemHead { - int tag1; - size_t len; - struct MemHead *next, *prev; - const char *name; - const char *nextname; - int tag2; - int mmap; /* if true, memory was mmapped */ -#ifdef DEBUG_MEMCOUNTER - int _count; -#endif - -#ifdef DEBUG_MEMDUPLINAME - int need_free_name, pad; -#endif - -#ifdef DEBUG_BACKTRACE - void *backtrace[BACKTRACE_SIZE]; - int backtrace_size; -#endif -} MemHead; - -/* for openmp threading asserts, saves time troubleshooting - * we may need to extend this if blender code starts using MEM_ - * functions inside OpenMP correctly with omp_set_lock() */ - -#if 0 /* disable for now, only use to debug openmp code which doesn lock threads for malloc */ -#if defined(_OPENMP) && defined(DEBUG) -# include -# include -# define DEBUG_OMP_MALLOC -#endif -#endif - -#ifdef DEBUG_THREADS -# include -# include -static pthread_t mainid; -#endif - -#ifdef DEBUG_BACKTRACE -# if defined(__linux__) || defined(__APPLE__) -# include -// Windows is not supported yet. -//# elif defined(_MSV_VER) -//# include -# endif -#endif - -typedef struct MemTail { - int tag3, pad; -} MemTail; - - -/* --------------------------------------------------------------------- */ -/* local functions */ -/* --------------------------------------------------------------------- */ - -static void addtail(volatile localListBase *listbase, void *vlink); -static void remlink(volatile localListBase *listbase, void *vlink); -static void rem_memblock(MemHead *memh); -static void MemorY_ErroR(const char *block, const char *error); -static const char *check_memlist(MemHead *memh); - -/* --------------------------------------------------------------------- */ -/* locally used defines */ -/* --------------------------------------------------------------------- */ - -#ifdef __BIG_ENDIAN__ -# define MAKE_ID(a, b, c, d) ((int)(a) << 24 | (int)(b) << 16 | (c) << 8 | (d)) -#else -# define MAKE_ID(a, b, c, d) ((int)(d) << 24 | (int)(c) << 16 | (b) << 8 | (a)) -#endif - -#define MEMTAG1 MAKE_ID('M', 'E', 'M', 'O') -#define MEMTAG2 MAKE_ID('R', 'Y', 'B', 'L') -#define MEMTAG3 MAKE_ID('O', 'C', 'K', '!') -#define MEMFREE MAKE_ID('F', 'R', 'E', 'E') - -#define MEMNEXT(x) \ - ((MemHead *)(((char *) x) - ((char *) &(((MemHead *)0)->next)))) - -/* --------------------------------------------------------------------- */ -/* vars */ -/* --------------------------------------------------------------------- */ - - -static unsigned int totblock = 0; -static size_t mem_in_use = 0, mmap_in_use = 0, peak_mem = 0; - -static volatile struct localListBase _membase; -static volatile struct localListBase *membase = &_membase; -static void (*error_callback)(const char *) = NULL; -static void (*thread_lock_callback)(void) = NULL; -static void (*thread_unlock_callback)(void) = NULL; - -static bool malloc_debug_memset = false; - -#ifdef malloc -#undef malloc -#endif - -#ifdef calloc -#undef calloc -#endif - -#ifdef free -#undef free -#endif - - -/* --------------------------------------------------------------------- */ -/* implementation */ -/* --------------------------------------------------------------------- */ - -#ifdef __GNUC__ -__attribute__ ((format(printf, 1, 2))) -#endif -static void print_error(const char *str, ...) -{ - char buf[512]; - va_list ap; - - va_start(ap, str); - vsnprintf(buf, sizeof(buf), str, ap); - va_end(ap); - buf[sizeof(buf) - 1] = '\0'; - - if (error_callback) error_callback(buf); -} - -static void mem_lock_thread(void) -{ -#ifdef DEBUG_THREADS - static int initialized = 0; - - if (initialized == 0) { - /* assume first allocation happens from main thread */ - mainid = pthread_self(); - initialized = 1; - } - - if (!pthread_equal(pthread_self(), mainid) && thread_lock_callback == NULL) { - assert(!"Memory function is called from non-main thread without lock"); - } -#endif - -#ifdef DEBUG_OMP_MALLOC - assert(omp_in_parallel() == 0); -#endif - - if (thread_lock_callback) - thread_lock_callback(); -} - -static void mem_unlock_thread(void) -{ -#ifdef DEBUG_THREADS - if (!pthread_equal(pthread_self(), mainid) && thread_lock_callback == NULL) { - assert(!"Thread lock was removed while allocation from thread is in progress"); - } -#endif - - if (thread_unlock_callback) - thread_unlock_callback(); -} - -bool MEM_check_memory_integrity(void) -{ - const char *err_val = NULL; - MemHead *listend; - /* check_memlist starts from the front, and runs until it finds - * the requested chunk. For this test, that's the last one. */ - listend = membase->last; - - err_val = check_memlist(listend); - - return (err_val != NULL); -} - - -void MEM_set_error_callback(void (*func)(const char *)) -{ - error_callback = func; -} - -void MEM_set_lock_callback(void (*lock)(void), void (*unlock)(void)) -{ - thread_lock_callback = lock; - thread_unlock_callback = unlock; -} - -void MEM_set_memory_debug(void) -{ - malloc_debug_memset = true; -} - -size_t MEM_allocN_len(const void *vmemh) -{ - if (vmemh) { - const MemHead *memh = vmemh; - - memh--; - return memh->len; - } - else { - return 0; - } -} - -void *MEM_dupallocN(const void *vmemh) -{ - void *newp = NULL; - - if (vmemh) { - const MemHead *memh = vmemh; - memh--; - -#ifndef DEBUG_MEMDUPLINAME - if (memh->mmap) - newp = MEM_mapallocN(memh->len, "dupli_mapalloc"); - else - newp = MEM_mallocN(memh->len, "dupli_alloc"); - - if (newp == NULL) return NULL; -#else - { - MemHead *nmemh; - char *name = malloc(strlen(memh->name) + 24); - - if (memh->mmap) { - sprintf(name, "%s %s", "dupli_mapalloc", memh->name); - newp = MEM_mapallocN(memh->len, name); - } - else { - sprintf(name, "%s %s", "dupli_alloc", memh->name); - newp = MEM_mallocN(memh->len, name); - } - - if (newp == NULL) return NULL; - - nmemh = newp; - nmemh--; - - nmemh->need_free_name = 1; - } -#endif - - memcpy(newp, vmemh, memh->len); - } - - return newp; -} - -void *MEM_reallocN_id(void *vmemh, size_t len, const char *str) -{ - void *newp = NULL; - - if (vmemh) { - MemHead *memh = vmemh; - memh--; - - newp = MEM_mallocN(len, memh->name); - if (newp) { - if (len < memh->len) { - /* shrink */ - memcpy(newp, vmemh, len); - } - else { - /* grow (or remain same size) */ - memcpy(newp, vmemh, memh->len); - } - } - - MEM_freeN(vmemh); - } - else { - newp = MEM_mallocN(len, str); - } - - return newp; -} - -void *MEM_recallocN_id(void *vmemh, size_t len, const char *str) -{ - void *newp = NULL; - - if (vmemh) { - MemHead *memh = vmemh; - memh--; - - newp = MEM_mallocN(len, memh->name); - if (newp) { - if (len < memh->len) { - /* shrink */ - memcpy(newp, vmemh, len); - } - else { - memcpy(newp, vmemh, memh->len); - - if (len > memh->len) { - /* grow */ - /* zero new bytes */ - memset(((char *)newp) + memh->len, 0, len - memh->len); - } - } - } - - MEM_freeN(vmemh); - } - else { - newp = MEM_callocN(len, str); - } - - return newp; -} - -#ifdef DEBUG_BACKTRACE -# if defined(__linux__) || defined(__APPLE__) -static void make_memhead_backtrace(MemHead *memh) -{ - memh->backtrace_size = backtrace(memh->backtrace, BACKTRACE_SIZE); -} - -static void print_memhead_backtrace(MemHead *memh) -{ - char **strings; - int i; - - strings = backtrace_symbols(memh->backtrace, memh->backtrace_size); - for (i = 0; i < memh->backtrace_size; i++) { - print_error(" %s\n", strings[i]); - } - - free(strings); -} -# else -static void make_memhead_backtrace(MemHead *memh) -{ - (void) memh; /* Ignored. */ -} - -static void print_memhead_backtrace(MemHead *memh) -{ - (void) memh; /* Ignored. */ -} -# endif /* defined(__linux__) || defined(__APPLE__) */ -#endif /* DEBUG_BACKTRACE */ - -static void make_memhead_header(MemHead *memh, size_t len, const char *str) -{ - MemTail *memt; - - memh->tag1 = MEMTAG1; - memh->name = str; - memh->nextname = NULL; - memh->len = len; - memh->mmap = 0; - memh->tag2 = MEMTAG2; - -#ifdef DEBUG_MEMDUPLINAME - memh->need_free_name = 0; -#endif - -#ifdef DEBUG_BACKTRACE - make_memhead_backtrace(memh); -#endif - - memt = (MemTail *)(((char *) memh) + sizeof(MemHead) + len); - memt->tag3 = MEMTAG3; - - atomic_add_u(&totblock, 1); - atomic_add_z(&mem_in_use, len); - - mem_lock_thread(); - addtail(membase, &memh->next); - if (memh->next) { - memh->nextname = MEMNEXT(memh->next)->name; - } - peak_mem = mem_in_use > peak_mem ? mem_in_use : peak_mem; - mem_unlock_thread(); -} - -void *MEM_mallocN(size_t len, const char *str) -{ - MemHead *memh; - - len = SIZET_ALIGN_4(len); - - memh = (MemHead *)malloc(len + sizeof(MemHead) + sizeof(MemTail)); - - if (memh) { - make_memhead_header(memh, len, str); - if (malloc_debug_memset && len) - memset(memh + 1, 255, len); - -#ifdef DEBUG_MEMCOUNTER - if (_mallocn_count == DEBUG_MEMCOUNTER_ERROR_VAL) - memcount_raise(__func__); - memh->_count = _mallocn_count++; -#endif - return (++memh); - } - print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total %u\n", - SIZET_ARG(len), str, (unsigned int) mem_in_use); - return NULL; -} - -void *MEM_callocN(size_t len, const char *str) -{ - MemHead *memh; - - len = SIZET_ALIGN_4(len); - - memh = (MemHead *)calloc(len + sizeof(MemHead) + sizeof(MemTail), 1); - - if (memh) { - make_memhead_header(memh, len, str); -#ifdef DEBUG_MEMCOUNTER - if (_mallocn_count == DEBUG_MEMCOUNTER_ERROR_VAL) - memcount_raise(__func__); - memh->_count = _mallocn_count++; -#endif - return (++memh); - } - print_error("Calloc returns null: len=" SIZET_FORMAT " in %s, total %u\n", - SIZET_ARG(len), str, (unsigned int) mem_in_use); - return NULL; -} - -/* note; mmap returns zero'd memory */ -void *MEM_mapallocN(size_t len, const char *str) -{ - MemHead *memh; - - len = SIZET_ALIGN_4(len); - -#if defined(WIN32) - /* our windows mmap implementation is not thread safe */ - mem_lock_thread(); -#endif - memh = mmap(NULL, len + sizeof(MemHead) + sizeof(MemTail), - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); -#if defined(WIN32) - mem_unlock_thread(); -#endif - - if (memh != (MemHead *)-1) { - make_memhead_header(memh, len, str); - memh->mmap = 1; - atomic_add_z(&mmap_in_use, len); - mem_lock_thread(); - peak_mem = mmap_in_use > peak_mem ? mmap_in_use : peak_mem; - mem_unlock_thread(); -#ifdef DEBUG_MEMCOUNTER - if (_mallocn_count == DEBUG_MEMCOUNTER_ERROR_VAL) - memcount_raise(__func__); - memh->_count = _mallocn_count++; -#endif - return (++memh); - } - else { - print_error("Mapalloc returns null, fallback to regular malloc: " - "len=" SIZET_FORMAT " in %s, total %u\n", - SIZET_ARG(len), str, (unsigned int) mmap_in_use); - return MEM_callocN(len, str); - } -} - -/* Memory statistics print */ -typedef struct MemPrintBlock { - const char *name; - uintptr_t len; - int items; -} MemPrintBlock; - -static int compare_name(const void *p1, const void *p2) -{ - const MemPrintBlock *pb1 = (const MemPrintBlock *)p1; - const MemPrintBlock *pb2 = (const MemPrintBlock *)p2; - - return strcmp(pb1->name, pb2->name); -} - -static int compare_len(const void *p1, const void *p2) -{ - const MemPrintBlock *pb1 = (const MemPrintBlock *)p1; - const MemPrintBlock *pb2 = (const MemPrintBlock *)p2; - - if (pb1->len < pb2->len) - return 1; - else if (pb1->len == pb2->len) - return 0; - else - return -1; -} - -void MEM_printmemlist_stats(void) -{ - MemHead *membl; - MemPrintBlock *pb, *printblock; - unsigned int totpb, a, b; -#ifdef HAVE_MALLOC_H - size_t mem_in_use_slop = 0; -#endif - mem_lock_thread(); - - /* put memory blocks into array */ - printblock = malloc(sizeof(MemPrintBlock) * totblock); - - pb = printblock; - totpb = 0; - - membl = membase->first; - if (membl) membl = MEMNEXT(membl); - - while (membl) { - pb->name = membl->name; - pb->len = membl->len; - pb->items = 1; - - totpb++; - pb++; - -#ifdef HAVE_MALLOC_H - if (!membl->mmap) { - mem_in_use_slop += (sizeof(MemHead) + sizeof(MemTail) + - malloc_usable_size((void *)membl)) - membl->len; - } -#endif - - if (membl->next) - membl = MEMNEXT(membl->next); - else break; - } - - /* sort by name and add together blocks with the same name */ - qsort(printblock, totpb, sizeof(MemPrintBlock), compare_name); - for (a = 0, b = 0; a < totpb; a++) { - if (a == b) { - continue; - } - else if (strcmp(printblock[a].name, printblock[b].name) == 0) { - printblock[b].len += printblock[a].len; - printblock[b].items++; - } - else { - b++; - memcpy(&printblock[b], &printblock[a], sizeof(MemPrintBlock)); - } - } - totpb = b + 1; - - /* sort by length and print */ - qsort(printblock, totpb, sizeof(MemPrintBlock), compare_len); - printf("\ntotal memory len: %.3f MB\n", - (double)mem_in_use / (double)(1024 * 1024)); - printf("peak memory len: %.3f MB\n", - (double)peak_mem / (double)(1024 * 1024)); -#ifdef HAVE_MALLOC_H - printf("slop memory len: %.3f MB\n", - (double)mem_in_use_slop / (double)(1024 * 1024)); -#endif - printf(" ITEMS TOTAL-MiB AVERAGE-KiB TYPE\n"); - for (a = 0, pb = printblock; a < totpb; a++, pb++) { - printf("%6d (%8.3f %8.3f) %s\n", - pb->items, (double)pb->len / (double)(1024 * 1024), - (double)pb->len / 1024.0 / (double)pb->items, pb->name); - } - free(printblock); - - mem_unlock_thread(); - -#ifdef HAVE_MALLOC_H /* GLIBC only */ - printf("System Statistics:\n"); - malloc_stats(); -#endif -} - -static const char mem_printmemlist_pydict_script[] = -"mb_userinfo = {}\n" -"totmem = 0\n" -"for mb_item in membase:\n" -" mb_item_user_size = mb_userinfo.setdefault(mb_item['name'], [0,0])\n" -" mb_item_user_size[0] += 1 # Add a user\n" -" mb_item_user_size[1] += mb_item['len'] # Increment the size\n" -" totmem += mb_item['len']\n" -"print('(membase) items:', len(membase), '| unique-names:',\n" -" len(mb_userinfo), '| total-mem:', totmem)\n" -"mb_userinfo_sort = list(mb_userinfo.items())\n" -"for sort_name, sort_func in (('size', lambda a: -a[1][1]),\n" -" ('users', lambda a: -a[1][0]),\n" -" ('name', lambda a: a[0])):\n" -" print('\\nSorting by:', sort_name)\n" -" mb_userinfo_sort.sort(key = sort_func)\n" -" for item in mb_userinfo_sort:\n" -" print('name:%%s, users:%%i, len:%%i' %%\n" -" (item[0], item[1][0], item[1][1]))\n"; - -/* Prints in python syntax for easy */ -static void MEM_printmemlist_internal(int pydict) -{ - MemHead *membl; - - mem_lock_thread(); - - membl = membase->first; - if (membl) membl = MEMNEXT(membl); - - if (pydict) { - print_error("# membase_debug.py\n"); - print_error("membase = [\n"); - } - while (membl) { - if (pydict) { - fprintf(stderr, - " {'len':" SIZET_FORMAT ", " - "'name':'''%s''', " - "'pointer':'%p'},\n", - SIZET_ARG(membl->len), membl->name, (void *)(membl + 1)); - } - else { -#ifdef DEBUG_MEMCOUNTER - print_error("%s len: " SIZET_FORMAT " %p, count: %d\n", - membl->name, SIZET_ARG(membl->len), membl + 1, - membl->_count); -#else - print_error("%s len: " SIZET_FORMAT " %p\n", - membl->name, SIZET_ARG(membl->len), membl + 1); -#endif -#ifdef DEBUG_BACKTRACE - print_memhead_backtrace(membl); -#endif - } - if (membl->next) - membl = MEMNEXT(membl->next); - else break; - } - if (pydict) { - fprintf(stderr, "]\n\n"); - fprintf(stderr, mem_printmemlist_pydict_script); - } - - mem_unlock_thread(); -} - -void MEM_callbackmemlist(void (*func)(void *)) -{ - MemHead *membl; - - mem_lock_thread(); - - membl = membase->first; - if (membl) membl = MEMNEXT(membl); - - while (membl) { - func(membl + 1); - if (membl->next) - membl = MEMNEXT(membl->next); - else break; - } - - mem_unlock_thread(); -} - -#if 0 -short MEM_testN(void *vmemh) -{ - MemHead *membl; - - mem_lock_thread(); - - membl = membase->first; - if (membl) membl = MEMNEXT(membl); - - while (membl) { - if (vmemh == membl + 1) { - mem_unlock_thread(); - return 1; - } - - if (membl->next) - membl = MEMNEXT(membl->next); - else break; - } - - mem_unlock_thread(); - - print_error("Memoryblock %p: pointer not in memlist\n", vmemh); - return 0; -} -#endif - -void MEM_printmemlist(void) -{ - MEM_printmemlist_internal(0); -} -void MEM_printmemlist_pydict(void) -{ - MEM_printmemlist_internal(1); -} - -void MEM_freeN(void *vmemh) -{ - MemTail *memt; - MemHead *memh = vmemh; - const char *name; - - if (memh == NULL) { - MemorY_ErroR("free", "attempt to free NULL pointer"); - /* print_error(err_stream, "%d\n", (memh+4000)->tag1); */ - return; - } - - if (sizeof(intptr_t) == 8) { - if (((intptr_t) memh) & 0x7) { - MemorY_ErroR("free", "attempt to free illegal pointer"); - return; - } - } - else { - if (((intptr_t) memh) & 0x3) { - MemorY_ErroR("free", "attempt to free illegal pointer"); - return; - } - } - - memh--; - if (memh->tag1 == MEMFREE && memh->tag2 == MEMFREE) { - MemorY_ErroR(memh->name, "double free"); - return; - } - - if ((memh->tag1 == MEMTAG1) && - (memh->tag2 == MEMTAG2) && - ((memh->len & 0x3) == 0)) - { - memt = (MemTail *)(((char *) memh) + sizeof(MemHead) + memh->len); - if (memt->tag3 == MEMTAG3) { - - memh->tag1 = MEMFREE; - memh->tag2 = MEMFREE; - memt->tag3 = MEMFREE; - /* after tags !!! */ - rem_memblock(memh); - - return; - } - MemorY_ErroR(memh->name, "end corrupt"); - name = check_memlist(memh); - if (name != NULL) { - if (name != memh->name) MemorY_ErroR(name, "is also corrupt"); - } - } - else { - mem_lock_thread(); - name = check_memlist(memh); - mem_unlock_thread(); - if (name == NULL) - MemorY_ErroR("free", "pointer not in memlist"); - else - MemorY_ErroR(name, "error in header"); - } - - totblock--; - /* here a DUMP should happen */ - - return; -} - -/* --------------------------------------------------------------------- */ -/* local functions */ -/* --------------------------------------------------------------------- */ - -static void addtail(volatile localListBase *listbase, void *vlink) -{ - struct localLink *link = vlink; - - /* for a generic API error checks here is fine but - * the limited use here they will never be NULL */ -#if 0 - if (link == NULL) return; - if (listbase == NULL) return; -#endif - - link->next = NULL; - link->prev = listbase->last; - - if (listbase->last) ((struct localLink *)listbase->last)->next = link; - if (listbase->first == NULL) listbase->first = link; - listbase->last = link; -} - -static void remlink(volatile localListBase *listbase, void *vlink) -{ - struct localLink *link = vlink; - - /* for a generic API error checks here is fine but - * the limited use here they will never be NULL */ -#if 0 - if (link == NULL) return; - if (listbase == NULL) return; -#endif - - if (link->next) link->next->prev = link->prev; - if (link->prev) link->prev->next = link->next; - - if (listbase->last == link) listbase->last = link->prev; - if (listbase->first == link) listbase->first = link->next; -} - -static void rem_memblock(MemHead *memh) -{ - mem_lock_thread(); - remlink(membase, &memh->next); - if (memh->prev) { - if (memh->next) - MEMNEXT(memh->prev)->nextname = MEMNEXT(memh->next)->name; - else - MEMNEXT(memh->prev)->nextname = NULL; - } - mem_unlock_thread(); - - atomic_sub_u(&totblock, 1); - atomic_sub_z(&mem_in_use, memh->len); - -#ifdef DEBUG_MEMDUPLINAME - if (memh->need_free_name) - free((char *) memh->name); -#endif - - if (memh->mmap) { - atomic_sub_z(&mmap_in_use, memh->len); -#if defined(WIN32) - /* our windows mmap implementation is not thread safe */ - mem_lock_thread(); -#endif - if (munmap(memh, memh->len + sizeof(MemHead) + sizeof(MemTail))) - printf("Couldn't unmap memory %s\n", memh->name); -#if defined(WIN32) - mem_unlock_thread(); -#endif - } - else { - if (malloc_debug_memset && memh->len) - memset(memh + 1, 255, memh->len); - free(memh); - } -} - -static void MemorY_ErroR(const char *block, const char *error) -{ - print_error("Memoryblock %s: %s\n", block, error); - -#ifdef WITH_ASSERT_ABORT - abort(); -#endif -} - -static const char *check_memlist(MemHead *memh) -{ - MemHead *forw, *back, *forwok, *backok; - const char *name; - - forw = membase->first; - if (forw) forw = MEMNEXT(forw); - forwok = NULL; - while (forw) { - if (forw->tag1 != MEMTAG1 || forw->tag2 != MEMTAG2) break; - forwok = forw; - if (forw->next) forw = MEMNEXT(forw->next); - else forw = NULL; - } - - back = (MemHead *) membase->last; - if (back) back = MEMNEXT(back); - backok = NULL; - while (back) { - if (back->tag1 != MEMTAG1 || back->tag2 != MEMTAG2) break; - backok = back; - if (back->prev) back = MEMNEXT(back->prev); - else back = NULL; - } - - if (forw != back) return ("MORE THAN 1 MEMORYBLOCK CORRUPT"); - - if (forw == NULL && back == NULL) { - /* no wrong headers found then but in search of memblock */ - - forw = membase->first; - if (forw) forw = MEMNEXT(forw); - forwok = NULL; - while (forw) { - if (forw == memh) break; - if (forw->tag1 != MEMTAG1 || forw->tag2 != MEMTAG2) break; - forwok = forw; - if (forw->next) forw = MEMNEXT(forw->next); - else forw = NULL; - } - if (forw == NULL) return NULL; - - back = (MemHead *) membase->last; - if (back) back = MEMNEXT(back); - backok = NULL; - while (back) { - if (back == memh) break; - if (back->tag1 != MEMTAG1 || back->tag2 != MEMTAG2) break; - backok = back; - if (back->prev) back = MEMNEXT(back->prev); - else back = NULL; - } - } - - if (forwok) name = forwok->nextname; - else name = "No name found"; - - if (forw == memh) { - /* to be sure but this block is removed from the list */ - if (forwok) { - if (backok) { - forwok->next = (MemHead *)&backok->next; - backok->prev = (MemHead *)&forwok->next; - forwok->nextname = backok->name; - } - else { - forwok->next = NULL; - membase->last = (struct localLink *) &forwok->next; - } - } - else { - if (backok) { - backok->prev = NULL; - membase->first = &backok->next; - } - else { - membase->first = membase->last = NULL; - } - } - } - else { - MemorY_ErroR(name, "Additional error in header"); - return("Additional error in header"); - } - - return(name); -} - -size_t MEM_get_peak_memory(void) -{ - size_t _peak_mem; - - mem_lock_thread(); - _peak_mem = peak_mem; - mem_unlock_thread(); - - return _peak_mem; -} - -void MEM_reset_peak_memory(void) -{ - mem_lock_thread(); - peak_mem = 0; - mem_unlock_thread(); -} - -uintptr_t MEM_get_memory_in_use(void) -{ - uintptr_t _mem_in_use; - - mem_lock_thread(); - _mem_in_use = mem_in_use; - mem_unlock_thread(); - - return _mem_in_use; -} - -uintptr_t MEM_get_mapped_memory_in_use(void) -{ - uintptr_t _mmap_in_use; - - mem_lock_thread(); - _mmap_in_use = mmap_in_use; - mem_unlock_thread(); - - return _mmap_in_use; -} - -unsigned int MEM_get_memory_blocks_in_use(void) -{ - unsigned int _totblock; - - mem_lock_thread(); - _totblock = totblock; - mem_unlock_thread(); - - return _totblock; -} +#include "mallocn_intern.h" + +size_t (*MEM_allocN_len)(const void *vmemh) = MEM_lockfree_allocN_len; +void (*MEM_freeN)(void *vmemh) = MEM_lockfree_freeN; +void *(*MEM_dupallocN)(const void *vmemh) = MEM_lockfree_dupallocN; +void *(*MEM_reallocN_id)(void *vmemh, size_t len, const char *str) = MEM_lockfree_reallocN_id; +void *(*MEM_recallocN_id)(void *vmemh, size_t len, const char *str) = MEM_lockfree_recallocN_id;; +void *(*MEM_callocN)(size_t len, const char *str) = MEM_lockfree_callocN; +void *(*MEM_mallocN)(size_t len, const char *str) = MEM_lockfree_mallocN; +void *(*MEM_mapallocN)(size_t len, const char *str) = MEM_lockfree_mapallocN; +void (*MEM_printmemlist_pydict)(void) = MEM_lockfree_printmemlist_pydict; +void (*MEM_printmemlist)(void) = MEM_lockfree_printmemlist; +void (*MEM_callbackmemlist)(void (*func)(void *)) = MEM_lockfree_callbackmemlist; +void (*MEM_printmemlist_stats)(void) = MEM_lockfree_printmemlist_stats; +void (*MEM_set_error_callback)(void (*func)(const char *)) = MEM_lockfree_set_error_callback; +bool (*MEM_check_memory_integrity)(void) = MEM_lockfree_check_memory_integrity; +void (*MEM_set_lock_callback)(void (*lock)(void), void (*unlock)(void)) = MEM_lockfree_set_lock_callback; +void (*MEM_set_memory_debug)(void) = MEM_lockfree_set_memory_debug; +uintptr_t (*MEM_get_memory_in_use)(void) = MEM_lockfree_get_memory_in_use; +uintptr_t (*MEM_get_mapped_memory_in_use)(void) = MEM_lockfree_get_mapped_memory_in_use; +unsigned int (*MEM_get_memory_blocks_in_use)(void) = MEM_lockfree_get_memory_blocks_in_use; +void (*MEM_reset_peak_memory)(void) = MEM_lockfree_reset_peak_memory; +uintptr_t (*MEM_get_peak_memory)(void) = MEM_lockfree_get_peak_memory; #ifndef NDEBUG -const char *MEM_name_ptr(void *vmemh) -{ - if (vmemh) { - MemHead *memh = vmemh; - memh--; - return memh->name; - } - else { - return "MEM_name_ptr(NULL)"; - } -} -#endif /* NDEBUG */ - -#else /* !WITH_GUARDEDALLOC */ - -#ifdef __GNUC__ -# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) -#else -# define UNUSED(x) UNUSED_ ## x +const char *(*MEM_name_ptr)(void *vmemh) = MEM_lockfree_name_ptr; #endif -#include - -size_t MEM_allocN_len(const void *vmemh) +void MEM_use_guarded_allocator(void) { - return malloc_usable_size((void *)vmemh); + MEM_allocN_len = MEM_guarded_allocN_len; + MEM_freeN = MEM_guarded_freeN; + MEM_dupallocN = MEM_guarded_dupallocN; + MEM_reallocN_id = MEM_guarded_reallocN_id; + MEM_recallocN_id = MEM_guarded_recallocN_id;; + MEM_callocN = MEM_guarded_callocN; + MEM_mallocN = MEM_guarded_mallocN; + MEM_mapallocN = MEM_guarded_mapallocN; + MEM_printmemlist_pydict = MEM_guarded_printmemlist_pydict; + MEM_printmemlist = MEM_guarded_printmemlist; + MEM_callbackmemlist = MEM_guarded_callbackmemlist; + MEM_printmemlist_stats = MEM_guarded_printmemlist_stats; + MEM_set_error_callback = MEM_guarded_set_error_callback; + MEM_check_memory_integrity = MEM_guarded_check_memory_integrity; + MEM_set_lock_callback = MEM_guarded_set_lock_callback; + MEM_set_memory_debug = MEM_guarded_set_memory_debug; + MEM_get_memory_in_use = MEM_guarded_get_memory_in_use; + MEM_get_mapped_memory_in_use = MEM_guarded_get_mapped_memory_in_use; + MEM_get_memory_blocks_in_use = MEM_guarded_get_memory_blocks_in_use; + MEM_reset_peak_memory = MEM_guarded_reset_peak_memory; + MEM_get_peak_memory = MEM_guarded_get_peak_memory; + +#ifndef NDEBUG + MEM_name_ptr = MEM_guarded_name_ptr; +#endif } - -void MEM_freeN(void *vmemh) -{ - free(vmemh); -} - -void *MEM_dupallocN(const void *vmemh) -{ - void *newp = NULL; - if (vmemh) { - const size_t prev_size = MEM_allocN_len(vmemh); - newp = malloc(prev_size); - memcpy(newp, vmemh, prev_size); - } - return newp; -} - -void *MEM_reallocN_id(void *vmemh, size_t len, const char *UNUSED(str)) -{ - return realloc(vmemh, len); -} - -void *MEM_recallocN_id(void *vmemh, size_t len, const char *UNUSED(str)) -{ - void *newp = NULL; - - if (vmemh) { - size_t vmemh_len = MEM_allocN_len(vmemh); - newp = malloc(len); - if (newp) { - if (len < vmemh_len) { - /* shrink */ - memcpy(newp, vmemh, len); - } - else { - memcpy(newp, vmemh, vmemh_len); - - if (len > vmemh_len) { - /* grow */ - /* zero new bytes */ - memset(((char *)newp) + vmemh_len, 0, len - vmemh_len); - } - } - } - - free(vmemh); - } - else { - newp = calloc(1, len); - } - - return newp; -} - -void *MEM_callocN(size_t len, const char *UNUSED(str)) -{ - return calloc(1, len); -} - -void *MEM_mallocN(size_t len, const char *UNUSED(str)) -{ - return malloc(len); -} - -void *MEM_mapallocN(size_t len, const char *UNUSED(str)) -{ - /* could us mmap */ - return calloc(1, len); -} - -void MEM_printmemlist_pydict(void) {} -void MEM_printmemlist(void) {} - -/* unused */ -void MEM_callbackmemlist(void (*func)(void *)) -{ - (void)func; -} - -void MEM_printmemlist_stats(void) {} - -void MEM_set_error_callback(void (*func)(const char *)) -{ - (void)func; -} - -int MEM_check_memory_integrity(void) -{ - return 1; -} - -void MEM_set_lock_callback(void (*lock)(void), void (*unlock)(void)) -{ - (void)lock; - (void)unlock; -} - -void MEM_set_memory_debug(void) {} - -uintptr_t MEM_get_memory_in_use(void) -{ - struct mallinfo mi; - mi = mallinfo(); - return mi.uordblks; -} - -uintptr_t MEM_get_mapped_memory_in_use(void) -{ - return MEM_get_memory_in_use(); -} - -int MEM_get_memory_blocks_in_use(void) -{ - struct mallinfo mi; - mi = mallinfo(); - return mi.smblks + mi.hblks; -} - -/* dummy */ -void MEM_reset_peak_memory(void) {} - -uintptr_t MEM_get_peak_memory(void) -{ - return MEM_get_memory_in_use(); -} - -#endif /* WITH_GUARDEDALLOC */ diff --git a/intern/guardedalloc/intern/mallocn_guarded_impl.c b/intern/guardedalloc/intern/mallocn_guarded_impl.c new file mode 100644 index 00000000000..92392ce4dd3 --- /dev/null +++ b/intern/guardedalloc/intern/mallocn_guarded_impl.c @@ -0,0 +1,1114 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): Brecht Van Lommel + * Campbell Barton + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file guardedalloc/intern/mallocn.c + * \ingroup MEM + * + * Guarded memory allocation, and boundary-write detection. + */ + +#include +#include /* memcpy */ +#include +#include + +#include "MEM_guardedalloc.h" + +/* to ensure strict conversions */ +#include "../../source/blender/blenlib/BLI_strict_flags.h" + +#include "mallocn_intern.h" +#include "atomic_ops.h" + +/* Only for debugging: + * store original buffer's name when doing MEM_dupallocN + * helpful to profile issues with non-freed "dup_alloc" buffers, + * but this introduces some overhead to memory header and makes + * things slower a bit, so better to keep disabled by default + */ +//#define DEBUG_MEMDUPLINAME + +/* Only for debugging: + * lets you count the allocations so as to find the allocator of unfreed memory + * in situations where the leak is predictable */ + +//#define DEBUG_MEMCOUNTER + +/* Only for debugging: + * defining DEBUG_THREADS will enable check whether memory manager + * is locked with a mutex when allocation is called from non-main + * thread. + * + * This helps troubleshooting memory issues caused by the fact + * guarded allocator is not thread-safe, however this check will + * fail to check allocations from openmp threads. + */ +//#define DEBUG_THREADS + +/* Only for debugging: + * Defining DEBUG_BACKTRACE will store a backtrace from where + * memory block was allocated and print this trace for all + * unfreed blocks. + */ +//#define DEBUG_BACKTRACE + +#ifdef DEBUG_BACKTRACE +# define BACKTRACE_SIZE 100 +#endif + +#ifdef DEBUG_MEMCOUNTER + /* set this to the value that isn't being freed */ +# define DEBUG_MEMCOUNTER_ERROR_VAL 0 +static int _mallocn_count = 0; + +/* breakpoint here */ +static void memcount_raise(const char *name) +{ + fprintf(stderr, "%s: memcount-leak, %d\n", name, _mallocn_count); +} +#endif + +/* --------------------------------------------------------------------- */ +/* Data definition */ +/* --------------------------------------------------------------------- */ +/* all memory chunks are put in linked lists */ +typedef struct localLink { + struct localLink *next, *prev; +} localLink; + +typedef struct localListBase { + void *first, *last; +} localListBase; + +/* note: keep this struct aligned (e.g., irix/gcc) - Hos */ +typedef struct MemHead { + int tag1; + size_t len; + struct MemHead *next, *prev; + const char *name; + const char *nextname; + int tag2; + int mmap; /* if true, memory was mmapped */ +#ifdef DEBUG_MEMCOUNTER + int _count; +#endif + +#ifdef DEBUG_MEMDUPLINAME + int need_free_name, pad; +#endif + +#ifdef DEBUG_BACKTRACE + void *backtrace[BACKTRACE_SIZE]; + int backtrace_size; +#endif +} MemHead; + +/* for openmp threading asserts, saves time troubleshooting + * we may need to extend this if blender code starts using MEM_ + * functions inside OpenMP correctly with omp_set_lock() */ + +#if 0 /* disable for now, only use to debug openmp code which doesn lock threads for malloc */ +#if defined(_OPENMP) && defined(DEBUG) +# include +# include +# define DEBUG_OMP_MALLOC +#endif +#endif + +#ifdef DEBUG_THREADS +# include +# include +static pthread_t mainid; +#endif + +#ifdef DEBUG_BACKTRACE +# if defined(__linux__) || defined(__APPLE__) +# include +// Windows is not supported yet. +//# elif defined(_MSV_VER) +//# include +# endif +#endif + +typedef struct MemTail { + int tag3, pad; +} MemTail; + + +/* --------------------------------------------------------------------- */ +/* local functions */ +/* --------------------------------------------------------------------- */ + +static void addtail(volatile localListBase *listbase, void *vlink); +static void remlink(volatile localListBase *listbase, void *vlink); +static void rem_memblock(MemHead *memh); +static void MemorY_ErroR(const char *block, const char *error); +static const char *check_memlist(MemHead *memh); + +/* --------------------------------------------------------------------- */ +/* locally used defines */ +/* --------------------------------------------------------------------- */ + +#ifdef __BIG_ENDIAN__ +# define MAKE_ID(a, b, c, d) ((int)(a) << 24 | (int)(b) << 16 | (c) << 8 | (d)) +#else +# define MAKE_ID(a, b, c, d) ((int)(d) << 24 | (int)(c) << 16 | (b) << 8 | (a)) +#endif + +#define MEMTAG1 MAKE_ID('M', 'E', 'M', 'O') +#define MEMTAG2 MAKE_ID('R', 'Y', 'B', 'L') +#define MEMTAG3 MAKE_ID('O', 'C', 'K', '!') +#define MEMFREE MAKE_ID('F', 'R', 'E', 'E') + +#define MEMNEXT(x) \ + ((MemHead *)(((char *) x) - ((char *) &(((MemHead *)0)->next)))) + +/* --------------------------------------------------------------------- */ +/* vars */ +/* --------------------------------------------------------------------- */ + + +static unsigned int totblock = 0; +static size_t mem_in_use = 0, mmap_in_use = 0, peak_mem = 0; + +static volatile struct localListBase _membase; +static volatile struct localListBase *membase = &_membase; +static void (*error_callback)(const char *) = NULL; +static void (*thread_lock_callback)(void) = NULL; +static void (*thread_unlock_callback)(void) = NULL; + +static bool malloc_debug_memset = false; + +#ifdef malloc +#undef malloc +#endif + +#ifdef calloc +#undef calloc +#endif + +#ifdef free +#undef free +#endif + + +/* --------------------------------------------------------------------- */ +/* implementation */ +/* --------------------------------------------------------------------- */ + +#ifdef __GNUC__ +__attribute__ ((format(printf, 1, 2))) +#endif +static void print_error(const char *str, ...) +{ + char buf[512]; + va_list ap; + + va_start(ap, str); + vsnprintf(buf, sizeof(buf), str, ap); + va_end(ap); + buf[sizeof(buf) - 1] = '\0'; + + if (error_callback) error_callback(buf); +} + +static void mem_lock_thread(void) +{ +#ifdef DEBUG_THREADS + static int initialized = 0; + + if (initialized == 0) { + /* assume first allocation happens from main thread */ + mainid = pthread_self(); + initialized = 1; + } + + if (!pthread_equal(pthread_self(), mainid) && thread_lock_callback == NULL) { + assert(!"Memory function is called from non-main thread without lock"); + } +#endif + +#ifdef DEBUG_OMP_MALLOC + assert(omp_in_parallel() == 0); +#endif + + if (thread_lock_callback) + thread_lock_callback(); +} + +static void mem_unlock_thread(void) +{ +#ifdef DEBUG_THREADS + if (!pthread_equal(pthread_self(), mainid) && thread_lock_callback == NULL) { + assert(!"Thread lock was removed while allocation from thread is in progress"); + } +#endif + + if (thread_unlock_callback) + thread_unlock_callback(); +} + +bool MEM_guarded_check_memory_integrity(void) +{ + const char *err_val = NULL; + MemHead *listend; + /* check_memlist starts from the front, and runs until it finds + * the requested chunk. For this test, that's the last one. */ + listend = membase->last; + + err_val = check_memlist(listend); + + return (err_val != NULL); +} + + +void MEM_guarded_set_error_callback(void (*func)(const char *)) +{ + error_callback = func; +} + +void MEM_guarded_set_lock_callback(void (*lock)(void), void (*unlock)(void)) +{ + thread_lock_callback = lock; + thread_unlock_callback = unlock; +} + +void MEM_guarded_set_memory_debug(void) +{ + malloc_debug_memset = true; +} + +size_t MEM_guarded_allocN_len(const void *vmemh) +{ + if (vmemh) { + const MemHead *memh = vmemh; + + memh--; + return memh->len; + } + else { + return 0; + } +} + +void *MEM_guarded_dupallocN(const void *vmemh) +{ + void *newp = NULL; + + if (vmemh) { + const MemHead *memh = vmemh; + memh--; + +#ifndef DEBUG_MEMDUPLINAME + if (memh->mmap) + newp = MEM_guarded_mapallocN(memh->len, "dupli_mapalloc"); + else + newp = MEM_guarded_mallocN(memh->len, "dupli_alloc"); + + if (newp == NULL) return NULL; +#else + { + MemHead *nmemh; + char *name = malloc(strlen(memh->name) + 24); + + if (memh->mmap) { + sprintf(name, "%s %s", "dupli_mapalloc", memh->name); + newp = MEM_guarded_mapallocN(memh->len, name); + } + else { + sprintf(name, "%s %s", "dupli_alloc", memh->name); + newp = MEM_guarded_mallocN(memh->len, name); + } + + if (newp == NULL) return NULL; + + nmemh = newp; + nmemh--; + + nmemh->need_free_name = 1; + } +#endif + + memcpy(newp, vmemh, memh->len); + } + + return newp; +} + +void *MEM_guarded_reallocN_id(void *vmemh, size_t len, const char *str) +{ + void *newp = NULL; + + if (vmemh) { + MemHead *memh = vmemh; + memh--; + + newp = MEM_guarded_mallocN(len, memh->name); + if (newp) { + if (len < memh->len) { + /* shrink */ + memcpy(newp, vmemh, len); + } + else { + /* grow (or remain same size) */ + memcpy(newp, vmemh, memh->len); + } + } + + MEM_guarded_freeN(vmemh); + } + else { + newp = MEM_guarded_mallocN(len, str); + } + + return newp; +} + +void *MEM_guarded_recallocN_id(void *vmemh, size_t len, const char *str) +{ + void *newp = NULL; + + if (vmemh) { + MemHead *memh = vmemh; + memh--; + + newp = MEM_guarded_mallocN(len, memh->name); + if (newp) { + if (len < memh->len) { + /* shrink */ + memcpy(newp, vmemh, len); + } + else { + memcpy(newp, vmemh, memh->len); + + if (len > memh->len) { + /* grow */ + /* zero new bytes */ + memset(((char *)newp) + memh->len, 0, len - memh->len); + } + } + } + + MEM_guarded_freeN(vmemh); + } + else { + newp = MEM_guarded_callocN(len, str); + } + + return newp; +} + +#ifdef DEBUG_BACKTRACE +# if defined(__linux__) || defined(__APPLE__) +static void make_memhead_backtrace(MemHead *memh) +{ + memh->backtrace_size = backtrace(memh->backtrace, BACKTRACE_SIZE); +} + +static void print_memhead_backtrace(MemHead *memh) +{ + char **strings; + int i; + + strings = backtrace_symbols(memh->backtrace, memh->backtrace_size); + for (i = 0; i < memh->backtrace_size; i++) { + print_error(" %s\n", strings[i]); + } + + free(strings); +} +# else +static void make_memhead_backtrace(MemHead *memh) +{ + (void) memh; /* Ignored. */ +} + +static void print_memhead_backtrace(MemHead *memh) +{ + (void) memh; /* Ignored. */ +} +# endif /* defined(__linux__) || defined(__APPLE__) */ +#endif /* DEBUG_BACKTRACE */ + +static void make_memhead_header(MemHead *memh, size_t len, const char *str) +{ + MemTail *memt; + + memh->tag1 = MEMTAG1; + memh->name = str; + memh->nextname = NULL; + memh->len = len; + memh->mmap = 0; + memh->tag2 = MEMTAG2; + +#ifdef DEBUG_MEMDUPLINAME + memh->need_free_name = 0; +#endif + +#ifdef DEBUG_BACKTRACE + make_memhead_backtrace(memh); +#endif + + memt = (MemTail *)(((char *) memh) + sizeof(MemHead) + len); + memt->tag3 = MEMTAG3; + + atomic_add_u(&totblock, 1); + atomic_add_z(&mem_in_use, len); + + mem_lock_thread(); + addtail(membase, &memh->next); + if (memh->next) { + memh->nextname = MEMNEXT(memh->next)->name; + } + peak_mem = mem_in_use > peak_mem ? mem_in_use : peak_mem; + mem_unlock_thread(); +} + +void *MEM_guarded_mallocN(size_t len, const char *str) +{ + MemHead *memh; + + len = SIZET_ALIGN_4(len); + + memh = (MemHead *)malloc(len + sizeof(MemHead) + sizeof(MemTail)); + + if (memh) { + make_memhead_header(memh, len, str); + if (malloc_debug_memset && len) + memset(memh + 1, 255, len); + +#ifdef DEBUG_MEMCOUNTER + if (_mallocn_count == DEBUG_MEMCOUNTER_ERROR_VAL) + memcount_raise(__func__); + memh->_count = _mallocn_count++; +#endif + return (++memh); + } + print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total %u\n", + SIZET_ARG(len), str, (unsigned int) mem_in_use); + return NULL; +} + +void *MEM_guarded_callocN(size_t len, const char *str) +{ + MemHead *memh; + + len = SIZET_ALIGN_4(len); + + memh = (MemHead *)calloc(len + sizeof(MemHead) + sizeof(MemTail), 1); + + if (memh) { + make_memhead_header(memh, len, str); +#ifdef DEBUG_MEMCOUNTER + if (_mallocn_count == DEBUG_MEMCOUNTER_ERROR_VAL) + memcount_raise(__func__); + memh->_count = _mallocn_count++; +#endif + return (++memh); + } + print_error("Calloc returns null: len=" SIZET_FORMAT " in %s, total %u\n", + SIZET_ARG(len), str, (unsigned int) mem_in_use); + return NULL; +} + +/* note; mmap returns zero'd memory */ +void *MEM_guarded_mapallocN(size_t len, const char *str) +{ + MemHead *memh; + + len = SIZET_ALIGN_4(len); + +#if defined(WIN32) + /* our windows mmap implementation is not thread safe */ + mem_lock_thread(); +#endif + memh = mmap(NULL, len + sizeof(MemHead) + sizeof(MemTail), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); +#if defined(WIN32) + mem_unlock_thread(); +#endif + + if (memh != (MemHead *)-1) { + make_memhead_header(memh, len, str); + memh->mmap = 1; + atomic_add_z(&mmap_in_use, len); + mem_lock_thread(); + peak_mem = mmap_in_use > peak_mem ? mmap_in_use : peak_mem; + mem_unlock_thread(); +#ifdef DEBUG_MEMCOUNTER + if (_mallocn_count == DEBUG_MEMCOUNTER_ERROR_VAL) + memcount_raise(__func__); + memh->_count = _mallocn_count++; +#endif + return (++memh); + } + else { + print_error("Mapalloc returns null, fallback to regular malloc: " + "len=" SIZET_FORMAT " in %s, total %u\n", + SIZET_ARG(len), str, (unsigned int) mmap_in_use); + return MEM_guarded_callocN(len, str); + } +} + +/* Memory statistics print */ +typedef struct MemPrintBlock { + const char *name; + uintptr_t len; + int items; +} MemPrintBlock; + +static int compare_name(const void *p1, const void *p2) +{ + const MemPrintBlock *pb1 = (const MemPrintBlock *)p1; + const MemPrintBlock *pb2 = (const MemPrintBlock *)p2; + + return strcmp(pb1->name, pb2->name); +} + +static int compare_len(const void *p1, const void *p2) +{ + const MemPrintBlock *pb1 = (const MemPrintBlock *)p1; + const MemPrintBlock *pb2 = (const MemPrintBlock *)p2; + + if (pb1->len < pb2->len) + return 1; + else if (pb1->len == pb2->len) + return 0; + else + return -1; +} + +void MEM_guarded_printmemlist_stats(void) +{ + MemHead *membl; + MemPrintBlock *pb, *printblock; + unsigned int totpb, a, b; + size_t mem_in_use_slop = 0; + + mem_lock_thread(); + + /* put memory blocks into array */ + printblock = malloc(sizeof(MemPrintBlock) * totblock); + + pb = printblock; + totpb = 0; + + membl = membase->first; + if (membl) membl = MEMNEXT(membl); + + while (membl) { + pb->name = membl->name; + pb->len = membl->len; + pb->items = 1; + + totpb++; + pb++; + + if (!membl->mmap) { + mem_in_use_slop += (sizeof(MemHead) + sizeof(MemTail) + + malloc_usable_size((void *)membl)) - membl->len; + } + + if (membl->next) + membl = MEMNEXT(membl->next); + else break; + } + + /* sort by name and add together blocks with the same name */ + qsort(printblock, totpb, sizeof(MemPrintBlock), compare_name); + for (a = 0, b = 0; a < totpb; a++) { + if (a == b) { + continue; + } + else if (strcmp(printblock[a].name, printblock[b].name) == 0) { + printblock[b].len += printblock[a].len; + printblock[b].items++; + } + else { + b++; + memcpy(&printblock[b], &printblock[a], sizeof(MemPrintBlock)); + } + } + totpb = b + 1; + + /* sort by length and print */ + qsort(printblock, totpb, sizeof(MemPrintBlock), compare_len); + printf("\ntotal memory len: %.3f MB\n", + (double)mem_in_use / (double)(1024 * 1024)); + printf("peak memory len: %.3f MB\n", + (double)peak_mem / (double)(1024 * 1024)); + printf("slop memory len: %.3f MB\n", + (double)mem_in_use_slop / (double)(1024 * 1024)); + printf(" ITEMS TOTAL-MiB AVERAGE-KiB TYPE\n"); + for (a = 0, pb = printblock; a < totpb; a++, pb++) { + printf("%6d (%8.3f %8.3f) %s\n", + pb->items, (double)pb->len / (double)(1024 * 1024), + (double)pb->len / 1024.0 / (double)pb->items, pb->name); + } + free(printblock); + + mem_unlock_thread(); + +#ifdef HAVE_MALLOC_STATS + printf("System Statistics:\n"); + malloc_stats(); +#endif +} + +static const char mem_printmemlist_pydict_script[] = +"mb_userinfo = {}\n" +"totmem = 0\n" +"for mb_item in membase:\n" +" mb_item_user_size = mb_userinfo.setdefault(mb_item['name'], [0,0])\n" +" mb_item_user_size[0] += 1 # Add a user\n" +" mb_item_user_size[1] += mb_item['len'] # Increment the size\n" +" totmem += mb_item['len']\n" +"print('(membase) items:', len(membase), '| unique-names:',\n" +" len(mb_userinfo), '| total-mem:', totmem)\n" +"mb_userinfo_sort = list(mb_userinfo.items())\n" +"for sort_name, sort_func in (('size', lambda a: -a[1][1]),\n" +" ('users', lambda a: -a[1][0]),\n" +" ('name', lambda a: a[0])):\n" +" print('\\nSorting by:', sort_name)\n" +" mb_userinfo_sort.sort(key = sort_func)\n" +" for item in mb_userinfo_sort:\n" +" print('name:%%s, users:%%i, len:%%i' %%\n" +" (item[0], item[1][0], item[1][1]))\n"; + +/* Prints in python syntax for easy */ +static void MEM_guarded_printmemlist_internal(int pydict) +{ + MemHead *membl; + + mem_lock_thread(); + + membl = membase->first; + if (membl) membl = MEMNEXT(membl); + + if (pydict) { + print_error("# membase_debug.py\n"); + print_error("membase = [\n"); + } + while (membl) { + if (pydict) { + fprintf(stderr, + " {'len':" SIZET_FORMAT ", " + "'name':'''%s''', " + "'pointer':'%p'},\n", + SIZET_ARG(membl->len), membl->name, (void *)(membl + 1)); + } + else { +#ifdef DEBUG_MEMCOUNTER + print_error("%s len: " SIZET_FORMAT " %p, count: %d\n", + membl->name, SIZET_ARG(membl->len), membl + 1, + membl->_count); +#else + print_error("%s len: " SIZET_FORMAT " %p\n", + membl->name, SIZET_ARG(membl->len), membl + 1); +#endif +#ifdef DEBUG_BACKTRACE + print_memhead_backtrace(membl); +#endif + } + if (membl->next) + membl = MEMNEXT(membl->next); + else break; + } + if (pydict) { + fprintf(stderr, "]\n\n"); + fprintf(stderr, mem_printmemlist_pydict_script); + } + + mem_unlock_thread(); +} + +void MEM_guarded_callbackmemlist(void (*func)(void *)) +{ + MemHead *membl; + + mem_lock_thread(); + + membl = membase->first; + if (membl) membl = MEMNEXT(membl); + + while (membl) { + func(membl + 1); + if (membl->next) + membl = MEMNEXT(membl->next); + else break; + } + + mem_unlock_thread(); +} + +#if 0 +short MEM_guarded_testN(void *vmemh) +{ + MemHead *membl; + + mem_lock_thread(); + + membl = membase->first; + if (membl) membl = MEMNEXT(membl); + + while (membl) { + if (vmemh == membl + 1) { + mem_unlock_thread(); + return 1; + } + + if (membl->next) + membl = MEMNEXT(membl->next); + else break; + } + + mem_unlock_thread(); + + print_error("Memoryblock %p: pointer not in memlist\n", vmemh); + return 0; +} +#endif + +void MEM_guarded_printmemlist(void) +{ + MEM_guarded_printmemlist_internal(0); +} +void MEM_guarded_printmemlist_pydict(void) +{ + MEM_guarded_printmemlist_internal(1); +} + +void MEM_guarded_freeN(void *vmemh) +{ + MemTail *memt; + MemHead *memh = vmemh; + const char *name; + + if (memh == NULL) { + MemorY_ErroR("free", "attempt to free NULL pointer"); + /* print_error(err_stream, "%d\n", (memh+4000)->tag1); */ + return; + } + + if (sizeof(intptr_t) == 8) { + if (((intptr_t) memh) & 0x7) { + MemorY_ErroR("free", "attempt to free illegal pointer"); + return; + } + } + else { + if (((intptr_t) memh) & 0x3) { + MemorY_ErroR("free", "attempt to free illegal pointer"); + return; + } + } + + memh--; + if (memh->tag1 == MEMFREE && memh->tag2 == MEMFREE) { + MemorY_ErroR(memh->name, "double free"); + return; + } + + if ((memh->tag1 == MEMTAG1) && + (memh->tag2 == MEMTAG2) && + ((memh->len & 0x3) == 0)) + { + memt = (MemTail *)(((char *) memh) + sizeof(MemHead) + memh->len); + if (memt->tag3 == MEMTAG3) { + + memh->tag1 = MEMFREE; + memh->tag2 = MEMFREE; + memt->tag3 = MEMFREE; + /* after tags !!! */ + rem_memblock(memh); + + return; + } + MemorY_ErroR(memh->name, "end corrupt"); + name = check_memlist(memh); + if (name != NULL) { + if (name != memh->name) MemorY_ErroR(name, "is also corrupt"); + } + } + else { + mem_lock_thread(); + name = check_memlist(memh); + mem_unlock_thread(); + if (name == NULL) + MemorY_ErroR("free", "pointer not in memlist"); + else + MemorY_ErroR(name, "error in header"); + } + + totblock--; + /* here a DUMP should happen */ + + return; +} + +/* --------------------------------------------------------------------- */ +/* local functions */ +/* --------------------------------------------------------------------- */ + +static void addtail(volatile localListBase *listbase, void *vlink) +{ + struct localLink *link = vlink; + + /* for a generic API error checks here is fine but + * the limited use here they will never be NULL */ +#if 0 + if (link == NULL) return; + if (listbase == NULL) return; +#endif + + link->next = NULL; + link->prev = listbase->last; + + if (listbase->last) ((struct localLink *)listbase->last)->next = link; + if (listbase->first == NULL) listbase->first = link; + listbase->last = link; +} + +static void remlink(volatile localListBase *listbase, void *vlink) +{ + struct localLink *link = vlink; + + /* for a generic API error checks here is fine but + * the limited use here they will never be NULL */ +#if 0 + if (link == NULL) return; + if (listbase == NULL) return; +#endif + + if (link->next) link->next->prev = link->prev; + if (link->prev) link->prev->next = link->next; + + if (listbase->last == link) listbase->last = link->prev; + if (listbase->first == link) listbase->first = link->next; +} + +static void rem_memblock(MemHead *memh) +{ + mem_lock_thread(); + remlink(membase, &memh->next); + if (memh->prev) { + if (memh->next) + MEMNEXT(memh->prev)->nextname = MEMNEXT(memh->next)->name; + else + MEMNEXT(memh->prev)->nextname = NULL; + } + mem_unlock_thread(); + + atomic_sub_u(&totblock, 1); + atomic_sub_z(&mem_in_use, memh->len); + +#ifdef DEBUG_MEMDUPLINAME + if (memh->need_free_name) + free((char *) memh->name); +#endif + + if (memh->mmap) { + atomic_sub_z(&mmap_in_use, memh->len); +#if defined(WIN32) + /* our windows mmap implementation is not thread safe */ + mem_lock_thread(); +#endif + if (munmap(memh, memh->len + sizeof(MemHead) + sizeof(MemTail))) + printf("Couldn't unmap memory %s\n", memh->name); +#if defined(WIN32) + mem_unlock_thread(); +#endif + } + else { + if (malloc_debug_memset && memh->len) + memset(memh + 1, 255, memh->len); + free(memh); + } +} + +static void MemorY_ErroR(const char *block, const char *error) +{ + print_error("Memoryblock %s: %s\n", block, error); + +#ifdef WITH_ASSERT_ABORT + abort(); +#endif +} + +static const char *check_memlist(MemHead *memh) +{ + MemHead *forw, *back, *forwok, *backok; + const char *name; + + forw = membase->first; + if (forw) forw = MEMNEXT(forw); + forwok = NULL; + while (forw) { + if (forw->tag1 != MEMTAG1 || forw->tag2 != MEMTAG2) break; + forwok = forw; + if (forw->next) forw = MEMNEXT(forw->next); + else forw = NULL; + } + + back = (MemHead *) membase->last; + if (back) back = MEMNEXT(back); + backok = NULL; + while (back) { + if (back->tag1 != MEMTAG1 || back->tag2 != MEMTAG2) break; + backok = back; + if (back->prev) back = MEMNEXT(back->prev); + else back = NULL; + } + + if (forw != back) return ("MORE THAN 1 MEMORYBLOCK CORRUPT"); + + if (forw == NULL && back == NULL) { + /* no wrong headers found then but in search of memblock */ + + forw = membase->first; + if (forw) forw = MEMNEXT(forw); + forwok = NULL; + while (forw) { + if (forw == memh) break; + if (forw->tag1 != MEMTAG1 || forw->tag2 != MEMTAG2) break; + forwok = forw; + if (forw->next) forw = MEMNEXT(forw->next); + else forw = NULL; + } + if (forw == NULL) return NULL; + + back = (MemHead *) membase->last; + if (back) back = MEMNEXT(back); + backok = NULL; + while (back) { + if (back == memh) break; + if (back->tag1 != MEMTAG1 || back->tag2 != MEMTAG2) break; + backok = back; + if (back->prev) back = MEMNEXT(back->prev); + else back = NULL; + } + } + + if (forwok) name = forwok->nextname; + else name = "No name found"; + + if (forw == memh) { + /* to be sure but this block is removed from the list */ + if (forwok) { + if (backok) { + forwok->next = (MemHead *)&backok->next; + backok->prev = (MemHead *)&forwok->next; + forwok->nextname = backok->name; + } + else { + forwok->next = NULL; + membase->last = (struct localLink *) &forwok->next; + } + } + else { + if (backok) { + backok->prev = NULL; + membase->first = &backok->next; + } + else { + membase->first = membase->last = NULL; + } + } + } + else { + MemorY_ErroR(name, "Additional error in header"); + return("Additional error in header"); + } + + return(name); +} + +size_t MEM_guarded_get_peak_memory(void) +{ + size_t _peak_mem; + + mem_lock_thread(); + _peak_mem = peak_mem; + mem_unlock_thread(); + + return _peak_mem; +} + +void MEM_guarded_reset_peak_memory(void) +{ + mem_lock_thread(); + peak_mem = 0; + mem_unlock_thread(); +} + +uintptr_t MEM_guarded_get_memory_in_use(void) +{ + uintptr_t _mem_in_use; + + mem_lock_thread(); + _mem_in_use = mem_in_use; + mem_unlock_thread(); + + return _mem_in_use; +} + +uintptr_t MEM_guarded_get_mapped_memory_in_use(void) +{ + uintptr_t _mmap_in_use; + + mem_lock_thread(); + _mmap_in_use = mmap_in_use; + mem_unlock_thread(); + + return _mmap_in_use; +} + +unsigned int MEM_guarded_get_memory_blocks_in_use(void) +{ + unsigned int _totblock; + + mem_lock_thread(); + _totblock = totblock; + mem_unlock_thread(); + + return _totblock; +} + +#ifndef NDEBUG +const char *MEM_guarded_name_ptr(void *vmemh) +{ + if (vmemh) { + MemHead *memh = vmemh; + memh--; + return memh->name; + } + else { + return "MEM_guarded_name_ptr(NULL)"; + } +} +#endif /* NDEBUG */ diff --git a/intern/guardedalloc/intern/mallocn_intern.h b/intern/guardedalloc/intern/mallocn_intern.h new file mode 100644 index 00000000000..cf77ce60e0a --- /dev/null +++ b/intern/guardedalloc/intern/mallocn_intern.h @@ -0,0 +1,129 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2013 by Blender Foundation. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): Sergey Sharybin + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file guardedalloc/intern/mallocn_intern.h + * \ingroup MEM + */ + +#ifndef __MALLOCN_INTERN_H__ +#define __MALLOCN_INTERN_H__ + +/* mmap exception */ +#if defined(WIN32) +# include "mmap_win.h" +#else +# include +#endif + +#if defined(_MSC_VER) +# define __func__ __FUNCTION__ +#endif + +#ifdef __GNUC__ +# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) +#else +# define UNUSED(x) UNUSED_ ## x +#endif + +#undef HAVE_MALLOC_STATS + +#if defined(__linux__) +# include +# define HAVE_MALLOC_STATS +#elif defined(__APPLE__) +# define malloc_usable_size malloc_size +#elif defined(WIN32) +# include +# define malloc_usable_size _msize +#else +# error "We don't know how to use malloc_usable_size on your platform" +#endif + +/* Blame Microsoft for LLP64 and no inttypes.h, quick workaround needed: */ +#if defined(WIN64) +# define SIZET_FORMAT "%I64u" +# define SIZET_ARG(a) ((unsigned long long)(a)) +#else +# define SIZET_FORMAT "%lu" +# define SIZET_ARG(a) ((unsigned long)(a)) +#endif + +#define SIZET_ALIGN_4(len) ((len + 3) & ~(size_t)3) + +/* Prototypes for counted allocator functions */ +size_t MEM_lockfree_allocN_len(const void *vmemh) ATTR_WARN_UNUSED_RESULT; +void MEM_lockfree_freeN(void *vmemh); +void *MEM_lockfree_dupallocN(const void *vmemh) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT; +void *MEM_lockfree_reallocN_id(void *vmemh, size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(2); +void *MEM_lockfree_recallocN_id(void *vmemh, size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(2); +void *MEM_lockfree_callocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); +void *MEM_lockfree_mallocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); +void *MEM_lockfree_mapallocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); +void MEM_lockfree_printmemlist_pydict(void); +void MEM_lockfree_printmemlist(void); +void MEM_lockfree_callbackmemlist(void (*func)(void *)); +void MEM_lockfree_printmemlist_stats(void); +void MEM_lockfree_set_error_callback(void (*func)(const char *)); +bool MEM_lockfree_check_memory_integrity(void); +void MEM_lockfree_set_lock_callback(void (*lock)(void), void (*unlock)(void)); +void MEM_lockfree_set_memory_debug(void); +uintptr_t MEM_lockfree_get_memory_in_use(void); +uintptr_t MEM_lockfree_get_mapped_memory_in_use(void); +unsigned int MEM_lockfree_get_memory_blocks_in_use(void); +void MEM_lockfree_reset_peak_memory(void); +uintptr_t MEM_lockfree_get_peak_memory(void) ATTR_WARN_UNUSED_RESULT; +#ifndef NDEBUG +const char *MEM_lockfree_name_ptr(void *vmemh); +#endif + +/* Prototypes for fully guarded allocator functions */ +size_t MEM_guarded_allocN_len(const void *vmemh) ATTR_WARN_UNUSED_RESULT; +void MEM_guarded_freeN(void *vmemh); +void *MEM_guarded_dupallocN(const void *vmemh) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT; +void *MEM_guarded_reallocN_id(void *vmemh, size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(2); +void *MEM_guarded_recallocN_id(void *vmemh, size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(2); +void *MEM_guarded_callocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); +void *MEM_guarded_mallocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); +void *MEM_guarded_mapallocN(size_t len, const char *UNUSED(str)) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1) ATTR_NONNULL(2); +void MEM_guarded_printmemlist_pydict(void); +void MEM_guarded_printmemlist(void); +void MEM_guarded_callbackmemlist(void (*func)(void *)); +void MEM_guarded_printmemlist_stats(void); +void MEM_guarded_set_error_callback(void (*func)(const char *)); +bool MEM_guarded_check_memory_integrity(void); +void MEM_guarded_set_lock_callback(void (*lock)(void), void (*unlock)(void)); +void MEM_guarded_set_memory_debug(void); +uintptr_t MEM_guarded_get_memory_in_use(void); +uintptr_t MEM_guarded_get_mapped_memory_in_use(void); +unsigned int MEM_guarded_get_memory_blocks_in_use(void); +void MEM_guarded_reset_peak_memory(void); +uintptr_t MEM_guarded_get_peak_memory(void) ATTR_WARN_UNUSED_RESULT; +#ifndef NDEBUG +const char *MEM_guarded_name_ptr(void *vmemh); +#endif + +#endif /* __MALLOCN_INTERN_H__ */ diff --git a/intern/guardedalloc/intern/mallocn_lockfree_impl.c b/intern/guardedalloc/intern/mallocn_lockfree_impl.c new file mode 100644 index 00000000000..35caebcc5e8 --- /dev/null +++ b/intern/guardedalloc/intern/mallocn_lockfree_impl.c @@ -0,0 +1,376 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): Brecht Van Lommel + * Campbell Barton + * Sergey Sharybin + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file guardedalloc/intern/mallocn.c + * \ingroup MEM + * + * Memory allocation which keeps track on allocated memory counters + */ + +#include +#include /* memcpy */ +#include +#include + +#include "MEM_guardedalloc.h" + +/* to ensure strict conversions */ +#include "../../source/blender/blenlib/BLI_strict_flags.h" + +#include "atomic_ops.h" +#include "mallocn_intern.h" + +typedef struct MemHead { + /* Length of allocated memory block. */ + size_t len; +} MemHead; + +static unsigned int totblock = 0; +static size_t mem_in_use = 0, mmap_in_use = 0, peak_mem = 0; +static bool malloc_debug_memset = false; + +static void (*error_callback)(const char *) = NULL; +static void (*thread_lock_callback)(void) = NULL; +static void (*thread_unlock_callback)(void) = NULL; + +#define MEMHEAD_FROM_PTR(ptr) (((MemHead*) vmemh) - 1) +#define PTR_FROM_MEMHEAD(memhead) (memhead + 1) + +#ifdef __GNUC__ +__attribute__ ((format(printf, 1, 2))) +#endif +static void print_error(const char *str, ...) +{ + char buf[512]; + va_list ap; + + va_start(ap, str); + vsnprintf(buf, sizeof(buf), str, ap); + va_end(ap); + buf[sizeof(buf) - 1] = '\0'; + + if (error_callback) { + error_callback(buf); + } +} + +#if defined(WIN32) +static void mem_lock_thread(void) +{ + if (thread_lock_callback) + thread_lock_callback(); +} + +static void mem_unlock_thread(void) +{ + if (thread_unlock_callback) + thread_unlock_callback(); +} +#endif + +size_t MEM_lockfree_allocN_len(const void *vmemh) +{ + if (vmemh) { + return MEMHEAD_FROM_PTR(vmemh)->len & ~((size_t) 1); + } + else { + return 0; + } +} + +void MEM_lockfree_freeN(void *vmemh) +{ + MemHead *memh = MEMHEAD_FROM_PTR(vmemh); + size_t len = MEM_lockfree_allocN_len(vmemh); + + atomic_sub_u(&totblock, 1); + atomic_sub_z(&mem_in_use, len); + + if (memh->len & (size_t) 1) { + atomic_sub_z(&mmap_in_use, len); +#if defined(WIN32) + /* our windows mmap implementation is not thread safe */ + mem_lock_thread(); +#endif + if (munmap(memh, memh->len + sizeof(MemHead))) + printf("Couldn't unmap memory\n"); +#if defined(WIN32) + mem_unlock_thread(); +#endif + } + else { + if (malloc_debug_memset && len) { + memset(memh + 1, 255, len); + } + free(memh); + } +} + +void *MEM_lockfree_dupallocN(const void *vmemh) +{ + void *newp = NULL; + if (vmemh) { + const size_t prev_size = MEM_allocN_len(vmemh); + newp = MEM_lockfree_mallocN(prev_size, "dupli_malloc"); + memcpy(newp, vmemh, prev_size); + } + return newp; +} + +void *MEM_lockfree_reallocN_id(void *vmemh, size_t len, const char *str) +{ + void *newp = NULL; + + if (vmemh) { + size_t old_len = MEM_allocN_len(vmemh); + + newp = MEM_lockfree_mallocN(len, "realloc"); + if (newp) { + if (len < old_len) { + /* shrink */ + memcpy(newp, vmemh, len); + } + else { + /* grow (or remain same size) */ + memcpy(newp, vmemh, old_len); + } + } + + MEM_lockfree_freeN(vmemh); + } + else { + newp = MEM_lockfree_mallocN(len, str); + } + + return newp; +} + +void *MEM_lockfree_recallocN_id(void *vmemh, size_t len, const char *str) +{ + void *newp = NULL; + + if (vmemh) { + size_t old_len = MEM_allocN_len(vmemh); + + newp = MEM_lockfree_mallocN(len, "recalloc"); + if (newp) { + if (len < old_len) { + /* shrink */ + memcpy(newp, vmemh, len); + } + else { + memcpy(newp, vmemh, old_len); + + if (len > old_len) { + /* grow */ + /* zero new bytes */ + memset(((char *)newp) + old_len, 0, len - old_len); + } + } + } + + MEM_lockfree_freeN(vmemh); + } + else { + newp = MEM_lockfree_callocN(len, str); + } + + return newp; +} + +void *MEM_lockfree_callocN(size_t len, const char *str) +{ + MemHead *memh; + + len = SIZET_ALIGN_4(len); + + memh = (MemHead *)calloc(1, len + sizeof(MemHead)); + + if (memh) { + memh->len = len; + atomic_add_u(&totblock, 1); + atomic_add_z(&mem_in_use, len); + + /* TODO(sergey): Not strictly speaking thread-safe. */ + peak_mem = mem_in_use > peak_mem ? mem_in_use : peak_mem; + + return PTR_FROM_MEMHEAD(memh); + } + print_error("Calloc returns null: len=" SIZET_FORMAT " in %s, total %u\n", + SIZET_ARG(len), str, (unsigned int) mem_in_use); + return NULL; +} + +void *MEM_lockfree_mallocN(size_t len, const char *str) +{ + MemHead *memh; + + len = SIZET_ALIGN_4(len); + + memh = (MemHead *)malloc(len + sizeof(MemHead)); + + if (memh) { + if (malloc_debug_memset && len) { + memset(memh + 1, 255, len); + } + + memh->len = len; + atomic_add_u(&totblock, 1); + atomic_add_z(&mem_in_use, len); + + /* TODO(sergey): Not strictly speaking thread-safe. */ + peak_mem = mem_in_use > peak_mem ? mem_in_use : peak_mem; + + return PTR_FROM_MEMHEAD(memh); + } + print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total %u\n", + SIZET_ARG(len), str, (unsigned int) mem_in_use); + return NULL; +} + +void *MEM_lockfree_mapallocN(size_t len, const char *str) +{ + MemHead *memh; + + len = SIZET_ALIGN_4(len); + +#if defined(WIN32) + /* our windows mmap implementation is not thread safe */ + mem_lock_thread(); +#endif + memh = mmap(NULL, len + sizeof(MemHead), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); +#if defined(WIN32) + mem_unlock_thread(); +#endif + + if (memh != (MemHead *)-1) { + memh->len = len | (size_t) 1; + atomic_add_u(&totblock, 1); + atomic_add_z(&mem_in_use, len); + atomic_add_z(&mmap_in_use, len); + + /* TODO(sergey): Not strictly speaking thread-safe. */ + peak_mem = mem_in_use > peak_mem ? mem_in_use : peak_mem; + peak_mem = mmap_in_use > peak_mem ? mmap_in_use : peak_mem; + + return PTR_FROM_MEMHEAD(memh); + } + print_error("Mapalloc returns null, fallback to regular malloc: " + "len=" SIZET_FORMAT " in %s, total %u\n", + SIZET_ARG(len), str, (unsigned int) mmap_in_use); + return MEM_lockfree_callocN(len, str); +} + +void MEM_lockfree_printmemlist_pydict(void) +{ +} + +void MEM_lockfree_printmemlist(void) +{ +} + +/* unused */ +void MEM_lockfree_callbackmemlist(void (*func)(void *)) +{ + (void) func; /* Ignored. */ +} + +void MEM_lockfree_printmemlist_stats(void) +{ + printf("\ntotal memory len: %.3f MB\n", + (double)mem_in_use / (double)(1024 * 1024)); + printf("peak memory len: %.3f MB\n", + (double)peak_mem / (double)(1024 * 1024)); + printf("\nFor more detailed per-block statistics run Blender with memory debugging command line argument.\n"); + +#ifdef HAVE_MALLOC_STATS + printf("System Statistics:\n"); + malloc_stats(); +#endif +} + +void MEM_lockfree_set_error_callback(void (*func)(const char *)) +{ + error_callback = func; +} + +bool MEM_lockfree_check_memory_integrity(void) +{ + return true; +} + +void MEM_lockfree_set_lock_callback(void (*lock)(void), void (*unlock)(void)) +{ + thread_lock_callback = lock; + thread_unlock_callback = unlock; +} + +void MEM_lockfree_set_memory_debug(void) +{ + malloc_debug_memset = true; +} + +uintptr_t MEM_lockfree_get_memory_in_use(void) +{ + return mem_in_use; +} + +uintptr_t MEM_lockfree_get_mapped_memory_in_use(void) +{ + return mmap_in_use; +} + +unsigned int MEM_lockfree_get_memory_blocks_in_use(void) +{ + return totblock; +} + +/* dummy */ +void MEM_lockfree_reset_peak_memory(void) +{ + peak_mem = 0; +} + +uintptr_t MEM_lockfree_get_peak_memory(void) +{ + return peak_mem; +} + +#ifndef NDEBUG +const char *MEM_lockfree_name_ptr(void *vmemh) +{ + if (vmemh) { + return "unknown block name ptr"; + } + else { + return "MEM_lockfree_name_ptr(NULL)"; + } +} +#endif /* NDEBUG */ diff --git a/source/blender/makesdna/intern/CMakeLists.txt b/source/blender/makesdna/intern/CMakeLists.txt index cae607949a9..317141b14e3 100644 --- a/source/blender/makesdna/intern/CMakeLists.txt +++ b/source/blender/makesdna/intern/CMakeLists.txt @@ -40,6 +40,8 @@ blender_include_dirs( set(SRC makesdna.c ../../../../intern/guardedalloc/intern/mallocn.c + ../../../../intern/guardedalloc/intern/mallocn_guarded_impl.c + ../../../../intern/guardedalloc/intern/mallocn_lockfree_impl.c ) if(WIN32 AND NOT UNIX) @@ -50,11 +52,6 @@ endif() # SRC_DNA_INC is defined in the parent dir -# for mallocn.c -if (WITH_GUARDEDALLOC) - add_definitions(-DWITH_GUARDEDALLOC) -endif() - add_executable(makesdna ${SRC} ${SRC_DNA_INC}) # Output dna.c diff --git a/source/blender/makesdna/intern/SConscript b/source/blender/makesdna/intern/SConscript index 7c3af30ee83..35b4ff262ed 100644 --- a/source/blender/makesdna/intern/SConscript +++ b/source/blender/makesdna/intern/SConscript @@ -36,8 +36,6 @@ cflags = '' defines = [] root_build_dir=normpath(env['BF_BUILDDIR']) -defines.append('WITH_GUARDEDALLOC') - source_files = ['makesdna.c'] header_files = env.Glob('../*.h') diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index 9554941ec1f..3d710917e3d 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -137,6 +137,8 @@ set(SRC ${DEFSRC} ${APISRC} ../../../../intern/guardedalloc/intern/mallocn.c + ../../../../intern/guardedalloc/intern/mallocn_guarded_impl.c + ../../../../intern/guardedalloc/intern/mallocn_lockfree_impl.c ../../../../intern/guardedalloc/intern/mmap_win.c ) @@ -253,11 +255,6 @@ if(WITH_BULLET) add_definitions(-DWITH_BULLET) endif() -# for mallocn.c -if (WITH_GUARDEDALLOC) - add_definitions(-DWITH_GUARDEDALLOC) -endif() - # Build makesrna executable blender_include_dirs( . diff --git a/source/blender/makesrna/intern/SConscript b/source/blender/makesrna/intern/SConscript index 10b6a8c8d10..e1fad55f485 100644 --- a/source/blender/makesrna/intern/SConscript +++ b/source/blender/makesrna/intern/SConscript @@ -150,8 +150,6 @@ if env['WITH_BF_INTERNATIONAL']: if not env['BF_DEBUG']: defs.append('NDEBUG') -defs.append('WITH_GUARDEDALLOC') - makesrna_tool.Append(CPPDEFINES=defs) makesrna_tool.Append (CPPPATH = Split(incs)) diff --git a/source/creator/creator.c b/source/creator/creator.c index a3bce79b6e7..1596fe5359c 100644 --- a/source/creator/creator.c +++ b/source/creator/creator.c @@ -300,6 +300,7 @@ static int print_help(int UNUSED(argc), const char **UNUSED(argv), void *data) #ifdef WITH_LIBMV BLI_argsPrintArgDoc(ba, "--debug-libmv"); #endif + BLI_argsPrintArgDoc(ba, "--debug-memory"); BLI_argsPrintArgDoc(ba, "--debug-jobs"); BLI_argsPrintArgDoc(ba, "--debug-python"); @@ -439,6 +440,12 @@ static int debug_mode_libmv(int UNUSED(argc), const char **UNUSED(argv), void *U } #endif +static int debug_mode_memory(int UNUSED(argc), const char **UNUSED(argv), void *UNUSED(data)) +{ + MEM_set_memory_debug(); + return 0; +} + static int set_debug_value(int argc, const char **argv, void *UNUSED(data)) { if (argc > 1) { @@ -1384,6 +1391,7 @@ static void setupArguments(bContext *C, bArgs *ba, SYS_SystemHandle *syshandle) #ifdef WITH_LIBMV BLI_argsAdd(ba, 1, NULL, "--debug-libmv", "\n\tEnable debug messages from libmv library", debug_mode_libmv, NULL); #endif + BLI_argsAdd(ba, 1, NULL, "--debug-memory", "\n\tEnable fully guarded memory allocation and debugging", debug_mode_memory, NULL); BLI_argsAdd(ba, 1, NULL, "--debug-value", "\n\tSet debug value of on startup\n", set_debug_value, NULL); BLI_argsAdd(ba, 1, NULL, "--debug-jobs", "\n\tEnable time profiling for background jobs.", debug_mode_generic, (void *)G_DEBUG_JOBS); @@ -1454,23 +1462,50 @@ int main(int argc, const char **UNUSED(argv_c)) /* Do not mess with const */ int main(int argc, const char **argv) #endif { - bContext *C = CTX_create(); + bContext *C; SYS_SystemHandle syshandle; #ifndef WITH_PYTHON_MODULE bArgs *ba; #endif -#ifdef WIN32 +#ifdef WIN32 /* Win32 Unicode Args */ + /* NOTE: cannot use guardedalloc malloc here, as it's not yet initialised + * (it depends on the args passed in, which is what we're getting here!) + */ wchar_t **argv_16 = CommandLineToArgvW(GetCommandLineW(), &argc); + char **argv = malloc(argc * sizeof(char *)); int argci = 0; - char **argv = MEM_mallocN(argc * sizeof(char *), "argv array"); + for (argci = 0; argci < argc; argci++) { argv[argci] = alloc_utf_8_from_16(argv_16[argci], 0); } + LocalFree(argv_16); #endif + /* NOTE: Special exception for guarded allocator type switch: + * we need to perform switch from lock-free to fully + * guarded allocator before any allocation happened. + */ + { + int i; + for (i = 0; i < argc; i++) { + if (STREQ(argv[i], "--debug") || STREQ(argv[i], "-d") || + STREQ(argv[i], "--debug-memory")) + { + printf("Switching to fully guarded memory allocator.\n"); + MEM_use_guarded_allocator(); + break; + } + else if (STREQ(argv[i], "--")) { + break; + } + } + } + + C = CTX_create(); + #ifdef WITH_PYTHON_MODULE #ifdef __APPLE__ environ = *_NSGetEnviron(); @@ -1641,7 +1676,7 @@ int main(int argc, const char **argv) while (argci) { free(argv[--argci]); } - MEM_freeN(argv); + free(argv); argv = NULL; #endif